com.purej.vminspect.data.statistics.Statistics Maven / Gradle / Ivy
// Copyright (c), 2013, adopus consulting GmbH Switzerland, all rights reserved.
package com.purej.vminspect.data.statistics;
import java.awt.Color;
import java.awt.GradientPaint;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import org.jrobin.core.RrdBackendFactory;
import org.jrobin.core.RrdDb;
import org.jrobin.core.RrdDef;
import org.jrobin.core.RrdException;
import org.jrobin.core.RrdMemoryBackendFactory;
import org.jrobin.core.Sample;
import org.jrobin.core.Util;
import org.jrobin.graph.RrdGraph;
import org.jrobin.graph.RrdGraphDef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.purej.vminspect.data.SystemData;
import com.purej.vminspect.util.Utils;
/**
* Represents a single statistics using JRobin as the data store.
* See http://oldwww.jrobin.org/api/index.html for API documentation.
*
* @author Stefan Mueller
*/
public final class Statistics {
private static final Logger LOG = LoggerFactory.getLogger(StatisticsCollector.class);
private static final String FUNCTION_AVG = "AVERAGE";
private static final String FUNCTION_MAX = "MAX";
private static final int HOUR = 60 * 60;
private static final int DAY = 24 * HOUR;
private final String _name;
private final String _label;
private final String _description;
private final String _unit;
private final ValueProvider _valueProvider;
private final int _resolutionSeconds;
private final RrdBackendFactory _rrdBackendFactory;
private final String _rrdPath;
/**
* Creates a new instance of this class.
*
* @param name the name of the statistics, must be a simple name without spaces and special characters
* @param label the label to be shown on the generated statistics graphics
* @param unit the unit to be shown on the generated statistics graphics
* @param description the description to be shown on the UI
* @param valueProvider the provider for statistics values
* @param storageDir where the JRobin file should be stored
* @param resolutionSeconds the resolution in seconds
* @param rrdBackendFactory the backend factory to be used
* @throws IOException if a JRobin file access error occurred
*/
public Statistics(String name, String label, String unit, String description, ValueProvider valueProvider, String storageDir,
int resolutionSeconds, RrdBackendFactory rrdBackendFactory) throws IOException {
if (name == null || name.length() > 20) {
throw new IllegalArgumentException("JRobin names cannot exceed 20 characaters!");
}
_name = Utils.checkNotNull(name);
_label = Utils.checkNotNull(label);
_unit = Utils.checkNotNull(unit);
_description = Utils.checkNotNull(description);
_valueProvider = Utils.checkNotNull(valueProvider);
_resolutionSeconds = resolutionSeconds;
_rrdBackendFactory = Utils.checkNotNull(rrdBackendFactory);
_rrdPath = storageDir != null ? new File(storageDir, name + ".rrd").getCanonicalPath() : name + ".rrd";
initRrdDb(false);
}
private RrdDef createRrdDef() throws RrdException {
RrdDef rrdDef = new RrdDef(_rrdPath, _resolutionSeconds);
rrdDef.setStartTime(Util.getTime() - _resolutionSeconds); // Matches more or less as collect is called right after init
rrdDef.addDatasource(_name, "GAUGE", _resolutionSeconds * 2, 0, Double.NaN); // Single gauge
// Archives for average/max for each supported period:
// 1 second for 1 hour periods:
rrdDef.addArchive(FUNCTION_AVG, 0.25, 1, DAY / _resolutionSeconds);
rrdDef.addArchive(FUNCTION_MAX, 0.25, 1, DAY / _resolutionSeconds);
// 1 hour for 1 week periods:
rrdDef.addArchive(FUNCTION_AVG, 0.25, HOUR / _resolutionSeconds, 7 * 24);
rrdDef.addArchive(FUNCTION_MAX, 0.25, HOUR / _resolutionSeconds, 7 * 24);
// 6 hours for 1 Month periods:
rrdDef.addArchive(FUNCTION_AVG, 0.25, 6 * HOUR / _resolutionSeconds, 31 * 4);
rrdDef.addArchive(FUNCTION_MAX, 0.25, 6 * HOUR / _resolutionSeconds, 31 * 4);
// 2 days for year/all periods:
rrdDef.addArchive(FUNCTION_AVG, 0.25, 2 * DAY / _resolutionSeconds, 2 * 12 * 15);
rrdDef.addArchive(FUNCTION_MAX, 0.25, 2 * DAY / _resolutionSeconds, 2 * 12 * 15);
return rrdDef;
}
private void initRrdDb(boolean overwrite) throws IOException {
try {
if (_rrdBackendFactory instanceof RrdMemoryBackendFactory) {
RrdDb rrdDb = new RrdDb(createRrdDef(), _rrdBackendFactory);
rrdDb.close();
}
else {
// File backend:
File rrdFile = new File(_rrdPath);
if (overwrite || !rrdFile.exists() || rrdFile.length() == 0) {
RrdDb rrdDb = new RrdDb(createRrdDef(), _rrdBackendFactory);
rrdDb.close();
}
}
}
catch (RrdException e) {
throw new IOException(e);
}
}
/**
* Returns the technical name (JRobin datasource name).
*/
public String getName() {
return _name;
}
/**
* Returns the label for the UI.
*/
public String getLabel() {
return _label;
}
/**
* Returns the description for the UI.
*/
public String getDescription() {
return _description;
}
/**
* Collects the current value of this statistics using the configured {@link ValueProvider}.
* This method will be called on a regular basis by the {@link StatisticsCollector}.
*/
public void collectValue(SystemData data) throws IOException {
double value = _valueProvider.getValue(data);
try {
addValue(value);
}
catch (FileNotFoundException e) {
LOG.warn("JRobin file '" + _rrdPath + "' does not exist, recreating it");
initRrdDb(true);
addValue(value);
}
}
private void addValue(double value) throws IOException {
try {
// Open the existing file in r/w mode:
RrdDb rrdDb = new RrdDb(_rrdPath, false, _rrdBackendFactory);
try {
// Create sample with the current timestamp:
Sample sample = rrdDb.createSample();
if (sample.getTime() > rrdDb.getLastUpdateTime()) {
sample.setValue(_name, value);
sample.update();
}
}
finally {
rrdDb.close();
}
}
catch (RrdException e) {
String msg = "Accessing JRobin statistics file '" + _rrdPath + "' failed! If the problem persists, delete the file so it will be recreated.";
LOG.error(msg, e);
throw new IOException(msg, e);
}
}
/**
* Creates a graphics binary of this statistics in PNG format.
*
* @param range the range to be shown
* @param width the width of the created PNG
* @param height the height of the created PNG
* @return the created binary image data
* @throws IOException if image creation failed
*/
public byte[] createGraph(Range range, int width, int height) throws IOException {
try {
// Create the graph definition:
RrdGraphDef graphDef = new RrdGraphDef();
graphDef.setPoolUsed(true);
graphDef.setFilename("-"); // Important for in-memory generation!
// Set datasources:
graphDef.datasource("average", _rrdPath, _name, FUNCTION_AVG, _rrdBackendFactory.getFactoryName());
graphDef.datasource("max", _rrdPath, _name, FUNCTION_MAX, _rrdBackendFactory.getFactoryName());
graphDef.setMinValue(0);
// Set graphics stuff:
graphDef.setImageFormat("png");
graphDef.area("average", new GradientPaint(0, 0, Color.RED, 0, height, Color.GREEN, false), "Mean");
graphDef.line("max", Color.BLUE, "Maximum");
graphDef.gprint("average", FUNCTION_AVG, "Mean: %9.0f " + _unit + "\\r");
graphDef.gprint("max", FUNCTION_MAX, "Maximum: %9.0f " + _unit + "\\r");
graphDef.setWidth(width);
graphDef.setHeight(height);
setGraphStartEndTime(graphDef, range);
setGraphTitle(graphDef, _label, range, width);
return new RrdGraph(graphDef).getRrdGraphInfo().getBytes();
}
catch (final RrdException e) {
throw new IOException(e);
}
}
private static void setGraphStartEndTime(RrdGraphDef graphDef, Range range) {
long endTime; // Current timestamp or custom end-date
long startTime; // Depending on the range and end-date
if (range.getPeriod().equals(Period.CUSTOM)) {
// Custom period:
endTime = Math.min(range.getEndDate().getTime() / 1000, Util.getTime());
startTime = range.getStartDate().getTime() / 1000;
}
else {
endTime = Util.getTime();
startTime = endTime - range.getPeriod().getDurationSeconds();
}
graphDef.setStartTime(startTime);
graphDef.setEndTime(endTime);
graphDef.setFirstDayOfWeek(Calendar.getInstance().getFirstDayOfWeek());
}
private static void setGraphTitle(RrdGraphDef graphDef, String label, Range range, int width) {
String titleStart = label + " - " + range.getPeriod().getLabel();
String titleEnd = "";
if (width > 400) {
if (range.getPeriod().equals(Period.CUSTOM)) {
titleEnd = " - " + Utils.formatDate(range.getStartDate()) + " - " + Utils.formatDate(range.getEndDate());
}
else {
titleEnd = " - " + Utils.formatDate(new Date());
}
}
graphDef.setTitle(titleStart + titleEnd);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy