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