blob: 8ea3dd64697e03969722e58cc3358a095d514772 [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;
Michael Wachenschwanz3e20a102017-12-14 18:32:14 -080022import android.os.IBinder;
23import android.os.IStoraged;
24import android.os.RemoteException;
25import android.os.ServiceManager;
Dan Egnor621bc542010-03-25 16:20:14 -070026import android.os.StatFs;
27import android.os.SystemClock;
Jeff Sharkeyb92b05b2016-01-28 09:50:00 -070028import android.os.storage.StorageManager;
Amith Yamasani38f91ff2017-01-10 14:42:38 -080029import android.service.diskstats.DiskStatsAppSizesProto;
30import android.service.diskstats.DiskStatsCachedValuesProto;
31import android.service.diskstats.DiskStatsFreeSpaceProto;
32import android.service.diskstats.DiskStatsServiceDumpProto;
Daniel Nishi090b2d92016-12-13 10:38:42 -080033import android.util.Log;
Amith Yamasani38f91ff2017-01-10 14:42:38 -080034import android.util.Slog;
35import android.util.proto.ProtoOutputStream;
Daniel Nishi090b2d92016-12-13 10:38:42 -080036
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060037import com.android.internal.util.DumpUtils;
Daniel Nishi090b2d92016-12-13 10:38:42 -080038import com.android.server.storage.DiskStatsFileLogger;
39import com.android.server.storage.DiskStatsLoggingService;
40
41import libcore.io.IoUtils;
42
Amith Yamasani38f91ff2017-01-10 14:42:38 -080043import org.json.JSONArray;
Daniel Nishi090b2d92016-12-13 10:38:42 -080044import org.json.JSONException;
45import org.json.JSONObject;
Dan Egnor621bc542010-03-25 16:20:14 -070046
47import java.io.File;
48import java.io.FileDescriptor;
49import java.io.FileOutputStream;
50import java.io.IOException;
51import java.io.PrintWriter;
52
53/**
54 * This service exists only as a "dumpsys" target which reports
55 * statistics about the status of the disk.
56 */
57public class DiskStatsService extends Binder {
Jeff Sharkeyeb4cc4922012-04-26 18:17:29 -070058 private static final String TAG = "DiskStatsService";
Daniel Nishi090b2d92016-12-13 10:38:42 -080059 private static final String DISKSTATS_DUMP_FILE = "/data/system/diskstats_cache.json";
Jeff Sharkeyeb4cc4922012-04-26 18:17:29 -070060
Dan Egnor621bc542010-03-25 16:20:14 -070061 private final Context mContext;
62
63 public DiskStatsService(Context context) {
64 mContext = context;
Daniel Nishi090b2d92016-12-13 10:38:42 -080065 DiskStatsLoggingService.schedule(context);
Dan Egnor621bc542010-03-25 16:20:14 -070066 }
67
68 @Override
69 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkey6df866a2017-03-31 14:08:23 -060070 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
Dan Egnor621bc542010-03-25 16:20:14 -070071
72 // Run a quick-and-dirty performance test: write 512 bytes
73 byte[] junk = new byte[512];
74 for (int i = 0; i < junk.length; i++) junk[i] = (byte) i; // Write nonzero bytes
75
76 File tmp = new File(Environment.getDataDirectory(), "system/perftest.tmp");
77 FileOutputStream fos = null;
78 IOException error = null;
79
80 long before = SystemClock.uptimeMillis();
81 try {
82 fos = new FileOutputStream(tmp);
83 fos.write(junk);
84 } catch (IOException e) {
85 error = e;
86 } finally {
87 try { if (fos != null) fos.close(); } catch (IOException e) {}
88 }
89
90 long after = SystemClock.uptimeMillis();
91 if (tmp.exists()) tmp.delete();
92
Amith Yamasani38f91ff2017-01-10 14:42:38 -080093 boolean protoFormat = hasOption(args, "--proto");
94 ProtoOutputStream proto = null;
95
96 if (protoFormat) {
97 proto = new ProtoOutputStream(fd);
98 pw = null;
99 proto.write(DiskStatsServiceDumpProto.HAS_TEST_ERROR, error != null);
100 if (error != null) {
101 proto.write(DiskStatsServiceDumpProto.ERROR_MESSAGE, error.toString());
102 } else {
103 proto.write(DiskStatsServiceDumpProto.WRITE_512B_LATENCY_MILLIS, after - before);
104 }
Dan Egnor621bc542010-03-25 16:20:14 -0700105 } else {
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800106 if (error != null) {
107 pw.print("Test-Error: ");
108 pw.println(error.toString());
109 } else {
110 pw.print("Latency: ");
111 pw.print(after - before);
112 pw.println("ms [512B Data Write]");
113 }
Dan Egnor621bc542010-03-25 16:20:14 -0700114 }
115
Michael Wachenschwanz3e20a102017-12-14 18:32:14 -0800116 if (protoFormat) {
117 reportDiskWriteSpeedProto(proto);
118 } else {
119 reportDiskWriteSpeed(pw);
120 }
121
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800122 reportFreeSpace(Environment.getDataDirectory(), "Data", pw, proto,
123 DiskStatsFreeSpaceProto.FOLDER_DATA);
124 reportFreeSpace(Environment.getDownloadCacheDirectory(), "Cache", pw, proto,
125 DiskStatsFreeSpaceProto.FOLDER_CACHE);
126 reportFreeSpace(new File("/system"), "System", pw, proto,
127 DiskStatsFreeSpaceProto.FOLDER_SYSTEM);
Dan Egnor621bc542010-03-25 16:20:14 -0700128
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800129 boolean fileBased = StorageManager.isFileEncryptedNativeOnly();
130 boolean blockBased = fileBased ? false : StorageManager.isBlockEncrypted();
131 if (protoFormat) {
132 if (fileBased) {
133 proto.write(DiskStatsServiceDumpProto.ENCRYPTION,
134 DiskStatsServiceDumpProto.ENCRYPTION_FILE_BASED);
135 } else if (blockBased) {
136 proto.write(DiskStatsServiceDumpProto.ENCRYPTION,
137 DiskStatsServiceDumpProto.ENCRYPTION_FULL_DISK);
138 } else {
139 proto.write(DiskStatsServiceDumpProto.ENCRYPTION,
140 DiskStatsServiceDumpProto.ENCRYPTION_NONE);
141 }
142 } else if (fileBased) {
Jeff Sharkeyb92b05b2016-01-28 09:50:00 -0700143 pw.println("File-based Encryption: true");
144 }
145
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800146 if (protoFormat) {
147 reportCachedValuesProto(proto);
148 } else {
149 reportCachedValues(pw);
150 }
Daniel Nishi090b2d92016-12-13 10:38:42 -0800151
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800152 if (protoFormat) {
153 proto.flush();
154 }
Dan Egnor621bc542010-03-25 16:20:14 -0700155 // TODO: Read /proc/yaffs and report interesting values;
156 // add configurable (through args) performance test parameters.
157 }
158
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800159 private void reportFreeSpace(File path, String name, PrintWriter pw,
160 ProtoOutputStream proto, int folderType) {
Dan Egnor621bc542010-03-25 16:20:14 -0700161 try {
162 StatFs statfs = new StatFs(path.getPath());
163 long bsize = statfs.getBlockSize();
164 long avail = statfs.getAvailableBlocks();
165 long total = statfs.getBlockCount();
166 if (bsize <= 0 || total <= 0) {
167 throw new IllegalArgumentException(
168 "Invalid stat: bsize=" + bsize + " avail=" + avail + " total=" + total);
169 }
170
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800171 if (proto != null) {
172 long freeSpaceToken = proto.start(DiskStatsServiceDumpProto.PARTITIONS_FREE_SPACE);
173 proto.write(DiskStatsFreeSpaceProto.FOLDER, folderType);
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700174 proto.write(DiskStatsFreeSpaceProto.AVAILABLE_SPACE_KB, avail * bsize / 1024);
175 proto.write(DiskStatsFreeSpaceProto.TOTAL_SPACE_KB, total * bsize / 1024);
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800176 proto.end(freeSpaceToken);
177 } else {
178 pw.print(name);
179 pw.print("-Free: ");
180 pw.print(avail * bsize / 1024);
181 pw.print("K / ");
182 pw.print(total * bsize / 1024);
183 pw.print("K total = ");
184 pw.print(avail * 100 / total);
185 pw.println("% free");
186 }
Dan Egnor621bc542010-03-25 16:20:14 -0700187 } catch (IllegalArgumentException e) {
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800188 if (proto != null) {
189 // Empty proto
190 } else {
191 pw.print(name);
192 pw.print("-Error: ");
193 pw.println(e.toString());
194 }
Dan Egnor621bc542010-03-25 16:20:14 -0700195 return;
196 }
197 }
Daniel Nishi090b2d92016-12-13 10:38:42 -0800198
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800199 private boolean hasOption(String[] args, String arg) {
200 for (String opt : args) {
201 if (arg.equals(opt)) {
202 return true;
203 }
204 }
205 return false;
206 }
207
208 // If you change this method, make sure to modify the Proto version of this method as well.
Daniel Nishi090b2d92016-12-13 10:38:42 -0800209 private void reportCachedValues(PrintWriter pw) {
210 try {
211 String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
212 JSONObject json = new JSONObject(jsonString);
213 pw.print("App Size: ");
214 pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
Daniel Nishib6cc8382017-09-14 17:10:00 -0700215 pw.print("App Data Size: ");
216 pw.println(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
Daniel Nishi090b2d92016-12-13 10:38:42 -0800217 pw.print("App Cache Size: ");
218 pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
219 pw.print("Photos Size: ");
220 pw.println(json.getLong(DiskStatsFileLogger.PHOTOS_KEY));
221 pw.print("Videos Size: ");
222 pw.println(json.getLong(DiskStatsFileLogger.VIDEOS_KEY));
223 pw.print("Audio Size: ");
224 pw.println(json.getLong(DiskStatsFileLogger.AUDIO_KEY));
225 pw.print("Downloads Size: ");
226 pw.println(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY));
227 pw.print("System Size: ");
228 pw.println(json.getLong(DiskStatsFileLogger.SYSTEM_KEY));
229 pw.print("Other Size: ");
230 pw.println(json.getLong(DiskStatsFileLogger.MISC_KEY));
231 pw.print("Package Names: ");
232 pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY));
233 pw.print("App Sizes: ");
234 pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY));
Daniel Nishib6cc8382017-09-14 17:10:00 -0700235 pw.print("App Data Sizes: ");
236 pw.println(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY));
Daniel Nishi090b2d92016-12-13 10:38:42 -0800237 pw.print("Cache Sizes: ");
238 pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY));
239 } catch (IOException | JSONException e) {
240 Log.w(TAG, "exception reading diskstats cache file", e);
241 }
242 }
243
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800244 private void reportCachedValuesProto(ProtoOutputStream proto) {
245 try {
246 String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
247 JSONObject json = new JSONObject(jsonString);
248 long cachedValuesToken = proto.start(DiskStatsServiceDumpProto.CACHED_FOLDER_SIZES);
249
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700250 proto.write(DiskStatsCachedValuesProto.AGG_APPS_SIZE_KB,
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800251 json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700252 proto.write(DiskStatsCachedValuesProto.AGG_APPS_DATA_SIZE_KB,
Daniel Nishib6cc8382017-09-14 17:10:00 -0700253 json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700254 proto.write(DiskStatsCachedValuesProto.AGG_APPS_CACHE_SIZE_KB,
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800255 json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700256 proto.write(DiskStatsCachedValuesProto.PHOTOS_SIZE_KB,
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800257 json.getLong(DiskStatsFileLogger.PHOTOS_KEY));
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700258 proto.write(DiskStatsCachedValuesProto.VIDEOS_SIZE_KB,
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800259 json.getLong(DiskStatsFileLogger.VIDEOS_KEY));
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700260 proto.write(DiskStatsCachedValuesProto.AUDIO_SIZE_KB,
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800261 json.getLong(DiskStatsFileLogger.AUDIO_KEY));
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700262 proto.write(DiskStatsCachedValuesProto.DOWNLOADS_SIZE_KB,
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800263 json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY));
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700264 proto.write(DiskStatsCachedValuesProto.SYSTEM_SIZE_KB,
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800265 json.getLong(DiskStatsFileLogger.SYSTEM_KEY));
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700266 proto.write(DiskStatsCachedValuesProto.OTHER_SIZE_KB,
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800267 json.getLong(DiskStatsFileLogger.MISC_KEY));
268
269 JSONArray packageNamesArray = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
270 JSONArray appSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
Daniel Nishib6cc8382017-09-14 17:10:00 -0700271 JSONArray appDataSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY);
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800272 JSONArray cacheSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
273 final int len = packageNamesArray.length();
Daniel Nishib6cc8382017-09-14 17:10:00 -0700274 if (len == appSizesArray.length()
275 && len == appDataSizesArray.length()
276 && len == cacheSizesArray.length()) {
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800277 for (int i = 0; i < len; i++) {
278 long packageToken = proto.start(DiskStatsCachedValuesProto.APP_SIZES);
279
280 proto.write(DiskStatsAppSizesProto.PACKAGE_NAME,
281 packageNamesArray.getString(i));
Kweku Adamse7d2f0e2018-03-15 11:29:42 -0700282 proto.write(DiskStatsAppSizesProto.APP_SIZE_KB, appSizesArray.getLong(i));
283 proto.write(DiskStatsAppSizesProto.APP_DATA_SIZE_KB, appDataSizesArray.getLong(i));
284 proto.write(DiskStatsAppSizesProto.CACHE_SIZE_KB, cacheSizesArray.getLong(i));
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800285
286 proto.end(packageToken);
287 }
288 } else {
Daniel Nishib6cc8382017-09-14 17:10:00 -0700289 Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray, appDataSizesArray "
290 + " and cacheSizesArray are not the same");
Amith Yamasani38f91ff2017-01-10 14:42:38 -0800291 }
292
293 proto.end(cachedValuesToken);
294 } catch (IOException | JSONException e) {
295 Log.w(TAG, "exception reading diskstats cache file", e);
296 }
297 }
Michael Wachenschwanz3e20a102017-12-14 18:32:14 -0800298
299 private int getRecentPerf() throws RemoteException, IllegalStateException {
300 IBinder binder = ServiceManager.getService("storaged");
301 if (binder == null) throw new IllegalStateException("storaged not found");
302 IStoraged storaged = IStoraged.Stub.asInterface(binder);
303 return storaged.getRecentPerf();
304 }
305
306 // Keep reportDiskWriteSpeed and reportDiskWriteSpeedProto in sync
307 private void reportDiskWriteSpeed(PrintWriter pw) {
308 try {
309 long perf = getRecentPerf();
310 if (perf != 0) {
311 pw.print("Recent Disk Write Speed (kB/s) = ");
312 pw.println(perf);
313 } else {
314 pw.println("Recent Disk Write Speed data unavailable");
315 Log.w(TAG, "Recent Disk Write Speed data unavailable!");
316 }
317 } catch (RemoteException | IllegalStateException e) {
318 pw.println(e.toString());
319 Log.e(TAG, e.toString());
320 }
321 }
322
323 private void reportDiskWriteSpeedProto(ProtoOutputStream proto) {
324 try {
325 long perf = getRecentPerf();
326 if (perf != 0) {
327 proto.write(DiskStatsServiceDumpProto.BENCHMARKED_WRITE_SPEED_KBPS, perf);
328 } else {
329 Log.w(TAG, "Recent Disk Write Speed data unavailable!");
330 }
331 } catch (RemoteException | IllegalStateException e) {
332 Log.e(TAG, e.toString());
333 }
334 }
Dan Egnor621bc542010-03-25 16:20:14 -0700335}