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