/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.util.test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
/**
* Profiler tool for timing calls to a given code fragment.
*/
public final class Profiler {
private static final Logger s_logger = LoggerFactory.getLogger(Profiler.class);
private static final Collection<Profiler> s_profilers = Sets.newSetFromMap(new MapMaker().weakKeys().<Profiler, Boolean>makeMap());
private static volatile boolean s_enabled;
private static volatile int s_reset;
private final String _name;
private final AtomicInteger _lock = new AtomicInteger();
private final AtomicLong _time = new AtomicLong();
private final AtomicInteger _operations = new AtomicInteger();
private volatile boolean _snapshotPending;
private double _snapshotTime;
private int _snapshotOperations;
private int _reset;
private Profiler(final String name) {
_name = name;
}
public void begin() {
if (!isEnabled()) {
return;
}
do {
int lock = _lock.get();
if (lock == 0) {
if (_lock.compareAndSet(0, 1)) {
break;
}
} else if (lock > 0) {
if (_lock.compareAndSet(lock, lock + 1)) {
break;
}
}
} while (true);
_time.addAndGet(-System.nanoTime());
}
public void end() {
if (!isEnabled()) {
return;
}
_time.addAndGet(System.nanoTime());
_operations.incrementAndGet();
final int lock = _lock.decrementAndGet();
if (lock == 0) {
if (_snapshotPending) {
snapshot();
}
} else if (lock < 0) {
// This happens if the profiling was enabled between the calls to begin and end. Other threads see it as if
// the object is now locked for a snapshot so clear the values.
_time.set(0);
_operations.set(0);
_lock.incrementAndGet();
}
}
public void tick() {
if (!isEnabled()) {
return;
}
_operations.incrementAndGet();
}
public void tick(final long time) {
if (!isEnabled()) {
return;
}
_time.addAndGet(time);
_operations.incrementAndGet();
}
private synchronized void snapshot() {
if (!_lock.compareAndSet(0, -1)) {
// Can't snapshot; currently locked
_snapshotPending = true;
return;
}
_snapshotPending = false;
final int reset = s_reset;
if (_reset != reset) {
_snapshotTime = 0;
_snapshotOperations = 0;
_reset = reset;
}
_snapshotTime += (double) _time.getAndSet(0) / 1e6;
_snapshotOperations += _operations.getAndSet(0);
assert _lock.get() == -1;
_lock.set(0);
}
public static synchronized Profiler create(final String name) {
final Profiler profiler = new Profiler(name);
s_profilers.add(profiler);
return profiler;
}
public static synchronized Profiler create(final Class<?> clazz) {
return create(clazz.getName());
}
public static synchronized Profiler create(final Class<?> clazz, final String name) {
return create(clazz.getName() + '%' + name);
}
private static void insertNoClash(final Map<String, Object[]> report, final Object[] arg, int dot) {
final String key = (String) arg[0];
while (dot >= 0) {
final String substring = key.substring(dot + 1);
if (report.containsKey(substring)) {
final Object[] clash = report.remove(substring);
final int xdot = key.length() - substring.length() - 2;
insertNoClash(report, clash, key.lastIndexOf('.', xdot));
dot = key.lastIndexOf('.', dot - 1);
} else {
report.put(substring, arg);
return;
}
}
report.put(key, arg);
}
private static synchronized void printProfilers() {
final Map<String, Object[]> report = new HashMap<String, Object[]>();
for (Profiler profiler : s_profilers) {
profiler.snapshot();
final Object[] arg = new Object[] {profiler._name, profiler._snapshotOperations, profiler._snapshotTime, (double) profiler._snapshotTime / (double) profiler._snapshotOperations };
insertNoClash(report, arg, profiler._name.lastIndexOf('.'));
}
List<String> keys = new ArrayList<String>(report.keySet());
Collections.sort(keys, new Comparator<String>() {
@Override
public int compare(final String a, final String b) {
final Object[] as = report.get(a);
final Object[] bs = report.get(b);
double d = (Double) bs[2] - (Double) as[2]; // sort by total time consumed
if (d < 0) {
return -1;
} else if (d > 0) {
return 1;
} else {
return 0;
}
}
});
for (String key : keys) {
final Object[] values = report.get(key);
values[0] = key;
s_logger.info("{} - {} in {}ms ({} ms/op)", values);
}
s_logger.debug("{} active profiler instances", s_profilers.size());
}
public static void enable(final long period) {
enable(period, 0);
}
public static synchronized void enable(final long period, final int resetPeriod) {
if (isEnabled()) {
s_logger.warn("Already enabled");
return;
}
s_enabled = true;
final Thread t = new Thread() {
@Override
public void run() {
for (int count = 0;; count++) {
try {
Thread.sleep(period);
} catch (InterruptedException e) {
return;
}
if (resetPeriod != 0) {
if ((count % resetPeriod) == 0) {
s_reset++;
}
}
printProfilers();
}
}
};
t.setName(Profiler.class.getSimpleName());
t.setDaemon(false);
t.start();
}
public static boolean isEnabled() {
return s_enabled;
}
}