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
This commit is contained in:
@ -3390,10 +3390,13 @@
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" >
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="com.android.server.pm.BackgroundDexOptService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE">
|
||||
<service android:name="com.android.server.pm.BackgroundDexOptService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE">
|
||||
</service>
|
||||
|
||||
<service android:name="com.android.server.storage.DiskStatsLoggingService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" >
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
@ -22,6 +22,15 @@ import android.os.Environment;
|
||||
import android.os.StatFs;
|
||||
import android.os.SystemClock;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.storage.DiskStatsFileLogger;
|
||||
import com.android.server.storage.DiskStatsLoggingService;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
@ -35,11 +44,13 @@ import java.io.PrintWriter;
|
||||
*/
|
||||
public class DiskStatsService extends Binder {
|
||||
private static final String TAG = "DiskStatsService";
|
||||
private static final String DISKSTATS_DUMP_FILE = "/data/system/diskstats_cache.json";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public DiskStatsService(Context context) {
|
||||
mContext = context;
|
||||
DiskStatsLoggingService.schedule(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -84,6 +95,10 @@ public class DiskStatsService extends Binder {
|
||||
pw.println("File-based Encryption: true");
|
||||
}
|
||||
|
||||
if (isCheckin(args)) {
|
||||
reportCachedValues(pw);
|
||||
}
|
||||
|
||||
// TODO: Read /proc/yaffs and report interesting values;
|
||||
// add configurable (through args) performance test parameters.
|
||||
}
|
||||
@ -114,4 +129,45 @@ public class DiskStatsService extends Binder {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCheckin(String[] args) {
|
||||
for (String opt : args) {
|
||||
if ("--checkin".equals(opt)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void reportCachedValues(PrintWriter pw) {
|
||||
try {
|
||||
String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
|
||||
JSONObject json = new JSONObject(jsonString);
|
||||
pw.print("App Size: ");
|
||||
pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
|
||||
pw.print("App Cache Size: ");
|
||||
pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
|
||||
pw.print("Photos Size: ");
|
||||
pw.println(json.getLong(DiskStatsFileLogger.PHOTOS_KEY));
|
||||
pw.print("Videos Size: ");
|
||||
pw.println(json.getLong(DiskStatsFileLogger.VIDEOS_KEY));
|
||||
pw.print("Audio Size: ");
|
||||
pw.println(json.getLong(DiskStatsFileLogger.AUDIO_KEY));
|
||||
pw.print("Downloads Size: ");
|
||||
pw.println(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY));
|
||||
pw.print("System Size: ");
|
||||
pw.println(json.getLong(DiskStatsFileLogger.SYSTEM_KEY));
|
||||
pw.print("Other Size: ");
|
||||
pw.println(json.getLong(DiskStatsFileLogger.MISC_KEY));
|
||||
pw.print("Package Names: ");
|
||||
pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY));
|
||||
pw.print("App Sizes: ");
|
||||
pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY));
|
||||
pw.print("Cache Sizes: ");
|
||||
pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY));
|
||||
} catch (IOException | JSONException e) {
|
||||
Log.w(TAG, "exception reading diskstats cache file", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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.content.pm.PackageStats;
|
||||
import android.os.Environment;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.storage.FileCollector.MeasurementResult;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* DiskStatsFileLogger logs collected storage information to a file in a JSON format.
|
||||
*
|
||||
* The following information is cached in the file:
|
||||
* 1. Size of images on disk.
|
||||
* 2. Size of videos on disk.
|
||||
* 3. Size of audio on disk.
|
||||
* 4. Size of the downloads folder.
|
||||
* 5. System size.
|
||||
* 6. Aggregate and individual app and app cache sizes.
|
||||
* 7. How much storage couldn't be categorized in one of the above categories.
|
||||
*/
|
||||
public class DiskStatsFileLogger {
|
||||
private static final String TAG = "DiskStatsLogger";
|
||||
|
||||
public static final String PHOTOS_KEY = "photosSize";
|
||||
public static final String VIDEOS_KEY = "videosSize";
|
||||
public static final String AUDIO_KEY = "audioSize";
|
||||
public static final String DOWNLOADS_KEY = "downloadsSize";
|
||||
public static final String SYSTEM_KEY = "systemSize";
|
||||
public static final String MISC_KEY = "otherSize";
|
||||
public static final String APP_SIZE_AGG_KEY = "appSize";
|
||||
public static final String APP_CACHE_AGG_KEY = "cacheSize";
|
||||
public static final String PACKAGE_NAMES_KEY = "packageNames";
|
||||
public static final String APP_SIZES_KEY = "appSizes";
|
||||
public static final String APP_CACHES_KEY = "cacheSizes";
|
||||
public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime";
|
||||
|
||||
private MeasurementResult mResult;
|
||||
private long mDownloadsSize;
|
||||
private long mSystemSize;
|
||||
private List<PackageStats> mPackageStats;
|
||||
|
||||
/**
|
||||
* Constructs a DiskStatsFileLogger with calculated measurement results.
|
||||
*/
|
||||
public DiskStatsFileLogger(MeasurementResult result, MeasurementResult downloadsResult,
|
||||
List<PackageStats> stats, long systemSize) {
|
||||
mResult = result;
|
||||
mDownloadsSize = downloadsResult.totalAccountedSize();
|
||||
mSystemSize = systemSize;
|
||||
mPackageStats = stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the storage collection output to a file.
|
||||
* @param file File to write the output into.
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public void dumpToFile(File file) throws FileNotFoundException {
|
||||
PrintWriter pw = new PrintWriter(file);
|
||||
JSONObject representation = getJsonRepresentation();
|
||||
if (representation != null) {
|
||||
pw.println(representation);
|
||||
}
|
||||
pw.close();
|
||||
}
|
||||
|
||||
private JSONObject getJsonRepresentation() {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put(LAST_QUERY_TIMESTAMP_KEY, System.currentTimeMillis());
|
||||
json.put(PHOTOS_KEY, mResult.imagesSize);
|
||||
json.put(VIDEOS_KEY, mResult.videosSize);
|
||||
json.put(AUDIO_KEY, mResult.audioSize);
|
||||
json.put(DOWNLOADS_KEY, mDownloadsSize);
|
||||
json.put(SYSTEM_KEY, mSystemSize);
|
||||
json.put(MISC_KEY, mResult.miscSize);
|
||||
addAppsToJson(json);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
private void addAppsToJson(JSONObject json) throws JSONException {
|
||||
JSONArray names = new JSONArray();
|
||||
JSONArray appSizeList = new JSONArray();
|
||||
JSONArray cacheSizeList = new JSONArray();
|
||||
|
||||
long appSizeSum = 0L;
|
||||
long cacheSizeSum = 0L;
|
||||
boolean isExternal = Environment.isExternalStorageEmulated();
|
||||
for (Map.Entry<String, PackageStats> entry : mergePackagesAcrossUsers().entrySet()) {
|
||||
PackageStats stat = entry.getValue();
|
||||
long appSize = stat.codeSize + stat.dataSize;
|
||||
long cacheSize = stat.cacheSize;
|
||||
if (isExternal) {
|
||||
appSize += stat.externalCodeSize + stat.externalDataSize;
|
||||
cacheSize += stat.externalCacheSize;
|
||||
}
|
||||
appSizeSum += appSize;
|
||||
cacheSizeSum += cacheSize;
|
||||
|
||||
names.put(stat.packageName);
|
||||
appSizeList.put(appSize);
|
||||
cacheSizeList.put(cacheSize);
|
||||
}
|
||||
json.put(PACKAGE_NAMES_KEY, names);
|
||||
json.put(APP_SIZES_KEY, appSizeList);
|
||||
json.put(APP_CACHES_KEY, cacheSizeList);
|
||||
json.put(APP_SIZE_AGG_KEY, appSizeSum);
|
||||
json.put(APP_CACHE_AGG_KEY, cacheSizeSum);
|
||||
}
|
||||
|
||||
/**
|
||||
* A given package may exist for multiple users with distinct sizes. This function merges
|
||||
* the duplicated packages together and sums up their sizes to get the actual totals for the
|
||||
* package.
|
||||
* @return A mapping of package name to merged package stats.
|
||||
*/
|
||||
private ArrayMap<String, PackageStats> mergePackagesAcrossUsers() {
|
||||
ArrayMap<String, PackageStats> packageMap = new ArrayMap<>();
|
||||
for (PackageStats stat : mPackageStats) {
|
||||
PackageStats existingStats = packageMap.get(stat.packageName);
|
||||
if (existingStats != null) {
|
||||
existingStats.cacheSize += stat.cacheSize;
|
||||
existingStats.codeSize += stat.codeSize;
|
||||
existingStats.dataSize += stat.dataSize;
|
||||
existingStats.externalCacheSize += stat.externalCacheSize;
|
||||
existingStats.externalCodeSize += stat.externalCodeSize;
|
||||
existingStats.externalDataSize += stat.externalDataSize;
|
||||
} else {
|
||||
packageMap.put(stat.packageName, new PackageStats(stat));
|
||||
}
|
||||
}
|
||||
return packageMap;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,10 @@
|
||||
package com.android.server.storage;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.os.storage.VolumeInfo;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import java.io.File;
|
||||
@ -150,6 +153,32 @@ public class FileCollector {
|
||||
new MeasurementResult());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of a system for a given context. This is done by finding the difference
|
||||
* between the shared data and the total primary storage size.
|
||||
* @param context Context to use to get storage information.
|
||||
*/
|
||||
public static long getSystemSize(Context context) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
VolumeInfo primaryVolume = pm.getPrimaryStorageCurrentVolume();
|
||||
|
||||
StorageManager sm = context.getSystemService(StorageManager.class);
|
||||
VolumeInfo shared = sm.findEmulatedForPrivate(primaryVolume);
|
||||
if (shared == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final long sharedDataSize = shared.getPath().getTotalSpace();
|
||||
long systemSize = sm.getPrimaryStorageSize() - sharedDataSize;
|
||||
|
||||
// This case is not exceptional -- we just fallback to the shared data volume in this case.
|
||||
if (systemSize <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return systemSize;
|
||||
}
|
||||
|
||||
private static MeasurementResult collectFiles(File file, MeasurementResult result) {
|
||||
File[] files = file.listFiles();
|
||||
|
||||
|
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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/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.storage;
|
||||
|
||||
import android.content.pm.PackageStats;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.ArraySet;
|
||||
import libcore.io.IoUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class DiskStatsFileLoggerTest extends AndroidTestCase {
|
||||
@Rule public TemporaryFolder temporaryFolder;
|
||||
public FileCollector.MeasurementResult mMainResult;
|
||||
public FileCollector.MeasurementResult mDownloadsResult;
|
||||
private ArrayList<PackageStats> mPackages;
|
||||
private File mOutputFile;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
temporaryFolder = new TemporaryFolder();
|
||||
temporaryFolder.create();
|
||||
mOutputFile = temporaryFolder.newFile();
|
||||
mMainResult = new FileCollector.MeasurementResult();
|
||||
mDownloadsResult = new FileCollector.MeasurementResult();
|
||||
mPackages = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyStorage() throws Exception {
|
||||
DiskStatsFileLogger logger = new DiskStatsFileLogger(
|
||||
mMainResult, mDownloadsResult,mPackages, 0L);
|
||||
|
||||
logger.dumpToFile(mOutputFile);
|
||||
|
||||
JSONObject output = getOutputFileAsJson();
|
||||
assertThat(output.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(0L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(0L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(0L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(0L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(0L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(0L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(0L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(0L);
|
||||
assertThat(
|
||||
output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(0L);
|
||||
assertThat(output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(0L);
|
||||
assertThat(output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMeasurementResultsReported() throws Exception {
|
||||
mMainResult.audioSize = 1;
|
||||
mMainResult.imagesSize = 10;
|
||||
mMainResult.miscSize = 100;
|
||||
mDownloadsResult.miscSize = 1000;
|
||||
DiskStatsFileLogger logger = new DiskStatsFileLogger(
|
||||
mMainResult, mDownloadsResult,mPackages, 3L);
|
||||
|
||||
logger.dumpToFile(mOutputFile);
|
||||
|
||||
JSONObject output = getOutputFileAsJson();
|
||||
assertThat(output.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(1L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(10L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(100L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(1000L);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppsReported() throws Exception {
|
||||
PackageStats firstPackage = new PackageStats("com.test.app");
|
||||
firstPackage.codeSize = 100;
|
||||
firstPackage.dataSize = 1000;
|
||||
firstPackage.cacheSize = 20;
|
||||
mPackages.add(firstPackage);
|
||||
|
||||
PackageStats secondPackage = new PackageStats("com.test.app2");
|
||||
secondPackage.codeSize = 10;
|
||||
secondPackage.dataSize = 1;
|
||||
secondPackage.cacheSize = 2;
|
||||
mPackages.add(secondPackage);
|
||||
|
||||
DiskStatsFileLogger logger = new DiskStatsFileLogger(
|
||||
mMainResult, mDownloadsResult, mPackages, 0L);
|
||||
logger.dumpToFile(mOutputFile);
|
||||
|
||||
JSONObject output = getOutputFileAsJson();
|
||||
assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(1111);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(22);
|
||||
|
||||
JSONArray packageNames = output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
|
||||
assertThat(packageNames.length()).isEqualTo(2);
|
||||
JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
|
||||
assertThat(appSizes.length()).isEqualTo(2);
|
||||
JSONArray cacheSizes = output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
|
||||
assertThat(cacheSizes.length()).isEqualTo(2);
|
||||
|
||||
// We need to do this crazy Set over this because the DiskStatsFileLogger provides no
|
||||
// guarantee of the ordering of the apps in its output. By using a set, we avoid any order
|
||||
// problems.
|
||||
ArraySet<AppSizeGrouping> apps = new ArraySet<>();
|
||||
for (int i = 0; i < packageNames.length(); i++) {
|
||||
AppSizeGrouping app = new AppSizeGrouping(packageNames.getString(i),
|
||||
appSizes.getLong(i), cacheSizes.getLong(i));
|
||||
apps.add(app);
|
||||
}
|
||||
assertThat(apps).containsAllOf(new AppSizeGrouping("com.test.app", 1100, 20),
|
||||
new AppSizeGrouping("com.test.app2", 11, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmulatedExternalStorageCounted() throws Exception {
|
||||
PackageStats app = new PackageStats("com.test.app");
|
||||
app.dataSize = 1000;
|
||||
app.externalDataSize = 1000;
|
||||
app.cacheSize = 20;
|
||||
mPackages.add(app);
|
||||
|
||||
DiskStatsFileLogger logger = new DiskStatsFileLogger(
|
||||
mMainResult, mDownloadsResult, mPackages, 0L);
|
||||
logger.dumpToFile(mOutputFile);
|
||||
|
||||
JSONObject output = getOutputFileAsJson();
|
||||
JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
|
||||
assertThat(appSizes.length()).isEqualTo(1);
|
||||
assertThat(appSizes.getLong(0)).isEqualTo(2000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatePackageNameIsMergedAcrossMultipleUsers() throws Exception {
|
||||
PackageStats app = new PackageStats("com.test.app");
|
||||
app.dataSize = 1000;
|
||||
app.externalDataSize = 1000;
|
||||
app.cacheSize = 20;
|
||||
app.userHandle = 0;
|
||||
mPackages.add(app);
|
||||
|
||||
PackageStats secondApp = new PackageStats("com.test.app");
|
||||
secondApp.dataSize = 100;
|
||||
secondApp.externalDataSize = 100;
|
||||
secondApp.cacheSize = 2;
|
||||
secondApp.userHandle = 1;
|
||||
mPackages.add(secondApp);
|
||||
|
||||
DiskStatsFileLogger logger = new DiskStatsFileLogger(
|
||||
mMainResult, mDownloadsResult, mPackages, 0L);
|
||||
logger.dumpToFile(mOutputFile);
|
||||
|
||||
JSONObject output = getOutputFileAsJson();
|
||||
assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(2200);
|
||||
assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(22);
|
||||
JSONArray packageNames = output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
|
||||
assertThat(packageNames.length()).isEqualTo(1);
|
||||
assertThat(packageNames.getString(0)).isEqualTo("com.test.app");
|
||||
|
||||
JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
|
||||
assertThat(appSizes.length()).isEqualTo(1);
|
||||
assertThat(appSizes.getLong(0)).isEqualTo(2200);
|
||||
|
||||
JSONArray cacheSizes = output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
|
||||
assertThat(cacheSizes.length()).isEqualTo(1);
|
||||
assertThat(cacheSizes.getLong(0)).isEqualTo(22);
|
||||
}
|
||||
|
||||
private JSONObject getOutputFileAsJson() throws Exception {
|
||||
return new JSONObject(IoUtils.readFileAsString(mOutputFile.getAbsolutePath()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This class exists for putting zipped app size information arrays into a set for comparison
|
||||
* purposes.
|
||||
*/
|
||||
private class AppSizeGrouping {
|
||||
public String packageName;
|
||||
public long appSize;
|
||||
public long cacheSize;
|
||||
|
||||
public AppSizeGrouping(String packageName, long appSize, long cacheSize) {
|
||||
this.packageName = packageName;
|
||||
this.appSize = appSize;
|
||||
this.cacheSize = cacheSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 37 * result + (int)(appSize ^ (appSize >>> 32));
|
||||
result = 37 * result + (int)(cacheSize ^ (cacheSize >>> 32));
|
||||
result = 37 * result + packageName.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof AppSizeGrouping)) {
|
||||
return false;
|
||||
}
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
AppSizeGrouping grouping = (AppSizeGrouping) o;
|
||||
return packageName.equals(grouping.packageName) && appSize == grouping.appSize &&
|
||||
cacheSize == grouping.cacheSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return packageName + " " + appSize + " " + cacheSize;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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/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.storage;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.pm.PackageStats;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.android.server.storage.DiskStatsLoggingService.LogRunnable;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class DiskStatsLoggingServiceTest extends AndroidTestCase {
|
||||
@Rule public TemporaryFolder mTemporaryFolder;
|
||||
@Rule public TemporaryFolder mDownloads;
|
||||
@Rule public TemporaryFolder mRootDirectory;
|
||||
@Mock private AppCollector mCollector;
|
||||
private File mInputFile;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mTemporaryFolder = new TemporaryFolder();
|
||||
mTemporaryFolder.create();
|
||||
mInputFile = mTemporaryFolder.newFile();
|
||||
mDownloads = new TemporaryFolder();
|
||||
mDownloads.create();
|
||||
mRootDirectory = new TemporaryFolder();
|
||||
mRootDirectory.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyLog() throws Exception {
|
||||
LogRunnable task = new LogRunnable();
|
||||
task.setAppCollector(mCollector);
|
||||
task.setDownloadsDirectory(mDownloads.getRoot());
|
||||
task.setRootDirectory(mRootDirectory.getRoot());
|
||||
task.setLogOutputFile(mInputFile);
|
||||
task.setSystemSize(0L);
|
||||
task.run();
|
||||
|
||||
JSONObject json = getJsonOutput();
|
||||
assertThat(json.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(0L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(0L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(0L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(0L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(0L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(0L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(0L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(0L);
|
||||
assertThat(
|
||||
json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(0L);
|
||||
assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(0L);
|
||||
assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopulatedLogTask() throws Exception {
|
||||
// Write data to directories.
|
||||
writeDataToFile(mDownloads.newFile(), "lol");
|
||||
writeDataToFile(mRootDirectory.newFile("test.jpg"), "1234");
|
||||
writeDataToFile(mRootDirectory.newFile("test.mp4"), "12345");
|
||||
writeDataToFile(mRootDirectory.newFile("test.mp3"), "123456");
|
||||
writeDataToFile(mRootDirectory.newFile("test.whatever"), "1234567");
|
||||
|
||||
// Write apps.
|
||||
ArrayList<PackageStats> apps = new ArrayList<>();
|
||||
PackageStats testApp = new PackageStats("com.test.app");
|
||||
testApp.dataSize = 5L;
|
||||
testApp.cacheSize = 55L;
|
||||
testApp.codeSize = 10L;
|
||||
apps.add(testApp);
|
||||
when(mCollector.getPackageStats(anyInt())).thenReturn(apps);
|
||||
|
||||
LogRunnable task = new LogRunnable();
|
||||
task.setAppCollector(mCollector);
|
||||
task.setDownloadsDirectory(mDownloads.getRoot());
|
||||
task.setRootDirectory(mRootDirectory.getRoot());
|
||||
task.setLogOutputFile(mInputFile);
|
||||
task.setSystemSize(10L);
|
||||
task.run();
|
||||
|
||||
JSONObject json = getJsonOutput();
|
||||
assertThat(json.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(4L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(5L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(6L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(3L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(10L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(7L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(15L);
|
||||
assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(55L);
|
||||
assertThat(
|
||||
json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(1L);
|
||||
assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(1L);
|
||||
assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(1L);
|
||||
}
|
||||
|
||||
private void writeDataToFile(File f, String data) throws Exception{
|
||||
PrintStream out = new PrintStream(f);
|
||||
out.print(data);
|
||||
out.close();
|
||||
}
|
||||
|
||||
private JSONObject getJsonOutput() throws Exception {
|
||||
return new JSONObject(IoUtils.readFileAsString(mInputFile.getAbsolutePath()));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user