make it possible to draw the legend outside of the plot area
This commit is contained in:
@@ -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<String> 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<String> getGroupBy() {
|
||||
return groupBy;
|
||||
}
|
||||
|
||||
public void setGroupBy(final List<String> 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<String> 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<String> getGroupBy() {
|
||||
return groupBy;
|
||||
}
|
||||
|
||||
public void setGroupBy(final List<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> 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> 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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 = "<none>";
|
||||
|
||||
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> dataSeries = new ArrayList<>();
|
||||
|
||||
final String query = plotSettings.getQuery();
|
||||
final List<String> 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<Entry> 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> 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<? super DataSeries> getDataSeriesComparator(final Limit limitBy) {
|
||||
|
||||
switch (limitBy) {
|
||||
case MOST_VALUES:
|
||||
return DataSeries.BY_NUMBER_OF_VALUES.reversed();
|
||||
case FEWEST_VALUES:
|
||||
return DataSeries.BY_NUMBER_OF_VALUES;
|
||||
case MAX_VALUE:
|
||||
return DataSeries.BY_MAX_VALUE.reversed();
|
||||
case MIN_VALUE:
|
||||
return DataSeries.BY_MAX_VALUE;
|
||||
case NO_LIMIT:
|
||||
return DataSeries.BY_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<Entry> 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<Entry> 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 = "<none>";
|
||||
|
||||
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> dataSeries = new ArrayList<>();
|
||||
|
||||
final String query = plotSettings.getQuery();
|
||||
final List<String> 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<Entry> 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> 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<? super DataSeries> getDataSeriesComparator(final Limit limitBy) {
|
||||
|
||||
switch (limitBy) {
|
||||
case MOST_VALUES:
|
||||
return DataSeries.BY_NUMBER_OF_VALUES.reversed();
|
||||
case FEWEST_VALUES:
|
||||
return DataSeries.BY_NUMBER_OF_VALUES;
|
||||
case MAX_VALUE:
|
||||
return DataSeries.BY_MAX_VALUE.reversed();
|
||||
case MIN_VALUE:
|
||||
return DataSeries.BY_MAX_VALUE;
|
||||
case NO_LIMIT:
|
||||
return DataSeries.BY_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<Entry> 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<Entry> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user