diff --git a/CHANGELOG.md b/CHANGELOG.md index 19c44ff5f..e9f7124c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -337,6 +337,13 @@ Entries land here as they merge. ### Internal +- **Benchmark suite cleanup (not shipped).** Removed three redundant + benchmark mains: `FullCvBenchmark` (superseded by the JMH + `TemplateCvJmhBenchmark`), `GraphComposeBenchmark` (early-engine relic + duplicating `CurrentSpeedBenchmark`'s `engine-simple` scenario), and + `ScalabilityBenchmark` (its thread-scaling sweep folded into + `CurrentSpeedBenchmark`'s full-profile throughput run, now `1,2,4,8,16`). + Dropped the matching `run-benchmarks.ps1` steps and doc entries. - **Removed the `java.awt.*` / `java.util.*` co-wildcard in four files.** `InvoiceTemplateComposer`, `ProposalTemplateComposer`, `WeeklyScheduleTemplateComposer`, and the engine `PdfRenderingSystemECS` diff --git a/benchmarks/README.md b/benchmarks/README.md index f6041365c..e232c6e21 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -62,15 +62,11 @@ | File | Role | |---|---| | `CurrentSpeedBenchmark` | Default scenario runner — what CI's `perf-smoke` job exercises. Takes a `-Dgraphcompose.benchmark.profile=smoke\|full\|stress` switch. | -| `ComparativeBenchmark` | Renders the same fixtures through GraphCompose, iText, openHTMLToPDF, JasperReports. **Rough local comparison only** — see "When not to use" above. | -| `FullCvBenchmark`, `ScalabilityBenchmark` | Fixture-specific runners for CV and table-heavy scenarios. | -| `CanonicalBenchmarkSupport`, `BenchmarkSupport` | Shared fixture builders + measurement helpers. | +| `ComparativeBenchmark` | Renders the same fixtures through GraphCompose, iText, openHTMLToPDF, JasperReports. **Rough local comparison only** — see "When not to use" above. || `CanonicalBenchmarkSupport`, `BenchmarkSupport` | Shared fixture builders + measurement helpers. | | `BenchmarkReportWriter` | Writes JSON / CSV / text reports under `benchmarks/target/benchmarks/`. | | `BenchmarkDiffTool` | Compares two JSON reports and prints a delta table. Useful for pre/post comparisons. | | `BenchmarkMedianTool` | Median + dispersion across N runs of the same scenario. | | `GraphComposeStressTest`, `EnduranceTest` | Long-running stress / endurance harnesses. | -| `GraphComposeBenchmark` | Legacy entry point preserved for one downstream caller. New work should target `CurrentSpeedBenchmark`. | - ## Running From the repo root: diff --git a/benchmarks/src/main/java/com/demcha/compose/CurrentSpeedBenchmark.java b/benchmarks/src/main/java/com/demcha/compose/CurrentSpeedBenchmark.java index 2858d64a6..bbda30b8f 100644 --- a/benchmarks/src/main/java/com/demcha/compose/CurrentSpeedBenchmark.java +++ b/benchmarks/src/main/java/com/demcha/compose/CurrentSpeedBenchmark.java @@ -55,7 +55,9 @@ public final class CurrentSpeedBenchmark { private static final int DEFAULT_FULL_WARMUP_ITERATIONS = 12; private static final int DEFAULT_FULL_MEASUREMENT_ITERATIONS = 40; private static final int DEFAULT_FULL_DOCS_PER_THREAD = 12; - private static final String DEFAULT_FULL_THREAD_COUNTS = "1,2,4,8"; + // The 16-thread tier is absorbed from the removed ScalabilityBenchmark so the + // full profile keeps a thread-scaling data point (smoke runs no throughput). + private static final String DEFAULT_FULL_THREAD_COUNTS = "1,2,4,8,16"; // Bumped from 2/5 to 30/100 so smoke runs reach a steady JIT state and the // p95 calculation actually has enough samples to interpolate rather than // collapsing to the maximum observed time. The smoke profile remains the diff --git a/benchmarks/src/main/java/com/demcha/compose/FullCvBenchmark.java b/benchmarks/src/main/java/com/demcha/compose/FullCvBenchmark.java deleted file mode 100644 index c035f96e3..000000000 --- a/benchmarks/src/main/java/com/demcha/compose/FullCvBenchmark.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.demcha.compose; - -import com.demcha.compose.document.api.DocumentSession; -import com.demcha.compose.document.templates.api.DocumentTemplate; -import com.demcha.compose.document.templates.cv.presets.ModernProfessional; -import com.demcha.compose.document.templates.cv.spec.CvSpec; -import com.demcha.compose.document.theme.BusinessTheme; -import org.apache.pdfbox.pdmodel.common.PDRectangle; - -import java.util.Arrays; - -public class FullCvBenchmark { - - private static final int WARMUP_ITERATIONS = Integer.getInteger("graphcompose.benchmark.fullCv.warmup", 100); - private static final int MEASUREMENT_ITERATIONS = Integer.getInteger("graphcompose.benchmark.fullCv.iterations", 500); - - public static void main(String[] args) { - BenchmarkSupport.configureQuietLogging(); - System.out.println("Starting FullCvBenchmark..."); - - CvSpec cv = CanonicalBenchmarkSupport.canonicalCv(); - DocumentTemplate template = ModernProfessional.create(BusinessTheme.modern()); - - System.out.println("Warming up JVM (JIT compilation, font cache warmup)..."); - for (int i = 0; i < WARMUP_ITERATIONS; i++) { - generateCvInMemory(template, cv); - } - - System.out.println("Measuring performance (" + MEASUREMENT_ITERATIONS + " iterations)..."); - long[] durationsNs = new long[MEASUREMENT_ITERATIONS]; - - for (int i = 0; i < MEASUREMENT_ITERATIONS; i++) { - long start = System.nanoTime(); - generateCvInMemory(template, cv); - long end = System.nanoTime(); - durationsNs[i] = end - start; - } - - printStatistics(durationsNs); - } - - private static void generateCvInMemory(DocumentTemplate template, CvSpec cv) { - try (DocumentSession document = GraphCompose.document() - .pageSize(com.demcha.compose.document.api.DocumentPageSize.A4) - .margin(15, 10, 15, 15) - .create()) { - template.compose(document, cv); - document.toPdfBytes(); - } catch (Exception e) { - throw new RuntimeException("Failed to generate PDF", e); - } - } - - private static void printStatistics(long[] durationsNs) { - Arrays.sort(durationsNs); - - double[] durationsMs = Arrays.stream(durationsNs).mapToDouble(ns -> ns / 1_000_000.0).toArray(); - - double min = durationsMs[0]; - double max = durationsMs[durationsMs.length - 1]; - double avg = Arrays.stream(durationsMs).average().orElse(0.0); - double median = durationsMs[(int) (durationsMs.length * 0.5)]; - double p95 = durationsMs[(int) (durationsMs.length * 0.95)]; - double p99 = durationsMs[(int) (durationsMs.length * 0.99)]; - - System.out.println("\nBenchmark results (milliseconds):"); - System.out.println("------------------------------------------------"); - System.out.printf("Min time: %.2f ms%n", min); - System.out.printf("Average time: %.2f ms%n", avg); - System.out.printf("Median (50%%): %.2f ms (typical response time)%n", median); - System.out.printf("95th percentile: %.2f ms (95%% of runs finish within this)%n", p95); - System.out.printf("99th percentile: %.2f ms (rare spikes or GC pressure)%n", p99); - System.out.printf("Max time: %.2f ms%n", max); - System.out.println("------------------------------------------------"); - - if (median < 200) { - System.out.println("Verdict: Excellent. The engine is very fast for this scenario."); - } else if (median < 1000) { - System.out.println("Verdict: Good. This is a healthy speed for complex generation."); - } else { - System.out.println("Verdict: Slow enough to investigate with a profiler."); - } - } -} diff --git a/benchmarks/src/main/java/com/demcha/compose/GraphComposeBenchmark.java b/benchmarks/src/main/java/com/demcha/compose/GraphComposeBenchmark.java deleted file mode 100644 index f4717e66c..000000000 --- a/benchmarks/src/main/java/com/demcha/compose/GraphComposeBenchmark.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.demcha.compose; - -import com.demcha.compose.engine.components.style.Margin; -import org.apache.pdfbox.pdmodel.common.PDRectangle; - -import java.util.Arrays; - -public class GraphComposeBenchmark { - - private static final int WARMUP_ITERATIONS = Integer.getInteger("graphcompose.benchmark.coreEngine.warmup", 100); - private static final int MEASUREMENT_ITERATIONS = Integer.getInteger("graphcompose.benchmark.coreEngine.iterations", 500); - - public static void main(String[] args) { - BenchmarkSupport.configureQuietLogging(); - System.out.println("Starting GraphComposeBenchmark..."); - - System.out.println("Warming up JVM (JIT compilation, font cache warmup)..."); - for (int i = 0; i < WARMUP_ITERATIONS; i++) { - generateCvInMemory(); - } - - System.out.println("Measuring performance (" + MEASUREMENT_ITERATIONS + " iterations)..."); - long[] durationsNs = new long[MEASUREMENT_ITERATIONS]; - - for (int i = 0; i < MEASUREMENT_ITERATIONS; i++) { - long start = System.nanoTime(); - generateCvInMemory(); - long end = System.nanoTime(); - durationsNs[i] = end - start; - } - - printStatistics(durationsNs); - } - - private static void generateCvInMemory() { - try { - CanonicalBenchmarkSupport.renderSimpleBenchmarkDocument( - PDRectangle.A4, - Margin.of(24), - "CoreEngineRoot", - "GraphCompose Core Benchmark", - "Analytical engineer focused on reliable platform design. " - + "Testing paragraph breaking and layout calculation engine."); - } catch (Exception e) { - throw new RuntimeException("Failed to generate PDF", e); - } - } - - private static void printStatistics(long[] durationsNs) { - Arrays.sort(durationsNs); - - double[] durationsMs = Arrays.stream(durationsNs).mapToDouble(ns -> ns / 1_000_000.0).toArray(); - - double min = durationsMs[0]; - double max = durationsMs[durationsMs.length - 1]; - double avg = Arrays.stream(durationsMs).average().orElse(0.0); - double median = durationsMs[(int) (durationsMs.length * 0.5)]; - double p95 = durationsMs[(int) (durationsMs.length * 0.95)]; - double p99 = durationsMs[(int) (durationsMs.length * 0.99)]; - - System.out.println("\nBenchmark results (milliseconds):"); - System.out.println("------------------------------------------------"); - System.out.printf("Min time: %.2f ms%n", min); - System.out.printf("Average time: %.2f ms%n", avg); - System.out.printf("Median (50%%): %.2f ms (typical response time)%n", median); - System.out.printf("95th percentile: %.2f ms (95%% of runs finish within this)%n", p95); - System.out.printf("99th percentile: %.2f ms (rare spikes or GC pressure)%n", p99); - System.out.printf("Max time: %.2f ms%n", max); - System.out.println("------------------------------------------------"); - - if (median < 100) { - System.out.println("Verdict: Excellent. The engine is very fast for this scenario."); - } else if (median < 500) { - System.out.println("Verdict: Good. This is a healthy speed for a synchronous REST API."); - } else { - System.out.println("Verdict: Slow enough to investigate with a profiler."); - } - } -} diff --git a/benchmarks/src/main/java/com/demcha/compose/ScalabilityBenchmark.java b/benchmarks/src/main/java/com/demcha/compose/ScalabilityBenchmark.java deleted file mode 100644 index b8e945ef6..000000000 --- a/benchmarks/src/main/java/com/demcha/compose/ScalabilityBenchmark.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.demcha.compose; - -import com.demcha.compose.engine.components.style.Margin; -import org.apache.pdfbox.pdmodel.common.PDRectangle; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.*; - -/** - * Linear Scalability Test - * Measures throughput (documents per second) as thread count increases. - */ -public class ScalabilityBenchmark { - - private static final int DOCUMENTS_PER_THREAD = Integer.getInteger("graphcompose.scalability.documentsPerThread", 100); - private static final int WARMUP_DOCS = Integer.getInteger("graphcompose.scalability.warmupDocs", 100); - private static final String THREAD_COUNTS = System.getProperty("graphcompose.scalability.threads", "1,2,4,8,16"); - - public static void main(String[] args) throws Exception { - BenchmarkSupport.configureQuietLogging(); - System.out.println("Starting Scalability Benchmark: Linear Scalability"); - System.out.println("------------------------------------------------------------"); - - // Warmup - for (int i = 0; i < WARMUP_DOCS; i++) { - generateOne(); - } - - int[] threadCounts = parseThreadCounts(THREAD_COUNTS); - System.out.println(String.format("%-10s | %-15s | %-12s", "Threads", "Total Docs", "Throughput (docs/sec)")); - System.out.println("------------------------------------------------------------"); - - for (int threads : threadCounts) { - runScalabilityTest(threads); - } - } - - private static void runScalabilityTest(int threads) throws Exception { - int totalDocs = threads * DOCUMENTS_PER_THREAD; - ExecutorService executor = Executors.newFixedThreadPool(threads); - - long startTime = System.nanoTime(); - - List> futures = new ArrayList<>(); - for (int i = 0; i < totalDocs; i++) { - futures.add(executor.submit(() -> { - try { - generateOne(); - } catch (Exception e) { - e.printStackTrace(); - } - })); - } - - for (Future future : futures) { - future.get(); - } - - long endTime = System.nanoTime(); - executor.shutdown(); - executor.awaitTermination(1, TimeUnit.MINUTES); - - double durationSec = (endTime - startTime) / 1_000_000_000.0; - double throughput = totalDocs / durationSec; - - System.out.println(String.format("%-10d | %-15d | %12.2f", threads, totalDocs, throughput)); - } - - private static void generateOne() throws Exception { - CanonicalBenchmarkSupport.renderSimpleBenchmarkDocument( - PDRectangle.A4, - Margin.of(24), - "ScalabilityRoot", - "Scalability", - "Scalability test message."); - } - - private static int[] parseThreadCounts(String raw) { - return Arrays.stream(raw.split(",")) - .map(String::trim) - .filter(value -> !value.isEmpty()) - .mapToInt(Integer::parseInt) - .filter(value -> value > 0) - .toArray(); - } -} diff --git a/docs/operations/benchmarks.md b/docs/operations/benchmarks.md index 315f4d523..775483384 100644 --- a/docs/operations/benchmarks.md +++ b/docs/operations/benchmarks.md @@ -36,15 +36,10 @@ The script prints numbered sections so you can map console output to the pipelin 1. `01-build-classpath` Builds the test classpath once and writes `target/benchmark.classpath`. 2. `02-current-speed` - Runs `CurrentSpeedBenchmark` in the selected profile. + Runs `CurrentSpeedBenchmark` in the selected profile. The full profile also + runs the thread-scaling throughput sweep (1 → 16 threads). 3. `03-comparative` Runs the GraphCompose canonical vs iText 5 vs JasperReports comparison. -4. `04-core-engine` - Runs `GraphComposeBenchmark`. -5. `05-full-cv` - Runs `FullCvBenchmark`. -6. `06-scalability` - Runs the thread-scaling throughput benchmark. 7. `07-stress` Runs the concurrent stability stress test. 8. `08-endurance` diff --git a/scripts/run-benchmarks.ps1 b/scripts/run-benchmarks.ps1 index dbe162c08..e3d3947b6 100644 --- a/scripts/run-benchmarks.ps1 +++ b/scripts/run-benchmarks.ps1 @@ -5,8 +5,8 @@ Runs the local GraphCompose benchmark pipeline and stores timestamped logs and r .DESCRIPTION The wrapper performs a staged local run: -01 build classpath, 02 current-speed, 03 comparative, 04 core engine, 05 full CV, 06 scalability, -07 stress, optional 08 endurance, then 09/10 diff steps. +01 build classpath, 02 current-speed, 03 comparative, 07 stress, +optional 08 endurance, then 09/10 diff steps. Current-speed diffs are profile-aware. The wrapper only compares reports from the same current-speed profile (`smoke` or `full`) and skips the @@ -368,9 +368,6 @@ try { -InputPaths $comparativeRuns | Out-Null } - Invoke-JavaMain -Name "04-core-engine" -Classpath $javaClasspath -MainClass "com.demcha.compose.GraphComposeBenchmark" - Invoke-JavaMain -Name "05-full-cv" -Classpath $javaClasspath -MainClass "com.demcha.compose.FullCvBenchmark" - Invoke-JavaMain -Name "06-scalability" -Classpath $javaClasspath -MainClass "com.demcha.compose.ScalabilityBenchmark" Invoke-JavaMain -Name "07-stress" -Classpath $javaClasspath -MainClass "com.demcha.compose.GraphComposeStressTest" if ($IncludeEndurance) {