summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/util/Profile.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/util/Profile.java')
-rw-r--r--src/com/android/gallery3d/util/Profile.java226
1 files changed, 226 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/util/Profile.java b/src/com/android/gallery3d/util/Profile.java
new file mode 100644
index 000000000..7ed72c90e
--- /dev/null
+++ b/src/com/android/gallery3d/util/Profile.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.util;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+// The Profile class is used to collect profiling information for a thread. It
+// samples stack traces for a thread periodically. enable() and disable() is
+// used to enable and disable profiling for the calling thread. The profiling
+// information can then be dumped to a file using the dumpToFile() method.
+//
+// The disableAll() method can be used to disable profiling for all threads and
+// can be called in onPause() to ensure all profiling is disabled when an
+// activity is paused.
+public class Profile {
+ @SuppressWarnings("unused")
+ private static final String TAG = "Profile";
+ private static final int NS_PER_MS = 1000000;
+
+ // This is a watchdog entry for one thread.
+ // For every cycleTime period, we dump the stack of the thread.
+ private static class WatchEntry {
+ Thread thread;
+
+ // Both are in milliseconds
+ int cycleTime;
+ int wakeTime;
+
+ boolean isHolding;
+ ArrayList<String[]> holdingStacks = new ArrayList<String[]>();
+ }
+
+ // This is a watchdog thread which dumps stacks of other threads periodically.
+ private static Watchdog sWatchdog = new Watchdog();
+
+ private static class Watchdog {
+ private ArrayList<WatchEntry> mList = new ArrayList<WatchEntry>();
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private Runnable mProcessRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (Watchdog.this) {
+ processList();
+ }
+ }
+ };
+ private Random mRandom = new Random();
+ private ProfileData mProfileData = new ProfileData();
+
+ public Watchdog() {
+ mHandlerThread = new HandlerThread("Watchdog Handler",
+ Process.THREAD_PRIORITY_FOREGROUND);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ public synchronized void addWatchEntry(Thread thread, int cycleTime) {
+ WatchEntry e = new WatchEntry();
+ e.thread = thread;
+ e.cycleTime = cycleTime;
+ int firstDelay = 1 + mRandom.nextInt(cycleTime);
+ e.wakeTime = (int) (System.nanoTime() / NS_PER_MS) + firstDelay;
+ mList.add(e);
+ processList();
+ }
+
+ public synchronized void removeWatchEntry(Thread thread) {
+ for (int i = 0; i < mList.size(); i++) {
+ if (mList.get(i).thread == thread) {
+ mList.remove(i);
+ break;
+ }
+ }
+ processList();
+ }
+
+ public synchronized void removeAllWatchEntries() {
+ mList.clear();
+ processList();
+ }
+
+ private void processList() {
+ mHandler.removeCallbacks(mProcessRunnable);
+ if (mList.size() == 0) return;
+
+ int currentTime = (int) (System.nanoTime() / NS_PER_MS);
+ int nextWakeTime = 0;
+
+ for (WatchEntry entry : mList) {
+ if (currentTime > entry.wakeTime) {
+ entry.wakeTime += entry.cycleTime;
+ Thread thread = entry.thread;
+ sampleStack(entry);
+ }
+
+ if (entry.wakeTime > nextWakeTime) {
+ nextWakeTime = entry.wakeTime;
+ }
+ }
+
+ long delay = nextWakeTime - currentTime;
+ mHandler.postDelayed(mProcessRunnable, delay);
+ }
+
+ private void sampleStack(WatchEntry entry) {
+ Thread thread = entry.thread;
+ StackTraceElement[] stack = thread.getStackTrace();
+ String[] lines = new String[stack.length];
+ for (int i = 0; i < stack.length; i++) {
+ lines[i] = stack[i].toString();
+ }
+ if (entry.isHolding) {
+ entry.holdingStacks.add(lines);
+ } else {
+ mProfileData.addSample(lines);
+ }
+ }
+
+ private WatchEntry findEntry(Thread thread) {
+ for (int i = 0; i < mList.size(); i++) {
+ WatchEntry entry = mList.get(i);
+ if (entry.thread == thread) return entry;
+ }
+ return null;
+ }
+
+ public synchronized void dumpToFile(String filename) {
+ mProfileData.dumpToFile(filename);
+ }
+
+ public synchronized void reset() {
+ mProfileData.reset();
+ }
+
+ public synchronized void hold(Thread t) {
+ WatchEntry entry = findEntry(t);
+
+ // This can happen if the profiling is disabled (probably from
+ // another thread). Same check is applied in commit() and drop()
+ // below.
+ if (entry == null) return;
+
+ entry.isHolding = true;
+ }
+
+ public synchronized void commit(Thread t) {
+ WatchEntry entry = findEntry(t);
+ if (entry == null) return;
+ ArrayList<String[]> stacks = entry.holdingStacks;
+ for (int i = 0; i < stacks.size(); i++) {
+ mProfileData.addSample(stacks.get(i));
+ }
+ entry.isHolding = false;
+ entry.holdingStacks.clear();
+ }
+
+ public synchronized void drop(Thread t) {
+ WatchEntry entry = findEntry(t);
+ if (entry == null) return;
+ entry.isHolding = false;
+ entry.holdingStacks.clear();
+ }
+ }
+
+ // Enable profiling for the calling thread. Periodically (every cycleTimeInMs
+ // milliseconds) sample the stack trace of the calling thread.
+ public static void enable(int cycleTimeInMs) {
+ Thread t = Thread.currentThread();
+ sWatchdog.addWatchEntry(t, cycleTimeInMs);
+ }
+
+ // Disable profiling for the calling thread.
+ public static void disable() {
+ sWatchdog.removeWatchEntry(Thread.currentThread());
+ }
+
+ // Disable profiling for all threads.
+ public static void disableAll() {
+ sWatchdog.removeAllWatchEntries();
+ }
+
+ // Dump the profiling data to a file.
+ public static void dumpToFile(String filename) {
+ sWatchdog.dumpToFile(filename);
+ }
+
+ // Reset the collected profiling data.
+ public static void reset() {
+ sWatchdog.reset();
+ }
+
+ // Hold the future samples coming from current thread until commit() or
+ // drop() is called, and those samples are recorded or ignored as a result.
+ // This must called after enable() to be effective.
+ public static void hold() {
+ sWatchdog.hold(Thread.currentThread());
+ }
+
+ public static void commit() {
+ sWatchdog.commit(Thread.currentThread());
+ }
+
+ public static void drop() {
+ sWatchdog.drop(Thread.currentThread());
+ }
+}