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