blob: 7ed72c90ed1e28aafe60da1953fc4256da7a861e [file] [log] [blame]
/*
* 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());
}
}