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