Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #define LOG_TAG "storaged" |
| 18 | |
| 19 | #include <stdint.h> |
| 20 | #include <stdlib.h> |
| 21 | |
| 22 | #include <sstream> |
| 23 | |
| 24 | #include <android-base/file.h> |
| 25 | #include <android-base/logging.h> |
| 26 | #include <log/log_event_list.h> |
| 27 | |
| 28 | #include "storaged.h" |
| 29 | #include "storaged_diskstats.h" |
| 30 | |
| 31 | namespace { |
| 32 | |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 33 | using android::sp; |
| 34 | using android::hardware::health::V2_0::DiskStats; |
| 35 | using android::hardware::health::V2_0::IHealth; |
| 36 | using android::hardware::health::V2_0::Result; |
| 37 | using android::hardware::health::V2_0::toString; |
| 38 | |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 39 | #ifdef DEBUG |
| 40 | void log_debug_disk_perf(struct disk_perf* perf, const char* type) { |
| 41 | // skip if the input structure are all zeros |
| 42 | if (perf == NULL || perf->is_zero()) return; |
| 43 | |
| 44 | LOG_TO(SYSTEM, INFO) << "disk_perf " << type |
| 45 | << " rd: " << perf->read_perf << " kbps, " << perf->read_ios << " iops" |
| 46 | << " wr: " << perf->write_perf << " kbps, " << perf->write_ios << " iops" |
| 47 | << " q: " << perf->queue; |
| 48 | } |
| 49 | #else |
| 50 | void log_debug_disk_perf(struct disk_perf* perf, const char* type) {} |
| 51 | #endif |
| 52 | |
| 53 | void log_event_disk_stats(struct disk_stats* stats, const char* type) { |
| 54 | // skip if the input structure are all zeros |
| 55 | if (stats == NULL || stats->is_zero()) return; |
| 56 | |
| 57 | android_log_event_list(EVENTLOGTAG_DISKSTATS) |
| 58 | << type << stats->start_time << stats->end_time |
| 59 | << stats->read_ios << stats->read_merges |
| 60 | << stats->read_sectors << stats->read_ticks |
| 61 | << stats->write_ios << stats->write_merges |
| 62 | << stats->write_sectors << stats->write_ticks |
| 63 | << (uint64_t)stats->io_avg << stats->io_ticks << stats->io_in_queue |
| 64 | << LOG_ID_EVENTS; |
| 65 | } |
| 66 | |
| 67 | } // namespace |
| 68 | |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 69 | bool get_time(struct timespec* ts) { |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 70 | // Use monotonic to exclude suspend time so that we measure IO bytes/sec |
| 71 | // when system is running. |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 72 | int ret = clock_gettime(CLOCK_MONOTONIC, ts); |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 73 | if (ret < 0) { |
| 74 | PLOG_TO(SYSTEM, ERROR) << "clock_gettime() failed"; |
| 75 | return false; |
| 76 | } |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 77 | return true; |
| 78 | } |
| 79 | |
| 80 | void init_disk_stats_other(const struct timespec& ts, struct disk_stats* stats) { |
| 81 | stats->start_time = 0; |
| 82 | stats->end_time = (uint64_t)ts.tv_sec * SEC_TO_MSEC + ts.tv_nsec / (MSEC_TO_USEC * USEC_TO_NSEC); |
| 83 | stats->counter = 1; |
| 84 | stats->io_avg = (double)stats->io_in_flight; |
| 85 | } |
| 86 | |
| 87 | bool parse_disk_stats(const char* disk_stats_path, struct disk_stats* stats) { |
| 88 | // Get time |
| 89 | struct timespec ts; |
| 90 | if (!get_time(&ts)) { |
| 91 | return false; |
| 92 | } |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 93 | |
| 94 | std::string buffer; |
| 95 | if (!android::base::ReadFileToString(disk_stats_path, &buffer)) { |
| 96 | PLOG_TO(SYSTEM, ERROR) << disk_stats_path << ": ReadFileToString failed."; |
| 97 | return false; |
| 98 | } |
| 99 | |
| 100 | // Regular diskstats entries |
| 101 | std::stringstream ss(buffer); |
| 102 | for (uint i = 0; i < DISK_STATS_SIZE; ++i) { |
| 103 | ss >> *((uint64_t*)stats + i); |
| 104 | } |
| 105 | // Other entries |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 106 | init_disk_stats_other(ts, stats); |
| 107 | return true; |
| 108 | } |
| 109 | |
| 110 | void convert_hal_disk_stats(struct disk_stats* dst, const DiskStats& src) { |
| 111 | dst->read_ios = src.reads; |
| 112 | dst->read_merges = src.readMerges; |
| 113 | dst->read_sectors = src.readSectors; |
| 114 | dst->read_ticks = src.readTicks; |
| 115 | dst->write_ios = src.writes; |
| 116 | dst->write_merges = src.writeMerges; |
| 117 | dst->write_sectors = src.writeSectors; |
| 118 | dst->write_ticks = src.writeTicks; |
| 119 | dst->io_in_flight = src.ioInFlight; |
| 120 | dst->io_ticks = src.ioTicks; |
| 121 | dst->io_in_queue = src.ioInQueue; |
| 122 | } |
| 123 | |
| 124 | bool get_disk_stats_from_health_hal(const sp<IHealth>& service, struct disk_stats* stats) { |
| 125 | struct timespec ts; |
| 126 | if (!get_time(&ts)) { |
| 127 | return false; |
| 128 | } |
| 129 | |
| 130 | bool success = false; |
| 131 | auto ret = service->getDiskStats([&success, stats](auto result, const auto& halStats) { |
| 132 | if (result != Result::SUCCESS || halStats.size() == 0) { |
| 133 | LOG_TO(SYSTEM, ERROR) << "getDiskStats failed with result " << toString(result) |
| 134 | << " and size " << halStats.size(); |
| 135 | return; |
| 136 | } |
| 137 | |
| 138 | convert_hal_disk_stats(stats, halStats[0]); |
| 139 | success = true; |
| 140 | }); |
| 141 | |
| 142 | if (!ret.isOk()) { |
| 143 | LOG_TO(SYSTEM, ERROR) << "getDiskStats failed with " << ret.description(); |
| 144 | return false; |
| 145 | } |
| 146 | |
| 147 | if (!success) { |
| 148 | return false; |
| 149 | } |
| 150 | |
| 151 | init_disk_stats_other(ts, stats); |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 152 | return true; |
| 153 | } |
| 154 | |
| 155 | struct disk_perf get_disk_perf(struct disk_stats* stats) |
| 156 | { |
| 157 | struct disk_perf perf = {}; |
| 158 | |
| 159 | if (stats->io_ticks) { |
| 160 | if (stats->read_ticks) { |
| 161 | unsigned long long divisor = stats->read_ticks * stats->io_ticks; |
| 162 | perf.read_perf = ((unsigned long long)SECTOR_SIZE * |
| 163 | stats->read_sectors * stats->io_in_queue + |
| 164 | (divisor >> 1)) / divisor; |
| 165 | perf.read_ios = ((unsigned long long)SEC_TO_MSEC * |
| 166 | stats->read_ios * stats->io_in_queue + |
| 167 | (divisor >> 1)) / divisor; |
| 168 | } |
| 169 | if (stats->write_ticks) { |
| 170 | unsigned long long divisor = stats->write_ticks * stats->io_ticks; |
| 171 | perf.write_perf = ((unsigned long long)SECTOR_SIZE * |
| 172 | stats->write_sectors * stats->io_in_queue + |
| 173 | (divisor >> 1)) / divisor; |
| 174 | perf.write_ios = ((unsigned long long)SEC_TO_MSEC * |
| 175 | stats->write_ios * stats->io_in_queue + |
| 176 | (divisor >> 1)) / divisor; |
| 177 | } |
| 178 | perf.queue = (stats->io_in_queue + (stats->io_ticks >> 1)) / |
| 179 | stats->io_ticks; |
| 180 | } |
| 181 | return perf; |
| 182 | } |
| 183 | |
| 184 | void get_inc_disk_stats(const struct disk_stats* prev, const struct disk_stats* curr, |
| 185 | struct disk_stats* inc) |
| 186 | { |
| 187 | *inc = *curr - *prev; |
| 188 | inc->start_time = prev->end_time; |
| 189 | inc->end_time = curr->end_time; |
| 190 | inc->io_avg = curr->io_avg; |
| 191 | inc->counter = 1; |
| 192 | } |
| 193 | |
| 194 | // Add src to dst |
| 195 | void add_disk_stats(struct disk_stats* src, struct disk_stats* dst) |
| 196 | { |
| 197 | if (dst->end_time != 0 && dst->end_time != src->start_time) { |
| 198 | LOG_TO(SYSTEM, WARNING) << "Two dis-continuous periods of diskstats" |
| 199 | << " are added. dst end with " << dst->end_time |
| 200 | << ", src start with " << src->start_time; |
| 201 | } |
| 202 | |
| 203 | *dst += *src; |
| 204 | |
| 205 | dst->io_in_flight = src->io_in_flight; |
| 206 | if (dst->counter + src->counter) { |
| 207 | dst->io_avg = |
| 208 | ((dst->io_avg * dst->counter) + (src->io_avg * src->counter)) / |
| 209 | (dst->counter + src->counter); |
| 210 | } |
| 211 | dst->counter += src->counter; |
| 212 | dst->end_time = src->end_time; |
| 213 | if (dst->start_time == 0) { |
| 214 | dst->start_time = src->start_time; |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | /* disk_stats_monitor */ |
| 219 | void disk_stats_monitor::update_mean() |
| 220 | { |
| 221 | CHECK(mValid); |
| 222 | mMean.read_perf = (uint32_t)mStats.read_perf.get_mean(); |
| 223 | mMean.read_ios = (uint32_t)mStats.read_ios.get_mean(); |
| 224 | mMean.write_perf = (uint32_t)mStats.write_perf.get_mean(); |
| 225 | mMean.write_ios = (uint32_t)mStats.write_ios.get_mean(); |
| 226 | mMean.queue = (uint32_t)mStats.queue.get_mean(); |
| 227 | } |
| 228 | |
| 229 | void disk_stats_monitor::update_std() |
| 230 | { |
| 231 | CHECK(mValid); |
| 232 | mStd.read_perf = (uint32_t)mStats.read_perf.get_std(); |
| 233 | mStd.read_ios = (uint32_t)mStats.read_ios.get_std(); |
| 234 | mStd.write_perf = (uint32_t)mStats.write_perf.get_std(); |
| 235 | mStd.write_ios = (uint32_t)mStats.write_ios.get_std(); |
| 236 | mStd.queue = (uint32_t)mStats.queue.get_std(); |
| 237 | } |
| 238 | |
| 239 | void disk_stats_monitor::add(struct disk_perf* perf) |
| 240 | { |
| 241 | mStats.read_perf.add(perf->read_perf); |
| 242 | mStats.read_ios.add(perf->read_ios); |
| 243 | mStats.write_perf.add(perf->write_perf); |
| 244 | mStats.write_ios.add(perf->write_ios); |
| 245 | mStats.queue.add(perf->queue); |
| 246 | } |
| 247 | |
| 248 | void disk_stats_monitor::evict(struct disk_perf* perf) { |
| 249 | mStats.read_perf.evict(perf->read_perf); |
| 250 | mStats.read_ios.evict(perf->read_ios); |
| 251 | mStats.write_perf.evict(perf->write_perf); |
| 252 | mStats.write_ios.evict(perf->write_ios); |
| 253 | mStats.queue.evict(perf->queue); |
| 254 | } |
| 255 | |
| 256 | bool disk_stats_monitor::detect(struct disk_perf* perf) |
| 257 | { |
| 258 | return ((double)perf->queue >= (double)mMean.queue + mSigma * (double)mStd.queue) && |
| 259 | ((double)perf->read_perf < (double)mMean.read_perf - mSigma * (double)mStd.read_perf) && |
| 260 | ((double)perf->write_perf < (double)mMean.write_perf - mSigma * (double)mStd.write_perf); |
| 261 | } |
| 262 | |
| 263 | void disk_stats_monitor::update(struct disk_stats* curr) |
| 264 | { |
| 265 | disk_stats inc; |
| 266 | get_inc_disk_stats(&mPrevious, curr, &inc); |
| 267 | add_disk_stats(&inc, &mAccumulate_pub); |
| 268 | |
| 269 | struct disk_perf perf = get_disk_perf(&inc); |
| 270 | log_debug_disk_perf(&perf, "regular"); |
| 271 | |
| 272 | add(&perf); |
| 273 | mBuffer.push(perf); |
| 274 | if (mBuffer.size() > mWindow) { |
| 275 | evict(&mBuffer.front()); |
| 276 | mBuffer.pop(); |
| 277 | mValid = true; |
| 278 | } |
| 279 | |
| 280 | // Update internal data structures |
| 281 | if (LIKELY(mValid)) { |
| 282 | CHECK_EQ(mBuffer.size(), mWindow); |
| 283 | update_mean(); |
| 284 | update_std(); |
| 285 | if (UNLIKELY(detect(&perf))) { |
| 286 | mStall = true; |
| 287 | add_disk_stats(&inc, &mAccumulate); |
| 288 | log_debug_disk_perf(&mMean, "stalled_mean"); |
| 289 | log_debug_disk_perf(&mStd, "stalled_std"); |
| 290 | } else { |
| 291 | if (mStall) { |
| 292 | struct disk_perf acc_perf = get_disk_perf(&mAccumulate); |
| 293 | log_debug_disk_perf(&acc_perf, "stalled"); |
| 294 | log_event_disk_stats(&mAccumulate, "stalled"); |
| 295 | mStall = false; |
| 296 | memset(&mAccumulate, 0, sizeof(mAccumulate)); |
| 297 | } |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | mPrevious = *curr; |
| 302 | } |
| 303 | |
| 304 | void disk_stats_monitor::update() { |
| 305 | disk_stats curr; |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 306 | if (mHealth != nullptr) { |
| 307 | if (!get_disk_stats_from_health_hal(mHealth, &curr)) { |
| 308 | return; |
| 309 | } |
| 310 | } else { |
| 311 | if (!parse_disk_stats(DISK_STATS_PATH, &curr)) { |
| 312 | return; |
| 313 | } |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 314 | } |
| 315 | |
| 316 | update(&curr); |
| 317 | } |
| 318 | |
| 319 | void disk_stats_monitor::publish(void) |
| 320 | { |
| 321 | struct disk_perf perf = get_disk_perf(&mAccumulate_pub); |
| 322 | log_debug_disk_perf(&perf, "regular"); |
| 323 | log_event_disk_stats(&mAccumulate, "regular"); |
| 324 | // Reset global structures |
| 325 | memset(&mAccumulate_pub, 0, sizeof(struct disk_stats)); |
| 326 | } |