From bde75706592c77379fb6546283e733abaca6fe04 Mon Sep 17 00:00:00 2001 From: Sen Hu Date: Fri, 28 May 2010 01:54:03 -0700 Subject: [PATCH] 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 pairs, instantiated by caller. Currently header format is (also in source comment): Version: \n Process: \n Package: \n Package-Version: \n Build: \n \n BUG=2732642 Change-Id: I2c1699f1728e603de13dbd38f9d8443cd3eecc06 --- core/java/android/app/ActivityThread.java | 24 ++- core/java/android/provider/Settings.java | 16 +- .../os/SamplingProfilerIntegration.java | 142 +++++++++++------- .../server/SamplingProfilerService.java | 118 +++++++++++++++ .../java/com/android/server/SystemServer.java | 18 ++- 5 files changed, 248 insertions(+), 70 deletions(-) create mode 100644 services/java/com/android/server/SamplingProfilerService.java diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 468b27132f4f7..112d9da2b2733 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -31,6 +31,7 @@ import android.content.pm.ApplicationInfo; 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 @@ public final class ActivityThread { 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 37f18decf14e7..a66c9ed7740eb 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.Cursor; 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; /** @@ -2415,6 +2407,14 @@ public final class Settings { */ 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 diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java index 5f5c7a47e2199..38362c123d282 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 @@ public class SamplingProfilerIntegration { 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 class SamplingProfilerIntegration { */ 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 class SamplingProfilerIntegration { */ 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 @@ public class SamplingProfilerIntegration { * 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: \n + * Process: \n + * Package: \n + * Package-Version: \n + * Build: \n + * \n + * + */ + 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()); } } diff --git a/services/java/com/android/server/SamplingProfilerService.java b/services/java/com/android/server/SamplingProfilerService.java new file mode 100644 index 0000000000000..26af7f780f217 --- /dev/null +++ b/services/java/com/android/server/SamplingProfilerService.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.ContentResolver; +import android.os.DropBoxManager; +import android.os.FileObserver; +import android.os.Binder; + +import android.util.Slog; +import android.content.Context; +import android.database.ContentObserver; +import android.os.SystemProperties; +import android.provider.Settings; +import com.android.internal.os.SamplingProfilerIntegration; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; + +public class SamplingProfilerService extends Binder { + + private static final String TAG = "SamplingProfilerService"; + private static final boolean LOCAL_LOGV = false; + public static final String SNAPSHOT_DIR = SamplingProfilerIntegration.SNAPSHOT_DIR; + + private FileObserver snapshotObserver; + + public SamplingProfilerService(Context context) { + registerSettingObserver(context); + startWorking(context); + } + + private void startWorking(Context context) { + if (LOCAL_LOGV) Slog.v(TAG, "starting SamplingProfilerService!"); + + final DropBoxManager dropbox = + (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE); + + // before FileObserver is ready, there could have already been some snapshots + // in the directory, we don't want to miss them + File[] snapshotFiles = new File(SNAPSHOT_DIR).listFiles(); + for (int i = 0; snapshotFiles != null && i < snapshotFiles.length; i++) { + handleSnapshotFile(snapshotFiles[i], dropbox); + } + + // detect new snapshot and put it in dropbox + // delete it afterwards no matter what happened before + // Note: needs listening at event ATTRIB rather than CLOSE_WRITE, because we set the + // readability of snapshot files after writing them! + snapshotObserver = new FileObserver(SNAPSHOT_DIR, FileObserver.ATTRIB) { + @Override + public void onEvent(int event, String path) { + handleSnapshotFile(new File(SNAPSHOT_DIR, path), dropbox); + } + }; + snapshotObserver.startWatching(); + + if (LOCAL_LOGV) Slog.v(TAG, "SamplingProfilerService activated"); + } + + private void handleSnapshotFile(File file, DropBoxManager dropbox) { + try { + dropbox.addFile(TAG, file, 0); + if (LOCAL_LOGV) Slog.v(TAG, file.getPath() + " added to dropbox"); + } catch (IOException e) { + Slog.e(TAG, "Can't add " + file.getPath() + " to dropbox", e); + } finally { + file.delete(); + } + } + + private void registerSettingObserver(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + contentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SAMPLING_PROFILER_HZ), + false, new SamplingProfilerSettingsObserver(contentResolver)); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("SamplingProfilerService:"); + pw.println("Watching directory: " + SNAPSHOT_DIR); + } + + private class SamplingProfilerSettingsObserver extends ContentObserver { + private ContentResolver mContentResolver; + public SamplingProfilerSettingsObserver(ContentResolver contentResolver) { + super(null); + mContentResolver = contentResolver; + onChange(false); + } + @Override + public void onChange(boolean selfChange) { + Integer samplingProfilerHz = Settings.Secure.getInt( + mContentResolver, Settings.Secure.SAMPLING_PROFILER_HZ, 0); + // setting this secure property will start or stop sampling profiler, + // as well as adjust the frequency of taking snapshots. + SystemProperties.set("persist.sys.profiler_hz", samplingProfilerHz.toString()); + } + } +} + diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e7b8c02c3405c..e511d1f976b9a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -77,7 +77,7 @@ class ServerThread extends Thread { android.os.Process.THREAD_PRIORITY_FOREGROUND); BinderInternal.disableBackgroundScheduling(true); - + String factoryTestStr = SystemProperties.get("ro.factorytest"); int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF : Integer.parseInt(factoryTestStr); @@ -401,6 +401,18 @@ class ServerThread extends Thread { } catch (Throwable e) { Slog.e(TAG, "Failure starting DiskStats Service", e); } + + try { + // need to add this service even if SamplingProfilerIntegration.isEnabled() + // is false, because it is this service that detects system property change and + // turns on SamplingProfilerIntegration. Plus, when sampling profiler doesn't work, + // there is little overhead for running this service. + Slog.i(TAG, "SamplingProfiler Service"); + ServiceManager.addService("samplingprofiler", + new SamplingProfilerService(context)); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting SamplingProfiler Service", e); + } } // make sure the ADB_ENABLED setting value matches the secure property value @@ -519,7 +531,7 @@ public class SystemServer { timer.schedule(new TimerTask() { @Override public void run() { - SamplingProfilerIntegration.writeSnapshot("system_server"); + SamplingProfilerIntegration.writeSnapshot("system_server", null); } }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL); } @@ -527,7 +539,7 @@ public class SystemServer { // The system server has to run all of the time, so it needs to be // as efficient as possible with its memory usage. VMRuntime.getRuntime().setTargetHeapUtilization(0.8f); - + System.loadLibrary("android_servers"); init1(args); }