package org.dcache.commons.stats; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.InstanceAlreadyExistsException; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import java.lang.management.ManagementFactory; import java.util.Formatter; import java.util.concurrent.TimeUnit; import org.dcache.util.TimeUtils; /** * This class stores an average and other statistics of the execution time of the request. */ public class RequestExecutionTimeGaugeImpl implements RequestExecutionTimeGaugeMXBean { private static final Logger LOG = LoggerFactory.getLogger(RequestExecutionTimeGaugeImpl.class); private final String name; private final Statistics statistics = new Statistics(); /** * last value fed to the gauge */ private long lastExecutionTime; private long startTime; /** * * @param name */ public RequestExecutionTimeGaugeImpl(String name, String family) { this.name = name; String mxName = String.format("%s:type=RequestExecutionTimeGauge,family=%s,name=%s", this.getClass().getPackage().getName(), family, this.name); MBeanServer server = ManagementFactory.getPlatformMBeanServer(); try { ObjectName mxBeanName = new ObjectName(mxName); if (!server.isRegistered(mxBeanName)) { server.registerMBean(this, mxBeanName); } } catch (MalformedObjectNameException ex) { LOG.warn("Failed to create a MXBean with name: {} : {}" , mxName, ex.toString()); } catch (InstanceAlreadyExistsException | MBeanRegistrationException ex) { LOG.warn("Failed to register a MXBean: {}", ex.toString()); } catch (NotCompliantMBeanException ex) { LOG.warn("Failed to create a MXBean: {}", ex.toString()); } startTime = System.currentTimeMillis(); } /** * * @param nextExecTime */ @Override public synchronized void update(long nextExecTime) { if (nextExecTime < 0) { LOG.info("possible backwards time shift detected; discarding invalid data ({})", nextExecTime); return; } statistics.update(nextExecTime); lastExecutionTime = nextExecTime; } /** * Returns average over the lifetime of the gauge. */ @Override public synchronized double getAverageExecutionTime() { return statistics.getMean(); } /** * Returns average over the last period and reset the gauge. */ @Override public synchronized double resetAndGetAverageExecutionTime() { double avg = getAverageExecutionTime(); reset(); return avg; } /** * Returns string representation of this RequestExecutionTimeGauge * Only long term statistics is printed */ @Override public synchronized String toString() { String aName = (name.length() > 34) ? name.substring(0, 34) : name; long updatePeriod = System.currentTimeMillis() - startTime; StringBuilder sb = new StringBuilder(); try (Formatter formatter = new Formatter(sb)) { formatter.format("%-34s %,12.2f\u00B1%,10.2f %,12d %,12d %,12.2f %,12d %12s", aName, statistics.getMean(), statistics.getStandardError(), getMinExecutionTime(), getMaxExecutionTime(), statistics.getSampleStandardDeviation(), statistics.getSampleSize(), TimeUtils.duration(updatePeriod, TimeUnit.MILLISECONDS, TimeUtils.TimeUnitFormat.SHORT)); } return sb.toString(); } /** * @return the minExecutionTime */ @Override public synchronized long getMinExecutionTime() { return Math.round(statistics.getMin()); } /** * @return the maxExecutionTime */ @Override public synchronized long getMaxExecutionTime() { return Math.round(statistics.getMax()); } /** * @return the name */ @Override public String getName() { return name; } @Override public synchronized double getStandardDeviation() { return statistics.getSampleStandardDeviation(); } /** * * @return standard error of the mean */ @Override public synchronized double getStandardError() { return statistics.getStandardError(); } /** * @return the updateNum */ @Override public synchronized long getUpdateNum() { return statistics.getSampleSize(); } /** * @return the lastExecutionTime */ @Override public synchronized long getLastExecutionTime() { return lastExecutionTime; } /** * @return the startTime */ @Override public synchronized long getStartTime() { return startTime; } @Override public synchronized void reset() { startTime = System.currentTimeMillis(); lastExecutionTime = 0; statistics.reset(); } /** * Encapsulates an online algorithm for maintaining various statistics about * samples. * * See https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance * for an explanation. */ private static class Statistics { private double mean; // Running mean private double m2; // Sum of squares of differences from mean private double min = Double.NaN; // Smallest sample private double max = Double.NaN; // Largest sample private long n; // Number of samples public void reset() { mean = m2 = 0; min = max = Double.NaN; n = 0; } public void update(double x) { min = (n == 0) ? x : Math.min(x, min); max = (n == 0) ? x : Math.max(x, max); n++; double nextMean = mean + (x - mean) / n; double nextM2 = m2 + (x - mean) * (x - nextMean); mean = nextMean; m2 = nextM2; } public double getMean() { return (n > 0) ? mean : Double.NaN; } public double getSampleVariance() { return (n > 1) ? m2 / (n - 1) : Double.NaN; } public double getPopulationVariance() { return (n > 0) ? m2 / n : Double.NaN; } public double getSampleStandardDeviation() { return Math.sqrt(getSampleVariance()); } public double getPopulationStandardDeviation() { return Math.sqrt(getPopulationVariance()); } public double getStandardError() { return getSampleStandardDeviation() / Math.sqrt(n); } public long getSampleSize() { return n; } public double getMin() { return min; } public double getMax() { return max; } } }