wire up sampling profiler to dropbox
When system property "persist.sys.profiler_hz" > 0, SamplingProfilerService is
loaded to SystemServer. It creates a FileObserver, watching any new file in the snapshot
directory. When a snapshot is found, it is put in dropbox and deleted after that.
SamplingProfilerIntegration writes snapshots with headers. Headers are <name, value> pairs,
instantiated by caller.
Currently header format is (also in source comment):
Version: <version number of profiler>\n
Process: <process name>\n
Package: <package name, if exists>\n
Package-Version: <version number of the package, if exists>\n
Build: <fingerprint>\n
\n
<the actual snapshot content begins here...>
BUG=2732642
Change-Id: I2c1699f1728e603de13dbd38f9d8443cd3eecc06
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 468b271..112d9da 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -31,6 +31,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.res.AssetManager;
@@ -2149,8 +2150,27 @@
void maybeSnapshot() {
if (mBoundApplication != null) {
- SamplingProfilerIntegration.writeSnapshot(
- mBoundApplication.processName);
+ // convert the *private* ActivityThread.PackageInfo to *public* known
+ // android.content.pm.PackageInfo
+ String packageName = mBoundApplication.info.mPackageName;
+ android.content.pm.PackageInfo packageInfo = null;
+ try {
+ Context context = getSystemContext();
+ if(context == null) {
+ Log.e(TAG, "cannot get a valid context");
+ return;
+ }
+ PackageManager pm = context.getPackageManager();
+ if(pm == null) {
+ Log.e(TAG, "cannot get a valid PackageManager");
+ return;
+ }
+ packageInfo = pm.getPackageInfo(
+ packageName, PackageManager.GET_ACTIVITIES);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "cannot get package info for " + packageName, e);
+ }
+ SamplingProfilerIntegration.writeSnapshot(mBoundApplication.processName, packageInfo);
}
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 37f18de..a66c9ed 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -16,14 +16,11 @@
package android.provider;
-import com.google.android.collect.Maps;
-import org.apache.commons.codec.binary.Base64;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
-import android.content.ContentQueryMap;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -38,19 +35,14 @@
import android.database.SQLException;
import android.net.Uri;
import android.os.*;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Config;
import android.util.Log;
import java.net.URISyntaxException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Map;
/**
@@ -2416,6 +2408,14 @@
public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
/**
+ * A positive value indicates the frequency of SamplingProfiler
+ * taking snapshots in hertz. Zero value means SamplingProfiler is disabled.
+ *
+ * @hide
+ */
+ public static final String SAMPLING_PROFILER_HZ = "sampling_profiler_hz";
+
+ /**
* Settings classname to launch when Settings is clicked from All
* Applications. Needed because of user testing between the old
* and new Settings apps.
diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
index 5f5c7a4..38362c1 100644
--- a/core/java/com/android/internal/os/SamplingProfilerIntegration.java
+++ b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
@@ -16,14 +16,15 @@
package com.android.internal.os;
+import android.content.pm.PackageInfo;
import dalvik.system.SamplingProfiler;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.FileNotFoundException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
import android.util.Log;
import android.os.*;
@@ -35,15 +36,27 @@
private static final String TAG = "SamplingProfilerIntegration";
+ public static final String SNAPSHOT_DIR = "/data/snapshots";
+
private static final boolean enabled;
private static final Executor snapshotWriter;
+ private static final int samplingProfilerHz;
+
+ /** Whether or not we've created the snapshots dir. */
+ private static boolean dirMade = false;
+
+ /** Whether or not a snapshot is being persisted. */
+ private static final AtomicBoolean pending = new AtomicBoolean(false);
+
static {
- enabled = "1".equals(SystemProperties.get("persist.sampling_profiler"));
- if (enabled) {
+ samplingProfilerHz = SystemProperties.getInt("persist.sys.profiler_hz", 0);
+ if (samplingProfilerHz > 0) {
snapshotWriter = Executors.newSingleThreadExecutor();
- Log.i(TAG, "Profiler is enabled.");
+ enabled = true;
+ Log.i(TAG, "Profiler is enabled. Sampling Profiler Hz: " + samplingProfilerHz);
} else {
snapshotWriter = null;
+ enabled = false;
Log.i(TAG, "Profiler is disabled.");
}
}
@@ -60,45 +73,45 @@
*/
public static void start() {
if (!enabled) return;
- SamplingProfiler.getInstance().start(10);
+ SamplingProfiler.getInstance().start(samplingProfilerHz);
}
- /** Whether or not we've created the snapshots dir. */
- static boolean dirMade = false;
-
- /** Whether or not a snapshot is being persisted. */
- static volatile boolean pending;
-
/**
- * Writes a snapshot to the SD card if profiling is enabled.
+ * Writes a snapshot if profiling is enabled.
*/
- public static void writeSnapshot(final String name) {
+ public static void writeSnapshot(final String processName, final PackageInfo packageInfo) {
if (!enabled) return;
/*
- * If we're already writing a snapshot, don't bother enqueing another
+ * If we're already writing a snapshot, don't bother enqueueing another
* request right now. This will reduce the number of individual
* snapshots and in turn the total amount of memory consumed (one big
* snapshot is smaller than N subset snapshots).
*/
- if (!pending) {
- pending = true;
+ if (pending.compareAndSet(false, true)) {
snapshotWriter.execute(new Runnable() {
public void run() {
- String dir = "/sdcard/snapshots";
if (!dirMade) {
- new File(dir).mkdirs();
- if (new File(dir).isDirectory()) {
+ File dir = new File(SNAPSHOT_DIR);
+ dir.mkdirs();
+ // the directory needs to be writable to anybody
+ dir.setWritable(true, false);
+ // the directory needs to be executable to anybody
+ // don't know why yet, but mode 723 would work, while
+ // mode 722 throws FileNotFoundExecption at line 151
+ dir.setExecutable(true, false);
+ if (new File(SNAPSHOT_DIR).isDirectory()) {
dirMade = true;
} else {
- Log.w(TAG, "Creation of " + dir + " failed.");
+ Log.w(TAG, "Creation of " + SNAPSHOT_DIR + " failed.");
+ pending.set(false);
return;
}
}
try {
- writeSnapshot(dir, name);
+ writeSnapshot(SNAPSHOT_DIR, processName, packageInfo);
} finally {
- pending = false;
+ pending.set(false);
}
}
});
@@ -110,13 +123,13 @@
*/
public static void writeZygoteSnapshot() {
if (!enabled) return;
-
- String dir = "/data/zygote/snapshots";
- new File(dir).mkdirs();
- writeSnapshot(dir, "zygote");
+ writeSnapshot("zygote", null);
}
- private static void writeSnapshot(String dir, String name) {
+ /**
+ * pass in PackageInfo to retrieve various values for snapshot header
+ */
+ private static void writeSnapshot(String dir, String processName, PackageInfo packageInfo) {
byte[] snapshot = SamplingProfiler.getInstance().snapshot();
if (snapshot == null) {
return;
@@ -128,39 +141,54 @@
* we capture two snapshots in rapid succession.
*/
long start = System.currentTimeMillis();
- String path = dir + "/" + name.replace(':', '.') + "-" +
- + System.currentTimeMillis() + ".snapshot";
+ String name = processName.replaceAll(":", ".");
+ String path = dir + "/" + name + "-" +System.currentTimeMillis() + ".snapshot";
+ FileOutputStream out = null;
try {
- // Try to open the file a few times. The SD card may not be mounted.
- FileOutputStream out;
- int count = 0;
- while (true) {
- try {
- out = new FileOutputStream(path);
- break;
- } catch (FileNotFoundException e) {
- if (++count > 3) {
- Log.e(TAG, "Could not open " + path + ".");
- return;
- }
-
- // Sleep for a bit and then try again.
- try {
- Thread.sleep(2500);
- } catch (InterruptedException e1) { /* ignore */ }
- }
- }
-
- try {
- out.write(snapshot);
- } finally {
- out.close();
- }
- long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "Wrote snapshot for " + name
- + " in " + elapsed + "ms.");
+ out = new FileOutputStream(path);
+ generateSnapshotHeader(name, packageInfo, out);
+ out.write(snapshot);
} catch (IOException e) {
Log.e(TAG, "Error writing snapshot.", e);
+ } finally {
+ try {
+ if(out != null) {
+ out.close();
+ }
+ } catch (IOException ex) {
+ // let it go.
+ }
}
+ // set file readable to the world so that SamplingProfilerService
+ // can put it to dropbox
+ new File(path).setReadable(true, false);
+
+ long elapsed = System.currentTimeMillis() - start;
+ Log.i(TAG, "Wrote snapshot for " + name + " in " + elapsed + "ms.");
+ }
+
+ /**
+ * generate header for snapshots, with the following format (like http header):
+ *
+ * Version: <version number of profiler>\n
+ * Process: <process name>\n
+ * Package: <package name, if exists>\n
+ * Package-Version: <version number of the package, if exists>\n
+ * Build: <fingerprint>\n
+ * \n
+ * <the actual snapshot content begins here...>
+ */
+ private static void generateSnapshotHeader(String processName, PackageInfo packageInfo,
+ FileOutputStream out) throws IOException {
+ // profiler version
+ out.write("Version: 1\n".getBytes());
+ out.write(("Process: " + processName + "\n").getBytes());
+ if(packageInfo != null) {
+ out.write(("Package: " + packageInfo.packageName + "\n").getBytes());
+ out.write(("Package-Version: " + packageInfo.versionCode + "\n").getBytes());
+ }
+ out.write(("Build: " + Build.FINGERPRINT + "\n").getBytes());
+ // single blank line means the end of snapshot header.
+ out.write("\n".getBytes());
}
}