1 /*
2  * Copyright (C) 2009 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.internal.os;
18 
19 import android.content.pm.PackageInfo;
20 import android.os.Build;
21 import android.os.SystemProperties;
22 import android.util.Log;
23 import dalvik.system.profiler.BinaryHprofWriter;
24 import dalvik.system.profiler.SamplingProfiler;
25 import java.io.BufferedOutputStream;
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.OutputStream;
30 import java.io.PrintStream;
31 import java.util.Date;
32 import java.util.concurrent.Executor;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.ThreadFactory;
35 import java.util.concurrent.atomic.AtomicBoolean;
36 import libcore.io.IoUtils;
37 
38 /**
39  * Integrates the framework with Dalvik's sampling profiler.
40  */
41 public class SamplingProfilerIntegration {
42 
43     private static final String TAG = "SamplingProfilerIntegration";
44 
45     public static final String SNAPSHOT_DIR = "/data/snapshots";
46 
47     private static final boolean enabled;
48     private static final Executor snapshotWriter;
49     private static final int samplingProfilerMilliseconds;
50     private static final int samplingProfilerDepth;
51 
52     /** Whether or not a snapshot is being persisted. */
53     private static final AtomicBoolean pending = new AtomicBoolean(false);
54 
55     static {
56         samplingProfilerMilliseconds = SystemProperties.getInt("persist.sys.profiler_ms", 0);
57         samplingProfilerDepth = SystemProperties.getInt("persist.sys.profiler_depth", 4);
58         if (samplingProfilerMilliseconds > 0) {
59             File dir = new File(SNAPSHOT_DIR);
dir.mkdirs()60             dir.mkdirs();
61             // the directory needs to be writable to anybody to allow file writing
dir.setWritable(true, false)62             dir.setWritable(true, false);
63             // the directory needs to be executable to anybody to allow file creation
dir.setExecutable(true, false)64             dir.setExecutable(true, false);
65             if (dir.isDirectory()) {
66                 snapshotWriter = Executors.newSingleThreadExecutor(new ThreadFactory() {
67                         public Thread newThread(Runnable r) {
68                             return new Thread(r, TAG);
69                         }
70                     });
71                 enabled = true;
Log.i(TAG, "Profiling enabled. Sampling interval ms: " + samplingProfilerMilliseconds)72                 Log.i(TAG, "Profiling enabled. Sampling interval ms: "
73                       + samplingProfilerMilliseconds);
74             } else {
75                 snapshotWriter = null;
76                 enabled = true;
Log.w(TAG, "Profiling setup failed. Could not create " + SNAPSHOT_DIR)77                 Log.w(TAG, "Profiling setup failed. Could not create " + SNAPSHOT_DIR);
78             }
79         } else {
80             snapshotWriter = null;
81             enabled = false;
Log.i(TAG, "Profiling disabled.")82             Log.i(TAG, "Profiling disabled.");
83         }
84     }
85 
86     private static SamplingProfiler samplingProfiler;
87     private static long startMillis;
88 
89     /**
90      * Is profiling enabled?
91      */
isEnabled()92     public static boolean isEnabled() {
93         return enabled;
94     }
95 
96     /**
97      * Starts the profiler if profiling is enabled.
98      */
start()99     public static void start() {
100         if (!enabled) {
101             return;
102         }
103         if (samplingProfiler != null) {
104             Log.e(TAG, "SamplingProfilerIntegration already started at " + new Date(startMillis));
105             return;
106         }
107 
108         ThreadGroup group = Thread.currentThread().getThreadGroup();
109         SamplingProfiler.ThreadSet threadSet = SamplingProfiler.newThreadGroupThreadSet(group);
110         samplingProfiler = new SamplingProfiler(samplingProfilerDepth, threadSet);
111         samplingProfiler.start(samplingProfilerMilliseconds);
112         startMillis = System.currentTimeMillis();
113     }
114 
115     /**
116      * Writes a snapshot if profiling is enabled.
117      */
writeSnapshot(final String processName, final PackageInfo packageInfo)118     public static void writeSnapshot(final String processName, final PackageInfo packageInfo) {
119         if (!enabled) {
120             return;
121         }
122         if (samplingProfiler == null) {
123             Log.e(TAG, "SamplingProfilerIntegration is not started");
124             return;
125         }
126 
127         /*
128          * If we're already writing a snapshot, don't bother enqueueing another
129          * request right now. This will reduce the number of individual
130          * snapshots and in turn the total amount of memory consumed (one big
131          * snapshot is smaller than N subset snapshots).
132          */
133         if (pending.compareAndSet(false, true)) {
134             snapshotWriter.execute(new Runnable() {
135                 public void run() {
136                     try {
137                         writeSnapshotFile(processName, packageInfo);
138                     } finally {
139                         pending.set(false);
140                     }
141                 }
142             });
143         }
144     }
145 
146     /**
147      * Writes the zygote's snapshot to internal storage if profiling is enabled.
148      */
writeZygoteSnapshot()149     public static void writeZygoteSnapshot() {
150         if (!enabled) {
151             return;
152         }
153         writeSnapshotFile("zygote", null);
154         samplingProfiler.shutdown();
155         samplingProfiler = null;
156         startMillis = 0;
157     }
158 
159     /**
160      * pass in PackageInfo to retrieve various values for snapshot header
161      */
writeSnapshotFile(String processName, PackageInfo packageInfo)162     private static void writeSnapshotFile(String processName, PackageInfo packageInfo) {
163         if (!enabled) {
164             return;
165         }
166         samplingProfiler.stop();
167 
168         /*
169          * We use the global start time combined with the process name
170          * as a unique ID. We can't use a counter because processes
171          * restart. This could result in some overlap if we capture
172          * two snapshots in rapid succession.
173          */
174         String name = processName.replaceAll(":", ".");
175         String path = SNAPSHOT_DIR + "/" + name + "-" + startMillis + ".snapshot";
176         long start = System.currentTimeMillis();
177         OutputStream outputStream = null;
178         try {
179             outputStream = new BufferedOutputStream(new FileOutputStream(path));
180             PrintStream out = new PrintStream(outputStream);
181             generateSnapshotHeader(name, packageInfo, out);
182             if (out.checkError()) {
183                 throw new IOException();
184             }
185             BinaryHprofWriter.write(samplingProfiler.getHprofData(), outputStream);
186         } catch (IOException e) {
187             Log.e(TAG, "Error writing snapshot to " + path, e);
188             return;
189         } finally {
190             IoUtils.closeQuietly(outputStream);
191         }
192         // set file readable to the world so that SamplingProfilerService
193         // can put it to dropbox
194         new File(path).setReadable(true, false);
195 
196         long elapsed = System.currentTimeMillis() - start;
197         Log.i(TAG, "Wrote snapshot " + path + " in " + elapsed + "ms.");
198         samplingProfiler.start(samplingProfilerMilliseconds);
199     }
200 
201     /**
202      * generate header for snapshots, with the following format
203      * (like an HTTP header but without the \r):
204      *
205      * Version: <version number of profiler>\n
206      * Process: <process name>\n
207      * Package: <package name, if exists>\n
208      * Package-Version: <version number of the package, if exists>\n
209      * Build: <fingerprint>\n
210      * \n
211      * <the actual snapshot content begins here...>
212      */
generateSnapshotHeader(String processName, PackageInfo packageInfo, PrintStream out)213     private static void generateSnapshotHeader(String processName, PackageInfo packageInfo,
214             PrintStream out) {
215         // profiler version
216         out.println("Version: 3");
217         out.println("Process: " + processName);
218         if (packageInfo != null) {
219             out.println("Package: " + packageInfo.packageName);
220             out.println("Package-Version: " + packageInfo.versionCode);
221         }
222         out.println("Build: " + Build.FINGERPRINT);
223         // single blank line means the end of snapshot header.
224         out.println();
225     }
226 }
227