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