blob: 2d2c6b0bc8b3e54388d364ac0680b3063441984f [file] [log] [blame]
Dan Egnor621bc542010-03-25 16:20:14 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import android.content.Context;
20import android.os.Binder;
21import android.os.Environment;
Dan Egnor621bc542010-03-25 16:20:14 -070022import android.os.StatFs;
23import android.os.SystemClock;
Jeff Sharkeyb92b05b2016-01-28 09:50:00 -070024import android.os.storage.StorageManager;
Amith Yamasani38f91ff2017-01-10 14:42:38 -080025import android.service.diskstats.DiskStatsAppSizesProto;
26import android.service.diskstats.DiskStatsCachedValuesProto;
27import android.service.diskstats.DiskStatsFreeSpaceProto;
28import android.service.diskstats.DiskStatsServiceDumpProto;
Daniel Nishi090b2d92016-12-13 10:38:42 -080029import android.util.Log;
Amith Yamasani38f91ff2017-01-10 14:42:38 -080030import android.util.Slog;
31import android.util.proto.ProtoOutputStream;
Daniel Nishi090b2d92016-12-13 10:38:42 -080032
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060033import com.android.internal.util.DumpUtils;
Daniel Nishi090b2d92016-12-13 10:38:42 -080034import com.android.server.storage.DiskStatsFileLogger;
35import com.android.server.storage.DiskStatsLoggingService;
36
37import libcore.io.IoUtils;
38
Amith Yamasani38f91ff2017-01-10 14:42:38 -080039import org.json.JSONArray;
Daniel Nishi090b2d92016-12-13 10:38:42 -080040import org.json.JSONException;
41import org.json.JSONObject;
Dan Egnor621bc542010-03-25 16:20:14 -070042
43import java.io.File;
44import java.io.FileDescriptor;
45import java.io.FileOutputStream;
46import java.io.IOException;
47import java.io.PrintWriter;
48
49/**
50 * This service exists only as a "dumpsys" target which reports
51 * statistics about the status of the disk.
52 */
53public class DiskStatsService extends Binder {
Jeff Sharkeyeb4cc4922012-04-26 18:17:29 -070054 private static final String TAG = "DiskStatsService";
Daniel Nishi090b2d92016-12-13 10:38:42 -080055 private static final String DISKSTATS_DUMP_FILE = "/data/system/diskstats_cache.json";
Jeff Sharkeyeb4cc4922012-04-26 18:17:29 -070056
Dan Egnor621bc542010-03-25 16:20:14 -070057 private final Context mContext;
58
59 public DiskStatsService(Context context) {
60 mContext = context;
Daniel Nishi090b2d92016-12-13 10:38:42 -080061 DiskStatsLoggingService.schedule(context);
Dan Egnor621bc542010-03-25 16:20:14 -070062 }
63
64 @Override
65 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkey6df866a2017-03-31 14:08:23 -060066 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
Dan Egnor621bc542010-03-25 16:20:14 -070067
68 // Run a quick-and-dirty performance test: write 512 bytes
69 byte[] junk = new byte[512];
70 for (int i = 0; i < junk.length; i++) junk[i] = (byte) i; // Write nonzero bytes
71
72 File tmp = new File(Environment.getDataDirectory(), "system/perftest.tmp");
73 FileOutputStream fos = null;
74 IOException error = null;
75
76 long before = SystemClock.uptimeMillis();
77 try {
78 fos = new FileOutputStream(tmp);
79 fos.write(junk);
80 } catch (IOException e) {
81 error = e;
82 } finally {
83 try { if (fos != null) fos.close(); } catch (IOException e) {}
84 }
85
86 long after = SystemClock.uptimeMillis();
87 if (tmp.exists()) tmp.delete();
88
Amith Yamasani38f91ff2017-01-10 14:42:38 -080089 boolean protoFormat = hasOption(args, "--proto");
90 ProtoOutputStream proto = null;
91
92 if (protoFormat) {
93 proto = new ProtoOutputStream(fd);
94 pw = null;
95 proto.write(DiskStatsServiceDumpProto.HAS_TEST_ERROR, error != null);
96 if (error != null) {
97 proto.write(DiskStatsServiceDumpProto.ERROR_MESSAGE, error.toString());
98 } else {
99 proto.write(DiskStatsServiceDumpProto.WRITE_512B_LATENCY_MILLIS, after - before);
100 }
Dan Egnor621bc542010-03-25 16:20:14 -0700101 } else {
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800102 if (error != null) {
103 pw.print("Test-Error: ");
104 pw.println(error.toString());
105 } else {
106 pw.print("Latency: ");
107 pw.print(after - before);
108 pw.println("ms [512B Data Write]");
109 }
Dan Egnor621bc542010-03-25 16:20:14 -0700110 }
111
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800112 reportFreeSpace(Environment.getDataDirectory(), "Data", pw, proto,
113 DiskStatsFreeSpaceProto.FOLDER_DATA);
114 reportFreeSpace(Environment.getDownloadCacheDirectory(), "Cache", pw, proto,
115 DiskStatsFreeSpaceProto.FOLDER_CACHE);
116 reportFreeSpace(new File("/system"), "System", pw, proto,
117 DiskStatsFreeSpaceProto.FOLDER_SYSTEM);
Dan Egnor621bc542010-03-25 16:20:14 -0700118
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800119 boolean fileBased = StorageManager.isFileEncryptedNativeOnly();
120 boolean blockBased = fileBased ? false : StorageManager.isBlockEncrypted();
121 if (protoFormat) {
122 if (fileBased) {
123 proto.write(DiskStatsServiceDumpProto.ENCRYPTION,
124 DiskStatsServiceDumpProto.ENCRYPTION_FILE_BASED);
125 } else if (blockBased) {
126 proto.write(DiskStatsServiceDumpProto.ENCRYPTION,
127 DiskStatsServiceDumpProto.ENCRYPTION_FULL_DISK);
128 } else {
129 proto.write(DiskStatsServiceDumpProto.ENCRYPTION,
130 DiskStatsServiceDumpProto.ENCRYPTION_NONE);
131 }
132 } else if (fileBased) {
Jeff Sharkeyb92b05b2016-01-28 09:50:00 -0700133 pw.println("File-based Encryption: true");
134 }
135
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800136 if (protoFormat) {
137 reportCachedValuesProto(proto);
138 } else {
139 reportCachedValues(pw);
140 }
Daniel Nishi090b2d92016-12-13 10:38:42 -0800141
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800142 if (protoFormat) {
143 proto.flush();
144 }
Dan Egnor621bc542010-03-25 16:20:14 -0700145 // TODO: Read /proc/yaffs and report interesting values;
146 // add configurable (through args) performance test parameters.
147 }
148
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800149 private void reportFreeSpace(File path, String name, PrintWriter pw,
150 ProtoOutputStream proto, int folderType) {
Dan Egnor621bc542010-03-25 16:20:14 -0700151 try {
152 StatFs statfs = new StatFs(path.getPath());
153 long bsize = statfs.getBlockSize();
154 long avail = statfs.getAvailableBlocks();
155 long total = statfs.getBlockCount();
156 if (bsize <= 0 || total <= 0) {
157 throw new IllegalArgumentException(
158 "Invalid stat: bsize=" + bsize + " avail=" + avail + " total=" + total);
159 }
160
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800161 if (proto != null) {
162 long freeSpaceToken = proto.start(DiskStatsServiceDumpProto.PARTITIONS_FREE_SPACE);
163 proto.write(DiskStatsFreeSpaceProto.FOLDER, folderType);
164 proto.write(DiskStatsFreeSpaceProto.AVAILABLE_SPACE, avail * bsize / 1024);
165 proto.write(DiskStatsFreeSpaceProto.TOTAL_SPACE, total * bsize / 1024);
166 proto.end(freeSpaceToken);
167 } else {
168 pw.print(name);
169 pw.print("-Free: ");
170 pw.print(avail * bsize / 1024);
171 pw.print("K / ");
172 pw.print(total * bsize / 1024);
173 pw.print("K total = ");
174 pw.print(avail * 100 / total);
175 pw.println("% free");
176 }
Dan Egnor621bc542010-03-25 16:20:14 -0700177 } catch (IllegalArgumentException e) {
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800178 if (proto != null) {
179 // Empty proto
180 } else {
181 pw.print(name);
182 pw.print("-Error: ");
183 pw.println(e.toString());
184 }
Dan Egnor621bc542010-03-25 16:20:14 -0700185 return;
186 }
187 }
Daniel Nishi090b2d92016-12-13 10:38:42 -0800188
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800189 private boolean hasOption(String[] args, String arg) {
190 for (String opt : args) {
191 if (arg.equals(opt)) {
192 return true;
193 }
194 }
195 return false;
196 }
197
198 // If you change this method, make sure to modify the Proto version of this method as well.
Daniel Nishi090b2d92016-12-13 10:38:42 -0800199 private void reportCachedValues(PrintWriter pw) {
200 try {
201 String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
202 JSONObject json = new JSONObject(jsonString);
203 pw.print("App Size: ");
204 pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
Daniel Nishib6cc8382017-09-14 17:10:00 -0700205 pw.print("App Data Size: ");
206 pw.println(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
Daniel Nishi090b2d92016-12-13 10:38:42 -0800207 pw.print("App Cache Size: ");
208 pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
209 pw.print("Photos Size: ");
210 pw.println(json.getLong(DiskStatsFileLogger.PHOTOS_KEY));
211 pw.print("Videos Size: ");
212 pw.println(json.getLong(DiskStatsFileLogger.VIDEOS_KEY));
213 pw.print("Audio Size: ");
214 pw.println(json.getLong(DiskStatsFileLogger.AUDIO_KEY));
215 pw.print("Downloads Size: ");
216 pw.println(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY));
217 pw.print("System Size: ");
218 pw.println(json.getLong(DiskStatsFileLogger.SYSTEM_KEY));
219 pw.print("Other Size: ");
220 pw.println(json.getLong(DiskStatsFileLogger.MISC_KEY));
221 pw.print("Package Names: ");
222 pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY));
223 pw.print("App Sizes: ");
224 pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY));
Daniel Nishib6cc8382017-09-14 17:10:00 -0700225 pw.print("App Data Sizes: ");
226 pw.println(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY));
Daniel Nishi090b2d92016-12-13 10:38:42 -0800227 pw.print("Cache Sizes: ");
228 pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY));
229 } catch (IOException | JSONException e) {
230 Log.w(TAG, "exception reading diskstats cache file", e);
231 }
232 }
233
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800234 private void reportCachedValuesProto(ProtoOutputStream proto) {
235 try {
236 String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
237 JSONObject json = new JSONObject(jsonString);
238 long cachedValuesToken = proto.start(DiskStatsServiceDumpProto.CACHED_FOLDER_SIZES);
239
240 proto.write(DiskStatsCachedValuesProto.AGG_APPS_SIZE,
241 json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
Daniel Nishib6cc8382017-09-14 17:10:00 -0700242 proto.write(DiskStatsCachedValuesProto.AGG_APPS_DATA_SIZE,
243 json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800244 proto.write(DiskStatsCachedValuesProto.AGG_APPS_CACHE_SIZE,
245 json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
246 proto.write(DiskStatsCachedValuesProto.PHOTOS_SIZE,
247 json.getLong(DiskStatsFileLogger.PHOTOS_KEY));
248 proto.write(DiskStatsCachedValuesProto.VIDEOS_SIZE,
249 json.getLong(DiskStatsFileLogger.VIDEOS_KEY));
250 proto.write(DiskStatsCachedValuesProto.AUDIO_SIZE,
251 json.getLong(DiskStatsFileLogger.AUDIO_KEY));
252 proto.write(DiskStatsCachedValuesProto.DOWNLOADS_SIZE,
253 json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY));
254 proto.write(DiskStatsCachedValuesProto.SYSTEM_SIZE,
255 json.getLong(DiskStatsFileLogger.SYSTEM_KEY));
256 proto.write(DiskStatsCachedValuesProto.OTHER_SIZE,
257 json.getLong(DiskStatsFileLogger.MISC_KEY));
258
259 JSONArray packageNamesArray = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
260 JSONArray appSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
Daniel Nishib6cc8382017-09-14 17:10:00 -0700261 JSONArray appDataSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY);
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800262 JSONArray cacheSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
263 final int len = packageNamesArray.length();
Daniel Nishib6cc8382017-09-14 17:10:00 -0700264 if (len == appSizesArray.length()
265 && len == appDataSizesArray.length()
266 && len == cacheSizesArray.length()) {
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800267 for (int i = 0; i < len; i++) {
268 long packageToken = proto.start(DiskStatsCachedValuesProto.APP_SIZES);
269
270 proto.write(DiskStatsAppSizesProto.PACKAGE_NAME,
271 packageNamesArray.getString(i));
272 proto.write(DiskStatsAppSizesProto.APP_SIZE, appSizesArray.getLong(i));
Daniel Nishib6cc8382017-09-14 17:10:00 -0700273 proto.write(DiskStatsAppSizesProto.APP_DATA_SIZE, appDataSizesArray.getLong(i));
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800274 proto.write(DiskStatsAppSizesProto.CACHE_SIZE, cacheSizesArray.getLong(i));
275
276 proto.end(packageToken);
277 }
278 } else {
Daniel Nishib6cc8382017-09-14 17:10:00 -0700279 Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray, appDataSizesArray "
280 + " and cacheSizesArray are not the same");
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800281 }
282
283 proto.end(cachedValuesToken);
284 } catch (IOException | JSONException e) {
285 Log.w(TAG, "exception reading diskstats cache file", e);
286 }
287 }
Dan Egnor621bc542010-03-25 16:20:14 -0700288}