| /* |
| * 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()); |
| } |
| } |