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 | 5291a67 | 2021-12-06 14:29:40 -0800 | [diff] [blame] | 33 | using aidl::android::hardware::health::DiskStats; |
| 34 | using aidl::android::hardware::health::IHealth; |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 35 | |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 36 | #ifdef DEBUG |
| 37 | void log_debug_disk_perf(struct disk_perf* perf, const char* type) { |
| 38 | // skip if the input structure are all zeros |
| 39 | if (perf == NULL || perf->is_zero()) return; |
| 40 | |
Tom Cherry | b8c1147 | 2020-01-10 17:08:15 -0800 | [diff] [blame] | 41 | LOG(INFO) << "disk_perf " << type << " rd: " << perf->read_perf << " kbps, " << perf->read_ios |
| 42 | << " iops" |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 43 | << " wr: " << perf->write_perf << " kbps, " << perf->write_ios << " iops" |
| 44 | << " q: " << perf->queue; |
| 45 | } |
| 46 | #else |
| 47 | void log_debug_disk_perf(struct disk_perf* perf, const char* type) {} |
| 48 | #endif |
| 49 | |
| 50 | void log_event_disk_stats(struct disk_stats* stats, const char* type) { |
| 51 | // skip if the input structure are all zeros |
| 52 | if (stats == NULL || stats->is_zero()) return; |
| 53 | |
| 54 | android_log_event_list(EVENTLOGTAG_DISKSTATS) |
| 55 | << type << stats->start_time << stats->end_time |
| 56 | << stats->read_ios << stats->read_merges |
| 57 | << stats->read_sectors << stats->read_ticks |
| 58 | << stats->write_ios << stats->write_merges |
| 59 | << stats->write_sectors << stats->write_ticks |
| 60 | << (uint64_t)stats->io_avg << stats->io_ticks << stats->io_in_queue |
| 61 | << LOG_ID_EVENTS; |
| 62 | } |
| 63 | |
| 64 | } // namespace |
| 65 | |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 66 | bool get_time(struct timespec* ts) { |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 67 | // Use monotonic to exclude suspend time so that we measure IO bytes/sec |
| 68 | // when system is running. |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 69 | int ret = clock_gettime(CLOCK_MONOTONIC, ts); |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 70 | if (ret < 0) { |
Tom Cherry | b8c1147 | 2020-01-10 17:08:15 -0800 | [diff] [blame] | 71 | PLOG(ERROR) << "clock_gettime() failed"; |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 72 | return false; |
| 73 | } |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 74 | return true; |
| 75 | } |
| 76 | |
| 77 | void init_disk_stats_other(const struct timespec& ts, struct disk_stats* stats) { |
| 78 | stats->start_time = 0; |
| 79 | stats->end_time = (uint64_t)ts.tv_sec * SEC_TO_MSEC + ts.tv_nsec / (MSEC_TO_USEC * USEC_TO_NSEC); |
| 80 | stats->counter = 1; |
| 81 | stats->io_avg = (double)stats->io_in_flight; |
| 82 | } |
| 83 | |
| 84 | bool parse_disk_stats(const char* disk_stats_path, struct disk_stats* stats) { |
| 85 | // Get time |
| 86 | struct timespec ts; |
| 87 | if (!get_time(&ts)) { |
| 88 | return false; |
| 89 | } |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 90 | |
| 91 | std::string buffer; |
| 92 | if (!android::base::ReadFileToString(disk_stats_path, &buffer)) { |
Tom Cherry | b8c1147 | 2020-01-10 17:08:15 -0800 | [diff] [blame] | 93 | PLOG(ERROR) << disk_stats_path << ": ReadFileToString failed."; |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 94 | return false; |
| 95 | } |
| 96 | |
| 97 | // Regular diskstats entries |
| 98 | std::stringstream ss(buffer); |
| 99 | for (uint i = 0; i < DISK_STATS_SIZE; ++i) { |
| 100 | ss >> *((uint64_t*)stats + i); |
| 101 | } |
| 102 | // Other entries |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 103 | init_disk_stats_other(ts, stats); |
| 104 | return true; |
| 105 | } |
| 106 | |
| 107 | void convert_hal_disk_stats(struct disk_stats* dst, const DiskStats& src) { |
| 108 | dst->read_ios = src.reads; |
| 109 | dst->read_merges = src.readMerges; |
| 110 | dst->read_sectors = src.readSectors; |
| 111 | dst->read_ticks = src.readTicks; |
| 112 | dst->write_ios = src.writes; |
| 113 | dst->write_merges = src.writeMerges; |
| 114 | dst->write_sectors = src.writeSectors; |
| 115 | dst->write_ticks = src.writeTicks; |
| 116 | dst->io_in_flight = src.ioInFlight; |
| 117 | dst->io_ticks = src.ioTicks; |
| 118 | dst->io_in_queue = src.ioInQueue; |
| 119 | } |
| 120 | |
Yifan Hong | 5291a67 | 2021-12-06 14:29:40 -0800 | [diff] [blame] | 121 | bool get_disk_stats_from_health_hal(const std::shared_ptr<IHealth>& service, |
| 122 | struct disk_stats* stats) { |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 123 | struct timespec ts; |
| 124 | if (!get_time(&ts)) { |
| 125 | return false; |
| 126 | } |
| 127 | |
Yifan Hong | 5291a67 | 2021-12-06 14:29:40 -0800 | [diff] [blame] | 128 | std::vector<DiskStats> halStats; |
| 129 | auto ret = service->getDiskStats(&halStats); |
| 130 | if (ret.isOk()) { |
| 131 | if (halStats.size() > 0) { |
| 132 | convert_hal_disk_stats(stats, halStats[0]); |
| 133 | init_disk_stats_other(ts, stats); |
| 134 | return true; |
Yifan Hong | 539ee58 | 2018-09-13 09:39:41 -0700 | [diff] [blame] | 135 | } |
Yifan Hong | 5291a67 | 2021-12-06 14:29:40 -0800 | [diff] [blame] | 136 | LOG(ERROR) << "getDiskStats succeeded but size is 0"; |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 137 | return false; |
| 138 | } |
Yifan Hong | 5291a67 | 2021-12-06 14:29:40 -0800 | [diff] [blame] | 139 | if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { |
| 140 | LOG(DEBUG) << "getDiskStats is not supported on health HAL."; |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 141 | return false; |
| 142 | } |
Yifan Hong | 5291a67 | 2021-12-06 14:29:40 -0800 | [diff] [blame] | 143 | LOG(ERROR) << "getDiskStats failed with " << ret.getDescription(); |
| 144 | return false; |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 145 | } |
| 146 | |
| 147 | struct disk_perf get_disk_perf(struct disk_stats* stats) |
| 148 | { |
| 149 | struct disk_perf perf = {}; |
| 150 | |
| 151 | if (stats->io_ticks) { |
| 152 | if (stats->read_ticks) { |
| 153 | unsigned long long divisor = stats->read_ticks * stats->io_ticks; |
| 154 | perf.read_perf = ((unsigned long long)SECTOR_SIZE * |
| 155 | stats->read_sectors * stats->io_in_queue + |
| 156 | (divisor >> 1)) / divisor; |
| 157 | perf.read_ios = ((unsigned long long)SEC_TO_MSEC * |
| 158 | stats->read_ios * stats->io_in_queue + |
| 159 | (divisor >> 1)) / divisor; |
| 160 | } |
| 161 | if (stats->write_ticks) { |
| 162 | unsigned long long divisor = stats->write_ticks * stats->io_ticks; |
| 163 | perf.write_perf = ((unsigned long long)SECTOR_SIZE * |
| 164 | stats->write_sectors * stats->io_in_queue + |
| 165 | (divisor >> 1)) / divisor; |
| 166 | perf.write_ios = ((unsigned long long)SEC_TO_MSEC * |
| 167 | stats->write_ios * stats->io_in_queue + |
| 168 | (divisor >> 1)) / divisor; |
| 169 | } |
| 170 | perf.queue = (stats->io_in_queue + (stats->io_ticks >> 1)) / |
| 171 | stats->io_ticks; |
| 172 | } |
| 173 | return perf; |
| 174 | } |
| 175 | |
| 176 | void get_inc_disk_stats(const struct disk_stats* prev, const struct disk_stats* curr, |
| 177 | struct disk_stats* inc) |
| 178 | { |
| 179 | *inc = *curr - *prev; |
| 180 | inc->start_time = prev->end_time; |
| 181 | inc->end_time = curr->end_time; |
| 182 | inc->io_avg = curr->io_avg; |
| 183 | inc->counter = 1; |
| 184 | } |
| 185 | |
| 186 | // Add src to dst |
| 187 | void add_disk_stats(struct disk_stats* src, struct disk_stats* dst) |
| 188 | { |
| 189 | if (dst->end_time != 0 && dst->end_time != src->start_time) { |
Tom Cherry | b8c1147 | 2020-01-10 17:08:15 -0800 | [diff] [blame] | 190 | LOG(WARNING) << "Two dis-continuous periods of diskstats" |
| 191 | << " are added. dst end with " << dst->end_time << ", src start with " |
| 192 | << src->start_time; |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 193 | } |
| 194 | |
| 195 | *dst += *src; |
| 196 | |
| 197 | dst->io_in_flight = src->io_in_flight; |
| 198 | if (dst->counter + src->counter) { |
| 199 | dst->io_avg = |
| 200 | ((dst->io_avg * dst->counter) + (src->io_avg * src->counter)) / |
| 201 | (dst->counter + src->counter); |
| 202 | } |
| 203 | dst->counter += src->counter; |
| 204 | dst->end_time = src->end_time; |
| 205 | if (dst->start_time == 0) { |
| 206 | dst->start_time = src->start_time; |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | /* disk_stats_monitor */ |
| 211 | void disk_stats_monitor::update_mean() |
| 212 | { |
| 213 | CHECK(mValid); |
| 214 | mMean.read_perf = (uint32_t)mStats.read_perf.get_mean(); |
| 215 | mMean.read_ios = (uint32_t)mStats.read_ios.get_mean(); |
| 216 | mMean.write_perf = (uint32_t)mStats.write_perf.get_mean(); |
| 217 | mMean.write_ios = (uint32_t)mStats.write_ios.get_mean(); |
| 218 | mMean.queue = (uint32_t)mStats.queue.get_mean(); |
| 219 | } |
| 220 | |
| 221 | void disk_stats_monitor::update_std() |
| 222 | { |
| 223 | CHECK(mValid); |
| 224 | mStd.read_perf = (uint32_t)mStats.read_perf.get_std(); |
| 225 | mStd.read_ios = (uint32_t)mStats.read_ios.get_std(); |
| 226 | mStd.write_perf = (uint32_t)mStats.write_perf.get_std(); |
| 227 | mStd.write_ios = (uint32_t)mStats.write_ios.get_std(); |
| 228 | mStd.queue = (uint32_t)mStats.queue.get_std(); |
| 229 | } |
| 230 | |
| 231 | void disk_stats_monitor::add(struct disk_perf* perf) |
| 232 | { |
| 233 | mStats.read_perf.add(perf->read_perf); |
| 234 | mStats.read_ios.add(perf->read_ios); |
| 235 | mStats.write_perf.add(perf->write_perf); |
| 236 | mStats.write_ios.add(perf->write_ios); |
| 237 | mStats.queue.add(perf->queue); |
| 238 | } |
| 239 | |
| 240 | void disk_stats_monitor::evict(struct disk_perf* perf) { |
| 241 | mStats.read_perf.evict(perf->read_perf); |
| 242 | mStats.read_ios.evict(perf->read_ios); |
| 243 | mStats.write_perf.evict(perf->write_perf); |
| 244 | mStats.write_ios.evict(perf->write_ios); |
| 245 | mStats.queue.evict(perf->queue); |
| 246 | } |
| 247 | |
| 248 | bool disk_stats_monitor::detect(struct disk_perf* perf) |
| 249 | { |
| 250 | return ((double)perf->queue >= (double)mMean.queue + mSigma * (double)mStd.queue) && |
| 251 | ((double)perf->read_perf < (double)mMean.read_perf - mSigma * (double)mStd.read_perf) && |
| 252 | ((double)perf->write_perf < (double)mMean.write_perf - mSigma * (double)mStd.write_perf); |
| 253 | } |
| 254 | |
| 255 | void disk_stats_monitor::update(struct disk_stats* curr) |
| 256 | { |
| 257 | disk_stats inc; |
| 258 | get_inc_disk_stats(&mPrevious, curr, &inc); |
| 259 | add_disk_stats(&inc, &mAccumulate_pub); |
| 260 | |
| 261 | struct disk_perf perf = get_disk_perf(&inc); |
| 262 | log_debug_disk_perf(&perf, "regular"); |
| 263 | |
| 264 | add(&perf); |
| 265 | mBuffer.push(perf); |
| 266 | if (mBuffer.size() > mWindow) { |
| 267 | evict(&mBuffer.front()); |
| 268 | mBuffer.pop(); |
| 269 | mValid = true; |
| 270 | } |
| 271 | |
| 272 | // Update internal data structures |
| 273 | if (LIKELY(mValid)) { |
| 274 | CHECK_EQ(mBuffer.size(), mWindow); |
| 275 | update_mean(); |
| 276 | update_std(); |
| 277 | if (UNLIKELY(detect(&perf))) { |
| 278 | mStall = true; |
| 279 | add_disk_stats(&inc, &mAccumulate); |
| 280 | log_debug_disk_perf(&mMean, "stalled_mean"); |
| 281 | log_debug_disk_perf(&mStd, "stalled_std"); |
| 282 | } else { |
| 283 | if (mStall) { |
| 284 | struct disk_perf acc_perf = get_disk_perf(&mAccumulate); |
| 285 | log_debug_disk_perf(&acc_perf, "stalled"); |
| 286 | log_event_disk_stats(&mAccumulate, "stalled"); |
| 287 | mStall = false; |
| 288 | memset(&mAccumulate, 0, sizeof(mAccumulate)); |
| 289 | } |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | mPrevious = *curr; |
| 294 | } |
| 295 | |
| 296 | void disk_stats_monitor::update() { |
| 297 | disk_stats curr; |
Yifan Hong | c4b46e0 | 2018-01-16 15:49:08 -0800 | [diff] [blame] | 298 | if (mHealth != nullptr) { |
| 299 | if (!get_disk_stats_from_health_hal(mHealth, &curr)) { |
| 300 | return; |
| 301 | } |
| 302 | } else { |
| 303 | if (!parse_disk_stats(DISK_STATS_PATH, &curr)) { |
| 304 | return; |
| 305 | } |
Jin Qian | 65dea71 | 2017-08-29 16:48:20 -0700 | [diff] [blame] | 306 | } |
| 307 | |
| 308 | update(&curr); |
| 309 | } |
| 310 | |
| 311 | void disk_stats_monitor::publish(void) |
| 312 | { |
| 313 | struct disk_perf perf = get_disk_perf(&mAccumulate_pub); |
| 314 | log_debug_disk_perf(&perf, "regular"); |
| 315 | log_event_disk_stats(&mAccumulate, "regular"); |
| 316 | // Reset global structures |
| 317 | memset(&mAccumulate_pub, 0, sizeof(struct disk_stats)); |
| 318 | } |