diff options
| author | 2010-05-28 01:54:03 -0700 | |
|---|---|---|
| committer | 2010-06-14 13:27:01 -0700 | |
| commit | bde75706592c77379fb6546283e733abaca6fe04 (patch) | |
| tree | b41ddf2bdae6a8ba1223ccabcbd611f21474ee79 | |
| parent | e377032899fd7a9f88ad1313847e77c098b9f248 (diff) | |
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
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 24 | ||||
| -rw-r--r-- | core/java/android/provider/Settings.java | 16 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/SamplingProfilerIntegration.java | 140 | ||||
| -rw-r--r-- | services/java/com/android/server/SamplingProfilerService.java | 118 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 18 |
5 files changed, 247 insertions, 69 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 468b27132f4f..112d9da2b273 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 37f18decf14e..a66c9ed7740e 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; /** @@ -2416,6 +2408,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 * 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 5f5c7a47e219..38362c123d28 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 */ } + 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); - try { - out.write(snapshot); - } finally { - out.close(); - } - long elapsed = System.currentTimeMillis() - start; - Log.i(TAG, "Wrote snapshot for " + name - + " in " + elapsed + "ms."); - } catch (IOException e) { - Log.e(TAG, "Error writing snapshot.", e); + 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()); } } diff --git a/services/java/com/android/server/SamplingProfilerService.java b/services/java/com/android/server/SamplingProfilerService.java new file mode 100644 index 000000000000..26af7f780f21 --- /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 e7b8c02c3405..e511d1f976b9 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); } |