Home | History | Annotate | Download | only in server
      1 /*
      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 
     17 package com.android.server;
     18 
     19 import android.content.Context;
     20 import android.os.Binder;
     21 import android.os.Environment;
     22 import android.os.StatFs;
     23 import android.os.SystemClock;
     24 import android.os.storage.StorageManager;
     25 import android.service.diskstats.DiskStatsAppSizesProto;
     26 import android.service.diskstats.DiskStatsCachedValuesProto;
     27 import android.service.diskstats.DiskStatsFreeSpaceProto;
     28 import android.service.diskstats.DiskStatsServiceDumpProto;
     29 import android.util.Log;
     30 import android.util.Slog;
     31 import android.util.proto.ProtoOutputStream;
     32 
     33 import com.android.internal.util.DumpUtils;
     34 import com.android.server.storage.DiskStatsFileLogger;
     35 import com.android.server.storage.DiskStatsLoggingService;
     36 
     37 import libcore.io.IoUtils;
     38 
     39 import org.json.JSONArray;
     40 import org.json.JSONException;
     41 import org.json.JSONObject;
     42 
     43 import java.io.File;
     44 import java.io.FileDescriptor;
     45 import java.io.FileOutputStream;
     46 import java.io.IOException;
     47 import 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  */
     53 public class DiskStatsService extends Binder {
     54     private static final String TAG = "DiskStatsService";
     55     private static final String DISKSTATS_DUMP_FILE = "/data/system/diskstats_cache.json";
     56 
     57     private final Context mContext;
     58 
     59     public DiskStatsService(Context context) {
     60         mContext = context;
     61         DiskStatsLoggingService.schedule(context);
     62     }
     63 
     64     @Override
     65     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
     66         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
     67 
     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 
     89         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             }
    101         } else {
    102             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             }
    110         }
    111 
    112         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);
    118 
    119         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) {
    133             pw.println("File-based Encryption: true");
    134         }
    135 
    136         if (protoFormat) {
    137             reportCachedValuesProto(proto);
    138         } else {
    139             reportCachedValues(pw);
    140         }
    141 
    142         if (protoFormat) {
    143             proto.flush();
    144         }
    145         // TODO: Read /proc/yaffs and report interesting values;
    146         // add configurable (through args) performance test parameters.
    147     }
    148 
    149     private void reportFreeSpace(File path, String name, PrintWriter pw,
    150             ProtoOutputStream proto, int folderType) {
    151         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 
    161             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             }
    177         } catch (IllegalArgumentException e) {
    178             if (proto != null) {
    179                 // Empty proto
    180             } else {
    181                 pw.print(name);
    182                 pw.print("-Error: ");
    183                 pw.println(e.toString());
    184             }
    185             return;
    186         }
    187     }
    188 
    189     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.
    199     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));
    205             pw.print("App Cache Size: ");
    206             pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
    207             pw.print("Photos Size: ");
    208             pw.println(json.getLong(DiskStatsFileLogger.PHOTOS_KEY));
    209             pw.print("Videos Size: ");
    210             pw.println(json.getLong(DiskStatsFileLogger.VIDEOS_KEY));
    211             pw.print("Audio Size: ");
    212             pw.println(json.getLong(DiskStatsFileLogger.AUDIO_KEY));
    213             pw.print("Downloads Size: ");
    214             pw.println(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY));
    215             pw.print("System Size: ");
    216             pw.println(json.getLong(DiskStatsFileLogger.SYSTEM_KEY));
    217             pw.print("Other Size: ");
    218             pw.println(json.getLong(DiskStatsFileLogger.MISC_KEY));
    219             pw.print("Package Names: ");
    220             pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY));
    221             pw.print("App Sizes: ");
    222             pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY));
    223             pw.print("Cache Sizes: ");
    224             pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY));
    225         } catch (IOException | JSONException e) {
    226             Log.w(TAG, "exception reading diskstats cache file", e);
    227         }
    228     }
    229 
    230     private void reportCachedValuesProto(ProtoOutputStream proto) {
    231         try {
    232             String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE);
    233             JSONObject json = new JSONObject(jsonString);
    234             long cachedValuesToken = proto.start(DiskStatsServiceDumpProto.CACHED_FOLDER_SIZES);
    235 
    236             proto.write(DiskStatsCachedValuesProto.AGG_APPS_SIZE,
    237                     json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
    238             proto.write(DiskStatsCachedValuesProto.AGG_APPS_CACHE_SIZE,
    239                     json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
    240             proto.write(DiskStatsCachedValuesProto.PHOTOS_SIZE,
    241                     json.getLong(DiskStatsFileLogger.PHOTOS_KEY));
    242             proto.write(DiskStatsCachedValuesProto.VIDEOS_SIZE,
    243                     json.getLong(DiskStatsFileLogger.VIDEOS_KEY));
    244             proto.write(DiskStatsCachedValuesProto.AUDIO_SIZE,
    245                     json.getLong(DiskStatsFileLogger.AUDIO_KEY));
    246             proto.write(DiskStatsCachedValuesProto.DOWNLOADS_SIZE,
    247                     json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY));
    248             proto.write(DiskStatsCachedValuesProto.SYSTEM_SIZE,
    249                     json.getLong(DiskStatsFileLogger.SYSTEM_KEY));
    250             proto.write(DiskStatsCachedValuesProto.OTHER_SIZE,
    251                     json.getLong(DiskStatsFileLogger.MISC_KEY));
    252 
    253             JSONArray packageNamesArray = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
    254             JSONArray appSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
    255             JSONArray cacheSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
    256             final int len = packageNamesArray.length();
    257             if (len == appSizesArray.length() && len == cacheSizesArray.length()) {
    258                 for (int i = 0; i < len; i++) {
    259                     long packageToken = proto.start(DiskStatsCachedValuesProto.APP_SIZES);
    260 
    261                     proto.write(DiskStatsAppSizesProto.PACKAGE_NAME,
    262                             packageNamesArray.getString(i));
    263                     proto.write(DiskStatsAppSizesProto.APP_SIZE, appSizesArray.getLong(i));
    264                     proto.write(DiskStatsAppSizesProto.CACHE_SIZE, cacheSizesArray.getLong(i));
    265 
    266                     proto.end(packageToken);
    267                 }
    268             } else {
    269                 Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray and cacheSizesArray "
    270                         + "are not the same");
    271             }
    272 
    273             proto.end(cachedValuesToken);
    274         } catch (IOException | JSONException e) {
    275             Log.w(TAG, "exception reading diskstats cache file", e);
    276         }
    277     }
    278 }
    279