Default setup for excessive I/O notification
- Set sample default values for the thresholds that trigger
the I/O notification;
- Centralize tracking of the service's configuration;
- Implement a default broadcast agent.
Test: bit CarServiceTest:com.android.car.CarStorageMonitoringTest
Change-Id: I9eb0259eedd34a27532cb621383b87075c6460a3
diff --git a/car_product/overlay/packages/services/Car/service/res/values/Config.xml b/car_product/overlay/packages/services/Car/service/res/values/Config.xml
index 0680f15..021143c 100644
--- a/car_product/overlay/packages/services/Car/service/res/values/Config.xml
+++ b/car_product/overlay/packages/services/Car/service/res/values/Config.xml
@@ -1,4 +1,9 @@
<resources>
<!-- default activity whitelist which are allowed while driving -->
<string name="defauiltActivityWhitelist">com.android.systemui,com.android.car.dialer,com.android.car.hvac,com.android.car.media,com.android.car.radio,com.android.support.car.lenspicker,com.google.android.setupwizard</string>
+
+ <integer name="acceptableWrittenKBytesPerSample">115000</integer>
+ <integer name="acceptableFsyncCallsPerSample">150</integer>
+ <integer name="maxExcessiveIoSamplesInWindow">11</integer>
+ <string name="intentReceiverForUnacceptableIoMetrics">com.google.android.car.defaultstoragemonitoringcompanionapp/.ExcessiveIoIntentReceiver</string>
</resources>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 21925a3..69cc748 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -107,7 +107,10 @@
<integer name="ioStatsNumSamplesToStore">15</integer>
<!-- The maximum number of KB (1024 bytes) that can be written to storage in one sample
- before CarService deems I/O activity excessive. -->
+ before CarService deems I/O activity excessive. A simple way to set this value
+ is - given the total writable amount (storage size * P/E cycles) - to make
+ reasonable assumptions about the expected lifetime of the vehicle and the average
+ daily driving time, and use that to allocate a per-sample budget. -->
<integer name="acceptableWrittenKBytesPerSample">0</integer>
<!-- The maximum number of fsync() system calls that can be made in one sample before
CarService deems I/O activity excessive. -->
@@ -121,6 +124,9 @@
<!-- The name of an intent to be notified by CarService whenever it detects too many
samples with excessive I/O activity. Value must either be an empty string, which
means that no notification will take place, or be in the format of a flattened
- ComponentName and reference a valid BroadcastReceiver. -->
+ ComponentName and reference a valid BroadcastReceiver. This broadcast receiver
+ must be registered in its containing app's AndroidManifest.xml and it is
+ strongly recommended that it be protected with the
+ android.car.permission.STORAGE_MONITORING permission. -->
<string name="intentReceiverForUnacceptableIoMetrics"></string>
</resources>
diff --git a/service/src/com/android/car/CarStorageMonitoringService.java b/service/src/com/android/car/CarStorageMonitoringService.java
index f262b6f..25f885a 100644
--- a/service/src/com/android/car/CarStorageMonitoringService.java
+++ b/service/src/com/android/car/CarStorageMonitoringService.java
@@ -80,9 +80,7 @@
private final SlidingWindow<UidIoStatsDelta> mIoStatsSamples;
private final RemoteCallbackList<IUidIoStatsListener> mListeners;
private final Object mIoStatsSamplesLock = new Object();
- private final long mAcceptableBytesWrittenPerSample;
- private final int mAcceptableFsyncCallsPerSample;
- private final int mThresholdSamplesCount;
+ private final Configuration mConfiguration;
private final CarPermission mStorageMonitoringPermission;
@@ -96,6 +94,10 @@
public CarStorageMonitoringService(Context context, SystemInterface systemInterface) {
mContext = context;
Resources resources = mContext.getResources();
+ mConfiguration = new Configuration(resources);
+
+ Log.d(TAG, "service configuration: " + mConfiguration);
+
mUidIoStatsProvider = systemInterface.getUidIoStatsProvider();
mUptimeTrackerFile = new File(systemInterface.getFilesDir(), UPTIME_TRACKER_FILENAME);
mWearInfoFile = new File(systemInterface.getFilesDir(), WEAR_INFO_FILENAME);
@@ -105,20 +107,10 @@
mStorageMonitoringPermission =
new CarPermission(mContext, Car.PERMISSION_STORAGE_MONITORING);
mWearEstimateChanges = Collections.emptyList();
- mIoStatsSamples = new SlidingWindow<>(
- resources.getInteger(R.integer.ioStatsNumSamplesToStore));
+ mIoStatsSamples = new SlidingWindow<>(mConfiguration.ioStatsNumSamplesToStore);
+ mListeners = new RemoteCallbackList<>();
systemInterface.scheduleActionForBootCompleted(this::doInitServiceIfNeeded,
Duration.ofSeconds(10));
- mListeners = new RemoteCallbackList<>();
- mAcceptableBytesWrittenPerSample = 1024 * resources.getInteger(
- R.integer.acceptableWrittenKBytesPerSample);
- mAcceptableFsyncCallsPerSample = resources.getInteger(
- R.integer.acceptableFsyncCallsPerSample);
- mThresholdSamplesCount = resources.getInteger(R.integer.maxExcessiveIoSamplesInWindow);
- }
-
- private static long getUptimeSnapshotIntervalMs() {
- return Duration.ofHours(R.integer.uptimeHoursIntervalBetweenUptimeDataWrite).toMillis();
}
private Optional<WearInformation> loadWearInformation() {
@@ -187,13 +179,12 @@
Log.d(TAG, "CarStorageMonitoringService init()");
mUptimeTracker = new UptimeTracker(mUptimeTrackerFile,
- getUptimeSnapshotIntervalMs(),
+ mConfiguration.uptimeIntervalBetweenUptimeDataWriteMs,
mSystemInterface);
}
private void launchWearChangeActivity() {
- final String activityPath = mContext.getResources().getString(
- R.string.activityHandlerForFlashWearChanges);
+ final String activityPath = mConfiguration.activityHandlerForFlashWearChanges;
if (activityPath.isEmpty()) return;
try {
final ComponentName activityComponent =
@@ -254,8 +245,7 @@
private void sendExcessiveIoBroadcast() {
Log.w(TAG, "sending excessive I/O notification");
- final String receiverPath = mContext.getResources().getString(
- R.string.intentReceiverForUnacceptableIoMetrics);
+ final String receiverPath = mConfiguration.intentReceiverForUnacceptableIoMetrics;
if (receiverPath.isEmpty()) return;
final ComponentName receiverComponent;
@@ -277,9 +267,12 @@
synchronized (mIoStatsSamplesLock) {
return mIoStatsSamples.count((UidIoStatsDelta delta) -> {
Metrics total = delta.getTotals();
- return (total.bytesWrittenToStorage > mAcceptableBytesWrittenPerSample) ||
- (total.fsyncCalls > mAcceptableFsyncCallsPerSample);
- }) > mThresholdSamplesCount;
+ final boolean tooManyBytesWritten =
+ (total.bytesWrittenToStorage > mConfiguration.acceptableBytesWrittenPerSample);
+ final boolean tooManyFsyncCalls =
+ (total.fsyncCalls > mConfiguration.acceptableFsyncCallsPerSample);
+ return tooManyBytesWritten || tooManyFsyncCalls;
+ }) > mConfiguration.maxExcessiveIoSamplesInWindow;
}
}
@@ -301,8 +294,6 @@
Log.d(TAG, "initializing CarStorageMonitoringService");
- final Resources resources = mContext.getResources();
-
mWearInformation = loadWearInformation();
// TODO(egranata): can this be done lazily?
@@ -312,8 +303,8 @@
storeWearHistory(wearHistory);
}
Log.d(TAG, "wear history being tracked is " + wearHistory);
- mWearEstimateChanges = wearHistory.toWearEstimateChanges(resources.getInteger(
- R.integer.acceptableHoursPerOnePercentFlashWear));
+ mWearEstimateChanges = wearHistory.toWearEstimateChanges(
+ mConfiguration.acceptableHoursPerOnePercentFlashWear);
mOnShutdownReboot.addAction((Context ctx, Intent intent) -> release());
@@ -335,14 +326,12 @@
return stats;
}).collect(Collectors.toList());
- final long newStatsDelayMs =
- 1000L * resources.getInteger(R.integer.ioStatsRefreshRateSeconds);
-
mIoStatsTracker = new IoStatsTracker(mBootIoStats,
- newStatsDelayMs,
+ mConfiguration.ioStatsRefreshRateMs,
mSystemInterface.getSystemStateInterface());
- mSystemInterface.scheduleAction(this::collectNewIoMetrics, newStatsDelayMs);
+ mSystemInterface.scheduleAction(this::collectNewIoMetrics,
+ mConfiguration.ioStatsRefreshRateMs);
Log.i(TAG, "CarStorageMonitoringService is up");
@@ -460,4 +449,60 @@
mListeners.unregister(listener);
}
+
+ private static final class Configuration {
+ final long acceptableBytesWrittenPerSample;
+ final int acceptableFsyncCallsPerSample;
+ final int acceptableHoursPerOnePercentFlashWear;
+ final String activityHandlerForFlashWearChanges;
+ final String intentReceiverForUnacceptableIoMetrics;
+ final int ioStatsNumSamplesToStore;
+ final int ioStatsRefreshRateMs;
+ final int maxExcessiveIoSamplesInWindow;
+ final long uptimeIntervalBetweenUptimeDataWriteMs;
+
+ Configuration(Resources resources) throws Resources.NotFoundException {
+ ioStatsNumSamplesToStore = resources.getInteger(R.integer.ioStatsNumSamplesToStore);
+ acceptableBytesWrittenPerSample =
+ 1024 * resources.getInteger(R.integer.acceptableWrittenKBytesPerSample);
+ acceptableFsyncCallsPerSample =
+ resources.getInteger(R.integer.acceptableFsyncCallsPerSample);
+ maxExcessiveIoSamplesInWindow =
+ resources.getInteger(R.integer.maxExcessiveIoSamplesInWindow);
+ uptimeIntervalBetweenUptimeDataWriteMs =
+ 60 * 60 * 1000 *
+ resources.getInteger(R.integer.uptimeHoursIntervalBetweenUptimeDataWrite);
+ acceptableHoursPerOnePercentFlashWear =
+ resources.getInteger(R.integer.acceptableHoursPerOnePercentFlashWear);
+ ioStatsRefreshRateMs =
+ 1000 * resources.getInteger(R.integer.ioStatsRefreshRateSeconds);
+ activityHandlerForFlashWearChanges =
+ resources.getString(R.string.activityHandlerForFlashWearChanges);
+ intentReceiverForUnacceptableIoMetrics =
+ resources.getString(R.string.intentReceiverForUnacceptableIoMetrics);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "acceptableBytesWrittenPerSample = %d, " +
+ "acceptableFsyncCallsPerSample = %d, " +
+ "acceptableHoursPerOnePercentFlashWear = %d, " +
+ "activityHandlerForFlashWearChanges = %s, " +
+ "intentReceiverForUnacceptableIoMetrics = %s, " +
+ "ioStatsNumSamplesToStore = %d, " +
+ "ioStatsRefreshRateMs = %d, " +
+ "maxExcessiveIoSamplesInWindow = %d, " +
+ "uptimeIntervalBetweenUptimeDataWriteMs = %d",
+ acceptableBytesWrittenPerSample,
+ acceptableFsyncCallsPerSample,
+ acceptableHoursPerOnePercentFlashWear,
+ activityHandlerForFlashWearChanges,
+ intentReceiverForUnacceptableIoMetrics,
+ ioStatsNumSamplesToStore,
+ ioStatsRefreshRateMs,
+ maxExcessiveIoSamplesInWindow,
+ uptimeIntervalBetweenUptimeDataWriteMs);
+ }
+ }
}
diff --git a/tests/DefaultStorageMonitoringCompanionApp/AndroidManifest.xml b/tests/DefaultStorageMonitoringCompanionApp/AndroidManifest.xml
index 45380ce..a676e18 100644
--- a/tests/DefaultStorageMonitoringCompanionApp/AndroidManifest.xml
+++ b/tests/DefaultStorageMonitoringCompanionApp/AndroidManifest.xml
@@ -31,6 +31,16 @@
android:exported="true"
android:permission="android.car.permission.STORAGE_MONITORING">
</activity>
+
+ <receiver
+ android:name=".ExcessiveIoIntentReceiver"
+ android:exported="true"
+ android:permission="android.car.permission.STORAGE_MONITORING">
+ <intent-filter>
+ <action android:name="android.car.storagemonitoring.EXCESSIVE_IO"/>
+ </intent-filter>
+ </receiver>
+
</application>
</manifest>
diff --git a/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/ExcessiveIoIntentReceiver.java b/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/ExcessiveIoIntentReceiver.java
new file mode 100644
index 0000000..2190bbd
--- /dev/null
+++ b/tests/DefaultStorageMonitoringCompanionApp/src/com/google/android/car/defaultstoragemonitoringcompanionapp/ExcessiveIoIntentReceiver.java
@@ -0,0 +1,20 @@
+package com.google.android.car.defaultstoragemonitoringcompanionapp;
+
+import android.car.storagemonitoring.CarStorageMonitoringManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class ExcessiveIoIntentReceiver extends BroadcastReceiver {
+ private final static String TAG = ExcessiveIoIntentReceiver.class.getSimpleName();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (CarStorageMonitoringManager.INTENT_EXCESSIVE_IO.equals(intent.getAction())) {
+ Log.d(TAG, "excessive I/O activity detected.");
+ } else {
+ Log.w(TAG, "unexpected intent received: " + intent);
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml b/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
index b310c3d..cf98695 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
@@ -45,6 +45,12 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Write 1K"/>
+ <Button
+ android:id="@+id/perform_fsync"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Call fsync()"/>
</LinearLayout>
<TextView
android:id="@+id/free_disk_space"
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
index a229904..69d4af4 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/storagelifetime/StorageLifetimeFragment.java
@@ -26,6 +26,7 @@
import android.os.Bundle;
import android.os.StatFs;
import android.support.v4.app.Fragment;
+import android.system.ErrnoException;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -35,6 +36,9 @@
import android.widget.TextView;
import com.google.android.car.kitchensink.KitchenSinkActivity;
import com.google.android.car.kitchensink.R;
+import java.io.FileDescriptor;
+import java.nio.ByteBuffer;
+import libcore.io.Libcore;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -44,6 +48,9 @@
import java.security.SecureRandom;
import java.util.List;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_RDWR;
+
public class StorageLifetimeFragment extends Fragment {
private static final String FILE_NAME = "storage.bin";
private static final String TAG = "CAR.STORAGELIFETIME.KS";
@@ -114,14 +121,19 @@
}
}
+ private Path getFilePath() throws IOException {
+ Path filePath = new File(mActivity.getFilesDir(), FILE_NAME).toPath();
+ if (Files.notExists(filePath)) {
+ Files.createFile(filePath);
+ }
+ return filePath;
+ }
+
private void writeBytesToFile(int size) {
try {
+ final Path filePath = getFilePath();
byte[] data = new byte[size];
SecureRandom.getInstanceStrong().nextBytes(data);
- Path filePath = new File(mActivity.getFilesDir(), FILE_NAME).toPath();
- if (Files.notExists(filePath)) {
- Files.createFile(filePath);
- }
Files.write(filePath,
data,
StandardOpenOption.APPEND);
@@ -130,6 +142,24 @@
}
}
+ private void fsyncFile() {
+ try {
+ final Path filePath = getFilePath();
+ FileDescriptor fd = Libcore.os.open(filePath.toString(), O_APPEND | O_RDWR, 0);
+ if (!fd.valid()) {
+ Log.w(TAG, "file descriptor is invalid");
+ return;
+ }
+ // fill byteBuffer with arbitrary data in order to make an fsync() meaningful
+ ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[] {101, 110, 114, 105, 99, 111});
+ Libcore.os.write(fd, byteBuffer);
+ Libcore.os.fsync(fd);
+ Libcore.os.close(fd);
+ } catch (ErrnoException | IOException e) {
+ Log.w(TAG, "could not fsync data", e);
+ }
+ }
+
@Nullable
@Override
public View onCreateView(
@@ -149,6 +179,9 @@
view.findViewById(R.id.write_one_megabyte).setOnClickListener(
v -> writeBytesToFile(MEGABYTE));
+ view.findViewById(R.id.perform_fsync).setOnClickListener(
+ v -> fsyncFile());
+
return view;
}
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index 21d8a75..d290e52 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -48,7 +48,7 @@
<activity android:name="com.android.car.SystemActivityMonitoringServiceTest$BlockingActivity"
android:taskAffinity="com.android.car.carservicetest.block"/>
- <receiver android:name=".CarStorageMonitoringBroadcastReceiver"
+ <receiver android:name="com.android.car.CarStorageMonitoringBroadcastReceiver"
android:exported="true"
android:permission="android.car.permission.STORAGE_MONITORING">
<intent-filter>