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