001/*
002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.benchmarking.common.report;
017
018import com.google.gson.JsonArray;
019import com.google.gson.JsonElement;
020import com.google.gson.JsonObject;
021import de.cuioss.benchmarking.common.constants.BenchmarkConstants;
022
023import static de.cuioss.benchmarking.common.constants.BenchmarkConstants.Metrics.Modes.*;
024import static de.cuioss.benchmarking.common.constants.BenchmarkConstants.Metrics.Units.OPS;
025import static de.cuioss.benchmarking.common.constants.BenchmarkConstants.Metrics.Units.SUFFIX_OP;
026import static de.cuioss.benchmarking.common.constants.BenchmarkConstants.Report.Errors.*;
027import static de.cuioss.benchmarking.common.constants.BenchmarkConstants.Report.Grades.*;
028import static de.cuioss.benchmarking.common.constants.BenchmarkConstants.Report.JsonFields.*;
029
030/**
031 * Computes benchmark-specific metrics from JSON results.
032 * <p>
033 * This class is responsible for extracting and calculating domain-specific benchmark metrics:
034 * <ul>
035 *   <li>Extracting throughput and latency values from JMH JSON output</li>
036 *   <li>Converting units to standard measurements (ops/s, ms/op)</li>
037 *   <li>Calculating composite performance scores</li>
038 *   <li>Assigning performance grades based on scores</li>
039 * </ul>
040 * <p>
041 * Use this class when you need to process raw JMH benchmark results into meaningful metrics.
042 * For pure statistical computations, use {@link StatisticsCalculator}.
043 * For time-series analysis and trend detection, use {@link TrendDataProcessor}.
044 * 
045 * @see StatisticsCalculator for pure statistical computations
046 * @see TrendDataProcessor for trend analysis and historical data processing
047 */
048public class MetricsComputer {
049
050    private final String throughputBenchmarkName;
051    private final String latencyBenchmarkName;
052
053    public MetricsComputer(String throughputBenchmarkName, String latencyBenchmarkName) {
054        if (throughputBenchmarkName == null || throughputBenchmarkName.isBlank()) {
055            throw new IllegalArgumentException(THROUGHPUT_NAME_REQUIRED);
056        }
057        if (latencyBenchmarkName == null || latencyBenchmarkName.isBlank()) {
058            throw new IllegalArgumentException(LATENCY_NAME_REQUIRED);
059        }
060        this.throughputBenchmarkName = throughputBenchmarkName;
061        this.latencyBenchmarkName = latencyBenchmarkName;
062    }
063
064    public BenchmarkMetrics computeMetrics(JsonArray benchmarks) {
065        if (benchmarks == null || benchmarks.isEmpty()) {
066            throw new IllegalArgumentException(NO_RESULTS_PROVIDED);
067        }
068
069        double throughput = extractThroughput(benchmarks);
070        double latency = extractLatency(benchmarks);
071        double rawPerformanceScore = calculatePerformanceScore(throughput, latency);
072        // Performance score is always stored as rounded value
073        double performanceScore = Math.round(rawPerformanceScore);
074        String performanceGrade = getPerformanceGrade(performanceScore);
075
076        return new BenchmarkMetrics(
077                throughputBenchmarkName,
078                latencyBenchmarkName,
079                throughput,
080                latency,
081                performanceScore,
082                performanceGrade
083        );
084    }
085
086    private double extractThroughput(JsonArray benchmarks) {
087        for (JsonElement element : benchmarks) {
088            JsonObject benchmark = element.getAsJsonObject();
089            String benchmarkName = benchmark.get(BENCHMARK).getAsString();
090
091            if (benchmarkName.contains(throughputBenchmarkName)) {
092                String mode = benchmark.get(MODE).getAsString();
093                JsonObject primaryMetric = benchmark.getAsJsonObject(PRIMARY_METRIC);
094                double score = primaryMetric.get(SCORE).getAsDouble();
095                String unit = primaryMetric.get(SCORE_UNIT).getAsString();
096
097                if (BenchmarkConstants.Metrics.Modes.THROUGHPUT.equals(mode) || unit.contains(OPS)) {
098                    return MetricConversionUtil.convertToOpsPerSecond(score, unit);
099                }
100            }
101        }
102
103        throw new IllegalStateException(
104                THROUGHPUT_NOT_FOUND_FORMAT.formatted(throughputBenchmarkName));
105    }
106
107    private double extractLatency(JsonArray benchmarks) {
108        for (JsonElement element : benchmarks) {
109            JsonObject benchmark = element.getAsJsonObject();
110            String benchmarkName = benchmark.get(BENCHMARK).getAsString();
111
112            if (benchmarkName.contains(latencyBenchmarkName)) {
113                String mode = benchmark.get(MODE).getAsString();
114                JsonObject primaryMetric = benchmark.getAsJsonObject(PRIMARY_METRIC);
115                double score = primaryMetric.get(SCORE).getAsDouble();
116                String unit = primaryMetric.get(SCORE_UNIT).getAsString();
117
118                if (AVERAGE_TIME.equals(mode) || SAMPLE.equals(mode) || unit.contains(SUFFIX_OP)) {
119                    return MetricConversionUtil.convertToMillisecondsPerOp(score, unit);
120                }
121            }
122        }
123
124        throw new IllegalStateException(
125                LATENCY_NOT_FOUND_FORMAT.formatted(latencyBenchmarkName));
126    }
127
128    private double calculatePerformanceScore(double throughput, double latency) {
129        double throughputScore = (throughput / 100.0) * 0.5;
130        double latencyScore = (100.0 / latency) * 0.5;
131        return throughputScore + latencyScore;
132    }
133
134    private String getPerformanceGrade(double score) {
135        if (score >= 95) return A_PLUS;
136        if (score >= 90) return A;
137        if (score >= 75) return B;
138        if (score >= 60) return C;
139        if (score >= 40) return D;
140        return F;
141    }
142
143}