package com.plexobject.rbac.metric;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.validator.GenericValidator;
/**
* This class maintains profiling metrics. This is thread safe class.
*
* @author Shahzad Bhatti
*
*/
public class Metric {
private final String name;
private AtomicLong totalDuration;
private AtomicLong totalCalls;
private static final long ONE_MILLI_SEC = 1000000L;
private static final long ONE_SEC = ONE_MILLI_SEC * 1000;
private static final Map<String, Metric> METRICS = new ConcurrentHashMap<String, Metric>();
// This class can only be created through factory-method and it's using
// package scope for testing.
Metric(final String name) {
this.name = name;
this.totalCalls = new AtomicLong();
this.totalDuration = new AtomicLong();
}
public static Collection<Metric> getMetrics() {
return Collections.unmodifiableCollection(METRICS.values());
}
/**
* This method finds or creates metrics
*
* @param name
* - name of metrics
* @return Metric instance
*/
public static Metric getMetric(final String name) {
if (GenericValidator.isBlankOrNull(name)) {
throw new IllegalArgumentException("name not specified");
}
Metric metric = null;
synchronized (name.intern()) {
metric = METRICS.get(name);
if (metric == null) {
metric = new Metric(name);
METRICS.put(name, metric);
}
}
return metric;
}
/**
* This method finds metric and creates a new timer for it. The caller must
* call lapse or stop to add the results back to the metric.
*
* @param name
* - name of metric
* @return Timer instance
*/
public static Timing newTiming(final String name) {
return getMetric(name).newTiming();
}
/**
* This method creates a new timer for it. The caller must call lapse or
* stop to add the results back to the metric.
*
* @return Timer instance
*/
public Timing newTiming() {
return new Timing(this);
}
/**
*
* @return total duration in nano-secs
*/
public long getTotalDurationInNanoSecs() {
return totalDuration.get();
}
/**
*
* @return average duration in nano-secs
*/
public double getAverageDurationInNanoSecs() {
return getTotalDurationInNanoSecs() / (double) getTotalCalls();
}
/**
*
* @return total duration in milli-secs
*/
public long getTotalDurationInMillis() {
return (long) (getAverageDurationInNanoSecs() / ONE_MILLI_SEC);
}
/**
*
* @return average duration in nano-secs
*/
public double getAverageDurationInMillis() {
return getAverageDurationInNanoSecs() / ONE_MILLI_SEC;
}
/**
*
* @return number of calls invoked
*/
public long getTotalCalls() {
return totalCalls.get();
}
void finishedTimer(final int totalCalls, final long totalDuration) {
this.totalCalls.addAndGet(totalCalls);
this.totalDuration.addAndGet(totalDuration);
}
@Override
public String toString() {
double nanoAvg = getAverageDurationInNanoSecs();
String avgTime = nanoAvg > ONE_SEC ? String.format("%.2f secs", nanoAvg
/ ONE_SEC) : nanoAvg > ONE_MILLI_SEC ? String.format(
"%.2f millis", nanoAvg / ONE_MILLI_SEC) : String.format(
"%.2f nanos", nanoAvg);
return new ToStringBuilder(this).append("name", name).append("calls",
getTotalCalls()).append("average duration", avgTime).toString();
}
}