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