Home | History | Annotate | Download | only in leak
      1 /*
      2  * Copyright (C) 2017 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.systemui.util.leak;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.net.Uri;
     22 import android.os.Build;
     23 import android.support.v4.content.FileProvider;
     24 import android.util.Log;
     25 
     26 import com.android.systemui.Dependency;
     27 
     28 import java.io.BufferedInputStream;
     29 import java.io.File;
     30 import java.io.FileInputStream;
     31 import java.io.FileOutputStream;
     32 import java.io.IOException;
     33 import java.io.InputStream;
     34 import java.util.ArrayList;
     35 import java.util.Arrays;
     36 import java.util.zip.ZipEntry;
     37 import java.util.zip.ZipOutputStream;
     38 
     39 /**
     40  * Utility class for dumping, compressing, sending, and serving heap dump files.
     41  *
     42  * <p>Unlike the Internet, this IS a big truck you can dump something on.
     43  */
     44 public class DumpTruck {
     45     private static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
     46     private static final String FILEPROVIDER_PATH = "leak";
     47 
     48     private static final String TAG = "DumpTruck";
     49     private static final int BUFSIZ = 512 * 1024; // 512K
     50 
     51     private final Context context;
     52     private Uri hprofUri;
     53     final StringBuilder body = new StringBuilder();
     54 
     55     public DumpTruck(Context context) {
     56         this.context = context;
     57     }
     58 
     59     /**
     60      * Capture memory for the given processes and zip them up for sharing.
     61      *
     62      * @param pids
     63      * @return this, for chaining
     64      */
     65     public DumpTruck captureHeaps(int[] pids) {
     66         final GarbageMonitor gm = Dependency.get(GarbageMonitor.class);
     67 
     68         final File dumpDir = new File(context.getCacheDir(), FILEPROVIDER_PATH);
     69         dumpDir.mkdirs();
     70         hprofUri = null;
     71 
     72         body.setLength(0);
     73         body.append("Build: ").append(Build.DISPLAY).append("\n\nProcesses:\n");
     74 
     75         final ArrayList<String> paths = new ArrayList<String>();
     76         final int myPid = android.os.Process.myPid();
     77 
     78         final int[] pids_copy = Arrays.copyOf(pids, pids.length);
     79         for (int pid : pids_copy) {
     80             body.append("  pid ").append(pid);
     81             if (gm != null) {
     82                 GarbageMonitor.ProcessMemInfo info = gm.getMemInfo(pid);
     83                 if (info != null) {
     84                     body.append(":")
     85                             .append(" up=")
     86                             .append(info.getUptime())
     87                             .append(" pss=")
     88                             .append(info.currentPss)
     89                             .append(" uss=")
     90                             .append(info.currentUss);
     91                 }
     92             }
     93             if (pid == myPid) {
     94                 final String path =
     95                         new File(dumpDir, String.format("heap-%d.ahprof", pid)).getPath();
     96                 Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
     97                 try {
     98                     android.os.Debug.dumpHprofData(path); // will block
     99                     paths.add(path);
    100                     body.append(" (hprof attached)");
    101                 } catch (IOException e) {
    102                     Log.e(TAG, "error dumping memory:", e);
    103                     body.append("\n** Could not dump heap: \n").append(e.toString()).append("\n");
    104                 }
    105             }
    106             body.append("\n");
    107         }
    108 
    109         try {
    110             final String zipfile =
    111                     new File(dumpDir, String.format("hprof-%d.zip", System.currentTimeMillis()))
    112                             .getCanonicalPath();
    113             if (DumpTruck.zipUp(zipfile, paths)) {
    114                 final File pathFile = new File(zipfile);
    115                 hprofUri = FileProvider.getUriForFile(context, FILEPROVIDER_AUTHORITY, pathFile);
    116             }
    117         } catch (IOException e) {
    118             Log.e(TAG, "unable to zip up heapdumps", e);
    119             body.append("\n** Could not zip up files: \n").append(e.toString()).append("\n");
    120         }
    121 
    122         return this;
    123     }
    124 
    125     /**
    126      * Get the Uri of the current heap dump. Be sure to call captureHeaps first.
    127      *
    128      * @return Uri to the dump served by the SystemUI file provider
    129      */
    130     public Uri getDumpUri() {
    131         return hprofUri;
    132     }
    133 
    134     /**
    135      * Get an ACTION_SEND intent suitable for startActivity() or attaching to a Notification.
    136      *
    137      * @return share intent
    138      */
    139     public Intent createShareIntent() {
    140         Intent shareIntent = new Intent(Intent.ACTION_SEND);
    141         shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    142         shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    143         shareIntent.putExtra(Intent.EXTRA_SUBJECT, "SystemUI memory dump");
    144 
    145         shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
    146 
    147         if (hprofUri != null) {
    148             shareIntent.setType("application/zip");
    149             shareIntent.putExtra(Intent.EXTRA_STREAM, hprofUri);
    150         }
    151         return shareIntent;
    152     }
    153 
    154     private static boolean zipUp(String zipfilePath, ArrayList<String> paths) {
    155         try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipfilePath))) {
    156             final byte[] buf = new byte[BUFSIZ];
    157 
    158             for (String filename : paths) {
    159                 try (InputStream is = new BufferedInputStream(new FileInputStream(filename))) {
    160                     ZipEntry entry = new ZipEntry(filename);
    161                     zos.putNextEntry(entry);
    162                     int len;
    163                     while (0 < (len = is.read(buf, 0, BUFSIZ))) {
    164                         zos.write(buf, 0, len);
    165                     }
    166                     zos.closeEntry();
    167                 }
    168             }
    169             return true;
    170         } catch (IOException e) {
    171             Log.e(TAG, "error zipping up profile data", e);
    172         }
    173         return false;
    174     }
    175 }
    176