Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bde75706 authored by Sen Hu's avatar Sen Hu
Browse files

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
parent e3770328
Loading
Loading
Loading
Loading
+22 −2
Original line number Diff line number Diff line
@@ -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);
            }
        }
    }
+8 −8
Original line number Diff line number Diff line
@@ -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
+84 −56
Original line number Diff line number Diff line
@@ -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";
        try {
            // Try to open the file a few times. The SD card may not be mounted.
            FileOutputStream out;
            int count = 0;
            while (true) {
        String name = processName.replaceAll(":", ".");
        String path = dir + "/" + name + "-" +System.currentTimeMillis() + ".snapshot";
        FileOutputStream out = null;
        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 {
            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.");
        } catch (IOException e) {
            Log.e(TAG, "Error writing snapshot.", e);
        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());
    }
}
+118 −0
Original line number Diff line number Diff line
/*
 * 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());
        }
    }
}
+15 −3
Original line number Diff line number Diff line
@@ -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);
        }