package io.pcp.parfait.timing;
import java.lang.management.ManagementFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static tec.uom.se.AbstractUnit.ONE;
import javax.measure.Unit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.pcp.parfait.MonitorableRegistry;
import io.pcp.parfait.MonitoredCounter;
import com.google.common.collect.ImmutableList;
import net.jcip.annotations.ThreadSafe;
/**
* A class to provide a {@link EventMetricCollector} to each {@link Timeable} on demand, guaranteed
* to be thread-safe as long is it's only ever used by the requesting thread.
*/
@ThreadSafe
public class EventTimer {
/**
* Setting this {@link Logger} to DEBUG level will list all the created metrics in a
* tab-delimited format, useful for importing elsewhere
*/
private static final Logger LOG = LoggerFactory.getLogger(EventTimer.class);
private final Map<Object, EventCounters> perEventGroupCounters = new ConcurrentHashMap<Object, EventCounters>();
private final List<StepMeasurementSink> stepMeasurementSinks;
private final ThreadValue<EventMetricCollector> metricCollectors = new ThreadValue.WeakReferenceThreadMap<EventMetricCollector>() {
@Override
protected EventMetricCollector initialValue() {
return new EventMetricCollector(perEventGroupCounters, stepMeasurementSinks);
}
};
/**
* Holds the singleton total counters which are used across all events. The key is the
* metric name
*/
private final Map<String, MonitoredCounter> totalCountersAcrossEvents = new HashMap<String, MonitoredCounter>();
private final ThreadMetricSuite metricSuite;
private final String prefix;
private final MonitorableRegistry registry;
public EventTimer(String prefix, MonitorableRegistry registry, ThreadMetricSuite metrics,
boolean enableCpuCollection, boolean enableContentionCollection) {
this(prefix, registry, metrics, enableCpuCollection, enableContentionCollection, Collections.<StepMeasurementSink>emptyList());
}
public EventTimer(String prefix, MonitorableRegistry registry, ThreadMetricSuite metrics,
boolean enableCpuCollection, boolean enableContentionCollection, List<StepMeasurementSink> stepMeasurementSinks) {
this.metricSuite = metrics;
this.prefix = prefix;
this.registry = registry;
if (enableCpuCollection) {
ManagementFactory.getThreadMXBean().setThreadCpuTimeEnabled(true);
}
if (enableContentionCollection) {
ManagementFactory.getThreadMXBean().setThreadContentionMonitoringEnabled(true);
}
this.stepMeasurementSinks = ImmutableList.copyOf(stepMeasurementSinks);
}
public EventMetricCollector getCollector() {
return metricCollectors.get();
}
public void registerTimeable(Timeable timeable, String eventGroup) {
timeable.setEventTimer(this);
perEventGroupCounters.put(timeable, getCounterSet(eventGroup));
}
public void registerMetric(String eventGroup) {
perEventGroupCounters.put(eventGroup, getCounterSet(eventGroup));
}
private EventCounters getCounterSet(String eventGroup) {
EventMetricCounters invocationCounter = createEventMetricCounters(eventGroup, "count",
"Total number of times the event was directly triggered", ONE);
EventCounters counters = new EventCounters(invocationCounter, cleanName(eventGroup));
for (ThreadMetric metric : metricSuite.metrics()) {
EventMetricCounters timingCounter = createEventMetricCounters(eventGroup, metric
.getCounterSuffix(), metric.getDescription(), metric.getUnit());
counters.addMetric(metric, timingCounter);
}
return counters;
}
private MonitoredCounter createMetric(String beanName, String metric, String description, Unit<?> unit) {
String metricName = getMetricName(beanName, metric);
String metricDescription = String.format(description, beanName);
LOG.debug("Created metric: " + metricName + "\t" + metricDescription);
return new MonitoredCounter(metricName, metricDescription, registry, unit);
}
private EventMetricCounters createEventMetricCounters(String beanName, String metric,
String metricDescription, Unit<?> unit) {
MonitoredCounter metricCounter = createMetric(beanName, metric, metricDescription + " ["
+ beanName + "]", unit);
MonitoredCounter totalCounter;
totalCounter = totalCountersAcrossEvents.get(metric);
if (totalCounter == null) {
totalCounter = new MonitoredCounter(getTotalMetricName(metric), metricDescription
+ " [TOTAL]", registry, unit);
totalCountersAcrossEvents.put(metric, totalCounter);
}
return new EventMetricCounters(metricCounter, totalCounter);
}
private String getMetricName(String eventGroup, String metric) {
return prefix + "." + cleanName(eventGroup) + "." + metric;
}
private String cleanName(String eventGroup) {
// TODO do name cleanup elsewhere
return eventGroup.replace("/", "");
}
private String getTotalMetricName(String metric) {
return prefix + ".total." + metric;
}
Integer getNumberOfTotalEventCounters() {
return totalCountersAcrossEvents.size();
}
EventCounters getCounterSetForEventGroup(Object eventGroup) {
return perEventGroupCounters.get(eventGroup);
}
ThreadMetricSuite getMetricSuite() {
return metricSuite;
}
Map<Thread, EventMetricCollector> getCollectorThreadMap() {
return metricCollectors.asMap();
}
}