apply new code formatter and save action

This commit is contained in:
2019-11-24 10:20:43 +01:00
parent 5ea82c6a4c
commit 06b379494f
184 changed files with 13455 additions and 13489 deletions

View File

@@ -1,17 +1,18 @@
package org.lucares.pdb.plot.api;
/**
* Note: The order in this enum defines the order in which the aggregates are drawn.
* Note: The order in this enum defines the order in which the aggregates are
* drawn.
*/
public enum Aggregate {
PARALLEL,
SCATTER,
/**
* Empirical cumulative distribution functions
*
* @see https://serialmentor.com/dataviz/ecdf-qq.html
*/
CUM_DISTRIBUTION,
PARALLEL,
SCATTER,
/**
* Empirical cumulative distribution functions
*
* @see https://serialmentor.com/dataviz/ecdf-qq.html
*/
CUM_DISTRIBUTION,
}

View File

@@ -13,52 +13,53 @@ import org.lucares.recommind.logs.LineStyle;
public abstract class AggregateHandler implements Appender {
private GnuplotAxis xAxis = GnuplotAxis.X1;
private GnuplotAxis yAxis = GnuplotAxis.Y1;
public GnuplotAxis getxAxis() {
return xAxis;
}
private GnuplotAxis xAxis = GnuplotAxis.X1;
public void updateAxis(GnuplotAxis axis) {
switch (axis) {
case X1:
case X2:
this.xAxis = axis;
break;
case Y1:
case Y2:
this.yAxis = axis;
break;
default:
throw new IllegalArgumentException("Unexpected value: " + axis);
private GnuplotAxis yAxis = GnuplotAxis.Y1;
public GnuplotAxis getxAxis() {
return xAxis;
}
}
public GnuplotAxis getyAxis() {
return yAxis;
}
public void updateAxis(final GnuplotAxis axis) {
switch (axis) {
case X1:
case X2:
this.xAxis = axis;
break;
case Y1:
case Y2:
this.yAxis = axis;
break;
default:
throw new IllegalArgumentException("Unexpected value: " + axis);
}
}
protected String gnuplotXYAxis() {
return xAxis.getAxisNameForPlots()+yAxis.getAxisNameForPlots();
}
abstract Type getAxisType(GnuplotAxis axis);
public GnuplotAxis getyAxis() {
return yAxis;
}
abstract Aggregate getAggregateType();
protected String gnuplotXYAxis() {
return xAxis.getAxisNameForPlots() + yAxis.getAxisNameForPlots();
}
abstract AxisSettings createXAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries);
abstract Type getAxisType(GnuplotAxis axis);
abstract AxisSettings createYAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries);
abstract Aggregate getAggregateType();
abstract void addPlot(StringBuilder result, AggregatedData aggregatedData, LineStyle lineStyle, Optional<String> title);
abstract AxisSettings createXAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries);
abstract CustomAggregator createCustomAggregator(Path tmpDir, PlotSettings plotSettings, long fromEpochMilli,
long toEpochMilli);
abstract AxisSettings createYAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries);
protected String gnuplotTitle(Optional<String> title) {
return title.isPresent() ? "title '" + title.get() + "'" : "notitle";
}
abstract void addPlot(StringBuilder result, AggregatedData aggregatedData, LineStyle lineStyle,
Optional<String> title);
abstract CustomAggregator createCustomAggregator(Path tmpDir, PlotSettings plotSettings, long fromEpochMilli,
long toEpochMilli);
protected String gnuplotTitle(final Optional<String> title) {
return title.isPresent() ? "title '" + title.get() + "'" : "notitle";
}
}

View File

@@ -17,91 +17,97 @@ import org.lucares.utils.CollectionUtils;
import org.lucares.utils.Preconditions;
public class AggregateHandlerCollection {
private static final Comparator<AggregateHandler> PLOTTING_ORDER = Comparator.comparing(AggregateHandler::getAggregateType);
private final List<AggregateHandler> aggregateHandlers = new ArrayList<>();
private static final Comparator<AggregateHandler> PLOTTING_ORDER = Comparator
.comparing(AggregateHandler::getAggregateType);
public void add(AggregateHandler aggregateHandler) {
aggregateHandlers.add(aggregateHandler);
}
public void updateAxisForHandlers() {
updateAxisForHandlers(GnuplotAxis.X1);
updateAxisForHandlers(GnuplotAxis.Y1);
}
private final List<AggregateHandler> aggregateHandlers = new ArrayList<>();
private void updateAxisForHandlers(GnuplotAxis axis) {
final EnumSet<Type> result = EnumSet.noneOf(Type.class);
for (AggregateHandler handler : aggregateHandlers) {
final Type type = handler.getAxisType(axis);
if (result.isEmpty()) {
result.add(type);
}else {
final boolean containsType = result.contains(type);
if (containsType) {
// already has an axis of this type
// TODO merge axis definitions and use the greater values for: range, ticsIncrement
} else{
Preconditions.checkSmaller(result.size(), 2, "at most two different axis are supported");
final GnuplotAxis mirrorAxis = axis.mirrorAxis();
handler.updateAxis(mirrorAxis);
result.add(type);
public void add(final AggregateHandler aggregateHandler) {
aggregateHandlers.add(aggregateHandler);
}
public void updateAxisForHandlers() {
updateAxisForHandlers(GnuplotAxis.X1);
updateAxisForHandlers(GnuplotAxis.Y1);
}
private void updateAxisForHandlers(final GnuplotAxis axis) {
final EnumSet<Type> result = EnumSet.noneOf(Type.class);
for (final AggregateHandler handler : aggregateHandlers) {
final Type type = handler.getAxisType(axis);
if (result.isEmpty()) {
result.add(type);
} else {
final boolean containsType = result.contains(type);
if (containsType) {
// already has an axis of this type
// TODO merge axis definitions and use the greater values for: range,
// ticsIncrement
} else {
Preconditions.checkSmaller(result.size(), 2, "at most two different axis are supported");
final GnuplotAxis mirrorAxis = axis.mirrorAxis();
handler.updateAxis(mirrorAxis);
result.add(type);
}
}
}
}
}
}
public List<AxisSettings> getXAxisDefinitions(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
final List<AxisSettings> result = new ArrayList<>();
for (AggregateHandler handler : aggregateHandlers) {
AxisSettings axis = handler.createXAxisSettings(settings, dataSeries);
result.add(axis);
}
return result;
}
public List<AxisSettings> getYAxisDefinitions(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
List<AxisSettings> result = new ArrayList<>();
for (AggregateHandler handler : aggregateHandlers) {
final AxisSettings axis = handler.createYAxisSettings(settings, dataSeries);
result.add(axis);
}
return result;
}
public AggregatorCollection createCustomAggregator(Path tmpDir, PlotSettings plotSettings, long fromEpochMilli,
long toEpochMilli) {
final List<CustomAggregator> aggregators = new ArrayList<>();
for (AggregateHandler handler : aggregateHandlers) {
final CustomAggregator aggregator = handler.createCustomAggregator(tmpDir, plotSettings, fromEpochMilli, toEpochMilli);
if (aggregator != null) {
aggregators.add(aggregator);
}
}
return new AggregatorCollection(aggregators);
}
public void addPlots(StringBuilder result, Collection<DataSeries> dataSeries) {
boolean first = true;
final List<AggregateHandler> handlersInPlottingOrder = CollectionUtils.copySort(aggregateHandlers, PLOTTING_ORDER);
for (AggregateHandler handler : handlersInPlottingOrder) {
for (DataSeries dataSerie : dataSeries) {
final Optional<String> title = first ? Optional.of(dataSerie.getTitle()) : Optional.empty();
Optional<AggregatedData> aggregatedData = dataSerie.getAggregatedData().get(handler.getAggregateType());
if(aggregatedData.isPresent()) {
handler.addPlot(result, aggregatedData.get(), dataSerie.getStyle(), title);
public List<AxisSettings> getXAxisDefinitions(final GnuplotSettings settings,
final Collection<DataSeries> dataSeries) {
final List<AxisSettings> result = new ArrayList<>();
for (final AggregateHandler handler : aggregateHandlers) {
final AxisSettings axis = handler.createXAxisSettings(settings, dataSeries);
result.add(axis);
}
return result;
}
public List<AxisSettings> getYAxisDefinitions(final GnuplotSettings settings,
final Collection<DataSeries> dataSeries) {
final List<AxisSettings> result = new ArrayList<>();
for (final AggregateHandler handler : aggregateHandlers) {
final AxisSettings axis = handler.createYAxisSettings(settings, dataSeries);
result.add(axis);
}
return result;
}
public AggregatorCollection createCustomAggregator(final Path tmpDir, final PlotSettings plotSettings,
final long fromEpochMilli, final long toEpochMilli) {
final List<CustomAggregator> aggregators = new ArrayList<>();
for (final AggregateHandler handler : aggregateHandlers) {
final CustomAggregator aggregator = handler.createCustomAggregator(tmpDir, plotSettings, fromEpochMilli,
toEpochMilli);
if (aggregator != null) {
aggregators.add(aggregator);
}
}
return new AggregatorCollection(aggregators);
}
public void addPlots(final StringBuilder result, final Collection<DataSeries> dataSeries) {
boolean first = true;
final List<AggregateHandler> handlersInPlottingOrder = CollectionUtils.copySort(aggregateHandlers,
PLOTTING_ORDER);
for (final AggregateHandler handler : handlersInPlottingOrder) {
for (final DataSeries dataSerie : dataSeries) {
final Optional<String> title = first ? Optional.of(dataSerie.getTitle()) : Optional.empty();
final Optional<AggregatedData> aggregatedData = dataSerie.getAggregatedData()
.get(handler.getAggregateType());
if (aggregatedData.isPresent()) {
handler.addPlot(result, aggregatedData.get(), dataSerie.getStyle(), title);
}
}
first = false;
}
}
first = false;
}
}
}

View File

@@ -3,19 +3,19 @@ package org.lucares.pdb.plot.api;
import java.io.File;
public class AggregatedData {
private final String label;
private final File dataFile;
private final String label;
private final File dataFile;
public AggregatedData(final String label, final File dataFile) {
this.label = label;
this.dataFile = dataFile;
}
public AggregatedData(final String label, final File dataFile) {
this.label = label;
this.dataFile = dataFile;
}
public String getLabel() {
return label;
}
public String getLabel() {
return label;
}
public File getDataFile() {
return dataFile;
}
public File getDataFile() {
return dataFile;
}
}

View File

@@ -4,19 +4,19 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Optional;
public class AggregatedDataCollection implements Iterable<AggregatedData>{
private final LinkedHashMap<Aggregate, AggregatedData> aggregatedDatas = new LinkedHashMap<>();
public class AggregatedDataCollection implements Iterable<AggregatedData> {
private final LinkedHashMap<Aggregate, AggregatedData> aggregatedDatas = new LinkedHashMap<>();
public void put(Aggregate aggregate, AggregatedData aggregatedData) {
aggregatedDatas.put(aggregate, aggregatedData);
}
public void put(final Aggregate aggregate, final AggregatedData aggregatedData) {
aggregatedDatas.put(aggregate, aggregatedData);
}
@Override
public Iterator<AggregatedData> iterator() {
return aggregatedDatas.values().iterator();
}
@Override
public Iterator<AggregatedData> iterator() {
return aggregatedDatas.values().iterator();
}
public Optional<AggregatedData> get(Aggregate aggregateType) {
return Optional.ofNullable(aggregatedDatas.get(aggregateType));
}
public Optional<AggregatedData> get(final Aggregate aggregateType) {
return Optional.ofNullable(aggregatedDatas.get(aggregateType));
}
}

View File

@@ -1,18 +1,21 @@
package org.lucares.pdb.plot.api;
public class AggregatedDataEntry {
private final double epochSeconds;
private final long value;
public AggregatedDataEntry(double epochSeconds, long value) {
super();
this.epochSeconds = epochSeconds;
this.value = value;
}
public double getEpochSeconds() {
return epochSeconds;
}
public long getValue() {
return value;
}
private final double epochSeconds;
private final long value;
public AggregatedDataEntry(final double epochSeconds, final long value) {
super();
this.epochSeconds = epochSeconds;
this.value = value;
}
public double getEpochSeconds() {
return epochSeconds;
}
public long getValue() {
return value;
}
}

View File

@@ -4,26 +4,26 @@ import java.io.IOException;
import java.util.List;
public class AggregatorCollection {
private final List<CustomAggregator> aggregators;
private final List<CustomAggregator> aggregators;
public AggregatorCollection(List<CustomAggregator> aggregators) {
this.aggregators = aggregators;
}
public void addValue(boolean valueIsInYRange, long epochMilli, long value) {
for (CustomAggregator aggregator : aggregators) {
aggregator.addValue(valueIsInYRange, epochMilli, value);
public AggregatorCollection(final List<CustomAggregator> aggregators) {
this.aggregators = aggregators;
}
}
public AggregatedDataCollection getAggregatedData() throws IOException {
AggregatedDataCollection result = new AggregatedDataCollection();
for (CustomAggregator aggregator : aggregators) {
result.put(aggregator.getType(), aggregator.getAggregatedData());
public void addValue(final boolean valueIsInYRange, final long epochMilli, final long value) {
for (final CustomAggregator aggregator : aggregators) {
aggregator.addValue(valueIsInYRange, epochMilli, value);
}
}
public AggregatedDataCollection getAggregatedData() throws IOException {
final AggregatedDataCollection result = new AggregatedDataCollection();
for (final CustomAggregator aggregator : aggregators) {
result.put(aggregator.getType(), aggregator.getAggregatedData());
}
return result;
}
return result;
}
}

View File

@@ -3,15 +3,15 @@ package org.lucares.pdb.plot.api;
import java.util.Locale;
public interface Appender {
default void appendln(final StringBuilder builder, final String string) {
builder.append(string + "\n");
}
default void appendln(final StringBuilder builder, final String string) {
builder.append(string + "\n");
}
default void appendfln(final StringBuilder builder, final String format, final Object... args) {
builder.append(String.format(Locale.US,format + "\n", args));
}
default void appendfln(final StringBuilder builder, final String format, final Object... args) {
builder.append(String.format(Locale.US, format + "\n", args));
}
default void appendf(final StringBuilder builder, final String format, final Object... args) {
builder.append(String.format(Locale.US,format, args));
}
default void appendf(final StringBuilder builder, final String format, final Object... args) {
builder.append(String.format(Locale.US, format, args));
}
}

View File

@@ -1,5 +1,5 @@
package org.lucares.pdb.plot.api;
public enum AxisScale {
LINEAR, LOG10
LINEAR, LOG10
}

View File

@@ -15,114 +15,114 @@ import org.lucares.collections.LongLongHashMap;
public class CumulativeDistributionCustomAggregator implements CustomAggregator {
private final static int POINTS = 500;
private final static int POINTS = 500;
private static final class ToPercentiles implements LongLongConsumer {
private static final class ToPercentiles implements LongLongConsumer {
private long cumulativeCount = 0;
private long cumulativeCount = 0;
private long maxValue = 0;
private long maxValue = 0;
private final LinkedHashMap<Double, Long> percentiles = new LinkedHashMap<>(POINTS);
private final LinkedHashMap<Double, Long> percentiles = new LinkedHashMap<>(POINTS);
private final double stepSize;
private final double stepSize;
private double lastPercentile;
private double nextPercentile;
private double lastPercentile;
private double nextPercentile;
private final long totalValues;
private final long totalValues;
public ToPercentiles(final long totalValues) {
this.totalValues = totalValues;
stepSize = 100.0 / POINTS;
nextPercentile = stepSize;
}
public ToPercentiles(final long totalValues) {
this.totalValues = totalValues;
stepSize = 100.0 / POINTS;
nextPercentile = stepSize;
}
@Override
public void accept(final long duration, final long count) {
maxValue = duration;
@Override
public void accept(final long duration, final long count) {
maxValue = duration;
cumulativeCount += count;
final double newPercentile = cumulativeCount * 100.0 / totalValues;
cumulativeCount += count;
final double newPercentile = cumulativeCount * 100.0 / totalValues;
if (newPercentile >= nextPercentile) {
double currentPercentile = lastPercentile + stepSize;
while (currentPercentile <= newPercentile) {
percentiles.put(currentPercentile, duration);
currentPercentile += stepSize;
}
nextPercentile = currentPercentile;
lastPercentile = currentPercentile - stepSize;
}
}
if (newPercentile >= nextPercentile) {
double currentPercentile = lastPercentile + stepSize;
while (currentPercentile <= newPercentile) {
percentiles.put(currentPercentile, duration);
currentPercentile += stepSize;
}
nextPercentile = currentPercentile;
lastPercentile = currentPercentile - stepSize;
}
}
public long getMaxValue() {
return maxValue;
}
public long getMaxValue() {
return maxValue;
}
public LinkedHashMap<Double, Long> getPercentiles() {
return percentiles;
}
public LinkedHashMap<Double, Long> getPercentiles() {
return percentiles;
}
}
}
// the rather large initial capacity should prevent too many grow&re-hash phases
private final LongLongHashMap map = new LongLongHashMap(5_000, 0.75);
// the rather large initial capacity should prevent too many grow&re-hash phases
private final LongLongHashMap map = new LongLongHashMap(5_000, 0.75);
private long totalValues = 0;
private long totalValues = 0;
private final Path tmpDir;
private final Path tmpDir;
public CumulativeDistributionCustomAggregator(final Path tmpDir) {
this.tmpDir = tmpDir;
}
public CumulativeDistributionCustomAggregator(final Path tmpDir) {
this.tmpDir = tmpDir;
}
@Override
public void addValue(boolean valueIsInYRange, final long epochMilli, final long value) {
map.compute(value, 0, l -> l + 1);
totalValues++;
}
@Override
public void addValue(boolean valueIsInYRange, final long epochMilli, final long value) {
map.compute(value, 0, l -> l + 1);
totalValues++;
}
@Override
public AggregatedData getAggregatedData() throws IOException {
final char separator = ',';
final char newline = '\n';
@Override
public AggregatedData getAggregatedData() throws IOException {
final char separator = ',';
final char newline = '\n';
final ToPercentiles toPercentiles = new ToPercentiles(totalValues);
map.forEachOrdered(toPercentiles);
final ToPercentiles toPercentiles = new ToPercentiles(totalValues);
map.forEachOrdered(toPercentiles);
final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile());
try (final Writer output = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.US_ASCII));) {
final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile());
try (final Writer output = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.US_ASCII));) {
final StringBuilder data = new StringBuilder();
if (map.size() > 0) {
// compute the percentiles
toPercentiles.getPercentiles().forEach((percentile, value) -> {
final StringBuilder data = new StringBuilder();
if (map.size() > 0) {
// compute the percentiles
toPercentiles.getPercentiles().forEach((percentile, value) -> {
data.append(percentile);
data.append(separator);
data.append(value);
data.append(newline);
});
data.append(percentile);
data.append(separator);
data.append(value);
data.append(newline);
});
final long maxValue = toPercentiles.getMaxValue();
data.append(100);
data.append(separator);
data.append(maxValue);
data.append(newline);
}
output.write(data.toString());
final long maxValue = toPercentiles.getMaxValue();
data.append(100);
data.append(separator);
data.append(maxValue);
data.append(newline);
}
output.write(data.toString());
}
}
final String title = String.format("cumulative distribution");
return new AggregatedData(title, dataFile);
}
final String title = String.format("cumulative distribution");
return new AggregatedData(title, dataFile);
}
@Override
public Aggregate getType() {
return Aggregate.CUM_DISTRIBUTION;
}
@Override
public Aggregate getType() {
return Aggregate.CUM_DISTRIBUTION;
}
}

View File

@@ -14,74 +14,74 @@ import org.lucares.recommind.logs.AxisSettings.Type;
public class CumulativeDistributionHandler extends AggregateHandler {
@Override
public CustomAggregator createCustomAggregator(final Path tmpDir, PlotSettings plotSettings,
final long fromEpochMilli, final long toEpochMilli) {
return new CumulativeDistributionCustomAggregator(tmpDir);
}
public CumulativeDistributionHandler() {
}
@Override
Type getAxisType(GnuplotAxis axis) {
switch (axis) {
case X1:
case X2:
return Type.Percent;
case Y1:
case Y2:
return Type.Duration;
default:
throw new IllegalArgumentException("Unexpected value: " + axis);
@Override
public CustomAggregator createCustomAggregator(final Path tmpDir, PlotSettings plotSettings,
final long fromEpochMilli, final long toEpochMilli) {
return new CumulativeDistributionCustomAggregator(tmpDir);
}
}
@Override
public AxisSettings createYAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
AxisSettings result = AxisTime.createYAxis(settings, dataSeries);
result.setAxis(getyAxis());
return result;
}
@Override
public AxisSettings createXAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
AxisSettings result = new AxisSettings();
result.setLabel("Cumulative Distribution");
result.setType(Type.Percent);
result.setAxis(getxAxis());
result.setFormat("%.0f%%");
result.setTicIncrement(computeTicIncrement(settings));
result.setFrom("0");
result.setTo("100");
return result;
}
private int computeTicIncrement(GnuplotSettings settings) {
int widthByFontSize = settings.getWidth() / GnuplotSettings.TICKS_FONT_SIZE;
if (widthByFontSize < 50) {
return 20;
} else if (widthByFontSize < 75) {
return 10;
} else {
return 5;
public CumulativeDistributionHandler() {
}
}
@Override
public void addPlot(StringBuilder result, AggregatedData aggregatedData, LineStyle lineStyle,
Optional<String> title) {
appendfln(result, "'%s' using 1:2 %s with lines axes %s lw 2 %s, \\", //
aggregatedData.getDataFile().getAbsolutePath(), //
gnuplotTitle(title), //
gnuplotXYAxis(), //
lineStyle.darker()//
);
}
@Override
Type getAxisType(GnuplotAxis axis) {
switch (axis) {
case X1:
case X2:
return Type.Percent;
case Y1:
case Y2:
return Type.Duration;
default:
throw new IllegalArgumentException("Unexpected value: " + axis);
}
}
@Override
public Aggregate getAggregateType() {
return Aggregate.CUM_DISTRIBUTION;
}
@Override
public AxisSettings createYAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
AxisSettings result = AxisTime.createYAxis(settings, dataSeries);
result.setAxis(getyAxis());
return result;
}
@Override
public AxisSettings createXAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
AxisSettings result = new AxisSettings();
result.setLabel("Cumulative Distribution");
result.setType(Type.Percent);
result.setAxis(getxAxis());
result.setFormat("%.0f%%");
result.setTicIncrement(computeTicIncrement(settings));
result.setFrom("0");
result.setTo("100");
return result;
}
private int computeTicIncrement(GnuplotSettings settings) {
int widthByFontSize = settings.getWidth() / GnuplotSettings.TICKS_FONT_SIZE;
if (widthByFontSize < 50) {
return 20;
} else if (widthByFontSize < 75) {
return 10;
} else {
return 5;
}
}
@Override
public void addPlot(StringBuilder result, AggregatedData aggregatedData, LineStyle lineStyle,
Optional<String> title) {
appendfln(result, "'%s' using 1:2 %s with lines axes %s lw 2 %s, \\", //
aggregatedData.getDataFile().getAbsolutePath(), //
gnuplotTitle(title), //
gnuplotXYAxis(), //
lineStyle.darker()//
);
}
@Override
public Aggregate getAggregateType() {
return Aggregate.CUM_DISTRIBUTION;
}
}

View File

@@ -4,9 +4,9 @@ import java.io.IOException;
public interface CustomAggregator {
void addValue(boolean valueIsInYRange, long epochMilli, long value);
void addValue(boolean valueIsInYRange, long epochMilli, long value);
AggregatedData getAggregatedData() throws IOException;
AggregatedData getAggregatedData() throws IOException;
Aggregate getType();
Aggregate getType();
}

View File

@@ -1,5 +1,5 @@
package org.lucares.pdb.plot.api;
public enum Limit {
NO_LIMIT, MOST_VALUES, FEWEST_VALUES, MAX_VALUE, MIN_VALUE
NO_LIMIT, MOST_VALUES, FEWEST_VALUES, MAX_VALUE, MIN_VALUE
}

View File

@@ -15,61 +15,61 @@ import org.lucares.recommind.logs.DataSeries;
public class ParallelRequestsAggregate extends AggregateHandler {
@Override
Type getAxisType(GnuplotAxis axis) {
switch (axis) {
case X1:
case X2:
return Type.Time;
case Y1:
case Y2:
return Type.Number;
default:
throw new IllegalArgumentException("Unexpected value: " + axis);
@Override
Type getAxisType(GnuplotAxis axis) {
switch (axis) {
case X1:
case X2:
return Type.Time;
case Y1:
case Y2:
return Type.Number;
default:
throw new IllegalArgumentException("Unexpected value: " + axis);
}
}
}
@Override
public AxisSettings createYAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
final AxisSettings result = new AxisSettings();
result.setLabel("Parallel Requests");
result.setType(Type.Number);
result.setAxis(getyAxis());
result.setTicsEnabled(true);
result.setFrom("0");
return result;
}
@Override
public AxisSettings createXAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
final AxisSettings result = AxisTime.createXAxis(settings);
result.setAxis(getxAxis());
return result;
}
@Override
public void addPlot(StringBuilder result, AggregatedData aggregatedData, LineStyle lineStyle,
Optional<String> title) {
appendfln(result, "'%s' using 1:2 %s with filledcurve axes %s lw 1 %s, \\", //
aggregatedData.getDataFile().getAbsolutePath(), //
gnuplotTitle(title), //
gnuplotXYAxis(), //
lineStyle.brighter().asGnuplotLineStyle()//
);
}
@Override
public CustomAggregator createCustomAggregator(final Path tmpDir, PlotSettings plotSettings,
final long fromEpochMilli, final long toEpochMilli) {
if ((toEpochMilli - fromEpochMilli) <= TimeUnit.HOURS.toMillis(50)) {
return new ParallelRequestsAggregator(tmpDir, fromEpochMilli, toEpochMilli);
} else {
return null;
@Override
public AxisSettings createYAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
final AxisSettings result = new AxisSettings();
result.setLabel("Parallel Requests");
result.setType(Type.Number);
result.setAxis(getyAxis());
result.setTicsEnabled(true);
result.setFrom("0");
return result;
}
}
@Override
public Aggregate getAggregateType() {
return Aggregate.PARALLEL;
}
@Override
public AxisSettings createXAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
final AxisSettings result = AxisTime.createXAxis(settings);
result.setAxis(getxAxis());
return result;
}
@Override
public void addPlot(StringBuilder result, AggregatedData aggregatedData, LineStyle lineStyle,
Optional<String> title) {
appendfln(result, "'%s' using 1:2 %s with filledcurve axes %s lw 1 %s, \\", //
aggregatedData.getDataFile().getAbsolutePath(), //
gnuplotTitle(title), //
gnuplotXYAxis(), //
lineStyle.brighter().asGnuplotLineStyle()//
);
}
@Override
public CustomAggregator createCustomAggregator(final Path tmpDir, PlotSettings plotSettings,
final long fromEpochMilli, final long toEpochMilli) {
if ((toEpochMilli - fromEpochMilli) <= TimeUnit.HOURS.toMillis(50)) {
return new ParallelRequestsAggregator(tmpDir, fromEpochMilli, toEpochMilli);
} else {
return null;
}
}
@Override
public Aggregate getAggregateType() {
return Aggregate.PARALLEL;
}
}

View File

@@ -15,88 +15,88 @@ import org.slf4j.LoggerFactory;
public class ParallelRequestsAggregator implements CustomAggregator {
private static final char NEWLINE = '\n';
private static final char NEWLINE = '\n';
private static final char SEPARATOR = ',';
private static final char SEPARATOR = ',';
private static final Logger METRICS_LOGGER = LoggerFactory
.getLogger("org.lucares.metrics.aggregator.parallelRequests");
private static final Logger METRICS_LOGGER = LoggerFactory
.getLogger("org.lucares.metrics.aggregator.parallelRequests");
private final Path tmpDir;
private final Path tmpDir;
private final short[] increments;
private final short[] increments;
private final long fromEpochMilli;
private final long fromEpochMilli;
private final long toEpochMilli;
private final long toEpochMilli;
public ParallelRequestsAggregator(final Path tmpDir, final long fromEpochMilli, final long toEpochMilli) {
this.tmpDir = tmpDir;
this.fromEpochMilli = fromEpochMilli;
this.toEpochMilli = toEpochMilli;
public ParallelRequestsAggregator(final Path tmpDir, final long fromEpochMilli, final long toEpochMilli) {
this.tmpDir = tmpDir;
this.fromEpochMilli = fromEpochMilli;
this.toEpochMilli = toEpochMilli;
final int milliseconds = (int) (toEpochMilli - fromEpochMilli);
increments = new short[milliseconds+1];
}
final int milliseconds = (int) (toEpochMilli - fromEpochMilli);
increments = new short[milliseconds + 1];
}
@Override
public void addValue(boolean valueIsInYRange,final long epochMilli, final long value) {
@Override
public void addValue(boolean valueIsInYRange, final long epochMilli, final long value) {
final int endPos = (int) (epochMilli - fromEpochMilli);
increments[endPos]--;
final int endPos = (int) (epochMilli - fromEpochMilli);
increments[endPos]--;
final int startPos = Math.max(0, (int) (endPos - value));
increments[startPos]++;
final int startPos = Math.max(0, (int) (endPos - value));
increments[startPos]++;
}
}
@Override
public AggregatedData getAggregatedData() throws IOException {
@Override
public AggregatedData getAggregatedData() throws IOException {
final long start = System.nanoTime();
final long start = System.nanoTime();
final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile());
try (final Writer output = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.US_ASCII));) {
final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile());
try (final Writer output = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.US_ASCII));) {
final StringBuilder data = new StringBuilder();
final StringBuilder data = new StringBuilder();
// first and last value should be 0, or gnuplot will draw a diagonal line
appendTimeAndValue(data, fromEpochMilli, 0);
// first and last value should be 0, or gnuplot will draw a diagonal line
appendTimeAndValue(data, fromEpochMilli, 0);
int value = 0;
for (int i = 0; i < increments.length - 1; i++) {
final int increment = increments[i];
final int nextIncrement = increments[i + 1];
if (increment != 0 || nextIncrement != 0) {
value += increment;
appendTimeAndValue(data, fromEpochMilli + i, value);
}
}
int value = 0;
for (int i = 0; i < increments.length - 1; i++) {
final int increment = increments[i];
final int nextIncrement = increments[i + 1];
if (increment != 0 || nextIncrement != 0) {
value += increment;
appendTimeAndValue(data, fromEpochMilli + i, value);
}
}
// first and last value should be 0, or gnuplot will draw a diagonal line
appendTimeAndValue(data, toEpochMilli, 0);
// first and last value should be 0, or gnuplot will draw a diagonal line
appendTimeAndValue(data, toEpochMilli, 0);
output.write(data.toString());
output.write(data.toString());
}
}
final String title = String.format("parallelRequests");
METRICS_LOGGER.debug("wrote parallelRequests csv in: {}ms file={}", (System.nanoTime() - start) / 1_000_000.0,
dataFile);
return new AggregatedData(title, dataFile);
}
final String title = String.format("parallelRequests");
METRICS_LOGGER.debug("wrote parallelRequests csv in: {}ms file={}", (System.nanoTime() - start) / 1_000_000.0,
dataFile);
return new AggregatedData(title, dataFile);
}
private void appendTimeAndValue(final StringBuilder builder, final long timeEpochMilli, final int value) {
builder.append(String.format(Locale.US, "%.3f", timeEpochMilli / 1000.0));
builder.append(SEPARATOR);
builder.append(value);
builder.append(NEWLINE);
}
private void appendTimeAndValue(final StringBuilder builder, final long timeEpochMilli, final int value) {
builder.append(String.format(Locale.US, "%.3f", timeEpochMilli / 1000.0));
builder.append(SEPARATOR);
builder.append(value);
builder.append(NEWLINE);
}
@Override
public Aggregate getType() {
return Aggregate.PARALLEL;
}
@Override
public Aggregate getType() {
return Aggregate.PARALLEL;
}
}

View File

@@ -12,186 +12,186 @@ import org.lucares.utils.Preconditions;
public class PlotSettings {
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private String query;
private String query;
private int height;
private int height;
private int width;
private int width;
private int thumbnailMaxWidth = 0;
private int thumbnailMaxWidth = 0;
private int thumbnailMaxHeight = 0;
private int thumbnailMaxHeight = 0;
private List<String> groupBy;
private List<String> groupBy;
private Limit limitBy;
private Limit limitBy;
private int limit;
private int limit;
private String dateRangeAsString;
private String dateRangeAsString;
private AxisScale yAxisScale;
private AxisScale yAxisScale;
private AggregateHandlerCollection aggregates;
private AggregateHandlerCollection aggregates;
private int yRangeMin;
private int yRangeMax;
private TimeRangeUnitInternal yRangeUnit = TimeRangeUnitInternal.AUTOMATIC;
private int yRangeMin;
private int yRangeMax;
private TimeRangeUnitInternal yRangeUnit = TimeRangeUnitInternal.AUTOMATIC;
private boolean keyOutside;
private boolean keyOutside;
private boolean generateThumbnail;
private boolean generateThumbnail;
public String getQuery() {
return query;
}
public String getQuery() {
return query;
}
public void setQuery(final String query) {
this.query = query;
}
public void setQuery(final String query) {
this.query = query;
}
public int getHeight() {
return height;
}
public int getHeight() {
return height;
}
public void setHeight(final int height) {
this.height = height;
}
public void setHeight(final int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public int getWidth() {
return width;
}
public void setWidth(final int width) {
this.width = width;
}
public void setWidth(final int width) {
this.width = width;
}
public int getThumbnailMaxWidth() {
return thumbnailMaxWidth;
}
public int getThumbnailMaxWidth() {
return thumbnailMaxWidth;
}
public void setThumbnailMaxWidth(final int thumbnailMaxWidth) {
this.thumbnailMaxWidth = thumbnailMaxWidth;
}
public void setThumbnailMaxWidth(final int thumbnailMaxWidth) {
this.thumbnailMaxWidth = thumbnailMaxWidth;
}
public int getThumbnailMaxHeight() {
return thumbnailMaxHeight;
}
public int getThumbnailMaxHeight() {
return thumbnailMaxHeight;
}
public void setThumbnailMaxHeight(final int thumbnailMaxHeight) {
this.thumbnailMaxHeight = thumbnailMaxHeight;
}
public void setThumbnailMaxHeight(final int thumbnailMaxHeight) {
this.thumbnailMaxHeight = thumbnailMaxHeight;
}
public List<String> getGroupBy() {
return groupBy;
}
public List<String> getGroupBy() {
return groupBy;
}
public void setGroupBy(final List<String> groupBy) {
this.groupBy = groupBy;
}
public void setGroupBy(final List<String> groupBy) {
this.groupBy = groupBy;
}
public Limit getLimitBy() {
return limitBy;
}
public Limit getLimitBy() {
return limitBy;
}
public void setLimitBy(final Limit limitBy) {
this.limitBy = limitBy;
}
public void setLimitBy(final Limit limitBy) {
this.limitBy = limitBy;
}
public int getLimit() {
return limit;
}
public int getLimit() {
return limit;
}
public void setLimit(final int limit) {
this.limit = limit;
}
public void setLimit(final int limit) {
this.limit = limit;
}
public String getDateRange() {
return dateRangeAsString;
}
public String getDateRange() {
return dateRangeAsString;
}
public void setDateRange(final String dateRangeAsString) {
this.dateRangeAsString = dateRangeAsString;
}
public void setDateRange(final String dateRangeAsString) {
this.dateRangeAsString = dateRangeAsString;
}
public DateTimeRange dateRange() {
public DateTimeRange dateRange() {
final String[] startEnd = dateRangeAsString.split(Pattern.quote(" - "));
Preconditions.checkEqual(startEnd.length, 2, "invalid date range: ''{0}''", dateRangeAsString);
final String[] startEnd = dateRangeAsString.split(Pattern.quote(" - "));
Preconditions.checkEqual(startEnd.length, 2, "invalid date range: ''{0}''", dateRangeAsString);
final OffsetDateTime startDate = LocalDateTime.parse(startEnd[0], DATE_FORMAT).atOffset(ZoneOffset.UTC);
final OffsetDateTime endDate = LocalDateTime.parse(startEnd[1], DATE_FORMAT).atOffset(ZoneOffset.UTC);
final OffsetDateTime startDate = LocalDateTime.parse(startEnd[0], DATE_FORMAT).atOffset(ZoneOffset.UTC);
final OffsetDateTime endDate = LocalDateTime.parse(startEnd[1], DATE_FORMAT).atOffset(ZoneOffset.UTC);
return new DateTimeRange(startDate, endDate);
return new DateTimeRange(startDate, endDate);
}
}
public void setYAxisScale(final AxisScale axisScale) {
this.yAxisScale = axisScale;
}
public void setYAxisScale(final AxisScale axisScale) {
this.yAxisScale = axisScale;
}
public AxisScale getYAxisScale() {
return yAxisScale;
}
public AxisScale getYAxisScale() {
return yAxisScale;
}
@Override
public String toString() {
return "PlotSettings [query=" + query + ", height=" + height + ", width=" + width + ", thumbnailMaxWidth="
+ thumbnailMaxWidth + ", thumbnailMaxHeight=" + thumbnailMaxHeight + ", groupBy=" + groupBy
+ ", limitBy=" + limitBy + ", limit=" + limit + ", dateRangeAsString=" + dateRangeAsString
+ ", yAxisScale=" + yAxisScale + ", aggregates=" + aggregates + ", yRangeMin=" + yRangeMin
+ ", yRangeMax=" + yRangeMax + ", yRangeUnit=" + yRangeUnit + ", keyOutside=" + keyOutside
+ ", generateThumbnail=" + generateThumbnail + "]";
}
@Override
public String toString() {
return "PlotSettings [query=" + query + ", height=" + height + ", width=" + width + ", thumbnailMaxWidth="
+ thumbnailMaxWidth + ", thumbnailMaxHeight=" + thumbnailMaxHeight + ", groupBy=" + groupBy
+ ", limitBy=" + limitBy + ", limit=" + limit + ", dateRangeAsString=" + dateRangeAsString
+ ", yAxisScale=" + yAxisScale + ", aggregates=" + aggregates + ", yRangeMin=" + yRangeMin
+ ", yRangeMax=" + yRangeMax + ", yRangeUnit=" + yRangeUnit + ", keyOutside=" + keyOutside
+ ", generateThumbnail=" + generateThumbnail + "]";
}
public void setAggregates(final AggregateHandlerCollection aggregates) {
this.aggregates = aggregates;
}
public void setAggregates(final AggregateHandlerCollection aggregates) {
this.aggregates = aggregates;
}
public AggregateHandlerCollection getAggregates() {
return aggregates;
}
public AggregateHandlerCollection getAggregates() {
return aggregates;
}
public void setKeyOutside(final boolean keyOutside) {
this.keyOutside = keyOutside;
}
public void setKeyOutside(final boolean keyOutside) {
this.keyOutside = keyOutside;
}
public boolean isKeyOutside() {
return keyOutside;
}
public boolean isKeyOutside() {
return keyOutside;
}
public void setGenerateThumbnail(final boolean generateThumbnail) {
this.generateThumbnail = generateThumbnail;
}
public void setGenerateThumbnail(final boolean generateThumbnail) {
this.generateThumbnail = generateThumbnail;
}
public boolean isGenerateThumbnail() {
return generateThumbnail;
}
public boolean isGenerateThumbnail() {
return generateThumbnail;
}
public int getYRangeMin() {
return yRangeMin;
}
public int getYRangeMin() {
return yRangeMin;
}
public void setYRangeMin(final int yRangeMin) {
this.yRangeMin = yRangeMin;
}
public void setYRangeMin(final int yRangeMin) {
this.yRangeMin = yRangeMin;
}
public int getYRangeMax() {
return yRangeMax;
}
public int getYRangeMax() {
return yRangeMax;
}
public void setYRangeMax(final int yRangeMax) {
this.yRangeMax = yRangeMax;
}
public void setYRangeMax(final int yRangeMax) {
this.yRangeMax = yRangeMax;
}
public TimeRangeUnitInternal getYRangeUnit() {
return yRangeUnit;
}
public TimeRangeUnitInternal getYRangeUnit() {
return yRangeUnit;
}
public void setYRangeUnit(final TimeRangeUnitInternal yRangeUnit) {
this.yRangeUnit = yRangeUnit;
}
public void setYRangeUnit(final TimeRangeUnitInternal yRangeUnit) {
this.yRangeUnit = yRangeUnit;
}
}

View File

@@ -15,56 +15,56 @@ import org.lucares.recommind.logs.AxisSettings.Type;
public class ScatterAggregateHandler extends AggregateHandler {
@Override
Type getAxisType(GnuplotAxis axis) {
switch (axis) {
case X1:
case X2:
return Type.Time;
case Y1:
case Y2:
return Type.Duration;
default:
throw new IllegalArgumentException("Unexpected value: " + axis);
@Override
Type getAxisType(GnuplotAxis axis) {
switch (axis) {
case X1:
case X2:
return Type.Time;
case Y1:
case Y2:
return Type.Duration;
default:
throw new IllegalArgumentException("Unexpected value: " + axis);
}
}
}
@Override
public AxisSettings createYAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
final AxisSettings result = AxisTime.createYAxis(settings, dataSeries);
result.setAxis(getyAxis());
return result;
}
@Override
public AxisSettings createXAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
final AxisSettings result = AxisTime.createXAxis(settings);
result.setAxis(getxAxis());
return result;
}
@Override
public AxisSettings createYAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
final AxisSettings result = AxisTime.createYAxis(settings, dataSeries);
result.setAxis(getyAxis());
return result;
}
@Override
public void addPlot(StringBuilder result, AggregatedData aggregatedData, LineStyle lineStyle,
Optional<String> title) {
@Override
public AxisSettings createXAxisSettings(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
final AxisSettings result = AxisTime.createXAxis(settings);
result.setAxis(getxAxis());
return result;
}
appendfln(result, "'%s' using 1:2 %s with %s axes %s %s, \\", //
aggregatedData.getDataFile(), //
gnuplotTitle(title), //
GnuplotLineType.Points, //
gnuplotXYAxis(),//
lineStyle//
);
}
@Override
public void addPlot(StringBuilder result, AggregatedData aggregatedData, LineStyle lineStyle,
Optional<String> title) {
@Override
public CustomAggregator createCustomAggregator(Path tmpDir, PlotSettings plotSettings, long fromEpochMilli,
long toEpochMilli) {
appendfln(result, "'%s' using 1:2 %s with %s axes %s %s, \\", //
aggregatedData.getDataFile(), //
gnuplotTitle(title), //
GnuplotLineType.Points, //
gnuplotXYAxis(), //
lineStyle//
);
}
return new ScatterAggregator(tmpDir, plotSettings, fromEpochMilli, toEpochMilli);
}
@Override
public CustomAggregator createCustomAggregator(Path tmpDir, PlotSettings plotSettings, long fromEpochMilli,
long toEpochMilli) {
@Override
public Aggregate getAggregateType() {
return Aggregate.SCATTER;
}
return new ScatterAggregator(tmpDir, plotSettings, fromEpochMilli, toEpochMilli);
}
@Override
public Aggregate getAggregateType() {
return Aggregate.SCATTER;
}
}

View File

@@ -18,83 +18,82 @@ import org.lucares.recommind.logs.LongUtils;
public class ScatterAggregator implements CustomAggregator {
private final Sparse2DLongArray matrix2d = new Sparse2DLongArray();
private final Sparse2DLongArray matrix2d = new Sparse2DLongArray();
private final boolean useMillis;
private final long plotAreaWidthInPx;
private final long plotAreaHeightInPx;
private final long epochMillisPerPixel;
private final boolean useMillis;
private final long plotAreaWidthInPx;
private final long plotAreaHeightInPx;
private final long epochMillisPerPixel;
private final long minValue;
private final long maxValue;
private final long durationMillisPerPixel;
private final long minValue;
private final long maxValue;
private final long durationMillisPerPixel;
private Path tmpDir;
private Path tmpDir;
public ScatterAggregator(Path tmpDir, PlotSettings plotSettings, long fromEpochMilli, long toEpochMilli) {
public ScatterAggregator(Path tmpDir, PlotSettings plotSettings, long fromEpochMilli, long toEpochMilli) {
this.tmpDir = tmpDir;
useMillis = (toEpochMilli - fromEpochMilli) < TimeUnit.MINUTES.toMillis(5);
plotAreaWidthInPx = plotSettings.getWidth() - GnuplotSettings.GNUPLOT_LEFT_RIGHT_MARGIN;
plotAreaHeightInPx = plotSettings.getHeight() - GnuplotSettings.GNUPLOT_TOP_BOTTOM_MARGIN;
epochMillisPerPixel = Math.max(1, (toEpochMilli - fromEpochMilli) / plotAreaWidthInPx);
this.tmpDir = tmpDir;
useMillis = (toEpochMilli - fromEpochMilli) < TimeUnit.MINUTES.toMillis(5);
plotAreaWidthInPx = plotSettings.getWidth() - GnuplotSettings.GNUPLOT_LEFT_RIGHT_MARGIN;
plotAreaHeightInPx = plotSettings.getHeight() - GnuplotSettings.GNUPLOT_TOP_BOTTOM_MARGIN;
epochMillisPerPixel = Math.max(1, (toEpochMilli - fromEpochMilli) / plotAreaWidthInPx);
minValue = plotSettings.getYRangeUnit() == TimeRangeUnitInternal.AUTOMATIC ? 0
: plotSettings.getYRangeUnit().toMilliSeconds(plotSettings.getYRangeMin());
maxValue = plotSettings.getYRangeUnit() == TimeRangeUnitInternal.AUTOMATIC ? Long.MAX_VALUE
: plotSettings.getYRangeUnit().toMilliSeconds(plotSettings.getYRangeMax());
durationMillisPerPixel = plotSettings.getYAxisScale() == AxisScale.LINEAR
? Math.max(1, (maxValue - minValue) / plotAreaHeightInPx)
: 1;
}
@Override
public void addValue(boolean valueIsInYRange, long epochMilli, long value) {
final long roundedEpochMilli = epochMilli - epochMilli % epochMillisPerPixel;
final long roundedValue = value - value % durationMillisPerPixel;
matrix2d.put(roundedEpochMilli, roundedValue, 1);
}
@Override
public AggregatedData getAggregatedData() throws IOException {
final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile());
final int separator = ',';
final int newline = '\n';
long[] actualValuesWritten = new long[1];
final StringBuilder formattedDateBuilder = new StringBuilder();
try (
final LambdaFriendlyWriter output = new LambdaFriendlyWriter(
new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.ISO_8859_1)));
final Formatter formatter = new Formatter(formattedDateBuilder);) {
matrix2d.forEach((epochMilli, value, __) -> {
final String stringValue = LongUtils.longToString(value);
final String formattedDate;
if (useMillis) {
formattedDateBuilder.delete(0, formattedDateBuilder.length());
formatter.format(Locale.US, "%.3f", epochMilli / 1000.0);
formattedDate = formattedDateBuilder.toString();
} else {
formattedDate = String.valueOf(epochMilli / 1000);
}
output.write(formattedDate);
output.write(separator);
output.write(stringValue);
output.write(newline);
actualValuesWritten[0]++;
});
minValue = plotSettings.getYRangeUnit() == TimeRangeUnitInternal.AUTOMATIC ? 0
: plotSettings.getYRangeUnit().toMilliSeconds(plotSettings.getYRangeMin());
maxValue = plotSettings.getYRangeUnit() == TimeRangeUnitInternal.AUTOMATIC ? Long.MAX_VALUE
: plotSettings.getYRangeUnit().toMilliSeconds(plotSettings.getYRangeMax());
durationMillisPerPixel = plotSettings.getYAxisScale() == AxisScale.LINEAR
? Math.max(1, (maxValue - minValue) / plotAreaHeightInPx)
: 1;
}
return new AggregatedData("scatter", dataFile);
}
@Override
public void addValue(boolean valueIsInYRange, long epochMilli, long value) {
final long roundedEpochMilli = epochMilli - epochMilli % epochMillisPerPixel;
final long roundedValue = value - value % durationMillisPerPixel;
matrix2d.put(roundedEpochMilli, roundedValue, 1);
}
@Override
public Aggregate getType() {
return Aggregate.SCATTER;
}
@Override
public AggregatedData getAggregatedData() throws IOException {
final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile());
final int separator = ',';
final int newline = '\n';
long[] actualValuesWritten = new long[1];
final StringBuilder formattedDateBuilder = new StringBuilder();
try (final LambdaFriendlyWriter output = new LambdaFriendlyWriter(new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.ISO_8859_1)));
final Formatter formatter = new Formatter(formattedDateBuilder);) {
matrix2d.forEach((epochMilli, value, __) -> {
final String stringValue = LongUtils.longToString(value);
final String formattedDate;
if (useMillis) {
formattedDateBuilder.delete(0, formattedDateBuilder.length());
formatter.format(Locale.US, "%.3f", epochMilli / 1000.0);
formattedDate = formattedDateBuilder.toString();
} else {
formattedDate = String.valueOf(epochMilli / 1000);
}
output.write(formattedDate);
output.write(separator);
output.write(stringValue);
output.write(newline);
actualValuesWritten[0]++;
});
}
return new AggregatedData("scatter", dataFile);
}
@Override
public Aggregate getType() {
return Aggregate.SCATTER;
}
}

View File

@@ -1,24 +1,24 @@
package org.lucares.pdb.plot.api;
public enum TimeRangeUnitInternal {
AUTOMATIC, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS;
AUTOMATIC, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS;
public int toMilliSeconds(final int value) {
public int toMilliSeconds(final int value) {
switch (this) {
case MILLISECONDS:
return value;
case SECONDS:
return value * 1000;
case MINUTES:
return value * 60 * 1000;
case HOURS:
return value * 60 * 60 * 1000;
case DAYS:
return value * 24 * 60 * 60 * 1000;
case AUTOMATIC:
return Integer.MAX_VALUE;
}
return Integer.MAX_VALUE;
}
switch (this) {
case MILLISECONDS:
return value;
case SECONDS:
return value * 1000;
case MINUTES:
return value * 60 * 1000;
case HOURS:
return value * 60 * 60 * 1000;
case DAYS:
return value * 24 * 60 * 60 * 1000;
case AUTOMATIC:
return Integer.MAX_VALUE;
}
return Integer.MAX_VALUE;
}
}

View File

@@ -9,175 +9,173 @@ import com.fasterxml.jackson.databind.ObjectMapper;
public class AxisSettings {
public enum Type {
Number, Time, Duration, Percent
}
private String format = "";
private String label = "";
private int rotateLabel = 0;
private String from;
private String to;
private Type type = Type.Number;
private GnuplotAxis axis = GnuplotAxis.X1;
private double ticIncrement;
private boolean ticsEnabled;
private boolean logscale;
private List<String> ticsLabels;
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public int getRotateXAxisLabel() {
return rotateLabel;
}
public void setRotateLabel(int rotateLabel) {
this.rotateLabel = rotateLabel;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public GnuplotAxis getAxis() {
return axis;
}
public void setAxis(GnuplotAxis axis) {
this.axis = axis;
}
public void setTicIncrement(double ticIncrement) {
this.ticIncrement = ticIncrement;
}
public double getTicIncrement() {
return ticIncrement;
}
public void setTicsEnabled(boolean ticsEnabled) {
this.ticsEnabled = ticsEnabled;
}
public boolean isTicsEnabled() {
return ticsEnabled;
}
public void setLogscale(boolean logscale) {
this.logscale = logscale;
}
public boolean isLogscale() {
return logscale;
}
public void setTics(List<String> ticsLabels) {
this.ticsLabels = ticsLabels;
}
public List<String> getTics() {
return ticsLabels;
}
public String toGnuplotDefinition(boolean renderLabels) {
StringBuilder result = new StringBuilder();
if (type == Type.Time) {
appendfln(result, "set %sdata time", axis);
public enum Type {
Number, Time, Duration, Percent
}
if (renderLabels) {
if (ticIncrement != 0) {
appendfln(result, "set %stics %f nomirror", axis, ticIncrement);
}
else if (ticsLabels != null && ticsLabels.size() > 0) {
appendfln(result,"set %stics(%s) nomirror", axis, String.join(", ", ticsLabels));
}else if(ticsEnabled) {
appendfln(result, "set %stics nomirror", axis);
}
if (StringUtils.isNotBlank(format)) {
appendfln(result, "set format %s \"%s\"", axis, format);
}
if (rotateLabel != 0) {
appendfln(result, "set %stics nomirror rotate by %d", axis, rotateLabel);
}
if (StringUtils.isNotBlank(label)) {
appendfln(result, "set %slabel \"%s\"", axis, label);
}
}else {
appendfln(result, "set format %s \"\"", axis);
appendfln(result, "set %slabel \"\"", axis);
private String format = "";
private String label = "";
private int rotateLabel = 0;
private String from;
private String to;
private Type type = Type.Number;
private GnuplotAxis axis = GnuplotAxis.X1;
private double ticIncrement;
private boolean ticsEnabled;
private boolean logscale;
private List<String> ticsLabels;
public String getFormat() {
return format;
}
if (!StringUtils.isAllBlank(from, to)) {
final String f = StringUtils.isEmpty(from) ? "" : "\""+from+"\"";
final String t = StringUtils.isEmpty(to) ? "" : "\""+to+"\"";
appendfln(result, "set %srange [%s:%s]", axis, f, t);
public void setFormat(String format) {
this.format = format;
}
if (logscale) {
appendfln(result, "set logscale %s", axis);
}
return result.toString();
}
private void appendfln(final StringBuilder builder, final String format, final Object... args) {
builder.append(String.format(format + "\n", args));
}
@Override
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
return e.getMessage();
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public int getRotateXAxisLabel() {
return rotateLabel;
}
public void setRotateLabel(int rotateLabel) {
this.rotateLabel = rotateLabel;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public GnuplotAxis getAxis() {
return axis;
}
public void setAxis(GnuplotAxis axis) {
this.axis = axis;
}
public void setTicIncrement(double ticIncrement) {
this.ticIncrement = ticIncrement;
}
public double getTicIncrement() {
return ticIncrement;
}
public void setTicsEnabled(boolean ticsEnabled) {
this.ticsEnabled = ticsEnabled;
}
public boolean isTicsEnabled() {
return ticsEnabled;
}
public void setLogscale(boolean logscale) {
this.logscale = logscale;
}
public boolean isLogscale() {
return logscale;
}
public void setTics(List<String> ticsLabels) {
this.ticsLabels = ticsLabels;
}
public List<String> getTics() {
return ticsLabels;
}
public String toGnuplotDefinition(boolean renderLabels) {
StringBuilder result = new StringBuilder();
if (type == Type.Time) {
appendfln(result, "set %sdata time", axis);
}
if (renderLabels) {
if (ticIncrement != 0) {
appendfln(result, "set %stics %f nomirror", axis, ticIncrement);
} else if (ticsLabels != null && ticsLabels.size() > 0) {
appendfln(result, "set %stics(%s) nomirror", axis, String.join(", ", ticsLabels));
} else if (ticsEnabled) {
appendfln(result, "set %stics nomirror", axis);
}
if (StringUtils.isNotBlank(format)) {
appendfln(result, "set format %s \"%s\"", axis, format);
}
if (rotateLabel != 0) {
appendfln(result, "set %stics nomirror rotate by %d", axis, rotateLabel);
}
if (StringUtils.isNotBlank(label)) {
appendfln(result, "set %slabel \"%s\"", axis, label);
}
} else {
appendfln(result, "set format %s \"\"", axis);
appendfln(result, "set %slabel \"\"", axis);
}
if (!StringUtils.isAllBlank(from, to)) {
final String f = StringUtils.isEmpty(from) ? "" : "\"" + from + "\"";
final String t = StringUtils.isEmpty(to) ? "" : "\"" + to + "\"";
appendfln(result, "set %srange [%s:%s]", axis, f, t);
}
if (logscale) {
appendfln(result, "set logscale %s", axis);
}
return result.toString();
}
private void appendfln(final StringBuilder builder, final String format, final Object... args) {
builder.append(String.format(format + "\n", args));
}
@Override
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
}

View File

@@ -11,116 +11,89 @@ import org.lucares.pdb.plot.api.AxisScale;
import org.lucares.recommind.logs.AxisSettings.Type;
public class AxisTime {
public static AxisSettings createXAxis(GnuplotSettings settings) {
AxisSettings result = new AxisSettings();
final OffsetDateTime minDate = settings.getDateTimeRange().getStart();
final OffsetDateTime maxDate = settings.getDateTimeRange().getEnd();
final String formatX;
if (minDate.until(maxDate, ChronoUnit.WEEKS) > 1) {
formatX = "%Y-%m-%d";
} else if (minDate.until(maxDate, ChronoUnit.SECONDS) > 30) {
formatX = "%Y-%m-%d\\n%H:%M:%S";
} else {
formatX = "%Y-%m-%d\\n%H:%M:%.3S";
}
final String formattedMinDate = String.valueOf(minDate.toEpochSecond());
final String formattedMaxDate = String.valueOf(maxDate.toEpochSecond());
public static AxisSettings createXAxis(GnuplotSettings settings) {
AxisSettings result = new AxisSettings();
result.setLabel("Time");
result.setType(Type.Time);
result.setTicsEnabled(true);
result.setFormat(formatX);
result.setFrom(formattedMinDate);
result.setTo(formattedMaxDate);
result.setTicIncrement(computeTimeTicIncrement(settings.getWidth(), settings.getDateTimeRange()));
return result;
}
public static AxisSettings createYAxis(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
AxisSettings result = new AxisSettings();
result.setLabel("Duration");
result.setType(Type.Duration);
result.setAxis(GnuplotAxis.Y1);
result.setTicsEnabled(true);
final OffsetDateTime minDate = settings.getDateTimeRange().getStart();
final OffsetDateTime maxDate = settings.getDateTimeRange().getEnd();
final String formatX;
if (minDate.until(maxDate, ChronoUnit.WEEKS) > 1) {
formatX = "%Y-%m-%d";
} else if (minDate.until(maxDate, ChronoUnit.SECONDS) > 30) {
formatX = "%Y-%m-%d\\n%H:%M:%S";
} else {
formatX = "%Y-%m-%d\\n%H:%M:%.3S";
}
final String formattedMinDate = String.valueOf(minDate.toEpochSecond());
final String formattedMaxDate = String.valueOf(maxDate.toEpochSecond());
final int graphOffset = settings.getYAxisScale() == AxisScale.LINEAR ? 0 : 1;
if (settings.hasYRange()) {
final int min = Math.max(settings.getYRangeMin(), graphOffset);
final int max = settings.getYRangeMax();
result.setFrom(String.valueOf(min));
result.setTo(String.valueOf(max));
} else {
result.setFrom(String.valueOf(graphOffset));
result.setLabel("Time");
result.setType(Type.Time);
result.setTicsEnabled(true);
result.setFormat(formatX);
result.setFrom(formattedMinDate);
result.setTo(formattedMaxDate);
result.setTicIncrement(computeTimeTicIncrement(settings.getWidth(), settings.getDateTimeRange()));
return result;
}
result.setLogscale(settings.getYAxisScale() == AxisScale.LOG10);
result.setTics(YAxisTicks.computeYTicks(settings, dataSeries));
public static AxisSettings createYAxis(GnuplotSettings settings, Collection<DataSeries> dataSeries) {
AxisSettings result = new AxisSettings();
result.setLabel("Duration");
result.setType(Type.Duration);
result.setAxis(GnuplotAxis.Y1);
result.setTicsEnabled(true);
return result;
}
final int graphOffset = settings.getYAxisScale() == AxisScale.LINEAR ? 0 : 1;
if (settings.hasYRange()) {
final int min = Math.max(settings.getYRangeMin(), graphOffset);
final int max = settings.getYRangeMax();
result.setFrom(String.valueOf(min));
result.setTo(String.valueOf(max));
} else {
result.setFrom(String.valueOf(graphOffset));
}
public static double computeTimeTicIncrement(int width, DateTimeRange dateTimeRange) {
final long startEpochMilli = dateTimeRange.getStartEpochMilli();
final long endEpochMilli = dateTimeRange.getEndEpochMilli();
final long rangeInMs = endEpochMilli - startEpochMilli + 1;
int widthInPx = width - GnuplotSettings.GNUPLOT_LEFT_RIGHT_MARGIN;
final long maxLabels = Math.max(1, widthInPx / (GnuplotSettings.TICKS_FONT_SIZE * 8));
result.setLogscale(settings.getYAxisScale() == AxisScale.LOG10);
final long tickIncrement = roundToTickIncrement(rangeInMs / maxLabels);
return tickIncrement/1000.0;
}
private static long roundToTickIncrement(long milliseconds) {
LongList increments = LongList.of(
100,
200,
500,
TimeUnit.SECONDS.toMillis(1),
TimeUnit.SECONDS.toMillis(2),
TimeUnit.SECONDS.toMillis(5),
TimeUnit.SECONDS.toMillis(10),
TimeUnit.SECONDS.toMillis(15),
TimeUnit.SECONDS.toMillis(30),
TimeUnit.MINUTES.toMillis(1),
TimeUnit.MINUTES.toMillis(2),
TimeUnit.MINUTES.toMillis(5),
TimeUnit.MINUTES.toMillis(10),
TimeUnit.MINUTES.toMillis(15),
TimeUnit.MINUTES.toMillis(30),
TimeUnit.HOURS.toMillis(1),
TimeUnit.HOURS.toMillis(2),
TimeUnit.HOURS.toMillis(3),
TimeUnit.HOURS.toMillis(6),
TimeUnit.HOURS.toMillis(12),
TimeUnit.HOURS.toMillis(18),
TimeUnit.DAYS.toMillis(1),
TimeUnit.DAYS.toMillis(2),
TimeUnit.DAYS.toMillis(3),
TimeUnit.DAYS.toMillis(4),
TimeUnit.DAYS.toMillis(5),
TimeUnit.DAYS.toMillis(6),
TimeUnit.DAYS.toMillis(7),
TimeUnit.DAYS.toMillis(14),
TimeUnit.DAYS.toMillis(30),
TimeUnit.DAYS.toMillis(90),
TimeUnit.DAYS.toMillis(180),
TimeUnit.DAYS.toMillis(365),
TimeUnit.DAYS.toMillis(365*2),
TimeUnit.DAYS.toMillis(365*5),
TimeUnit.DAYS.toMillis(365*10),
TimeUnit.DAYS.toMillis(365*20)
);
for ( int i = 0; i < increments.size(); i++) {
if (increments.get(i) > milliseconds) {
return increments.get(i);
}
result.setTics(YAxisTicks.computeYTicks(settings, dataSeries));
return result;
}
public static double computeTimeTicIncrement(int width, DateTimeRange dateTimeRange) {
final long startEpochMilli = dateTimeRange.getStartEpochMilli();
final long endEpochMilli = dateTimeRange.getEndEpochMilli();
final long rangeInMs = endEpochMilli - startEpochMilli + 1;
int widthInPx = width - GnuplotSettings.GNUPLOT_LEFT_RIGHT_MARGIN;
final long maxLabels = Math.max(1, widthInPx / (GnuplotSettings.TICKS_FONT_SIZE * 8));
final long tickIncrement = roundToTickIncrement(rangeInMs / maxLabels);
return tickIncrement / 1000.0;
}
private static long roundToTickIncrement(long milliseconds) {
LongList increments = LongList.of(100, 200, 500, TimeUnit.SECONDS.toMillis(1), TimeUnit.SECONDS.toMillis(2),
TimeUnit.SECONDS.toMillis(5), TimeUnit.SECONDS.toMillis(10), TimeUnit.SECONDS.toMillis(15),
TimeUnit.SECONDS.toMillis(30), TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(2),
TimeUnit.MINUTES.toMillis(5), TimeUnit.MINUTES.toMillis(10), TimeUnit.MINUTES.toMillis(15),
TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), TimeUnit.HOURS.toMillis(2),
TimeUnit.HOURS.toMillis(3), TimeUnit.HOURS.toMillis(6), TimeUnit.HOURS.toMillis(12),
TimeUnit.HOURS.toMillis(18), TimeUnit.DAYS.toMillis(1), TimeUnit.DAYS.toMillis(2),
TimeUnit.DAYS.toMillis(3), TimeUnit.DAYS.toMillis(4), TimeUnit.DAYS.toMillis(5),
TimeUnit.DAYS.toMillis(6), TimeUnit.DAYS.toMillis(7), TimeUnit.DAYS.toMillis(14),
TimeUnit.DAYS.toMillis(30), TimeUnit.DAYS.toMillis(90), TimeUnit.DAYS.toMillis(180),
TimeUnit.DAYS.toMillis(365), TimeUnit.DAYS.toMillis(365 * 2), TimeUnit.DAYS.toMillis(365 * 5),
TimeUnit.DAYS.toMillis(365 * 10), TimeUnit.DAYS.toMillis(365 * 20));
for (int i = 0; i < increments.size(); i++) {
if (increments.get(i) > milliseconds) {
return increments.get(i);
}
}
return TimeUnit.DAYS.toMillis(365 * 10);
}
return TimeUnit.DAYS.toMillis(365*10);
}
}

View File

@@ -4,5 +4,5 @@ import java.nio.file.Path;
import java.nio.file.Paths;
public class Config {
public static final Path DATA_DIR = Paths.get("/home/andi/ws/performanceDb/db");
public static final Path DATA_DIR = Paths.get("/home/andi/ws/performanceDb/db");
}

View File

@@ -3,52 +3,51 @@ package org.lucares.recommind.logs;
import org.lucares.pdb.plot.api.AggregatedDataCollection;
class CsvSummary {
private final int values;
private final long maxValue;
private final AggregatedDataCollection aggregatedData;
private final double statsAverage;
private final int plottedValues;
private final int values;
private final long maxValue;
private final AggregatedDataCollection aggregatedData;
private final double statsAverage;
private final int plottedValues;
public CsvSummary(final int values, final int plottedValues, final long maxValue,
final double statsAverage, final AggregatedDataCollection aggregatedData) {
super();
this.values = values;
this.plottedValues = plottedValues;
this.maxValue = maxValue;
this.statsAverage = statsAverage;
this.aggregatedData = aggregatedData;
}
public CsvSummary(final int values, final int plottedValues, final long maxValue, final double statsAverage,
final AggregatedDataCollection aggregatedData) {
super();
this.values = values;
this.plottedValues = plottedValues;
this.maxValue = maxValue;
this.statsAverage = statsAverage;
this.aggregatedData = aggregatedData;
}
/**
* Total number of values in the selected date range.
*
* @see CsvSummary#getPlottedValues()
* @return total number of values
*/
public int getValues() {
return values;
}
/**
* Total number of values in the selected date range.
*
* @see CsvSummary#getPlottedValues()
* @return total number of values
*/
public int getValues() {
return values;
}
/**
* Number of plotted values in the selected date range <em>and</em> y-range.
*
* @see CsvSummary#getValues()
* @return number of plotted values
*/
public int getPlottedValues() {
return plottedValues;
}
/**
* Number of plotted values in the selected date range <em>and</em> y-range.
*
* @see CsvSummary#getValues()
* @return number of plotted values
*/
public int getPlottedValues() {
return plottedValues;
}
public long getMaxValue() {
return maxValue;
}
public long getMaxValue() {
return maxValue;
}
public double getStatsAverage() {
return statsAverage;
}
public double getStatsAverage() {
return statsAverage;
}
public AggregatedDataCollection getAggregatedData() {
return aggregatedData;
}
public AggregatedDataCollection getAggregatedData() {
return aggregatedData;
}
}

View File

@@ -2,20 +2,20 @@ package org.lucares.recommind.logs;
public enum DashTypes {
DASH_TYPE_2("2"), DASH_TYPE_3("3"), DASH_TYPE_4("4"), DASH_TYPE_5("5"), DASH_TYPE_6("6"), DASH_TYPE_DOT(
"\".\""), DASH_TYPE_DASH("\"-\""), DASH_TYPE_DOT_DASH("\"._\""), DASH_TYPE_DOT_DOT_DASH("\"..- \"");
DASH_TYPE_2("2"), DASH_TYPE_3("3"), DASH_TYPE_4("4"), DASH_TYPE_5("5"), DASH_TYPE_6("6"), DASH_TYPE_DOT("\".\""),
DASH_TYPE_DASH("\"-\""), DASH_TYPE_DOT_DASH("\"._\""), DASH_TYPE_DOT_DOT_DASH("\"..- \"");
private final String gnuplotDashType;
private final String gnuplotDashType;
private DashTypes(final String gnuplotDashType) {
this.gnuplotDashType = gnuplotDashType;
}
private DashTypes(final String gnuplotDashType) {
this.gnuplotDashType = gnuplotDashType;
}
public String toGnuplotDashType() {
return gnuplotDashType;
}
public String toGnuplotDashType() {
return gnuplotDashType;
}
static DashTypes get(final int i) {
return values()[i % values().length];
}
static DashTypes get(final int i) {
return values()[i % values().length];
}
}

View File

@@ -10,110 +10,110 @@ import org.lucares.pdb.plot.api.AggregatedDataCollection;
import org.lucares.pdb.plot.api.Limit;
public interface DataSeries {
public static final Comparator<? super DataSeries> BY_NUMBER_OF_VALUES = (a, b) -> {
return a.getValues() - b.getValues();
};
public static final Comparator<? super DataSeries> BY_NUMBER_OF_VALUES = (a, b) -> {
return a.getValues() - b.getValues();
};
public static final Comparator<? super DataSeries> BY_MAX_VALUE = (a, b) -> {
final long result = a.getMaxValue() - b.getMaxValue();
return result < 0 ? -1 : (result > 0 ? 1 : 0);
};
public static final Comparator<? super DataSeries> BY_MAX_VALUE = (a, b) -> {
final long result = a.getMaxValue() - b.getMaxValue();
return result < 0 ? -1 : (result > 0 ? 1 : 0);
};
public static final Comparator<? super DataSeries> BY_NAME = (a, b) -> {
return a.getTitle().compareToIgnoreCase(b.getTitle());
};
public static final Comparator<? super DataSeries> BY_NAME = (a, b) -> {
return a.getTitle().compareToIgnoreCase(b.getTitle());
};
public String getIdAsString();
public String getIdAsString();
public int getId();
public int getId();
public String getTitle();
public String getTitle();
public int getValues();
public int getValues();
public int getPlottedValues();
public int getPlottedValues();
public long getMaxValue();
public long getMaxValue();
public double getAverage();
public double getAverage();
public void setStyle(LineStyle style);
public void setStyle(LineStyle style);
public LineStyle getStyle();
public LineStyle getStyle();
public AggregatedDataCollection getAggregatedData();
public AggregatedDataCollection getAggregatedData();
public static Map<String, Integer> toMap(final List<DataSeries> dataSeries) {
final Map<String, Integer> result = new LinkedHashMap<>();
public static Map<String, Integer> toMap(final List<DataSeries> dataSeries) {
final Map<String, Integer> result = new LinkedHashMap<>();
for (final DataSeries dataSerie : dataSeries) {
for (final DataSeries dataSerie : dataSeries) {
result.put(dataSerie.getTitle(), dataSerie.getValues());
result.put(dataSerie.getTitle(), dataSerie.getValues());
}
}
return result;
}
return result;
}
static Comparator<? super DataSeries> getDataSeriesComparator(final Limit limitBy) {
static Comparator<? super DataSeries> getDataSeriesComparator(final Limit limitBy) {
switch (limitBy) {
case MOST_VALUES:
return DataSeries.BY_NUMBER_OF_VALUES.reversed();
case FEWEST_VALUES:
return DataSeries.BY_NUMBER_OF_VALUES;
case MAX_VALUE:
return DataSeries.BY_MAX_VALUE.reversed();
case MIN_VALUE:
return DataSeries.BY_MAX_VALUE;
case NO_LIMIT:
return DataSeries.BY_NAME;
}
throw new IllegalStateException("unhandled enum: " + limitBy);
}
switch (limitBy) {
case MOST_VALUES:
return DataSeries.BY_NUMBER_OF_VALUES.reversed();
case FEWEST_VALUES:
return DataSeries.BY_NUMBER_OF_VALUES;
case MAX_VALUE:
return DataSeries.BY_MAX_VALUE.reversed();
case MIN_VALUE:
return DataSeries.BY_MAX_VALUE;
case NO_LIMIT:
return DataSeries.BY_NAME;
}
throw new IllegalStateException("unhandled enum: " + limitBy);
}
static void sortAndLimit(final List<DataSeries> dataSeries, final Limit limitBy, final int limit) {
static void sortAndLimit(final List<DataSeries> dataSeries, final Limit limitBy, final int limit) {
dataSeries.sort(DataSeries.getDataSeriesComparator(limitBy));
dataSeries.sort(DataSeries.getDataSeriesComparator(limitBy));
switch (limitBy) {
case FEWEST_VALUES:
case MOST_VALUES:
case MAX_VALUE:
case MIN_VALUE:
while (dataSeries.size() > limit) {
dataSeries.remove(limit);
}
break;
case NO_LIMIT:
}
}
switch (limitBy) {
case FEWEST_VALUES:
case MOST_VALUES:
case MAX_VALUE:
case MIN_VALUE:
while (dataSeries.size() > limit) {
dataSeries.remove(limit);
}
break;
case NO_LIMIT:
}
}
static void setColors(final List<DataSeries> dataSeries) {
static void setColors(final List<DataSeries> dataSeries) {
int i = 0;
int i = 0;
for (final DataSeries dataSerie : dataSeries) {
for (final DataSeries dataSerie : dataSeries) {
final int numColors = GnuplotColorPalettes.DEFAULT.size();
final int numColors = GnuplotColorPalettes.DEFAULT.size();
final GnuplotColor color = GnuplotColorPalettes.DEFAULT.get(i % numColors);
final GnuplotColor color = GnuplotColorPalettes.DEFAULT.get(i % numColors);
final DashTypes dashType = DashTypes.get(i / numColors);
final LineStyle lineStyle = new LineStyle(color, dashType);
dataSerie.setStyle(lineStyle);
i++;
}
}
final DashTypes dashType = DashTypes.get(i / numColors);
final LineStyle lineStyle = new LineStyle(color, dashType);
dataSerie.setStyle(lineStyle);
i++;
}
}
public static long maxValue(final Collection<DataSeries> dataSeries) {
long result = 0;
public static long maxValue(final Collection<DataSeries> dataSeries) {
long result = 0;
for (final DataSeries series : dataSeries) {
result = Math.max(result, series.getMaxValue());
}
for (final DataSeries series : dataSeries) {
result = Math.max(result, series.getMaxValue());
}
return result;
}
return result;
}
}

View File

@@ -4,69 +4,67 @@ import org.lucares.pdb.plot.api.AggregatedDataCollection;
public class FileBackedDataSeries implements DataSeries {
private final String title;
private final String title;
private final CsvSummary csvSummary;
private final CsvSummary csvSummary;
private final int id;
private final int id;
private LineStyle style;
private LineStyle style;
public FileBackedDataSeries(final int id, final String title, final CsvSummary csvSummary
) {
this.id = id;
this.title = title;
this.csvSummary = csvSummary;
}
public FileBackedDataSeries(final int id, final String title, final CsvSummary csvSummary) {
this.id = id;
this.title = title;
this.csvSummary = csvSummary;
}
@Override
public String getIdAsString() {
return "id" + id;
}
@Override
public String getIdAsString() {
return "id" + id;
}
@Override
public int getId() {
return id;
}
@Override
public int getId() {
return id;
}
@Override
public void setStyle(final LineStyle style) {
this.style = style;
}
@Override
public void setStyle(final LineStyle style) {
this.style = style;
}
@Override
public LineStyle getStyle() {
return style;
}
@Override
public LineStyle getStyle() {
return style;
}
@Override
public String getTitle() {
return title;
}
@Override
public String getTitle() {
return title;
}
@Override
public int getValues() {
return csvSummary.getValues();
}
@Override
public int getValues() {
return csvSummary.getValues();
}
@Override
public int getPlottedValues() {
return csvSummary.getPlottedValues();
}
@Override
public int getPlottedValues() {
return csvSummary.getPlottedValues();
}
@Override
public long getMaxValue() {
return csvSummary.getMaxValue();
}
@Override
public long getMaxValue() {
return csvSummary.getMaxValue();
}
@Override
public double getAverage() {
return csvSummary.getStatsAverage();
}
@Override
public double getAverage() {
return csvSummary.getStatsAverage();
}
@Override
public AggregatedDataCollection getAggregatedData() {
return csvSummary.getAggregatedData();
}
@Override
public AggregatedDataCollection getAggregatedData() {
return csvSummary.getAggregatedData();
}
}

View File

@@ -18,95 +18,95 @@ import org.slf4j.LoggerFactory;
public class Gnuplot {
private static final Logger LOGGER = LoggerFactory.getLogger(Gnuplot.class);
private static final Logger METRICS_LOGGER = LoggerFactory.getLogger("org.lucares.metrics.gnuplot");
private static final Logger LOGGER = LoggerFactory.getLogger(Gnuplot.class);
private static final Logger METRICS_LOGGER = LoggerFactory.getLogger("org.lucares.metrics.gnuplot");
private static final String ENV_GNUPLOT_HOME = "GNUPLOT_HOME";
private static final String PROPERTY_GNUPLOT_HOME = "gnuplot.home";
private final Path tmpDirectory;
private static final String ENV_GNUPLOT_HOME = "GNUPLOT_HOME";
private static final String PROPERTY_GNUPLOT_HOME = "gnuplot.home";
private final Path tmpDirectory;
// This would be bad style if this code was executed in a web-container, because
// it would cause a memory leak.
// But this code is only (and will only) be executed as standalone application.
private static final ExecutorService POOL = Executors.newCachedThreadPool();
// This would be bad style if this code was executed in a web-container, because
// it would cause a memory leak.
// But this code is only (and will only) be executed as standalone application.
private static final ExecutorService POOL = Executors.newCachedThreadPool();
public Gnuplot(final Path tmpDirectory) {
this.tmpDirectory = tmpDirectory;
}
public Gnuplot(final Path tmpDirectory) {
this.tmpDirectory = tmpDirectory;
}
public void plot(final GnuplotSettings settings, final Collection<DataSeries> dataSeries)
throws IOException, InterruptedException {
public void plot(final GnuplotSettings settings, final Collection<DataSeries> dataSeries)
throws IOException, InterruptedException {
final GnuplotFileGenerator generator = new GnuplotFileGenerator();
final GnuplotFileGenerator generator = new GnuplotFileGenerator();
final String gnuplotFileContent = generator.generate(settings, dataSeries);
LOGGER.debug(gnuplotFileContent);
final String gnuplotFileContent = generator.generate(settings, dataSeries);
LOGGER.debug(gnuplotFileContent);
final File gnuplotFile = File.createTempFile("gnuplot", ".dem", tmpDirectory.toFile());
Files.writeString(gnuplotFile.toPath(), gnuplotFileContent, StandardCharsets.UTF_8);
final File gnuplotFile = File.createTempFile("gnuplot", ".dem", tmpDirectory.toFile());
Files.writeString(gnuplotFile.toPath(), gnuplotFileContent, StandardCharsets.UTF_8);
final long start = System.nanoTime();
final long start = System.nanoTime();
try {
final ProcessBuilder processBuilder = new ProcessBuilder(gnuplotBinary(), gnuplotFile.getAbsolutePath())//
.redirectOutput(Redirect.PIPE)//
.redirectError(Redirect.PIPE);
try {
final ProcessBuilder processBuilder = new ProcessBuilder(gnuplotBinary(), gnuplotFile.getAbsolutePath())//
.redirectOutput(Redirect.PIPE)//
.redirectError(Redirect.PIPE);
final Process process = processBuilder.start();
logOutput("stderr", process.getErrorStream());
logOutput("stdout", process.getInputStream());
process.waitFor();
} catch (final IOException e) {
if (e.getMessage().contains("No such file or directory")) {
throw new IOException("Did not find gnuplot. Add it to the 'PATH' or create an environment variable '"
+ ENV_GNUPLOT_HOME + "' or add the java property '" + PROPERTY_GNUPLOT_HOME + "': "
+ e.getMessage(), e);
} else {
throw e;
}
}
final Process process = processBuilder.start();
logOutput("stderr", process.getErrorStream());
logOutput("stdout", process.getInputStream());
process.waitFor();
} catch (final IOException e) {
if (e.getMessage().contains("No such file or directory")) {
throw new IOException("Did not find gnuplot. Add it to the 'PATH' or create an environment variable '"
+ ENV_GNUPLOT_HOME + "' or add the java property '" + PROPERTY_GNUPLOT_HOME + "': "
+ e.getMessage(), e);
} else {
throw e;
}
}
METRICS_LOGGER.debug("gnuplot: {}ms", (System.nanoTime() - start) / 1_000_000.0);
}
METRICS_LOGGER.debug("gnuplot: {}ms", (System.nanoTime() - start) / 1_000_000.0);
}
private void logOutput(final String humanReadableType, final InputStream stream) throws IOException {
private void logOutput(final String humanReadableType, final InputStream stream) throws IOException {
POOL.submit(() -> {
try {
final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
LOGGER.info("gnuplot {}: {}", humanReadableType, line);
}
} catch (final Exception e) {
LOGGER.warn("Exception while reading " + humanReadableType + " of gnuplot command", e);
}
});
}
POOL.submit(() -> {
try {
final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
LOGGER.info("gnuplot {}: {}", humanReadableType, line);
}
} catch (final Exception e) {
LOGGER.warn("Exception while reading " + humanReadableType + " of gnuplot command", e);
}
});
}
private String gnuplotBinary() {
private String gnuplotBinary() {
if (System.getProperty(PROPERTY_GNUPLOT_HOME) != null) {
if (System.getProperty(PROPERTY_GNUPLOT_HOME) != null) {
if (isWindows()) {
return System.getProperty(PROPERTY_GNUPLOT_HOME) + "\\bin\\gnuplot.exe";
} else {
return System.getProperty(PROPERTY_GNUPLOT_HOME) + "/bin/gnuplot";
}
}
if (System.getenv(ENV_GNUPLOT_HOME) != null) {
if (isWindows()) {
return System.getProperty(PROPERTY_GNUPLOT_HOME) + "\\bin\\gnuplot.exe";
} else {
return System.getProperty(PROPERTY_GNUPLOT_HOME) + "/bin/gnuplot";
}
}
if (System.getenv(ENV_GNUPLOT_HOME) != null) {
if (isWindows()) {
return System.getenv(ENV_GNUPLOT_HOME) + "\\bin\\gnuplot.exe";
} else {
return System.getenv(ENV_GNUPLOT_HOME) + "/bin/gnuplot";
}
}
if (isWindows()) {
return System.getenv(ENV_GNUPLOT_HOME) + "\\bin\\gnuplot.exe";
} else {
return System.getenv(ENV_GNUPLOT_HOME) + "/bin/gnuplot";
}
}
return "gnuplot";
}
return "gnuplot";
}
private boolean isWindows() {
return System.getProperty("os.name").toLowerCase().startsWith("windows");
}
private boolean isWindows() {
return System.getProperty("os.name").toLowerCase().startsWith("windows");
}
}

View File

@@ -1,43 +1,43 @@
package org.lucares.recommind.logs;
public enum GnuplotAxis {
X1("x", "x1"),
X1("x", "x1"),
X2("x2", "x2"),
X2("x2", "x2"),
Y1("y", "y1"),
Y1("y", "y1"),
Y2("y2", "y2");
Y2("y2", "y2");
private String axis;
private String axisNameForPlots;
private String axis;
private String axisNameForPlots;
private GnuplotAxis(String axis, String axisNameForPlots) {
this.axis = axis;
this.axisNameForPlots = axisNameForPlots;
}
@Override
public String toString() {
return axis;
}
public String getAxisNameForPlots() {
return axisNameForPlots;
}
public GnuplotAxis mirrorAxis() {
switch (this) {
case X1:
return X2;
case X2:
return X1;
case Y1:
return Y2;
case Y2:
return Y1;
default:
throw new IllegalArgumentException("Unexpected value: " + this);
private GnuplotAxis(String axis, String axisNameForPlots) {
this.axis = axis;
this.axisNameForPlots = axisNameForPlots;
}
@Override
public String toString() {
return axis;
}
public String getAxisNameForPlots() {
return axisNameForPlots;
}
public GnuplotAxis mirrorAxis() {
switch (this) {
case X1:
return X2;
case X2:
return X1;
case Y1:
return Y2;
case Y2:
return Y1;
default:
throw new IllegalArgumentException("Unexpected value: " + this);
}
}
}
}

View File

@@ -3,54 +3,53 @@ package org.lucares.recommind.logs;
import java.awt.Color;
public class GnuplotColor {
private final String color; // hex: 00efcc
private final String color; // hex: 00efcc
private GnuplotColor(String color) {
this.color = color;
}
private GnuplotColor(String color) {
this.color = color;
}
public static GnuplotColor byHex(String aHex) {
return new GnuplotColor(aHex);
}
public static GnuplotColor byHex(String aHex) {
return new GnuplotColor(aHex);
}
public static GnuplotColor byAwtColor(Color color) {
public static GnuplotColor byAwtColor(Color color) {
final String hex = String.format("%02x%02x%02x",//
color.getRed(),//
color.getGreen(),//
color.getBlue()//
);
final String hex = String.format("%02x%02x%02x", //
color.getRed(), //
color.getGreen(), //
color.getBlue()//
);
return new GnuplotColor(hex);
}
return new GnuplotColor(hex);
}
public String getColor() {
return "rgb \"#" + color + "\"";
}
public String getColor() {
return "rgb \"#" + color + "\"";
}
@Override
public String toString() {
return getColor();
}
@Override
public String toString() {
return getColor();
}
Color toAwtColor() {
int red = Integer.parseInt(color.substring(0, 2), 16);
int green = Integer.parseInt(color.substring(2, 4), 16);
int blue = Integer.parseInt(color.substring(4, 6), 16);
return new Color(red, green, blue);
}
Color toAwtColor() {
int red = Integer.parseInt(color.substring(0, 2), 16);
int green = Integer.parseInt(color.substring(2, 4), 16);
int blue = Integer.parseInt(color.substring(4, 6), 16);
return new Color(red, green, blue);
}
public GnuplotColor brighter() {
public GnuplotColor brighter() {
final Color brighterColor = toAwtColor().brighter();
return byAwtColor(brighterColor);
}
final Color brighterColor = toAwtColor().brighter();
public GnuplotColor darker() {
return byAwtColor(brighterColor);
}
final Color darkerColor = toAwtColor().darker();
return byAwtColor(darkerColor);
}
public GnuplotColor darker() {
final Color darkerColor = toAwtColor().darker();
return byAwtColor(darkerColor);
}
}

View File

@@ -5,52 +5,50 @@ import java.util.List;
public interface GnuplotColorPalettes {
/**
*
* <div style="background-color: #9400D3; display:inline; padding:
* 8px;">#9400D3</div>
* <div style="background-color: #009e73; display:inline; padding:
* 8px;">#009e73</div>
* <div style="background-color: #56b4e9; display:inline; padding:
* 8px;">#56b4e9</div>
* <div style="background-color: #e69f00; display:inline; padding:
* 8px;">#e69f00</div>
* <div style="background-color: #f0e442; display:inline; padding:
* 8px;">#f0e442</div>
* <div style="background-color: #0072b2; display:inline; padding:
* 8px;">#0072b2</div>
* <div style="background-color: #e51e10; display:inline; padding:
* 8px;">#e51e10</div>
* <div style="background-color: #FF69B4; display:inline; padding:
* 8px;">#FF69B4</div>
*/
/**
*
* <div style="background-color: #9400D3; display:inline; padding:
* 8px;">#9400D3</div>
* <div style="background-color: #009e73; display:inline; padding:
* 8px;">#009e73</div>
* <div style="background-color: #56b4e9; display:inline; padding:
* 8px;">#56b4e9</div>
* <div style="background-color: #e69f00; display:inline; padding:
* 8px;">#e69f00</div>
* <div style="background-color: #f0e442; display:inline; padding:
* 8px;">#f0e442</div>
* <div style="background-color: #0072b2; display:inline; padding:
* 8px;">#0072b2</div>
* <div style="background-color: #e51e10; display:inline; padding:
* 8px;">#e51e10</div>
* <div style="background-color: #FF69B4; display:inline; padding:
* 8px;">#FF69B4</div>
*/
List<GnuplotColor> GNUPLOT = Arrays.asList(//
GnuplotColor.byHex("9400D3"), // purple
GnuplotColor.byHex("009e73"), // green
GnuplotColor.byHex("56b4e9"), // light blue
GnuplotColor.byHex("e69f00"), // orange
GnuplotColor.byHex("f0e442"), // yellow
GnuplotColor.byHex("0072b2"), // blue
GnuplotColor.byHex("e51e10"), // red
GnuplotColor.byHex("FF69B4")// magenta
);
List<GnuplotColor> GNUPLOT_REORDERED = Arrays.asList(//
GnuplotColor.byHex("0072b2"), // blue
GnuplotColor.byHex("e69f00"), // orange
GnuplotColor.byHex("9400D3"), //purple
GnuplotColor.byHex("009e73"), //green
List<GnuplotColor> GNUPLOT = Arrays.asList(//
GnuplotColor.byHex("9400D3"), // purple
GnuplotColor.byHex("009e73"), // green
GnuplotColor.byHex("56b4e9"), // light blue
GnuplotColor.byHex("e69f00"), // orange
GnuplotColor.byHex("f0e442"), // yellow
GnuplotColor.byHex("0072b2"), // blue
GnuplotColor.byHex("e51e10"), // red
GnuplotColor.byHex("FF69B4")// magenta
);
List<GnuplotColor> GNUPLOT_REORDERED = Arrays.asList(//
GnuplotColor.byHex("0072b2"), // blue
GnuplotColor.byHex("e69f00"), // orange
GnuplotColor.byHex("9400D3"), // purple
GnuplotColor.byHex("009e73"), // green
GnuplotColor.byHex("f0e442"), // yellow
GnuplotColor.byHex("e51e10"), // red
GnuplotColor.byHex("56b4e9"), // lightblue
GnuplotColor.byHex("FF69B4")// magenta
);
/**
* <div style="background-color: #1f77b4; display:inline; padding:
/**
* <div style="background-color: #1f77b4; display:inline; padding:
* 8px;">#1f77b4</div>
* <div style="background-color: #ff7f0e; display:inline; padding:
* 8px;">#ff7f0e</div>
@@ -63,23 +61,21 @@ public interface GnuplotColorPalettes {
* <div style="background-color: #b3df72; display:inline; padding:
* 8px;">#b3df72</div>
* <div style="background-color: #feffbe; display:inline; padding:
* 8px;">#feffbe</div>
* --
* 8px;">#feffbe</div> --
* <div style="background-color: #4660ff; display:inline; padding:
* 8px;">#4660ff</div>
*
*
*/
List<GnuplotColor> MATPLOTLIB = Arrays.asList(//
GnuplotColor.byHex("1f77b4"), // blue
GnuplotColor.byHex("ff7f0e"), // orange
GnuplotColor.byHex("d62728"), // red
GnuplotColor.byHex("2ca02c"), // green
GnuplotColor.byHex("fdbb6c"), // light orange
GnuplotColor.byHex("b3df72"), // light green
GnuplotColor.byHex("feffbe")// light yellow
);
List<GnuplotColor> DEFAULT = GNUPLOT_REORDERED;
*
*
*/
List<GnuplotColor> MATPLOTLIB = Arrays.asList(//
GnuplotColor.byHex("1f77b4"), // blue
GnuplotColor.byHex("ff7f0e"), // orange
GnuplotColor.byHex("d62728"), // red
GnuplotColor.byHex("2ca02c"), // green
GnuplotColor.byHex("fdbb6c"), // light orange
GnuplotColor.byHex("b3df72"), // light green
GnuplotColor.byHex("feffbe")// light yellow
);
List<GnuplotColor> DEFAULT = GNUPLOT_REORDERED;
}

View File

@@ -9,72 +9,72 @@ import org.slf4j.LoggerFactory;
public class GnuplotFileGenerator implements Appender {
private static final Logger LOGGER = LoggerFactory.getLogger(GnuplotFileGenerator.class);
private static final Logger LOGGER = LoggerFactory.getLogger(GnuplotFileGenerator.class);
private static final int KEY_FONT_SIZE = 10;
private static final int KEY_FONT_SIZE = 10;
public String generate(final GnuplotSettings settings, final Collection<DataSeries> dataSeries) {
public String generate(final GnuplotSettings settings, final Collection<DataSeries> dataSeries) {
final StringBuilder result = new StringBuilder();
final StringBuilder result = new StringBuilder();
appendfln(result, "set terminal %s noenhanced size %d,%d", settings.getTerminal(), settings.getWidth(),
settings.getHeight());
appendfln(result, "set terminal %s noenhanced size %d,%d", settings.getTerminal(), settings.getWidth(),
settings.getHeight());
appendfln(result, "set datafile separator \"%s\"", settings.getDatafileSeparator());
appendfln(result, "set timefmt '%s'", settings.getTimefmt());
appendfln(result, "set datafile separator \"%s\"", settings.getDatafileSeparator());
appendfln(result, "set timefmt '%s'", settings.getTimefmt());
final List<AxisSettings> xAxisDefinitions = settings.getAggregates().getXAxisDefinitions(settings, dataSeries);
for (AxisSettings axisSettings : xAxisDefinitions) {
appendln(result, axisSettings.toGnuplotDefinition(settings.isRenderLabels()));
final List<AxisSettings> xAxisDefinitions = settings.getAggregates().getXAxisDefinitions(settings, dataSeries);
for (AxisSettings axisSettings : xAxisDefinitions) {
appendln(result, axisSettings.toGnuplotDefinition(settings.isRenderLabels()));
}
final List<AxisSettings> yAxisDefinitions = settings.getAggregates().getYAxisDefinitions(settings, dataSeries);
if (dataSeries.isEmpty()) {
// If there is no data, then Gnuplot won't generate an image.
// Workaround is to explicitly specify the y-axis range.
// We choose a range for which no ticks are defined. This creates an empty
// y-axis.
yAxisDefinitions.forEach(s -> s.setFrom("0"));
yAxisDefinitions.forEach(s -> s.setFrom("-1"));
}
for (AxisSettings axisSettings : yAxisDefinitions) {
appendln(result, axisSettings.toGnuplotDefinition(settings.isRenderLabels()));
}
appendfln(result, "set grid");
appendfln(result, "set output \"%s\"", settings.getOutput().toAbsolutePath().toString().replace("\\", "/"));
appendfln(result, "set key font \",%d\"", KEY_FONT_SIZE);
appendfln(result, "set tics font \",%d\"", GnuplotSettings.TICKS_FONT_SIZE);
if (!settings.isRenderLabels()) {
appendln(result, "set nokey");
} else {
if (settings.isKeyOutside()) {
appendfln(result, "set key outside");
} else {
// make sure left and right margins are always the same
// this is need to be able to zoom in by selecting a region
// (horizontal: 1 unit = 10px; vertical: 1 unit = 19px)
appendln(result, "set lmargin 11"); // margin 11 -> 110px
appendln(result, "set rmargin 11"); // margin 11 -> 110px
appendln(result, "set tmargin 3"); // margin 3 -> 57px - marker (1)
appendln(result, "set bmargin 4"); // margin 4 -> 76
}
}
appendf(result, "plot ");
settings.getAggregates().addPlots(result, dataSeries);
// Add a plot outside of the visible range. Without this gnuplot would not
// render images when there are not data points on it.
appendf(result, "-1 with lines notitle");
LOGGER.info("{}", result);
return result.toString();
}
final List<AxisSettings> yAxisDefinitions = settings.getAggregates().getYAxisDefinitions(settings, dataSeries);
if (dataSeries.isEmpty()) {
// If there is no data, then Gnuplot won't generate an image.
// Workaround is to explicitly specify the y-axis range.
// We choose a range for which no ticks are defined. This creates an empty
// y-axis.
yAxisDefinitions.forEach(s -> s.setFrom("0"));
yAxisDefinitions.forEach(s -> s.setFrom("-1"));
}
for (AxisSettings axisSettings : yAxisDefinitions) {
appendln(result, axisSettings.toGnuplotDefinition(settings.isRenderLabels()));
}
appendfln(result, "set grid");
appendfln(result, "set output \"%s\"", settings.getOutput().toAbsolutePath().toString().replace("\\", "/"));
appendfln(result, "set key font \",%d\"", KEY_FONT_SIZE);
appendfln(result, "set tics font \",%d\"", GnuplotSettings.TICKS_FONT_SIZE);
if (!settings.isRenderLabels()) {
appendln(result, "set nokey");
} else {
if (settings.isKeyOutside()) {
appendfln(result, "set key outside");
} else {
// make sure left and right margins are always the same
// this is need to be able to zoom in by selecting a region
// (horizontal: 1 unit = 10px; vertical: 1 unit = 19px)
appendln(result, "set lmargin 11"); // margin 11 -> 110px
appendln(result, "set rmargin 11"); // margin 11 -> 110px
appendln(result, "set tmargin 3"); // margin 3 -> 57px - marker (1)
appendln(result, "set bmargin 4"); // margin 4 -> 76
}
}
appendf(result, "plot ");
settings.getAggregates().addPlots(result, dataSeries);
// Add a plot outside of the visible range. Without this gnuplot would not
// render images when there are not data points on it.
appendf(result, "-1 with lines notitle");
LOGGER.info("{}", result);
return result.toString();
}
}

View File

@@ -1,20 +1,19 @@
package org.lucares.recommind.logs;
public enum GnuplotLineType {
LINE("line"),
Points("points");
private String gnuplotLineType;
LINE("line"),
GnuplotLineType(String gnuplotLineType) {
this.gnuplotLineType = gnuplotLineType;
}
Points("points");
private String gnuplotLineType;
GnuplotLineType(String gnuplotLineType) {
this.gnuplotLineType = gnuplotLineType;
}
@Override
public String toString() {
return gnuplotLineType;
}
@Override
public String toString() {
return gnuplotLineType;
}
}

View File

@@ -8,148 +8,148 @@ import org.lucares.pdb.plot.api.AxisScale;
public class GnuplotSettings {
public final static int GNUPLOT_LEFT_MARGIN = 110; // The left margin configured for gnuplot
public final static int GNUPLOT_RIGHT_MARGIN = 110; // The right margin configured for gnuplot
public final static int GNUPLOT_TOP_MARGIN = 57; // The top margin configured for gnuplot
public final static int GNUPLOT_BOTTOM_MARGIN = 76; // The bottom margin configured for gnuplot
public final static int GNUPLOT_TOP_BOTTOM_MARGIN = GNUPLOT_TOP_MARGIN + GNUPLOT_BOTTOM_MARGIN;
public final static int GNUPLOT_LEFT_RIGHT_MARGIN = GNUPLOT_LEFT_MARGIN+GNUPLOT_RIGHT_MARGIN;
public static final int TICKS_FONT_SIZE = 12;
public final static int GNUPLOT_LEFT_MARGIN = 110; // The left margin configured for gnuplot
public final static int GNUPLOT_RIGHT_MARGIN = 110; // The right margin configured for gnuplot
public final static int GNUPLOT_TOP_MARGIN = 57; // The top margin configured for gnuplot
public final static int GNUPLOT_BOTTOM_MARGIN = 76; // The bottom margin configured for gnuplot
public final static int GNUPLOT_TOP_BOTTOM_MARGIN = GNUPLOT_TOP_MARGIN + GNUPLOT_BOTTOM_MARGIN;
public final static int GNUPLOT_LEFT_RIGHT_MARGIN = GNUPLOT_LEFT_MARGIN + GNUPLOT_RIGHT_MARGIN;
public static final int TICKS_FONT_SIZE = 12;
private String terminal = "png";
private int height = 1200;
private int width = 1600;
private String timefmt = "%s"; // time as unix epoch, but as double
private String terminal = "png";
private int height = 1200;
private int width = 1600;
private String timefmt = "%s"; // time as unix epoch, but as double
// set datafile separator
private String datafileSeparator = ",";
// set datafile separator
private String datafileSeparator = ",";
// set output "datausage.png"
private final Path output;
// set output "datausage.png"
private final Path output;
private AxisScale yAxisScale;
private AggregateHandlerCollection aggregates;
private boolean keyOutside = false;
private AxisScale yAxisScale;
private AggregateHandlerCollection aggregates;
private boolean keyOutside = false;
private AxisSettings xAxisSettings = new AxisSettings();
private boolean renderLabels = true;
private int yRangeMin = -1;
private int yRangeMax = -1;
private DateTimeRange dateTimeRange;
private AxisSettings xAxisSettings = new AxisSettings();
private boolean renderLabels = true;
private int yRangeMin = -1;
private int yRangeMax = -1;
private DateTimeRange dateTimeRange;
public GnuplotSettings(final Path output) {
this.output = output;
}
public GnuplotSettings(final Path output) {
this.output = output;
}
public AxisSettings getxAxisSettings() {
return xAxisSettings;
}
public AxisSettings getxAxisSettings() {
return xAxisSettings;
}
public void setxAxisSettings(final AxisSettings xAxisSettings) {
this.xAxisSettings = xAxisSettings;
}
public void setxAxisSettings(final AxisSettings xAxisSettings) {
this.xAxisSettings = xAxisSettings;
}
public String getTerminal() {
return terminal;
}
public String getTerminal() {
return terminal;
}
public void setTerminal(final String terminal) {
this.terminal = terminal;
}
public void setTerminal(final String terminal) {
this.terminal = terminal;
}
public int getHeight() {
return height;
}
public int getHeight() {
return height;
}
public void setHeight(final int height) {
this.height = height;
}
public void setHeight(final int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public int getWidth() {
return width;
}
public void setWidth(final int width) {
this.width = width;
}
public void setWidth(final int width) {
this.width = width;
}
public String getTimefmt() {
return timefmt;
}
public String getTimefmt() {
return timefmt;
}
public void setTimefmt(final String timefmt) {
this.timefmt = timefmt;
}
public void setTimefmt(final String timefmt) {
this.timefmt = timefmt;
}
public String getDatafileSeparator() {
return datafileSeparator;
}
public String getDatafileSeparator() {
return datafileSeparator;
}
public void setDatafileSeparator(final String datafileSeparator) {
this.datafileSeparator = datafileSeparator;
}
public void setDatafileSeparator(final String datafileSeparator) {
this.datafileSeparator = datafileSeparator;
}
public Path getOutput() {
return output;
}
public Path getOutput() {
return output;
}
public void setYAxisScale(final AxisScale yAxisScale) {
this.yAxisScale = yAxisScale;
}
public void setYAxisScale(final AxisScale yAxisScale) {
this.yAxisScale = yAxisScale;
}
public AxisScale getYAxisScale() {
return yAxisScale;
}
public AxisScale getYAxisScale() {
return yAxisScale;
}
public void setAggregates(final AggregateHandlerCollection aggregates) {
this.aggregates = aggregates;
}
public void setAggregates(final AggregateHandlerCollection aggregates) {
this.aggregates = aggregates;
}
public AggregateHandlerCollection getAggregates() {
return aggregates;
}
public AggregateHandlerCollection getAggregates() {
return aggregates;
}
public void setKeyOutside(final boolean keyOutside) {
this.keyOutside = keyOutside;
}
public void setKeyOutside(final boolean keyOutside) {
this.keyOutside = keyOutside;
}
public boolean isKeyOutside() {
return keyOutside;
}
public boolean isKeyOutside() {
return keyOutside;
}
public void renderLabels(final boolean renderLabels) {
this.renderLabels = renderLabels;
}
public void renderLabels(final boolean renderLabels) {
this.renderLabels = renderLabels;
}
public boolean isRenderLabels() {
return renderLabels;
}
public boolean isRenderLabels() {
return renderLabels;
}
public boolean hasYRange() {
return yRangeMin >= 0 && yRangeMax >= 0 && yRangeMin < yRangeMax;
}
public boolean hasYRange() {
return yRangeMin >= 0 && yRangeMax >= 0 && yRangeMin < yRangeMax;
}
public void setYRange(final int yRangeMin, final int yRangeMax) {
this.yRangeMin = yRangeMin;
this.yRangeMax = yRangeMax;
}
public void setYRange(final int yRangeMin, final int yRangeMax) {
this.yRangeMin = yRangeMin;
this.yRangeMax = yRangeMax;
}
public int getYRangeMin() {
return yRangeMin;
}
public int getYRangeMin() {
return yRangeMin;
}
public int getYRangeMax() {
return yRangeMax;
}
public int getYRangeMax() {
return yRangeMax;
}
public void setDateTimeRange(DateTimeRange dateTimeRange) {
this.dateTimeRange = dateTimeRange;
}
public DateTimeRange getDateTimeRange() {
return dateTimeRange;
}
public void setDateTimeRange(DateTimeRange dateTimeRange) {
this.dateTimeRange = dateTimeRange;
}
// plot 'sample.txt' using 1:2 title 'Bytes' with linespoints 2
public DateTimeRange getDateTimeRange() {
return dateTimeRange;
}
// plot 'sample.txt' using 1:2 title 'Bytes' with linespoints 2
}

View File

@@ -2,13 +2,13 @@ package org.lucares.recommind.logs;
public class InternalPlottingException extends Exception {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
public InternalPlottingException() {
super();
}
public InternalPlottingException() {
super();
}
public InternalPlottingException(final String message, final Throwable cause) {
super(message, cause);
}
public InternalPlottingException(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@@ -5,75 +5,75 @@ import java.io.Writer;
import org.lucares.pdb.api.RuntimeIOException;
public class LambdaFriendlyWriter extends Writer{
public class LambdaFriendlyWriter extends Writer {
private final Writer writer;
public LambdaFriendlyWriter(Writer writer) {
this.writer = writer;
}
private final Writer writer;
@Override
public void write(char[] cbuf, int off, int len) {
try {
writer.write(cbuf, off, len);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public void write(int c) {
try {
writer.write(c);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public void write(String str) {
try {
writer.write(str);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public Writer append(CharSequence csq) {
try {
return writer.append(csq);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public Writer append(char c) {
try {
return writer.append(c);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
public LambdaFriendlyWriter(Writer writer) {
this.writer = writer;
}
@Override
public void flush() {
try {
writer.flush();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public void write(char[] cbuf, int off, int len) {
try {
writer.write(cbuf, off, len);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public void close() {
try {
writer.close();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public void write(int c) {
try {
writer.write(c);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public void write(String str) {
try {
writer.write(str);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public Writer append(CharSequence csq) {
try {
return writer.append(csq);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public Writer append(char c) {
try {
return writer.append(c);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public void flush() {
try {
writer.flush();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public void close() {
try {
writer.close();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
}

View File

@@ -2,31 +2,31 @@ package org.lucares.recommind.logs;
public class LineStyle {
private final GnuplotColor color;
private final DashTypes dashType;
private final GnuplotColor color;
private final DashTypes dashType;
public LineStyle(final GnuplotColor color, final DashTypes dashType) {
this.color = color;
this.dashType = dashType;
}
public LineStyle(final GnuplotColor color, final DashTypes dashType) {
this.color = color;
this.dashType = dashType;
}
public String asGnuplotLineStyle() {
return String.format("lt %s dt %s ", //
color.getColor(), //
dashType.toGnuplotDashType()//
);
}
public String asGnuplotLineStyle() {
return String.format("lt %s dt %s ", //
color.getColor(), //
dashType.toGnuplotDashType()//
);
}
@Override
public String toString() {
return asGnuplotLineStyle();
}
@Override
public String toString() {
return asGnuplotLineStyle();
}
public LineStyle brighter() {
return new LineStyle(color.brighter(), dashType);
}
public LineStyle brighter() {
return new LineStyle(color.brighter(), dashType);
}
public LineStyle darker() {
return new LineStyle(color.darker(), dashType);
}
public LineStyle darker() {
return new LineStyle(color.darker(), dashType);
}
}

View File

@@ -2,23 +2,23 @@ package org.lucares.recommind.logs;
public class LongUtils {
private static final int INT_TO_STRING_CACHE_SIZE = 10_000;
private static final String[] INT_TO_STRING;
static {
private static final int INT_TO_STRING_CACHE_SIZE = 10_000;
private static final String[] INT_TO_STRING;
static {
INT_TO_STRING = new String[INT_TO_STRING_CACHE_SIZE];
INT_TO_STRING = new String[INT_TO_STRING_CACHE_SIZE];
for (int i = 0; i < INT_TO_STRING_CACHE_SIZE; i++) {
INT_TO_STRING[i] = String.valueOf(i);
}
}
for (int i = 0; i < INT_TO_STRING_CACHE_SIZE; i++) {
INT_TO_STRING[i] = String.valueOf(i);
}
}
public static String longToString(final long value) {
// using pre-generated strings reduces memory allocation by up to 25%
public static String longToString(final long value) {
// using pre-generated strings reduces memory allocation by up to 25%
if (value < INT_TO_STRING_CACHE_SIZE) {
return INT_TO_STRING[(int) value];
}
return String.valueOf(value);
}
if (value < INT_TO_STRING_CACHE_SIZE) {
return INT_TO_STRING[(int) value];
}
return String.valueOf(value);
}
}

View File

@@ -2,9 +2,9 @@ package org.lucares.recommind.logs;
public class NoDataPointsException extends InternalPlottingException {
private static final long serialVersionUID = 1054594230615520105L;
private static final long serialVersionUID = 1054594230615520105L;
public NoDataPointsException() {
super();
}
public NoDataPointsException() {
super();
}
}

View File

@@ -4,34 +4,34 @@ import java.nio.file.Path;
import java.util.List;
public class PlotResult {
private final Path imagePath;
private final List<DataSeries> dataSeries;
private final Path thumbnail;
private final Path imagePath;
private final List<DataSeries> dataSeries;
private final Path thumbnail;
public PlotResult(final Path imagePath, final List<DataSeries> dataSeries, final Path thumbnail) {
super();
this.imagePath = imagePath;
this.dataSeries = dataSeries;
this.thumbnail = thumbnail;
}
public PlotResult(final Path imagePath, final List<DataSeries> dataSeries, final Path thumbnail) {
super();
this.imagePath = imagePath;
this.dataSeries = dataSeries;
this.thumbnail = thumbnail;
}
public Path getImageName() {
return imagePath.getFileName();
}
public Path getImageName() {
return imagePath.getFileName();
}
public Path getImagePath() {
return imagePath;
}
public Path getImagePath() {
return imagePath;
}
public Path getThumbnailName() {
return thumbnail.getFileName();
}
public Path getThumbnailName() {
return thumbnail.getFileName();
}
public Path getThumbnailPath() {
return thumbnail;
}
public Path getThumbnailPath() {
return thumbnail;
}
public List<DataSeries> getDataSeries() {
return dataSeries;
}
public List<DataSeries> getDataSeries() {
return dataSeries;
}
}

View File

@@ -32,234 +32,229 @@ import org.slf4j.LoggerFactory;
public class Plotter {
private static final Logger LOGGER = LoggerFactory.getLogger(Plotter.class);
private static final Logger METRICS_LOGGER = LoggerFactory.getLogger("org.lucares.metrics.plotter.scatter");
private static final Logger LOGGER = LoggerFactory.getLogger(Plotter.class);
private static final Logger METRICS_LOGGER = LoggerFactory.getLogger("org.lucares.metrics.plotter.scatter");
static final String DEFAULT_GROUP = "<none>";
static final String DEFAULT_GROUP = "<none>";
private final PerformanceDb db;
private final Path tmpBaseDir;
private final Path outputDir;
private final PerformanceDb db;
private final Path tmpBaseDir;
private final Path outputDir;
public Plotter(final PerformanceDb db, final Path tmpBaseDir, final Path outputDir) {
this.db = db;
this.tmpBaseDir = tmpBaseDir;
this.outputDir = outputDir;
public Plotter(final PerformanceDb db, final Path tmpBaseDir, final Path outputDir) {
this.db = db;
this.tmpBaseDir = tmpBaseDir;
this.outputDir = outputDir;
if (!Files.isDirectory(tmpBaseDir, LinkOption.NOFOLLOW_LINKS)) {
throw new IllegalArgumentException(tmpBaseDir + " is not a directory");
if (!Files.isDirectory(tmpBaseDir, LinkOption.NOFOLLOW_LINKS)) {
throw new IllegalArgumentException(tmpBaseDir + " is not a directory");
}
if (!Files.isDirectory(outputDir)) {
throw new IllegalArgumentException(outputDir + " is not a directory");
}
}
if (!Files.isDirectory(outputDir)) {
throw new IllegalArgumentException(outputDir + " is not a directory");
public Path getOutputDir() {
return outputDir;
}
}
public Path getOutputDir() {
return outputDir;
}
public PlotResult plot(final PlotSettings plotSettings) throws InternalPlottingException {
public PlotResult plot(final PlotSettings plotSettings) throws InternalPlottingException {
LOGGER.trace("start plot: {}", plotSettings);
LOGGER.trace("start plot: {}", plotSettings);
final String tmpSubDir = uniqueDirectoryName();
final Path tmpDir = tmpBaseDir.resolve(tmpSubDir);
try {
Files.createDirectories(tmpDir);
final List<DataSeries> dataSeries = Collections.synchronizedList(new ArrayList<>());
final String query = plotSettings.getQuery();
final List<String> groupBy = plotSettings.getGroupBy();
final int height = plotSettings.getHeight();
final int width = plotSettings.getWidth();
final DateTimeRange dateRange = plotSettings.dateRange();
final OffsetDateTime dateFrom = dateRange.getStart();
final OffsetDateTime dateTo = dateRange.getEnd();
final Result result = db.get(new Query(query, dateRange), groupBy);
final long start = System.nanoTime();
final AtomicInteger idCounter = new AtomicInteger(0);
result.getGroups().stream().parallel().forEach(groupResult -> {
final String tmpSubDir = uniqueDirectoryName();
final Path tmpDir = tmpBaseDir.resolve(tmpSubDir);
try {
final CsvSummary csvSummary = toCsvDeduplicated(groupResult, tmpDir, dateFrom, dateTo, plotSettings);
Files.createDirectories(tmpDir);
final List<DataSeries> dataSeries = Collections.synchronizedList(new ArrayList<>());
final int id = idCounter.incrementAndGet();
final String title = title(groupResult.getGroupedBy(), csvSummary);
final DataSeries dataSerie = new FileBackedDataSeries(id, title, csvSummary);
if (dataSerie.getValues() > 0) {
dataSeries.add(dataSerie);
}
} catch (final Exception e) {
throw new IllegalStateException(e);
final String query = plotSettings.getQuery();
final List<String> groupBy = plotSettings.getGroupBy();
final int height = plotSettings.getHeight();
final int width = plotSettings.getWidth();
final DateTimeRange dateRange = plotSettings.dateRange();
final OffsetDateTime dateFrom = dateRange.getStart();
final OffsetDateTime dateTo = dateRange.getEnd();
final Result result = db.get(new Query(query, dateRange), groupBy);
final long start = System.nanoTime();
final AtomicInteger idCounter = new AtomicInteger(0);
result.getGroups().stream().parallel().forEach(groupResult -> {
try {
final CsvSummary csvSummary = toCsvDeduplicated(groupResult, tmpDir, dateFrom, dateTo,
plotSettings);
final int id = idCounter.incrementAndGet();
final String title = title(groupResult.getGroupedBy(), csvSummary);
final DataSeries dataSerie = new FileBackedDataSeries(id, title, csvSummary);
if (dataSerie.getValues() > 0) {
dataSeries.add(dataSerie);
}
} catch (final Exception e) {
throw new IllegalStateException(e);
}
});
METRICS_LOGGER.debug("csv generation took: " + (System.nanoTime() - start) / 1_000_000.0 + "ms");
final Limit limitBy = plotSettings.getLimitBy();
final int limit = plotSettings.getLimit();
DataSeries.sortAndLimit(dataSeries, limitBy, limit);
DataSeries.setColors(dataSeries);
final Path outputFile = Files.createTempFile(outputDir, "", ".png");
{
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
final GnuplotSettings gnuplotSettings = new GnuplotSettings(outputFile);
gnuplotSettings.setHeight(height);
gnuplotSettings.setWidth(width);
gnuplotSettings.setDateTimeRange(plotSettings.dateRange());
gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale());
gnuplotSettings.setAggregates(plotSettings.getAggregates());
defineYRange(gnuplotSettings, plotSettings.getYRangeMin(), plotSettings.getYRangeMax(),
plotSettings.getYRangeUnit());
gnuplotSettings.setKeyOutside(plotSettings.isKeyOutside());
gnuplot.plot(gnuplotSettings, dataSeries);
}
final Path thumbnail;
if (plotSettings.isGenerateThumbnail()) {
thumbnail = Files.createTempFile(outputDir, "", ".png");
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
final GnuplotSettings gnuplotSettings = new GnuplotSettings(thumbnail);
gnuplotSettings.setHeight(plotSettings.getThumbnailMaxHeight());
gnuplotSettings.setWidth(plotSettings.getThumbnailMaxWidth());
gnuplotSettings.setDateTimeRange(plotSettings.dateRange());
gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale());
gnuplotSettings.setAggregates(plotSettings.getAggregates());
defineYRange(gnuplotSettings, plotSettings.getYRangeMin(), plotSettings.getYRangeMax(),
plotSettings.getYRangeUnit());
gnuplotSettings.setKeyOutside(false);
gnuplotSettings.renderLabels(false);
gnuplot.plot(gnuplotSettings, dataSeries);
} else {
thumbnail = null;
}
return new PlotResult(outputFile, dataSeries, thumbnail);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Plotting was interrupted.");
} catch (final IOException e) {
throw new InternalPlottingException("Plotting failed: " + e.getMessage(), e);
} finally {
FileUtils.delete(tmpDir);
LOGGER.trace("done plot");
}
});
METRICS_LOGGER.debug("csv generation took: " + (System.nanoTime() - start) / 1_000_000.0 + "ms");
final Limit limitBy = plotSettings.getLimitBy();
final int limit = plotSettings.getLimit();
DataSeries.sortAndLimit(dataSeries, limitBy, limit);
DataSeries.setColors(dataSeries);
final Path outputFile = Files.createTempFile(outputDir, "", ".png");
{
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
final GnuplotSettings gnuplotSettings = new GnuplotSettings(outputFile);
gnuplotSettings.setHeight(height);
gnuplotSettings.setWidth(width);
gnuplotSettings.setDateTimeRange(plotSettings.dateRange());
gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale());
gnuplotSettings.setAggregates(plotSettings.getAggregates());
defineYRange(gnuplotSettings, plotSettings.getYRangeMin(), plotSettings.getYRangeMax(),
plotSettings.getYRangeUnit());
gnuplotSettings.setKeyOutside(plotSettings.isKeyOutside());
gnuplot.plot(gnuplotSettings, dataSeries);
}
final Path thumbnail;
if (plotSettings.isGenerateThumbnail()) {
thumbnail = Files.createTempFile(outputDir, "", ".png");
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
final GnuplotSettings gnuplotSettings = new GnuplotSettings(thumbnail);
gnuplotSettings.setHeight(plotSettings.getThumbnailMaxHeight());
gnuplotSettings.setWidth(plotSettings.getThumbnailMaxWidth());
gnuplotSettings.setDateTimeRange(plotSettings.dateRange());
gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale());
gnuplotSettings.setAggregates(plotSettings.getAggregates());
defineYRange(gnuplotSettings, plotSettings.getYRangeMin(), plotSettings.getYRangeMax(),
plotSettings.getYRangeUnit());
gnuplotSettings.setKeyOutside(false);
gnuplotSettings.renderLabels(false);
gnuplot.plot(gnuplotSettings, dataSeries);
} else {
thumbnail = null;
}
return new PlotResult(outputFile, dataSeries, thumbnail);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Plotting was interrupted.");
} catch (final IOException e) {
throw new InternalPlottingException("Plotting failed: " + e.getMessage(), e);
} finally {
FileUtils.delete(tmpDir);
LOGGER.trace("done plot");
}
}
private void defineYRange(final GnuplotSettings gnuplotSettings, final int yRangeMin, final int yRangeMax,
final TimeRangeUnitInternal yRangeUnit) {
private void defineYRange(final GnuplotSettings gnuplotSettings, final int yRangeMin, final int yRangeMax,
final TimeRangeUnitInternal yRangeUnit) {
if (yRangeUnit != TimeRangeUnitInternal.AUTOMATIC) {
final int min = yRangeUnit.toMilliSeconds(yRangeMin);
final int max = yRangeUnit.toMilliSeconds(yRangeMax);
gnuplotSettings.setYRange(min, max);
if (yRangeUnit != TimeRangeUnitInternal.AUTOMATIC) {
final int min = yRangeUnit.toMilliSeconds(yRangeMin);
final int max = yRangeUnit.toMilliSeconds(yRangeMax);
gnuplotSettings.setYRange(min, max);
}
}
}
private static CsvSummary toCsvDeduplicated(final GroupResult groupResult, final Path tmpDir,
final OffsetDateTime dateFrom, final OffsetDateTime dateTo, final PlotSettings plotSettings)
throws IOException {
private static CsvSummary toCsvDeduplicated(final GroupResult groupResult, final Path tmpDir,
final OffsetDateTime dateFrom, final OffsetDateTime dateTo, final PlotSettings plotSettings) throws IOException {
final long start = System.nanoTime();
final Stream<LongList> timeValueStream = groupResult.asStream();
final long fromEpochMilli = dateFrom.toInstant().toEpochMilli();
final long toEpochMilli = dateTo.toInstant().toEpochMilli();
final boolean useMillis = (toEpochMilli - fromEpochMilli) < TimeUnit.MINUTES.toMillis(5);
final long start = System.nanoTime();
final Stream<LongList> timeValueStream = groupResult.asStream();
final long fromEpochMilli = dateFrom.toInstant().toEpochMilli();
final long toEpochMilli = dateTo.toInstant().toEpochMilli();
final boolean useMillis = (toEpochMilli - fromEpochMilli) < TimeUnit.MINUTES.toMillis(5);
final long minValue = plotSettings.getYRangeUnit() == TimeRangeUnitInternal.AUTOMATIC ? 0
: plotSettings.getYRangeUnit().toMilliSeconds(plotSettings.getYRangeMin());
final long maxValue = plotSettings.getYRangeUnit() == TimeRangeUnitInternal.AUTOMATIC ? Long.MAX_VALUE
: plotSettings.getYRangeUnit().toMilliSeconds(plotSettings.getYRangeMax());
final long minValue = plotSettings.getYRangeUnit() == TimeRangeUnitInternal.AUTOMATIC ? 0
: plotSettings.getYRangeUnit().toMilliSeconds(plotSettings.getYRangeMin());
final long maxValue = plotSettings.getYRangeUnit() == TimeRangeUnitInternal.AUTOMATIC ? Long.MAX_VALUE
: plotSettings.getYRangeUnit().toMilliSeconds(plotSettings.getYRangeMax());
final AggregatorCollection aggregator = plotSettings.getAggregates().createCustomAggregator(tmpDir,
plotSettings, fromEpochMilli, toEpochMilli);
final AggregatorCollection aggregator = plotSettings.getAggregates().createCustomAggregator(tmpDir, plotSettings, fromEpochMilli,
toEpochMilli);
int count = 0; // number of values in the x-axis range (used to compute stats)
int plottedValues = 0;
long statsMaxValue = 0;
double statsCurrentAverage = 0.0;
long ignoredValues = 0;
int count = 0; // number of values in the x-axis range (used to compute stats)
int plottedValues = 0;
long statsMaxValue = 0;
double statsCurrentAverage = 0.0;
long ignoredValues = 0;
final Iterator<LongList> it = timeValueStream.iterator();
while (it.hasNext()) {
final LongList entry = it.next();
final Iterator<LongList> it = timeValueStream.iterator();
while (it.hasNext()) {
final LongList entry = it.next();
for (int i = 0; i < entry.size(); i += 2) {
for (int i = 0; i < entry.size(); i += 2) {
final long epochMilli = entry.get(i);
if (fromEpochMilli > epochMilli || epochMilli > toEpochMilli) {
ignoredValues++;
continue;
}
final long epochMilli = entry.get(i);
if (fromEpochMilli > epochMilli || epochMilli > toEpochMilli) {
ignoredValues++;
continue;
final long value = entry.get(i + 1);
// compute stats
count++;
statsMaxValue = Math.max(statsMaxValue, value);
// compute average (important to do this after 'count' has been incremented)
statsCurrentAverage = statsCurrentAverage + (value - statsCurrentAverage) / count;
// check if value is in the selected y-range
boolean valueIsInYRange = value < minValue || value > maxValue;
if (valueIsInYRange) {
ignoredValues++;
} else {
plottedValues++;
}
aggregator.addValue(valueIsInYRange, epochMilli, value);
}
}
final long value = entry.get(i + 1);
METRICS_LOGGER.debug("wrote {} values to csv in: {}ms (ignored {} values) use millis: {}, grouping={}",
plottedValues, (System.nanoTime() - start) / 1_000_000.0, ignoredValues, Boolean.toString(useMillis),
groupResult.getGroupedBy().asString());
return new CsvSummary(count, plottedValues, statsMaxValue, statsCurrentAverage, aggregator.getAggregatedData());
// compute stats
count++;
statsMaxValue = Math.max(statsMaxValue, value);
}
// compute average (important to do this after 'count' has been incremented)
statsCurrentAverage = statsCurrentAverage + (value - statsCurrentAverage) / count;
static String uniqueDirectoryName() {
return OffsetDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm_ss")) + "_"
+ UUID.randomUUID().toString();
}
// check if value is in the selected y-range
boolean valueIsInYRange = value < minValue || value > maxValue;
if (valueIsInYRange) {
ignoredValues++;
}else {
plottedValues++;
static String title(final Tags tags, final CsvSummary csvSummary) {
final StringBuilder result = new StringBuilder();
final int values = csvSummary.getValues();
final int plottedValues = csvSummary.getPlottedValues();
if (tags.isEmpty()) {
result.append(DEFAULT_GROUP);
} else {
tags.forEach((k, v) -> {
if (result.length() > 0) {
result.append(" / ");
}
result.append(v);
});
}
aggregator.addValue(valueIsInYRange, epochMilli, value);
}
}
METRICS_LOGGER.debug(
"wrote {} values to csv in: {}ms (ignored {} values) use millis: {}, grouping={}",
plottedValues,
(System.nanoTime() - start) / 1_000_000.0, ignoredValues, Boolean.toString(useMillis),
groupResult.getGroupedBy().asString());
return new CsvSummary( count, plottedValues, statsMaxValue, statsCurrentAverage,
aggregator.getAggregatedData());
}
static String uniqueDirectoryName() {
return OffsetDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm_ss")) + "_"
+ UUID.randomUUID().toString();
}
static String title(final Tags tags, final CsvSummary csvSummary) {
final StringBuilder result = new StringBuilder();
final int values = csvSummary.getValues();
final int plottedValues = csvSummary.getPlottedValues();
if (tags.isEmpty()) {
result.append(DEFAULT_GROUP);
} else {
tags.forEach((k, v) -> {
if (result.length() > 0) {
result.append(" / ");
result.append(" (");
if (plottedValues != values) {
result.append(String.format("%,d / %,d", plottedValues, values));
} else {
result.append(String.format("%,d", values));
}
result.append(v);
});
}
result.append(")");
result.append(" (");
if (plottedValues != values) {
result.append(String.format("%,d / %,d", plottedValues, values));
} else {
result.append(String.format("%,d", values));
return result.toString();
}
result.append(")");
return result.toString();
}
}

View File

@@ -13,147 +13,144 @@ import java.util.Locale;
import java.util.concurrent.TimeUnit;
class YAxisTicks {
public static List<String> computeYTicks(final GnuplotSettings settings, final Collection<DataSeries> dataSeries) {
List<String> result = new ArrayList<String>();
final long yRangeMax;
final long yRangeMin;
if (settings.hasYRange()) {
yRangeMax = settings.getYRangeMax();
yRangeMin = settings.getYRangeMin();
} else {
yRangeMax = DataSeries.maxValue(dataSeries);
yRangeMin = 0;
}
final int height = settings.getHeight();
public static List<String> computeYTicks(final GnuplotSettings settings, final Collection<DataSeries> dataSeries) {
List<String> result = new ArrayList<String>();
switch (settings.getYAxisScale()) {
case LINEAR:
result = computeLinearYTicks(height, yRangeMin, yRangeMax);
break;
case LOG10:
result = computeLog10YTicks(height, yRangeMin, yRangeMax);
break;
default:
// use the default
}
return result;
}
private static List<String> computeLog10YTicks(final int height, final long yRangeMin, final long yRangeMax) {
final List<String> ticsLabels = Arrays.asList(//
"\"1ms\" 1", //
"\"2ms\" 2", //
"\"5ms\" 5", //
"\"10ms\" 10", //
"\"20ms\" 20", //
"\"50ms\" 50", //
"\"100ms\" 100", //
"\"200ms\" 200", //
"\"500ms\" 500", //
"\"1s\" 1000", //
"\"2s\" 2000", //
"\"5s\" 5000", //
"\"10s\" 10000", //
"\"30s\" 30000", //
"\"1m\" 60000", //
"\"2m\" 120000", //
"\"5m\" 300000", //
"\"10m\" 600000", //
"\"30m\" 1800000", //
"\"1h\" 3600000", //
"\"2h\" 7200000", //
"\"4h\" 14400000", //
"\"8h\" 28800000", //
"\"16h\" 57600000", //
"\"1d\" 86400000", //
"\"2d\" 172800000", //
"\"1 week\" 604800000", //
"\"2 week\" 1209600000.0", //
"\"4 week\" 2419200000.0", //
"\"3 month\" 7776000000.0", //
"\"1 year\" 31536000000.0", //
"\"5 year\" 157680000000.0", //
"\"10 year\" 315360000000.0"
);
return ticsLabels;
}
private static List<String> computeLinearYTicks(final long height, final long yRangeMin, final long yRangeMax) {
final long plotHeight = height - GnuplotSettings.GNUPLOT_TOP_BOTTOM_MARGIN;
final long maxLabels = plotHeight / (GnuplotSettings.TICKS_FONT_SIZE * 5);
final long range = yRangeMax - yRangeMin;
final long msPerLabel = roundToLinearLabelSteps(range / maxLabels);
final List<String> ticsLabels = new ArrayList<>();
for (long i = yRangeMin; i <= yRangeMax; i += msPerLabel) {
ticsLabels.add("\"" + msToTic(i, msPerLabel) + "\" " + i);
}
return ticsLabels;
}
private static long roundToLinearLabelSteps(final long msPerLabel) {
final List<Long> steps = Arrays.asList(2L, 5L, 10L, 20L, 50L, 100L, 200L, 500L, 1000L, 2000L, 5000L, 10_000L,
20_000L, MINUTES.toMillis(1), MINUTES.toMillis(2), MINUTES.toMillis(5), MINUTES.toMillis(10),
MINUTES.toMillis(15), MINUTES.toMillis(30), HOURS.toMillis(1), HOURS.toMillis(2), HOURS.toMillis(5),
HOURS.toMillis(10), HOURS.toMillis(12), DAYS.toMillis(1), DAYS.toMillis(2), DAYS.toMillis(5),
DAYS.toMillis(7));
for (final Long step : steps) {
if (msPerLabel < step) {
return step;
}
}
return msPerLabel;
}
private static String msToTic(final long ms, final double msPerLabel) {
if (ms < 1000) {
return ms + "ms";
} else if (ms < MINUTES.toMillis(1)) {
if (msPerLabel % 1000 == 0) {
return String.format(Locale.US,"%ds", ms / 1_000);
final long yRangeMax;
final long yRangeMin;
if (settings.hasYRange()) {
yRangeMax = settings.getYRangeMax();
yRangeMin = settings.getYRangeMin();
} else {
return String.format(Locale.US,"%.1fs", ms / 1_000.0);
yRangeMax = DataSeries.maxValue(dataSeries);
yRangeMin = 0;
}
} else if (ms < TimeUnit.HOURS.toMillis(1)) {
final int height = settings.getHeight();
final long sec = (ms % MINUTES.toMillis(1)) / SECONDS.toMillis(1);
final long min = ms / MINUTES.toMillis(1);
if (msPerLabel % MINUTES.toMillis(1) == 0) {
return min + "m ";
switch (settings.getYAxisScale()) {
case LINEAR:
result = computeLinearYTicks(height, yRangeMin, yRangeMax);
break;
case LOG10:
result = computeLog10YTicks(height, yRangeMin, yRangeMax);
break;
default:
// use the default
}
return result;
}
private static List<String> computeLog10YTicks(final int height, final long yRangeMin, final long yRangeMax) {
final List<String> ticsLabels = Arrays.asList(//
"\"1ms\" 1", //
"\"2ms\" 2", //
"\"5ms\" 5", //
"\"10ms\" 10", //
"\"20ms\" 20", //
"\"50ms\" 50", //
"\"100ms\" 100", //
"\"200ms\" 200", //
"\"500ms\" 500", //
"\"1s\" 1000", //
"\"2s\" 2000", //
"\"5s\" 5000", //
"\"10s\" 10000", //
"\"30s\" 30000", //
"\"1m\" 60000", //
"\"2m\" 120000", //
"\"5m\" 300000", //
"\"10m\" 600000", //
"\"30m\" 1800000", //
"\"1h\" 3600000", //
"\"2h\" 7200000", //
"\"4h\" 14400000", //
"\"8h\" 28800000", //
"\"16h\" 57600000", //
"\"1d\" 86400000", //
"\"2d\" 172800000", //
"\"1 week\" 604800000", //
"\"2 week\" 1209600000.0", //
"\"4 week\" 2419200000.0", //
"\"3 month\" 7776000000.0", //
"\"1 year\" 31536000000.0", //
"\"5 year\" 157680000000.0", //
"\"10 year\" 315360000000.0");
return ticsLabels;
}
private static List<String> computeLinearYTicks(final long height, final long yRangeMin, final long yRangeMax) {
final long plotHeight = height - GnuplotSettings.GNUPLOT_TOP_BOTTOM_MARGIN;
final long maxLabels = plotHeight / (GnuplotSettings.TICKS_FONT_SIZE * 5);
final long range = yRangeMax - yRangeMin;
final long msPerLabel = roundToLinearLabelSteps(range / maxLabels);
final List<String> ticsLabels = new ArrayList<>();
for (long i = yRangeMin; i <= yRangeMax; i += msPerLabel) {
ticsLabels.add("\"" + msToTic(i, msPerLabel) + "\" " + i);
}
return ticsLabels;
}
private static long roundToLinearLabelSteps(final long msPerLabel) {
final List<Long> steps = Arrays.asList(2L, 5L, 10L, 20L, 50L, 100L, 200L, 500L, 1000L, 2000L, 5000L, 10_000L,
20_000L, MINUTES.toMillis(1), MINUTES.toMillis(2), MINUTES.toMillis(5), MINUTES.toMillis(10),
MINUTES.toMillis(15), MINUTES.toMillis(30), HOURS.toMillis(1), HOURS.toMillis(2), HOURS.toMillis(5),
HOURS.toMillis(10), HOURS.toMillis(12), DAYS.toMillis(1), DAYS.toMillis(2), DAYS.toMillis(5),
DAYS.toMillis(7));
for (final Long step : steps) {
if (msPerLabel < step) {
return step;
}
}
return msPerLabel;
}
private static String msToTic(final long ms, final double msPerLabel) {
if (ms < 1000) {
return ms + "ms";
} else if (ms < MINUTES.toMillis(1)) {
if (msPerLabel % 1000 == 0) {
return String.format(Locale.US, "%ds", ms / 1_000);
} else {
return String.format(Locale.US, "%.1fs", ms / 1_000.0);
}
} else if (ms < TimeUnit.HOURS.toMillis(1)) {
final long sec = (ms % MINUTES.toMillis(1)) / SECONDS.toMillis(1);
final long min = ms / MINUTES.toMillis(1);
if (msPerLabel % MINUTES.toMillis(1) == 0) {
return min + "m ";
} else {
return min + "m " + sec + "s";
}
} else if (ms < DAYS.toMillis(1)) {
// ms is a multiple of 1 hour, see roundToLinearLabelSteps
final long hour = (ms % DAYS.toMillis(1)) / HOURS.toMillis(1);
final long min = (ms % HOURS.toMillis(1)) / MINUTES.toMillis(1);
final long sec = (ms % MINUTES.toMillis(1)) / SECONDS.toMillis(1);
if (msPerLabel % MINUTES.toMillis(1) == 0) {
return hour + "h " + min + "m ";
} else if (msPerLabel % HOURS.toMillis(1) == 0) {
return hour + "h ";
} else {
return hour + "h " + min + "m " + sec + "s";
}
} else {
return min + "m " + sec + "s";
}
} else if (ms < DAYS.toMillis(1)) {
// ms is a multiple of 1 hour, see roundToLinearLabelSteps
final long hour = (ms % DAYS.toMillis(1)) / HOURS.toMillis(1);
final long min = (ms % HOURS.toMillis(1)) / MINUTES.toMillis(1);
final long sec = (ms % MINUTES.toMillis(1)) / SECONDS.toMillis(1);
// ms is a multiple of 1 day, see roundToLinearLabelSteps
final long day = ms / DAYS.toMillis(1);
final long hour = (ms % DAYS.toMillis(1)) / HOURS.toMillis(1);
if (msPerLabel % MINUTES.toMillis(1) == 0) {
return hour + "h " + min + "m ";
} else if (msPerLabel % HOURS.toMillis(1) == 0) {
return hour + "h ";
} else {
return hour + "h " + min + "m " + sec + "s";
return day + "d " + hour + "h ";
}
} else {
// ms is a multiple of 1 day, see roundToLinearLabelSteps
final long day = ms / DAYS.toMillis(1);
final long hour = (ms % DAYS.toMillis(1)) / HOURS.toMillis(1);
return day + "d " + hour + "h ";
}
}
}