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