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 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 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 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) 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 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 200 public void init() { 201 Log.d(TAG, "CarStorageMonitoringService init()"); 202 203 mUptimeTracker = new UptimeTracker(mUptimeTrackerFile, 204 mConfiguration.uptimeIntervalBetweenUptimeDataWriteMs, 205 mSystemInterface); 206 } 207 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 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 234 private SparseArray<UidIoRecord> loadNewIoStats() { 235 SparseArray<UidIoRecord> ioRecords = mUidIoStatsProvider.load(); 236 return (ioRecords == null ? new SparseArray<>() : ioRecords); 237 } 238 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 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 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 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 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 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 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 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 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 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 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 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 535 public List<WearEstimateChange> getWearEstimateHistory() { 536 mStorageMonitoringPermission.assertGranted(); 537 doInitServiceIfNeeded(); 538 539 return mWearEstimateChanges; 540 } 541 542 @Override 543 public List<IoStatsEntry> getBootIoStats() { 544 mStorageMonitoringPermission.assertGranted(); 545 doInitServiceIfNeeded(); 546 547 return mBootIoStats; 548 } 549 550 @Override 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 560 public long getShutdownDiskWriteAmount() { 561 mStorageMonitoringPermission.assertGranted(); 562 doInitServiceIfNeeded(); 563 564 return mShutdownCostInfo; 565 } 566 567 @Override 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 578 public void registerListener(IIoStatsListener listener) { 579 mStorageMonitoringPermission.assertGranted(); 580 doInitServiceIfNeeded(); 581 582 mListeners.register(listener); 583 } 584 585 @Override 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 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 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