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