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