• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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