HistogramMeasurement.java
package com.jeantessier.metrics;
import com.jeantessier.classreader.AttributeType;
import org.apache.logging.log4j.LogManager;
import org.apache.oro.text.perl.Perl5Util;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.*;
/**
* <p>Creates a histogram of a measurement in submetrics.</p>
*
* <p>This is the syntax for initializing this type of
* measurement:</p>
*
* <pre>
* <init>
* measurement name [DISPOSE_x]
* [PLOT_x]
* </init>
* </pre>
*/
public class HistogramMeasurement extends MeasurementBase {
public enum Plot {
/**
* Plot both the x-axis and y-axis using a linear scale.
*/
LINEAR("linear", "/plot_linear$/i"),
/**
* Plot the x-axis using a linear scale and the y-axis using a logarithmic scale.
* Log-lin plot. https://en.wikipedia.org/wiki/Semi-log_plot.
*/
LOG_LINEAR("log-lin", "/plot_log_lin(ear)?$/i"),
/**
* Plot the x-axis using a logarithmic scale and the y-axis using a linear scale.
* Lin-log plot. https://en.wikipedia.org/wiki/Semi-log_plot.
*/
LINEAR_LOG("lin-log", "/plot_lin(ear)?_log$/i"),
/**
* Plot both the x-axis and y-axis using a logarithmic scale.
* Log-log plot. See https://en.wikipedia.org/wiki/Log%E2%80%93log_plot.
*/
LOG_LOG("log-log", "/plot_log_log$/i");
private final static Perl5Util perl = new Perl5Util();
private final String label;
private final String regex;
Plot(String label, String regex) {
this.label = label;
this.regex = regex;
}
public String getLabel() {
return label;
}
public static Plot forName(String name) {
return Arrays.stream(values())
.filter(plot -> perl.match(plot.regex, name))
.findFirst()
.orElse(null);
}
}
private String monitoredMeasurement;
private int dispose;
private Map<Integer, Integer> histogram = new HashMap<>();
private Plot plot;
private int nbSubmetrics = -1;
public HistogramMeasurement(MeasurementDescriptor descriptor, Metrics context, String initText) {
super(descriptor, context, initText);
try (var in = new BufferedReader(new StringReader(initText))) {
monitoredMeasurement = in.readLine().trim();
synchronized (perl()) {
if (perl().match("/(.*)\\s+(dispose_\\w+)$/i", monitoredMeasurement)) {
monitoredMeasurement = perl().group(1);
String disposeText = perl().group(2);
dispose = StatisticalMeasurement.getDispose(disposeText, () -> {
LogManager.getLogger(getClass()).error("Unknown dispose value \"{}\" for monitored measurement \"{}\" of measurement \"{}\", defaulting to DISPOSE_IGNORE", disposeText, monitoredMeasurement, descriptor.getLongName());
return StatisticalMeasurement.DISPOSE_IGNORE;
});
} else {
dispose = StatisticalMeasurement.DISPOSE_IGNORE;
}
}
String plotText = in.readLine();
if (plotText != null) {
plot = Plot.forName(plotText);
}
if (plot == null) {
plot = Plot.LINEAR;
}
} catch (Exception ex) {
LogManager.getLogger(getClass()).debug("Cannot initialize with \"{}\"", initText, ex);
monitoredMeasurement = null;
}
}
public Map<Integer, Integer> getHistogram() {
collectData();
return histogram;
}
public Plot getPlot() {
return plot;
}
private void collectData() {
if (getContext().getSubMetrics().size() != nbSubmetrics) {
synchronized (this) {
if (getContext().getSubMetrics().size() != nbSubmetrics) {
histogram = new HashMap<>();
setEmpty(true);
getContext().getSubMetrics().forEach(this::visitMetrics);
nbSubmetrics = getContext().getSubMetrics().size();
}
}
}
}
private void visitMetrics(Metrics metrics) {
LogManager.getLogger(getClass()).debug("VisitMetrics: {}", metrics.getName());
Measurement measurement = metrics.getMeasurement(monitoredMeasurement);
LogManager.getLogger(getClass()).debug("measurement for {} is {}", monitoredMeasurement, measurement.getClass());
if (measurement instanceof StatisticalMeasurement stats) {
LogManager.getLogger(getClass()).debug("dispose of StatisticalMeasurements is {}", dispose);
switch (dispose) {
case StatisticalMeasurement.DISPOSE_MINIMUM -> {
LogManager.getLogger(getClass()).debug("using Minimum(): {}", stats.getMinimum());
increment(stats.getMinimum());
}
case StatisticalMeasurement.DISPOSE_MEDIAN -> {
LogManager.getLogger(getClass()).debug("using Median(): {}", stats.getMedian());
increment(stats.getMedian());
}
case StatisticalMeasurement.DISPOSE_AVERAGE -> {
LogManager.getLogger(getClass()).debug("using Average(): {}", stats.getAverage());
increment(stats.getAverage());
}
case StatisticalMeasurement.DISPOSE_STANDARD_DEVIATION -> {
LogManager.getLogger(getClass()).debug("using StandardDeviation(): {}", stats.getStandardDeviation());
increment(stats.getStandardDeviation());
}
case StatisticalMeasurement.DISPOSE_MAXIMUM -> {
LogManager.getLogger(getClass()).debug("using Maximum(): {}", stats.getMaximum());
increment(stats.getMaximum());
}
case StatisticalMeasurement.DISPOSE_SUM -> {
LogManager.getLogger(getClass()).debug("using Sum(): {}", stats.getSum());
increment(stats.getSum());
}
case StatisticalMeasurement.DISPOSE_NB_DATA_POINTS -> {
LogManager.getLogger(getClass()).debug("using NbDataPoints(): {}", stats.getNbDataPoints());
increment(stats.getNbDataPoints());
}
default -> {
LogManager.getLogger(getClass()).debug("Skipping to next level ...");
metrics.getSubMetrics().forEach(this::visitMetrics);
}
}
} else if (measurement instanceof NullMeasurement) {
LogManager.getLogger(getClass()).debug("Skipping to next level ...");
metrics.getSubMetrics().forEach(this::visitMetrics);
} else {
Number value = measurement.getValue();
LogManager.getLogger(getClass()).debug("{} on {} is {}", monitoredMeasurement, metrics.getName(), value);
if (value != null) {
increment(value.doubleValue());
}
}
if (super.isEmpty()) {
setEmpty(measurement.isEmpty());
}
}
private void increment(double value) {
histogram.compute((int) Math.round(value), (bucket, count) -> count == null ? 1 : count + 1);
}
public boolean isEmpty() {
collectData();
return super.isEmpty();
}
public void accept(MeasurementVisitor visitor) {
visitor.visitHistogramMeasurement(this);
}
protected double compute() {
collectData();
if (histogram.isEmpty()) {
return Double.NaN;
}
return histogram.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.get();
}
public String toString() {
collectData();
return histogram.toString();
}
}