diff --git a/pdb-js/src/app/plot.service.ts b/pdb-js/src/app/plot.service.ts index b0e1040..a4da8c0 100644 --- a/pdb-js/src/app/plot.service.ts +++ b/pdb-js/src/app/plot.service.ts @@ -17,7 +17,8 @@ export class PlotService { this.plotTypes.push(new PlotType("CUM_DISTRIBUTION", "Cumulative Distribution", "cumulative-distribution-chart", true, DataType.Percent, DataType.Duration)); this.plotTypes.push(new PlotType("HISTOGRAM", "Histogram", "histogram", true, DataType.HistogramBin, DataType.HistogramCount)); this.plotTypes.push(new PlotType("PARALLEL", "Parallel Requests", "parallel-requests-chart", true, DataType.Time, DataType.Count)); - this.plotTypes.push(new PlotType("BAR", "Bar", "bar-chart", true, DataType.Group, DataType.Count)); + this.plotTypes.push(new PlotType("BAR", "Bar (number of requests)", "bar-chart", true, DataType.Group, DataType.Count)); + this.plotTypes.push(new PlotType("BOX", "Box", "box-plot", true, DataType.Time, DataType.Duration)); this.plotTypes.push(new PlotType("HEATMAP", "Heatmap", "heatmap", false, DataType.Other, DataType.Other)); this.plotTypes.push(new PlotType("CONTOUR", "Contour", "contour-chart", false, DataType.Time, DataType.Duration)); diff --git a/pdb-js/src/app/visualization-page/visualization-page.component.html b/pdb-js/src/app/visualization-page/visualization-page.component.html index cd209a7..a2a40a3 100644 --- a/pdb-js/src/app/visualization-page/visualization-page.component.html +++ b/pdb-js/src/app/visualization-page/visualization-page.component.html @@ -31,7 +31,7 @@ -
+
Intervals (only bar chart): @@ -46,7 +46,7 @@
-
+
Show Tic Labels (bar chart)
diff --git a/pdb-js/src/app/visualization-page/visualization-page.component.ts b/pdb-js/src/app/visualization-page/visualization-page.component.ts index d15a678..1176100 100644 --- a/pdb-js/src/app/visualization-page/visualization-page.component.ts +++ b/pdb-js/src/app/visualization-page/visualization-page.component.ts @@ -92,8 +92,8 @@ export class VisualizationPageComponent implements OnInit { this.y2AxisAvailable = axesTypes.y.length == 2; } - selectedPlotTypesContains(plotTypeId: string){ - return this.selectedPlotType.filter(pt => pt.id == plotTypeId).length > 0; + selectedPlotTypesContains(plotTypeIds: Array){ + return this.selectedPlotType.filter(pt => plotTypeIds.includes(pt.id)).length > 0; } diff --git a/pdb-js/src/assets/img/box-plot.svg b/pdb-js/src/assets/img/box-plot.svg new file mode 100644 index 0000000..2869cf3 --- /dev/null +++ b/pdb-js/src/assets/img/box-plot.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/Aggregate.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/Aggregate.java index 7dfa279..8916a8a 100644 --- a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/Aggregate.java +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/Aggregate.java @@ -18,7 +18,9 @@ public enum Aggregate { */ CUM_DISTRIBUTION("Cumulative Distribution"), - HISTOGRAM("Histogram"); + HISTOGRAM("Histogram"), + + BOX("Box"); private final String axisLabel; diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BarChartAggregatorForIntervals.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BarChartAggregatorForIntervals.java index 8e14f69..c545ff0 100644 --- a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BarChartAggregatorForIntervals.java +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BarChartAggregatorForIntervals.java @@ -30,7 +30,7 @@ public class BarChartAggregatorForIntervals implements CustomAggregator, Indexed public BarChartAggregatorForIntervals(final PlotSettings settings) { this.settings = settings; this.interval = settings.getInterval().get(); - buckets = interval.getBuckets(); + buckets = interval.getBuckets(AtomicLong::new); } @Override diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BoxAggregator.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BoxAggregator.java new file mode 100644 index 0000000..f76bd27 --- /dev/null +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BoxAggregator.java @@ -0,0 +1,101 @@ +package org.lucares.pdb.plot.api; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +import org.lucares.recommind.logs.GnuplotAxis; + +public class BoxAggregator implements CustomAggregator, IndexedAggregator { + + private static final double SPACE_BETWEEN_BARS = 0.6; + private Long index = null; + private Long numberOfDataSeries; + + private final String dataName = "$data" + UUID.randomUUID().toString().replace("-", ""); + + private final Interval interval; + + private final Map buckets = new HashMap<>(); + + public BoxAggregator(final PlotSettings settings) { + + this.interval = settings.getInterval().get(); + } + + @Override + public void addValue(final long epochMilli, final long value) { + + final long bucketId = interval.toBucketMiddleTime(epochMilli); + buckets.putIfAbsent(bucketId, new PercentilesAggregator()); + buckets.get(bucketId).addValue(epochMilli, value); + } + + @Override + public AggregatedData getAggregatedData() { + // not needed - usually this method is used to write the data to file, but bar + // charts use inline data + return null; + } + + @Override + public Aggregate getType() { + return Aggregate.BOX; + } + + @Override + public void setIndex(final long index, final long numberOfDataSeries) { + this.index = index; + this.numberOfDataSeries = numberOfDataSeries; + } + + @Override + public long getIndex() throws IllegalStateException { + if (this.index == null) { + throw new IllegalStateException("index was not set"); + } + return this.index; + } + + @Override + public long getNumberOfDataSeries() throws IllegalStateException { + if (this.numberOfDataSeries == null) { + throw new IllegalStateException("index was not set"); + } + return this.numberOfDataSeries; + } + + public Object getDataName() { + return dataName; + } + + public Interval getInterval() { + return interval; + } + + public String asCsv(final boolean renderLabels) { + final StringBuilder csv = new StringBuilder(); + int i = 0; + for (final long bucketId : buckets.keySet()) { + final Percentiles percentiles = buckets.get(bucketId).getPercentiles(); + csv.append(String.format(Locale.US, "%d,%d,%d,%d,%d,%d", // + bucketId / 1000, // + percentiles.get("0.000"), // + percentiles.get("25.000"), // + percentiles.get("50.000"), // + percentiles.get("75.000"), // + percentiles.get("100.000")// + )); + csv.append("\n"); + i++; + } + return csv.toString(); + } + + public String renderLabels(final GnuplotAxis xAxis) { + final StringBuilder result = new StringBuilder(); + return result.toString(); + } + +} diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BoxChartHandler.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BoxChartHandler.java new file mode 100644 index 0000000..6e8d848 --- /dev/null +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/BoxChartHandler.java @@ -0,0 +1,116 @@ +package org.lucares.pdb.plot.api; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; + +import org.lucares.recommind.logs.AxisSettings; +import org.lucares.recommind.logs.AxisTime; +import org.lucares.recommind.logs.DataSeries; +import org.lucares.recommind.logs.GnuplotAxis; +import org.lucares.recommind.logs.GnuplotLineType; +import org.lucares.recommind.logs.GnuplotSettings; +import org.lucares.recommind.logs.LineStyle; +import org.lucares.recommind.logs.Type; + +public class BoxChartHandler extends AggregateHandler { + + @Override + Type getAxisType(final 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 + Aggregate getAggregateType() { + return Aggregate.BOX; + } + + @Override + AxisSettings createXAxisSettings(final GnuplotSettings settings, final Collection dataSeries) { + final AxisSettings result = AxisTime.createXAxis(settings); + result.setAxis(getxAxis()); + result.setShowGrid(getxAxis() == GnuplotAxis.X1); + + return result; + } + + @Override + String beforePlot(final CustomAggregator aggregator, final GnuplotSettings settings) { + final StringBuilder result = new StringBuilder(); + + final BoxAggregator boxAggregator = (BoxAggregator) aggregator; + + appendfln(result, "%s < title) { + final BoxAggregator boxAggregator = (BoxAggregator) aggregator; + + final String candlestick = formatln( + "'%s' using 1:3:2:6:5:(%.1f) %s axes %s with %s whiskerbars 0.5 fs empty %s linewidth 1, \\", // + boxAggregator.getDataName(), // + width(boxAggregator.getInterval().getIntervalTimeUnit()), // + gnuplotTitle(title), // + gnuplotXYAxis(), // + GnuplotLineType.BOX, // + lineStyle.asGnuplotLineStyle()// + ); + final String median = formatln( + "'%s' using 1:4:4:4:4:(%.1f) axes %s with candlesticks notitle fs empty %s linewidth 2, \\", // + boxAggregator.getDataName(), // + width(boxAggregator.getInterval().getIntervalTimeUnit()), // + gnuplotXYAxis(), // + lineStyle.asGnuplotLineStyle()); + return candlestick + median; + } + + private double width(final IntervalTimeUnit intervalTimeUnit) { + switch (intervalTimeUnit) { + case SECOND: + return 1; + case MINUTE: + return 60; + case HOUR: + return 3600; + case DAY: + return 86400; + case WEEK: + return 24 * 3600; + case MONTH: + return 30 * 24 * 3600; + case YEAR: + return 365 * 24 * 3600; + default: + throw new IllegalArgumentException("Unexpected value: " + this); + } + } + + @Override + CustomAggregator createCustomAggregator(final Path tmpDir, final PlotSettings plotSettings, + final long fromEpochMilli, final long toEpochMilli) { + if (plotSettings.getInterval().isPresent()) { + return new BoxAggregator(plotSettings); + } else { + return null; + } + } + +} diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/Interval.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/Interval.java index 1eefae6..3eefc60 100644 --- a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/Interval.java +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/Interval.java @@ -3,7 +3,7 @@ package org.lucares.pdb.plot.api; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; import org.lucares.pdb.api.DateTimeRange; import org.lucares.pdb.datastore.internal.LongToDateBucket; @@ -51,6 +51,27 @@ public class Interval { return bucketer.toPartitionId(epochMilli); } + public long toBucketMiddleTime(final long epochMilli) { + switch (intervalTimeUnit) { + case SECOND: + return epochMilli - epochMilli % 1000 + 500; + case MINUTE: + return epochMilli - epochMilli % 60000 + 30000; + case HOUR: + return epochMilli - epochMilli % 3600000 + 1800000; + case DAY: + return epochMilli - epochMilli % 86400000 + 43200000; +// case WEEK: +// return "YYYY-ww"; // use week based year! Otherwise intervals over the year boundary will be wrong +// case MONTH: +// return "yyyy-MM"; +// case YEAR: +// return "yyyy"; + default: + throw new IllegalArgumentException("Unexpected value: " + intervalTimeUnit); + } + } + public IntervalTimeUnit getIntervalTimeUnit() { return intervalTimeUnit; } @@ -72,13 +93,13 @@ public class Interval { return null; } - public Map getBuckets() { - final Map result = new HashMap<>(); + public Map getBuckets(final Supplier initialValueSupplier) { + final Map result = new HashMap<>(); final List bucketIds = bucketer.toPartitionIds(dateTimeRange.getStart(), dateTimeRange.getEnd(), intervalTimeUnit.toChronoUnit()); for (final String bucketId : bucketIds) { - result.put(bucketId, new AtomicLong(0)); + result.put(bucketId, initialValueSupplier.get()); } return result; diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PercentilesAggregator.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PercentilesAggregator.java index 618d6d7..cf6d670 100644 --- a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PercentilesAggregator.java +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PercentilesAggregator.java @@ -12,6 +12,7 @@ public class PercentilesAggregator { private long cumulativeCount = 0; + private long minValue = Long.MAX_VALUE; private long maxValue = 0; private final Percentiles percentiles = new Percentiles(POINTS); @@ -26,11 +27,12 @@ public class PercentilesAggregator { public ToPercentiles(final long totalValues) { this.totalValues = totalValues; stepSize = 100.0 / POINTS; - nextPercentile = stepSize; + nextPercentile = 0; } @Override public void accept(final long duration, final long count) { + minValue = Math.min(minValue, duration); maxValue = duration; cumulativeCount += count; @@ -54,6 +56,7 @@ public class PercentilesAggregator { public void collect(final LongLongHashMap map) { map.forEachOrdered(this); + percentiles.put("0.000", minValue); percentiles.put("100.000", maxValue); } diff --git a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotFileGenerator.java b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotFileGenerator.java index 8ce739b..5126ba4 100644 --- a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotFileGenerator.java +++ b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotFileGenerator.java @@ -66,6 +66,7 @@ public class GnuplotFileGenerator implements Appender { // appendfln(result, "set xrange [-1:1]"); appendfln(result, "set boxwidth 0.5"); + // appendfln(result, "set boxwidth 3600"); appendfln(result, "set style fill transparent solid 0.5"); @@ -75,7 +76,7 @@ public class GnuplotFileGenerator implements Appender { // render images when there are not data points on it. appendf(result, "-1 with lines notitle"); - LOGGER.debug("{}", result); + LOGGER.info("{}", result); return result.toString(); } diff --git a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotLineType.java b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotLineType.java index a5fb428..75ebbbc 100644 --- a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotLineType.java +++ b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotLineType.java @@ -5,6 +5,8 @@ public enum GnuplotLineType { Bar("boxes"), + BOX("candlesticks"), + Points("points"); private String gnuplotLineType; diff --git a/pdb-plotting/src/main/java/org/lucares/recommind/logs/LineStyle.java b/pdb-plotting/src/main/java/org/lucares/recommind/logs/LineStyle.java index 0165ba8..dcc2b00 100644 --- a/pdb-plotting/src/main/java/org/lucares/recommind/logs/LineStyle.java +++ b/pdb-plotting/src/main/java/org/lucares/recommind/logs/LineStyle.java @@ -13,13 +13,7 @@ public class LineStyle { } private String asGnuplotLineStyle(final String colorHex) { - // TODO revert -// return String.format("lt rgb \"#%s\" dt %s ", // -// colorHex, // -// dashType.toGnuplotDashType()// -// ); - - return String.format("lt rgb \"#%s\" ", // + return String.format("linetype rgb \"#%s\" ", // colorHex// ); } diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/PlotSettingsTransformer.java b/pdb-ui/src/main/java/org/lucares/pdbui/PlotSettingsTransformer.java index da47781..2eb2042 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/PlotSettingsTransformer.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/PlotSettingsTransformer.java @@ -5,6 +5,7 @@ import java.util.List; import org.lucares.pdb.plot.api.Aggregate; import org.lucares.pdb.plot.api.AggregateHandlerCollection; import org.lucares.pdb.plot.api.BarChartHandler; +import org.lucares.pdb.plot.api.BoxChartHandler; import org.lucares.pdb.plot.api.CumulativeDistributionHandler; import org.lucares.pdb.plot.api.HistogramHandler; import org.lucares.pdb.plot.api.Interval; @@ -62,6 +63,9 @@ class PlotSettingsTransformer { case BAR: aggregateHandlerCollection.addAggregateHandler(new BarChartHandler()); break; + case BOX: + aggregateHandlerCollection.addAggregateHandler(new BoxChartHandler()); + break; default: throw new IllegalStateException("unhandled enum: " + aggregate); }