/* This file is part of VoltDB.
* Copyright (C) 2008-2010 VoltDB L.L.C.
*
* This file contains original code and/or modifications of original code.
* Any modifications made by VoltDB L.L.C. are licensed under the following
* terms and conditions:
*
* VoltDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VoltDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2008
* Evan Jones
* Massachusetts Institute of Technology
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb.utils;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
/** A minimal pure Java sampling profiler. */
public class VoltSampler extends Thread {
public static class SampleUnit implements Comparable<SampleUnit> {
public String className;
public String methodName;
int inclusiveSamples = 0;
int endpointSamples = 0;
public static boolean compareEndpoints = false;
@Override
public int compareTo(SampleUnit other) {
assert(other != null);
if (compareEndpoints)
return other.endpointSamples - endpointSamples;
else
return other.inclusiveSamples - inclusiveSamples;
}
}
public static class PerThreadData {
public String name;
public Map<String, SampleUnit> samples = new HashMap<String, SampleUnit>();
public int nativeSamples = 0;
public int javaSamples = 0;
public int parkedSamples = 0;
}
// used during collection
private int intersampleMillis;
private String outputPath;
private final AtomicBoolean doStop = new AtomicBoolean(false);
private final Deque<Map<Thread, StackTraceElement[]>> samples = new ArrayDeque<Map<Thread, StackTraceElement[]>>();
// used during analysis and transformation
private final TreeMap<String, PerThreadData> m_data = new TreeMap<String, PerThreadData>();
public VoltSampler(int intersampleMillis, String outputPath) {
this.intersampleMillis = intersampleMillis;
this.outputPath = outputPath;
}
public void sample() {
// Note: Thread.getAllStackTraces seems to be a bit faster than
// ThreadMXBean.dumpAllThreads.
// 187190 < 214187 ns / call
// ThreadMXBean.getAllThreadIds() seems to be faster than
// Thread.enumerate
// 3662 < 7389 ns / call
// Filtering Thread objects from Thread.enumerate() then using
// Thread.getStackTrace() on
// only the threads we care about is slower than getting all stack
// traces, even if we only
// call Thread.enumerate every 128th time. Same with using
// ThreadMXBean.getAllThreadIds()
// then getThreadInfo()
samples.add(Thread.getAllStackTraces());
}
static final String[] SPECIAL_NAMES = { "Finalizer", "Reference Handler", "Dispatcher" };
public void dumpSamples(PrintStream out, Thread sampleThread) {
// reset from any previous dump
m_data.clear();
// The set of threads that will not be dumped
HashSet<Thread> ignoreThreads = new HashSet<Thread>();
// Ignore the thread that was sampling
ignoreThreads.add(sampleThread);
// Ignore the "special" threads
for (Map.Entry<Thread, StackTraceElement[]> entry : Thread
.getAllStackTraces().entrySet()) {
Thread t = entry.getKey();
for (String special : SPECIAL_NAMES) {
if (t.getName().startsWith(special)) {
ignoreThreads.add(t);
break;
}
}
if (entry.getValue().length == 0) {
// Many JVM daemon threads have no stack
ignoreThreads.add(t);
}
}
// populate all the data structures
for (Map<Thread, StackTraceElement[]> sample : samples) {
for (Map.Entry<Thread, StackTraceElement[]> entry : sample.entrySet()) {
if (ignoreThreads.contains(entry.getKey()))
continue;
assert entry.getValue().length > 0;
StackTraceElement[] elements = entry.getValue();
for (int i = 0; i < elements.length; i++) {
incrementCounters(entry.getKey(), elements[i], i == 0);
//out.println(elements[i].toString());
}
//out.println();
}
}
for (Entry<String, PerThreadData> e : m_data.entrySet()) {
final int MAX_LINES = 50;
PerThreadData ptd = e.getValue();
int totalSamples = ptd.javaSamples + ptd.nativeSamples;
out.printf("\n====================================================\n");
out.printf("THREAD: %s\n", ptd.name);
out.printf("====================================================\n\n");
out.printf("%d native and %d java samples at %.2f%% java\n\n",
ptd.nativeSamples, ptd.javaSamples,
ptd.javaSamples * 100.0 / totalSamples);
out.printf("%d/%d parked samples at %.2f%% parked\n\n",
ptd.parkedSamples, totalSamples,
ptd.parkedSamples * 100.0 / totalSamples);
out.printf(" = All Methods by Inclusive Time = \n\n");
ArrayList<SampleUnit> unitsList = new ArrayList<SampleUnit>();
unitsList.addAll(ptd.samples.values());
SampleUnit[] units = unitsList.toArray(new SampleUnit[0]);
SampleUnit.compareEndpoints = false;
Arrays.sort(units);
//Collections.sort(units);
int maxIndex = (units.length > MAX_LINES) ? MAX_LINES : units.length;
for (int i = 0; i < maxIndex; i++) {
SampleUnit unit = units[i];
out.printf("%80s - %6d/%3.2f%% in method, %6d/%3.2f%% in method code.\n",
unit.className + "." + unit.methodName,
unit.inclusiveSamples, unit.inclusiveSamples * 100.0 / totalSamples,
unit.endpointSamples, unit.endpointSamples * 100.0 / totalSamples);
}
int i = 0;
int found = 0;
out.printf("\n = Restricted to VoltDB Methods by Inclusive Time = \n\n");
while ((i < units.length) && (found < MAX_LINES)) {
SampleUnit unit = units[i++];
if (unit.className.startsWith("org.voltdb") == false)
continue;
found++;
out.printf("%80s - %6d/%3.2f%% in method, %6d/%3.2f%% in method code.\n",
unit.className + "." + unit.methodName,
unit.inclusiveSamples, unit.inclusiveSamples * 100.0 / totalSamples,
unit.endpointSamples, unit.endpointSamples * 100.0 / totalSamples);
}
out.printf("\n = All Methods by In-Method Time = \n\n");
SampleUnit.compareEndpoints = true;
Arrays.sort(units);
//Collections.sort(units);
for (i = 0; i < maxIndex; i++) {
SampleUnit unit = units[i];
out.printf("%80s - %6d/%3.2f%% in method, %6d/%3.2f%% in method code.\n",
unit.className + "." + unit.methodName,
unit.inclusiveSamples, unit.inclusiveSamples * 100.0 / totalSamples,
unit.endpointSamples, unit.endpointSamples * 100.0 / totalSamples);
}
i = 0;
found = 0;
out.printf("\n = Restricted to VoltDB Methods by In-Method Time = \n\n");
while ((i < units.length) && (found < MAX_LINES)) {
SampleUnit unit = units[i++];
if (unit.className.startsWith("org.voltdb") == false)
continue;
found++;
out.printf("%80s - %6d/%3.2f%% in method, %6d/%3.2f%% in method code.\n",
unit.className + "." + unit.methodName,
unit.inclusiveSamples, unit.inclusiveSamples * 100.0 / totalSamples,
unit.endpointSamples, unit.endpointSamples * 100.0 / totalSamples);
}
}
}
void incrementCounters(final Thread thread, StackTraceElement element, boolean endpoint) {
String fullName = element.getClassName() + "." + element.getMethodName();
PerThreadData ptd = m_data.get(thread.getName());
if (ptd == null) {
ptd = new PerThreadData();
ptd.name = thread.getName();
m_data.put(thread.getName(), ptd);
}
SampleUnit unit = ptd.samples.get(fullName);
if (unit == null) {
unit = new SampleUnit();
unit.className = element.getClassName();
unit.methodName = element.getMethodName();
ptd.samples.put(fullName, unit);
}
unit.inclusiveSamples++;
if (endpoint) {
unit.endpointSamples++;
if (unit.methodName.startsWith("native")) ptd.nativeSamples++;
else ptd.javaSamples++;
if (unit.methodName.equals("park")) ptd.parkedSamples++;
}
}
@Override
public void run() {
assert Thread.currentThread().getThreadGroup().getParent() == null;
System.err.println("sampling every " + intersampleMillis);
long start = System.currentTimeMillis();
while (!doStop.get()) {
sample();
try {
Thread.sleep(intersampleMillis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
long end = System.currentTimeMillis();
System.err.println("duration = " + (end - start) + " " + samples.size()
+ " samples; real rate = " + ((end - start) / samples.size()));
try {
PrintStream out = new PrintStream(new File(outputPath));
dumpSamples(out, Thread.currentThread());
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void setShouldStop() {
this.doStop.set(true);
}
}