blob: 1d88dd17001886c274db909477d077c6247af128 [file] [log] [blame]
/*
* Copyright (C) 2009 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 dalvik.system;
import java.util.logging.Logger;
import java.io.DataInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* A sampling profiler.
*
* @hide
*/
public class SamplingProfiler {
private static final Logger logger = Logger.getLogger(
SamplingProfiler.class.getName());
static final boolean DEBUG = false;
enum State {
/** The sampling thread hasn't started or is waiting to resume. */
PAUSED,
/** The sampling thread is collecting samples. */
RUNNING,
/** The sampling thread is shutting down. */
SHUTTING_DOWN
}
/** Pointer to native state. */
int pointer = 0;
/** The thread that collects samples. */
Thread samplingThread;
/** Time between samples. */
volatile int delay; // ms
/** Number of samples taken (~samples per second * number of threads). */
int totalThreadsSampled = 0;
/** Total time spent collecting samples. */
long totalSampleTime = 0;
/** The state of the profiler. */
volatile State state = State.PAUSED;
private SamplingProfiler() {}
/**
* Returns true if the profiler is running.
*/
public boolean isRunning() {
return state == State.RUNNING;
}
/**
* Starts collecting samples.
*
* @param samplesPerSecond number of times to sample each thread per second
* @throws IllegalStateException if the profiler is
* {@linkplain #shutDown()} shutting down}
*/
public synchronized void start(int samplesPerSecond) {
if (samplesPerSecond < 1) {
throw new IllegalArgumentException("samplesPerSecond < 1");
}
ensureNotShuttingDown();
delay = 1000 / samplesPerSecond;
if (!isRunning()) {
if (DEBUG) logger.info("Starting profiler...");
state = State.RUNNING;
if (samplingThread == null) {
// TODO: Priority?
samplingThread = new Thread(new Sampler(), "SamplingProfiler");
samplingThread.setDaemon(true);
samplingThread.start();
} else {
notifyAll();
}
}
}
/**
* Pauses sample collection.
*/
public synchronized void pause() {
if (isRunning()) {
if (DEBUG) logger.info("Pausing profiler...");
state = State.PAUSED;
}
}
/**
* Captures collected samples and clears the sample set. Returns null
* if no data has been captured.
*
* <p>Note: The exact format is not documented because it's not set in
* stone yet.
*
* @throws IllegalStateException if the profiler is
* {@linkplain #shutDown()} shutting down}
*/
public synchronized byte[] snapshot() {
ensureNotShuttingDown();
if (pointer == 0 || totalThreadsSampled == 0) {
return null;
}
if (DEBUG) {
int size = size(pointer);
int collisions = collisions(pointer);
long start = System.nanoTime();
byte[] bytes = snapshot(pointer);
long elapsed = System.nanoTime() - start;
/*
* We shifted the times by 10 bits in the sampling thread to avoid
* overflow. Undo the shift and then convert from ns to us.
*/
long averageSampleTime = ((totalSampleTime / totalThreadsSampled)
<< 10) / 1000;
logger.info("Grabbed snapshot in " + (elapsed / 1000) + "us."
+ " Samples collected: " + totalThreadsSampled
+ ", Average sample time (per thread): "
+ averageSampleTime + "us"
+ ", Set size: " + size
+ ", Collisions: " + collisions);
totalThreadsSampled = 0;
totalSampleTime = 0;
return bytes;
} else {
totalThreadsSampled = 0;
return snapshot(pointer);
}
}
/**
* Identifies the "event thread". For a user-facing application, this
* might be the UI thread. For a background process, this might be the
* thread that processes incoming requests.
*
* @throws IllegalStateException if the profiler is
* {@linkplain #shutDown()} shutting down}
*/
public synchronized void setEventThread(Thread eventThread) {
ensureNotShuttingDown();
if (pointer == 0) {
pointer = allocate();
}
setEventThread(pointer, eventThread);
}
private void ensureNotShuttingDown() {
if (state == State.SHUTTING_DOWN) {
throw new IllegalStateException("Profiler is shutting down.");
}
}
/**
* Shuts down the profiler thread and frees native memory. The profiler
* will recreate the thread the next time {@link #start(int)} is called.
*
* @throws IllegalStateException if the profiler is already shutting down
* or if it hasn't started yet
*
*/
public void shutDown() {
Thread toStop;
synchronized (this) {
ensureNotShuttingDown();
toStop = samplingThread;
if (toStop == null) {
throw new IllegalStateException(
"The profiler was never started.");
}
state = State.SHUTTING_DOWN;
samplingThread = null;
notifyAll();
}
// Release lock to 'this' so background thread can grab it and stop.
// Interrupt the thread in case it's sleeping.
toStop.interrupt();
while (true) {
try {
toStop.join();
break;
} catch (InterruptedException e) { /* ignore */ }
}
synchronized (this) {
if (pointer != 0) {
free(pointer);
pointer = 0;
}
totalThreadsSampled = 0;
totalSampleTime = 0;
state = State.PAUSED;
}
}
/** Collects some data. Returns number of threads sampled. */
private static native int sample(int pointer);
/** Allocates native state. */
private static native int allocate();
/** Frees native state. */
private static native void free(int pointer);
/** Gets the number of methods in the sample set. */
private static native int size(int pointer);
/** Gets the number of collisions in the sample set. */
private static native int collisions(int pointer);
/** Captures data. */
private static native byte[] snapshot(int pointer);
/** Identifies the "event thread". */
private static native void setEventThread(int pointer, Thread thread);
/**
* Background thread that collects samples.
*/
class Sampler implements Runnable {
public void run() {
boolean firstSample = true;
while (true) {
synchronized (SamplingProfiler.this) {
if (!isRunning()) {
if (DEBUG) logger.info("Paused profiler.");
while (!isRunning()) {
if (state == State.SHUTTING_DOWN) {
// Stop thread.
return;
}
try {
SamplingProfiler.this.wait();
} catch (InterruptedException e) { /* ignore */ }
}
firstSample = true;
}
if (pointer == 0) {
pointer = allocate();
}
if (firstSample) {
if (DEBUG) logger.info("Started profiler.");
firstSample = false;
}
if (DEBUG) {
long start = System.nanoTime();
int threadsSampled = sample(pointer);
long elapsed = System.nanoTime() - start;
totalThreadsSampled += threadsSampled;
totalSampleTime += elapsed >> 10; // avoids overflow.
} else {
totalThreadsSampled += sample(pointer);
}
}
try {
Thread.sleep(delay);
} catch (InterruptedException e) { /* ignore */ }
}
}
}
/**
* Dumps a snapshot to the log. Useful for debugging.
*/
public static void logSnapshot(byte[] snapshot) {
DataInputStream in = new DataInputStream(
new ByteArrayInputStream(snapshot));
try {
int version = in.readUnsignedShort();
int classCount = in.readUnsignedShort();
StringBuilder sb = new StringBuilder();
sb.append("version=").append(version).append(' ')
.append("classes=").append(classCount).append('\n');
logger.info(sb.toString());
for (int i = 0; i < classCount; i++) {
sb = new StringBuilder();
sb.append("class ").append(in.readUTF()).append('\n');
int methodCount = in.readUnsignedShort();
for (int m = 0; m < methodCount; m++) {
sb.append(" ").append(in.readUTF()).append(":\n");
sb.append(" event:\n");
appendCounts(in, sb);
sb.append(" other:\n");
appendCounts(in, sb);
}
logger.info(sb.toString());
}
} catch (IOException e) {
logger.warning(e.toString());
}
}
private static void appendCounts(DataInputStream in, StringBuilder sb)
throws IOException {
sb.append(" running:\n");
sb.append(" caller: ").append(in.readShort()).append('\n');
sb.append(" leaf: ").append(in.readShort()).append('\n');
sb.append(" suspended:\n");
sb.append(" caller: ").append(in.readShort()).append('\n');
sb.append(" leaf: ").append(in.readShort()).append('\n');
}
/** This will be allocated when the user calls getInstance(). */
private static final SamplingProfiler instance = new SamplingProfiler();
/**
* Gets the profiler. The profiler is not running by default. Start it
* with {@link #start(int)}.
*/
public static synchronized SamplingProfiler getInstance() {
return instance;
}
}