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