Merge "Add smart idle maintenance service"

This commit is contained in:
Daeho Jeong
2022-01-13 16:38:04 +00:00
committed by Gerrit Code Review
3 changed files with 313 additions and 1 deletions

View File

@@ -6235,6 +6235,11 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<service android:name="com.android.server.SmartStorageMaintIdler"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<service android:name="com.android.server.ZramWriteback"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" >

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.util.Slog;
import java.util.concurrent.TimeUnit;
public class SmartStorageMaintIdler extends JobService {
private static final String TAG = "SmartStorageMaintIdler";
private static final ComponentName SMART_STORAGE_MAINT_SERVICE =
new ComponentName("android", SmartStorageMaintIdler.class.getName());
private static final int SMART_MAINT_JOB_ID = 2808;
private boolean mStarted;
private JobParameters mJobParams;
private final Runnable mFinishCallback = new Runnable() {
@Override
public void run() {
Slog.i(TAG, "Got smart storage maintenance service completion callback");
if (mStarted) {
jobFinished(mJobParams, false);
mStarted = false;
}
// ... and try again in a next period
scheduleSmartIdlePass(SmartStorageMaintIdler.this,
StorageManagerService.SMART_IDLE_MAINT_PERIOD);
}
};
@Override
public boolean onStartJob(JobParameters params) {
mJobParams = params;
StorageManagerService ms = StorageManagerService.sSelf;
if (ms != null) {
mStarted = true;
ms.runSmartIdleMaint(mFinishCallback);
}
return ms != null;
}
@Override
public boolean onStopJob(JobParameters params) {
mStarted = false;
return false;
}
/**
* Schedule the smart storage idle maintenance job
*/
public static void scheduleSmartIdlePass(Context context, int nHours) {
StorageManagerService ms = StorageManagerService.sSelf;
if ((ms == null) || ms.isPassedLifetimeThresh()) {
return;
}
JobScheduler tm = context.getSystemService(JobScheduler.class);
long nextScheduleTime = TimeUnit.HOURS.toMillis(nHours);
JobInfo.Builder builder = new JobInfo.Builder(SMART_MAINT_JOB_ID,
SMART_STORAGE_MAINT_SERVICE);
builder.setMinimumLatency(nextScheduleTime);
tm.schedule(builder.build());
}
}

View File

@@ -79,6 +79,7 @@ import android.content.res.Configuration;
import android.content.res.ObbInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.DropBoxManager;
import android.os.Environment;
@@ -158,6 +159,8 @@ import com.android.server.storage.StorageSessionController.ExternalStorageServic
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -339,7 +342,44 @@ class StorageManagerService extends IStorageManager.Stub
@Nullable public static String sMediaStoreAuthorityProcessName;
// Run period in hour for smart idle maintenance
static final int SMART_IDLE_MAINT_PERIOD = 1;
private final AtomicFile mSettingsFile;
private final AtomicFile mHourlyWriteFile;
private static final int MAX_HOURLY_WRITE_RECORDS = 72;
/**
* Default config values for smart idle maintenance
* Actual values will be controlled by DeviceConfig
*/
// Decide whether smart idle maintenance is enabled or not
private static final boolean DEFAULT_SMART_IDLE_MAINT_ENABLED = false;
// Storage lifetime percentage threshold to decide to turn off the feature
private static final int DEFAULT_LIFETIME_PERCENT_THRESHOLD = 70;
// Minimum required number of dirty + free segments to trigger GC
private static final int DEFAULT_MIN_SEGMENTS_THRESHOLD = 512;
// Determine how much portion of current dirty segments will be GCed
private static final float DEFAULT_DIRTY_RECLAIM_RATE = 0.5F;
// Multiplier to amplify the target segment number for GC
private static final float DEFAULT_SEGMENT_RECLAIM_WEIGHT = 1.0F;
// Low battery level threshold to decide to turn off the feature
private static final float DEFAULT_LOW_BATTERY_LEVEL = 20F;
// Decide whether charging is required to turn on the feature
private static final boolean DEFAULT_CHARGING_REQUIRED = true;
private volatile int mLifetimePercentThreshold;
private volatile int mMinSegmentsThreshold;
private volatile float mDirtyReclaimRate;
private volatile float mSegmentReclaimWeight;
private volatile float mLowBatteryLevel;
private volatile boolean mChargingRequired;
private volatile boolean mNeedGC;
private volatile boolean mPassedLifetimeThresh;
// Tracking storage hourly write amounts
private volatile int[] mStorageHourlyWrites;
/**
* <em>Never</em> hold the lock while performing downcalls into vold, since
@@ -901,6 +941,10 @@ class StorageManagerService extends IStorageManager.Stub
}
private void handleSystemReady() {
if (prepareSmartIdleMaint()) {
SmartStorageMaintIdler.scheduleSmartIdlePass(mContext, SMART_IDLE_MAINT_PERIOD);
}
// Start scheduling nominally-daily fstrim operations
MountServiceIdler.scheduleIdlePass(mContext);
@@ -1907,6 +1951,10 @@ class StorageManagerService extends IStorageManager.Stub
mSettingsFile = new AtomicFile(
new File(Environment.getDataSystemDirectory(), "storage.xml"), "storage-settings");
mHourlyWriteFile = new AtomicFile(
new File(Environment.getDataSystemDirectory(), "storage-hourly-writes"));
mStorageHourlyWrites = new int[MAX_HOURLY_WRITE_RECORDS];
synchronized (mLock) {
readSettingsLocked();
@@ -2572,7 +2620,7 @@ class StorageManagerService extends IStorageManager.Stub
// fstrim time is still updated. If file based checkpoints are used, we run
// idle maintenance (GC + fstrim) regardless of checkpoint status.
if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
mVold.runIdleMaint(new IVoldTaskListener.Stub() {
mVold.runIdleMaint(mNeedGC, new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
// Not currently used
@@ -2623,6 +2671,176 @@ class StorageManagerService extends IStorageManager.Stub
abortIdleMaint(null);
}
private boolean prepareSmartIdleMaint() {
/**
* We can choose whether going with a new storage smart idle maintenance job
* or falling back to the traditional way using DeviceConfig
*/
boolean smartIdleMaintEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
"smart_idle_maint_enabled",
DEFAULT_SMART_IDLE_MAINT_ENABLED);
if (smartIdleMaintEnabled) {
mLifetimePercentThreshold = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
"lifetime_threshold", DEFAULT_LIFETIME_PERCENT_THRESHOLD);
mMinSegmentsThreshold = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
"min_segments_threshold", DEFAULT_MIN_SEGMENTS_THRESHOLD);
mDirtyReclaimRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
"dirty_reclaim_rate", DEFAULT_DIRTY_RECLAIM_RATE);
mSegmentReclaimWeight = DeviceConfig.getFloat(
DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
"segment_reclaim_weight", DEFAULT_SEGMENT_RECLAIM_WEIGHT);
mLowBatteryLevel = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
"low_battery_level", DEFAULT_LOW_BATTERY_LEVEL);
mChargingRequired = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
"charging_required", DEFAULT_CHARGING_REQUIRED);
// If we use the smart idle maintenance, we need to turn off GC in the traditional idle
// maintenance to avoid the conflict
mNeedGC = false;
loadStorageHourlyWrites();
try {
mVold.refreshLatestWrite();
} catch (Exception e) {
Slog.wtf(TAG, e);
}
refreshLifetimeConstraint();
}
return smartIdleMaintEnabled;
}
// Return whether storage lifetime exceeds the threshold
public boolean isPassedLifetimeThresh() {
return mPassedLifetimeThresh;
}
private void loadStorageHourlyWrites() {
FileInputStream fis = null;
try {
fis = mHourlyWriteFile.openRead();
ObjectInputStream ois = new ObjectInputStream(fis);
mStorageHourlyWrites = (int[])ois.readObject();
} catch (FileNotFoundException e) {
// Missing data is okay, probably first boot
} catch (Exception e) {
Slog.wtf(TAG, "Failed reading write records", e);
} finally {
IoUtils.closeQuietly(fis);
}
}
private int getAverageHourlyWrite() {
return Arrays.stream(mStorageHourlyWrites).sum() / MAX_HOURLY_WRITE_RECORDS;
}
private void updateStorageHourlyWrites(int latestWrite) {
FileOutputStream fos = null;
System.arraycopy(mStorageHourlyWrites,0, mStorageHourlyWrites, 1,
MAX_HOURLY_WRITE_RECORDS - 1);
mStorageHourlyWrites[0] = latestWrite;
try {
fos = mHourlyWriteFile.startWrite();
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(mStorageHourlyWrites);
mHourlyWriteFile.finishWrite(fos);
} catch (IOException e) {
if (fos != null) {
mHourlyWriteFile.failWrite(fos);
}
}
}
private boolean checkChargeStatus() {
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = mContext.registerReceiver(null, ifilter);
if (mChargingRequired) {
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
if (status != BatteryManager.BATTERY_STATUS_CHARGING &&
status != BatteryManager.BATTERY_STATUS_FULL) {
Slog.w(TAG, "Battery is not being charged");
return false;
}
}
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float chargePercent = level * 100f / (float)scale;
if (chargePercent < mLowBatteryLevel) {
Slog.w(TAG, "Battery level is " + chargePercent + ", which is lower than threshold: " +
mLowBatteryLevel);
return false;
}
return true;
}
private boolean refreshLifetimeConstraint() {
int storageLifeTime = 0;
try {
storageLifeTime = mVold.getStorageLifeTime();
} catch (Exception e) {
Slog.wtf(TAG, e);
return false;
}
if (storageLifeTime == -1) {
Slog.w(TAG, "Failed to get storage lifetime");
return false;
} else if (storageLifeTime > mLifetimePercentThreshold) {
Slog.w(TAG, "Ended smart idle maintenance, because of lifetime(" + storageLifeTime +
")" + ", lifetime threshold(" + mLifetimePercentThreshold + ")");
mPassedLifetimeThresh = true;
return false;
}
Slog.i(TAG, "Storage lifetime: " + storageLifeTime);
return true;
}
void runSmartIdleMaint(Runnable callback) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
try {
// Block based checkpoint process runs fstrim. So, if checkpoint is in progress
// (first boot after OTA), We skip the smart idle maintenance
if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
if (!refreshLifetimeConstraint() || !checkChargeStatus()) {
return;
}
int latestHourlyWrite = mVold.getWriteAmount();
if (latestHourlyWrite == -1) {
Slog.w(TAG, "Failed to get storage hourly write");
return;
}
updateStorageHourlyWrites(latestHourlyWrite);
int avgHourlyWrite = getAverageHourlyWrite();
Slog.i(TAG, "Set smart idle maintenance: " + "latest hourly write: " +
latestHourlyWrite + ", average hourly write: " + avgHourlyWrite +
", min segment threshold: " + mMinSegmentsThreshold +
", dirty reclaim rate: " + mDirtyReclaimRate +
", segment reclaim weight:" + mSegmentReclaimWeight);
mVold.setGCUrgentPace(avgHourlyWrite, mMinSegmentsThreshold, mDirtyReclaimRate,
mSegmentReclaimWeight);
} else {
Slog.i(TAG, "Skipping smart idle maintenance - block based checkpoint in progress");
}
} catch (Exception e) {
Slog.wtf(TAG, e);
} finally {
if (callback != null) {
callback.run();
}
}
}
@Override
public void setDebugFlags(int flags, int mask) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);