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 mOnShutdownReboot.init(); 236 synchronized (mLock) { 237 mUptimeTracker = new UptimeTracker(mUptimeTrackerFile, 238 mConfiguration.uptimeIntervalBetweenUptimeDataWriteMs, 239 mSystemInterface); 240 } 241 } 242 launchWearChangeActivity()243 private void launchWearChangeActivity() { 244 final String activityPath = mConfiguration.activityHandlerForFlashWearChanges; 245 if (activityPath.isEmpty()) return; 246 try { 247 final ComponentName activityComponent = 248 Objects.requireNonNull(ComponentName.unflattenFromString(activityPath)); 249 Intent intent = new Intent(); 250 intent.setComponent(activityComponent); 251 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 252 mContext.startActivity(intent); 253 } catch (ActivityNotFoundException | NullPointerException e) { 254 Slogf.e(TAG, "value of activityHandlerForFlashWearChanges invalid non-empty string " 255 + activityPath, e); 256 } 257 } 258 logOnAdverseWearLevel(WearInformation wearInformation)259 private static void logOnAdverseWearLevel(WearInformation wearInformation) { 260 if (wearInformation.preEolInfo > WearInformation.PRE_EOL_INFO_NORMAL || 261 Math.max(wearInformation.lifetimeEstimateA, 262 wearInformation.lifetimeEstimateB) >= MIN_WEAR_ESTIMATE_OF_CONCERN) { 263 Slogf.w(TAG, "flash storage reached wear a level that requires attention: " 264 + wearInformation); 265 } 266 } 267 loadNewIoStats()268 private SparseArray<UidIoRecord> loadNewIoStats() { 269 SparseArray<UidIoRecord> ioRecords = mUidIoStatsProvider.load(); 270 return (ioRecords == null ? new SparseArray<>() : ioRecords); 271 } 272 collectNewIoMetrics()273 private void collectNewIoMetrics() { 274 SparseArray<IoStatsEntry> currentSample; 275 boolean needsExcessiveIoBroadcast; 276 IoStats ioStats; 277 synchronized (mLock) { 278 mIoStatsTracker.update(loadNewIoStats()); 279 currentSample = mIoStatsTracker.getCurrentSample(); 280 ioStats = new IoStats( 281 SparseArrayStream.valueStream(currentSample).collect(Collectors.toList()), 282 mSystemInterface.getUptime()); 283 mIoStatsSamples.add(ioStats); 284 needsExcessiveIoBroadcast = needsExcessiveIoBroadcastLocked(); 285 } 286 287 dispatchNewIoEvent(ioStats); 288 289 if (DBG) { 290 if (currentSample.size() == 0) { 291 Slogf.d(TAG, "no new I/O stat data"); 292 } else { 293 SparseArrayStream.valueStream(currentSample).forEach( 294 uidIoStats -> Slogf.d(TAG, "updated I/O stat data: " + uidIoStats)); 295 } 296 } 297 298 if (needsExcessiveIoBroadcast) { 299 Slogf.d(TAG, "about to send " + INTENT_EXCESSIVE_IO); 300 sendExcessiveIoBroadcast(); 301 } 302 } 303 sendExcessiveIoBroadcast()304 private void sendExcessiveIoBroadcast() { 305 Slogf.w(TAG, "sending " + INTENT_EXCESSIVE_IO); 306 307 final String receiverPath = mConfiguration.intentReceiverForUnacceptableIoMetrics; 308 if (receiverPath.isEmpty()) return; 309 310 final ComponentName receiverComponent; 311 try { 312 receiverComponent = Objects.requireNonNull( 313 ComponentName.unflattenFromString(receiverPath)); 314 } catch (NullPointerException e) { 315 Slogf.e(TAG, "value of intentReceiverForUnacceptableIoMetrics non-null but invalid:" 316 + receiverPath, e); 317 return; 318 } 319 320 Intent intent = new Intent(INTENT_EXCESSIVE_IO); 321 intent.setComponent(receiverComponent); 322 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 323 mContext.sendBroadcast(intent, mStorageMonitoringPermission.toString()); 324 } 325 326 @GuardedBy("mLock") needsExcessiveIoBroadcastLocked()327 private boolean needsExcessiveIoBroadcastLocked() { 328 return mIoStatsSamples.count((IoStats delta) -> { 329 Metrics total = delta.getTotals(); 330 final boolean tooManyBytesWritten = 331 (total.bytesWrittenToStorage > mConfiguration.acceptableBytesWrittenPerSample); 332 final boolean tooManyFsyncCalls = 333 (total.fsyncCalls > mConfiguration.acceptableFsyncCallsPerSample); 334 return tooManyBytesWritten || tooManyFsyncCalls; 335 }) > mConfiguration.maxExcessiveIoSamplesInWindow; 336 } 337 dispatchNewIoEvent(IoStats delta)338 private void dispatchNewIoEvent(IoStats delta) { 339 final int listenersCount = mListeners.beginBroadcast(); 340 IntStream.range(0, listenersCount).forEach( 341 i -> { 342 try { 343 mListeners.getBroadcastItem(i).onSnapshot(delta); 344 } catch (RemoteException e) { 345 Slogf.w(TAG, "failed to dispatch snapshot", e); 346 } 347 }); 348 mListeners.finishBroadcast(); 349 } 350 351 @GuardedBy("mLock") doInitServiceIfNeededLocked()352 private void doInitServiceIfNeededLocked() { 353 if (mInitialized) return; 354 355 Slogf.d(TAG, "initializing CarStorageMonitoringService"); 356 357 mWearInformation = loadWearInformation(); 358 359 // TODO(egranata): can this be done lazily? 360 final WearHistory wearHistory = loadWearHistory(); 361 final boolean didWearChangeHappen = addEventIfNeededLocked(wearHistory); 362 if (didWearChangeHappen) { 363 storeWearHistory(wearHistory); 364 } 365 Slogf.d(TAG, "wear history being tracked is " + wearHistory); 366 mWearEstimateChanges = wearHistory.toWearEstimateChanges( 367 mConfiguration.acceptableHoursPerOnePercentFlashWear); 368 369 mOnShutdownReboot.addAction((c, i) -> logLifetimeWrites()) 370 .addAction((c, i) -> release()); 371 372 mWearInformation.ifPresent(CarStorageMonitoringService::logOnAdverseWearLevel); 373 374 if (didWearChangeHappen) { 375 launchWearChangeActivity(); 376 } 377 378 long bootUptime = mSystemInterface.getUptime(); 379 mBootIoStats = SparseArrayStream.valueStream(loadNewIoStats()) 380 .map(record -> { 381 // at boot, assume all UIDs have been running for as long as the system has 382 // been up, since we don't really know any better 383 IoStatsEntry stats = new IoStatsEntry(record, bootUptime); 384 if (DBG) { 385 Slogf.d(TAG, "loaded boot I/O stat data: " + stats); 386 } 387 return stats; 388 }).collect(Collectors.toList()); 389 390 mIoStatsTracker = new IoStatsTracker(mBootIoStats, 391 mConfiguration.ioStatsRefreshRateMs, 392 mSystemInterface.getSystemStateInterface()); 393 394 if (mConfiguration.ioStatsNumSamplesToStore > 0) { 395 mSystemInterface.scheduleAction(this::collectNewIoMetrics, 396 mConfiguration.ioStatsRefreshRateMs); 397 } else { 398 Slogf.i(TAG, "service configuration disabled I/O sample window. not collecting " 399 + "samples"); 400 } 401 402 mShutdownCostInfo = computeShutdownCostLocked(); 403 Slogf.d(TAG, "calculated data written in last shutdown was " + mShutdownCostInfo 404 + " bytes"); 405 mLifetimeWriteFile.delete(); 406 407 Slogf.i(TAG, "CarStorageMonitoringService is up"); 408 409 mInitialized = true; 410 } 411 412 @GuardedBy("mLock") computeShutdownCostLocked()413 private long computeShutdownCostLocked() { 414 List<LifetimeWriteInfo> shutdownWrites = loadLifetimeWrites(); 415 if (shutdownWrites.isEmpty()) { 416 Slogf.d(TAG, "lifetime write data from last shutdown missing"); 417 mShutdownCostMissingReason = "no historical writes stored at last shutdown"; 418 return SHUTDOWN_COST_INFO_MISSING; 419 } 420 List<LifetimeWriteInfo> currentWrites = 421 Arrays.asList(mSystemInterface.getLifetimeWriteInfoProvider().load()); 422 if (currentWrites.isEmpty()) { 423 Slogf.d(TAG, "current lifetime write data missing"); 424 mShutdownCostMissingReason = "current write data cannot be obtained"; 425 return SHUTDOWN_COST_INFO_MISSING; 426 } 427 428 long shutdownCost = 0; 429 430 Map<String, Long> shutdownLifetimeWrites = new HashMap<>(); 431 shutdownWrites.forEach(li -> 432 shutdownLifetimeWrites.put(li.partition, li.writtenBytes)); 433 434 // for every partition currently available, look for it in the shutdown data 435 for (int i = 0; i < currentWrites.size(); ++i) { 436 LifetimeWriteInfo li = currentWrites.get(i); 437 // if this partition was not available when we last shutdown the system, then 438 // just pretend we had written the same amount of data then as we have now 439 final long writtenAtShutdown = 440 shutdownLifetimeWrites.getOrDefault(li.partition, li.writtenBytes); 441 final long costDelta = li.writtenBytes - writtenAtShutdown; 442 if (costDelta >= 0) { 443 Slogf.d(TAG, "partition " + li.partition + " had " + costDelta 444 + " bytes written to it during shutdown"); 445 shutdownCost += costDelta; 446 } else { 447 // the counter of written bytes should be monotonic; a decrease might mean 448 // corrupt data, improper shutdown or that the kernel in use does not 449 // have proper monotonic guarantees on the lifetime write data. If any of these 450 // occur, it's probably safer to just bail out and say we don't know 451 mShutdownCostMissingReason = li.partition + " has a negative write amount (" 452 + costDelta + " bytes)"; 453 Slogf.e(TAG, "partition " + li.partition + " reported " + costDelta 454 + " bytes written to it during shutdown. assuming we can't" 455 + " determine proper shutdown information."); 456 return SHUTDOWN_COST_INFO_MISSING; 457 } 458 } 459 460 return shutdownCost; 461 } 462 loadLifetimeWrites()463 private List<LifetimeWriteInfo> loadLifetimeWrites() { 464 if (!mLifetimeWriteFile.exists() || !mLifetimeWriteFile.isFile()) { 465 Slogf.d(TAG, "lifetime write file missing or inaccessible " + mLifetimeWriteFile); 466 return Collections.emptyList(); 467 } 468 try { 469 JSONObject jsonObject = new JSONObject( 470 new String(Files.readAllBytes(mLifetimeWriteFile.toPath()))); 471 472 JSONArray jsonArray = jsonObject.getJSONArray("lifetimeWriteInfo"); 473 474 List<LifetimeWriteInfo> result = new ArrayList<>(); 475 for (int i = 0; i < jsonArray.length(); ++i) { 476 result.add(new LifetimeWriteInfo(jsonArray.getJSONObject(i))); 477 } 478 return result; 479 } catch (JSONException | IOException e) { 480 Slogf.e(TAG, "lifetime write file does not contain valid JSON", e); 481 return Collections.emptyList(); 482 } 483 } 484 logLifetimeWrites()485 private void logLifetimeWrites() { 486 try { 487 LifetimeWriteInfo[] lifetimeWriteInfos = 488 mSystemInterface.getLifetimeWriteInfoProvider().load(); 489 JsonWriter jsonWriter = new JsonWriter(new FileWriter(mLifetimeWriteFile)); 490 jsonWriter.beginObject(); 491 jsonWriter.name("lifetimeWriteInfo").beginArray(); 492 for (LifetimeWriteInfo writeInfo : lifetimeWriteInfos) { 493 Slogf.d(TAG, "storing lifetime write info " + writeInfo); 494 writeInfo.writeToJson(jsonWriter); 495 } 496 jsonWriter.endArray().endObject(); 497 jsonWriter.close(); 498 } catch (IOException e) { 499 Slogf.e(TAG, "unable to save lifetime write info on shutdown", e); 500 } 501 } 502 503 @Override release()504 public void release() { 505 Slogf.i(TAG, "tearing down CarStorageMonitoringService"); 506 synchronized (mLock) { 507 if (mUptimeTracker != null) { 508 mUptimeTracker.onDestroy(); 509 } 510 } 511 mOnShutdownReboot.release(); 512 mListeners.kill(); 513 } 514 515 @Override 516 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)517 public void dump(IndentingPrintWriter writer) { 518 writer.println("*CarStorageMonitoringService*"); 519 synchronized (mLock) { 520 doInitServiceIfNeededLocked(); 521 writer.println("last wear information retrieved: " 522 + mWearInformation.map(WearInformation::toString).orElse("missing")); 523 writer.println("wear change history: " 524 + mWearEstimateChanges.stream() 525 .map(WearEstimateChange::toString) 526 .collect(Collectors.joining("\n"))); 527 writer.println("boot I/O stats: " 528 + mBootIoStats.stream() 529 .map(IoStatsEntry::toString) 530 .collect(Collectors.joining("\n"))); 531 writer.println("aggregate I/O stats: " 532 + SparseArrayStream.valueStream(mIoStatsTracker.getTotal()) 533 .map(IoStatsEntry::toString) 534 .collect(Collectors.joining("\n"))); 535 writer.println("I/O stats snapshots: "); 536 writer.println( 537 mIoStatsSamples.stream().map( 538 sample -> sample.getStats().stream() 539 .map(IoStatsEntry::toString) 540 .collect(Collectors.joining("\n"))) 541 .collect(Collectors.joining("\n------\n"))); 542 if (mShutdownCostInfo < 0) { 543 writer.print("last shutdown cost: missing. "); 544 if (mShutdownCostMissingReason != null && !mShutdownCostMissingReason.isEmpty()) { 545 writer.println("reason: " + mShutdownCostMissingReason); 546 } 547 } else { 548 writer.println("last shutdown cost: " + mShutdownCostInfo + " bytes, estimated"); 549 } 550 } 551 } 552 553 // ICarStorageMonitoring implementation 554 555 @Override getPreEolIndicatorStatus()556 public int getPreEolIndicatorStatus() { 557 mStorageMonitoringPermission.assertGranted(); 558 synchronized (mLock) { 559 doInitServiceIfNeededLocked(); 560 561 return mWearInformation.map(wi -> wi.preEolInfo) 562 .orElse(WearInformation.UNKNOWN_PRE_EOL_INFO); 563 } 564 } 565 566 /** 567 * @deprecated wear estimate data is unreliable 568 */ 569 @Deprecated 570 @Override getWearEstimate()571 public WearEstimate getWearEstimate() { 572 mStorageMonitoringPermission.assertGranted(); 573 synchronized (mLock) { 574 doInitServiceIfNeededLocked(); 575 576 return mWearInformation.map(wi -> 577 new WearEstimate(wi.lifetimeEstimateA, wi.lifetimeEstimateB)).orElse( 578 WearEstimate.UNKNOWN_ESTIMATE); 579 } 580 } 581 582 /** 583 * @deprecated wear estimate data is unreliable 584 */ 585 @Deprecated 586 @Override getWearEstimateHistory()587 public List<WearEstimateChange> getWearEstimateHistory() { 588 mStorageMonitoringPermission.assertGranted(); 589 synchronized (mLock) { 590 doInitServiceIfNeededLocked(); 591 592 return Collections.unmodifiableList(mWearEstimateChanges); 593 } 594 } 595 596 /** 597 * @deprecated use 598 * {@link com.android.car.watchdog.CarWatchdogService#getResourceOveruseStats(int, int)} 599 * instead. 600 * WARNING: The metrics provided are aggregated through time and could include data retrieved 601 * after system boot. Also, the I/O stats are only for the calling package. 602 */ 603 @Deprecated 604 @Override getBootIoStats()605 public List<IoStatsEntry> getBootIoStats() { 606 mStorageMonitoringPermission.assertGranted(); 607 synchronized (mLock) { 608 doInitServiceIfNeededLocked(); 609 610 return Collections.unmodifiableList(mBootIoStats); 611 } 612 } 613 614 /** 615 * @deprecated use 616 * {@link com.android.car.watchdog.CarWatchdogService#getResourceOveruseStats(int, int)} instead. 617 * WARNING: The I/O stats returned are only for the calling package. 618 */ 619 @Deprecated 620 @Override getAggregateIoStats()621 public List<IoStatsEntry> getAggregateIoStats() { 622 mStorageMonitoringPermission.assertGranted(); 623 synchronized (mLock) { 624 doInitServiceIfNeededLocked(); 625 626 return Collections.unmodifiableList(SparseArrayStream.valueStream( 627 mIoStatsTracker.getTotal()).collect(Collectors.toList())); 628 } 629 } 630 631 /** 632 * @deprecated use 633 * {@link com.android.car.watchdog.CarWatchdogService#getResourceOveruseStats(int, int)} 634 * instead. 635 * WARNING: The metrics provided are aggregated through time and could include data not related 636 * to system shutdown. Also, the I/O stats are only for the calling package. 637 */ 638 @Deprecated 639 @Override getShutdownDiskWriteAmount()640 public long getShutdownDiskWriteAmount() { 641 mStorageMonitoringPermission.assertGranted(); 642 synchronized (mLock) { 643 doInitServiceIfNeededLocked(); 644 645 return mShutdownCostInfo; 646 } 647 } 648 649 /** 650 * @deprecated use 651 * {@link com.android.car.watchdog.CarWatchdogService#getResourceOveruseStats(int, int)} instead. 652 * WARNING: The I/O stats returned are only for the calling package. 653 */ 654 @Deprecated 655 @Override getIoStatsDeltas()656 public List<IoStats> getIoStatsDeltas() { 657 mStorageMonitoringPermission.assertGranted(); 658 synchronized (mLock) { 659 doInitServiceIfNeededLocked(); 660 661 return Collections.unmodifiableList( 662 mIoStatsSamples.stream().collect(Collectors.toList())); 663 } 664 } 665 666 /** 667 * @deprecated {@link IIoStatsListener} is deprecated 668 */ 669 @Deprecated 670 @Override registerListener(IIoStatsListener listener)671 public void registerListener(IIoStatsListener listener) { 672 mStorageMonitoringPermission.assertGranted(); 673 synchronized (mLock) { 674 doInitServiceIfNeededLocked(); 675 } 676 mListeners.register(listener); 677 } 678 679 /** 680 * @deprecated {@link IIoStatsListener} is deprecated 681 */ 682 @Deprecated 683 @Override unregisterListener(IIoStatsListener listener)684 public void unregisterListener(IIoStatsListener listener) { 685 mStorageMonitoringPermission.assertGranted(); 686 // no need to initialize service if unregistering 687 688 mListeners.unregister(listener); 689 } 690 691 private static final class Configuration { 692 final long acceptableBytesWrittenPerSample; 693 final int acceptableFsyncCallsPerSample; 694 final int acceptableHoursPerOnePercentFlashWear; 695 final String activityHandlerForFlashWearChanges; 696 final String intentReceiverForUnacceptableIoMetrics; 697 final int ioStatsNumSamplesToStore; 698 final int ioStatsRefreshRateMs; 699 final int maxExcessiveIoSamplesInWindow; 700 final long uptimeIntervalBetweenUptimeDataWriteMs; 701 final String eMmcLifetimeFilePath; 702 final String eMmcEolFilePath; 703 Configuration(Resources resources)704 Configuration(Resources resources) throws Resources.NotFoundException { 705 ioStatsNumSamplesToStore = resources.getInteger(R.integer.ioStatsNumSamplesToStore); 706 acceptableBytesWrittenPerSample = 707 1024 * resources.getInteger(R.integer.acceptableWrittenKBytesPerSample); 708 acceptableFsyncCallsPerSample = 709 resources.getInteger(R.integer.acceptableFsyncCallsPerSample); 710 maxExcessiveIoSamplesInWindow = 711 resources.getInteger(R.integer.maxExcessiveIoSamplesInWindow); 712 uptimeIntervalBetweenUptimeDataWriteMs = 60 * 60 * 1000 713 * resources.getInteger(R.integer.uptimeHoursIntervalBetweenUptimeDataWrite); 714 acceptableHoursPerOnePercentFlashWear = 715 resources.getInteger(R.integer.acceptableHoursPerOnePercentFlashWear); 716 ioStatsRefreshRateMs = 1000 * resources.getInteger(R.integer.ioStatsRefreshRateSeconds); 717 activityHandlerForFlashWearChanges = 718 resources.getString(R.string.activityHandlerForFlashWearChanges); 719 intentReceiverForUnacceptableIoMetrics = 720 resources.getString(R.string.intentReceiverForUnacceptableIoMetrics); 721 eMmcLifetimeFilePath = resources.getString(R.string.eMmcLifetimeFilePath); 722 eMmcEolFilePath = resources.getString(R.string.eMmcEolFilePath); 723 } 724 725 @Override toString()726 public String toString() { 727 return String.format("acceptableBytesWrittenPerSample = %d, " 728 + "acceptableFsyncCallsPerSample = %d, " 729 + "acceptableHoursPerOnePercentFlashWear = %d, " 730 + "activityHandlerForFlashWearChanges = %s, " 731 + "intentReceiverForUnacceptableIoMetrics = %s, " 732 + "ioStatsNumSamplesToStore = %d, " 733 + "ioStatsRefreshRateMs = %d, " 734 + "maxExcessiveIoSamplesInWindow = %d, " 735 + "uptimeIntervalBetweenUptimeDataWriteMs = %d, " 736 + "eMmcLifetimeFilePath = %s, " 737 + "eMmcEolFilePath = %s", 738 acceptableBytesWrittenPerSample, 739 acceptableFsyncCallsPerSample, 740 acceptableHoursPerOnePercentFlashWear, 741 activityHandlerForFlashWearChanges, 742 intentReceiverForUnacceptableIoMetrics, 743 ioStatsNumSamplesToStore, 744 ioStatsRefreshRateMs, 745 maxExcessiveIoSamplesInWindow, 746 uptimeIntervalBetweenUptimeDataWriteMs, 747 eMmcLifetimeFilePath, 748 eMmcEolFilePath); 749 } 750 } 751 } 752