Merge "Add smart idle maintenance service" am: 0134ac7710
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1936378 Change-Id: I58a8320c57bfdcc77e862b2d0693f67066826956
This commit is contained in:
@@ -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" >
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user