public static void comparePerformance(final int rounds,
                                          final long iterations,
                                          final TestSetup setup,
                                          final Consumer<String> outputConsumer,
                                          final TimedCase... testCases) {

        final Map<String, List<Duration>> summaryMap = new HashMap<>();
        Arrays.stream(testCases)
                .map(TimedCase::getName)
                .forEach(name ->
                        summaryMap.computeIfAbsent(name, k -> new ArrayList<>()));

        Objects.requireNonNull(testCases);
        for (int i = 0; i < rounds; i++) {
            final int round = i + 1;
            outputConsumer.accept("Starting round " + round);
            for (final TimedCase testCase : testCases) {
                final String name = testCase.getName();
                final MeasuredWork work = testCase.getWork();
                if (setup != null) {
                    LOGGER.debug("Running setup");
                    setup.run(round, iterations);
                }
                final Duration duration = DurationTimer.measure(() -> {
                    work.run(round, iterations);
                });
                summaryMap.get(name).add(duration);
                outputConsumer.accept(LogUtil.message("Completed '{}' (round {}) in {}", name, round, duration));
            }
        }
        final List<Entry<String, List<Duration>>> summaryData = Arrays.stream(testCases)
                .map(testCase -> Map.entry(testCase.name, summaryMap.get(testCase.name)))
                .toList();

        final TableBuilder<Entry<String, List<Duration>>> tableBuilder = AsciiTable.builder(summaryData)
                .withColumn(Column.of("Name", Entry::getKey));

        final int warmedUpRounds = rounds > 1
                ? rounds - 1
                : rounds;
        int idx = 0;

        if (rounds > 1) {
            final int idxCopy = idx++;
            tableBuilder.withColumn(Column.durationNanos("Warmup Round", entry ->
                    entry.getValue().get(idxCopy)));
        }
        // Rest of the rounds
        for (int round = 1; round <= warmedUpRounds; round++) {
            final int idxCopy = idx++;
            tableBuilder.withColumn(Column.durationNanos("Round " + round, entry ->
                    entry.getValue().get(idxCopy)));
        }
        // If we have multiple rounds then ignore first round for jvm warm-up
        final int skipCount = rounds > 1
                ? 1
                : 0;
        final String tableStr = tableBuilder
                .withColumn(Column.durationNanos("Min", entry ->
                        entry.getValue().stream().skip(skipCount).min(Duration::compareTo).get()))
                .withColumn(Column.durationNanos("Max", entry ->
                        entry.getValue().stream().skip(skipCount).max(Duration::compareTo).get()))
                .withColumn(Column.durationNanos("Avg over rounds", entry ->
                        Duration.ofNanos((long) entry.getValue()
                                .stream()
                                .skip(skipCount)
                                .mapToLong(Duration::toNanos)
                                .average()
                                .getAsDouble())))
                .withColumn(Column.decimal("Per iter (last round)", entry ->
                        entry.getValue().get(rounds - 1).toNanos() / (double) iterations, 6))
                .build();
        outputConsumer.accept(LogUtil.message("Summary (iterations: {}, values in nanos):\n{}",
                ModelStringUtil.formatCsv(iterations),
                tableStr));
    }
Compares the performance of one or more {@link TimedCase}s, repeating each testCase
over n iterations and repeating all that over x rounds.

@param rounds         Number of rounds to perform. A round runs n iterations for each testCase.
                      If rounds >1 then the results for the first round are treated as a JVM warm-up
                      and are not counted in the aggregate stats.
@param iterations     Number of times to run each testCase in a round.
@param setup          Run before each test case in each round
@param outputConsumer The consumer for the tabular results data
@param testCases      The test cases to run in each round.
stroom-test-common/src/main/java/stroom/test/common/TestUtil.java