make it possible to draw the legend outside of the plot area

This commit is contained in:
2017-09-30 17:51:33 +02:00
parent d4fd25dc4c
commit 386f211377
9 changed files with 1460 additions and 1402 deletions

View File

@@ -1,171 +1,181 @@
package org.lucares.pdb.plot.api; package org.lucares.pdb.plot.api;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
public class PlotSettings { 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 List<String> groupBy; private List<String> groupBy;
private Limit limitBy; private Limit limitBy;
private int limit; private int limit;
private String dateFrom; private String dateFrom;
private String dateRange; private String dateRange;
private AxisScale yAxisScale; private AxisScale yAxisScale;
private AggreateInternal aggregate; private AggreateInternal aggregate;
public String getQuery() { private boolean keyOutside;
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 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 getDateFrom() { }
return dateFrom;
} public String getDateFrom() {
return dateFrom;
public void setDateFrom(final String dateFrom) { }
this.dateFrom = dateFrom;
} public void setDateFrom(final String dateFrom) {
this.dateFrom = dateFrom;
public String getDateRange() { }
return dateRange;
} public String getDateRange() {
return dateRange;
public void setDateRange(final String dateRange) { }
this.dateRange = dateRange;
} public void setDateRange(final String dateRange) {
this.dateRange = dateRange;
public OffsetDateTime dateFrom() { }
if (StringUtils.isEmpty(dateFrom)) { public OffsetDateTime dateFrom() {
return OffsetDateTime.ofInstant(Instant.ofEpochMilli(Long.MIN_VALUE), ZoneOffset.UTC); if (StringUtils.isEmpty(dateFrom)) {
} else {
return LocalDateTime.parse(dateFrom, DATE_FORMAT).atOffset(ZoneOffset.UTC); return OffsetDateTime.ofInstant(Instant.ofEpochMilli(Long.MIN_VALUE), ZoneOffset.UTC);
} } else {
} return LocalDateTime.parse(dateFrom, DATE_FORMAT).atOffset(ZoneOffset.UTC);
}
public OffsetDateTime dateTo() { }
if (StringUtils.isEmpty(dateRange)) { public OffsetDateTime dateTo() {
return OffsetDateTime.ofInstant(Instant.ofEpochMilli(Long.MAX_VALUE), ZoneOffset.UTC);
} else { if (StringUtils.isEmpty(dateRange)) {
final int period = Integer.parseInt(dateRange.split(" ")[0]); return OffsetDateTime.ofInstant(Instant.ofEpochMilli(Long.MAX_VALUE), ZoneOffset.UTC);
final ChronoUnit unit = toChronoUnit(dateRange.split(" ")[1]); } else {
final int period = Integer.parseInt(dateRange.split(" ")[0]);
return dateFrom().plus(period, unit); final ChronoUnit unit = toChronoUnit(dateRange.split(" ")[1]);
}
} return dateFrom().plus(period, unit);
}
private ChronoUnit toChronoUnit(final String string) { }
switch (string) { private ChronoUnit toChronoUnit(final String string) {
case "second":
case "seconds": switch (string) {
return ChronoUnit.SECONDS; case "second":
case "minute": case "seconds":
case "minutes": return ChronoUnit.SECONDS;
return ChronoUnit.MINUTES; case "minute":
case "hour": case "minutes":
case "hours": return ChronoUnit.MINUTES;
return ChronoUnit.HOURS; case "hour":
case "day": case "hours":
case "days": return ChronoUnit.HOURS;
return ChronoUnit.DAYS; case "day":
case "week": case "days":
case "weeks": return ChronoUnit.DAYS;
return ChronoUnit.WEEKS; case "week":
case "month": case "weeks":
case "months": return ChronoUnit.WEEKS;
return ChronoUnit.MONTHS; case "month":
default: case "months":
throw new IllegalArgumentException(string + " is an unknown chrono unit"); return ChronoUnit.MONTHS;
} default:
} throw new IllegalArgumentException(string + " is an unknown chrono unit");
}
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 + ", groupBy=" + groupBy @Override
+ ", limitBy=" + limitBy + ", limit=" + limit + ", dateFrom=" + dateFrom + ", dateRange=" + dateRange public String toString() {
+ ", axisScale=" + yAxisScale + ", aggregate="+aggregate+"]"; return "PlotSettings [query=" + query + ", height=" + height + ", width=" + width + ", groupBy=" + groupBy
} + ", limitBy=" + limitBy + ", limit=" + limit + ", dateFrom=" + dateFrom + ", dateRange=" + dateRange
+ ", axisScale=" + yAxisScale + ", aggregate="+aggregate+", keyOutside="+keyOutside+"]";
public void setAggregate(AggreateInternal aggregate) { }
this.aggregate = aggregate;
} public void setAggregate(AggreateInternal aggregate) {
this.aggregate = aggregate;
public AggreateInternal getAggregate() { }
return aggregate;
} public AggreateInternal getAggregate() {
} return aggregate;
}
public void setKeyOutside(boolean keyOutside) {
this.keyOutside = keyOutside;
}
public boolean isKeyOutside() {
return keyOutside;
}
}

View File

@@ -1,87 +1,92 @@
package org.lucares.recommind.logs; package org.lucares.recommind.logs;
import java.util.Collection; import java.util.Collection;
import org.lucares.pdb.plot.api.AggreateInternal; import org.lucares.pdb.plot.api.AggreateInternal;
import org.lucares.pdb.plot.api.AxisScale; import org.lucares.pdb.plot.api.AxisScale;
public class GnuplotFileGenerator { public class GnuplotFileGenerator {
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(), appendfln(result, "set terminal %s noenhanced size %d,%d", settings.getTerminal(), settings.getWidth(),
settings.getHeight()); settings.getHeight());
appendfln(result, "set datafile separator \"%s\"", settings.getDatafileSeparator()); appendfln(result, "set datafile separator \"%s\"", settings.getDatafileSeparator());
int count = 1; int count = 1;
if (settings.getAggregate() != AggreateInternal.NONE) if (settings.getAggregate() != AggreateInternal.NONE)
{ {
for (final DataSeries dataSerie : dataSeries) { for (final DataSeries dataSerie : dataSeries) {
appendfln(result, "stats '%s' using 2 prefix \"A%d\"", dataSerie.getDataFile(),count); appendfln(result, "stats '%s' using 2 prefix \"A%d\"", dataSerie.getDataFile(),count);
count++; count++;
} }
} }
appendfln(result, "set timefmt '%s'", settings.getTimefmt()); appendfln(result, "set timefmt '%s'", settings.getTimefmt());
appendfln(result, "set xdata time"); appendfln(result, "set xdata time");
appendfln(result, "set format x \"%s\"", settings.getFormatX()); appendfln(result, "set format x \"%s\"", settings.getFormatX());
appendfln(result, "set xlabel \"%s\"", settings.getXlabel()); appendfln(result, "set xlabel \"%s\"", settings.getXlabel());
appendfln(result, "set xtics rotate by %d", settings.getRotateXAxisLabel()); appendfln(result, "set xtics rotate by %d", settings.getRotateXAxisLabel());
appendfln(result, "set xrange [\"%s\":\"%s\"]", settings.getDateFrom(), settings.getDateTo()); appendfln(result, "set xrange [\"%s\":\"%s\"]", settings.getDateFrom(), settings.getDateTo());
final long graphOffset = settings.getYAxisScale() == AxisScale.LINEAR ? 0 : 1; final long graphOffset = settings.getYAxisScale() == AxisScale.LINEAR ? 0 : 1;
appendfln(result, "set yrange [\""+graphOffset+"\":]"); appendfln(result, "set yrange [\""+graphOffset+"\":]");
appendfln(result, "set ylabel \"%s\"", settings.getYlabel()); appendfln(result, "set ylabel \"%s\"", settings.getYlabel());
switch (settings.getYAxisScale()) { switch (settings.getYAxisScale()) {
case LINEAR: case LINEAR:
break; break;
case LOG10: case LOG10:
appendfln(result, "set logscale y"); appendfln(result, "set logscale y");
break; break;
case LOG2: case LOG2:
appendfln(result, "set logscale y 2"); appendfln(result, "set logscale y 2");
break; break;
} }
appendfln(result, "set grid"); appendfln(result, "set grid");
appendfln(result, "set output \"%s\"", settings.getOutput().toAbsolutePath().toString().replace("\\", "/")); appendfln(result, "set output \"%s\"", settings.getOutput().toAbsolutePath().toString().replace("\\", "/"));
// marker lines that show which area will be zoomed // marker lines that show which area will be zoomed
final long minDate = Long.parseLong(settings.getDateFrom()); final long minDate = Long.parseLong(settings.getDateFrom());
final long maxDate = Long.parseLong(settings.getDateTo()); final long maxDate = Long.parseLong(settings.getDateTo());
appendfln(result, "set arrow from "+(minDate + (maxDate-minDate)*0.25)+","+graphOffset+" rto graph 0,1 lt 3 lc rgb \"#EEEEEE\" nohead"); appendfln(result, "set arrow from "+(minDate + (maxDate-minDate)*0.25)+","+graphOffset+" rto graph 0,1 lt 3 lc rgb \"#EEEEEE\" nohead");
appendfln(result, "set arrow from "+(minDate + (maxDate-minDate)*0.75)+","+graphOffset+" rto graph 0,1 lc rgb \"#EEEEEE\" nohead"); appendfln(result, "set arrow from "+(minDate + (maxDate-minDate)*0.75)+","+graphOffset+" rto graph 0,1 lc rgb \"#EEEEEE\" nohead");
appendf(result, "plot "); if (settings.isKeyOutside()){
appendfln(result, "set key outside");
count = 1; }
for (final DataSeries dataSerie : dataSeries) { appendfln(result, "set key font \",10\"");
appendfln(result, "'%s' using 1:2 title '%s' with points, \\", dataSerie.getDataFile(),
dataSerie.getTitle()); appendf(result, "plot ");
if (settings.getAggregate() == AggreateInternal.MEAN) {
appendfln(result, "A%d_mean title '%s Mean', \\", count, dataSerie.getTitle(), count = 1;
dataSerie.getTitle()); for (final DataSeries dataSerie : dataSeries) {
} appendfln(result, "'%s' using 1:2 title '%s' with points, \\", dataSerie.getDataFile(),
count++; dataSerie.getTitle());
} if (settings.getAggregate() == AggreateInternal.MEAN) {
appendfln(result, "A%d_mean title '%s Mean', \\", count, dataSerie.getTitle(),
return result.toString(); dataSerie.getTitle());
} }
count++;
private void appendfln(final StringBuilder builder, final String format, final Object... args) { }
builder.append(String.format(format + "\n", args));
} return result.toString();
}
private void appendf(final StringBuilder builder, final String format, final Object... args) {
builder.append(String.format(format, args)); private void appendfln(final StringBuilder builder, final String format, final Object... args) {
} builder.append(String.format(format + "\n", args));
}
}
private void appendf(final StringBuilder builder, final String format, final Object... args) {
builder.append(String.format(format, args));
}
}

View File

@@ -1,150 +1,159 @@
package org.lucares.recommind.logs; package org.lucares.recommind.logs;
import java.nio.file.Path; import java.nio.file.Path;
import org.lucares.pdb.plot.api.AggreateInternal; import org.lucares.pdb.plot.api.AggreateInternal;
import org.lucares.pdb.plot.api.AxisScale; import org.lucares.pdb.plot.api.AxisScale;
public class GnuplotSettings { public class GnuplotSettings {
private String terminal = "png"; private String terminal = "png";
private int height = 1200; private int height = 1200;
private int width = 1600; private int width = 1600;
private String timefmt = "%s"; //"%Y-%m-%dT%H:%M:%S"; // TODO @ahr timefmt private String timefmt = "%s"; //"%Y-%m-%dT%H:%M:%S"; // TODO @ahr timefmt
// set format for x-axis // set format for x-axis
private String formatX = "%Y-%m-%d %H:%M:%S"; private String formatX = "%Y-%m-%d %H:%M:%S";
// set datafile separator // set datafile separator
private String datafileSeparator = ","; private String datafileSeparator = ",";
// set xlabel // set xlabel
private String xlabel = "Time"; private String xlabel = "Time";
// set ylabel // set ylabel
private String ylabel = "Duration in ms"; private String ylabel = "Duration in ms";
// set output "datausage.png" // set output "datausage.png"
private final Path output; private final Path output;
// set xtics rotate by 10 degree // set xtics rotate by 10 degree
private int rotateXAxisLabel = -10; private int rotateXAxisLabel = -10;
private AxisScale yAxisScale; private AxisScale yAxisScale;
private String dateFrom; private String dateFrom;
private String dateTo; private String dateTo;
private AggreateInternal aggregate; private AggreateInternal aggregate;
private boolean keyOutside = false;
public GnuplotSettings(final Path output) {
this.output = output; public GnuplotSettings(final Path output) {
} this.output = output;
}
public int getRotateXAxisLabel() {
return rotateXAxisLabel; public int getRotateXAxisLabel() {
} return rotateXAxisLabel;
}
public void setRotateXAxisLabel(final int rotateXAxisLabel) {
this.rotateXAxisLabel = rotateXAxisLabel; public void setRotateXAxisLabel(final int rotateXAxisLabel) {
} this.rotateXAxisLabel = rotateXAxisLabel;
}
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 getFormatX() {
return formatX; public String getFormatX() {
} return formatX;
}
public void setFormatX(final String formatX) {
this.formatX = formatX; public void setFormatX(final String formatX) {
} this.formatX = formatX;
}
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 String getXlabel() {
return xlabel; public String getXlabel() {
} return xlabel;
}
public void setXlabel(final String xlabel) {
this.xlabel = xlabel; public void setXlabel(final String xlabel) {
} this.xlabel = xlabel;
}
public String getYlabel() {
return ylabel; public String getYlabel() {
} return ylabel;
}
public void setYlabel(final String ylabel) {
this.ylabel = ylabel; public void setYlabel(final String ylabel) {
} this.ylabel = ylabel;
}
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 setDateFrom(final String dateFrom) {
this.dateFrom = dateFrom; public void setDateFrom(final String dateFrom) {
} this.dateFrom = dateFrom;
}
public String getDateFrom() {
return dateFrom; public String getDateFrom() {
} return dateFrom;
}
public void setDateTo(final String dateTo) {
this.dateTo = dateTo; public void setDateTo(final String dateTo) {
} this.dateTo = dateTo;
}
public String getDateTo() {
return dateTo; public String getDateTo() {
} return dateTo;
}
public void setAggregate(AggreateInternal aggregate) {
this.aggregate = aggregate; public void setAggregate(AggreateInternal aggregate) {
} this.aggregate = aggregate;
}
public AggreateInternal getAggregate() {
return aggregate; public AggreateInternal getAggregate() {
} return aggregate;
}
// plot 'sample.txt' using 1:2 title 'Bytes' with linespoints 2
public void setKeyOutside(boolean keyOutside) {
} this.keyOutside = keyOutside;
}
public boolean isKeyOutside() {
return keyOutside;
}
// plot 'sample.txt' using 1:2 title 'Bytes' with linespoints 2
}

View File

@@ -1,284 +1,285 @@
package org.lucares.recommind.logs; package org.lucares.recommind.logs;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.LinkOption; import java.nio.file.LinkOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.Formatter; import java.util.Formatter;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.lucares.pdb.api.Entry; import org.lucares.pdb.api.Entry;
import org.lucares.pdb.api.GroupResult; import org.lucares.pdb.api.GroupResult;
import org.lucares.pdb.api.Result; import org.lucares.pdb.api.Result;
import org.lucares.pdb.api.Tags; import org.lucares.pdb.api.Tags;
import org.lucares.pdb.plot.api.Limit; import org.lucares.pdb.plot.api.Limit;
import org.lucares.pdb.plot.api.PlotSettings; import org.lucares.pdb.plot.api.PlotSettings;
import org.lucares.performance.db.PerformanceDb; import org.lucares.performance.db.PerformanceDb;
import org.lucares.utils.file.FileUtils; import org.lucares.utils.file.FileUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class Plotter { public class Plotter {
private static final Logger LOGGER = LoggerFactory.getLogger(Plotter.class); private static final Logger LOGGER = LoggerFactory.getLogger(Plotter.class);
private static final Logger METRICS_LOGGER = LoggerFactory.getLogger("org.lucares.metrics.plotter"); private static final Logger METRICS_LOGGER = LoggerFactory.getLogger("org.lucares.metrics.plotter");
private static final String DEFAULT_GROUP = "<none>"; private static final String DEFAULT_GROUP = "<none>";
private static final int INT_TO_STRING_CACHE_SIZE= 1000; private static final int INT_TO_STRING_CACHE_SIZE= 1000;
private static final String[] INT_TO_STRING; private static final String[] INT_TO_STRING;
static { 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++){ for (int i = 0; i < INT_TO_STRING_CACHE_SIZE; i++){
INT_TO_STRING[i] = String.valueOf(i); INT_TO_STRING[i] = String.valueOf(i);
} }
} }
private final PerformanceDb db; private final PerformanceDb db;
private final Path tmpBaseDir; private final Path tmpBaseDir;
private final Path outputDir; private final Path outputDir;
public Plotter(final PerformanceDb db, final Path tmpBaseDir, final Path outputDir) { public Plotter(final PerformanceDb db, final Path tmpBaseDir, final Path outputDir) {
this.db = db; this.db = db;
this.tmpBaseDir = tmpBaseDir; this.tmpBaseDir = tmpBaseDir;
this.outputDir = outputDir; this.outputDir = outputDir;
if (!Files.isDirectory(tmpBaseDir, LinkOption.NOFOLLOW_LINKS)) { if (!Files.isDirectory(tmpBaseDir, LinkOption.NOFOLLOW_LINKS)) {
throw new IllegalArgumentException(tmpBaseDir + " is not a directory"); throw new IllegalArgumentException(tmpBaseDir + " is not a directory");
} }
if (!Files.isDirectory(outputDir)) { if (!Files.isDirectory(outputDir)) {
throw new IllegalArgumentException(outputDir + " is not a directory"); throw new IllegalArgumentException(outputDir + " is not a directory");
} }
} }
public Path getOutputDir() { public Path getOutputDir() {
return outputDir; 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 String tmpSubDir = uniqueDirectoryName();
final Path tmpDir = tmpBaseDir.resolve(tmpSubDir); final Path tmpDir = tmpBaseDir.resolve(tmpSubDir);
try { try {
Files.createDirectories(tmpDir); Files.createDirectories(tmpDir);
final List<DataSeries> dataSeries = new ArrayList<>(); final List<DataSeries> dataSeries = new ArrayList<>();
final String query = plotSettings.getQuery(); final String query = plotSettings.getQuery();
final List<String> groupBy = plotSettings.getGroupBy(); final List<String> groupBy = plotSettings.getGroupBy();
final int height = plotSettings.getHeight(); final int height = plotSettings.getHeight();
final int width = plotSettings.getWidth(); final int width = plotSettings.getWidth();
final OffsetDateTime dateFrom = plotSettings.dateFrom(); final OffsetDateTime dateFrom = plotSettings.dateFrom();
final OffsetDateTime dateTo = plotSettings.dateTo(); final OffsetDateTime dateTo = plotSettings.dateTo();
final Result result = db.get(query, groupBy); final Result result = db.get(query, groupBy);
for (final GroupResult groupResult : result.getGroups()) { for (final GroupResult groupResult : result.getGroups()) {
final Stream<Entry> entries = groupResult.asStream(); final Stream<Entry> entries = groupResult.asStream();
final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile()); final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile());
final CsvSummary csvSummary = toCsv(entries, dataFile, dateFrom, dateTo); final CsvSummary csvSummary = toCsv(entries, dataFile, dateFrom, dateTo);
final String title = title(groupResult.getGroupedBy(), csvSummary.getValues()); final String title = title(groupResult.getGroupedBy(), csvSummary.getValues());
final DataSeries dataSerie = new DataSeries(dataFile, title, csvSummary.getValues(), csvSummary.getMaxValue()); final DataSeries dataSerie = new DataSeries(dataFile, title, csvSummary.getValues(), csvSummary.getMaxValue());
if (dataSerie.getValues() > 0) { if (dataSerie.getValues() > 0) {
dataSeries.add(dataSerie); dataSeries.add(dataSerie);
} }
} }
if (dataSeries.isEmpty()) { if (dataSeries.isEmpty()) {
throw new NoDataPointsException(); throw new NoDataPointsException();
} }
sortAndLimit(dataSeries, plotSettings); sortAndLimit(dataSeries, plotSettings);
final Path outputFile = Files.createTempFile(outputDir, "out", ".png"); final Path outputFile = Files.createTempFile(outputDir, "out", ".png");
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir); final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
final GnuplotSettings gnuplotSettings = new GnuplotSettings(outputFile); final GnuplotSettings gnuplotSettings = new GnuplotSettings(outputFile);
gnuplotSettings.setHeight(height); gnuplotSettings.setHeight(height);
gnuplotSettings.setWidth(width); gnuplotSettings.setWidth(width);
defineXAxis(gnuplotSettings, plotSettings.dateFrom(), plotSettings.dateTo()); defineXAxis(gnuplotSettings, plotSettings.dateFrom(), plotSettings.dateTo());
gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale()); gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale());
gnuplotSettings.setAggregate(plotSettings.getAggregate()); gnuplotSettings.setAggregate(plotSettings.getAggregate());
gnuplot.plot(gnuplotSettings, dataSeries); gnuplotSettings.setKeyOutside(plotSettings.isKeyOutside());
gnuplot.plot(gnuplotSettings, dataSeries);
return new PlotResult(outputFile.getFileName(), dataSeries);
} catch (final InterruptedException e) { return new PlotResult(outputFile.getFileName(), dataSeries);
Thread.currentThread().interrupt(); } catch (final InterruptedException e) {
throw new IllegalStateException("Plotting was interrupted."); Thread.currentThread().interrupt();
} catch (final IOException e) { throw new IllegalStateException("Plotting was interrupted.");
throw new InternalPlottingException("Plotting failed: " + e.getMessage(), e); } catch (final IOException e) {
} finally { throw new InternalPlottingException("Plotting failed: " + e.getMessage(), e);
FileUtils.delete(tmpDir); } finally {
LOGGER.trace("done plot"); FileUtils.delete(tmpDir);
} LOGGER.trace("done plot");
} }
}
private void defineXAxis(final GnuplotSettings gnuplotSettings, final OffsetDateTime minDate,
final OffsetDateTime maxDate) { private void defineXAxis(final GnuplotSettings gnuplotSettings, final OffsetDateTime minDate,
final OffsetDateTime maxDate) {
String formatX;
int rotateX; String formatX;
String formattedMinDate; int rotateX;
String formattedMaxDate; String formattedMinDate;
if (minDate.until(maxDate, ChronoUnit.WEEKS) > 1) { String formattedMaxDate;
formatX = "%Y-%m-%d"; if (minDate.until(maxDate, ChronoUnit.WEEKS) > 1) {
rotateX = 0; formatX = "%Y-%m-%d";
} else if (minDate.until(maxDate, ChronoUnit.SECONDS) > 30) { rotateX = 0;
formatX = "%Y-%m-%d %H:%M:%S"; } else if (minDate.until(maxDate, ChronoUnit.SECONDS) > 30) {
rotateX = gnuplotSettings.getRotateXAxisLabel(); formatX = "%Y-%m-%d %H:%M:%S";
} else { rotateX = gnuplotSettings.getRotateXAxisLabel();
formatX = "%Y-%m-%d %H:%M:%.3S"; } else {
rotateX = gnuplotSettings.getRotateXAxisLabel(); formatX = "%Y-%m-%d %H:%M:%.3S";
} rotateX = gnuplotSettings.getRotateXAxisLabel();
formattedMinDate = String.valueOf(minDate.toEpochSecond()); }
formattedMaxDate = String.valueOf(maxDate.toEpochSecond()); formattedMinDate = String.valueOf(minDate.toEpochSecond());
formattedMaxDate = String.valueOf(maxDate.toEpochSecond());
gnuplotSettings.setFormatX(formatX);
gnuplotSettings.setRotateXAxisLabel(rotateX); gnuplotSettings.setFormatX(formatX);
gnuplotSettings.setDateFrom(formattedMinDate); gnuplotSettings.setRotateXAxisLabel(rotateX);
gnuplotSettings.setDateTo(formattedMaxDate); gnuplotSettings.setDateFrom(formattedMinDate);
} gnuplotSettings.setDateTo(formattedMaxDate);
}
private String uniqueDirectoryName() {
return OffsetDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm_ss")) + "_" private String uniqueDirectoryName() {
+ UUID.randomUUID().toString(); return OffsetDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm_ss")) + "_"
} + UUID.randomUUID().toString();
}
private void sortAndLimit(final List<DataSeries> dataSeries, final PlotSettings plotSettings) {
private void sortAndLimit(final List<DataSeries> dataSeries, final PlotSettings plotSettings) {
final Limit limitBy = plotSettings.getLimitBy();
dataSeries.sort(getDataSeriesComparator(limitBy)); final Limit limitBy = plotSettings.getLimitBy();
dataSeries.sort(getDataSeriesComparator(limitBy));
switch (limitBy) {
case FEWEST_VALUES: switch (limitBy) {
case MOST_VALUES: case FEWEST_VALUES:
case MAX_VALUE: case MOST_VALUES:
case MIN_VALUE: case MAX_VALUE:
while (dataSeries.size() > plotSettings.getLimit()) { case MIN_VALUE:
dataSeries.remove(plotSettings.getLimit()); while (dataSeries.size() > plotSettings.getLimit()) {
} dataSeries.remove(plotSettings.getLimit());
break; }
case NO_LIMIT: break;
} case NO_LIMIT:
} }
}
private Comparator<? super DataSeries> getDataSeriesComparator(final Limit limitBy) {
private Comparator<? super DataSeries> getDataSeriesComparator(final Limit limitBy) {
switch (limitBy) {
case MOST_VALUES: switch (limitBy) {
return DataSeries.BY_NUMBER_OF_VALUES.reversed(); case MOST_VALUES:
case FEWEST_VALUES: return DataSeries.BY_NUMBER_OF_VALUES.reversed();
return DataSeries.BY_NUMBER_OF_VALUES; case FEWEST_VALUES:
case MAX_VALUE: return DataSeries.BY_NUMBER_OF_VALUES;
return DataSeries.BY_MAX_VALUE.reversed(); case MAX_VALUE:
case MIN_VALUE: return DataSeries.BY_MAX_VALUE.reversed();
return DataSeries.BY_MAX_VALUE; case MIN_VALUE:
case NO_LIMIT: return DataSeries.BY_MAX_VALUE;
return DataSeries.BY_NUMBER_OF_VALUES; case NO_LIMIT:
} return DataSeries.BY_NUMBER_OF_VALUES;
throw new IllegalStateException("unhandled enum: "+ limitBy); }
} throw new IllegalStateException("unhandled enum: "+ limitBy);
}
private String title(final Tags tags, final int values) {
private String title(final Tags tags, final int values) {
final StringBuilder result = new StringBuilder();
final StringBuilder result = new StringBuilder();
if (tags.isEmpty()) {
result.append(DEFAULT_GROUP); if (tags.isEmpty()) {
} else { result.append(DEFAULT_GROUP);
tags.forEach((k, v) -> { } else {
if (result.length() > 0) { tags.forEach((k, v) -> {
result.append(" / "); if (result.length() > 0) {
} result.append(" / ");
result.append(v); }
}); result.append(v);
} });
}
result.append(" (");
result.append(values); result.append(" (");
result.append(")"); result.append(values);
result.append(")");
return result.toString();
return result.toString();
}
}
private static CsvSummary toCsv(final Stream<Entry> entries, final File dataFile, final OffsetDateTime dateFrom,
final OffsetDateTime dateTo) throws IOException { private static CsvSummary toCsv(final Stream<Entry> entries, final File dataFile, final OffsetDateTime dateFrom,
final OffsetDateTime dateTo) throws IOException {
final long start = System.nanoTime();
int count = 0; final long start = System.nanoTime();
final long fromEpochMilli = dateFrom.toInstant().toEpochMilli(); int count = 0;
final long toEpochMilli = dateTo.toInstant().toEpochMilli(); final long fromEpochMilli = dateFrom.toInstant().toEpochMilli();
final boolean useMillis = (toEpochMilli - fromEpochMilli) < TimeUnit.MINUTES.toMillis(5); final long toEpochMilli = dateTo.toInstant().toEpochMilli();
final boolean useMillis = (toEpochMilli - fromEpochMilli) < TimeUnit.MINUTES.toMillis(5);
long maxValue = 0;
long ignoredValues = 0; long maxValue = 0;
final int separator = ','; long ignoredValues = 0;
final int newline = '\n'; final int separator = ',';
final StringBuilder formattedDateBuilder = new StringBuilder(); final int newline = '\n';
try (final Writer output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.US_ASCII)); final StringBuilder formattedDateBuilder = new StringBuilder();
final Formatter formatter = new Formatter(formattedDateBuilder);) { try (final Writer output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.US_ASCII));
final Formatter formatter = new Formatter(formattedDateBuilder);) {
final Iterator<Entry> it = entries.iterator();
while (it.hasNext()) { final Iterator<Entry> it = entries.iterator();
final Entry entry = it.next(); while (it.hasNext()) {
final Entry entry = it.next();
if (fromEpochMilli <= entry.getEpochMilli() && entry.getEpochMilli() <= toEpochMilli) {
if (fromEpochMilli <= entry.getEpochMilli() && entry.getEpochMilli() <= toEpochMilli) {
final String value = longToString(entry.getValue());
final String formattedDate; final String value = longToString(entry.getValue());
final String formattedDate;
if (useMillis){
formattedDateBuilder.delete(0, formattedDateBuilder.length()); if (useMillis){
formatter.format("%.3f", entry.getEpochMilli() / 1000.0); formattedDateBuilder.delete(0, formattedDateBuilder.length());
formattedDate = formattedDateBuilder.toString(); formatter.format("%.3f", entry.getEpochMilli() / 1000.0);
}else { formattedDate = formattedDateBuilder.toString();
formattedDate = String.valueOf(entry.getEpochMilli() / 1000); }else {
} formattedDate = String.valueOf(entry.getEpochMilli() / 1000);
}
output.write(formattedDate);
output.write(separator); output.write(formattedDate);
output.write(value); output.write(separator);
output.write(newline); output.write(value);
output.write(newline);
count++;
maxValue = Math.max(maxValue, entry.getValue()); count++;
}else { maxValue = Math.max(maxValue, entry.getValue());
ignoredValues++; }else {
} ignoredValues++;
} }
} }
}
METRICS_LOGGER.debug("wrote {} values to csv in: {}ms (ignored {} values) use millis: {}", count, (System.nanoTime() - start) / 1_000_000.0, ignoredValues, Boolean.toString(useMillis));
return new CsvSummary(count, maxValue); METRICS_LOGGER.debug("wrote {} values to csv in: {}ms (ignored {} values) use millis: {}", count, (System.nanoTime() - start) / 1_000_000.0, ignoredValues, Boolean.toString(useMillis));
} return new CsvSummary(count, maxValue);
}
private static String longToString(final long value){
// using pre-generated strings reduces memory allocation by up to 25% private 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]; if (value < INT_TO_STRING_CACHE_SIZE){
} return INT_TO_STRING[(int) value];
return String.valueOf(value); }
} return String.valueOf(value);
} }
}

View File

@@ -1,68 +1,69 @@
package org.lucares.pdbui; package org.lucares.pdbui;
import org.lucares.pdb.plot.api.AggreateInternal; import org.lucares.pdb.plot.api.AggreateInternal;
import org.lucares.pdb.plot.api.AxisScale; import org.lucares.pdb.plot.api.AxisScale;
import org.lucares.pdb.plot.api.Limit; import org.lucares.pdb.plot.api.Limit;
import org.lucares.pdb.plot.api.PlotSettings; import org.lucares.pdb.plot.api.PlotSettings;
import org.lucares.pdbui.domain.Aggregate; import org.lucares.pdbui.domain.Aggregate;
import org.lucares.pdbui.domain.LimitBy; import org.lucares.pdbui.domain.LimitBy;
import org.lucares.pdbui.domain.PlotRequest; import org.lucares.pdbui.domain.PlotRequest;
import org.lucares.pdbui.domain.YAxis; import org.lucares.pdbui.domain.YAxis;
class PlotSettingsTransformer { class PlotSettingsTransformer {
static PlotSettings toSettings(final PlotRequest request) { static PlotSettings toSettings(final PlotRequest request) {
final PlotSettings result = new PlotSettings(); final PlotSettings result = new PlotSettings();
result.setQuery(request.getQuery()); result.setQuery(request.getQuery());
result.setGroupBy(request.getGroupBy()); result.setGroupBy(request.getGroupBy());
result.setHeight(request.getHeight()); result.setHeight(request.getHeight());
result.setWidth(request.getWidth()); result.setWidth(request.getWidth());
result.setLimit(request.getLimit()); result.setLimit(request.getLimit());
result.setLimitBy(toLimit(request.getLimitBy())); result.setLimitBy(toLimit(request.getLimitBy()));
result.setDateFrom(request.getDateFrom()); result.setDateFrom(request.getDateFrom());
result.setDateRange(request.getDateRange()); result.setDateRange(request.getDateRange());
result.setYAxisScale(toAxisScale(request.getAxisScale())); result.setYAxisScale(toAxisScale(request.getAxisScale()));
result.setAggregate(toAggregateInternal(request.getAggregate())); result.setAggregate(toAggregateInternal(request.getAggregate()));
result.setKeyOutside(request.isKeyOutside());
return result;
} return result;
}
private static AggreateInternal toAggregateInternal(Aggregate aggregate) {
switch (aggregate) { private static AggreateInternal toAggregateInternal(Aggregate aggregate) {
case NONE:return AggreateInternal.NONE; switch (aggregate) {
case MEAN:return AggreateInternal.MEAN; case NONE:return AggreateInternal.NONE;
} case MEAN:return AggreateInternal.MEAN;
throw new IllegalStateException("unhandled enum: " + aggregate); }
} throw new IllegalStateException("unhandled enum: " + aggregate);
}
private static AxisScale toAxisScale(final YAxis yAxis) {
switch (yAxis) { private static AxisScale toAxisScale(final YAxis yAxis) {
case LINEAR: switch (yAxis) {
return AxisScale.LINEAR; case LINEAR:
case LOG10: return AxisScale.LINEAR;
return AxisScale.LOG10; case LOG10:
case LOG2: return AxisScale.LOG10;
return AxisScale.LOG2; case LOG2:
default: return AxisScale.LOG2;
throw new IllegalStateException("unhandled enum: " + yAxis); default:
} throw new IllegalStateException("unhandled enum: " + yAxis);
} }
}
private static Limit toLimit(final LimitBy limitBy) {
switch (limitBy) { private static Limit toLimit(final LimitBy limitBy) {
case NO_LIMIT: switch (limitBy) {
return Limit.NO_LIMIT; case NO_LIMIT:
case FEWEST_VALUES: return Limit.NO_LIMIT;
return Limit.FEWEST_VALUES; case FEWEST_VALUES:
case MOST_VALUES: return Limit.FEWEST_VALUES;
return Limit.MOST_VALUES; case MOST_VALUES:
case MAX_VALUE: return Limit.MOST_VALUES;
return Limit.MAX_VALUE; case MAX_VALUE:
case MIN_VALUE: return Limit.MAX_VALUE;
return Limit.MIN_VALUE; case MIN_VALUE:
default: return Limit.MIN_VALUE;
throw new IllegalStateException("unhandled enum: " + limitBy); default:
} throw new IllegalStateException("unhandled enum: " + limitBy);
} }
} }
}

View File

@@ -1,113 +1,123 @@
package org.lucares.pdbui.domain; package org.lucares.pdbui.domain;
import java.util.List; import java.util.List;
public class PlotRequest { public class PlotRequest {
private String query; private String query;
private int height = 1000; private int height = 1000;
private int width = 1000; private int width = 1000;
private List<String> groupBy; private List<String> groupBy;
private LimitBy limitBy = LimitBy.NO_LIMIT; private LimitBy limitBy = LimitBy.NO_LIMIT;
private YAxis yAxis = YAxis.LINEAR; private YAxis yAxis = YAxis.LINEAR;
private int limit = Integer.MAX_VALUE; private int limit = Integer.MAX_VALUE;
private String dateFrom; private String dateFrom;
private String dateRange; private String dateRange;
private Aggregate aggregate = Aggregate.NONE; private Aggregate aggregate = Aggregate.NONE;
public String getQuery() { private boolean keyOutside;
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 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 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;
@Override }
public String toString() {
return query + ":" + height + "x" + width; @Override
} public String toString() {
return query + ":" + height + "x" + width;
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 LimitBy getLimitBy() { }
return limitBy;
} public LimitBy getLimitBy() {
return limitBy;
public void setLimitBy(final LimitBy limitBy) { }
this.limitBy = limitBy;
} public void setLimitBy(final LimitBy 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 getDateFrom() { }
return dateFrom;
} public String getDateFrom() {
return dateFrom;
public void setDateFrom(final String dateFrom) { }
this.dateFrom = dateFrom;
} public void setDateFrom(final String dateFrom) {
this.dateFrom = dateFrom;
public String getDateRange() { }
return dateRange;
} public String getDateRange() {
return dateRange;
public void setDateRange(final String dateRange) { }
if (!dateRange.matches("\\d+ (second|minute|hour|day|week|month)s?")) {
throw new IllegalArgumentException(dateRange + " is not a valid range"); public void setDateRange(final String dateRange) {
} if (!dateRange.matches("\\d+ (second|minute|hour|day|week|month)s?")) {
this.dateRange = dateRange; throw new IllegalArgumentException(dateRange + " is not a valid range");
} }
this.dateRange = dateRange;
public YAxis getAxisScale() { }
return yAxis;
} public YAxis getAxisScale() {
return yAxis;
public void setAxisScale(final YAxis yAxis) { }
this.yAxis = yAxis;
} public void setAxisScale(final YAxis yAxis) {
this.yAxis = yAxis;
public void setAggregate(Aggregate aggregate) { }
this.aggregate = aggregate;
} public void setAggregate(Aggregate aggregate) {
this.aggregate = aggregate;
public Aggregate getAggregate() { }
return aggregate;
} public Aggregate getAggregate() {
} return aggregate;
}
public void setKeyOutside(boolean keyOutside) {
this.keyOutside = keyOutside;
}
public boolean isKeyOutside() {
return keyOutside;
}
}

View File

@@ -1,117 +1,125 @@
html { html {
height: 100%; height: 100%;
margin:0; margin:0;
padding:0; padding:0;
font-size: 14px; font-size: 14px;
} }
body{ body{
display: grid; display: grid;
height: 100vh; height: 100vh;
margin: 0; margin: 0;
grid: grid:
"search_field logo" auto "search_field logo" auto
"search logo" auto "search logo" auto
"navigation navigation" auto "navigation navigation" auto
"result result" 1fr "result result" 1fr
/ 1fr auto; / 1fr auto;
} }
@font-face { @font-face {
font-family: 'FontAwesome'; font-family: 'FontAwesome';
src: url('../fonts/fontawesome-webfont.eot?v=4.7.0'); src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
#logo { .group {
grid-area: logo; display: inline-block;
font-size: 1.2em; }
font-weight: bold;
background-color: black; #logo {
color: white; grid-area: logo;
padding: 3px; font-size: 1.2em;
} font-weight: bold;
background-color: black;
#search-input-wrapper { color: white;
grid-area: search_field; padding: 3px;
} }
#search-bar { #search-input-wrapper {
grid-area: search; grid-area: search_field;
background-color: #aaa; }
padding-bottom:3px;
} #search-bar {
grid-area: search;
#navigation { background-color: #aaa;
grid-area: navigation; padding-bottom:3px;
background-color: #aaa; }
display: flex;
justify-content: space-between; #navigation {
} grid-area: navigation;
background-color: #aaa;
.autocomplete .active { display: flex;
background-color: #AAA; justify-content: space-between;
} }
.autocomplete, #search-input-wrapper .autocomplete.open { .autocomplete .active {
overflow-y: scroll; background-color: #AAA;
} }
#search-input { /* scrollbars are nice, but with them an empty autocomplete box is shown
box-sizing: border-box;
border: 0; .autocomplete, #search-input-wrapper .autocomplete.open {
} overflow-y: scroll;
}
*/
#search-limit-value {
width: 4em; #search-input {
} box-sizing: border-box;
border: 0;
.input_date { }
max-width: 10em;
}
#search-limit-value {
#add-filter { width: 4em;
float:right; }
}
.input_date {
#button-bar { max-width: 10em;
text-align: right; }
}
#add-filter {
#search-submit { float:right;
margin-right:3px; }
}
#button-bar {
#result-view { text-align: right;
grid-area: result; }
background: #eee;
margin: 0; #search-submit {
padding: 0; margin-left:3px;
overflow: hidden; margin-right:3px;
} }
#result-view i { #result-view {
background: white; grid-area: result;
display:inline; background: #eee;
font-style:normal; margin: 0;
line-height: 1.2em; padding: 0;
} overflow: hidden;
}
.center #result-view i {
{ background: white;
display: flex; display:inline;
justify-content: center; font-style:normal;
align-items: center; line-height: 1.2em;
height: 100%; }
}
input:required:invalid { .center
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAT1JREFUeNpi/P//PwMpgImBRMACY/x7/uDX39sXt/67cMoDyOVgMjBjYFbV/8kkqcCBrIER5KS/967s+rmkXxzI5wJiRSBm/v8P7NTfHHFFl5mVdIzhGv4+u///x+xmuAlcdXPB9KeqeLgYd3bDU2ZpRRmwH4DOeAI07QXIRKipYPD35184/nn17CO4p/+cOfjl76+/X4GYAYThGn7/g+Mfh/ZZwjUA/aABpJVhpv6+dQUjZP78Z0YEK7OezS2gwltg64GmfTu6i+HL+mUMP34wgvGvL78ZOEysf8M1sGgZvQIqfA1SDAL8iUUMPIFRQLf+AmMQ4DQ0vYYSrL9vXDz2sq9LFsiX4dLRA0t8OX0SHKzi5bXf2HUMBVA0gN356N7p7xdOS3w5fAgcfNxWtn+BJi9gVVBOQfYPQIABABvRq3BwGT3OAAAAAElFTkSuQmCC); {
background-position: right top; display: flex;
background-repeat: no-repeat; justify-content: center;
box-shadow: none; align-items: center;
} height: 100%;
}
input:required:invalid {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAT1JREFUeNpi/P//PwMpgImBRMACY/x7/uDX39sXt/67cMoDyOVgMjBjYFbV/8kkqcCBrIER5KS/967s+rmkXxzI5wJiRSBm/v8P7NTfHHFFl5mVdIzhGv4+u///x+xmuAlcdXPB9KeqeLgYd3bDU2ZpRRmwH4DOeAI07QXIRKipYPD35184/nn17CO4p/+cOfjl76+/X4GYAYThGn7/g+Mfh/ZZwjUA/aABpJVhpv6+dQUjZP78Z0YEK7OezS2gwltg64GmfTu6i+HL+mUMP34wgvGvL78ZOEysf8M1sGgZvQIqfA1SDAL8iUUMPIFRQLf+AmMQ4DQ0vYYSrL9vXDz2sq9LFsiX4dLRA0t8OX0SHKzi5bXf2HUMBVA0gN356N7p7xdOS3w5fAgcfNxWtn+BJi9gVVBOQfYPQIABABvRq3BwGT3OAAAAAElFTkSuQmCC);
background-position: right top;
background-repeat: no-repeat;
box-shadow: none;
}

View File

@@ -1,330 +1,331 @@
$(document).ready(function(){ $(document).ready(function(){
$('#search-submit').click(plot); $('#search-submit').click(plot);
renderGroupBy(); renderGroupBy();
updateSearchLimitValue(); updateSearchLimitValue();
$('#search-limit-by').change(updateSearchLimitValue); $('#search-limit-by').change(updateSearchLimitValue);
$('#nav_left').click(dateLeftShift); $('#nav_left').click(dateLeftShift);
$('#nav_left_half').click(dateHalfLeftShift); $('#nav_left_half').click(dateHalfLeftShift);
$('#nav_right_half').click(dateHalfRightShift); $('#nav_right_half').click(dateHalfRightShift);
$('#nav_right').click(dateRightShift); $('#nav_right').click(dateRightShift);
$('#zoom_in').click(zoomIn); $('#zoom_in').click(zoomIn);
$('#zoom_out').click(zoomOut); $('#zoom_out').click(zoomOut);
AutoComplete({ AutoComplete({
HttpMethod: "GET", HttpMethod: "GET",
Delay: 300, Delay: 300,
_QueryArg: function() { _QueryArg: function() {
var caretIndex = document.getElementById('search-input').selectionStart + 1; var caretIndex = document.getElementById('search-input').selectionStart + 1;
return 'caretIndex=' + caretIndex + '&query'; return 'caretIndex=' + caretIndex + '&query';
}, },
_Pre: function() { _Pre: function() {
return encodeURI(this.Input.value); return encodeURI(this.Input.value);
}, },
_Post: function(response) { _Post: function(response) {
var result = []; var result = [];
var responseObject = JSON.parse(response); var responseObject = JSON.parse(response);
responseObject['proposals'].forEach(function(item, index){ responseObject['proposals'].forEach(function(item, index){
var proposal = {}; var proposal = {};
proposal['Label'] = item.value; proposal['Label'] = item.value;
proposal['Value'] = item.proposedQuery; proposal['Value'] = item.proposedQuery;
result.push(proposal); result.push(proposal);
}); });
console.log(JSON.stringify(result)); console.log(JSON.stringify(result));
return result; return result;
} }
}); });
}); });
function zoomIn() function zoomIn()
{ {
shiftDate(0.25); shiftDate(0.25);
zoom(0.5); zoom(0.5);
plot(); plot();
} }
function zoomOut() function zoomOut()
{ {
shiftDate(-0.5); shiftDate(-0.5);
zoom(2); zoom(2);
plot(); plot();
} }
function dateLeftShift() function dateLeftShift()
{ {
shiftDate(-1); shiftDate(-1);
plot(); plot();
} }
function dateHalfLeftShift() function dateHalfLeftShift()
{ {
shiftDate(-0.5); shiftDate(-0.5);
plot(); plot();
} }
function dateHalfRightShift() function dateHalfRightShift()
{ {
shiftDate(0.5); shiftDate(0.5);
plot(); plot();
} }
function dateRightShift() function dateRightShift()
{ {
shiftDate(1); shiftDate(1);
plot(); plot();
} }
function zoom(factor) function zoom(factor)
{ {
if (!$('#search-date-range').is(":invalid")) { if (!$('#search-date-range').is(":invalid")) {
var dateRange = $('#search-date-range').val(); var dateRange = $('#search-date-range').val();
var tokens = dateRange.split(/ +/,2); var tokens = dateRange.split(/ +/,2);
if(tokens.length == 2) if(tokens.length == 2)
{ {
var value = parseInt(tokens[0]); var value = parseInt(tokens[0]);
var period = tokens[1]; var period = tokens[1];
var newValue = value*factor; var newValue = value*factor;
while (newValue != Math.round(newValue)){ while (newValue != Math.round(newValue)){
switch (period) { switch (period) {
case "second": case "second":
case "seconds": case "seconds":
if (value == 1) { if (value == 1) {
// we reached the smallest range // we reached the smallest range
} }
else if (value % 2 == 1){ else if (value % 2 == 1){
value = value -1; value = value -1;
} }
break; break;
case "minute": case "minute":
case "minutes": case "minutes":
value = value * 60; value = value * 60;
period = "seconds"; period = "seconds";
break; break;
case "hour": case "hour":
case "hours": case "hours":
value = value * 60; value = value * 60;
period = "minutes"; period = "minutes";
break; break;
case "day": case "day":
case "days": case "days":
value = value * 24; value = value * 24;
period = "hours"; period = "hours";
break; break;
case "week": case "week":
case "weeks": case "weeks":
value = value * 7; value = value * 7;
period = "days"; period = "days";
break; break;
case "month": case "month":
case "months": case "months":
value = value * 30; value = value * 30;
period = "days"; period = "days";
break; break;
default: default:
console.log("unhandled value: "+ period); console.log("unhandled value: "+ period);
break; break;
} }
newValue = value*factor newValue = value*factor
} }
$('#search-date-range').val(newValue + " " + period); $('#search-date-range').val(newValue + " " + period);
} }
} }
} }
function shiftDate(directionalFactor) function shiftDate(directionalFactor)
{ {
var dateBefore = Date.parse($('#search-date-from').val()); var dateBefore = Date.parse($('#search-date-from').val());
var newDate = shiftByInterval(dateBefore, directionalFactor); var newDate = shiftByInterval(dateBefore, directionalFactor);
$('#search-date-from').val(newDate.toString("yyyy-MM-dd HH:mm:ss")); $('#search-date-from').val(newDate.toString("yyyy-MM-dd HH:mm:ss"));
} }
function shiftByInterval(date, directionalFactor) function shiftByInterval(date, directionalFactor)
{ {
if (!$('#search-date-range').is(":invalid")) { if (!$('#search-date-range').is(":invalid")) {
var dateRange = $('#search-date-range').val(); var dateRange = $('#search-date-range').val();
var tokens = dateRange.split(/ +/,2); var tokens = dateRange.split(/ +/,2);
if(tokens.length == 2) if(tokens.length == 2)
{ {
var value = parseInt(tokens[0]); var value = parseInt(tokens[0]);
var period = tokens[1]; var period = tokens[1];
var config = {}; var config = {};
value = directionalFactor * value; value = directionalFactor * value;
switch (period) { switch (period) {
case "second": case "second":
case "seconds": case "seconds":
config = { seconds: value }; config = { seconds: value };
break; break;
case "minute": case "minute":
case "minutes": case "minutes":
config = { minutes: value }; config = { minutes: value };
break; break;
case "hour": case "hour":
case "hours": case "hours":
config = { minutes: 60*value }; config = { minutes: 60*value };
break; break;
case "day": case "day":
case "days": case "days":
config = { days: value }; config = { days: value };
break; break;
case "week": case "week":
case "weeks": case "weeks":
config = { days: 7*value }; config = { days: 7*value };
break; break;
case "month": case "month":
case "months": case "months":
config = { days: 30*value }; config = { days: 30*value };
break; break;
default: default:
break; break;
} }
var newDate = date.add(config); var newDate = date.add(config);
return newDate; return newDate;
} }
} }
return date; return date;
} }
function updateSearchLimitValue () { function updateSearchLimitValue () {
var optionSelected = $('#search-limit-by').find("option:selected"); var optionSelected = $('#search-limit-by').find("option:selected");
var valueSelected = optionSelected.val(); var valueSelected = optionSelected.val();
if (valueSelected == "NO_LIMIT"){ if (valueSelected == "NO_LIMIT"){
$('#search-limit-value').hide(); $('#search-limit-value').hide();
}else{ }else{
$('#search-limit-value').show(); $('#search-limit-value').show();
} }
} }
function renderGroupBy() function renderGroupBy()
{ {
var request = {}; var request = {};
var success = function(response){ var success = function(response){
initSearchGroupBy('#search-group-by-1', response); initSearchGroupBy('#search-group-by-1', response);
initSearchGroupBy('#search-group-by-2', response); initSearchGroupBy('#search-group-by-2', response);
initSearchGroupBy('#search-group-by-3', response); initSearchGroupBy('#search-group-by-3', response);
}; };
var error = function(e) { var error = function(e) {
$('#result-view').text("FAILED: " + JSON.parse(e.responseText).message); $('#result-view').text("FAILED: " + JSON.parse(e.responseText).message);
}; };
getJson("fields", request, success, error); getJson("fields", request, success, error);
} }
function initSearchGroupBy(selector, response) function initSearchGroupBy(selector, response)
{ {
$(selector).empty(); $(selector).empty();
var option = new Option("", ""); var option = new Option("", "");
$(selector).append($(option)); $(selector).append($(option));
response.forEach( response.forEach(
(item, index) => { (item, index) => {
var option = new Option(item, item); var option = new Option(item, item);
$(selector).append($(option)); $(selector).append($(option));
} }
); );
} }
function showLoadingIcon() function showLoadingIcon()
{ {
$('#result-view').html("<div class='center'><div class='uil-cube-css' style='-webkit-transform:scale(0.41)'><div /><div></div><div></div><div></div></div></div>"); $('#result-view').html("<div class='center'><div class='uil-cube-css' style='-webkit-transform:scale(0.41)'><div /><div></div><div></div><div></div></div></div>");
} }
function plot(event){ function plot(event){
if(event){ if(event){
event.preventDefault(); // prevent submit of form which would reload the page event.preventDefault(); // prevent submit of form which would reload the page
} }
showLoadingIcon(); showLoadingIcon();
var request = {}; var request = {};
request['query'] = $('#search-input').val(); request['query'] = $('#search-input').val();
request['height'] = $('#result-view').height(); request['height'] = $('#result-view').height();
request['width'] = $('#result-view').width(); request['width'] = $('#result-view').width();
request['groupBy'] = groupBy(); request['groupBy'] = groupBy();
request['limitBy'] = $('#search-limit-by').val(); request['limitBy'] = $('#search-limit-by').val();
request['limit'] = parseInt($('#search-limit-value').val()); request['limit'] = parseInt($('#search-limit-value').val());
request['dateFrom'] = $('#search-date-from').val(); request['dateFrom'] = $('#search-date-from').val();
request['dateRange'] = $('#search-date-range').val(); request['dateRange'] = $('#search-date-range').val();
request['axisScale'] = $('#search-y-axis-scale').val(); request['axisScale'] = $('#search-y-axis-scale').val();
request['aggregate'] = $('#show-aggregate').val(); request['aggregate'] = $('#show-aggregate').val();
request['keyOutside'] = $('#key-outside').is(":checked");
var success = function(response){
var success = function(response){
$('#result-view').html('<img src=\"'+response.imageUrls+'" />');
}; $('#result-view').html('<img src=\"'+response.imageUrls+'" />');
var error = function(e) { };
var error = function(e) {
if (e.status == 404){
$('#result-view').text("No data points found."); if (e.status == 404){
} $('#result-view').text("No data points found.");
else{ }
$('#result-view').text("FAILED: " + JSON.parse(e.responseText).message); else{
} $('#result-view').text("FAILED: " + JSON.parse(e.responseText).message);
}; }
};
postJson("plots", request, success, error);
postJson("plots", request, success, error);
}
}
function groupBy()
{ function groupBy()
var result = []; {
var result = [];
for (var i = 1; i <= 3; i++)
{ for (var i = 1; i <= 3; i++)
if ($('#search-group-by-'+i).val() != "") {
{ if ($('#search-group-by-'+i).val() != "")
result.push($('#search-group-by-'+i).val()); {
} result.push($('#search-group-by-'+i).val());
} }
return result; }
} return result;
}
function postJson(url, requestData, successCallback, errorCallback) {
function postJson(url, requestData, successCallback, errorCallback) {
$.ajax({
type: "POST", $.ajax({
url: url, type: "POST",
data: JSON.stringify(requestData), url: url,
contentType: 'application/json' data: JSON.stringify(requestData),
}) contentType: 'application/json'
.done(successCallback) })
.fail(errorCallback); .done(successCallback)
} .fail(errorCallback);
}
function getJson(url, requestData, successCallback, errorCallback) {
function getJson(url, requestData, successCallback, errorCallback) {
$.ajax({
type: "GET", $.ajax({
url: url, type: "GET",
data: requestData, url: url,
contentType: 'application/json' data: requestData,
}) contentType: 'application/json'
.done(successCallback) })
.fail(errorCallback); .done(successCallback)
} .fail(errorCallback);
}

View File

@@ -1,83 +1,96 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<script type="text/javascript" src="js/jquery-3.2.0.min.js"></script> <script type="text/javascript" src="js/jquery-3.2.0.min.js"></script>
<script type="text/javascript" src="js/search.js"></script> <script type="text/javascript" src="js/search.js"></script>
<script type="text/javascript" src="js/autocomplete.js"></script> <script type="text/javascript" src="js/autocomplete.js"></script>
<script type="text/javascript" src="js/date.js"></script> <script type="text/javascript" src="js/date.js"></script>
<link rel="stylesheet" type="text/css" href="css/typography.css"> <link rel="stylesheet" type="text/css" href="css/typography.css">
<link rel="stylesheet" type="text/css" href="css/design.css"> <link rel="stylesheet" type="text/css" href="css/design.css">
<link rel="stylesheet" type="text/css" href="css/loading.css"> <link rel="stylesheet" type="text/css" href="css/loading.css">
<link rel="stylesheet" type="text/css" href="css/autocomplete.min.css"> <link rel="stylesheet" type="text/css" href="css/autocomplete.min.css">
<link rel="stylesheet" type="text/css" href="css/font-awesome.min.css"> <link rel="stylesheet" type="text/css" href="css/font-awesome.min.css">
</head> </head>
<body> <body>
<div id="logo" aria-hidden="true">LOGO</div> <div id="logo" aria-hidden="true">LOGO</div>
<div id="search-input-wrapper"> <div id="search-input-wrapper">
<input id="search-input" placeholder="field=value and anotherField=anotherValue" data-autocomplete="autocomplete" <input id="search-input" placeholder="field=value and anotherField=anotherValue" data-autocomplete="autocomplete"
data-autocomplete-empty-message="nothing found" /> data-autocomplete-empty-message="nothing found" />
</div> </div>
<div id="search-bar"> <div id="search-bar">
<form> <form>
<label for="search-group-by-1">Group By:</label> <div id="search-settings-bar">
<select id="search-group-by-1"></select> <div class="group">
<select id="search-group-by-2"></select> <label for="search-group-by-1">Group By:</label>
<select id="search-group-by-3"></select> <select id="search-group-by-1"></select>
<select id="search-group-by-2"></select>
<label for="search-limit-by">Limit By:</label> <select id="search-group-by-3"></select>
<select id="search-limit-by"> </div>
<option value="NO_LIMIT" selected="selected">no limit</option> <div class="group">
<option value="MOST_VALUES">most values</option> <label for="search-limit-by">Limit By:</label>
<option value="FEWEST_VALUES">fewest values</option> <select id="search-limit-by">
<option value="MAX_VALUE">max value</option> <option value="NO_LIMIT" selected="selected">no limit</option>
<option value="MIN_VALUE">min value</option> <option value="MOST_VALUES">most values</option>
</select> <option value="FEWEST_VALUES">fewest values</option>
<input type="number" id="search-limit-value" name="search-limit-value" min="1" max="1000" value="10"/> <option value="MAX_VALUE">max value</option>
<option value="MIN_VALUE">min value</option>
<label for="search-date-from">From Date:</label> </select>
<input id="search-date-from" class="input_date" type="text" value="{{oldestValue}}" required="required" pattern="\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2]\d|3[0-1]) [0-2]\d:[0-5]\d:[0-5]\d"> <input type="number" id="search-limit-value" name="search-limit-value" min="1" max="1000" value="10"/>
</div>
<label for="search-date-range">Interval:</label> <div class="group">
<input id="search-date-range" type="text" list="ranges" required="required" value="1 week" pattern="\d+ (second|minute|hour|day|week|month)s?"> <label for="search-date-from">From Date:</label>
<datalist id="ranges"> <input id="search-date-from" class="input_date" type="text" value="{{oldestValue}}" required="required" pattern="\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2]\d|3[0-1]) [0-2]\d:[0-5]\d:[0-5]\d">
<option value="60 seconds"> </div>
<option value="5 minutes"> <div class="group">
<option value="1 hour"> <label for="search-date-range">Interval:</label>
<option value="1 day"> <input id="search-date-range" type="text" list="ranges" required="required" value="1 week" pattern="\d+ (second|minute|hour|day|week|month)s?">
<option value="1 week"> <datalist id="ranges">
<option value="1 month"> <option value="60 seconds">
</datalist> <option value="5 minutes">
<option value="1 hour">
<label for="search-y-axis-scale">Y-Axis:</label> <option value="1 day">
<select id="search-y-axis-scale"> <option value="1 week">
<option value="LINEAR" selected="selected">linear</option> <option value="1 month">
<option value="LOG10">log 10</option> </datalist>
<option value="LOG2">log 2</option> </div>
</select> <div class="group">
<label for="search-y-axis-scale">Y-Axis:</label>
<label for="show-aggregate">Aggregate:</label> <select id="search-y-axis-scale">
<select id="show-aggregate"> <option value="LINEAR" selected="selected">linear</option>
<option value="NONE" selected="selected">-</option> <option value="LOG10">log 10</option>
<option value="MEAN">Mean</option> <option value="LOG2">log 2</option>
</select> </select>
</div>
<button id="search-submit"><i class="fa fa-area-chart" aria-hidden="true"></i> Plot</button> <div class="group">
</form> <label for="show-aggregate">Aggregate:</label>
</div> <select id="show-aggregate">
<option value="NONE" selected="selected">-</option>
<div id="navigation"> <option value="MEAN">Mean</option>
<button id="nav_left"><i class="fa fa-angle-double-left" aria-hidden="true"></i></button> </select>
<button id="nav_left_half"><i class="fa fa-angle-left" aria-hidden="true"></i></button> </div>
<div> <div class="group">
<button id="zoom_in"><i class="fa fa-plus-circle" aria-hidden="true"></i></button> <input type="checkbox" id="key-outside" />
<button id="zoom_out"><i class="fa fa-minus-circle" aria-hidden="true"></i></button> <label for="key-outside">Legend outside</label>
</div> </div>
<button id="nav_right_half"><i class="fa fa-angle-right" aria-hidden="true"></i></button> <button id="search-submit"><i class="fa fa-area-chart" aria-hidden="true"></i> Plot</button>
<button id="nav_right"><i class="fa fa-angle-double-right" aria-hidden="true"></i></button> </div>
</div>
<div id="result-view"> </form>
</div> </div>
</body>
<div id="navigation">
<button id="nav_left"><i class="fa fa-angle-double-left" aria-hidden="true"></i></button>
<button id="nav_left_half"><i class="fa fa-angle-left" aria-hidden="true"></i></button>
<div>
<button id="zoom_in"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>
<button id="zoom_out"><i class="fa fa-minus-circle" aria-hidden="true"></i></button>
</div>
<button id="nav_right_half"><i class="fa fa-angle-right" aria-hidden="true"></i></button>
<button id="nav_right"><i class="fa fa-angle-double-right" aria-hidden="true"></i></button>
</div>
<div id="result-view">
</div>
</body>
</html> </html>