diff options
Diffstat (limited to 'src/com/android/gallery3d/util/Profile.java')
-rw-r--r-- | src/com/android/gallery3d/util/Profile.java | 226 |
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()); + } +} |