• 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 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