Merge "Add smart idle maintenance service"
This commit is contained in:
@@ -6235,6 +6235,11 @@
|
|||||||
android:permission="android.permission.BIND_JOB_SERVICE" >
|
android:permission="android.permission.BIND_JOB_SERVICE" >
|
||||||
</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"
|
<service android:name="com.android.server.ZramWriteback"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE" >
|
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.content.res.ObbInfo;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.BatteryManager;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.DropBoxManager;
|
import android.os.DropBoxManager;
|
||||||
import android.os.Environment;
|
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;
|
||||||
import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;
|
import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;
|
||||||
|
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
import libcore.io.IoUtils;
|
import libcore.io.IoUtils;
|
||||||
import libcore.util.EmptyArray;
|
import libcore.util.EmptyArray;
|
||||||
|
|
||||||
@@ -339,7 +342,44 @@ class StorageManagerService extends IStorageManager.Stub
|
|||||||
|
|
||||||
@Nullable public static String sMediaStoreAuthorityProcessName;
|
@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 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
|
* <em>Never</em> hold the lock while performing downcalls into vold, since
|
||||||
@@ -901,6 +941,10 @@ class StorageManagerService extends IStorageManager.Stub
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleSystemReady() {
|
private void handleSystemReady() {
|
||||||
|
if (prepareSmartIdleMaint()) {
|
||||||
|
SmartStorageMaintIdler.scheduleSmartIdlePass(mContext, SMART_IDLE_MAINT_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
// Start scheduling nominally-daily fstrim operations
|
// Start scheduling nominally-daily fstrim operations
|
||||||
MountServiceIdler.scheduleIdlePass(mContext);
|
MountServiceIdler.scheduleIdlePass(mContext);
|
||||||
|
|
||||||
@@ -1907,6 +1951,10 @@ class StorageManagerService extends IStorageManager.Stub
|
|||||||
|
|
||||||
mSettingsFile = new AtomicFile(
|
mSettingsFile = new AtomicFile(
|
||||||
new File(Environment.getDataSystemDirectory(), "storage.xml"), "storage-settings");
|
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) {
|
synchronized (mLock) {
|
||||||
readSettingsLocked();
|
readSettingsLocked();
|
||||||
@@ -2572,7 +2620,7 @@ class StorageManagerService extends IStorageManager.Stub
|
|||||||
// fstrim time is still updated. If file based checkpoints are used, we run
|
// fstrim time is still updated. If file based checkpoints are used, we run
|
||||||
// idle maintenance (GC + fstrim) regardless of checkpoint status.
|
// idle maintenance (GC + fstrim) regardless of checkpoint status.
|
||||||
if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
|
if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
|
||||||
mVold.runIdleMaint(new IVoldTaskListener.Stub() {
|
mVold.runIdleMaint(mNeedGC, new IVoldTaskListener.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void onStatus(int status, PersistableBundle extras) {
|
public void onStatus(int status, PersistableBundle extras) {
|
||||||
// Not currently used
|
// Not currently used
|
||||||
@@ -2623,6 +2671,176 @@ class StorageManagerService extends IStorageManager.Stub
|
|||||||
abortIdleMaint(null);
|
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
|
@Override
|
||||||
public void setDebugFlags(int flags, int mask) {
|
public void setDebugFlags(int flags, int mask) {
|
||||||
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
|
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
|
||||||
|
|||||||
Reference in New Issue
Block a user