Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e8d9b1d7 authored by Daeho Jeong's avatar Daeho Jeong Committed by Automerger Merge Worker
Browse files

Merge "Add smart idle maintenance service" am: 0134ac77

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1936378

Change-Id: I58a8320c57bfdcc77e862b2d0693f67066826956
parents e2cf4c3a 0134ac77
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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" >
+89 −0
Original line number Diff line number Diff line
/*
 * 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());
    }
}
+219 −1
Original line number Diff line number Diff line
@@ -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);