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 DumpTruck(Context context)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 */ captureHeaps(int[] pids)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 */ getDumpUri()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 */ createShareIntent()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 zipUp(String zipfilePath, ArrayList<String> paths)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