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