319 lines
10 KiB
C++
319 lines
10 KiB
C++
/*
|
|
* Copyright (C) 2017 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 <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <sstream>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <log/log_event_list.h>
|
|
|
|
#include "storaged.h"
|
|
#include "storaged_diskstats.h"
|
|
|
|
namespace {
|
|
|
|
using aidl::android::hardware::health::DiskStats;
|
|
using aidl::android::hardware::health::IHealth;
|
|
|
|
#ifdef DEBUG
|
|
void log_debug_disk_perf(struct disk_perf* perf, const char* type) {
|
|
// skip if the input structure are all zeros
|
|
if (perf == NULL || perf->is_zero()) return;
|
|
|
|
LOG(INFO) << "disk_perf " << type << " rd: " << perf->read_perf << " kbps, " << perf->read_ios
|
|
<< " iops"
|
|
<< " wr: " << perf->write_perf << " kbps, " << perf->write_ios << " iops"
|
|
<< " q: " << perf->queue;
|
|
}
|
|
#else
|
|
void log_debug_disk_perf(struct disk_perf* perf, const char* type) {}
|
|
#endif
|
|
|
|
void log_event_disk_stats(struct disk_stats* stats, const char* type) {
|
|
// skip if the input structure are all zeros
|
|
if (stats == NULL || stats->is_zero()) return;
|
|
|
|
android_log_event_list(EVENTLOGTAG_DISKSTATS)
|
|
<< type << stats->start_time << stats->end_time
|
|
<< stats->read_ios << stats->read_merges
|
|
<< stats->read_sectors << stats->read_ticks
|
|
<< stats->write_ios << stats->write_merges
|
|
<< stats->write_sectors << stats->write_ticks
|
|
<< (uint64_t)stats->io_avg << stats->io_ticks << stats->io_in_queue
|
|
<< LOG_ID_EVENTS;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool get_time(struct timespec* ts) {
|
|
// Use monotonic to exclude suspend time so that we measure IO bytes/sec
|
|
// when system is running.
|
|
int ret = clock_gettime(CLOCK_MONOTONIC, ts);
|
|
if (ret < 0) {
|
|
PLOG(ERROR) << "clock_gettime() failed";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void init_disk_stats_other(const struct timespec& ts, struct disk_stats* stats) {
|
|
stats->start_time = 0;
|
|
stats->end_time = (uint64_t)ts.tv_sec * SEC_TO_MSEC + ts.tv_nsec / (MSEC_TO_USEC * USEC_TO_NSEC);
|
|
stats->counter = 1;
|
|
stats->io_avg = (double)stats->io_in_flight;
|
|
}
|
|
|
|
bool parse_disk_stats(const char* disk_stats_path, struct disk_stats* stats) {
|
|
// Get time
|
|
struct timespec ts;
|
|
if (!get_time(&ts)) {
|
|
return false;
|
|
}
|
|
|
|
std::string buffer;
|
|
if (!android::base::ReadFileToString(disk_stats_path, &buffer)) {
|
|
PLOG(ERROR) << disk_stats_path << ": ReadFileToString failed.";
|
|
return false;
|
|
}
|
|
|
|
// Regular diskstats entries
|
|
std::stringstream ss(buffer);
|
|
for (uint i = 0; i < DISK_STATS_SIZE; ++i) {
|
|
ss >> *((uint64_t*)stats + i);
|
|
}
|
|
// Other entries
|
|
init_disk_stats_other(ts, stats);
|
|
return true;
|
|
}
|
|
|
|
void convert_hal_disk_stats(struct disk_stats* dst, const DiskStats& src) {
|
|
dst->read_ios = src.reads;
|
|
dst->read_merges = src.readMerges;
|
|
dst->read_sectors = src.readSectors;
|
|
dst->read_ticks = src.readTicks;
|
|
dst->write_ios = src.writes;
|
|
dst->write_merges = src.writeMerges;
|
|
dst->write_sectors = src.writeSectors;
|
|
dst->write_ticks = src.writeTicks;
|
|
dst->io_in_flight = src.ioInFlight;
|
|
dst->io_ticks = src.ioTicks;
|
|
dst->io_in_queue = src.ioInQueue;
|
|
}
|
|
|
|
bool get_disk_stats_from_health_hal(const std::shared_ptr<IHealth>& service,
|
|
struct disk_stats* stats) {
|
|
struct timespec ts;
|
|
if (!get_time(&ts)) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<DiskStats> halStats;
|
|
auto ret = service->getDiskStats(&halStats);
|
|
if (ret.isOk()) {
|
|
if (halStats.size() > 0) {
|
|
convert_hal_disk_stats(stats, halStats[0]);
|
|
init_disk_stats_other(ts, stats);
|
|
return true;
|
|
}
|
|
LOG(ERROR) << "getDiskStats succeeded but size is 0";
|
|
return false;
|
|
}
|
|
if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
|
|
LOG(DEBUG) << "getDiskStats is not supported on health HAL.";
|
|
return false;
|
|
}
|
|
LOG(ERROR) << "getDiskStats failed with " << ret.getDescription();
|
|
return false;
|
|
}
|
|
|
|
struct disk_perf get_disk_perf(struct disk_stats* stats)
|
|
{
|
|
struct disk_perf perf = {};
|
|
|
|
if (stats->io_ticks) {
|
|
if (stats->read_ticks) {
|
|
unsigned long long divisor = stats->read_ticks * stats->io_ticks;
|
|
perf.read_perf = ((unsigned long long)SECTOR_SIZE *
|
|
stats->read_sectors * stats->io_in_queue +
|
|
(divisor >> 1)) / divisor;
|
|
perf.read_ios = ((unsigned long long)SEC_TO_MSEC *
|
|
stats->read_ios * stats->io_in_queue +
|
|
(divisor >> 1)) / divisor;
|
|
}
|
|
if (stats->write_ticks) {
|
|
unsigned long long divisor = stats->write_ticks * stats->io_ticks;
|
|
perf.write_perf = ((unsigned long long)SECTOR_SIZE *
|
|
stats->write_sectors * stats->io_in_queue +
|
|
(divisor >> 1)) / divisor;
|
|
perf.write_ios = ((unsigned long long)SEC_TO_MSEC *
|
|
stats->write_ios * stats->io_in_queue +
|
|
(divisor >> 1)) / divisor;
|
|
}
|
|
perf.queue = (stats->io_in_queue + (stats->io_ticks >> 1)) /
|
|
stats->io_ticks;
|
|
}
|
|
return perf;
|
|
}
|
|
|
|
void get_inc_disk_stats(const struct disk_stats* prev, const struct disk_stats* curr,
|
|
struct disk_stats* inc)
|
|
{
|
|
*inc = *curr - *prev;
|
|
inc->start_time = prev->end_time;
|
|
inc->end_time = curr->end_time;
|
|
inc->io_avg = curr->io_avg;
|
|
inc->counter = 1;
|
|
}
|
|
|
|
// Add src to dst
|
|
void add_disk_stats(struct disk_stats* src, struct disk_stats* dst)
|
|
{
|
|
if (dst->end_time != 0 && dst->end_time != src->start_time) {
|
|
LOG(WARNING) << "Two dis-continuous periods of diskstats"
|
|
<< " are added. dst end with " << dst->end_time << ", src start with "
|
|
<< src->start_time;
|
|
}
|
|
|
|
*dst += *src;
|
|
|
|
dst->io_in_flight = src->io_in_flight;
|
|
if (dst->counter + src->counter) {
|
|
dst->io_avg =
|
|
((dst->io_avg * dst->counter) + (src->io_avg * src->counter)) /
|
|
(dst->counter + src->counter);
|
|
}
|
|
dst->counter += src->counter;
|
|
dst->end_time = src->end_time;
|
|
if (dst->start_time == 0) {
|
|
dst->start_time = src->start_time;
|
|
}
|
|
}
|
|
|
|
/* disk_stats_monitor */
|
|
void disk_stats_monitor::update_mean()
|
|
{
|
|
CHECK(mValid);
|
|
mMean.read_perf = (uint32_t)mStats.read_perf.get_mean();
|
|
mMean.read_ios = (uint32_t)mStats.read_ios.get_mean();
|
|
mMean.write_perf = (uint32_t)mStats.write_perf.get_mean();
|
|
mMean.write_ios = (uint32_t)mStats.write_ios.get_mean();
|
|
mMean.queue = (uint32_t)mStats.queue.get_mean();
|
|
}
|
|
|
|
void disk_stats_monitor::update_std()
|
|
{
|
|
CHECK(mValid);
|
|
mStd.read_perf = (uint32_t)mStats.read_perf.get_std();
|
|
mStd.read_ios = (uint32_t)mStats.read_ios.get_std();
|
|
mStd.write_perf = (uint32_t)mStats.write_perf.get_std();
|
|
mStd.write_ios = (uint32_t)mStats.write_ios.get_std();
|
|
mStd.queue = (uint32_t)mStats.queue.get_std();
|
|
}
|
|
|
|
void disk_stats_monitor::add(struct disk_perf* perf)
|
|
{
|
|
mStats.read_perf.add(perf->read_perf);
|
|
mStats.read_ios.add(perf->read_ios);
|
|
mStats.write_perf.add(perf->write_perf);
|
|
mStats.write_ios.add(perf->write_ios);
|
|
mStats.queue.add(perf->queue);
|
|
}
|
|
|
|
void disk_stats_monitor::evict(struct disk_perf* perf) {
|
|
mStats.read_perf.evict(perf->read_perf);
|
|
mStats.read_ios.evict(perf->read_ios);
|
|
mStats.write_perf.evict(perf->write_perf);
|
|
mStats.write_ios.evict(perf->write_ios);
|
|
mStats.queue.evict(perf->queue);
|
|
}
|
|
|
|
bool disk_stats_monitor::detect(struct disk_perf* perf)
|
|
{
|
|
return ((double)perf->queue >= (double)mMean.queue + mSigma * (double)mStd.queue) &&
|
|
((double)perf->read_perf < (double)mMean.read_perf - mSigma * (double)mStd.read_perf) &&
|
|
((double)perf->write_perf < (double)mMean.write_perf - mSigma * (double)mStd.write_perf);
|
|
}
|
|
|
|
void disk_stats_monitor::update(struct disk_stats* curr)
|
|
{
|
|
disk_stats inc;
|
|
get_inc_disk_stats(&mPrevious, curr, &inc);
|
|
add_disk_stats(&inc, &mAccumulate_pub);
|
|
|
|
struct disk_perf perf = get_disk_perf(&inc);
|
|
log_debug_disk_perf(&perf, "regular");
|
|
|
|
add(&perf);
|
|
mBuffer.push(perf);
|
|
if (mBuffer.size() > mWindow) {
|
|
evict(&mBuffer.front());
|
|
mBuffer.pop();
|
|
mValid = true;
|
|
}
|
|
|
|
// Update internal data structures
|
|
if (LIKELY(mValid)) {
|
|
CHECK_EQ(mBuffer.size(), mWindow);
|
|
update_mean();
|
|
update_std();
|
|
if (UNLIKELY(detect(&perf))) {
|
|
mStall = true;
|
|
add_disk_stats(&inc, &mAccumulate);
|
|
log_debug_disk_perf(&mMean, "stalled_mean");
|
|
log_debug_disk_perf(&mStd, "stalled_std");
|
|
} else {
|
|
if (mStall) {
|
|
struct disk_perf acc_perf = get_disk_perf(&mAccumulate);
|
|
log_debug_disk_perf(&acc_perf, "stalled");
|
|
log_event_disk_stats(&mAccumulate, "stalled");
|
|
mStall = false;
|
|
memset(&mAccumulate, 0, sizeof(mAccumulate));
|
|
}
|
|
}
|
|
}
|
|
|
|
mPrevious = *curr;
|
|
}
|
|
|
|
void disk_stats_monitor::update() {
|
|
disk_stats curr;
|
|
if (mHealth != nullptr) {
|
|
if (!get_disk_stats_from_health_hal(mHealth, &curr)) {
|
|
return;
|
|
}
|
|
} else {
|
|
if (!parse_disk_stats(DISK_STATS_PATH, &curr)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
update(&curr);
|
|
}
|
|
|
|
void disk_stats_monitor::publish(void)
|
|
{
|
|
struct disk_perf perf = get_disk_perf(&mAccumulate_pub);
|
|
log_debug_disk_perf(&perf, "regular");
|
|
log_event_disk_stats(&mAccumulate, "regular");
|
|
// Reset global structures
|
|
memset(&mAccumulate_pub, 0, sizeof(struct disk_stats));
|
|
}
|