Augment diskstats dumpsys to have categorization and apps.
This adds a new service which opportunistically saves the
file system categorization information and the app sizes. This
information is fetched during a diskstats dumpsys call from a file
stored on the disk. This allows us to keep the dumpsys running quickly
while adding information which is costly to calculate.
Bug: 32207207
Test: System server instrumentation tests
Change-Id: Id59e84b9ad38a9debf3e46e5133ef06f7353829d
diff --git a/services/core/java/com/android/server/storage/DiskStatsLoggingService.java b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
new file mode 100644
index 0000000..4a86175
--- /dev/null
+++ b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2016 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/LICENSE2.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.storage;
+
+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.content.pm.PackageStats;
+import android.os.AsyncTask;
+import android.os.BatteryManager;
+import android.os.Environment;
+import android.os.Environment.UserEnvironment;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.storage.FileCollector.MeasurementResult;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * DiskStatsLoggingService is a JobService which collects storage categorization information and
+ * app size information on a roughly daily cadence.
+ */
+public class DiskStatsLoggingService extends JobService {
+ private static final String TAG = "DiskStatsLogService";
+ public static final String DUMPSYS_CACHE_PATH = "/data/system/diskstats_cache.json";
+ private static final int JOB_DISKSTATS_LOGGING = 0x4449534b; // DISK
+ private static ComponentName sDiskStatsLoggingService = new ComponentName(
+ "android",
+ DiskStatsLoggingService.class.getName());
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ // We need to check the preconditions again because they may not be enforced for
+ // subsequent runs.
+ if (!isCharging(this)) {
+ jobFinished(params, true);
+ return false;
+ }
+
+ final int userId = UserHandle.myUserId();
+ UserEnvironment environment = new UserEnvironment(userId);
+ AppCollector collector = new AppCollector(this,
+ getPackageManager().getPrimaryStorageCurrentVolume());
+ LogRunnable task = new LogRunnable();
+ task.setRootDirectory(environment.getExternalStorageDirectory());
+ task.setDownloadsDirectory(
+ environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
+ task.setSystemSize(FileCollector.getSystemSize(this));
+ task.setLogOutputFile(new File(DUMPSYS_CACHE_PATH));
+ task.setAppCollector(collector);
+ task.setJobService(this, params);
+ AsyncTask.execute(task);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ // TODO: Try to stop being handled.
+ return false;
+ }
+
+ /**
+ * Schedules a DiskStats collection task. This task only runs on device idle while charging
+ * once every 24 hours.
+ * @param context Context to use to get a job scheduler.
+ */
+ public static void schedule(Context context) {
+ JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+ js.schedule(new JobInfo.Builder(JOB_DISKSTATS_LOGGING, sDiskStatsLoggingService)
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setPeriodic(TimeUnit.DAYS.toMillis(1))
+ .build());
+ }
+
+ private static boolean isCharging(Context context) {
+ BatteryManager batteryManager = context.getSystemService(BatteryManager.class);
+ if (batteryManager != null) {
+ return batteryManager.isCharging();
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ static class LogRunnable implements Runnable {
+ private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+
+ private JobService mJobService;
+ private JobParameters mParams;
+ private AppCollector mCollector;
+ private File mOutputFile;
+ private File mRootDirectory;
+ private File mDownloadsDirectory;
+ private long mSystemSize;
+
+ public void setRootDirectory(File file) {
+ mRootDirectory = file;
+ }
+
+ public void setDownloadsDirectory(File file) {
+ mDownloadsDirectory = file;
+ }
+
+ public void setAppCollector(AppCollector collector) {
+ mCollector = collector;
+ }
+
+ public void setLogOutputFile(File file) {
+ mOutputFile = file;
+ }
+
+ public void setSystemSize(long size) {
+ mSystemSize = size;
+ }
+
+ public void setJobService(JobService jobService, JobParameters params) {
+ mJobService = jobService;
+ mParams = params;
+ }
+
+ public void run() {
+ FileCollector.MeasurementResult mainCategories =
+ FileCollector.getMeasurementResult(mRootDirectory);
+ FileCollector.MeasurementResult downloads =
+ FileCollector.getMeasurementResult(mDownloadsDirectory);
+
+ logToFile(mainCategories, downloads, mCollector.getPackageStats(TIMEOUT_MILLIS),
+ mSystemSize);
+
+ if (mJobService != null) {
+ mJobService.jobFinished(mParams, false);
+ }
+ }
+
+ private void logToFile(MeasurementResult mainCategories, MeasurementResult downloads,
+ List<PackageStats> stats, long systemSize) {
+ DiskStatsFileLogger logger = new DiskStatsFileLogger(mainCategories, downloads, stats,
+ systemSize);
+ try {
+ mOutputFile.createNewFile();
+ logger.dumpToFile(mOutputFile);
+ } catch (IOException e) {
+ Log.e(TAG, "Exception while writing opportunistic disk file cache.", e);
+ }
+ }
+ }
+}
\ No newline at end of file