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