diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PlotSettings.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PlotSettings.java index 6bb5d39..ca2bfc3 100644 --- a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PlotSettings.java +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PlotSettings.java @@ -1,171 +1,181 @@ -package org.lucares.pdb.plot.api; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - -public class PlotSettings { - - private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - - private String query; - - private int height; - - private int width; - - private List groupBy; - - private Limit limitBy; - - private int limit; - - private String dateFrom; - - private String dateRange; - - private AxisScale yAxisScale; - - private AggreateInternal aggregate; - - public String getQuery() { - return query; - } - - public void setQuery(final String query) { - this.query = query; - } - - public int getHeight() { - return height; - } - - public void setHeight(final int height) { - this.height = height; - } - - public int getWidth() { - return width; - } - - public void setWidth(final int width) { - this.width = width; - } - - public List getGroupBy() { - return groupBy; - } - - public void setGroupBy(final List groupBy) { - this.groupBy = groupBy; - } - - public Limit getLimitBy() { - return limitBy; - } - - public void setLimitBy(final Limit limitBy) { - this.limitBy = limitBy; - } - - public int getLimit() { - return limit; - } - - public void setLimit(final int limit) { - this.limit = limit; - } - - public String getDateFrom() { - return dateFrom; - } - - public void setDateFrom(final String dateFrom) { - this.dateFrom = dateFrom; - } - - public String getDateRange() { - return dateRange; - } - - public void setDateRange(final String dateRange) { - this.dateRange = dateRange; - } - - public OffsetDateTime dateFrom() { - - if (StringUtils.isEmpty(dateFrom)) { - - 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)) { - return OffsetDateTime.ofInstant(Instant.ofEpochMilli(Long.MAX_VALUE), ZoneOffset.UTC); - } else { - final int period = Integer.parseInt(dateRange.split(" ")[0]); - final ChronoUnit unit = toChronoUnit(dateRange.split(" ")[1]); - - return dateFrom().plus(period, unit); - } - } - - private ChronoUnit toChronoUnit(final String string) { - - switch (string) { - case "second": - case "seconds": - return ChronoUnit.SECONDS; - case "minute": - case "minutes": - return ChronoUnit.MINUTES; - case "hour": - case "hours": - return ChronoUnit.HOURS; - case "day": - case "days": - return ChronoUnit.DAYS; - case "week": - case "weeks": - return ChronoUnit.WEEKS; - case "month": - case "months": - return ChronoUnit.MONTHS; - default: - throw new IllegalArgumentException(string + " is an unknown chrono unit"); - } - } - - public void setYAxisScale(final AxisScale axisScale) { - this.yAxisScale = axisScale; - } - - public AxisScale getYAxisScale() { - return yAxisScale; - } - - @Override - public String toString() { - return "PlotSettings [query=" + query + ", height=" + height + ", width=" + width + ", groupBy=" + groupBy - + ", limitBy=" + limitBy + ", limit=" + limit + ", dateFrom=" + dateFrom + ", dateRange=" + dateRange - + ", axisScale=" + yAxisScale + ", aggregate="+aggregate+"]"; - } - - public void setAggregate(AggreateInternal aggregate) { - this.aggregate = aggregate; - } - - public AggreateInternal getAggregate() { - return aggregate; - } -} +package org.lucares.pdb.plot.api; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +public class PlotSettings { + + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private String query; + + private int height; + + private int width; + + private List groupBy; + + private Limit limitBy; + + private int limit; + + private String dateFrom; + + private String dateRange; + + private AxisScale yAxisScale; + + private AggreateInternal aggregate; + + private boolean keyOutside; + + public String getQuery() { + return query; + } + + public void setQuery(final String query) { + this.query = query; + } + + public int getHeight() { + return height; + } + + public void setHeight(final int height) { + this.height = height; + } + + public int getWidth() { + return width; + } + + public void setWidth(final int width) { + this.width = width; + } + + public List getGroupBy() { + return groupBy; + } + + public void setGroupBy(final List groupBy) { + this.groupBy = groupBy; + } + + public Limit getLimitBy() { + return limitBy; + } + + public void setLimitBy(final Limit limitBy) { + this.limitBy = limitBy; + } + + public int getLimit() { + return limit; + } + + public void setLimit(final int limit) { + this.limit = limit; + } + + public String getDateFrom() { + return dateFrom; + } + + public void setDateFrom(final String dateFrom) { + this.dateFrom = dateFrom; + } + + public String getDateRange() { + return dateRange; + } + + public void setDateRange(final String dateRange) { + this.dateRange = dateRange; + } + + public OffsetDateTime dateFrom() { + + if (StringUtils.isEmpty(dateFrom)) { + + 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)) { + return OffsetDateTime.ofInstant(Instant.ofEpochMilli(Long.MAX_VALUE), ZoneOffset.UTC); + } else { + final int period = Integer.parseInt(dateRange.split(" ")[0]); + final ChronoUnit unit = toChronoUnit(dateRange.split(" ")[1]); + + return dateFrom().plus(period, unit); + } + } + + private ChronoUnit toChronoUnit(final String string) { + + switch (string) { + case "second": + case "seconds": + return ChronoUnit.SECONDS; + case "minute": + case "minutes": + return ChronoUnit.MINUTES; + case "hour": + case "hours": + return ChronoUnit.HOURS; + case "day": + case "days": + return ChronoUnit.DAYS; + case "week": + case "weeks": + return ChronoUnit.WEEKS; + case "month": + case "months": + return ChronoUnit.MONTHS; + default: + throw new IllegalArgumentException(string + " is an unknown chrono unit"); + } + } + + public void setYAxisScale(final AxisScale axisScale) { + this.yAxisScale = axisScale; + } + + public AxisScale getYAxisScale() { + return yAxisScale; + } + + @Override + public String toString() { + 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 AggreateInternal getAggregate() { + return aggregate; + } + + public void setKeyOutside(boolean keyOutside) { + this.keyOutside = keyOutside; + } + + public boolean isKeyOutside() { + return keyOutside; + } +} 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 8b1f507..6abe50f 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 @@ -1,87 +1,92 @@ -package org.lucares.recommind.logs; - -import java.util.Collection; - -import org.lucares.pdb.plot.api.AggreateInternal; -import org.lucares.pdb.plot.api.AxisScale; - -public class GnuplotFileGenerator { - - public String generate(final GnuplotSettings settings, final Collection dataSeries) { - - final StringBuilder result = new StringBuilder(); - - appendfln(result, "set terminal %s noenhanced size %d,%d", settings.getTerminal(), settings.getWidth(), - settings.getHeight()); - - - - appendfln(result, "set datafile separator \"%s\"", settings.getDatafileSeparator()); - - int count = 1; - if (settings.getAggregate() != AggreateInternal.NONE) - { - for (final DataSeries dataSerie : dataSeries) { - - appendfln(result, "stats '%s' using 2 prefix \"A%d\"", dataSerie.getDataFile(),count); - count++; - } - } - - appendfln(result, "set timefmt '%s'", settings.getTimefmt()); - - appendfln(result, "set xdata time"); - appendfln(result, "set format x \"%s\"", settings.getFormatX()); - appendfln(result, "set xlabel \"%s\"", settings.getXlabel()); - appendfln(result, "set xtics rotate by %d", settings.getRotateXAxisLabel()); - appendfln(result, "set xrange [\"%s\":\"%s\"]", settings.getDateFrom(), settings.getDateTo()); - - final long graphOffset = settings.getYAxisScale() == AxisScale.LINEAR ? 0 : 1; - appendfln(result, "set yrange [\""+graphOffset+"\":]"); - - appendfln(result, "set ylabel \"%s\"", settings.getYlabel()); - switch (settings.getYAxisScale()) { - case LINEAR: - break; - case LOG10: - appendfln(result, "set logscale y"); - break; - case LOG2: - appendfln(result, "set logscale y 2"); - break; - } - - appendfln(result, "set grid"); - appendfln(result, "set output \"%s\"", settings.getOutput().toAbsolutePath().toString().replace("\\", "/")); - - // marker lines that show which area will be zoomed - final long minDate = Long.parseLong(settings.getDateFrom()); - 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.75)+","+graphOffset+" rto graph 0,1 lc rgb \"#EEEEEE\" nohead"); - - appendf(result, "plot "); - - count = 1; - for (final DataSeries dataSerie : dataSeries) { - appendfln(result, "'%s' using 1:2 title '%s' with points, \\", dataSerie.getDataFile(), - dataSerie.getTitle()); - if (settings.getAggregate() == AggreateInternal.MEAN) { - appendfln(result, "A%d_mean title '%s Mean', \\", count, dataSerie.getTitle(), - dataSerie.getTitle()); - } - count++; - } - - return result.toString(); - } - - 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)); - } - -} +package org.lucares.recommind.logs; + +import java.util.Collection; + +import org.lucares.pdb.plot.api.AggreateInternal; +import org.lucares.pdb.plot.api.AxisScale; + +public class GnuplotFileGenerator { + + public String generate(final GnuplotSettings settings, final Collection dataSeries) { + + final StringBuilder result = new StringBuilder(); + + appendfln(result, "set terminal %s noenhanced size %d,%d", settings.getTerminal(), settings.getWidth(), + settings.getHeight()); + + + + appendfln(result, "set datafile separator \"%s\"", settings.getDatafileSeparator()); + + int count = 1; + if (settings.getAggregate() != AggreateInternal.NONE) + { + for (final DataSeries dataSerie : dataSeries) { + + appendfln(result, "stats '%s' using 2 prefix \"A%d\"", dataSerie.getDataFile(),count); + count++; + } + } + + appendfln(result, "set timefmt '%s'", settings.getTimefmt()); + + appendfln(result, "set xdata time"); + appendfln(result, "set format x \"%s\"", settings.getFormatX()); + appendfln(result, "set xlabel \"%s\"", settings.getXlabel()); + appendfln(result, "set xtics rotate by %d", settings.getRotateXAxisLabel()); + appendfln(result, "set xrange [\"%s\":\"%s\"]", settings.getDateFrom(), settings.getDateTo()); + + final long graphOffset = settings.getYAxisScale() == AxisScale.LINEAR ? 0 : 1; + appendfln(result, "set yrange [\""+graphOffset+"\":]"); + + appendfln(result, "set ylabel \"%s\"", settings.getYlabel()); + switch (settings.getYAxisScale()) { + case LINEAR: + break; + case LOG10: + appendfln(result, "set logscale y"); + break; + case LOG2: + appendfln(result, "set logscale y 2"); + break; + } + + appendfln(result, "set grid"); + appendfln(result, "set output \"%s\"", settings.getOutput().toAbsolutePath().toString().replace("\\", "/")); + + // marker lines that show which area will be zoomed + final long minDate = Long.parseLong(settings.getDateFrom()); + 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.75)+","+graphOffset+" rto graph 0,1 lc rgb \"#EEEEEE\" nohead"); + + if (settings.isKeyOutside()){ + appendfln(result, "set key outside"); + } + appendfln(result, "set key font \",10\""); + + appendf(result, "plot "); + + count = 1; + for (final DataSeries dataSerie : dataSeries) { + appendfln(result, "'%s' using 1:2 title '%s' with points, \\", dataSerie.getDataFile(), + dataSerie.getTitle()); + if (settings.getAggregate() == AggreateInternal.MEAN) { + appendfln(result, "A%d_mean title '%s Mean', \\", count, dataSerie.getTitle(), + dataSerie.getTitle()); + } + count++; + } + + return result.toString(); + } + + 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)); + } + +} diff --git a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotSettings.java b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotSettings.java index 5953d9c..c440222 100644 --- a/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotSettings.java +++ b/pdb-plotting/src/main/java/org/lucares/recommind/logs/GnuplotSettings.java @@ -1,150 +1,159 @@ -package org.lucares.recommind.logs; - -import java.nio.file.Path; - -import org.lucares.pdb.plot.api.AggreateInternal; -import org.lucares.pdb.plot.api.AxisScale; - -public class GnuplotSettings { - private String terminal = "png"; - private int height = 1200; - private int width = 1600; - private String timefmt = "%s"; //"%Y-%m-%dT%H:%M:%S"; // TODO @ahr timefmt - - // set format for x-axis - private String formatX = "%Y-%m-%d %H:%M:%S"; - - // set datafile separator - private String datafileSeparator = ","; - - // set xlabel - private String xlabel = "Time"; - - // set ylabel - private String ylabel = "Duration in ms"; - - // set output "datausage.png" - private final Path output; - - // set xtics rotate by 10 degree - private int rotateXAxisLabel = -10; - private AxisScale yAxisScale; - private String dateFrom; - private String dateTo; - private AggreateInternal aggregate; - - public GnuplotSettings(final Path output) { - this.output = output; - } - - public int getRotateXAxisLabel() { - return rotateXAxisLabel; - } - - public void setRotateXAxisLabel(final int rotateXAxisLabel) { - this.rotateXAxisLabel = rotateXAxisLabel; - } - - public String getTerminal() { - return terminal; - } - - public void setTerminal(final String terminal) { - this.terminal = terminal; - } - - public int getHeight() { - return height; - } - - public void setHeight(final int height) { - this.height = height; - } - - public int getWidth() { - return width; - } - - public void setWidth(final int width) { - this.width = width; - } - - public String getTimefmt() { - return timefmt; - } - - public void setTimefmt(final String timefmt) { - this.timefmt = timefmt; - } - - public String getFormatX() { - return formatX; - } - - public void setFormatX(final String formatX) { - this.formatX = formatX; - } - - public String getDatafileSeparator() { - return datafileSeparator; - } - - public void setDatafileSeparator(final String datafileSeparator) { - this.datafileSeparator = datafileSeparator; - } - - public String getXlabel() { - return xlabel; - } - - public void setXlabel(final String xlabel) { - this.xlabel = xlabel; - } - - public String getYlabel() { - return ylabel; - } - - public void setYlabel(final String ylabel) { - this.ylabel = ylabel; - } - - public Path getOutput() { - return output; - } - - public void setYAxisScale(final AxisScale yAxisScale) { - this.yAxisScale = yAxisScale; - } - - public AxisScale getYAxisScale() { - return yAxisScale; - } - - public void setDateFrom(final String dateFrom) { - this.dateFrom = dateFrom; - } - - public String getDateFrom() { - return dateFrom; - } - - public void setDateTo(final String dateTo) { - this.dateTo = dateTo; - } - - public String getDateTo() { - return dateTo; - } - - public void setAggregate(AggreateInternal aggregate) { - this.aggregate = aggregate; - } - - public AggreateInternal getAggregate() { - return aggregate; - } - - // plot 'sample.txt' using 1:2 title 'Bytes' with linespoints 2 - -} +package org.lucares.recommind.logs; + +import java.nio.file.Path; + +import org.lucares.pdb.plot.api.AggreateInternal; +import org.lucares.pdb.plot.api.AxisScale; + +public class GnuplotSettings { + private String terminal = "png"; + private int height = 1200; + private int width = 1600; + private String timefmt = "%s"; //"%Y-%m-%dT%H:%M:%S"; // TODO @ahr timefmt + + // set format for x-axis + private String formatX = "%Y-%m-%d %H:%M:%S"; + + // set datafile separator + private String datafileSeparator = ","; + + // set xlabel + private String xlabel = "Time"; + + // set ylabel + private String ylabel = "Duration in ms"; + + // set output "datausage.png" + private final Path output; + + // set xtics rotate by 10 degree + private int rotateXAxisLabel = -10; + private AxisScale yAxisScale; + private String dateFrom; + private String dateTo; + private AggreateInternal aggregate; + private boolean keyOutside = false; + + public GnuplotSettings(final Path output) { + this.output = output; + } + + public int getRotateXAxisLabel() { + return rotateXAxisLabel; + } + + public void setRotateXAxisLabel(final int rotateXAxisLabel) { + this.rotateXAxisLabel = rotateXAxisLabel; + } + + public String getTerminal() { + return terminal; + } + + public void setTerminal(final String terminal) { + this.terminal = terminal; + } + + public int getHeight() { + return height; + } + + public void setHeight(final int height) { + this.height = height; + } + + public int getWidth() { + return width; + } + + public void setWidth(final int width) { + this.width = width; + } + + public String getTimefmt() { + return timefmt; + } + + public void setTimefmt(final String timefmt) { + this.timefmt = timefmt; + } + + public String getFormatX() { + return formatX; + } + + public void setFormatX(final String formatX) { + this.formatX = formatX; + } + + public String getDatafileSeparator() { + return datafileSeparator; + } + + public void setDatafileSeparator(final String datafileSeparator) { + this.datafileSeparator = datafileSeparator; + } + + public String getXlabel() { + return xlabel; + } + + public void setXlabel(final String xlabel) { + this.xlabel = xlabel; + } + + public String getYlabel() { + return ylabel; + } + + public void setYlabel(final String ylabel) { + this.ylabel = ylabel; + } + + public Path getOutput() { + return output; + } + + public void setYAxisScale(final AxisScale yAxisScale) { + this.yAxisScale = yAxisScale; + } + + public AxisScale getYAxisScale() { + return yAxisScale; + } + + public void setDateFrom(final String dateFrom) { + this.dateFrom = dateFrom; + } + + public String getDateFrom() { + return dateFrom; + } + + public void setDateTo(final String dateTo) { + this.dateTo = dateTo; + } + + public String getDateTo() { + return dateTo; + } + + public void setAggregate(AggreateInternal aggregate) { + this.aggregate = aggregate; + } + + public AggreateInternal getAggregate() { + return aggregate; + } + + public void setKeyOutside(boolean keyOutside) { + this.keyOutside = keyOutside; + } + + public boolean isKeyOutside() { + return keyOutside; + } + + // plot 'sample.txt' using 1:2 title 'Bytes' with linespoints 2 + +} diff --git a/pdb-plotting/src/main/java/org/lucares/recommind/logs/Plotter.java b/pdb-plotting/src/main/java/org/lucares/recommind/logs/Plotter.java index d550e50..c3bb105 100644 --- a/pdb-plotting/src/main/java/org/lucares/recommind/logs/Plotter.java +++ b/pdb-plotting/src/main/java/org/lucares/recommind/logs/Plotter.java @@ -1,284 +1,285 @@ -package org.lucares.recommind.logs; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Formatter; -import java.util.Iterator; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - -import org.lucares.pdb.api.Entry; -import org.lucares.pdb.api.GroupResult; -import org.lucares.pdb.api.Result; -import org.lucares.pdb.api.Tags; -import org.lucares.pdb.plot.api.Limit; -import org.lucares.pdb.plot.api.PlotSettings; -import org.lucares.performance.db.PerformanceDb; -import org.lucares.utils.file.FileUtils; -import org.slf4j.Logger; -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"); - - private static final String DEFAULT_GROUP = ""; - - private static final int INT_TO_STRING_CACHE_SIZE= 1000; - private static final String[] INT_TO_STRING; - static { - - 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); - } - } - - 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; - - 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"); - } - } - - public Path getOutputDir() { - return outputDir; - } - - public PlotResult plot(final PlotSettings plotSettings) throws InternalPlottingException { - - LOGGER.trace("start plot: {}", plotSettings); - - final String tmpSubDir = uniqueDirectoryName(); - final Path tmpDir = tmpBaseDir.resolve(tmpSubDir); - try { - Files.createDirectories(tmpDir); - final List dataSeries = new ArrayList<>(); - - final String query = plotSettings.getQuery(); - final List groupBy = plotSettings.getGroupBy(); - final int height = plotSettings.getHeight(); - final int width = plotSettings.getWidth(); - final OffsetDateTime dateFrom = plotSettings.dateFrom(); - final OffsetDateTime dateTo = plotSettings.dateTo(); - - final Result result = db.get(query, groupBy); - - for (final GroupResult groupResult : result.getGroups()) { - - final Stream entries = groupResult.asStream(); - - final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile()); - final CsvSummary csvSummary = toCsv(entries, dataFile, dateFrom, dateTo); - - final String title = title(groupResult.getGroupedBy(), csvSummary.getValues()); - final DataSeries dataSerie = new DataSeries(dataFile, title, csvSummary.getValues(), csvSummary.getMaxValue()); - if (dataSerie.getValues() > 0) { - dataSeries.add(dataSerie); - } - } - - if (dataSeries.isEmpty()) { - throw new NoDataPointsException(); - } - - sortAndLimit(dataSeries, plotSettings); - - final Path outputFile = Files.createTempFile(outputDir, "out", ".png"); - final Gnuplot gnuplot = new Gnuplot(tmpBaseDir); - final GnuplotSettings gnuplotSettings = new GnuplotSettings(outputFile); - gnuplotSettings.setHeight(height); - gnuplotSettings.setWidth(width); - defineXAxis(gnuplotSettings, plotSettings.dateFrom(), plotSettings.dateTo()); - - gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale()); - gnuplotSettings.setAggregate(plotSettings.getAggregate()); - gnuplot.plot(gnuplotSettings, dataSeries); - - return new PlotResult(outputFile.getFileName(), dataSeries); - } 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 defineXAxis(final GnuplotSettings gnuplotSettings, final OffsetDateTime minDate, - final OffsetDateTime maxDate) { - - String formatX; - int rotateX; - String formattedMinDate; - String formattedMaxDate; - if (minDate.until(maxDate, ChronoUnit.WEEKS) > 1) { - formatX = "%Y-%m-%d"; - rotateX = 0; - } else if (minDate.until(maxDate, ChronoUnit.SECONDS) > 30) { - formatX = "%Y-%m-%d %H:%M:%S"; - rotateX = gnuplotSettings.getRotateXAxisLabel(); - } else { - formatX = "%Y-%m-%d %H:%M:%.3S"; - rotateX = gnuplotSettings.getRotateXAxisLabel(); - } - formattedMinDate = String.valueOf(minDate.toEpochSecond()); - formattedMaxDate = String.valueOf(maxDate.toEpochSecond()); - - gnuplotSettings.setFormatX(formatX); - gnuplotSettings.setRotateXAxisLabel(rotateX); - gnuplotSettings.setDateFrom(formattedMinDate); - gnuplotSettings.setDateTo(formattedMaxDate); - } - - private String uniqueDirectoryName() { - return OffsetDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm_ss")) + "_" - + UUID.randomUUID().toString(); - } - - private void sortAndLimit(final List dataSeries, final PlotSettings plotSettings) { - - final Limit limitBy = plotSettings.getLimitBy(); - dataSeries.sort(getDataSeriesComparator(limitBy)); - - switch (limitBy) { - case FEWEST_VALUES: - case MOST_VALUES: - case MAX_VALUE: - case MIN_VALUE: - while (dataSeries.size() > plotSettings.getLimit()) { - dataSeries.remove(plotSettings.getLimit()); - } - break; - case NO_LIMIT: - } - } - - private Comparator 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_NUMBER_OF_VALUES; - } - throw new IllegalStateException("unhandled enum: "+ limitBy); - } - - private String title(final Tags tags, final int values) { - - final StringBuilder result = new StringBuilder(); - - if (tags.isEmpty()) { - result.append(DEFAULT_GROUP); - } else { - tags.forEach((k, v) -> { - if (result.length() > 0) { - result.append(" / "); - } - result.append(v); - }); - } - - result.append(" ("); - result.append(values); - result.append(")"); - - return result.toString(); - - } - - private static CsvSummary toCsv(final Stream entries, final File dataFile, final OffsetDateTime dateFrom, - final OffsetDateTime dateTo) throws IOException { - - final long start = System.nanoTime(); - int count = 0; - final long fromEpochMilli = dateFrom.toInstant().toEpochMilli(); - final long toEpochMilli = dateTo.toInstant().toEpochMilli(); - final boolean useMillis = (toEpochMilli - fromEpochMilli) < TimeUnit.MINUTES.toMillis(5); - - long maxValue = 0; - long ignoredValues = 0; - final int separator = ','; - final int newline = '\n'; - final StringBuilder formattedDateBuilder = new StringBuilder(); - try (final Writer output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.US_ASCII)); - final Formatter formatter = new Formatter(formattedDateBuilder);) { - - final Iterator it = entries.iterator(); - while (it.hasNext()) { - final Entry entry = it.next(); - - if (fromEpochMilli <= entry.getEpochMilli() && entry.getEpochMilli() <= toEpochMilli) { - - final String value = longToString(entry.getValue()); - final String formattedDate; - - if (useMillis){ - formattedDateBuilder.delete(0, formattedDateBuilder.length()); - formatter.format("%.3f", entry.getEpochMilli() / 1000.0); - formattedDate = formattedDateBuilder.toString(); - }else { - formattedDate = String.valueOf(entry.getEpochMilli() / 1000); - } - - output.write(formattedDate); - output.write(separator); - output.write(value); - output.write(newline); - - count++; - maxValue = Math.max(maxValue, entry.getValue()); - }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); - } - - 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]; - } - return String.valueOf(value); - } -} +package org.lucares.recommind.logs; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Formatter; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import org.lucares.pdb.api.Entry; +import org.lucares.pdb.api.GroupResult; +import org.lucares.pdb.api.Result; +import org.lucares.pdb.api.Tags; +import org.lucares.pdb.plot.api.Limit; +import org.lucares.pdb.plot.api.PlotSettings; +import org.lucares.performance.db.PerformanceDb; +import org.lucares.utils.file.FileUtils; +import org.slf4j.Logger; +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"); + + private static final String DEFAULT_GROUP = ""; + + private static final int INT_TO_STRING_CACHE_SIZE= 1000; + private static final String[] INT_TO_STRING; + static { + + 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); + } + } + + 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; + + 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"); + } + } + + public Path getOutputDir() { + return outputDir; + } + + public PlotResult plot(final PlotSettings plotSettings) throws InternalPlottingException { + + LOGGER.trace("start plot: {}", plotSettings); + + final String tmpSubDir = uniqueDirectoryName(); + final Path tmpDir = tmpBaseDir.resolve(tmpSubDir); + try { + Files.createDirectories(tmpDir); + final List dataSeries = new ArrayList<>(); + + final String query = plotSettings.getQuery(); + final List groupBy = plotSettings.getGroupBy(); + final int height = plotSettings.getHeight(); + final int width = plotSettings.getWidth(); + final OffsetDateTime dateFrom = plotSettings.dateFrom(); + final OffsetDateTime dateTo = plotSettings.dateTo(); + + final Result result = db.get(query, groupBy); + + for (final GroupResult groupResult : result.getGroups()) { + + final Stream entries = groupResult.asStream(); + + final File dataFile = File.createTempFile("data", ".dat", tmpDir.toFile()); + final CsvSummary csvSummary = toCsv(entries, dataFile, dateFrom, dateTo); + + final String title = title(groupResult.getGroupedBy(), csvSummary.getValues()); + final DataSeries dataSerie = new DataSeries(dataFile, title, csvSummary.getValues(), csvSummary.getMaxValue()); + if (dataSerie.getValues() > 0) { + dataSeries.add(dataSerie); + } + } + + if (dataSeries.isEmpty()) { + throw new NoDataPointsException(); + } + + sortAndLimit(dataSeries, plotSettings); + + final Path outputFile = Files.createTempFile(outputDir, "out", ".png"); + final Gnuplot gnuplot = new Gnuplot(tmpBaseDir); + final GnuplotSettings gnuplotSettings = new GnuplotSettings(outputFile); + gnuplotSettings.setHeight(height); + gnuplotSettings.setWidth(width); + defineXAxis(gnuplotSettings, plotSettings.dateFrom(), plotSettings.dateTo()); + + gnuplotSettings.setYAxisScale(plotSettings.getYAxisScale()); + gnuplotSettings.setAggregate(plotSettings.getAggregate()); + gnuplotSettings.setKeyOutside(plotSettings.isKeyOutside()); + gnuplot.plot(gnuplotSettings, dataSeries); + + return new PlotResult(outputFile.getFileName(), dataSeries); + } 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 defineXAxis(final GnuplotSettings gnuplotSettings, final OffsetDateTime minDate, + final OffsetDateTime maxDate) { + + String formatX; + int rotateX; + String formattedMinDate; + String formattedMaxDate; + if (minDate.until(maxDate, ChronoUnit.WEEKS) > 1) { + formatX = "%Y-%m-%d"; + rotateX = 0; + } else if (minDate.until(maxDate, ChronoUnit.SECONDS) > 30) { + formatX = "%Y-%m-%d %H:%M:%S"; + rotateX = gnuplotSettings.getRotateXAxisLabel(); + } else { + formatX = "%Y-%m-%d %H:%M:%.3S"; + rotateX = gnuplotSettings.getRotateXAxisLabel(); + } + formattedMinDate = String.valueOf(minDate.toEpochSecond()); + formattedMaxDate = String.valueOf(maxDate.toEpochSecond()); + + gnuplotSettings.setFormatX(formatX); + gnuplotSettings.setRotateXAxisLabel(rotateX); + gnuplotSettings.setDateFrom(formattedMinDate); + gnuplotSettings.setDateTo(formattedMaxDate); + } + + private String uniqueDirectoryName() { + return OffsetDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH_mm_ss")) + "_" + + UUID.randomUUID().toString(); + } + + private void sortAndLimit(final List dataSeries, final PlotSettings plotSettings) { + + final Limit limitBy = plotSettings.getLimitBy(); + dataSeries.sort(getDataSeriesComparator(limitBy)); + + switch (limitBy) { + case FEWEST_VALUES: + case MOST_VALUES: + case MAX_VALUE: + case MIN_VALUE: + while (dataSeries.size() > plotSettings.getLimit()) { + dataSeries.remove(plotSettings.getLimit()); + } + break; + case NO_LIMIT: + } + } + + private Comparator 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_NUMBER_OF_VALUES; + } + throw new IllegalStateException("unhandled enum: "+ limitBy); + } + + private String title(final Tags tags, final int values) { + + final StringBuilder result = new StringBuilder(); + + if (tags.isEmpty()) { + result.append(DEFAULT_GROUP); + } else { + tags.forEach((k, v) -> { + if (result.length() > 0) { + result.append(" / "); + } + result.append(v); + }); + } + + result.append(" ("); + result.append(values); + result.append(")"); + + return result.toString(); + + } + + private static CsvSummary toCsv(final Stream entries, final File dataFile, final OffsetDateTime dateFrom, + final OffsetDateTime dateTo) throws IOException { + + final long start = System.nanoTime(); + int count = 0; + final long fromEpochMilli = dateFrom.toInstant().toEpochMilli(); + final long toEpochMilli = dateTo.toInstant().toEpochMilli(); + final boolean useMillis = (toEpochMilli - fromEpochMilli) < TimeUnit.MINUTES.toMillis(5); + + long maxValue = 0; + long ignoredValues = 0; + final int separator = ','; + final int newline = '\n'; + final StringBuilder formattedDateBuilder = new StringBuilder(); + try (final Writer output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dataFile), StandardCharsets.US_ASCII)); + final Formatter formatter = new Formatter(formattedDateBuilder);) { + + final Iterator it = entries.iterator(); + while (it.hasNext()) { + final Entry entry = it.next(); + + if (fromEpochMilli <= entry.getEpochMilli() && entry.getEpochMilli() <= toEpochMilli) { + + final String value = longToString(entry.getValue()); + final String formattedDate; + + if (useMillis){ + formattedDateBuilder.delete(0, formattedDateBuilder.length()); + formatter.format("%.3f", entry.getEpochMilli() / 1000.0); + formattedDate = formattedDateBuilder.toString(); + }else { + formattedDate = String.valueOf(entry.getEpochMilli() / 1000); + } + + output.write(formattedDate); + output.write(separator); + output.write(value); + output.write(newline); + + count++; + maxValue = Math.max(maxValue, entry.getValue()); + }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); + } + + 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]; + } + return String.valueOf(value); + } +} 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 61ace69..6da060c 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/PlotSettingsTransformer.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/PlotSettingsTransformer.java @@ -1,68 +1,69 @@ -package org.lucares.pdbui; - -import org.lucares.pdb.plot.api.AggreateInternal; -import org.lucares.pdb.plot.api.AxisScale; -import org.lucares.pdb.plot.api.Limit; -import org.lucares.pdb.plot.api.PlotSettings; -import org.lucares.pdbui.domain.Aggregate; -import org.lucares.pdbui.domain.LimitBy; -import org.lucares.pdbui.domain.PlotRequest; -import org.lucares.pdbui.domain.YAxis; - -class PlotSettingsTransformer { - static PlotSettings toSettings(final PlotRequest request) { - - final PlotSettings result = new PlotSettings(); - - result.setQuery(request.getQuery()); - result.setGroupBy(request.getGroupBy()); - result.setHeight(request.getHeight()); - result.setWidth(request.getWidth()); - result.setLimit(request.getLimit()); - result.setLimitBy(toLimit(request.getLimitBy())); - result.setDateFrom(request.getDateFrom()); - result.setDateRange(request.getDateRange()); - result.setYAxisScale(toAxisScale(request.getAxisScale())); - result.setAggregate(toAggregateInternal(request.getAggregate())); - - return result; - } - - private static AggreateInternal toAggregateInternal(Aggregate aggregate) { - switch (aggregate) { - case NONE:return AggreateInternal.NONE; - case MEAN:return AggreateInternal.MEAN; - } - throw new IllegalStateException("unhandled enum: " + aggregate); - } - - private static AxisScale toAxisScale(final YAxis yAxis) { - switch (yAxis) { - case LINEAR: - return AxisScale.LINEAR; - case LOG10: - return AxisScale.LOG10; - case LOG2: - return AxisScale.LOG2; - default: - throw new IllegalStateException("unhandled enum: " + yAxis); - } - } - - private static Limit toLimit(final LimitBy limitBy) { - switch (limitBy) { - case NO_LIMIT: - return Limit.NO_LIMIT; - case FEWEST_VALUES: - return Limit.FEWEST_VALUES; - case MOST_VALUES: - return Limit.MOST_VALUES; - case MAX_VALUE: - return Limit.MAX_VALUE; - case MIN_VALUE: - return Limit.MIN_VALUE; - default: - throw new IllegalStateException("unhandled enum: " + limitBy); - } - } -} +package org.lucares.pdbui; + +import org.lucares.pdb.plot.api.AggreateInternal; +import org.lucares.pdb.plot.api.AxisScale; +import org.lucares.pdb.plot.api.Limit; +import org.lucares.pdb.plot.api.PlotSettings; +import org.lucares.pdbui.domain.Aggregate; +import org.lucares.pdbui.domain.LimitBy; +import org.lucares.pdbui.domain.PlotRequest; +import org.lucares.pdbui.domain.YAxis; + +class PlotSettingsTransformer { + static PlotSettings toSettings(final PlotRequest request) { + + final PlotSettings result = new PlotSettings(); + + result.setQuery(request.getQuery()); + result.setGroupBy(request.getGroupBy()); + result.setHeight(request.getHeight()); + result.setWidth(request.getWidth()); + result.setLimit(request.getLimit()); + result.setLimitBy(toLimit(request.getLimitBy())); + result.setDateFrom(request.getDateFrom()); + result.setDateRange(request.getDateRange()); + result.setYAxisScale(toAxisScale(request.getAxisScale())); + result.setAggregate(toAggregateInternal(request.getAggregate())); + result.setKeyOutside(request.isKeyOutside()); + + return result; + } + + private static AggreateInternal toAggregateInternal(Aggregate aggregate) { + switch (aggregate) { + case NONE:return AggreateInternal.NONE; + case MEAN:return AggreateInternal.MEAN; + } + throw new IllegalStateException("unhandled enum: " + aggregate); + } + + private static AxisScale toAxisScale(final YAxis yAxis) { + switch (yAxis) { + case LINEAR: + return AxisScale.LINEAR; + case LOG10: + return AxisScale.LOG10; + case LOG2: + return AxisScale.LOG2; + default: + throw new IllegalStateException("unhandled enum: " + yAxis); + } + } + + private static Limit toLimit(final LimitBy limitBy) { + switch (limitBy) { + case NO_LIMIT: + return Limit.NO_LIMIT; + case FEWEST_VALUES: + return Limit.FEWEST_VALUES; + case MOST_VALUES: + return Limit.MOST_VALUES; + case MAX_VALUE: + return Limit.MAX_VALUE; + case MIN_VALUE: + return Limit.MIN_VALUE; + default: + throw new IllegalStateException("unhandled enum: " + limitBy); + } + } +} diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/domain/PlotRequest.java b/pdb-ui/src/main/java/org/lucares/pdbui/domain/PlotRequest.java index ec0d249..d2347df 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/domain/PlotRequest.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/domain/PlotRequest.java @@ -1,113 +1,123 @@ -package org.lucares.pdbui.domain; - -import java.util.List; - -public class PlotRequest { - private String query; - - private int height = 1000; - - private int width = 1000; - - private List groupBy; - - private LimitBy limitBy = LimitBy.NO_LIMIT; - - private YAxis yAxis = YAxis.LINEAR; - - private int limit = Integer.MAX_VALUE; - - private String dateFrom; - - private String dateRange; - - private Aggregate aggregate = Aggregate.NONE; - - public String getQuery() { - return query; - } - - public void setQuery(final String query) { - this.query = query; - } - - public int getWidth() { - return width; - } - - public void setWidth(final int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(final int height) { - this.height = height; - } - - @Override - public String toString() { - return query + ":" + height + "x" + width; - } - - public List getGroupBy() { - return groupBy; - } - - public void setGroupBy(final List groupBy) { - this.groupBy = groupBy; - } - - public LimitBy getLimitBy() { - return limitBy; - } - - public void setLimitBy(final LimitBy limitBy) { - this.limitBy = limitBy; - } - - public int getLimit() { - return limit; - } - - public void setLimit(final int limit) { - this.limit = limit; - } - - public String getDateFrom() { - return dateFrom; - } - - public void setDateFrom(final String dateFrom) { - this.dateFrom = dateFrom; - } - - 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"); - } - this.dateRange = dateRange; - } - - public YAxis getAxisScale() { - return yAxis; - } - - public void setAxisScale(final YAxis yAxis) { - this.yAxis = yAxis; - } - - public void setAggregate(Aggregate aggregate) { - this.aggregate = aggregate; - } - - public Aggregate getAggregate() { - return aggregate; - } -} +package org.lucares.pdbui.domain; + +import java.util.List; + +public class PlotRequest { + private String query; + + private int height = 1000; + + private int width = 1000; + + private List groupBy; + + private LimitBy limitBy = LimitBy.NO_LIMIT; + + private YAxis yAxis = YAxis.LINEAR; + + private int limit = Integer.MAX_VALUE; + + private String dateFrom; + + private String dateRange; + + private Aggregate aggregate = Aggregate.NONE; + + private boolean keyOutside; + + public String getQuery() { + return query; + } + + public void setQuery(final String query) { + this.query = query; + } + + public int getWidth() { + return width; + } + + public void setWidth(final int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(final int height) { + this.height = height; + } + + @Override + public String toString() { + return query + ":" + height + "x" + width; + } + + public List getGroupBy() { + return groupBy; + } + + public void setGroupBy(final List groupBy) { + this.groupBy = groupBy; + } + + public LimitBy getLimitBy() { + return limitBy; + } + + public void setLimitBy(final LimitBy limitBy) { + this.limitBy = limitBy; + } + + public int getLimit() { + return limit; + } + + public void setLimit(final int limit) { + this.limit = limit; + } + + public String getDateFrom() { + return dateFrom; + } + + public void setDateFrom(final String dateFrom) { + this.dateFrom = dateFrom; + } + + 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"); + } + this.dateRange = dateRange; + } + + public YAxis getAxisScale() { + return yAxis; + } + + public void setAxisScale(final YAxis yAxis) { + this.yAxis = yAxis; + } + + public void setAggregate(Aggregate aggregate) { + this.aggregate = aggregate; + } + + public Aggregate getAggregate() { + return aggregate; + } + + public void setKeyOutside(boolean keyOutside) { + this.keyOutside = keyOutside; + } + + public boolean isKeyOutside() { + return keyOutside; + } +} diff --git a/pdb-ui/src/main/resources/resources/css/design.css b/pdb-ui/src/main/resources/resources/css/design.css index 8c591e8..c6b3702 100644 --- a/pdb-ui/src/main/resources/resources/css/design.css +++ b/pdb-ui/src/main/resources/resources/css/design.css @@ -1,117 +1,125 @@ -html { - height: 100%; - margin:0; - padding:0; - font-size: 14px; -} - -body{ - display: grid; - height: 100vh; - margin: 0; - grid: - "search_field logo" auto - "search logo" auto - "navigation navigation" auto - "result result" 1fr - / 1fr auto; -} - -@font-face { - font-family: 'FontAwesome'; - 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'); - font-weight: normal; - font-style: normal; -} - -#logo { - grid-area: logo; - font-size: 1.2em; - font-weight: bold; - background-color: black; - color: white; - padding: 3px; -} - -#search-input-wrapper { - grid-area: search_field; -} - -#search-bar { - grid-area: search; - background-color: #aaa; - padding-bottom:3px; -} - -#navigation { - grid-area: navigation; - background-color: #aaa; - display: flex; - justify-content: space-between; -} - -.autocomplete .active { - background-color: #AAA; -} - -.autocomplete, #search-input-wrapper .autocomplete.open { - overflow-y: scroll; -} - -#search-input { - box-sizing: border-box; - border: 0; -} - - -#search-limit-value { - width: 4em; -} - -.input_date { - max-width: 10em; -} - -#add-filter { - float:right; -} - -#button-bar { - text-align: right; -} - -#search-submit { - margin-right:3px; -} - -#result-view { - grid-area: result; - background: #eee; - margin: 0; - padding: 0; - overflow: hidden; -} - -#result-view i { - background: white; - display:inline; - font-style:normal; - line-height: 1.2em; -} - - -.center -{ - display: flex; - justify-content: center; - 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; -} +html { + height: 100%; + margin:0; + padding:0; + font-size: 14px; +} + +body{ + display: grid; + height: 100vh; + margin: 0; + grid: + "search_field logo" auto + "search logo" auto + "navigation navigation" auto + "result result" 1fr + / 1fr auto; +} + +@font-face { + font-family: 'FontAwesome'; + 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'); + font-weight: normal; + font-style: normal; +} + +.group { + display: inline-block; +} + +#logo { + grid-area: logo; + font-size: 1.2em; + font-weight: bold; + background-color: black; + color: white; + padding: 3px; +} + +#search-input-wrapper { + grid-area: search_field; +} + +#search-bar { + grid-area: search; + background-color: #aaa; + padding-bottom:3px; +} + +#navigation { + grid-area: navigation; + background-color: #aaa; + display: flex; + justify-content: space-between; +} + +.autocomplete .active { + background-color: #AAA; +} + +/* scrollbars are nice, but with them an empty autocomplete box is shown + +.autocomplete, #search-input-wrapper .autocomplete.open { + overflow-y: scroll; +} +*/ + +#search-input { + box-sizing: border-box; + border: 0; +} + + +#search-limit-value { + width: 4em; +} + +.input_date { + max-width: 10em; +} + +#add-filter { + float:right; +} + +#button-bar { + text-align: right; +} + +#search-submit { + margin-left:3px; + margin-right:3px; +} + +#result-view { + grid-area: result; + background: #eee; + margin: 0; + padding: 0; + overflow: hidden; +} + +#result-view i { + background: white; + display:inline; + font-style:normal; + line-height: 1.2em; +} + + +.center +{ + display: flex; + justify-content: center; + 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; +} diff --git a/pdb-ui/src/main/resources/resources/js/search.js b/pdb-ui/src/main/resources/resources/js/search.js index ba433d4..d4c384e 100644 --- a/pdb-ui/src/main/resources/resources/js/search.js +++ b/pdb-ui/src/main/resources/resources/js/search.js @@ -1,330 +1,331 @@ - -$(document).ready(function(){ - - $('#search-submit').click(plot); - - renderGroupBy(); - updateSearchLimitValue(); - - $('#search-limit-by').change(updateSearchLimitValue); - - $('#nav_left').click(dateLeftShift); - $('#nav_left_half').click(dateHalfLeftShift); - $('#nav_right_half').click(dateHalfRightShift); - $('#nav_right').click(dateRightShift); - - $('#zoom_in').click(zoomIn); - $('#zoom_out').click(zoomOut); - - AutoComplete({ - HttpMethod: "GET", - Delay: 300, - _QueryArg: function() { - var caretIndex = document.getElementById('search-input').selectionStart + 1; - return 'caretIndex=' + caretIndex + '&query'; - }, - _Pre: function() { - return encodeURI(this.Input.value); - }, - _Post: function(response) { - var result = []; - var responseObject = JSON.parse(response); - responseObject['proposals'].forEach(function(item, index){ - var proposal = {}; - proposal['Label'] = item.value; - proposal['Value'] = item.proposedQuery; - - result.push(proposal); - }); - - console.log(JSON.stringify(result)); - return result; - } - }); -}); - -function zoomIn() -{ - shiftDate(0.25); - zoom(0.5); - plot(); -} - -function zoomOut() -{ - shiftDate(-0.5); - zoom(2); - plot(); -} - -function dateLeftShift() -{ - shiftDate(-1); - plot(); -} -function dateHalfLeftShift() -{ - shiftDate(-0.5); - plot(); -} - -function dateHalfRightShift() -{ - shiftDate(0.5); - plot(); -} -function dateRightShift() -{ - shiftDate(1); - plot(); -} - -function zoom(factor) -{ - if (!$('#search-date-range').is(":invalid")) { - - var dateRange = $('#search-date-range').val(); - var tokens = dateRange.split(/ +/,2); - - if(tokens.length == 2) - { - var value = parseInt(tokens[0]); - var period = tokens[1]; - - var newValue = value*factor; - while (newValue != Math.round(newValue)){ - - switch (period) { - case "second": - case "seconds": - if (value == 1) { - // we reached the smallest range - } - else if (value % 2 == 1){ - value = value -1; - } - break; - case "minute": - case "minutes": - value = value * 60; - period = "seconds"; - break; - case "hour": - case "hours": - value = value * 60; - period = "minutes"; - break; - case "day": - case "days": - value = value * 24; - period = "hours"; - break; - case "week": - case "weeks": - value = value * 7; - period = "days"; - break; - case "month": - case "months": - value = value * 30; - period = "days"; - break; - default: - console.log("unhandled value: "+ period); - break; - } - - newValue = value*factor - } - - - $('#search-date-range').val(newValue + " " + period); - } - } -} - - - -function shiftDate(directionalFactor) -{ - var dateBefore = Date.parse($('#search-date-from').val()); - var newDate = shiftByInterval(dateBefore, directionalFactor); - $('#search-date-from').val(newDate.toString("yyyy-MM-dd HH:mm:ss")); -} - -function shiftByInterval(date, directionalFactor) -{ - if (!$('#search-date-range').is(":invalid")) { - - var dateRange = $('#search-date-range').val(); - var tokens = dateRange.split(/ +/,2); - - if(tokens.length == 2) - { - var value = parseInt(tokens[0]); - var period = tokens[1]; - var config = {}; - - value = directionalFactor * value; - - switch (period) { - case "second": - case "seconds": - config = { seconds: value }; - break; - case "minute": - case "minutes": - config = { minutes: value }; - break; - case "hour": - case "hours": - config = { minutes: 60*value }; - break; - case "day": - case "days": - config = { days: value }; - break; - case "week": - case "weeks": - config = { days: 7*value }; - break; - case "month": - case "months": - config = { days: 30*value }; - break; - - default: - break; - } - - var newDate = date.add(config); - return newDate; - } - } - return date; -} - -function updateSearchLimitValue () { - var optionSelected = $('#search-limit-by').find("option:selected"); - var valueSelected = optionSelected.val(); - - if (valueSelected == "NO_LIMIT"){ - $('#search-limit-value').hide(); - }else{ - $('#search-limit-value').show(); - } - } - -function renderGroupBy() -{ - var request = {}; - - var success = function(response){ - - initSearchGroupBy('#search-group-by-1', response); - initSearchGroupBy('#search-group-by-2', response); - initSearchGroupBy('#search-group-by-3', response); - }; - var error = function(e) { - $('#result-view').text("FAILED: " + JSON.parse(e.responseText).message); - }; - - - getJson("fields", request, success, error); -} - -function initSearchGroupBy(selector, response) -{ - $(selector).empty(); - var option = new Option("", ""); - $(selector).append($(option)); - - response.forEach( - (item, index) => { - var option = new Option(item, item); - $(selector).append($(option)); - } - ); -} - -function showLoadingIcon() -{ - $('#result-view').html("
"); -} - -function plot(event){ - if(event){ - event.preventDefault(); // prevent submit of form which would reload the page - } - - showLoadingIcon(); - var request = {}; - request['query'] = $('#search-input').val(); - request['height'] = $('#result-view').height(); - request['width'] = $('#result-view').width(); - request['groupBy'] = groupBy(); - request['limitBy'] = $('#search-limit-by').val(); - request['limit'] = parseInt($('#search-limit-value').val()); - request['dateFrom'] = $('#search-date-from').val(); - request['dateRange'] = $('#search-date-range').val(); - request['axisScale'] = $('#search-y-axis-scale').val(); - request['aggregate'] = $('#show-aggregate').val(); - - - var success = function(response){ - - $('#result-view').html(''); - }; - var error = function(e) { - - if (e.status == 404){ - $('#result-view').text("No data points found."); - } - else{ - $('#result-view').text("FAILED: " + JSON.parse(e.responseText).message); - } - }; - - postJson("plots", request, success, error); - -} - -function groupBy() -{ - var result = []; - - for (var i = 1; i <= 3; i++) - { - if ($('#search-group-by-'+i).val() != "") - { - result.push($('#search-group-by-'+i).val()); - } - } - return result; -} - -function postJson(url, requestData, successCallback, errorCallback) { - - $.ajax({ - type: "POST", - url: url, - data: JSON.stringify(requestData), - contentType: 'application/json' - }) - .done(successCallback) - .fail(errorCallback); -} - -function getJson(url, requestData, successCallback, errorCallback) { - - $.ajax({ - type: "GET", - url: url, - data: requestData, - contentType: 'application/json' - }) - .done(successCallback) - .fail(errorCallback); -} - - + +$(document).ready(function(){ + + $('#search-submit').click(plot); + + renderGroupBy(); + updateSearchLimitValue(); + + $('#search-limit-by').change(updateSearchLimitValue); + + $('#nav_left').click(dateLeftShift); + $('#nav_left_half').click(dateHalfLeftShift); + $('#nav_right_half').click(dateHalfRightShift); + $('#nav_right').click(dateRightShift); + + $('#zoom_in').click(zoomIn); + $('#zoom_out').click(zoomOut); + + AutoComplete({ + HttpMethod: "GET", + Delay: 300, + _QueryArg: function() { + var caretIndex = document.getElementById('search-input').selectionStart + 1; + return 'caretIndex=' + caretIndex + '&query'; + }, + _Pre: function() { + return encodeURI(this.Input.value); + }, + _Post: function(response) { + var result = []; + var responseObject = JSON.parse(response); + responseObject['proposals'].forEach(function(item, index){ + var proposal = {}; + proposal['Label'] = item.value; + proposal['Value'] = item.proposedQuery; + + result.push(proposal); + }); + + console.log(JSON.stringify(result)); + return result; + } + }); +}); + +function zoomIn() +{ + shiftDate(0.25); + zoom(0.5); + plot(); +} + +function zoomOut() +{ + shiftDate(-0.5); + zoom(2); + plot(); +} + +function dateLeftShift() +{ + shiftDate(-1); + plot(); +} +function dateHalfLeftShift() +{ + shiftDate(-0.5); + plot(); +} + +function dateHalfRightShift() +{ + shiftDate(0.5); + plot(); +} +function dateRightShift() +{ + shiftDate(1); + plot(); +} + +function zoom(factor) +{ + if (!$('#search-date-range').is(":invalid")) { + + var dateRange = $('#search-date-range').val(); + var tokens = dateRange.split(/ +/,2); + + if(tokens.length == 2) + { + var value = parseInt(tokens[0]); + var period = tokens[1]; + + var newValue = value*factor; + while (newValue != Math.round(newValue)){ + + switch (period) { + case "second": + case "seconds": + if (value == 1) { + // we reached the smallest range + } + else if (value % 2 == 1){ + value = value -1; + } + break; + case "minute": + case "minutes": + value = value * 60; + period = "seconds"; + break; + case "hour": + case "hours": + value = value * 60; + period = "minutes"; + break; + case "day": + case "days": + value = value * 24; + period = "hours"; + break; + case "week": + case "weeks": + value = value * 7; + period = "days"; + break; + case "month": + case "months": + value = value * 30; + period = "days"; + break; + default: + console.log("unhandled value: "+ period); + break; + } + + newValue = value*factor + } + + + $('#search-date-range').val(newValue + " " + period); + } + } +} + + + +function shiftDate(directionalFactor) +{ + var dateBefore = Date.parse($('#search-date-from').val()); + var newDate = shiftByInterval(dateBefore, directionalFactor); + $('#search-date-from').val(newDate.toString("yyyy-MM-dd HH:mm:ss")); +} + +function shiftByInterval(date, directionalFactor) +{ + if (!$('#search-date-range').is(":invalid")) { + + var dateRange = $('#search-date-range').val(); + var tokens = dateRange.split(/ +/,2); + + if(tokens.length == 2) + { + var value = parseInt(tokens[0]); + var period = tokens[1]; + var config = {}; + + value = directionalFactor * value; + + switch (period) { + case "second": + case "seconds": + config = { seconds: value }; + break; + case "minute": + case "minutes": + config = { minutes: value }; + break; + case "hour": + case "hours": + config = { minutes: 60*value }; + break; + case "day": + case "days": + config = { days: value }; + break; + case "week": + case "weeks": + config = { days: 7*value }; + break; + case "month": + case "months": + config = { days: 30*value }; + break; + + default: + break; + } + + var newDate = date.add(config); + return newDate; + } + } + return date; +} + +function updateSearchLimitValue () { + var optionSelected = $('#search-limit-by').find("option:selected"); + var valueSelected = optionSelected.val(); + + if (valueSelected == "NO_LIMIT"){ + $('#search-limit-value').hide(); + }else{ + $('#search-limit-value').show(); + } + } + +function renderGroupBy() +{ + var request = {}; + + var success = function(response){ + + initSearchGroupBy('#search-group-by-1', response); + initSearchGroupBy('#search-group-by-2', response); + initSearchGroupBy('#search-group-by-3', response); + }; + var error = function(e) { + $('#result-view').text("FAILED: " + JSON.parse(e.responseText).message); + }; + + + getJson("fields", request, success, error); +} + +function initSearchGroupBy(selector, response) +{ + $(selector).empty(); + var option = new Option("", ""); + $(selector).append($(option)); + + response.forEach( + (item, index) => { + var option = new Option(item, item); + $(selector).append($(option)); + } + ); +} + +function showLoadingIcon() +{ + $('#result-view').html("
"); +} + +function plot(event){ + if(event){ + event.preventDefault(); // prevent submit of form which would reload the page + } + + showLoadingIcon(); + var request = {}; + request['query'] = $('#search-input').val(); + request['height'] = $('#result-view').height(); + request['width'] = $('#result-view').width(); + request['groupBy'] = groupBy(); + request['limitBy'] = $('#search-limit-by').val(); + request['limit'] = parseInt($('#search-limit-value').val()); + request['dateFrom'] = $('#search-date-from').val(); + request['dateRange'] = $('#search-date-range').val(); + request['axisScale'] = $('#search-y-axis-scale').val(); + request['aggregate'] = $('#show-aggregate').val(); + request['keyOutside'] = $('#key-outside').is(":checked"); + + + var success = function(response){ + + $('#result-view').html(''); + }; + var error = function(e) { + + if (e.status == 404){ + $('#result-view').text("No data points found."); + } + else{ + $('#result-view').text("FAILED: " + JSON.parse(e.responseText).message); + } + }; + + postJson("plots", request, success, error); + +} + +function groupBy() +{ + var result = []; + + for (var i = 1; i <= 3; i++) + { + if ($('#search-group-by-'+i).val() != "") + { + result.push($('#search-group-by-'+i).val()); + } + } + return result; +} + +function postJson(url, requestData, successCallback, errorCallback) { + + $.ajax({ + type: "POST", + url: url, + data: JSON.stringify(requestData), + contentType: 'application/json' + }) + .done(successCallback) + .fail(errorCallback); +} + +function getJson(url, requestData, successCallback, errorCallback) { + + $.ajax({ + type: "GET", + url: url, + data: requestData, + contentType: 'application/json' + }) + .done(successCallback) + .fail(errorCallback); +} + + diff --git a/pdb-ui/src/main/resources/templates/main.html b/pdb-ui/src/main/resources/templates/main.html index 2913c2a..cdf41a3 100644 --- a/pdb-ui/src/main/resources/templates/main.html +++ b/pdb-ui/src/main/resources/templates/main.html @@ -1,83 +1,96 @@ - - - - - - - - - - - - - - - - - -
- -
- - - -
-
- + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+
+ \ No newline at end of file