| /* | |
| * 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 android::sp; | |
| using android::hardware::health::V2_0::DiskStats; | |
| using android::hardware::health::V2_0::IHealth; | |
| using android::hardware::health::V2_0::Result; | |
| using android::hardware::health::V2_0::toString; | |
| #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_TO(SYSTEM, 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_TO(SYSTEM, 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_TO(SYSTEM, 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 sp<IHealth>& service, struct disk_stats* stats) { | |
| struct timespec ts; | |
| if (!get_time(&ts)) { | |
| return false; | |
| } | |
| bool success = false; | |
| auto ret = service->getDiskStats([&success, stats](auto result, const auto& halStats) { | |
| if (result == Result::NOT_SUPPORTED) { | |
| LOG_TO(SYSTEM, DEBUG) << "getDiskStats is not supported on health HAL."; | |
| return; | |
| } | |
| if (result != Result::SUCCESS || halStats.size() == 0) { | |
| LOG_TO(SYSTEM, ERROR) << "getDiskStats failed with result " << toString(result) | |
| << " and size " << halStats.size(); | |
| return; | |
| } | |
| convert_hal_disk_stats(stats, halStats[0]); | |
| success = true; | |
| }); | |
| if (!ret.isOk()) { | |
| LOG_TO(SYSTEM, ERROR) << "getDiskStats failed with " << ret.description(); | |
| return false; | |
| } | |
| if (!success) { | |
| return false; | |
| } | |
| init_disk_stats_other(ts, stats); | |
| return true; | |
| } | |
| 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_TO(SYSTEM, 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)); | |
| } |