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