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