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 package com.android.car; 18 19 import android.car.Car; 20 import android.car.storagemonitoring.CarStorageMonitoringManager; 21 import android.car.storagemonitoring.ICarStorageMonitoring; 22 import android.car.storagemonitoring.IIoStatsListener; 23 import android.car.storagemonitoring.IoStats; 24 import android.car.storagemonitoring.IoStatsEntry; 25 import android.car.storagemonitoring.IoStatsEntry.Metrics; 26 import android.car.storagemonitoring.LifetimeWriteInfo; 27 import android.car.storagemonitoring.UidIoRecord; 28 import android.car.storagemonitoring.WearEstimate; 29 import android.car.storagemonitoring.WearEstimateChange; 30 import android.content.ActivityNotFoundException; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.res.Resources; 35 import android.os.RemoteCallbackList; 36 import android.os.RemoteException; 37 import android.util.IndentingPrintWriter; 38 import android.util.JsonWriter; 39 import android.util.Log; 40 import android.util.Slog; 41 import android.util.SparseArray; 42 43 import com.android.car.internal.CarPermission; 44 import com.android.car.storagemonitoring.IoStatsTracker; 45 import com.android.car.storagemonitoring.UidIoStatsProvider; 46 import com.android.car.storagemonitoring.WearEstimateRecord; 47 import com.android.car.storagemonitoring.WearHistory; 48 import com.android.car.storagemonitoring.WearInformation; 49 import com.android.car.storagemonitoring.WearInformationProvider; 50 import com.android.car.systeminterface.SystemInterface; 51 import com.android.internal.annotations.GuardedBy; 52 53 import org.json.JSONArray; 54 import org.json.JSONException; 55 import org.json.JSONObject; 56 57 import java.io.File; 58 import java.io.FileWriter; 59 import java.io.IOException; 60 import java.nio.file.Files; 61 import java.time.Duration; 62 import java.time.Instant; 63 import java.util.ArrayList; 64 import java.util.Arrays; 65 import java.util.Collections; 66 import java.util.HashMap; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.Objects; 70 import java.util.Optional; 71 import java.util.stream.Collectors; 72 import java.util.stream.IntStream; 73 74 /** 75 * A service to provide storage monitoring data like I/O statistics. In order to receive such data, 76 * users need to implement {@link IIoStatsListener} and register themselves against this service. 77 */ 78 public class CarStorageMonitoringService extends ICarStorageMonitoring.Stub 79 implements CarServiceBase { 80 public static final String INTENT_EXCESSIVE_IO = 81 CarStorageMonitoringManager.INTENT_EXCESSIVE_IO; 82 83 public static final long SHUTDOWN_COST_INFO_MISSING = 84 CarStorageMonitoringManager.SHUTDOWN_COST_INFO_MISSING; 85 86 private static final boolean DBG = false; 87 private static final String TAG = CarLog.tagFor(CarStorageMonitoringService.class); 88 private static final int MIN_WEAR_ESTIMATE_OF_CONCERN = 80; 89 90 static final String UPTIME_TRACKER_FILENAME = "service_uptime"; 91 static final String WEAR_INFO_FILENAME = "wear_info"; 92 static final String LIFETIME_WRITES_FILENAME = "lifetime_write"; 93 94 private final WearInformationProvider[] mWearInformationProviders; 95 private final Context mContext; 96 private final File mUptimeTrackerFile; 97 private final File mWearInfoFile; 98 private final File mLifetimeWriteFile; 99 private final OnShutdownReboot mOnShutdownReboot; 100 private final SystemInterface mSystemInterface; 101 private final UidIoStatsProvider mUidIoStatsProvider; 102 103 private final Object mLock = new Object(); 104 105 @GuardedBy("mLock") 106 private final SlidingWindow<IoStats> mIoStatsSamples; 107 108 private final RemoteCallbackList<IIoStatsListener> mListeners; 109 private final Configuration mConfiguration; 110 private final CarPermission mStorageMonitoringPermission; 111 112 @GuardedBy("mLock") 113 private UptimeTracker mUptimeTracker = null; 114 115 @GuardedBy("mLock") 116 private Optional<WearInformation> mWearInformation = Optional.empty(); 117 118 @GuardedBy("mLock") 119 private List<WearEstimateChange> mWearEstimateChanges; 120 121 @GuardedBy("mLock") 122 private List<IoStatsEntry> mBootIoStats = Collections.emptyList(); 123 124 @GuardedBy("mLock") 125 private IoStatsTracker mIoStatsTracker = null; 126 127 @GuardedBy("mLock") 128 private boolean mInitialized = false; 129 130 @GuardedBy("mLock") 131 private long mShutdownCostInfo = SHUTDOWN_COST_INFO_MISSING; 132 133 @GuardedBy("mLock") 134 private String mShutdownCostMissingReason; 135 CarStorageMonitoringService(Context context, SystemInterface systemInterface)136 public CarStorageMonitoringService(Context context, SystemInterface systemInterface) { 137 mContext = context; 138 Resources resources = mContext.getResources(); 139 mConfiguration = new Configuration(resources); 140 141 if (Log.isLoggable(TAG, Log.DEBUG)) { 142 Slog.d(TAG, "service configuration: " + mConfiguration); 143 } 144 145 mUidIoStatsProvider = systemInterface.getUidIoStatsProvider(); 146 mUptimeTrackerFile = new File(systemInterface.getSystemCarDir(), UPTIME_TRACKER_FILENAME); 147 mWearInfoFile = new File(systemInterface.getSystemCarDir(), WEAR_INFO_FILENAME); 148 mLifetimeWriteFile = new File(systemInterface.getSystemCarDir(), LIFETIME_WRITES_FILENAME); 149 mOnShutdownReboot = new OnShutdownReboot(mContext); 150 mSystemInterface = systemInterface; 151 mWearInformationProviders = systemInterface.getFlashWearInformationProviders(); 152 mStorageMonitoringPermission = 153 new CarPermission(mContext, Car.PERMISSION_STORAGE_MONITORING); 154 mWearEstimateChanges = Collections.emptyList(); 155 mIoStatsSamples = new SlidingWindow<>(mConfiguration.ioStatsNumSamplesToStore); 156 mListeners = new RemoteCallbackList<>(); 157 systemInterface.scheduleActionForBootCompleted(() -> { 158 synchronized (mLock) { 159 doInitServiceIfNeededLocked(); 160 }}, Duration.ofSeconds(10)); 161 } 162 loadWearInformation()163 private Optional<WearInformation> loadWearInformation() { 164 for (WearInformationProvider provider : mWearInformationProviders) { 165 WearInformation wearInfo = provider.load(); 166 if (wearInfo != null) { 167 Slog.d(TAG, "retrieved wear info " + wearInfo + " via provider " + provider); 168 return Optional.of(wearInfo); 169 } 170 } 171 172 Slog.d(TAG, "no wear info available"); 173 return Optional.empty(); 174 } 175 loadWearHistory()176 private WearHistory loadWearHistory() { 177 if (mWearInfoFile.exists()) { 178 try { 179 WearHistory wearHistory = WearHistory.fromJson(mWearInfoFile); 180 Slog.d(TAG, "retrieved wear history " + wearHistory); 181 return wearHistory; 182 } catch (IOException | JSONException e) { 183 Slog.e(TAG, "unable to read wear info file " + mWearInfoFile, e); 184 } 185 } 186 187 Slog.d(TAG, "no wear history available"); 188 return new WearHistory(); 189 } 190 191 // returns true iff a new event was added (and hence the history needs to be saved) 192 @GuardedBy("mLock") addEventIfNeededLocked(WearHistory wearHistory)193 private boolean addEventIfNeededLocked(WearHistory wearHistory) { 194 if (!mWearInformation.isPresent()) return false; 195 196 WearInformation wearInformation = mWearInformation.get(); 197 WearEstimate lastWearEstimate; 198 WearEstimate currentWearEstimate = wearInformation.toWearEstimate(); 199 200 if (wearHistory.size() == 0) { 201 lastWearEstimate = WearEstimate.UNKNOWN_ESTIMATE; 202 } else { 203 lastWearEstimate = wearHistory.getLast().getNewWearEstimate(); 204 } 205 206 if (currentWearEstimate.equals(lastWearEstimate)) return false; 207 208 WearEstimateRecord newRecord = new WearEstimateRecord(lastWearEstimate, 209 currentWearEstimate, 210 mUptimeTracker.getTotalUptime(), 211 Instant.now()); 212 Slog.d(TAG, "new wear record generated " + newRecord); 213 wearHistory.add(newRecord); 214 return true; 215 } 216 storeWearHistory(WearHistory wearHistory)217 private void storeWearHistory(WearHistory wearHistory) { 218 try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(mWearInfoFile))) { 219 wearHistory.writeToJson(jsonWriter); 220 } catch (IOException e) { 221 Slog.e(TAG, "unable to write wear info file" + mWearInfoFile, e); 222 } 223 } 224 225 @Override init()226 public void init() { 227 Slog.d(TAG, "CarStorageMonitoringService init()"); 228 synchronized (mLock) { 229 mUptimeTracker = new UptimeTracker(mUptimeTrackerFile, 230 mConfiguration.uptimeIntervalBetweenUptimeDataWriteMs, 231 mSystemInterface); 232 } 233 } 234 launchWearChangeActivity()235 private void launchWearChangeActivity() { 236 final String activityPath = mConfiguration.activityHandlerForFlashWearChanges; 237 if (activityPath.isEmpty()) return; 238 try { 239 final ComponentName activityComponent = 240 Objects.requireNonNull(ComponentName.unflattenFromString(activityPath)); 241 Intent intent = new Intent(); 242 intent.setComponent(activityComponent); 243 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 244 mContext.startActivity(intent); 245 } catch (ActivityNotFoundException | NullPointerException e) { 246 Slog.e(TAG, "value of activityHandlerForFlashWearChanges invalid non-empty string " 247 + activityPath, e); 248 } 249 } 250 logOnAdverseWearLevel(WearInformation wearInformation)251 private static void logOnAdverseWearLevel(WearInformation wearInformation) { 252 if (wearInformation.preEolInfo > WearInformation.PRE_EOL_INFO_NORMAL || 253 Math.max(wearInformation.lifetimeEstimateA, 254 wearInformation.lifetimeEstimateB) >= MIN_WEAR_ESTIMATE_OF_CONCERN) { 255 Slog.w(TAG, "flash storage reached wear a level that requires attention: " 256 + wearInformation); 257 } 258 } 259 loadNewIoStats()260 private SparseArray<UidIoRecord> loadNewIoStats() { 261 SparseArray<UidIoRecord> ioRecords = mUidIoStatsProvider.load(); 262 return (ioRecords == null ? new SparseArray<>() : ioRecords); 263 } 264 collectNewIoMetrics()265 private void collectNewIoMetrics() { 266 SparseArray<IoStatsEntry> currentSample; 267 boolean needsExcessiveIoBroadcast; 268 IoStats ioStats; 269 synchronized (mLock) { 270 mIoStatsTracker.update(loadNewIoStats()); 271 currentSample = mIoStatsTracker.getCurrentSample(); 272 ioStats = new IoStats( 273 SparseArrayStream.valueStream(currentSample).collect(Collectors.toList()), 274 mSystemInterface.getUptime()); 275 mIoStatsSamples.add(ioStats); 276 needsExcessiveIoBroadcast = needsExcessiveIoBroadcastLocked(); 277 } 278 279 dispatchNewIoEvent(ioStats); 280 281 if (DBG) { 282 if (currentSample.size() == 0) { 283 Slog.d(TAG, "no new I/O stat data"); 284 } else { 285 SparseArrayStream.valueStream(currentSample).forEach( 286 uidIoStats -> Slog.d(TAG, "updated I/O stat data: " + uidIoStats)); 287 } 288 } 289 290 if (needsExcessiveIoBroadcast) { 291 Slog.d(TAG, "about to send " + INTENT_EXCESSIVE_IO); 292 sendExcessiveIoBroadcast(); 293 } 294 } 295 sendExcessiveIoBroadcast()296 private void sendExcessiveIoBroadcast() { 297 Slog.w(TAG, "sending " + INTENT_EXCESSIVE_IO); 298 299 final String receiverPath = mConfiguration.intentReceiverForUnacceptableIoMetrics; 300 if (receiverPath.isEmpty()) return; 301 302 final ComponentName receiverComponent; 303 try { 304 receiverComponent = Objects.requireNonNull( 305 ComponentName.unflattenFromString(receiverPath)); 306 } catch (NullPointerException e) { 307 Slog.e(TAG, "value of intentReceiverForUnacceptableIoMetrics non-null but invalid:" 308 + receiverPath, e); 309 return; 310 } 311 312 Intent intent = new Intent(INTENT_EXCESSIVE_IO); 313 intent.setComponent(receiverComponent); 314 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 315 mContext.sendBroadcast(intent, mStorageMonitoringPermission.toString()); 316 } 317 318 @GuardedBy("mLock") needsExcessiveIoBroadcastLocked()319 private boolean needsExcessiveIoBroadcastLocked() { 320 return mIoStatsSamples.count((IoStats delta) -> { 321 Metrics total = delta.getTotals(); 322 final boolean tooManyBytesWritten = 323 (total.bytesWrittenToStorage > mConfiguration.acceptableBytesWrittenPerSample); 324 final boolean tooManyFsyncCalls = 325 (total.fsyncCalls > mConfiguration.acceptableFsyncCallsPerSample); 326 return tooManyBytesWritten || tooManyFsyncCalls; 327 }) > mConfiguration.maxExcessiveIoSamplesInWindow; 328 } 329 dispatchNewIoEvent(IoStats delta)330 private void dispatchNewIoEvent(IoStats delta) { 331 final int listenersCount = mListeners.beginBroadcast(); 332 IntStream.range(0, listenersCount).forEach( 333 i -> { 334 try { 335 mListeners.getBroadcastItem(i).onSnapshot(delta); 336 } catch (RemoteException e) { 337 Slog.w(TAG, "failed to dispatch snapshot", e); 338 } 339 }); 340 mListeners.finishBroadcast(); 341 } 342 343 @GuardedBy("mLock") doInitServiceIfNeededLocked()344 private void doInitServiceIfNeededLocked() { 345 if (mInitialized) return; 346 347 Slog.d(TAG, "initializing CarStorageMonitoringService"); 348 349 mWearInformation = loadWearInformation(); 350 351 // TODO(egranata): can this be done lazily? 352 final WearHistory wearHistory = loadWearHistory(); 353 final boolean didWearChangeHappen = addEventIfNeededLocked(wearHistory); 354 if (didWearChangeHappen) { 355 storeWearHistory(wearHistory); 356 } 357 Slog.d(TAG, "wear history being tracked is " + wearHistory); 358 mWearEstimateChanges = wearHistory.toWearEstimateChanges( 359 mConfiguration.acceptableHoursPerOnePercentFlashWear); 360 361 mOnShutdownReboot.addAction((c, i) -> logLifetimeWrites()) 362 .addAction((c, i) -> release()); 363 364 mWearInformation.ifPresent(CarStorageMonitoringService::logOnAdverseWearLevel); 365 366 if (didWearChangeHappen) { 367 launchWearChangeActivity(); 368 } 369 370 long bootUptime = mSystemInterface.getUptime(); 371 mBootIoStats = SparseArrayStream.valueStream(loadNewIoStats()) 372 .map(record -> { 373 // at boot, assume all UIDs have been running for as long as the system has 374 // been up, since we don't really know any better 375 IoStatsEntry stats = new IoStatsEntry(record, bootUptime); 376 if (DBG) { 377 Slog.d(TAG, "loaded boot I/O stat data: " + stats); 378 } 379 return stats; 380 }).collect(Collectors.toList()); 381 382 mIoStatsTracker = new IoStatsTracker(mBootIoStats, 383 mConfiguration.ioStatsRefreshRateMs, 384 mSystemInterface.getSystemStateInterface()); 385 386 if (mConfiguration.ioStatsNumSamplesToStore > 0) { 387 mSystemInterface.scheduleAction(this::collectNewIoMetrics, 388 mConfiguration.ioStatsRefreshRateMs); 389 } else { 390 Slog.i(TAG, "service configuration disabled I/O sample window. not collecting samples"); 391 } 392 393 mShutdownCostInfo = computeShutdownCostLocked(); 394 Slog.d(TAG, "calculated data written in last shutdown was " + mShutdownCostInfo + " bytes"); 395 mLifetimeWriteFile.delete(); 396 397 Slog.i(TAG, "CarStorageMonitoringService is up"); 398 399 mInitialized = true; 400 } 401 402 @GuardedBy("mLock") computeShutdownCostLocked()403 private long computeShutdownCostLocked() { 404 List<LifetimeWriteInfo> shutdownWrites = loadLifetimeWrites(); 405 if (shutdownWrites.isEmpty()) { 406 Slog.d(TAG, "lifetime write data from last shutdown missing"); 407 mShutdownCostMissingReason = "no historical writes stored at last shutdown"; 408 return SHUTDOWN_COST_INFO_MISSING; 409 } 410 List<LifetimeWriteInfo> currentWrites = 411 Arrays.asList(mSystemInterface.getLifetimeWriteInfoProvider().load()); 412 if (currentWrites.isEmpty()) { 413 Slog.d(TAG, "current lifetime write data missing"); 414 mShutdownCostMissingReason = "current write data cannot be obtained"; 415 return SHUTDOWN_COST_INFO_MISSING; 416 } 417 418 long shutdownCost = 0; 419 420 Map<String, Long> shutdownLifetimeWrites = new HashMap<>(); 421 shutdownWrites.forEach(li -> 422 shutdownLifetimeWrites.put(li.partition, li.writtenBytes)); 423 424 // for every partition currently available, look for it in the shutdown data 425 for (int i = 0; i < currentWrites.size(); ++i) { 426 LifetimeWriteInfo li = currentWrites.get(i); 427 // if this partition was not available when we last shutdown the system, then 428 // just pretend we had written the same amount of data then as we have now 429 final long writtenAtShutdown = 430 shutdownLifetimeWrites.getOrDefault(li.partition, li.writtenBytes); 431 final long costDelta = li.writtenBytes - writtenAtShutdown; 432 if (costDelta >= 0) { 433 Slog.d(TAG, "partition " + li.partition + " had " + costDelta 434 + " bytes written to it during shutdown"); 435 shutdownCost += costDelta; 436 } else { 437 // the counter of written bytes should be monotonic; a decrease might mean 438 // corrupt data, improper shutdown or that the kernel in use does not 439 // have proper monotonic guarantees on the lifetime write data. If any of these 440 // occur, it's probably safer to just bail out and say we don't know 441 mShutdownCostMissingReason = li.partition + " has a negative write amount (" 442 + costDelta + " bytes)"; 443 Slog.e(TAG, "partition " + li.partition + " reported " + costDelta 444 + " bytes written to it during shutdown. assuming we can't" 445 + " determine proper shutdown information."); 446 return SHUTDOWN_COST_INFO_MISSING; 447 } 448 } 449 450 return shutdownCost; 451 } 452 loadLifetimeWrites()453 private List<LifetimeWriteInfo> loadLifetimeWrites() { 454 if (!mLifetimeWriteFile.exists() || !mLifetimeWriteFile.isFile()) { 455 Slog.d(TAG, "lifetime write file missing or inaccessible " + mLifetimeWriteFile); 456 return Collections.emptyList(); 457 } 458 try { 459 JSONObject jsonObject = new JSONObject( 460 new String(Files.readAllBytes(mLifetimeWriteFile.toPath()))); 461 462 JSONArray jsonArray = jsonObject.getJSONArray("lifetimeWriteInfo"); 463 464 List<LifetimeWriteInfo> result = new ArrayList<>(); 465 for (int i = 0; i < jsonArray.length(); ++i) { 466 result.add(new LifetimeWriteInfo(jsonArray.getJSONObject(i))); 467 } 468 return result; 469 } catch (JSONException | IOException e) { 470 Slog.e(TAG, "lifetime write file does not contain valid JSON", e); 471 return Collections.emptyList(); 472 } 473 } 474 logLifetimeWrites()475 private void logLifetimeWrites() { 476 try { 477 LifetimeWriteInfo[] lifetimeWriteInfos = 478 mSystemInterface.getLifetimeWriteInfoProvider().load(); 479 JsonWriter jsonWriter = new JsonWriter(new FileWriter(mLifetimeWriteFile)); 480 jsonWriter.beginObject(); 481 jsonWriter.name("lifetimeWriteInfo").beginArray(); 482 for (LifetimeWriteInfo writeInfo : lifetimeWriteInfos) { 483 Slog.d(TAG, "storing lifetime write info " + writeInfo); 484 writeInfo.writeToJson(jsonWriter); 485 } 486 jsonWriter.endArray().endObject(); 487 jsonWriter.close(); 488 } catch (IOException e) { 489 Slog.e(TAG, "unable to save lifetime write info on shutdown", e); 490 } 491 } 492 493 @Override release()494 public void release() { 495 Slog.i(TAG, "tearing down CarStorageMonitoringService"); 496 synchronized (mLock) { 497 if (mUptimeTracker != null) { 498 mUptimeTracker.onDestroy(); 499 } 500 } 501 mOnShutdownReboot.clearActions(); 502 mListeners.kill(); 503 } 504 505 @Override dump(IndentingPrintWriter writer)506 public void dump(IndentingPrintWriter writer) { 507 writer.println("*CarStorageMonitoringService*"); 508 synchronized (mLock) { 509 doInitServiceIfNeededLocked(); 510 writer.println("last wear information retrieved: " 511 + mWearInformation.map(WearInformation::toString).orElse("missing")); 512 writer.println("wear change history: " 513 + mWearEstimateChanges.stream() 514 .map(WearEstimateChange::toString) 515 .collect(Collectors.joining("\n"))); 516 writer.println("boot I/O stats: " 517 + mBootIoStats.stream() 518 .map(IoStatsEntry::toString) 519 .collect(Collectors.joining("\n"))); 520 writer.println("aggregate I/O stats: " 521 + SparseArrayStream.valueStream(mIoStatsTracker.getTotal()) 522 .map(IoStatsEntry::toString) 523 .collect(Collectors.joining("\n"))); 524 writer.println("I/O stats snapshots: "); 525 writer.println( 526 mIoStatsSamples.stream().map( 527 sample -> sample.getStats().stream() 528 .map(IoStatsEntry::toString) 529 .collect(Collectors.joining("\n"))) 530 .collect(Collectors.joining("\n------\n"))); 531 if (mShutdownCostInfo < 0) { 532 writer.print("last shutdown cost: missing. "); 533 if (mShutdownCostMissingReason != null && !mShutdownCostMissingReason.isEmpty()) { 534 writer.println("reason: " + mShutdownCostMissingReason); 535 } 536 } else { 537 writer.println("last shutdown cost: " + mShutdownCostInfo + " bytes, estimated"); 538 } 539 } 540 } 541 542 // ICarStorageMonitoring implementation 543 544 @Override getPreEolIndicatorStatus()545 public int getPreEolIndicatorStatus() { 546 mStorageMonitoringPermission.assertGranted(); 547 synchronized (mLock) { 548 doInitServiceIfNeededLocked(); 549 550 return mWearInformation.map(wi -> wi.preEolInfo) 551 .orElse(WearInformation.UNKNOWN_PRE_EOL_INFO); 552 } 553 } 554 555 @Override getWearEstimate()556 public WearEstimate getWearEstimate() { 557 mStorageMonitoringPermission.assertGranted(); 558 synchronized (mLock) { 559 doInitServiceIfNeededLocked(); 560 561 return mWearInformation.map(wi -> 562 new WearEstimate(wi.lifetimeEstimateA, wi.lifetimeEstimateB)).orElse( 563 WearEstimate.UNKNOWN_ESTIMATE); 564 } 565 } 566 567 @Override getWearEstimateHistory()568 public List<WearEstimateChange> getWearEstimateHistory() { 569 mStorageMonitoringPermission.assertGranted(); 570 synchronized (mLock) { 571 doInitServiceIfNeededLocked(); 572 573 return Collections.unmodifiableList(mWearEstimateChanges); 574 } 575 } 576 577 @Override getBootIoStats()578 public List<IoStatsEntry> getBootIoStats() { 579 mStorageMonitoringPermission.assertGranted(); 580 doInitServiceIfNeededLocked(); 581 582 return Collections.unmodifiableList(mBootIoStats); 583 } 584 585 @Override getAggregateIoStats()586 public List<IoStatsEntry> getAggregateIoStats() { 587 mStorageMonitoringPermission.assertGranted(); 588 synchronized (mLock) { 589 doInitServiceIfNeededLocked(); 590 591 return Collections.unmodifiableList(SparseArrayStream.valueStream( 592 mIoStatsTracker.getTotal()).collect(Collectors.toList())); 593 } 594 } 595 596 @Override getShutdownDiskWriteAmount()597 public long getShutdownDiskWriteAmount() { 598 mStorageMonitoringPermission.assertGranted(); 599 synchronized (mLock) { 600 doInitServiceIfNeededLocked(); 601 602 return mShutdownCostInfo; 603 } 604 } 605 606 @Override getIoStatsDeltas()607 public List<IoStats> getIoStatsDeltas() { 608 mStorageMonitoringPermission.assertGranted(); 609 synchronized (mLock) { 610 doInitServiceIfNeededLocked(); 611 612 return Collections.unmodifiableList( 613 mIoStatsSamples.stream().collect(Collectors.toList())); 614 } 615 } 616 617 @Override registerListener(IIoStatsListener listener)618 public void registerListener(IIoStatsListener listener) { 619 mStorageMonitoringPermission.assertGranted(); 620 synchronized (mLock) { 621 doInitServiceIfNeededLocked(); 622 } 623 mListeners.register(listener); 624 } 625 626 @Override unregisterListener(IIoStatsListener listener)627 public void unregisterListener(IIoStatsListener listener) { 628 mStorageMonitoringPermission.assertGranted(); 629 // no need to initialize service if unregistering 630 631 mListeners.unregister(listener); 632 } 633 634 private static final class Configuration { 635 final long acceptableBytesWrittenPerSample; 636 final int acceptableFsyncCallsPerSample; 637 final int acceptableHoursPerOnePercentFlashWear; 638 final String activityHandlerForFlashWearChanges; 639 final String intentReceiverForUnacceptableIoMetrics; 640 final int ioStatsNumSamplesToStore; 641 final int ioStatsRefreshRateMs; 642 final int maxExcessiveIoSamplesInWindow; 643 final long uptimeIntervalBetweenUptimeDataWriteMs; 644 Configuration(Resources resources)645 Configuration(Resources resources) throws Resources.NotFoundException { 646 ioStatsNumSamplesToStore = resources.getInteger(R.integer.ioStatsNumSamplesToStore); 647 acceptableBytesWrittenPerSample = 648 1024 * resources.getInteger(R.integer.acceptableWrittenKBytesPerSample); 649 acceptableFsyncCallsPerSample = 650 resources.getInteger(R.integer.acceptableFsyncCallsPerSample); 651 maxExcessiveIoSamplesInWindow = 652 resources.getInteger(R.integer.maxExcessiveIoSamplesInWindow); 653 uptimeIntervalBetweenUptimeDataWriteMs = 60 * 60 * 1000 654 * resources.getInteger(R.integer.uptimeHoursIntervalBetweenUptimeDataWrite); 655 acceptableHoursPerOnePercentFlashWear = 656 resources.getInteger(R.integer.acceptableHoursPerOnePercentFlashWear); 657 ioStatsRefreshRateMs = 1000 * resources.getInteger(R.integer.ioStatsRefreshRateSeconds); 658 activityHandlerForFlashWearChanges = 659 resources.getString(R.string.activityHandlerForFlashWearChanges); 660 intentReceiverForUnacceptableIoMetrics = 661 resources.getString(R.string.intentReceiverForUnacceptableIoMetrics); 662 } 663 664 @Override toString()665 public String toString() { 666 return String.format("acceptableBytesWrittenPerSample = %d, " 667 + "acceptableFsyncCallsPerSample = %d, " 668 + "acceptableHoursPerOnePercentFlashWear = %d, " 669 + "activityHandlerForFlashWearChanges = %s, " 670 + "intentReceiverForUnacceptableIoMetrics = %s, " 671 + "ioStatsNumSamplesToStore = %d, " 672 + "ioStatsRefreshRateMs = %d, " 673 + "maxExcessiveIoSamplesInWindow = %d, " 674 + "uptimeIntervalBetweenUptimeDataWriteMs = %d", 675 acceptableBytesWrittenPerSample, 676 acceptableFsyncCallsPerSample, 677 acceptableHoursPerOnePercentFlashWear, 678 activityHandlerForFlashWearChanges, 679 intentReceiverForUnacceptableIoMetrics, 680 ioStatsNumSamplesToStore, 681 ioStatsRefreshRateMs, 682 maxExcessiveIoSamplesInWindow, 683 uptimeIntervalBetweenUptimeDataWriteMs); 684 } 685 } 686 } 687