Home | History | Annotate | Download | only in os
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.os;
     18 
     19 import android.content.pm.PackageInfo;
     20 import android.os.Build;
     21 import android.os.SystemProperties;
     22 import android.util.Log;
     23 import dalvik.system.profiler.BinaryHprofWriter;
     24 import dalvik.system.profiler.SamplingProfiler;
     25 import java.io.BufferedOutputStream;
     26 import java.io.File;
     27 import java.io.FileOutputStream;
     28 import java.io.IOException;
     29 import java.io.OutputStream;
     30 import java.io.PrintStream;
     31 import java.util.Date;
     32 import java.util.concurrent.Executor;
     33 import java.util.concurrent.Executors;
     34 import java.util.concurrent.ThreadFactory;
     35 import java.util.concurrent.atomic.AtomicBoolean;
     36 import libcore.io.IoUtils;
     37 
     38 /**
     39  * Integrates the framework with Dalvik's sampling profiler.
     40  */
     41 public class SamplingProfilerIntegration {
     42 
     43     private static final String TAG = "SamplingProfilerIntegration";
     44 
     45     public static final String SNAPSHOT_DIR = "/data/snapshots";
     46 
     47     private static final boolean enabled;
     48     private static final Executor snapshotWriter;
     49     private static final int samplingProfilerMilliseconds;
     50     private static final int samplingProfilerDepth;
     51 
     52     /** Whether or not a snapshot is being persisted. */
     53     private static final AtomicBoolean pending = new AtomicBoolean(false);
     54 
     55     static {
     56         samplingProfilerMilliseconds = SystemProperties.getInt("persist.sys.profiler_ms", 0);
     57         samplingProfilerDepth = SystemProperties.getInt("persist.sys.profiler_depth", 4);
     58         if (samplingProfilerMilliseconds > 0) {
     59             File dir = new File(SNAPSHOT_DIR);
     60             dir.mkdirs();
     61             // the directory needs to be writable to anybody to allow file writing
     62             dir.setWritable(true, false);
     63             // the directory needs to be executable to anybody to allow file creation
     64             dir.setExecutable(true, false);
     65             if (dir.isDirectory()) {
     66                 snapshotWriter = Executors.newSingleThreadExecutor(new ThreadFactory() {
     67                         public Thread newThread(Runnable r) {
     68                             return new Thread(r, TAG);
     69                         }
     70                     });
     71                 enabled = true;
     72                 Log.i(TAG, "Profiling enabled. Sampling interval ms: "
     73                       + samplingProfilerMilliseconds);
     74             } else {
     75                 snapshotWriter = null;
     76                 enabled = true;
     77                 Log.w(TAG, "Profiling setup failed. Could not create " + SNAPSHOT_DIR);
     78             }
     79         } else {
     80             snapshotWriter = null;
     81             enabled = false;
     82             Log.i(TAG, "Profiling disabled.");
     83         }
     84     }
     85 
     86     private static SamplingProfiler samplingProfiler;
     87     private static long startMillis;
     88 
     89     /**
     90      * Is profiling enabled?
     91      */
     92     public static boolean isEnabled() {
     93         return enabled;
     94     }
     95 
     96     /**
     97      * Starts the profiler if profiling is enabled.
     98      */
     99     public static void start() {
    100         if (!enabled) {
    101             return;
    102         }
    103         if (samplingProfiler != null) {
    104             Log.e(TAG, "SamplingProfilerIntegration already started at " + new Date(startMillis));
    105             return;
    106         }
    107 
    108         ThreadGroup group = Thread.currentThread().getThreadGroup();
    109         SamplingProfiler.ThreadSet threadSet = SamplingProfiler.newThreadGroupThreadSet(group);
    110         samplingProfiler = new SamplingProfiler(samplingProfilerDepth, threadSet);
    111         samplingProfiler.start(samplingProfilerMilliseconds);
    112         startMillis = System.currentTimeMillis();
    113     }
    114 
    115     /**
    116      * Writes a snapshot if profiling is enabled.
    117      */
    118     public static void writeSnapshot(final String processName, final PackageInfo packageInfo) {
    119         if (!enabled) {
    120             return;
    121         }
    122         if (samplingProfiler == null) {
    123             Log.e(TAG, "SamplingProfilerIntegration is not started");
    124             return;
    125         }
    126 
    127         /*
    128          * If we're already writing a snapshot, don't bother enqueueing another
    129          * request right now. This will reduce the number of individual
    130          * snapshots and in turn the total amount of memory consumed (one big
    131          * snapshot is smaller than N subset snapshots).
    132          */
    133         if (pending.compareAndSet(false, true)) {
    134             snapshotWriter.execute(new Runnable() {
    135                 public void run() {
    136                     try {
    137                         writeSnapshotFile(processName, packageInfo);
    138                     } finally {
    139                         pending.set(false);
    140                     }
    141                 }
    142             });
    143         }
    144     }
    145 
    146     /**
    147      * Writes the zygote's snapshot to internal storage if profiling is enabled.
    148      */
    149     public static void writeZygoteSnapshot() {
    150         if (!enabled) {
    151             return;
    152         }
    153         writeSnapshotFile("zygote", null);
    154         samplingProfiler.shutdown();
    155         samplingProfiler = null;
    156         startMillis = 0;
    157     }
    158 
    159     /**
    160      * pass in PackageInfo to retrieve various values for snapshot header
    161      */
    162     private static void writeSnapshotFile(String processName, PackageInfo packageInfo) {
    163         if (!enabled) {
    164             return;
    165         }
    166         samplingProfiler.stop();
    167 
    168         /*
    169          * We use the global start time combined with the process name
    170          * as a unique ID. We can't use a counter because processes
    171          * restart. This could result in some overlap if we capture
    172          * two snapshots in rapid succession.
    173          */
    174         String name = processName.replaceAll(":", ".");
    175         String path = SNAPSHOT_DIR + "/" + name + "-" + startMillis + ".snapshot";
    176         long start = System.currentTimeMillis();
    177         OutputStream outputStream = null;
    178         try {
    179             outputStream = new BufferedOutputStream(new FileOutputStream(path));
    180             PrintStream out = new PrintStream(outputStream);
    181             generateSnapshotHeader(name, packageInfo, out);
    182             if (out.checkError()) {
    183                 throw new IOException();
    184             }
    185             BinaryHprofWriter.write(samplingProfiler.getHprofData(), outputStream);
    186         } catch (IOException e) {
    187             Log.e(TAG, "Error writing snapshot to " + path, e);
    188             return;
    189         } finally {
    190             IoUtils.closeQuietly(outputStream);
    191         }
    192         // set file readable to the world so that SamplingProfilerService
    193         // can put it to dropbox
    194         new File(path).setReadable(true, false);
    195 
    196         long elapsed = System.currentTimeMillis() - start;
    197         Log.i(TAG, "Wrote snapshot " + path + " in " + elapsed + "ms.");
    198         samplingProfiler.start(samplingProfilerMilliseconds);
    199     }
    200 
    201     /**
    202      * generate header for snapshots, with the following format
    203      * (like an HTTP header but without the \r):
    204      *
    205      * Version: <version number of profiler>\n
    206      * Process: <process name>\n
    207      * Package: <package name, if exists>\n
    208      * Package-Version: <version number of the package, if exists>\n
    209      * Build: <fingerprint>\n
    210      * \n
    211      * <the actual snapshot content begins here...>
    212      */
    213     private static void generateSnapshotHeader(String processName, PackageInfo packageInfo,
    214             PrintStream out) {
    215         // profiler version
    216         out.println("Version: 3");
    217         out.println("Process: " + processName);
    218         if (packageInfo != null) {
    219             out.println("Package: " + packageInfo.packageName);
    220             out.println("Package-Version: " + packageInfo.versionCode);
    221         }
    222         out.println("Build: " + Build.FINGERPRINT);
    223         // single blank line means the end of snapshot header.
    224         out.println();
    225     }
    226 }
    227