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.ClipData; 20 import android.content.ClipDescription; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.net.Uri; 24 import android.os.Build; 25 import android.util.Log; 26 27 import androidx.core.content.FileProvider; 28 29 import java.io.BufferedInputStream; 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.FileOutputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.zip.ZipEntry; 38 import java.util.zip.ZipOutputStream; 39 40 /** 41 * Utility class for dumping, compressing, sending, and serving heap dump files. 42 * 43 * <p>Unlike the Internet, this IS a big truck you can dump something on. 44 */ 45 public class DumpTruck { 46 private static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider"; 47 private static final String FILEPROVIDER_PATH = "leak"; 48 49 private static final String TAG = "DumpTruck"; 50 private static final int BUFSIZ = 1024 * 1024; // 1MB 51 52 private final Context context; 53 private final GarbageMonitor mGarbageMonitor; 54 private Uri hprofUri; 55 private long rss; 56 final StringBuilder body = new StringBuilder(); 57 DumpTruck(Context context, GarbageMonitor garbageMonitor)58 public DumpTruck(Context context, GarbageMonitor garbageMonitor) { 59 this.context = context; 60 mGarbageMonitor = garbageMonitor; 61 } 62 63 /** 64 * Capture memory for the given processes and zip them up for sharing. 65 * 66 * @param pids 67 * @return this, for chaining 68 */ captureHeaps(List<Long> pids)69 public DumpTruck captureHeaps(List<Long> pids) { 70 final File dumpDir = new File(context.getCacheDir(), FILEPROVIDER_PATH); 71 dumpDir.mkdirs(); 72 hprofUri = null; 73 74 body.setLength(0); 75 body.append("Build: ").append(Build.DISPLAY).append("\n\nProcesses:\n"); 76 77 final ArrayList<String> paths = new ArrayList<String>(); 78 final int myPid = android.os.Process.myPid(); 79 80 for (Long pidL : pids) { 81 final int pid = pidL.intValue(); 82 body.append(" pid ").append(pid); 83 GarbageMonitor.ProcessMemInfo info = mGarbageMonitor.getMemInfo(pid); 84 if (info != null) { 85 body.append(":") 86 .append(" up=") 87 .append(info.getUptime()) 88 .append(" rss=") 89 .append(info.currentRss); 90 rss = info.currentRss; 91 } 92 if (pid == myPid) { 93 final String path = 94 new File(dumpDir, String.format("heap-%d.ahprof", pid)).getPath(); 95 Log.v(TAG, "Dumping memory info for process " + pid + " to " + path); 96 try { 97 android.os.Debug.dumpHprofData(path); // will block 98 paths.add(path); 99 body.append(" (hprof attached)"); 100 } catch (IOException e) { 101 Log.e(TAG, "error dumping memory:", e); 102 body.append("\n** Could not dump heap: \n").append(e.toString()).append("\n"); 103 } 104 } 105 body.append("\n"); 106 } 107 108 try { 109 final String zipfile = 110 new File(dumpDir, String.format("hprof-%d.zip", System.currentTimeMillis())) 111 .getCanonicalPath(); 112 if (DumpTruck.zipUp(zipfile, paths)) { 113 final File pathFile = new File(zipfile); 114 hprofUri = FileProvider.getUriForFile(context, FILEPROVIDER_AUTHORITY, pathFile); 115 Log.v(TAG, "Heap dump accessible at URI: " + hprofUri); 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_MULTIPLE); 141 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 142 shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 143 shareIntent.putExtra(Intent.EXTRA_SUBJECT, 144 String.format("SystemUI memory dump (rss=%dM)", rss / 1024)); 145 146 shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString()); 147 148 if (hprofUri != null) { 149 final ArrayList<Uri> uriList = new ArrayList<>(); 150 uriList.add(hprofUri); 151 shareIntent.setType("application/zip"); 152 shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList); 153 154 // Include URI in ClipData also, so that grantPermission picks it up. 155 // We don't use setData here because some apps interpret this as "to:". 156 ClipData clipdata = new ClipData(new ClipDescription("content", 157 new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), 158 new ClipData.Item(hprofUri)); 159 shareIntent.setClipData(clipdata); 160 shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 161 } 162 return shareIntent; 163 } 164 zipUp(String zipfilePath, ArrayList<String> paths)165 private static boolean zipUp(String zipfilePath, ArrayList<String> paths) { 166 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipfilePath))) { 167 final byte[] buf = new byte[BUFSIZ]; 168 169 for (String filename : paths) { 170 try (InputStream is = new BufferedInputStream(new FileInputStream(filename))) { 171 ZipEntry entry = new ZipEntry(filename); 172 zos.putNextEntry(entry); 173 int len; 174 while (0 < (len = is.read(buf, 0, BUFSIZ))) { 175 zos.write(buf, 0, len); 176 } 177 zos.closeEntry(); 178 } 179 } 180 return true; 181 } catch (IOException e) { 182 Log.e(TAG, "error zipping up profile data", e); 183 } 184 return false; 185 } 186 } 187