Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2012 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.gallery3d.util;
     18 
     19 import android.os.Handler;
     20 import android.os.HandlerThread;
     21 import android.os.Process;
     22 
     23 import java.util.ArrayList;
     24 import java.util.Random;
     25 
     26 // The Profile class is used to collect profiling information for a thread. It
     27 // samples stack traces for a thread periodically. enable() and disable() is
     28 // used to enable and disable profiling for the calling thread. The profiling
     29 // information can then be dumped to a file using the dumpToFile() method.
     30 //
     31 // The disableAll() method can be used to disable profiling for all threads and
     32 // can be called in onPause() to ensure all profiling is disabled when an
     33 // activity is paused.
     34 public class Profile {
     35     private static final String TAG = "Profile";
     36     private static final int NS_PER_MS = 1000000;
     37 
     38     // This is a watchdog entry for one thread.
     39     // For every cycleTime period, we dump the stack of the thread.
     40     private static class WatchEntry {
     41         Thread thread;
     42 
     43         // Both are in milliseconds
     44         int cycleTime;
     45         int wakeTime;
     46 
     47         boolean isHolding;
     48         ArrayList<String[]> holdingStacks = new ArrayList<String[]>();
     49     }
     50 
     51     // This is a watchdog thread which dumps stacks of other threads periodically.
     52     private static Watchdog sWatchdog = new Watchdog();
     53 
     54     private static class Watchdog {
     55         private ArrayList<WatchEntry> mList = new ArrayList<WatchEntry>();
     56         private HandlerThread mHandlerThread;
     57         private Handler mHandler;
     58         private Runnable mProcessRunnable = new Runnable() {
     59             public void run() {
     60                 synchronized (Watchdog.this) {
     61                     processList();
     62                 }
     63             }
     64         };
     65         private Random mRandom = new Random();
     66         private ProfileData mProfileData = new ProfileData();
     67 
     68         public Watchdog() {
     69             mHandlerThread = new HandlerThread("Watchdog Handler",
     70                     Process.THREAD_PRIORITY_FOREGROUND);
     71             mHandlerThread.start();
     72             mHandler = new Handler(mHandlerThread.getLooper());
     73         }
     74 
     75         public synchronized void addWatchEntry(Thread thread, int cycleTime) {
     76             WatchEntry e = new WatchEntry();
     77             e.thread = thread;
     78             e.cycleTime = cycleTime;
     79             int firstDelay = 1 + mRandom.nextInt(cycleTime);
     80             e.wakeTime = (int) (System.nanoTime() / NS_PER_MS) + firstDelay;
     81             mList.add(e);
     82             processList();
     83         }
     84 
     85         public synchronized void removeWatchEntry(Thread thread) {
     86             for (int i = 0; i < mList.size(); i++) {
     87                 if (mList.get(i).thread == thread) {
     88                     mList.remove(i);
     89                     break;
     90                 }
     91             }
     92             processList();
     93         }
     94 
     95         public synchronized void removeAllWatchEntries() {
     96             mList.clear();
     97             processList();
     98         }
     99 
    100         private void processList() {
    101             mHandler.removeCallbacks(mProcessRunnable);
    102             if (mList.size() == 0) return;
    103 
    104             int currentTime = (int) (System.nanoTime() / NS_PER_MS);
    105             int nextWakeTime = 0;
    106 
    107             for (WatchEntry entry : mList) {
    108                 if (currentTime > entry.wakeTime) {
    109                     entry.wakeTime += entry.cycleTime;
    110                     Thread thread = entry.thread;
    111                     sampleStack(entry);
    112                 }
    113 
    114                 if (entry.wakeTime > nextWakeTime) {
    115                     nextWakeTime = entry.wakeTime;
    116                 }
    117             }
    118 
    119             long delay = nextWakeTime - currentTime;
    120             mHandler.postDelayed(mProcessRunnable, delay);
    121         }
    122 
    123         private void sampleStack(WatchEntry entry) {
    124             Thread thread = entry.thread;
    125             StackTraceElement[] stack = thread.getStackTrace();
    126             String[] lines = new String[stack.length];
    127             for (int i = 0; i < stack.length; i++) {
    128                 lines[i] = stack[i].toString();
    129             }
    130             if (entry.isHolding) {
    131                 entry.holdingStacks.add(lines);
    132             } else {
    133                 mProfileData.addSample(lines);
    134             }
    135         }
    136 
    137         private WatchEntry findEntry(Thread thread) {
    138             for (int i = 0; i < mList.size(); i++) {
    139                 WatchEntry entry = mList.get(i);
    140                 if (entry.thread == thread) return entry;
    141             }
    142             return null;
    143         }
    144 
    145         public synchronized void dumpToFile(String filename) {
    146             mProfileData.dumpToFile(filename);
    147         }
    148 
    149         public synchronized void reset() {
    150             mProfileData.reset();
    151         }
    152 
    153         public synchronized void hold(Thread t) {
    154             WatchEntry entry = findEntry(t);
    155 
    156             // This can happen if the profiling is disabled (probably from
    157             // another thread). Same check is applied in commit() and drop()
    158             // below.
    159             if (entry == null) return;
    160 
    161             entry.isHolding = true;
    162         }
    163 
    164         public synchronized void commit(Thread t) {
    165             WatchEntry entry = findEntry(t);
    166             if (entry == null) return;
    167             ArrayList<String[]> stacks = entry.holdingStacks;
    168             for (int i = 0; i < stacks.size(); i++) {
    169                 mProfileData.addSample(stacks.get(i));
    170             }
    171             entry.isHolding = false;
    172             entry.holdingStacks.clear();
    173         }
    174 
    175         public synchronized void drop(Thread t) {
    176             WatchEntry entry = findEntry(t);
    177             if (entry == null) return;
    178             entry.isHolding = false;
    179             entry.holdingStacks.clear();
    180         }
    181     }
    182 
    183     // Enable profiling for the calling thread. Periodically (every cycleTimeInMs
    184     // milliseconds) sample the stack trace of the calling thread.
    185     public static void enable(int cycleTimeInMs) {
    186         Thread t = Thread.currentThread();
    187         sWatchdog.addWatchEntry(t, cycleTimeInMs);
    188     }
    189 
    190     // Disable profiling for the calling thread.
    191     public static void disable() {
    192         sWatchdog.removeWatchEntry(Thread.currentThread());
    193     }
    194 
    195     // Disable profiling for all threads.
    196     public static void disableAll() {
    197         sWatchdog.removeAllWatchEntries();
    198     }
    199 
    200     // Dump the profiling data to a file.
    201     public static void dumpToFile(String filename) {
    202         sWatchdog.dumpToFile(filename);
    203     }
    204 
    205     // Reset the collected profiling data.
    206     public static void reset() {
    207         sWatchdog.reset();
    208     }
    209 
    210     // Hold the future samples coming from current thread until commit() or
    211     // drop() is called, and those samples are recorded or ignored as a result.
    212     // This must called after enable() to be effective.
    213     public static void hold() {
    214         sWatchdog.hold(Thread.currentThread());
    215     }
    216 
    217     public static void commit() {
    218         sWatchdog.commit(Thread.currentThread());
    219     }
    220 
    221     public static void drop() {
    222         sWatchdog.drop(Thread.currentThread());
    223     }
    224 }
    225