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