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 Watchdog()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 addWatchEntry(Thread thread, int cycleTime)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 removeWatchEntry(Thread thread)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 removeAllWatchEntries()95 public synchronized void removeAllWatchEntries() { 96 mList.clear(); 97 processList(); 98 } 99 processList()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 sampleStack(WatchEntry entry)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 findEntry(Thread thread)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 dumpToFile(String filename)145 public synchronized void dumpToFile(String filename) { 146 mProfileData.dumpToFile(filename); 147 } 148 reset()149 public synchronized void reset() { 150 mProfileData.reset(); 151 } 152 hold(Thread t)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 commit(Thread t)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 drop(Thread t)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. enable(int cycleTimeInMs)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. disable()191 public static void disable() { 192 sWatchdog.removeWatchEntry(Thread.currentThread()); 193 } 194 195 // Disable profiling for all threads. disableAll()196 public static void disableAll() { 197 sWatchdog.removeAllWatchEntries(); 198 } 199 200 // Dump the profiling data to a file. dumpToFile(String filename)201 public static void dumpToFile(String filename) { 202 sWatchdog.dumpToFile(filename); 203 } 204 205 // Reset the collected profiling data. reset()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. hold()213 public static void hold() { 214 sWatchdog.hold(Thread.currentThread()); 215 } 216 commit()217 public static void commit() { 218 sWatchdog.commit(Thread.currentThread()); 219 } 220 drop()221 public static void drop() { 222 sWatchdog.drop(Thread.currentThread()); 223 } 224 } 225