352 lines
9.3 KiB
C++
352 lines
9.3 KiB
C++
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "storaged"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/statvfs.h>
|
|
|
|
#include <numeric>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/strings.h>
|
|
#include <log/log_event_list.h>
|
|
|
|
#include "storaged.h"
|
|
#include "storaged_info.h"
|
|
|
|
using namespace std;
|
|
using namespace chrono;
|
|
using namespace android::base;
|
|
using namespace storaged_proto;
|
|
|
|
using aidl::android::hardware::health::IHealth;
|
|
using aidl::android::hardware::health::StorageInfo;
|
|
|
|
const string emmc_info_t::emmc_sysfs = "/sys/bus/mmc/devices/mmc0:0001/";
|
|
const char* emmc_info_t::emmc_ver_str[9] = {
|
|
"4.0", "4.1", "4.2", "4.3", "Obsolete", "4.41", "4.5", "5.0", "5.1"
|
|
};
|
|
|
|
const string ufs_info_t::health_file = "/sys/devices/soc/624000.ufshc/health";
|
|
|
|
namespace {
|
|
|
|
bool FileExists(const std::string& filename)
|
|
{
|
|
struct stat buffer;
|
|
return stat(filename.c_str(), &buffer) == 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
storage_info_t* storage_info_t::get_storage_info(const shared_ptr<IHealth>& healthService) {
|
|
if (healthService != nullptr) {
|
|
return new health_storage_info_t(healthService);
|
|
}
|
|
if (FileExists(emmc_info_t::emmc_sysfs)) return new emmc_info_t;
|
|
|
|
if (FileExists(ufs_info_t::health_file)) {
|
|
return new ufs_info_t;
|
|
}
|
|
return new storage_info_t;
|
|
}
|
|
|
|
void storage_info_t::load_perf_history_proto(const IOPerfHistory& perf_history)
|
|
{
|
|
Mutex::Autolock _l(si_mutex);
|
|
|
|
if (!perf_history.has_day_start_sec() ||
|
|
perf_history.daily_perf_size() > (int)daily_perf.size() ||
|
|
perf_history.weekly_perf_size() > (int)weekly_perf.size()) {
|
|
LOG(ERROR) << "Invalid IOPerfHistory proto";
|
|
return;
|
|
}
|
|
|
|
day_start_tp = {};
|
|
day_start_tp += chrono::seconds(perf_history.day_start_sec());
|
|
|
|
nr_samples = perf_history.nr_samples();
|
|
if (nr_samples < recent_perf.size()) {
|
|
recent_perf.erase(recent_perf.begin() + nr_samples, recent_perf.end());
|
|
}
|
|
size_t i = 0;
|
|
for (auto bw : perf_history.recent_perf()) {
|
|
if (i < recent_perf.size()) {
|
|
recent_perf[i] = bw;
|
|
} else {
|
|
recent_perf.push_back(bw);
|
|
}
|
|
++i;
|
|
}
|
|
|
|
nr_days = perf_history.nr_days();
|
|
i = 0;
|
|
for (auto bw : perf_history.daily_perf()) {
|
|
daily_perf[i++] = bw;
|
|
}
|
|
|
|
nr_weeks = perf_history.nr_weeks();
|
|
i = 0;
|
|
for (auto bw : perf_history.weekly_perf()) {
|
|
weekly_perf[i++] = bw;
|
|
}
|
|
}
|
|
|
|
void storage_info_t::refresh(IOPerfHistory* perf_history)
|
|
{
|
|
struct statvfs buf;
|
|
if (statvfs(userdata_path.c_str(), &buf) != 0) {
|
|
PLOG(WARNING) << "Failed to get userdata info";
|
|
return;
|
|
}
|
|
|
|
userdata_total_kb = buf.f_bsize * buf.f_blocks >> 10;
|
|
userdata_free_kb = buf.f_bfree * buf.f_blocks >> 10;
|
|
|
|
Mutex::Autolock _l(si_mutex);
|
|
|
|
perf_history->Clear();
|
|
perf_history->set_day_start_sec(
|
|
duration_cast<chrono::seconds>(day_start_tp.time_since_epoch()).count());
|
|
for (const uint32_t& bw : recent_perf) {
|
|
perf_history->add_recent_perf(bw);
|
|
}
|
|
perf_history->set_nr_samples(nr_samples);
|
|
for (const uint32_t& bw : daily_perf) {
|
|
perf_history->add_daily_perf(bw);
|
|
}
|
|
perf_history->set_nr_days(nr_days);
|
|
for (const uint32_t& bw : weekly_perf) {
|
|
perf_history->add_weekly_perf(bw);
|
|
}
|
|
perf_history->set_nr_weeks(nr_weeks);
|
|
}
|
|
|
|
void storage_info_t::publish()
|
|
{
|
|
android_log_event_list(EVENTLOGTAG_EMMCINFO)
|
|
<< version << eol << lifetime_a << lifetime_b
|
|
<< LOG_ID_EVENTS;
|
|
}
|
|
|
|
void storage_info_t::update_perf_history(uint32_t bw,
|
|
const time_point<system_clock>& tp)
|
|
{
|
|
Mutex::Autolock _l(si_mutex);
|
|
|
|
if (tp > day_start_tp &&
|
|
duration_cast<chrono::seconds>(tp - day_start_tp).count() < DAY_TO_SEC) {
|
|
if (nr_samples >= recent_perf.size()) {
|
|
recent_perf.push_back(bw);
|
|
} else {
|
|
recent_perf[nr_samples] = bw;
|
|
}
|
|
nr_samples++;
|
|
return;
|
|
}
|
|
|
|
if (nr_samples < recent_perf.size()) {
|
|
recent_perf.erase(recent_perf.begin() + nr_samples, recent_perf.end());
|
|
}
|
|
|
|
uint32_t daily_avg_bw = 0;
|
|
if (!recent_perf.empty()) {
|
|
daily_avg_bw = accumulate(recent_perf.begin(), recent_perf.end(), 0) / recent_perf.size();
|
|
}
|
|
|
|
day_start_tp = tp - chrono::seconds(duration_cast<chrono::seconds>(
|
|
tp.time_since_epoch()).count() % DAY_TO_SEC);
|
|
|
|
nr_samples = 0;
|
|
if (recent_perf.empty())
|
|
recent_perf.resize(1);
|
|
recent_perf[nr_samples++] = bw;
|
|
|
|
if (nr_days < WEEK_TO_DAYS) {
|
|
daily_perf[nr_days++] = daily_avg_bw;
|
|
return;
|
|
}
|
|
|
|
DCHECK(nr_days > 0);
|
|
uint32_t week_avg_bw = accumulate(daily_perf.begin(),
|
|
daily_perf.begin() + nr_days, 0) / nr_days;
|
|
|
|
nr_days = 0;
|
|
daily_perf[nr_days++] = daily_avg_bw;
|
|
|
|
if (nr_weeks >= YEAR_TO_WEEKS) {
|
|
nr_weeks = 0;
|
|
}
|
|
weekly_perf[nr_weeks++] = week_avg_bw;
|
|
}
|
|
|
|
vector<int> storage_info_t::get_perf_history()
|
|
{
|
|
Mutex::Autolock _l(si_mutex);
|
|
|
|
vector<int> ret(3 + recent_perf.size() + daily_perf.size() + weekly_perf.size());
|
|
|
|
ret[0] = recent_perf.size();
|
|
ret[1] = daily_perf.size();
|
|
ret[2] = weekly_perf.size();
|
|
|
|
int start = 3;
|
|
for (size_t i = 0; i < recent_perf.size(); i++) {
|
|
int idx = (recent_perf.size() + nr_samples - 1 - i) % recent_perf.size();
|
|
ret[start + i] = recent_perf[idx];
|
|
}
|
|
|
|
start += recent_perf.size();
|
|
for (size_t i = 0; i < daily_perf.size(); i++) {
|
|
int idx = (daily_perf.size() + nr_days - 1 - i) % daily_perf.size();
|
|
ret[start + i] = daily_perf[idx];
|
|
}
|
|
|
|
start += daily_perf.size();
|
|
for (size_t i = 0; i < weekly_perf.size(); i++) {
|
|
int idx = (weekly_perf.size() + nr_weeks - 1 - i) % weekly_perf.size();
|
|
ret[start + i] = weekly_perf[idx];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
uint32_t storage_info_t::get_recent_perf() {
|
|
Mutex::Autolock _l(si_mutex);
|
|
if (recent_perf.size() == 0) return 0;
|
|
return accumulate(recent_perf.begin(), recent_perf.end(), recent_perf.size() / 2) /
|
|
recent_perf.size();
|
|
}
|
|
|
|
void emmc_info_t::report()
|
|
{
|
|
if (!report_sysfs()) return;
|
|
|
|
publish();
|
|
}
|
|
|
|
bool emmc_info_t::report_sysfs()
|
|
{
|
|
string buffer;
|
|
uint16_t rev = 0;
|
|
|
|
if (!ReadFileToString(emmc_sysfs + "rev", &buffer)) {
|
|
return false;
|
|
}
|
|
|
|
if (sscanf(buffer.c_str(), "0x%hx", &rev) < 1 ||
|
|
rev < 7 || rev > ARRAY_SIZE(emmc_ver_str)) {
|
|
return false;
|
|
}
|
|
|
|
version = "emmc ";
|
|
version += emmc_ver_str[rev];
|
|
|
|
if (!ReadFileToString(emmc_sysfs + "pre_eol_info", &buffer)) {
|
|
return false;
|
|
}
|
|
|
|
if (sscanf(buffer.c_str(), "%hx", &eol) < 1 || eol == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (!ReadFileToString(emmc_sysfs + "life_time", &buffer)) {
|
|
return false;
|
|
}
|
|
|
|
if (sscanf(buffer.c_str(), "0x%hx 0x%hx", &lifetime_a, &lifetime_b) < 2 ||
|
|
(lifetime_a == 0 && lifetime_b == 0)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ufs_info_t::report()
|
|
{
|
|
string buffer;
|
|
if (!ReadFileToString(health_file, &buffer)) {
|
|
return;
|
|
}
|
|
|
|
vector<string> lines = Split(buffer, "\n");
|
|
if (lines.empty()) {
|
|
return;
|
|
}
|
|
|
|
char rev[8];
|
|
if (sscanf(lines[0].c_str(), "ufs version: 0x%7s\n", rev) < 1) {
|
|
return;
|
|
}
|
|
|
|
version = "ufs " + string(rev);
|
|
|
|
for (size_t i = 1; i < lines.size(); i++) {
|
|
char token[32];
|
|
uint16_t val;
|
|
int ret;
|
|
if ((ret = sscanf(lines[i].c_str(),
|
|
"Health Descriptor[Byte offset 0x%*d]: %31s = 0x%hx",
|
|
token, &val)) < 2) {
|
|
continue;
|
|
}
|
|
|
|
if (string(token) == "bPreEOLInfo") {
|
|
eol = val;
|
|
} else if (string(token) == "bDeviceLifeTimeEstA") {
|
|
lifetime_a = val;
|
|
} else if (string(token) == "bDeviceLifeTimeEstB") {
|
|
lifetime_b = val;
|
|
}
|
|
}
|
|
|
|
if (eol == 0 || (lifetime_a == 0 && lifetime_b == 0)) {
|
|
return;
|
|
}
|
|
|
|
publish();
|
|
}
|
|
|
|
void health_storage_info_t::report() {
|
|
vector<StorageInfo> halInfos;
|
|
auto ret = mHealth->getStorageInfo(&halInfos);
|
|
if (ret.isOk()) {
|
|
if (halInfos.size() != 0) {
|
|
set_values_from_hal_storage_info(halInfos[0]);
|
|
publish();
|
|
return;
|
|
}
|
|
LOG(ERROR) << "getStorageInfo succeeded but size is 0";
|
|
return;
|
|
}
|
|
if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
|
|
LOG(DEBUG) << "getStorageInfo is not supported on health HAL.";
|
|
return;
|
|
}
|
|
LOG(ERROR) << "getStorageInfo failed with " << ret.getDescription();
|
|
}
|
|
|
|
void health_storage_info_t::set_values_from_hal_storage_info(const StorageInfo& halInfo) {
|
|
eol = halInfo.eol;
|
|
lifetime_a = halInfo.lifetimeA;
|
|
lifetime_b = halInfo.lifetimeB;
|
|
version = halInfo.version;
|
|
}
|