Integrated the profiler into the framework. We run it all the time if the persist.sampling_profiler
system property is set. Saves snapshots to the SD card.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8a26aba..2877aec 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -68,6 +68,7 @@
 
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.SamplingProfilerIntegration;
 import com.android.internal.util.ArrayUtils;
 
 import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
@@ -87,6 +88,8 @@
 import java.util.TimeZone;
 import java.util.regex.Pattern;
 
+import dalvik.system.SamplingProfiler;
+
 final class IntentReceiverLeaked extends AndroidRuntimeException {
     public IntentReceiverLeaked(String msg) {
         super(msg);
@@ -126,7 +129,7 @@
     private static final int LOG_ON_PAUSE_CALLED = 30021;
     private static final int LOG_ON_RESUME_CALLED = 30022;
 
-    
+
     public static final ActivityThread currentActivityThread() {
         return (ActivityThread)sThreadLocal.get();
     }
@@ -314,7 +317,7 @@
         public ApplicationInfo getApplicationInfo() {
             return mApplicationInfo;
         }
-        
+
         public boolean isSecurityViolation() {
             return mSecurityViolation;
         }
@@ -322,7 +325,7 @@
         /**
          * Gets the array of shared libraries that are listed as
          * used by the given package.
-         * 
+         *
          * @param packageName the name of the package (note: not its
          * file name)
          * @return null-ok; the array of shared libraries, each one
@@ -350,7 +353,7 @@
          * result is a single string with the names of the libraries
          * separated by colons, or <code>null</code> if both lists
          * were <code>null</code> or empty.
-         * 
+         *
          * @param list1 null-ok; the first list
          * @param list2 null-ok; the second list
          * @return null-ok; the combination
@@ -378,7 +381,7 @@
                     if (dupCheck && ArrayUtils.contains(list1, s)) {
                         continue;
                     }
-                    
+
                     if (first) {
                         first = false;
                     } else {
@@ -390,7 +393,7 @@
 
             return result.toString();
         }
-                
+
         public ClassLoader getClassLoader() {
             synchronized (this) {
                 if (mClassLoader != null) {
@@ -428,7 +431,7 @@
 
                     if ((mSharedLibraries != null) ||
                             (instrumentationLibs != null)) {
-                        zip = 
+                        zip =
                             combineLibs(mSharedLibraries, instrumentationLibs)
                             + ':' + zip;
                     }
@@ -485,9 +488,9 @@
             if (mApplication != null) {
                 return mApplication;
             }
-            
+
             Application app = null;
-            
+
             String appClass = mApplicationInfo.className;
             if (forceDefaultAppClass || (appClass == null)) {
                 appClass = "android.app.Application";
@@ -510,7 +513,7 @@
             mActivityThread.mAllApplications.add(app);
             return mApplication = app;
         }
-        
+
         public void removeContextRegistrations(Context context,
                 String who, String what) {
             HashMap<BroadcastReceiver, ReceiverDispatcher> rmap =
@@ -643,7 +646,7 @@
             final static class InnerReceiver extends IIntentReceiver.Stub {
                 final WeakReference<ReceiverDispatcher> mDispatcher;
                 final ReceiverDispatcher mStrongRef;
-                
+
                 InnerReceiver(ReceiverDispatcher rd, boolean strong) {
                     mDispatcher = new WeakReference<ReceiverDispatcher>(rd);
                     mStrongRef = strong ? rd : null;
@@ -661,7 +664,7 @@
                     }
                 }
             }
-            
+
             final IIntentReceiver.Stub mIIntentReceiver;
             final BroadcastReceiver mReceiver;
             final Context mContext;
@@ -770,7 +773,7 @@
             BroadcastReceiver getIntentReceiver() {
                 return mReceiver;
             }
-            
+
             IIntentReceiver getIIntentReceiver() {
                 return mIIntentReceiver;
             }
@@ -901,7 +904,7 @@
 
             private static class InnerConnection extends IServiceConnection.Stub {
                 final WeakReference<ServiceDispatcher> mDispatcher;
-                
+
                 InnerConnection(ServiceDispatcher sd) {
                     mDispatcher = new WeakReference<ServiceDispatcher>(sd);
                 }
@@ -913,7 +916,7 @@
                     }
                 }
             }
-            
+
             private final HashMap<ComponentName, ConnectionInfo> mActiveConnections
                 = new HashMap<ComponentName, ConnectionInfo>();
 
@@ -965,7 +968,7 @@
             IServiceConnection getIServiceConnection() {
                 return mIServiceConnection;
             }
-            
+
             int getFlags() {
                 return mFlags;
             }
@@ -1191,7 +1194,7 @@
                     + " mode=" + backupMode + "}";
         }
     }
-    
+
     private static final class CreateServiceData {
         IBinder token;
         ServiceInfo info;
@@ -1271,10 +1274,10 @@
         private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
         private static final String ONE_COUNT_COLUMN = "%17s %8d";
         private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
-        
+
         // Formatting for checkin service - update version if row format changes
         private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
-       
+
         public final void schedulePauseActivity(IBinder token, boolean finished,
                 boolean userLeaving, int configChanges) {
             queueOrSendMessage(
@@ -1343,7 +1346,7 @@
             synchronized (mRelaunchingActivities) {
                 mRelaunchingActivities.add(r);
             }
-            
+
             queueOrSendMessage(H.RELAUNCH_ACTIVITY, r, configChanges);
         }
 
@@ -1514,7 +1517,7 @@
                 throws RemoteException {
             receiver.performReceive(intent, resultCode, dataStr, extras, ordered);
         }
-        
+
         public void scheduleLowMemory() {
             queueOrSendMessage(H.LOW_MEMORY, null);
         }
@@ -1530,7 +1533,7 @@
             } catch (RemoteException e) {
             }
         }
-        
+
         public void profilerControl(boolean start, String path, ParcelFileDescriptor fd) {
             ProfilerControlData pcd = new ProfilerControlData();
             pcd.path = path;
@@ -1549,11 +1552,11 @@
                 Log.w(TAG, "Failed setting process group to " + group, e);
             }
         }
-        
+
         public void getMemoryInfo(Debug.MemoryInfo outInfo) {
             Debug.getMemoryInfo(outInfo);
         }
-        
+
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             long nativeMax = Debug.getNativeHeapSize() / 1024;
@@ -1589,7 +1592,7 @@
             long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
             SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats();
             SQLiteDebug.getPagerStats(stats);
-            
+
             // Check to see if we were called by checkin server. If so, print terse format.
             boolean doCheckinFormat = false;
             if (args != null) {
@@ -1597,79 +1600,79 @@
                     if ("-c".equals(arg)) doCheckinFormat = true;
                 }
             }
-            
+
             // For checkin, we print one long comma-separated list of values
             if (doCheckinFormat) {
                 // NOTE: if you change anything significant below, also consider changing
                 // ACTIVITY_THREAD_CHECKIN_VERSION.
-                String processName = (mBoundApplication != null) 
+                String processName = (mBoundApplication != null)
                         ? mBoundApplication.processName : "unknown";
-                
+
                 // Header
                 pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(',');
                 pw.print(Process.myPid()); pw.print(',');
                 pw.print(processName); pw.print(',');
-                
+
                 // Heap info - max
                 pw.print(nativeMax); pw.print(',');
                 pw.print(dalvikMax); pw.print(',');
                 pw.print("N/A,");
                 pw.print(nativeMax + dalvikMax); pw.print(',');
-                
+
                 // Heap info - allocated
                 pw.print(nativeAllocated); pw.print(',');
                 pw.print(dalvikAllocated); pw.print(',');
                 pw.print("N/A,");
                 pw.print(nativeAllocated + dalvikAllocated); pw.print(',');
-                
+
                 // Heap info - free
                 pw.print(nativeFree); pw.print(',');
                 pw.print(dalvikFree); pw.print(',');
                 pw.print("N/A,");
                 pw.print(nativeFree + dalvikFree); pw.print(',');
-                
+
                 // Heap info - proportional set size
                 pw.print(memInfo.nativePss); pw.print(',');
                 pw.print(memInfo.dalvikPss); pw.print(',');
                 pw.print(memInfo.otherPss); pw.print(',');
                 pw.print(memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); pw.print(',');
-                
+
                 // Heap info - shared
-                pw.print(nativeShared); pw.print(','); 
-                pw.print(dalvikShared); pw.print(','); 
-                pw.print(otherShared); pw.print(','); 
+                pw.print(nativeShared); pw.print(',');
+                pw.print(dalvikShared); pw.print(',');
+                pw.print(otherShared); pw.print(',');
                 pw.print(nativeShared + dalvikShared + otherShared); pw.print(',');
-                
+
                 // Heap info - private
-                pw.print(nativePrivate); pw.print(','); 
+                pw.print(nativePrivate); pw.print(',');
                 pw.print(dalvikPrivate); pw.print(',');
                 pw.print(otherPrivate); pw.print(',');
                 pw.print(nativePrivate + dalvikPrivate + otherPrivate); pw.print(',');
-                
+
                 // Object counts
                 pw.print(viewInstanceCount); pw.print(',');
                 pw.print(viewRootInstanceCount); pw.print(',');
                 pw.print(appContextInstanceCount); pw.print(',');
                 pw.print(activityInstanceCount); pw.print(',');
-                
+
                 pw.print(globalAssetCount); pw.print(',');
                 pw.print(globalAssetManagerCount); pw.print(',');
                 pw.print(binderLocalObjectCount); pw.print(',');
                 pw.print(binderProxyObjectCount); pw.print(',');
-                
+
                 pw.print(binderDeathObjectCount); pw.print(',');
                 pw.print(openSslSocketCount); pw.print(',');
-                
+
                 // SQL
                 pw.print(sqliteAllocated); pw.print(',');
-                pw.print(stats.databaseBytes / 1024); pw.print(','); 
+                pw.print(stats.databaseBytes / 1024); pw.print(',');
                 pw.print(stats.numPagers); pw.print(',');
                 pw.print((stats.totalBytes - stats.referencedBytes) / 1024); pw.print(',');
                 pw.print(stats.referencedBytes / 1024); pw.print('\n');
-                
+
                 return;
             }
-            
+
             // otherwise, show human-readable format
             printRow(pw, HEAP_COLUMN, "", "native", "dalvik", "other", "total");
             printRow(pw, HEAP_COLUMN, "size:", nativeMax, dalvikMax, "N/A", nativeMax + dalvikMax);
@@ -1702,7 +1705,7 @@
             printRow(pw, ONE_COUNT_COLUMN, "Death Recipients:", binderDeathObjectCount);
 
             printRow(pw, ONE_COUNT_COLUMN, "OpenSSL Sockets:", openSslSocketCount);
-            
+
             // SQLite mem info
             pw.println(" ");
             pw.println(" SQL");
@@ -1711,7 +1714,7 @@
             printRow(pw, TWO_COUNT_COLUMNS, "numPagers:", stats.numPagers, "inactivePageKB:",
                     (stats.totalBytes - stats.referencedBytes) / 1024);
             printRow(pw, ONE_COUNT_COLUMN, "activePageKB:", stats.referencedBytes / 1024);
-            
+
             // Asset details.
             String assetAlloc = AssetManager.getAssetAllocations();
             if (assetAlloc != null) {
@@ -1727,6 +1730,10 @@
     }
 
     private final class H extends Handler {
+        private H() {
+            SamplingProfiler.getInstance().setEventThread(mLooper.getThread());
+        }
+
         public static final int LAUNCH_ACTIVITY         = 100;
         public static final int PAUSE_ACTIVITY          = 101;
         public static final int PAUSE_ACTIVITY_FINISHING= 102;
@@ -1811,6 +1818,7 @@
                 } break;
                 case PAUSE_ACTIVITY:
                     handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2);
+                    maybeSnapshot();
                     break;
                 case PAUSE_ACTIVITY_FINISHING:
                     handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2);
@@ -1853,6 +1861,7 @@
                     break;
                 case RECEIVER:
                     handleReceiver((ReceiverData)msg.obj);
+                    maybeSnapshot();
                     break;
                 case CREATE_SERVICE:
                     handleCreateService((CreateServiceData)msg.obj);
@@ -1868,6 +1877,7 @@
                     break;
                 case STOP_SERVICE:
                     handleStopService((IBinder)msg.obj);
+                    maybeSnapshot();
                     break;
                 case REQUEST_THUMBNAIL:
                     handleRequestThumbnail((IBinder)msg.obj);
@@ -1907,6 +1917,13 @@
                     break;
             }
         }
+
+        void maybeSnapshot() {
+            if (mBoundApplication != null) {
+                SamplingProfilerIntegration.writeSnapshot(
+                        mBoundApplication.processName);
+            }
+        }
     }
 
     private final class Idler implements MessageQueue.IdleHandler {
@@ -1947,13 +1964,13 @@
         final private String mResDir;
         final private float mScale;
         final private int mHash;
-        
+
         ResourcesKey(String resDir, float scale) {
             mResDir = resDir;
             mScale = scale;
             mHash = mResDir.hashCode() << 2 + (int) (mScale * 2);
         }
-        
+
         @Override
         public int hashCode() {
             return mHash;
@@ -2004,7 +2021,7 @@
     final ArrayList<ActivityRecord> mRelaunchingActivities
             = new ArrayList<ActivityRecord>();
     Configuration mPendingConfiguration = null;
-    
+
     // These can be accessed by multiple threads; mPackages is the lock.
     // XXX For now we keep around information about all packages we have
     // seen, not removing entries from this map.
@@ -2139,7 +2156,7 @@
             return false;
         }
     }
-    
+
     ActivityThread() {
     }
 
@@ -2172,11 +2189,11 @@
     public Application getApplication() {
         return mInitialApplication;
     }
-    
+
     public String getProcessName() {
         return mBoundApplication.processName;
     }
-    
+
     public ApplicationContext getSystemContext() {
         synchronized (this) {
             if (mSystemContext == null) {
@@ -2231,7 +2248,7 @@
         }
         return aInfo;
     }
-    
+
     public final Activity startActivityNow(Activity parent, String id,
         Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
         Object lastNonConfigurationInstance) {
@@ -2314,7 +2331,7 @@
             r.packageInfo = getPackageInfo(aInfo.applicationInfo,
                     Context.CONTEXT_INCLUDE_CODE);
         }
-            
+
         ComponentName component = r.intent.getComponent();
         if (component == null) {
             component = r.intent.resolveActivity(
@@ -2346,7 +2363,7 @@
 
         try {
             Application app = r.packageInfo.makeApplication(false);
-            
+
             if (localLOGV) Log.v(TAG, "Performing launch of " + r);
             if (localLOGV) Log.v(
                     TAG, r + ": app=" + app
@@ -2365,7 +2382,7 @@
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         r.embeddedID, r.lastNonConfigurationInstance,
                         r.lastNonConfigurationChildInstances, config);
-                
+
                 if (customIntent != null) {
                     activity.mIntent = customIntent;
                 }
@@ -2503,7 +2520,7 @@
             }
         }
     }
-    
+
     private final void handleNewIntent(NewIntentData data) {
         performNewIntents(data.token, data.intents);
     }
@@ -2541,7 +2558,7 @@
 
         try {
             Application app = packageInfo.makeApplication(false);
-            
+
             if (localLOGV) Log.v(
                 TAG, "Performing receive of " + data.intent
                 + ": app=" + app
@@ -2598,7 +2615,7 @@
                     + " already exists");
             return;
         }
-        
+
         BackupAgent agent = null;
         String classname = data.appInfo.backupAgentName;
         if (classname == null) {
@@ -2652,7 +2669,7 @@
     // Tear down a BackupAgent
     private final void handleDestroyBackupAgent(CreateBackupAgentData data) {
         if (DEBUG_BACKUP) Log.v(TAG, "handleDestroyBackupAgent: " + data);
-        
+
         PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
         String packageName = packageInfo.mPackageName;
         BackupAgent agent = mBackupAgents.get(packageName);
@@ -2857,9 +2874,9 @@
                 }
                 r.activity.performResume();
 
-                EventLog.writeEvent(LOG_ON_RESUME_CALLED, 
+                EventLog.writeEvent(LOG_ON_RESUME_CALLED,
                         r.activity.getComponentName().getClassName());
-                
+
                 r.paused = false;
                 r.stopped = false;
                 if (r.activity.mStartedActivity) {
@@ -2895,7 +2912,7 @@
 
             final int forwardBit = isForward ?
                     WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
-            
+
             // If the window hasn't yet been added to the window manager,
             // and this guy didn't finish itself or start another activity,
             // then go ahead and add the window.
@@ -3014,7 +3031,7 @@
             if (userLeaving) {
                 performUserLeavingActivity(r);
             }
-            
+
             r.activity.mConfigChangeFlags |= configChanges;
             Bundle state = performPauseActivity(token, finished, true);
 
@@ -3191,7 +3208,7 @@
             + " win=" + r.window);
 
         updateVisibility(r, show);
-        
+
         // Tell activity manager we have been stopped.
         try {
             ActivityManagerNative.getDefault().activityStopped(
@@ -3307,7 +3324,7 @@
                 try {
                     r.activity.mCalled = false;
                     mInstrumentation.callActivityOnPause(r.activity);
-                    EventLog.writeEvent(LOG_ON_PAUSE_CALLED, 
+                    EventLog.writeEvent(LOG_ON_PAUSE_CALLED,
                             r.activity.getComponentName().getClassName());
                     if (!r.activity.mCalled) {
                         throw new SuperNotCalledException(
@@ -3364,7 +3381,7 @@
                                 + ": " + e.toString(), e);
                     }
                 }
-                
+
             }
             try {
                 r.activity.mCalled = false;
@@ -3446,7 +3463,7 @@
         unscheduleGcIdler();
 
         Configuration changedConfig = null;
-        
+
         // First: make sure we have the most recent configuration and most
         // recent version of the activity, or skip it if some previous call
         // had taken a more recent version.
@@ -3463,38 +3480,38 @@
                     N--;
                 }
             }
-            
+
             if (tmp == null) {
                 return;
             }
-            
+
             if (mPendingConfiguration != null) {
                 changedConfig = mPendingConfiguration;
                 mPendingConfiguration = null;
             }
         }
-        
+
         // If there was a pending configuration change, execute it first.
         if (changedConfig != null) {
             handleConfigurationChanged(changedConfig);
         }
-        
+
         ActivityRecord r = mActivities.get(tmp.token);
         if (localLOGV) Log.v(TAG, "Handling relaunch of " + r);
         if (r == null) {
             return;
         }
-        
+
         r.activity.mConfigChangeFlags |= configChanges;
         Intent currentIntent = r.activity.mIntent;
-        
+
         Bundle savedState = null;
         if (!r.paused) {
             savedState = performPauseActivity(r.token, false, true);
         }
-        
+
         handleDestroyActivity(r.token, false, configChanges, true);
-        
+
         r.activity = null;
         r.window = null;
         r.hideForNow = false;
@@ -3518,7 +3535,7 @@
         if (savedState != null) {
             r.state = savedState;
         }
-        
+
         handleLaunchActivity(r, currentIntent);
     }
 
@@ -3548,7 +3565,7 @@
             boolean allActivities, Configuration newConfig) {
         ArrayList<ComponentCallbacks> callbacks
                 = new ArrayList<ComponentCallbacks>();
-        
+
         if (mActivities.size() > 0) {
             Iterator<ActivityRecord> it = mActivities.values().iterator();
             while (it.hasNext()) {
@@ -3589,10 +3606,10 @@
         for (int i=0; i<N; i++) {
             callbacks.add(mAllApplications.get(i));
         }
-        
+
         return callbacks;
     }
-    
+
     private final void performConfigurationChanged(
             ComponentCallbacks cb, Configuration config) {
         // Only for Activity objects, check that they actually call up to their
@@ -3602,18 +3619,18 @@
         if (activity != null) {
             activity.mCalled = false;
         }
-        
+
         boolean shouldChangeConfig = false;
         if ((activity == null) || (activity.mCurrentConfig == null)) {
             shouldChangeConfig = true;
         } else {
-            
+
             // If the new config is the same as the config this Activity
             // is already running with then don't bother calling
             // onConfigurationChanged
             int diff = activity.mCurrentConfig.diff(config);
             if (diff != 0) {
-                
+
                 // If this activity doesn't handle any of the config changes
                 // then don't bother calling onConfigurationChanged as we're
                 // going to destroy it.
@@ -3622,10 +3639,10 @@
                 }
             }
         }
-        
+
         if (shouldChangeConfig) {
             cb.onConfigurationChanged(config);
-            
+
             if (activity != null) {
                 if (!activity.mCalled) {
                     throw new SuperNotCalledException(
@@ -3639,17 +3656,17 @@
     }
 
     final void handleConfigurationChanged(Configuration config) {
-        
+
         synchronized (mRelaunchingActivities) {
             if (mPendingConfiguration != null) {
                 config = mPendingConfiguration;
                 mPendingConfiguration = null;
             }
         }
-        
+
         ArrayList<ComponentCallbacks> callbacks
                 = new ArrayList<ComponentCallbacks>();
-        
+
         synchronized(mPackages) {
             if (mConfiguration == null) {
                 mConfiguration = new Configuration();
@@ -3684,10 +3701,10 @@
                     }
                 }
             }
-            
+
             callbacks = collectComponentCallbacksLocked(false, config);
         }
-        
+
         final int N = callbacks.size();
         for (int i=0; i<N; i++) {
             performConfigurationChanged(callbacks.get(i), config);
@@ -3699,7 +3716,7 @@
         if (r == null || r.activity == null) {
             return;
         }
-        
+
         performConfigurationChanged(r.activity, mConfiguration);
     }
 
@@ -3722,7 +3739,7 @@
             Debug.stopMethodTracing();
         }
     }
-    
+
     final void handleLowMemory() {
         ArrayList<ComponentCallbacks> callbacks
                 = new ArrayList<ComponentCallbacks>();
@@ -3730,7 +3747,7 @@
         synchronized(mPackages) {
             callbacks = collectComponentCallbacksLocked(true, null);
         }
-        
+
         final int N = callbacks.size();
         for (int i=0; i<N; i++) {
             callbacks.get(i).onLowMemory();
@@ -3741,7 +3758,7 @@
             int sqliteReleased = SQLiteDatabase.releaseMemory();
             EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased);
         }
-        
+
         // Ask graphics to free up as much as possible (font/image caches)
         Canvas.freeCaches();
 
@@ -3788,7 +3805,7 @@
                 == 0) {
             Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
         }
-        
+
         if (data.debugMode != IApplicationThread.DEBUG_OFF) {
             // XXX should have option to change the port.
             Debug.changeDebugPort(8100);
@@ -4213,6 +4230,8 @@
     }
 
     public static final void main(String[] args) {
+        SamplingProfilerIntegration.start();
+
         Process.setArgV0("<pre-initialized>");
 
         Looper.prepareMainLooper();
@@ -4228,8 +4247,11 @@
 
         thread.detach();
         String name;
-        if (thread.mInitialApplication != null) name = thread.mInitialApplication.getPackageName();
-        else name = "<unknown>";
+        if (thread.mInitialApplication != null) {
+            name = thread.mInitialApplication.getPackageName();
+        } else {
+            name = "<unknown>";
+        }
         Log.i(TAG, "Main thread of " + name + " is now exiting");
     }
 }
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 8486272..4e6f9ca 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -83,7 +83,7 @@
         Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
 
         int hasQwerty = getQwertyKeyboard();
-        
+
         if (Config.LOGV) Log.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
         if (hasQwerty == 1) {
             System.setProperty("qwerty", "1");
@@ -133,13 +133,13 @@
      * @param className Fully-qualified class name
      * @param argv Argument vector for main()
      */
-    private static void invokeStaticMain(String className, String[] argv) 
+    private static void invokeStaticMain(String className, String[] argv)
             throws ZygoteInit.MethodAndArgsCaller {
-        
+
         // We want to be fairly aggressive about heap utilization, to avoid
         // holding on to a lot of memory that isn't needed.
         VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
-        
+
         Class<?> cl;
 
         try {
@@ -178,7 +178,7 @@
 
     public static final void main(String[] argv) {
         commonInit();
-        
+
         /*
          * Now that we're running in interpreted code, call back into native code
          * to run the system.
@@ -187,7 +187,7 @@
 
         if (Config.LOGV) Log.d(TAG, "Leaving RuntimeInit!");
     }
-    
+
     public static final native void finishInit();
 
     /**
@@ -236,7 +236,7 @@
         }
 
         // Remaining arguments are passed to the start class's static main
-        
+
         String startClass = argv[curArg++];
         String[] startArgs = new String[argv.length - curArg];
 
@@ -245,28 +245,28 @@
     }
 
     public static final native void zygoteInitNative();
-    
+
     /**
      * Returns 1 if the computer is on. If the computer isn't on, the value returned by this method is undefined.
      */
     public static final native int isComputerOn();
 
     /**
-     * Turns the computer on if the computer is off. If the computer is on, the behavior of this method is undefined. 
+     * Turns the computer on if the computer is off. If the computer is on, the behavior of this method is undefined.
      */
     public static final native void turnComputerOn();
 
     /**
-     * 
+     *
      * @return 1 if the device has a qwerty keyboard
      */
     public static native int getQwertyKeyboard();
-    
+
     /**
      * Report a fatal error in the current process.  If this is a user-process,
      * a dialog may be displayed informing the user of the error.  This
      * function does not return; it forces the current process to exit.
-     * 
+     *
      * @param tag to use when logging the error
      * @param t exception that was generated by the error
      */
@@ -405,7 +405,7 @@
     /**
      * Replay an encoded CrashData record back into a useable CrashData record.  This can be
      * helpful for providing debugging output after a process error.
-     * 
+     *
      * @param crashDataBytes The byte array containing the encoded crash record
      * @return new CrashData record, or null if could not create one.
      */
diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
new file mode 100644
index 0000000..44bcd16
--- /dev/null
+++ b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
@@ -0,0 +1,144 @@
+package com.android.internal.os;
+
+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 android.util.Log;
+import android.os.*;
+import android.net.Uri;
+
+/**
+ * Integrates the framework with Dalvik's sampling profiler.
+ */
+public class SamplingProfilerIntegration {
+
+    private static final String TAG = "SamplingProfilerIntegration";
+
+    private static final boolean enabled;
+    private static final Executor snapshotWriter;
+    static {
+        enabled = "1".equals(SystemProperties.get("persist.sampling_profiler"));
+        if (enabled) {
+            snapshotWriter = Executors.newSingleThreadExecutor();
+            Log.i(TAG, "Profiler is enabled.");
+        } else {
+            snapshotWriter = null;
+            Log.i(TAG, "Profiler is disabled.");
+        }
+    }
+
+    /**
+     * Is profiling enabled?
+     */
+    public static boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Starts the profiler if profiling is enabled.
+     */
+    public static void start() {
+        if (!enabled) return;
+        SamplingProfiler.getInstance().start(10);
+    }
+
+    /** 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.
+     */
+    public static void writeSnapshot(final String name) {
+        if (!enabled) return;
+
+        if (!pending) {
+            pending = true;
+            snapshotWriter.execute(new Runnable() {
+                public void run() {
+                    String dir = "/sdcard/snapshots";
+                    if (!dirMade) {
+                        makeDirectory(dir);
+                        dirMade = true;
+                    }
+                    try {
+                        writeSnapshot(dir, name);
+                    } finally {
+                        pending = false;
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Writes the zygote's snapshot to internal storage if profiling is enabled.
+     */
+    public static void writeZygoteSnapshot() {
+        if (!enabled) return;
+
+        String dir = "/data/zygote/snapshots";
+        makeDirectory(dir);
+        writeSnapshot(dir, "zygote");
+    }
+
+    private static void writeSnapshot(String dir, String name) {
+        byte[] snapshot = SamplingProfiler.getInstance().snapshot();
+        if (snapshot == null) {
+            return;
+        }
+
+        /*
+         * We use the current time as a unique ID. We can't use a counter
+         * because processes restart. This could result in some overlap if
+         * we capture two snapshots in rapid succession.
+         */
+        long start = System.currentTimeMillis();
+        String path = dir + "/" + name.replace(':', '.') + "-"
+                + System.currentTimeMillis() + ".snapshot";
+        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.");
+        } catch (IOException e) {
+            Log.e(TAG, "Error writing snapshot.", e);
+        }
+    }
+
+    private static void makeDirectory(String dir) {
+        new File(dir).mkdirs();
+    }
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a2d3cd8..404c513 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -19,7 +19,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
 import android.net.LocalServerSocket;
 import android.os.Debug;
@@ -31,6 +30,7 @@
 
 import dalvik.system.VMRuntime;
 import dalvik.system.Zygote;
+import dalvik.system.SamplingProfiler;
 
 import java.io.BufferedReader;
 import java.io.FileDescriptor;
@@ -73,7 +73,7 @@
      * never gets destroyed.
      */
     private static Resources mResources;
-    
+
     /**
      * The number of times that the main Zygote loop
      * should run before calling gc() again.
@@ -192,7 +192,7 @@
      * RuntimeException on failure.
      */
     private static ZygoteConnection acceptCommandPeer() {
-        try {            
+        try {
             return new ZygoteConnection(sServerSocket.accept());
         } catch (IOException ex) {
             throw new RuntimeException(
@@ -251,7 +251,7 @@
      */
     private static void preloadClasses() {
         final VMRuntime runtime = VMRuntime.getRuntime();
-        
+
         InputStream is = ZygoteInit.class.getClassLoader().getResourceAsStream(
                 PRELOADED_CLASSES);
         if (is == null) {
@@ -259,7 +259,7 @@
         } else {
             Log.i(TAG, "Preloading classes...");
             long startTime = SystemClock.uptimeMillis();
-            
+
             // Drop root perms while running static initializers.
             setEffectiveGroup(UNPRIVILEGED_GID);
             setEffectiveUser(UNPRIVILEGED_UID);
@@ -275,7 +275,7 @@
             Debug.startAllocCounting();
 
             try {
-                BufferedReader br 
+                BufferedReader br
                     = new BufferedReader(new InputStreamReader(is), 256);
 
                 int count = 0;
@@ -394,7 +394,7 @@
      */
     private static void preloadResources() {
         final VMRuntime runtime = VMRuntime.getRuntime();
-        
+
         Debug.startAllocCounting();
         try {
             runtime.gcSoftReferences();
@@ -527,7 +527,7 @@
     /**
      * Prepare the arguments and fork for the system server process.
      */
-    private static boolean startSystemServer() 
+    private static boolean startSystemServer()
             throws MethodAndArgsCaller, RuntimeException {
         /* Hardcoded command line to start the system server */
         String args[] = {
@@ -561,8 +561,8 @@
                     parsedArgs.gids, debugFlags, null);
         } catch (IllegalArgumentException ex) {
             throw new RuntimeException(ex);
-        } 
- 
+        }
+
         /* For child process */
         if (pid == 0) {
             handleSystemServerProcess(parsedArgs);
@@ -573,6 +573,9 @@
 
     public static void main(String argv[]) {
         try {
+            // Start profiling the zygote initialization.
+            SamplingProfilerIntegration.start();
+
             registerZygoteSocket();
             EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                 SystemClock.uptimeMillis());
@@ -582,6 +585,13 @@
             EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                 SystemClock.uptimeMillis());
 
+            if (SamplingProfilerIntegration.isEnabled()) {
+                SamplingProfiler sp = SamplingProfiler.getInstance();
+                sp.pause();
+                SamplingProfilerIntegration.writeZygoteSnapshot();
+                sp.shutDown();
+            }
+
             // Do an initial gc to clean up after startup
             gc();
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1158a71..7c2a7ac 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -18,6 +18,7 @@
 
 import com.android.server.am.ActivityManagerService;
 import com.android.server.status.StatusBarService;
+import com.android.internal.os.SamplingProfilerIntegration;
 
 import dalvik.system.VMRuntime;
 
@@ -41,6 +42,9 @@
 import android.util.Log;
 import android.accounts.AccountManagerService;
 
+import java.util.Timer;
+import java.util.TimerTask;
+
 class ServerThread extends Thread {
     private static final String TAG = "SystemServer";
     private final static boolean INCLUDE_DEMO = false;
@@ -452,6 +456,9 @@
     public static final int FACTORY_TEST_LOW_LEVEL = 1;
     public static final int FACTORY_TEST_HIGH_LEVEL = 2;
 
+    static Timer timer;
+    static final long SNAPSHOT_INTERVAL = 60 * 60 * 1000; // 1hr
+
     /**
      * This method is called from Zygote to initialize the system. This will cause the native
      * services (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call back
@@ -460,6 +467,17 @@
     native public static void init1(String[] args);
 
     public static void main(String[] args) {
+        if (SamplingProfilerIntegration.isEnabled()) {
+            SamplingProfilerIntegration.start();
+            timer = new Timer();
+            timer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    SamplingProfilerIntegration.writeSnapshot("system_server");
+                }
+            }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
+        }
+
         // 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);