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 java.util.Locale; 019 020import static de.cuioss.benchmarking.common.constants.BenchmarkConstants.Metrics.Conversions; 021import static de.cuioss.benchmarking.common.constants.BenchmarkConstants.Metrics.Units; 022import static de.cuioss.benchmarking.common.constants.BenchmarkConstants.Report.Grades; 023 024/** 025 * Central utility for converting benchmark metrics between different units. 026 * This is the SINGLE source of truth for all metric conversions. 027 */ 028public final class MetricConversionUtil { 029 030 private MetricConversionUtil() { 031 // Utility class 032 } 033 034 /** 035 * Converts a benchmark score to operations per second. 036 * 037 * @param score the raw score value 038 * @param unit the unit string from JMH 039 * @return throughput in operations per second 040 */ 041 public static double convertToOpsPerSecond(double score, String unit) { 042 // Handle throughput units directly 043 // IMPORTANT: Check more specific units first to avoid partial matches! 044 if (unit.contains(Units.OPS_PER_NS)) { 045 return score * Conversions.NANOS_TO_SECONDS; 046 } else if (unit.contains(Units.OPS_PER_US)) { 047 return score * Conversions.MICROS_TO_SECONDS; 048 } else if (unit.contains(Units.OPS_PER_MS)) { 049 return score * Conversions.MILLIS_TO_SECONDS; 050 } else if (unit.contains(Units.OPS_PER_SEC) || unit.contains(Units.OPS_PER_SEC_ALT)) { 051 return score; 052 } else if (unit.contains(Units.NS_PER_OP)) { 053 return Conversions.NANOS_TO_SECONDS / score; 054 } else if (unit.contains(Units.US_PER_OP)) { 055 return Conversions.MICROS_TO_SECONDS / score; 056 } else if (unit.contains(Units.MS_PER_OP)) { 057 return Conversions.MILLIS_TO_SECONDS / score; 058 } else if (unit.contains(Units.SEC_PER_OP)) { 059 return 1.0 / score; 060 } 061 return score; 062 } 063 064 /** 065 * Converts a benchmark score to milliseconds per operation. 066 * This is the CENTRALIZED method for all latency conversions. 067 * 068 * @param score the raw score value 069 * @param unit the unit string from JMH 070 * @return latency in milliseconds per operation 071 */ 072 public static double convertToMillisecondsPerOp(double score, String unit) { 073 // Handle latency units (time per operation) 074 // IMPORTANT: Check more specific units first to avoid partial matches! 075 // "us/op" contains "s/op", so must check "us/op" before "s/op" 076 // "ns/op" contains "s/op", so must check "ns/op" before "s/op" 077 if (unit.contains(Units.NS_PER_OP)) { 078 return score / Conversions.NANOS_TO_MILLIS; // Convert nanoseconds to milliseconds 079 } else if (unit.contains(Units.US_PER_OP)) { 080 return score / Conversions.MICROS_TO_MILLIS; // Convert microseconds to milliseconds 081 } else if (unit.contains(Units.MS_PER_OP)) { 082 return score; // Already in ms/op 083 } else if (unit.contains(Units.SEC_PER_OP)) { 084 return score * Conversions.MILLIS_TO_SECONDS; // Convert seconds to milliseconds 085 } else if (unit.contains(Units.OPS_PER_NS)) { 086 return 1.0 / (score * Conversions.NANOS_TO_MILLIS); // ops/ns -> ns/op -> ms/op 087 } else if (unit.contains(Units.OPS_PER_US)) { 088 return 1.0 / (score * Conversions.MICROS_TO_MILLIS); // ops/us -> us/op -> ms/op 089 } else if (unit.contains(Units.OPS_PER_MS)) { 090 return 1.0 / score; // ops/ms -> ms/op 091 } else if (unit.contains(Units.OPS_PER_SEC) || unit.contains(Units.OPS_PER_SEC_ALT)) { 092 return Conversions.MILLIS_TO_SECONDS / score; // ops/s -> s/op -> ms/op 093 } 094 // Unknown unit, return 0 to filter out in calculations 095 return 0; 096 } 097 098 /** 099 * Calculates a performance grade based on throughput. 100 * 101 * @param throughput operations per second 102 * @return performance grade string 103 */ 104 public static String calculatePerformanceGrade(double throughput) { 105 return switch ((int) Math.log10(Math.max(1, throughput))) { 106 case 6, 7, 8, 9 -> Grades.A_PLUS; 107 case 5 -> Grades.A; 108 case 4 -> Grades.B; 109 case 3 -> Grades.C; 110 default -> Grades.D; 111 }; 112 } 113 114 /** 115 * Central method for formatting numeric values for display. 116 * Rules: 117 * - Values less than 2: 2 fraction digits 118 * - Values less than 10: 1 fraction digit 119 * - Values greater than or equal to 10: No fraction digits 120 * 121 * @param value the numeric value to format 122 * @return formatted string representation 123 */ 124 public static String formatForDisplay(double value) { 125 if (value < 2) { 126 return String.format(Locale.US, "%.2f", value); 127 } else if (value < 10) { 128 return String.format(Locale.US, "%.1f", value); 129 } else { 130 return String.format(Locale.US, "%d", Math.round(value)); 131 } 132 } 133 134 /** 135 * Formats throughput value with appropriate units. 136 * 137 * @param value throughput in ops/s 138 * @return formatted string with units 139 */ 140 public static String formatThroughput(double value) { 141 if (value >= 1_000_000) { 142 return formatForDisplay(value / 1_000_000) + Units.M_OPS_S; 143 } else if (value >= 1000) { 144 return formatForDisplay(value / 1000) + Units.K_OPS_S; 145 } else { 146 return formatForDisplay(value) + Units.SPACE_OPS_S; 147 } 148 } 149 150 /** 151 * Formats latency value with appropriate units. 152 * 153 * @param ms latency in milliseconds 154 * @return formatted string with units 155 */ 156 public static String formatLatency(double ms) { 157 if (ms >= 1000) { 158 return formatForDisplay(ms / 1000) + Units.SUFFIX_S; 159 } else { 160 return formatForDisplay(ms) + Units.SUFFIX_MS; 161 } 162 } 163}