From 2cf84e45d37049ba27bcb079dad488cb6c0d4a36 Mon Sep 17 00:00:00 2001 From: Holly Cummins Date: Fri, 21 Nov 2025 15:31:28 +0000 Subject: [PATCH] Add fine print explaining experimental parameters --- .../performance/graphics/GraphicsCommand.java | 1 + .../performance/graphics/ImageGenerator.java | 16 +- .../performance/graphics/charts/BarChart.java | 20 +- .../performance/graphics/charts/Chart.java | 12 +- .../graphics/{ => charts}/CubeChart.java | 25 +- .../graphics/charts/FinePrint.java | 129 ++++ .../graphics/charts/InlinedSVG.java | 62 ++ .../performance/graphics/charts/Label.java | 38 +- .../graphics/charts/Subcanvas.java | 10 + .../src/main/resources/github-mark-dark.svg | 1 + .../src/main/resources/github-mark-light.svg | 1 + .../graphics/DataIngesterTest.java | 24 +- .../graphics/ImageGeneratorTest.java | 18 + .../src/test/resources/data.json | 718 ++++++++++-------- 14 files changed, 713 insertions(+), 362 deletions(-) rename graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/{ => charts}/CubeChart.java (81%) create mode 100644 graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/FinePrint.java create mode 100644 graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/InlinedSVG.java create mode 100644 graphics-generator/src/main/resources/github-mark-dark.svg create mode 100644 graphics-generator/src/main/resources/github-mark-light.svg diff --git a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/GraphicsCommand.java b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/GraphicsCommand.java index b395f53..858dbc8 100644 --- a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/GraphicsCommand.java +++ b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/GraphicsCommand.java @@ -11,6 +11,7 @@ import io.quarkus.infra.performance.graphics.charts.BarChart; import io.quarkus.infra.performance.graphics.charts.Chart; +import io.quarkus.infra.performance.graphics.charts.CubeChart; import io.quarkus.infra.performance.graphics.model.BenchmarkData; import picocli.CommandLine.Command; import picocli.CommandLine.Parameters; diff --git a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/ImageGenerator.java b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/ImageGenerator.java index 413a3ae..588426d 100644 --- a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/ImageGenerator.java +++ b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/ImageGenerator.java @@ -7,17 +7,21 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; +import java.util.Collection; import java.util.function.BiFunction; import jakarta.enterprise.context.ApplicationScoped; +import org.apache.batik.anim.dom.SAXSVGDocumentFactory; import org.apache.batik.anim.dom.SVGDOMImplementation; import org.apache.batik.svggen.SVGGraphics2D; +import org.apache.batik.util.XMLResourceDescriptor; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; import io.quarkus.infra.performance.graphics.charts.Chart; +import io.quarkus.infra.performance.graphics.charts.InlinedSVG; import io.quarkus.infra.performance.graphics.model.BenchmarkData; @ApplicationScoped @@ -34,10 +38,11 @@ public void generate(BiFunction chartConstructor, B svgGenerator.setSVGCanvasSize(new Dimension(1200, 600)); Chart chart = chartConstructor.apply(svgGenerator, theme); - chart.draw(plotDefinition.title(), data.results().getDatasets(plotDefinition.fun())); + chart.draw(plotDefinition.title(), data.results().getDatasets(plotDefinition.fun()), data.config()); Element root = svgGenerator.getRoot(); initialiseFonts(doc, root); + inlineGraphics(doc, root, chart.getInlinedSVGs()); outFile.getParentFile().mkdirs(); try (Writer out = new OutputStreamWriter(new FileOutputStream(outFile), StandardCharsets.UTF_8)) { @@ -46,6 +51,15 @@ public void generate(BiFunction chartConstructor, B } } + private void inlineGraphics(Document doc, Element root, Collection inlinedSVGs) { + String parser = XMLResourceDescriptor.getXMLParserClassName(); + SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(parser); + + for (InlinedSVG inlinedSVG : inlinedSVGs) { + inlinedSVG.draw(factory, root, doc); + } + } + private static void initialiseFonts(Document doc, Element root) { Element style = doc.createElementNS(svgNS, "style"); style.setTextContent(Theme.FONT.getCss()); diff --git a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/BarChart.java b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/BarChart.java index 335e50a..40113f7 100644 --- a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/BarChart.java +++ b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/BarChart.java @@ -4,11 +4,13 @@ import java.awt.Font; import java.awt.geom.Rectangle2D; +import java.util.Collection; import java.util.List; import org.apache.batik.svggen.SVGGraphics2D; import io.quarkus.infra.performance.graphics.Theme; +import io.quarkus.infra.performance.graphics.model.Config; public class BarChart implements Chart { private static final int BAR_THICKNESS = 44; @@ -19,6 +21,7 @@ public class BarChart implements Chart { private final int canvasHeight; private final int canvasWidth; private final Theme theme; + private FinePrint fineprint; public BarChart(SVGGraphics2D g, Theme theme) { this.g = g; @@ -29,12 +32,11 @@ public BarChart(SVGGraphics2D g, Theme theme) { } @Override - public void draw(String title, List data) { + public void draw(String title, List data, Config metadata) { g.setPaint(theme.background()); g.fill(new Rectangle2D.Double(0, 0, canvasWidth, canvasWidth)); - // --- Draw section titles --- g.setPaint(theme.text()); double maxValue = data.stream().map(d -> d.value().getValue()).max(Double::compare).orElse(1.0); @@ -48,7 +50,8 @@ public void draw(String title, List data) { new Label(title, 0, titleTextSize).setTargetHeight(titleTextSize).setStyle(Font.BOLD).draw(titleCanvas); int plotWidth = canvasWidth - 2 * leftMargin; - int plotHeight = canvasHeight - titleCanvas.getHeight(); + int finePrintHeight = 80; + int plotHeight = canvasHeight - titleCanvas.getHeight() - finePrintHeight; Subcanvas chartArea = new Subcanvas(g, plotWidth, plotHeight, leftMargin, titleCanvas.getHeight()); int y = 0; @@ -57,6 +60,9 @@ public void draw(String title, List data) { int fudge = 40; Subcanvas labelArea = new Subcanvas(chartArea, labelAllowance - fudge, plotHeight, 0, 0); Subcanvas barArea = new Subcanvas(chartArea, barWidth, plotHeight, labelArea.getWidth(), 0); + int finePrintPadding = 300; + Subcanvas finePrintArea = new Subcanvas(chartArea, barWidth - 2 * finePrintPadding, finePrintHeight, finePrintPadding, + barArea.getHeight() - 50); // TODO Arbitrary fudge padding, remove when scaling work is done for (Datapoint d : data) { // If this framework isn't found, it will just be the text colour, which is fine @@ -79,6 +85,14 @@ public void draw(String title, List data) { y += BAR_THICKNESS + barSpacing; } + + this.fineprint = new FinePrint(finePrintArea, theme); + fineprint.draw(metadata); + } + + @Override + public Collection getInlinedSVGs() { + return fineprint.getInlinedSVGs(); } } diff --git a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Chart.java b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Chart.java index 8200f90..545b3d2 100644 --- a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Chart.java +++ b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Chart.java @@ -1,8 +1,18 @@ package io.quarkus.infra.performance.graphics.charts; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import io.quarkus.infra.performance.graphics.model.Config; + public interface Chart { - void draw(String title, List datasets); + void draw(String title, List datasets, Config metadata); + + // SVGs after to be done *after* the main drawing, because getting the document root before drawing causes all subsequent draws to be dropped. + // This seems to be a characteristic of the Batik streaming model. + default Collection getInlinedSVGs() { + return Collections.emptyList(); + } } diff --git a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/CubeChart.java b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/CubeChart.java similarity index 81% rename from graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/CubeChart.java rename to graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/CubeChart.java index cca4b22..123cdbd 100644 --- a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/CubeChart.java +++ b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/CubeChart.java @@ -1,18 +1,17 @@ -package io.quarkus.infra.performance.graphics; +package io.quarkus.infra.performance.graphics.charts; import static java.lang.Math.round; import java.awt.Font; import java.awt.geom.Rectangle2D; +import java.util.Collection; import java.util.List; import org.apache.batik.svggen.SVGGraphics2D; -import io.quarkus.infra.performance.graphics.charts.Alignment; -import io.quarkus.infra.performance.graphics.charts.Chart; -import io.quarkus.infra.performance.graphics.charts.Datapoint; -import io.quarkus.infra.performance.graphics.charts.Label; -import io.quarkus.infra.performance.graphics.charts.Subcanvas; +import io.quarkus.infra.performance.graphics.Theme; +import io.quarkus.infra.performance.graphics.VAlignment; +import io.quarkus.infra.performance.graphics.model.Config; public class CubeChart implements Chart { private static final int BAR_THICKNESS = 44; @@ -23,6 +22,7 @@ public class CubeChart implements Chart { private final int canvasHeight; private final int canvasWidth; private final Theme theme; + private FinePrint fineprint; public CubeChart(SVGGraphics2D g, Theme theme) { this.g = g; @@ -33,7 +33,7 @@ public CubeChart(SVGGraphics2D g, Theme theme) { } @Override - public void draw(String title, List data) { + public void draw(String title, List data, Config metadata) { g.setPaint(theme.background()); g.fill(new Rectangle2D.Double(0, 0, canvasWidth, canvasWidth)); @@ -53,6 +53,11 @@ public void draw(String title, List data) { int plotHeight = canvasHeight - titleCanvas.getHeight(); Subcanvas plotArea = new Subcanvas(g, plotWidth, plotHeight, leftMargin, titleCanvas.getHeight()); + int finePrintHeight = 80; + int finePrintPadding = 300; + Subcanvas finePrintArea = new Subcanvas(g, plotArea.getWidth() - 2 * finePrintPadding, finePrintHeight, + finePrintPadding, + plotArea.getHeight() - 50); // TODO Arbitrary fudge padding, remove when scaling work is done); int cubePadding = 1; @@ -107,6 +112,12 @@ public void draw(String title, List data) { x += dataArea.getWidth() + dataPadding; } + this.fineprint = new FinePrint(finePrintArea, theme); + fineprint.draw(metadata); } + @Override + public Collection getInlinedSVGs() { + return fineprint.getInlinedSVGs(); + } } diff --git a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/FinePrint.java b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/FinePrint.java new file mode 100644 index 0000000..eec42a2 --- /dev/null +++ b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/FinePrint.java @@ -0,0 +1,129 @@ +package io.quarkus.infra.performance.graphics.charts; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import io.quarkus.infra.performance.graphics.Theme; +import io.quarkus.infra.performance.graphics.VAlignment; +import io.quarkus.infra.performance.graphics.model.Config; + +public class FinePrint { + + private static final int padding = 60; + + private final Subcanvas g; + private final int canvasHeight; + private final int canvasWidth; + private final Theme theme; + private InlinedSVG svg; + + public FinePrint(Subcanvas g, Theme theme) { + this.g = g; + this.theme = theme; + this.canvasHeight = g.getHeight(); + this.canvasWidth = g.getWidth(); + + } + + public void draw(Config metadata) { + + g.setPaint(theme.background()); + + g.setPaint(theme.text()); + + List leftColumn = new ArrayList<>(); + List rightColumn = new ArrayList(); + + if (metadata.quarkus() != null) { + leftColumn.add("Quarkus: " + + metadata.quarkus().version()); + } + if (metadata.springboot() != null) { + leftColumn.add("Spring: " + + metadata.springboot().version()); + } + if (metadata.jvm() != null) { + + if (metadata.jvm().graalVM() != null) { + leftColumn.add("JVM: " + + metadata.jvm().graalVM().version()); + } + leftColumn.add("GraalVM: " + + metadata.jvm().version()); + + if (metadata.jvm().memory() != null) { + rightColumn.add("Memory: " + + metadata.jvm().memory()); + } + + if (metadata.jvm().args() != null && metadata.jvm().args().contains("-XX:ActiveProcessorCount=")) { + // TODO risky calculation, what if the core is a cgroup or something else and not an arg? + rightColumn.add("CPUs: " + + metadata.jvm().args().replace("-XX:ActiveProcessorCount=", "")); + } + } + + if (metadata.repo() != null) { + rightColumn.add("Source code: " + + metadata.repo().url().replace("https://github.com/", " ").replaceAll(".git$", "")); + // Use a few spaces to leave room for a logo + } + + if (!"main".equals(metadata.repo().branch())) { + rightColumn.add("Branch: " + metadata.repo().branch()); + + } + + // Make sure font sizes are the same + // TODO this can go away when we have label groups + while (rightColumn.size() < leftColumn.size()) { + // Put the padding before the last source control line + rightColumn.add(Math.max(0, rightColumn.size() - 1), " "); + } + + Label leftLabel = new Label(leftColumn.toArray(String[]::new), 0, 0) + .setHorizontalAlignment(Alignment.LEFT) + .setVerticalAlignment(VAlignment.TOP) + .setTargetHeight(g.getHeight()); + leftLabel.draw(g); + + int leftLabelWidth = leftLabel + .calculateWidth(leftColumn.stream().max(Comparator.comparingInt(String::length)).orElse("")); + int rightLabelX = leftLabelWidth + + padding; + Label rightLabel = new Label(rightColumn.toArray(String[]::new), rightLabelX, 0) + .setHorizontalAlignment(Alignment.LEFT) + .setVerticalAlignment(VAlignment.TOP) + .setTargetHeight(g.getHeight()); + rightLabel.draw(g); + int sw = rightLabel.calculateWidth("Source code:"); + + if (metadata.repo() != null) { + int logoSize = rightLabel.getAscent(); + int logoX = g.getXOffset() + rightLabelX + sw + 2; + int logoY = g.getYOffset() + (rightColumn.size()) * rightLabel.getLineHeight() - rightLabel.getAscent() + + rightLabel.getDescent() / 2; + this.svg = new InlinedSVG(getPath(theme), logoSize, + logoX, + logoY); + } + } + + private static String getPath(Theme theme) { + if (theme == Theme.DARK) { + return "/github-mark-dark.svg"; + } else { + return "/github-mark-light.svg"; + } + } + + public Collection getInlinedSVGs() { + if (svg == null) { + return Collections.emptyList(); + } + return List.of(svg); + } +} diff --git a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/InlinedSVG.java b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/InlinedSVG.java new file mode 100644 index 0000000..101850c --- /dev/null +++ b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/InlinedSVG.java @@ -0,0 +1,62 @@ +package io.quarkus.infra.performance.graphics.charts; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.batik.anim.dom.SAXSVGDocumentFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.svg.SVGDocument; + +public class InlinedSVG { + private final String resourcePath; + private final int height; + private int x; + private int y; + + public InlinedSVG(String path, int height, int x, int y) { + this.resourcePath = path; + this.height = height; + this.x = x; + this.y = y; + } + + public void draw(SAXSVGDocumentFactory factory, Element root, Document doc) { + // Load SVG from classpath + try (InputStream is = this.getClass().getResourceAsStream(resourcePath)) { + if (is == null) { + throw new IllegalArgumentException("Resource not found: " + resourcePath); + } + + SVGDocument sub = factory.createSVGDocument(null, is); + + // import the element + Element imported = (Element) doc.importNode(sub.getDocumentElement(), true); + + // Define a viewbox so scaling works + String originalWidth = imported.getAttribute("width"); + String originalHeight = imported.getAttribute("height"); + imported.setAttribute("viewBox", + String.format("0 0 %s %s", originalWidth, originalHeight)); + + // Redefine the height to the one we want + imported.setAttribute("height", + String.valueOf(height)); + // Adjust the width as well, or positioning goes wonky + imported.setAttribute("width", + String.valueOf((Integer.parseInt(originalHeight) * height) / Integer.parseInt(originalHeight))); + + // Set a position on the canvas + imported.setAttribute("x", + String.valueOf(x)); + + imported.setAttribute("y", + String.valueOf(y)); + + // append to main document + root.appendChild(imported); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Label.java b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Label.java index f549bc9..c501c87 100644 --- a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Label.java +++ b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Label.java @@ -8,7 +8,7 @@ public class Label { - private final String text; + private final String[] strings; private final int x; private final int y; private int targetHeight = 24; // Arbitrary default @@ -18,6 +18,9 @@ public class Label { private int style = Font.PLAIN; private Alignment alignment = Alignment.LEFT; private VAlignment valignment = VAlignment.MIDDLE; + private int lineHeight; + // TODO should be final, but needs constructor logic to swap round + private FontMetrics fontMetrics; /** * @param text Use \n for multiline text @@ -25,13 +28,18 @@ public class Label { * @param y The y position of the center of the text */ public Label(String text, int x, int y) { - this.text = text; + this.strings = text.split("\n"); + this.x = x; + this.y = y; + } + + public Label(String[] lines, int x, int y) { + this.strings = lines; this.x = x; this.y = y; } public void draw(Subcanvas g) { - String[] strings = text.split("\n"); int size = strings.length > 1 ? (int) (targetHeight / (strings.length * lineSpacing * realHeightRatio)) : (int) (targetHeight / (realHeightRatio)); Font font = new Font(Theme.FONT.getName(), style, size); @@ -43,15 +51,15 @@ public void draw(Subcanvas g) { // Four variables describe the height of a font: leading (pronounced like the metal), ascent, descent, and height. Leading is the amount of space required between lines of the same font. Ascent is the space above the baseline required by the tallest character in the font. Descent is the space required below the baseline by the lowest descender (the "tail" of a character like "y"). Height is the total of the three: ascent, baseline, and descent. // Should be the same as the targetHeight, but recalculate in case of rounding errors - FontMetrics fontMetrics = g.getGraphics().getFontMetrics(); - int lineHeight = (int) (fontMetrics.getHeight() * lineSpacing); + fontMetrics = g.getGraphics().getFontMetrics(); + lineHeight = (int) (fontMetrics.getHeight() * lineSpacing); int textBlockHeight = lineHeight * strings.length; // Compute starting y to vertically center the text block int yPosition = switch (valignment) { case TOP -> y + g.getGraphics().getFontMetrics().getHeight(); - case BOTTOM -> y - fontMetrics.getAscent(); - case MIDDLE -> y - textBlockHeight / 2 + fontMetrics.getAscent(); + case BOTTOM -> y - getAscent(); + case MIDDLE -> y - textBlockHeight / 2 + getAscent(); }; for (String string : strings) { @@ -67,6 +75,10 @@ public void draw(Subcanvas g) { } + int getAscent() { + return fontMetrics.getAscent(); + } + // We will need to have a labelGroup abstraction to keep sizes consistent, and perhaps also set a maximum width public Label setTargetHeight(int height) { this.targetHeight = height; @@ -90,4 +102,16 @@ public Label setVerticalAlignment(VAlignment vAlignment) { this.valignment = vAlignment; return this; } + + public int getLineHeight() { + return lineHeight; + } + + public int calculateWidth(String s) { + return fontMetrics.stringWidth(s); + } + + public int getDescent() { + return fontMetrics.getDescent(); + } } diff --git a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Subcanvas.java b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Subcanvas.java index 47e7717..88a4055 100644 --- a/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Subcanvas.java +++ b/graphics-generator/src/main/java/io/quarkus/infra/performance/graphics/charts/Subcanvas.java @@ -52,4 +52,14 @@ public int getWidth() { public int getHeight() { return height; } + + // This shouldn't need to be used much + public int getXOffset() { + return xOffset; + } + + // This shouldn't need to be used much + public int getYOffset() { + return yOffset; + } } diff --git a/graphics-generator/src/main/resources/github-mark-dark.svg b/graphics-generator/src/main/resources/github-mark-dark.svg new file mode 100644 index 0000000..d5e6491 --- /dev/null +++ b/graphics-generator/src/main/resources/github-mark-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/graphics-generator/src/main/resources/github-mark-light.svg b/graphics-generator/src/main/resources/github-mark-light.svg new file mode 100644 index 0000000..37fa923 --- /dev/null +++ b/graphics-generator/src/main/resources/github-mark-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/graphics-generator/src/test/java/io/quarkus/infra/performance/graphics/DataIngesterTest.java b/graphics-generator/src/test/java/io/quarkus/infra/performance/graphics/DataIngesterTest.java index ab27296..2b42a0d 100644 --- a/graphics-generator/src/test/java/io/quarkus/infra/performance/graphics/DataIngesterTest.java +++ b/graphics-generator/src/test/java/io/quarkus/infra/performance/graphics/DataIngesterTest.java @@ -33,27 +33,23 @@ public void testIngest() { // Don't check every value, but do some drilling down to sense check // Config - assertEquals("25-tem", data.config().jvm().version()); - assertEquals("25-graalce", data.config().jvm().graalVM().version()); - assertEquals("3.28.4", data.config().quarkus().version()); - - // CGroup - assertEquals(14, data.config().cgroup().memMax().getValue()); - assertEquals("G", data.config().cgroup().memMax().getUnits()); + assertEquals("25.0.1-tem", data.config().jvm().version()); + assertEquals("25.0.1-graalce", data.config().jvm().graalVM().version()); + assertEquals("3.29.3", data.config().quarkus().version()); // Results - assertEquals(27586.713333333333, data.results().framework(Framework.QUARKUS3_JVM).load().avThroughput().getValue()); - assertEquals(6.496666666666667, data.results().framework(Framework.SPRING3_NATIVE).build().avNativeRSS().getValue()); - assertEquals(35161, data.results().framework(Framework.SPRING3_NATIVE).build().classCount().getValue()); + assertEquals(18927.38, data.results().framework(Framework.QUARKUS3_JVM).load().avThroughput().getValue()); + assertEquals(7.183333333333334, data.results().framework(Framework.SPRING3_NATIVE).build().avNativeRSS().getValue()); + assertEquals(35173, data.results().framework(Framework.SPRING3_NATIVE).build().classCount().getValue()); // Timing - assertEquals(Instant.parse("2025-10-20T16:05:51Z"), data.timing().start()); - assertEquals(Instant.parse("2025-10-20T17:34:02Z"), data.timing().stop()); - assertEquals(5.526666666666666, + assertEquals(Instant.parse("2025-11-18T22:28:52Z"), data.timing().start()); + assertEquals(Instant.parse("2025-11-19T00:19:44Z"), data.timing().stop()); + assertEquals(8.81, data.results().framework(Framework.QUARKUS3_JVM).build().avBuildTime().getValue()); // Native - assertEquals(11620, data.results().framework(Framework.SPRING3_NATIVE).build().reflectionClassCount().getValue()); + assertEquals(11629, data.results().framework(Framework.SPRING3_NATIVE).build().reflectionClassCount().getValue()); } } \ No newline at end of file diff --git a/graphics-generator/src/test/java/io/quarkus/infra/performance/graphics/ImageGeneratorTest.java b/graphics-generator/src/test/java/io/quarkus/infra/performance/graphics/ImageGeneratorTest.java index 603ac45..47caa11 100644 --- a/graphics-generator/src/test/java/io/quarkus/infra/performance/graphics/ImageGeneratorTest.java +++ b/graphics-generator/src/test/java/io/quarkus/infra/performance/graphics/ImageGeneratorTest.java @@ -19,8 +19,11 @@ import io.quarkus.infra.performance.graphics.charts.BarChart; import io.quarkus.infra.performance.graphics.model.BenchmarkData; +import io.quarkus.infra.performance.graphics.model.Config; import io.quarkus.infra.performance.graphics.model.Framework; +import io.quarkus.infra.performance.graphics.model.FrameworkBuild; import io.quarkus.infra.performance.graphics.model.Load; +import io.quarkus.infra.performance.graphics.model.Repo; import io.quarkus.infra.performance.graphics.model.Result; import io.quarkus.infra.performance.graphics.model.Results; import io.quarkus.infra.performance.graphics.model.units.DimensionalNumber; @@ -54,6 +57,7 @@ public void setup() throws IOException { when(data.results()).thenReturn(results); addDatapoint(data, Framework.QUARKUS3_JVM, THROUGHPUT); addDatapoint(data, Framework.SPRING3_JVM, 267.87); + addConfig(data); Function fun = framework -> framework.load().avThroughput(); PlotDefinition plotDefinition = new PlotDefinition("test plot", fun); imageGenerator.generate(BarChart::new, data, plotDefinition, new File("target/images/test1.svg"), @@ -72,6 +76,13 @@ private static void addDatapoint(BenchmarkData data, Framework framework, Double when(load.avThroughput()).thenReturn(new TransactionsPerSecond(throughput)); } + private static void addConfig(BenchmarkData data) { + Config config = mock(Config.class); + when(data.config()).thenReturn(config); + when(config.repo()).thenReturn(new Repo("main", "somerepo")); + when(config.quarkus()).thenReturn(new FrameworkBuild("", "3.28.3")); + } + @Test public void testGeneration() { @@ -94,6 +105,13 @@ public void testDataLabels() throws IOException { assertTrue(contents.contains("457"), contents); } + @Test + public void testFinePrint() throws IOException { + String contents = getImageFileContents(); + assertTrue(contents.contains("somerepo"), contents); // Repo + assertTrue(contents.contains("3.28.3"), contents); // Quarkus version + } + private String getImageFileContents() throws IOException { return Files.readString(image.toPath()).replaceAll(" src: url\\(.*\\)", " src: url([truncated-hex])"); } diff --git a/graphics-generator/src/test/resources/data.json b/graphics-generator/src/test/resources/data.json index 1840bf2..aff85fd 100644 --- a/graphics-generator/src/test/resources/data.json +++ b/graphics-generator/src/test/resources/data.json @@ -1,331 +1,391 @@ { - "timing": { - "start": "2025-10-20T16:05:51Z", - "stop": "2025-10-20T17:34:02Z" - }, - "results": { - "quarkus3-jvm": { - "build": { - "timings": [ - 5.61, - 5.52, - 5.45 - ], - "avBuildTime": 5.526666666666666 - }, - "startup": { - "timings": [ - 2439, - 2460, - 2425 - ], - "avStartTime": 2441.3333333333335 - }, - "rss": { - "startup": [ - 271.65625, - 267, - 275.015625 - ], - "firstRequest": [ - 320.07421875, - 310.6484375, - 307.8515625 - ], - "avStartupRss": 271.2239583333333, - "avFirstRequestRss": 312.8580729166667 - }, - "load": { - "throughput": [ - 27171.86, - 27764.67, - 27823.61 - ], - "rss": [ - 740.3515625, - 1054.34375, - 1047.5234375 - ], - "throughputDensity": [ - 36.70129351553844, - 26.333603248466165, - 26.56132455270243 - ], - "avThroughput": 27586.713333333333, - "avMaxRss": 947.40625, - "maxThroughputDensity": 36.70129351553844 - } - }, - "quarkus3-native": { - "build": { - "timings": [ - 59.92, - 59.48, - 59.5 - ], - "native": { - "rss": [ - 4.7, - 4.53, - 4.47 - ], - "binarySize": 94.61 - }, - "avBuildTime": 59.63333333333333, - "avNativeRSS": 4.566666666666666, - "classCount": "22,086", - "fieldCount": "28,851", - "methodCount": "104,500", - "reflectionClassCount": "7,293", - "reflectionFieldCount": 233, - "reflectionMethodCount": "6,396" - }, - "startup": { - "timings": [ - 108, - 102, - 99 - ], - "avStartTime": 103 - }, - "rss": { - "startup": [ - 70.49609375, - 70.5390625, - 70.54296875 - ], - "firstRequest": [ - 77.33984375, - 77.38671875, - 77.39453125 - ], - "avStartupRss": 70.52604166666667, - "avFirstRequestRss": 77.37369791666667 - }, - "load": { - "throughput": [ - 19595.19, - 19493.77, - 19509.95 - ], - "rss": [ - 230.18359375, - 245.73046875, - 252.5859375 - ], - "throughputDensity": [ - 85.12852580311232, - 79.32988570429364, - 77.24084006062293 - ], - "avThroughput": 19532.97, - "avMaxRss": 242.83333333333334, - "maxThroughputDensity": 85.12852580311232 - } - }, - "spring3-jvm": { - "build": { - "timings": [ - 3.24, - 3.23, - 3.18 - ], - "avBuildTime": 3.216666666666667 - }, - "startup": { - "timings": [ - 3400, - 3485, - 3418 - ], - "avStartTime": 3434.3333333333335 - }, - "rss": { - "startup": [ - 429.49609375, - 427.7734375, - 436.26953125 - ], - "firstRequest": [ - 442.80859375, - 433.5625, - 443.44921875 - ], - "avStartupRss": 431.1796875, - "avFirstRequestRss": 439.9401041666667 - }, - "load": { - "throughput": [ - 10855.89, - 10870.09, - 10822.7 - ], - "rss": [ - 974.828125, - 948, - 996.22265625 - ], - "throughputDensity": [ - 11.136209267659362, - 11.466339662447258, - 10.863736065528775 - ], - "avThroughput": 10849.56, - "avMaxRss": 973.0169270833334, - "maxThroughputDensity": 11.466339662447258 - } - }, - "spring3-jvm-aot": { - "build": { - "timings": [ - 6.52, - 6.25, - 6.5 - ], - "avBuildTime": 6.423333333333333 - }, - "startup": { - "timings": [ - 3134, - 3082, - 3164 - ], - "avStartTime": 3126.6666666666665 - }, - "rss": { - "startup": [ - 431.125, - 439.921875, - 419.921875 - ], - "firstRequest": [ - 445.1171875, - 448.5078125, - 429.59375 - ], - "avStartupRss": 430.3229166666667, - "avFirstRequestRss": 441.0729166666667 - }, - "load": { - "throughput": [ - 10940.24, - 10823.94, - 10848.55 - ], - "rss": [ - 980.76953125, - 966.2421875, - 962.234375 - ], - "throughputDensity": [ - 11.154751092294395, - 11.202098335206463, - 11.274332202068752 - ], - "avThroughput": 10870.91, - "avMaxRss": 969.7486979166666, - "maxThroughputDensity": 11.274332202068752 - } - }, - "spring3-native": { - "build": { - "timings": [ - 85.54, - 86.1, - 85.46 - ], - "native": { - "rss": [ - 6.4, - 6.51, - 6.58 - ], - "binarySize": 156.05 - }, - "avBuildTime": 85.69999999999999, - "avNativeRSS": 6.496666666666667, - "classCount": "35,161", - "fieldCount": "49,774", - "methodCount": "167,076", - "reflectionClassCount": "11,620", - "reflectionFieldCount": "1,776", - "reflectionMethodCount": "12,572" - }, - "startup": { - "timings": [ - 243, - 231, - 223 - ], - "avStartTime": 232.33333333333334 - }, - "rss": { - "startup": [ - 158.40234375, - 158.33984375, - 158.2890625 - ], - "firstRequest": [ - 161.296875, - 161.234375, - 161.18359375 - ], - "avStartupRss": 158.34375, - "avFirstRequestRss": 161.23828125 - }, - "load": { - "throughput": [ - 8258.91, - 8260.43, - 8224.6 - ], - "rss": [ - 234.21484375, - 240.8515625, - 262.3828125 - ], - "throughputDensity": [ - 35.26211177638053, - 34.29676732946252, - 31.345803185946107 - ], - "avThroughput": 8247.980000000001, - "avMaxRss": 245.81640625, - "maxThroughputDensity": 35.26211177638053 - } - } - }, - "config": { - "jvm": { - "args": "-XX:ActiveProcessorCount=8", - "memory": "-Xms512m -Xmx512m", - "graalvm": { - "version": "25-graalce" - }, - "version": "25-tem" - }, - "CMD_PREFIX": "", - "num_iterations": 1, - "quarkus": { - "native_build_options": "", - "version": "3.28.4" - }, - "repo": { - "branch": "open-benchmarks", - "url": "https://github.com/edeandrea/spring-quarkus-perf-comparison.git" - }, - "profiler": { - "name": "none", - "events": "cpu" - }, - "cgroup": { - "mem_max": "14G", - "name": "spring-quarkus-perf-comparison", - "cpu": "0,2,4,6,8,10,12,14" - }, - "springboot": { - "native_build_options": "", - "version": "3.5.6" - } - } + "timing": { + "start": "2025-11-18T22:28:52Z", + "stop": "2025-11-19T00:19:44Z" + }, + "results": { + "quarkus3-jvm": { + "build": { + "timings": [ + 9.09, + 8.65, + 8.69 + ], + "avBuildTime": 8.81 + }, + "startup": { + "timings": [ + 4752, + 4319, + 4823 + ], + "avStartTime": 4631.333333333333 + }, + "rss": { + "startup": [ + 237.0078125, + 235.21875, + 233.2265625 + ], + "firstRequest": [ + 273.69921875, + 272.73828125, + 269.640625 + ], + "avStartupRss": 235.15104166666666, + "avFirstRequestRss": 272.0260416666667 + }, + "load": { + "throughput": [ + 18906.13, + 18930.89, + 18945.12 + ], + "connectionErrors": [ + 0, + 0, + 0 + ], + "requestTimeouts": [ + 0, + 0, + 0 + ], + "rss": [ + 892.6640625, + 850.1171875, + 867.515625 + ], + "throughputDensity": [ + 21.179445655122922, + 22.268565179432983, + 21.83836170097801 + ], + "avThroughput": 18927.38, + "avMaxRss": 870.0989583333334, + "maxThroughputDensity": 22.268565179432983, + "avConnectionErrors": 0, + "avRequestTimeouts": 0 + } + }, + "quarkus3-native": { + "build": { + "timings": [ + 74.67, + 73.54, + 75.39 + ], + "native": { + "rss": [ + 5.66, + 5.53, + 5.6 + ], + "binarySize": 94.37 + }, + "avBuildTime": 74.53333333333335, + "avNativeRSS": 5.596666666666667, + "classCount": "22,115", + "fieldCount": "28,910", + "methodCount": "104,754", + "reflectionClassCount": "7,297", + "reflectionFieldCount": 233, + "reflectionMethodCount": "6,508" + }, + "startup": { + "timings": [ + 427, + 78, + 82 + ], + "avStartTime": 195.66666666666666 + }, + "rss": { + "startup": [ + 70.70703125, + 71.671875, + 71.68359375 + ], + "firstRequest": [ + 77.390625, + 78.08984375, + 78.09375 + ], + "avStartupRss": 71.35416666666667, + "avFirstRequestRss": 77.85807291666667 + }, + "load": { + "throughput": [ + 16918.85, + 16976.84, + 16784.57 + ], + "connectionErrors": [ + 0, + 0, + 0 + ], + "requestTimeouts": [ + 0, + 0, + 0 + ], + "rss": [ + 310.5390625, + 299.46875, + 303.13671875 + ], + "throughputDensity": [ + 54.482195778510146, + 56.689854951476576, + 55.369636740847646 + ], + "avThroughput": 16893.420000000002, + "avMaxRss": 304.3815104166667, + "maxThroughputDensity": 56.689854951476576, + "avConnectionErrors": 0, + "avRequestTimeouts": 0 + } + }, + "spring3-jvm": { + "build": { + "timings": [ + 5.12, + 4.63, + 4.72 + ], + "avBuildTime": 4.823333333333333 + }, + "startup": { + "timings": [ + 5610, + 5576, + 5821 + ], + "avStartTime": 5669 + }, + "rss": { + "startup": [ + 497.66796875, + 490.27734375, + 502.5625 + ], + "firstRequest": [ + 507.6171875, + 501.54296875, + 512.640625 + ], + "avStartupRss": 496.8359375, + "avFirstRequestRss": 507.2669270833333 + }, + "load": { + "throughput": [ + 10064.36, + 10108.05, + 10139.21 + ], + "connectionErrors": [ + 2974, + 2987, + 3012 + ], + "requestTimeouts": [ + 0, + 0, + 0 + ], + "rss": [ + 691.6015625, + 725.8671875, + 716.984375 + ], + "throughputDensity": [ + 14.552251680316296, + 13.925481374648857, + 14.141465774620261 + ], + "avThroughput": 10103.873333333333, + "avMaxRss": 711.484375, + "maxThroughputDensity": 14.552251680316296, + "avConnectionErrors": 2991, + "avRequestTimeouts": 0 + } + }, + "spring3-jvm-aot": { + "build": { + "timings": [ + 9.76, + 9.83, + 9.94 + ], + "avBuildTime": 9.843333333333334 + }, + "startup": { + "timings": [ + 5166, + 5355, + 5101 + ], + "avStartTime": 5207.333333333333 + }, + "rss": { + "startup": [ + 492.6796875, + 496.2578125, + 495.1640625 + ], + "firstRequest": [ + 502.53125, + 506.296875, + 505.76171875 + ], + "avStartupRss": 494.7005208333333, + "avFirstRequestRss": 504.86328125 + }, + "load": { + "throughput": [ + 9751.67, + 9895.32, + 10019.83 + ], + "connectionErrors": [ + 2902, + 2954, + 2955 + ], + "requestTimeouts": [ + 0, + 0, + 0 + ], + "rss": [ + 720.640625, + 721.14453125, + 712.078125 + ], + "throughputDensity": [ + 13.531945968214046, + 13.72168763846533, + 14.071250959998244 + ], + "avThroughput": 9888.94, + "avMaxRss": 717.9544270833334, + "maxThroughputDensity": 14.071250959998244, + "avConnectionErrors": 2937, + "avRequestTimeouts": 0 + } + }, + "spring3-native": { + "build": { + "timings": [ + 101.37, + 101.1, + 101.21 + ], + "native": { + "rss": [ + 7.17, + 7.02, + 7.36 + ], + "binarySize": 156.25 + }, + "avBuildTime": 101.22666666666667, + "avNativeRSS": 7.183333333333334, + "classCount": "35,173", + "fieldCount": "49,794", + "methodCount": "167,125", + "reflectionClassCount": "11,629", + "reflectionFieldCount": "1,776", + "reflectionMethodCount": "12,584" + }, + "startup": { + "timings": [ + 250, + 253, + 251 + ], + "avStartTime": 251.33333333333334 + }, + "rss": { + "startup": [ + 182.12890625, + 182.109375, + 182.109375 + ], + "firstRequest": [ + 186.1015625, + 186.08203125, + 186.07421875 + ], + "avStartupRss": 182.11588541666666, + "avFirstRequestRss": 186.0859375 + }, + "load": { + "throughput": [ + 8662.98, + 8684.14, + 8702.74 + ], + "connectionErrors": [ + 2509, + 2539, + 2537 + ], + "requestTimeouts": [ + 0, + 0, + 0 + ], + "rss": [ + 307.640625, + 302.12109375, + 307.48828125 + ], + "throughputDensity": [ + 28.15941490172177, + 28.743904942916615, + 28.302672103865746 + ], + "avThroughput": 8683.286666666667, + "avMaxRss": 305.75, + "maxThroughputDensity": 28.743904942916615, + "avConnectionErrors": 2528.3333333333335, + "avRequestTimeouts": 0 + } + } + }, + "config": { + "jvm": { + "args": "-XX:ActiveProcessorCount=4", + "memory": "-Xms512m -Xmx512m", + "graalvm": { + "version": "25.0.1-graalce" + }, + "version": "25.0.1-tem" + }, + "CMD_PREFIX": "", + "num_iterations": 3, + "quarkus": { + "native_build_options": "", + "version": "3.29.3" + }, + "repo": { + "branch": "main", + "url": "https://github.com/quarkusio/spring-quarkus-perf-comparison.git" + }, + "profiler": { + "name": "none", + "events": "cpu" + }, + "cgroup": { + "mem_max": "", + "name": "spring-quarkus-perf-comparison", + "cpu": "" + }, + "springboot": { + "native_build_options": "", + "version": "3.5.7" + } + } } \ No newline at end of file