• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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