• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car;
18 
19 import android.car.Car;
20 import android.car.storagemonitoring.CarStorageMonitoringManager;
21 import android.car.storagemonitoring.ICarStorageMonitoring;
22 import android.car.storagemonitoring.IIoStatsListener;
23 import android.car.storagemonitoring.IoStats;
24 import android.car.storagemonitoring.IoStatsEntry;
25 import android.car.storagemonitoring.IoStatsEntry.Metrics;
26 import android.car.storagemonitoring.LifetimeWriteInfo;
27 import android.car.storagemonitoring.UidIoRecord;
28 import android.car.storagemonitoring.WearEstimate;
29 import android.car.storagemonitoring.WearEstimateChange;
30 import android.content.ActivityNotFoundException;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.res.Resources;
35 import android.os.RemoteCallbackList;
36 import android.os.RemoteException;
37 import android.util.JsonWriter;
38 import android.util.Log;
39 import android.util.SparseArray;
40 
41 import com.android.car.internal.CarPermission;
42 import com.android.car.storagemonitoring.IoStatsTracker;
43 import com.android.car.storagemonitoring.UidIoStatsProvider;
44 import com.android.car.storagemonitoring.WearEstimateRecord;
45 import com.android.car.storagemonitoring.WearHistory;
46 import com.android.car.storagemonitoring.WearInformation;
47 import com.android.car.storagemonitoring.WearInformationProvider;
48 import com.android.car.systeminterface.SystemInterface;
49 
50 import org.json.JSONArray;
51 import org.json.JSONException;
52 import org.json.JSONObject;
53 
54 import java.io.File;
55 import java.io.FileWriter;
56 import java.io.IOException;
57 import java.io.PrintWriter;
58 import java.nio.file.Files;
59 import java.time.Duration;
60 import java.time.Instant;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collections;
64 import java.util.HashMap;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Objects;
68 import java.util.Optional;
69 import java.util.stream.Collectors;
70 import java.util.stream.IntStream;
71 
72 public class CarStorageMonitoringService extends ICarStorageMonitoring.Stub
73         implements CarServiceBase {
74     public static final String INTENT_EXCESSIVE_IO =
75             CarStorageMonitoringManager.INTENT_EXCESSIVE_IO;
76 
77     public static final long SHUTDOWN_COST_INFO_MISSING =
78             CarStorageMonitoringManager.SHUTDOWN_COST_INFO_MISSING;
79 
80     private static final boolean DBG = false;
81     private static final String TAG = CarLog.TAG_STORAGE;
82     private static final int MIN_WEAR_ESTIMATE_OF_CONCERN = 80;
83 
84     static final String UPTIME_TRACKER_FILENAME = "service_uptime";
85     static final String WEAR_INFO_FILENAME = "wear_info";
86     static final String LIFETIME_WRITES_FILENAME = "lifetime_write";
87 
88     private final WearInformationProvider[] mWearInformationProviders;
89     private final Context mContext;
90     private final File mUptimeTrackerFile;
91     private final File mWearInfoFile;
92     private final File mLifetimeWriteFile;
93     private final OnShutdownReboot mOnShutdownReboot;
94     private final SystemInterface mSystemInterface;
95     private final UidIoStatsProvider mUidIoStatsProvider;
96     private final SlidingWindow<IoStats> mIoStatsSamples;
97     private final RemoteCallbackList<IIoStatsListener> mListeners;
98     private final Object mIoStatsSamplesLock = new Object();
99     private final Configuration mConfiguration;
100 
101     private final CarPermission mStorageMonitoringPermission;
102 
103     private UptimeTracker mUptimeTracker = null;
104     private Optional<WearInformation> mWearInformation = Optional.empty();
105     private List<WearEstimateChange> mWearEstimateChanges = Collections.emptyList();
106     private List<IoStatsEntry> mBootIoStats = Collections.emptyList();
107     private IoStatsTracker mIoStatsTracker = null;
108     private boolean mInitialized = false;
109 
110     private long mShutdownCostInfo = SHUTDOWN_COST_INFO_MISSING;
111     private String mShutdownCostMissingReason;
112 
CarStorageMonitoringService(Context context, SystemInterface systemInterface)113     public CarStorageMonitoringService(Context context, SystemInterface systemInterface) {
114         mContext = context;
115         Resources resources = mContext.getResources();
116         mConfiguration = new Configuration(resources);
117 
118         if (Log.isLoggable(TAG, Log.DEBUG)) {
119             Log.d(TAG, "service configuration: " + mConfiguration);
120         }
121 
122         mUidIoStatsProvider = systemInterface.getUidIoStatsProvider();
123         mUptimeTrackerFile = new File(systemInterface.getFilesDir(), UPTIME_TRACKER_FILENAME);
124         mWearInfoFile = new File(systemInterface.getFilesDir(), WEAR_INFO_FILENAME);
125         mLifetimeWriteFile = new File(systemInterface.getFilesDir(), LIFETIME_WRITES_FILENAME);
126         mOnShutdownReboot = new OnShutdownReboot(mContext);
127         mSystemInterface = systemInterface;
128         mWearInformationProviders = systemInterface.getFlashWearInformationProviders();
129         mStorageMonitoringPermission =
130                 new CarPermission(mContext, Car.PERMISSION_STORAGE_MONITORING);
131         mWearEstimateChanges = Collections.emptyList();
132         mIoStatsSamples = new SlidingWindow<>(mConfiguration.ioStatsNumSamplesToStore);
133         mListeners = new RemoteCallbackList<>();
134         systemInterface.scheduleActionForBootCompleted(this::doInitServiceIfNeeded,
135             Duration.ofSeconds(10));
136     }
137 
loadWearInformation()138     private Optional<WearInformation> loadWearInformation() {
139         for (WearInformationProvider provider : mWearInformationProviders) {
140             WearInformation wearInfo = provider.load();
141             if (wearInfo != null) {
142                 Log.d(TAG, "retrieved wear info " + wearInfo + " via provider " + provider);
143                 return Optional.of(wearInfo);
144             }
145         }
146 
147         Log.d(TAG, "no wear info available");
148         return Optional.empty();
149     }
150 
loadWearHistory()151     private WearHistory loadWearHistory() {
152         if (mWearInfoFile.exists()) {
153             try {
154                 WearHistory wearHistory = WearHistory.fromJson(mWearInfoFile);
155                 Log.d(TAG, "retrieved wear history " + wearHistory);
156                 return wearHistory;
157             } catch (IOException | JSONException e) {
158                 Log.e(TAG, "unable to read wear info file " + mWearInfoFile, e);
159             }
160         }
161 
162         Log.d(TAG, "no wear history available");
163         return new WearHistory();
164     }
165 
166     // returns true iff a new event was added (and hence the history needs to be saved)
addEventIfNeeded(WearHistory wearHistory)167     private boolean addEventIfNeeded(WearHistory wearHistory) {
168         if (!mWearInformation.isPresent()) return false;
169 
170         WearInformation wearInformation = mWearInformation.get();
171         WearEstimate lastWearEstimate;
172         WearEstimate currentWearEstimate = wearInformation.toWearEstimate();
173 
174         if (wearHistory.size() == 0) {
175             lastWearEstimate = WearEstimate.UNKNOWN_ESTIMATE;
176         } else {
177             lastWearEstimate = wearHistory.getLast().getNewWearEstimate();
178         }
179 
180         if (currentWearEstimate.equals(lastWearEstimate)) return false;
181 
182         WearEstimateRecord newRecord = new WearEstimateRecord(lastWearEstimate,
183             currentWearEstimate,
184             mUptimeTracker.getTotalUptime(),
185             Instant.now());
186         Log.d(TAG, "new wear record generated " + newRecord);
187         wearHistory.add(newRecord);
188         return true;
189     }
190 
storeWearHistory(WearHistory wearHistory)191     private void storeWearHistory(WearHistory wearHistory) {
192         try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(mWearInfoFile))) {
193             wearHistory.writeToJson(jsonWriter);
194         } catch (IOException e) {
195             Log.e(TAG, "unable to write wear info file" + mWearInfoFile, e);
196         }
197     }
198 
199     @Override
init()200     public void init() {
201         Log.d(TAG, "CarStorageMonitoringService init()");
202 
203         mUptimeTracker = new UptimeTracker(mUptimeTrackerFile,
204             mConfiguration.uptimeIntervalBetweenUptimeDataWriteMs,
205             mSystemInterface);
206     }
207 
launchWearChangeActivity()208     private void launchWearChangeActivity() {
209         final String activityPath = mConfiguration.activityHandlerForFlashWearChanges;
210         if (activityPath.isEmpty()) return;
211         try {
212             final ComponentName activityComponent =
213                 Objects.requireNonNull(ComponentName.unflattenFromString(activityPath));
214             Intent intent = new Intent();
215             intent.setComponent(activityComponent);
216             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
217             mContext.startActivity(intent);
218         } catch (ActivityNotFoundException | NullPointerException e) {
219             Log.e(TAG,
220                 "value of activityHandlerForFlashWearChanges invalid non-empty string " +
221                     activityPath, e);
222         }
223     }
224 
logOnAdverseWearLevel(WearInformation wearInformation)225     private static void logOnAdverseWearLevel(WearInformation wearInformation) {
226         if (wearInformation.preEolInfo > WearInformation.PRE_EOL_INFO_NORMAL ||
227             Math.max(wearInformation.lifetimeEstimateA,
228                 wearInformation.lifetimeEstimateB) >= MIN_WEAR_ESTIMATE_OF_CONCERN) {
229             Log.w(TAG, "flash storage reached wear a level that requires attention: "
230                     + wearInformation);
231         }
232     }
233 
loadNewIoStats()234     private SparseArray<UidIoRecord> loadNewIoStats() {
235         SparseArray<UidIoRecord> ioRecords = mUidIoStatsProvider.load();
236         return (ioRecords == null ? new SparseArray<>() : ioRecords);
237     }
238 
collectNewIoMetrics()239     private void collectNewIoMetrics() {
240         IoStats ioStats;
241 
242         mIoStatsTracker.update(loadNewIoStats());
243         synchronized (mIoStatsSamplesLock) {
244             ioStats = new IoStats(
245                 SparseArrayStream.valueStream(mIoStatsTracker.getCurrentSample())
246                     .collect(Collectors.toList()),
247                 mSystemInterface.getUptime());
248             mIoStatsSamples.add(ioStats);
249         }
250 
251         if (DBG) {
252             SparseArray<IoStatsEntry> currentSample = mIoStatsTracker.getCurrentSample();
253             if (currentSample.size() == 0) {
254                 Log.d(TAG, "no new I/O stat data");
255             } else {
256                 SparseArrayStream.valueStream(currentSample).forEach(
257                     uidIoStats -> Log.d(TAG, "updated I/O stat data: " + uidIoStats));
258             }
259         }
260 
261         dispatchNewIoEvent(ioStats);
262         if (needsExcessiveIoBroadcast()) {
263             Log.d(TAG, "about to send " + INTENT_EXCESSIVE_IO);
264             sendExcessiveIoBroadcast();
265         }
266     }
267 
sendExcessiveIoBroadcast()268     private void sendExcessiveIoBroadcast() {
269         Log.w(TAG, "sending " + INTENT_EXCESSIVE_IO);
270 
271         final String receiverPath = mConfiguration.intentReceiverForUnacceptableIoMetrics;
272         if (receiverPath.isEmpty()) return;
273 
274         final ComponentName receiverComponent;
275         try {
276             receiverComponent = Objects.requireNonNull(
277                     ComponentName.unflattenFromString(receiverPath));
278         } catch (NullPointerException e) {
279             Log.e(TAG, "value of intentReceiverForUnacceptableIoMetrics non-null but invalid:"
280                     + receiverPath, e);
281             return;
282         }
283 
284         Intent intent = new Intent(INTENT_EXCESSIVE_IO);
285         intent.setComponent(receiverComponent);
286         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
287         mContext.sendBroadcast(intent, mStorageMonitoringPermission.toString());
288     }
289 
needsExcessiveIoBroadcast()290     private boolean needsExcessiveIoBroadcast() {
291         synchronized (mIoStatsSamplesLock) {
292             return mIoStatsSamples.count((IoStats delta) -> {
293                 Metrics total = delta.getTotals();
294                 final boolean tooManyBytesWritten =
295                     (total.bytesWrittenToStorage > mConfiguration.acceptableBytesWrittenPerSample);
296                 final boolean tooManyFsyncCalls =
297                     (total.fsyncCalls > mConfiguration.acceptableFsyncCallsPerSample);
298                 return tooManyBytesWritten || tooManyFsyncCalls;
299             }) > mConfiguration.maxExcessiveIoSamplesInWindow;
300         }
301     }
302 
dispatchNewIoEvent(IoStats delta)303     private void dispatchNewIoEvent(IoStats delta) {
304         final int listenersCount = mListeners.beginBroadcast();
305         IntStream.range(0, listenersCount).forEach(
306             i -> {
307                 try {
308                     mListeners.getBroadcastItem(i).onSnapshot(delta);
309                 } catch (RemoteException e) {
310                     Log.w(TAG, "failed to dispatch snapshot", e);
311                 }
312             });
313         mListeners.finishBroadcast();
314     }
315 
doInitServiceIfNeeded()316     private synchronized void doInitServiceIfNeeded() {
317         if (mInitialized) return;
318 
319         Log.d(TAG, "initializing CarStorageMonitoringService");
320 
321         mWearInformation = loadWearInformation();
322 
323         // TODO(egranata): can this be done lazily?
324         final WearHistory wearHistory = loadWearHistory();
325         final boolean didWearChangeHappen = addEventIfNeeded(wearHistory);
326         if (didWearChangeHappen) {
327             storeWearHistory(wearHistory);
328         }
329         Log.d(TAG, "wear history being tracked is " + wearHistory);
330         mWearEstimateChanges = wearHistory.toWearEstimateChanges(
331                 mConfiguration.acceptableHoursPerOnePercentFlashWear);
332 
333         mOnShutdownReboot.addAction((c, i) -> logLifetimeWrites())
334                 .addAction((c, i) -> release());
335 
336         mWearInformation.ifPresent(CarStorageMonitoringService::logOnAdverseWearLevel);
337 
338         if (didWearChangeHappen) {
339             launchWearChangeActivity();
340         }
341 
342         long bootUptime = mSystemInterface.getUptime();
343         mBootIoStats = SparseArrayStream.valueStream(loadNewIoStats())
344             .map(record -> {
345                 // at boot, assume all UIDs have been running for as long as the system has
346                 // been up, since we don't really know any better
347                 IoStatsEntry stats = new IoStatsEntry(record, bootUptime);
348                 if (DBG) {
349                     Log.d(TAG, "loaded boot I/O stat data: " + stats);
350                 }
351                 return stats;
352             }).collect(Collectors.toList());
353 
354         mIoStatsTracker = new IoStatsTracker(mBootIoStats,
355                 mConfiguration.ioStatsRefreshRateMs,
356                 mSystemInterface.getSystemStateInterface());
357 
358         if (mConfiguration.ioStatsNumSamplesToStore > 0) {
359             mSystemInterface.scheduleAction(this::collectNewIoMetrics,
360                 mConfiguration.ioStatsRefreshRateMs);
361         } else {
362             Log.i(TAG, "service configuration disabled I/O sample window. not collecting samples");
363         }
364 
365         mShutdownCostInfo = computeShutdownCost();
366         Log.d(TAG, "calculated data written in last shutdown was " +
367                 mShutdownCostInfo + " bytes");
368         mLifetimeWriteFile.delete();
369 
370         Log.i(TAG, "CarStorageMonitoringService is up");
371 
372         mInitialized = true;
373     }
374 
computeShutdownCost()375     private long computeShutdownCost() {
376         List<LifetimeWriteInfo> shutdownWrites = loadLifetimeWrites();
377         if (shutdownWrites.isEmpty()) {
378             Log.d(TAG, "lifetime write data from last shutdown missing");
379             mShutdownCostMissingReason = "no historical writes stored at last shutdown";
380             return SHUTDOWN_COST_INFO_MISSING;
381         }
382         List<LifetimeWriteInfo> currentWrites =
383                 Arrays.asList(mSystemInterface.getLifetimeWriteInfoProvider().load());
384         if (currentWrites.isEmpty()) {
385             Log.d(TAG, "current lifetime write data missing");
386             mShutdownCostMissingReason = "current write data cannot be obtained";
387             return SHUTDOWN_COST_INFO_MISSING;
388         }
389 
390         long shutdownCost = 0;
391 
392         Map<String, Long> shutdownLifetimeWrites = new HashMap<>();
393         shutdownWrites.forEach(li ->
394             shutdownLifetimeWrites.put(li.partition, li.writtenBytes));
395 
396         // for every partition currently available, look for it in the shutdown data
397         for(int i = 0; i < currentWrites.size(); ++i) {
398             LifetimeWriteInfo li = currentWrites.get(i);
399             // if this partition was not available when we last shutdown the system, then
400             // just pretend we had written the same amount of data then as we have now
401             final long writtenAtShutdown =
402                     shutdownLifetimeWrites.getOrDefault(li.partition, li.writtenBytes);
403             final long costDelta = li.writtenBytes - writtenAtShutdown;
404             if (costDelta >= 0) {
405                 Log.d(TAG, "partition " + li.partition + " had " + costDelta +
406                     " bytes written to it during shutdown");
407                 shutdownCost += costDelta;
408             } else {
409                 // the counter of written bytes should be monotonic; a decrease might mean
410                 // corrupt data, improper shutdown or that the kernel in use does not
411                 // have proper monotonic guarantees on the lifetime write data. If any of these
412                 // occur, it's probably safer to just bail out and say we don't know
413                 mShutdownCostMissingReason = li.partition + " has a negative write amount (" +
414                         costDelta + " bytes)";
415                 Log.e(TAG, "partition " + li.partition + " reported " + costDelta +
416                     " bytes written to it during shutdown. assuming we can't" +
417                     " determine proper shutdown information.");
418                 return SHUTDOWN_COST_INFO_MISSING;
419             }
420         }
421 
422         return shutdownCost;
423     }
424 
loadLifetimeWrites()425     private List<LifetimeWriteInfo> loadLifetimeWrites() {
426         if (!mLifetimeWriteFile.exists() || !mLifetimeWriteFile.isFile()) {
427             Log.d(TAG, "lifetime write file missing or inaccessible " + mLifetimeWriteFile);
428             return Collections.emptyList();
429         }
430         try {
431             JSONObject jsonObject = new JSONObject(
432                 new String(Files.readAllBytes(mLifetimeWriteFile.toPath())));
433 
434             JSONArray jsonArray = jsonObject.getJSONArray("lifetimeWriteInfo");
435 
436             List<LifetimeWriteInfo> result = new ArrayList<>();
437             for (int i = 0; i < jsonArray.length(); ++i) {
438                 result.add(new LifetimeWriteInfo(jsonArray.getJSONObject(i)));
439             }
440             return result;
441         } catch (JSONException | IOException e) {
442             Log.e(TAG, "lifetime write file does not contain valid JSON", e);
443             return Collections.emptyList();
444         }
445     }
446 
logLifetimeWrites()447     private void logLifetimeWrites() {
448         try {
449             LifetimeWriteInfo[] lifetimeWriteInfos =
450                 mSystemInterface.getLifetimeWriteInfoProvider().load();
451             JsonWriter jsonWriter = new JsonWriter(new FileWriter(mLifetimeWriteFile));
452             jsonWriter.beginObject();
453             jsonWriter.name("lifetimeWriteInfo").beginArray();
454             for (LifetimeWriteInfo writeInfo : lifetimeWriteInfos) {
455                 Log.d(TAG, "storing lifetime write info " + writeInfo);
456                 writeInfo.writeToJson(jsonWriter);
457             }
458             jsonWriter.endArray().endObject();
459             jsonWriter.close();
460         } catch (IOException e) {
461             Log.e(TAG, "unable to save lifetime write info on shutdown", e);
462         }
463     }
464 
465     @Override
release()466     public void release() {
467         Log.i(TAG, "tearing down CarStorageMonitoringService");
468         if (mUptimeTracker != null) {
469             mUptimeTracker.onDestroy();
470         }
471         mOnShutdownReboot.clearActions();
472         mListeners.kill();
473     }
474 
475     @Override
dump(PrintWriter writer)476     public void dump(PrintWriter writer) {
477         doInitServiceIfNeeded();
478 
479         writer.println("*CarStorageMonitoringService*");
480         writer.println("last wear information retrieved: " +
481             mWearInformation.map(WearInformation::toString).orElse("missing"));
482         writer.println("wear change history: " +
483             mWearEstimateChanges.stream()
484                 .map(WearEstimateChange::toString)
485                 .collect(Collectors.joining("\n")));
486         writer.println("boot I/O stats: " +
487             mBootIoStats.stream()
488                 .map(IoStatsEntry::toString)
489                 .collect(Collectors.joining("\n")));
490         writer.println("aggregate I/O stats: " +
491             SparseArrayStream.valueStream(mIoStatsTracker.getTotal())
492                 .map(IoStatsEntry::toString)
493                 .collect(Collectors.joining("\n")));
494         writer.println("I/O stats snapshots: ");
495         synchronized (mIoStatsSamplesLock) {
496             writer.println(
497                 mIoStatsSamples.stream().map(
498                     sample -> sample.getStats().stream()
499                         .map(IoStatsEntry::toString)
500                         .collect(Collectors.joining("\n")))
501                     .collect(Collectors.joining("\n------\n")));
502         }
503         if (mShutdownCostInfo < 0) {
504             writer.print("last shutdown cost: missing. ");
505             if (mShutdownCostMissingReason != null && !mShutdownCostMissingReason.isEmpty()) {
506                 writer.println("reason: " + mShutdownCostMissingReason);
507             }
508         } else {
509             writer.println("last shutdown cost: " + mShutdownCostInfo + " bytes, estimated");
510         }
511     }
512 
513     // ICarStorageMonitoring implementation
514 
515     @Override
getPreEolIndicatorStatus()516     public int getPreEolIndicatorStatus() {
517         mStorageMonitoringPermission.assertGranted();
518         doInitServiceIfNeeded();
519 
520         return mWearInformation.map(wi -> wi.preEolInfo)
521                 .orElse(WearInformation.UNKNOWN_PRE_EOL_INFO);
522     }
523 
524     @Override
getWearEstimate()525     public WearEstimate getWearEstimate() {
526         mStorageMonitoringPermission.assertGranted();
527         doInitServiceIfNeeded();
528 
529         return mWearInformation.map(wi ->
530                 new WearEstimate(wi.lifetimeEstimateA,wi.lifetimeEstimateB)).orElse(
531                     WearEstimate.UNKNOWN_ESTIMATE);
532     }
533 
534     @Override
getWearEstimateHistory()535     public List<WearEstimateChange> getWearEstimateHistory() {
536         mStorageMonitoringPermission.assertGranted();
537         doInitServiceIfNeeded();
538 
539         return mWearEstimateChanges;
540     }
541 
542     @Override
getBootIoStats()543     public List<IoStatsEntry> getBootIoStats() {
544         mStorageMonitoringPermission.assertGranted();
545         doInitServiceIfNeeded();
546 
547         return mBootIoStats;
548     }
549 
550     @Override
getAggregateIoStats()551     public List<IoStatsEntry> getAggregateIoStats() {
552         mStorageMonitoringPermission.assertGranted();
553         doInitServiceIfNeeded();
554 
555         return SparseArrayStream.valueStream(mIoStatsTracker.getTotal())
556                 .collect(Collectors.toList());
557     }
558 
559     @Override
getShutdownDiskWriteAmount()560     public long getShutdownDiskWriteAmount() {
561         mStorageMonitoringPermission.assertGranted();
562         doInitServiceIfNeeded();
563 
564         return mShutdownCostInfo;
565     }
566 
567     @Override
getIoStatsDeltas()568     public List<IoStats> getIoStatsDeltas() {
569         mStorageMonitoringPermission.assertGranted();
570         doInitServiceIfNeeded();
571 
572         synchronized (mIoStatsSamplesLock) {
573             return mIoStatsSamples.stream().collect(Collectors.toList());
574         }
575     }
576 
577     @Override
registerListener(IIoStatsListener listener)578     public void registerListener(IIoStatsListener listener) {
579         mStorageMonitoringPermission.assertGranted();
580         doInitServiceIfNeeded();
581 
582         mListeners.register(listener);
583     }
584 
585     @Override
unregisterListener(IIoStatsListener listener)586     public void unregisterListener(IIoStatsListener listener) {
587         mStorageMonitoringPermission.assertGranted();
588         // no need to initialize service if unregistering
589 
590         mListeners.unregister(listener);
591     }
592 
593     private static final class Configuration {
594         final long acceptableBytesWrittenPerSample;
595         final int acceptableFsyncCallsPerSample;
596         final int acceptableHoursPerOnePercentFlashWear;
597         final String activityHandlerForFlashWearChanges;
598         final String intentReceiverForUnacceptableIoMetrics;
599         final int ioStatsNumSamplesToStore;
600         final int ioStatsRefreshRateMs;
601         final int maxExcessiveIoSamplesInWindow;
602         final long uptimeIntervalBetweenUptimeDataWriteMs;
603 
Configuration(Resources resources)604         Configuration(Resources resources) throws Resources.NotFoundException {
605             ioStatsNumSamplesToStore = resources.getInteger(R.integer.ioStatsNumSamplesToStore);
606             acceptableBytesWrittenPerSample =
607                     1024 * resources.getInteger(R.integer.acceptableWrittenKBytesPerSample);
608             acceptableFsyncCallsPerSample =
609                     resources.getInteger(R.integer.acceptableFsyncCallsPerSample);
610             maxExcessiveIoSamplesInWindow =
611                     resources.getInteger(R.integer.maxExcessiveIoSamplesInWindow);
612             uptimeIntervalBetweenUptimeDataWriteMs =
613                         60 * 60 * 1000 *
614                         resources.getInteger(R.integer.uptimeHoursIntervalBetweenUptimeDataWrite);
615             acceptableHoursPerOnePercentFlashWear =
616                     resources.getInteger(R.integer.acceptableHoursPerOnePercentFlashWear);
617             ioStatsRefreshRateMs =
618                     1000 * resources.getInteger(R.integer.ioStatsRefreshRateSeconds);
619             activityHandlerForFlashWearChanges =
620                     resources.getString(R.string.activityHandlerForFlashWearChanges);
621             intentReceiverForUnacceptableIoMetrics =
622                     resources.getString(R.string.intentReceiverForUnacceptableIoMetrics);
623         }
624 
625         @Override
toString()626         public String toString() {
627             return String.format(
628                 "acceptableBytesWrittenPerSample = %d, " +
629                 "acceptableFsyncCallsPerSample = %d, " +
630                 "acceptableHoursPerOnePercentFlashWear = %d, " +
631                 "activityHandlerForFlashWearChanges = %s, " +
632                 "intentReceiverForUnacceptableIoMetrics = %s, " +
633                 "ioStatsNumSamplesToStore = %d, " +
634                 "ioStatsRefreshRateMs = %d, " +
635                 "maxExcessiveIoSamplesInWindow = %d, " +
636                 "uptimeIntervalBetweenUptimeDataWriteMs = %d",
637                 acceptableBytesWrittenPerSample,
638                 acceptableFsyncCallsPerSample,
639                 acceptableHoursPerOnePercentFlashWear,
640                 activityHandlerForFlashWearChanges,
641                 intentReceiverForUnacceptableIoMetrics,
642                 ioStatsNumSamplesToStore,
643                 ioStatsRefreshRateMs,
644                 maxExcessiveIoSamplesInWindow,
645                 uptimeIntervalBetweenUptimeDataWriteMs);
646         }
647     }
648 }
649