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
17package com.android.gallery3d.util;
18
19import android.os.Handler;
20import android.os.HandlerThread;
21import android.os.Process;
22
23import java.util.ArrayList;
24import 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.
34public 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