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