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.metrics; 017 018import de.cuioss.benchmarking.common.report.MetricConversionUtil; 019 020import java.time.Instant; 021import java.util.LinkedHashMap; 022import java.util.Map; 023 024/** 025 * Transforms raw Prometheus metrics into structured Quarkus runtime metrics. 026 * This class is responsible for all metric restructuring, renaming, and organization. 027 * 028 */ 029public class MetricsTransformer { 030 031 private static final String AREA_HEAP = "area=\"heap\""; 032 033 /** 034 * Transforms raw Prometheus metrics into the structured Quarkus runtime metrics format. 035 * 036 * @param allMetrics Raw metrics from Prometheus endpoint 037 * @return Structured metrics ready for JSON export 038 */ 039 public Map<String, Object> transformToQuarkusRuntimeMetrics(Map<String, Double> allMetrics) { 040 Map<String, Object> runtimeMetrics = new LinkedHashMap<>(); 041 042 // Add timestamp 043 runtimeMetrics.put("timestamp", Instant.now().toString()); 044 045 // Transform and add the four main sections 046 runtimeMetrics.put("system", createSystemMetrics(allMetrics)); 047 runtimeMetrics.put("http_server_requests", createHttpServerRequestsMetrics(allMetrics)); 048 runtimeMetrics.put("sheriff_oauth_validation_success_operations_total", createJwtValidationSuccessMetrics(allMetrics)); 049 runtimeMetrics.put("sheriff_oauth_validation_errors", createJwtValidationErrorsMetrics(allMetrics)); 050 051 return runtimeMetrics; 052 } 053 054 private Map<String, Object> createSystemMetrics(Map<String, Double> allMetrics) { 055 Map<String, Object> systemMetrics = new LinkedHashMap<>(); 056 057 processCpuAndThreadMetrics(allMetrics, systemMetrics); 058 processMemoryMetrics(allMetrics, systemMetrics); 059 060 return systemMetrics; 061 } 062 063 private void processCpuAndThreadMetrics(Map<String, Double> allMetrics, Map<String, Object> systemMetrics) { 064 for (Map.Entry<String, Double> entry : allMetrics.entrySet()) { 065 String metricName = entry.getKey(); 066 Double value = entry.getValue(); 067 068 if (metricName.startsWith("system_cpu_count")) { 069 systemMetrics.put("cpu_cores_available", value.intValue()); 070 } else if (metricName.startsWith("system_load_average_1m")) { 071 systemMetrics.put("cpu_load_average", formatNumber(value)); 072 } else if (metricName.startsWith("jdk_threads_peak_threads")) { 073 systemMetrics.put("threads_peak", value.intValue()); 074 } else if (metricName.startsWith("process_cpu_usage") && value > 0) { 075 systemMetrics.put("quarkus_cpu_usage_percent", formatNumber(value * 100)); 076 } else if (metricName.startsWith("system_cpu_usage") && value > 0) { 077 systemMetrics.put("system_cpu_usage_percent", formatNumber(value * 100)); 078 } else if (metricName.startsWith("jvm_gc_overhead_percent") && value > 0) { 079 systemMetrics.put("gc_overhead_percent", formatNumber(value * 100)); 080 } 081 } 082 } 083 084 private void processMemoryMetrics(Map<String, Double> allMetrics, Map<String, Object> systemMetrics) { 085 long totalHeapUsed = 0; 086 long totalHeapCommitted = 0; 087 long totalHeapMax = 0; 088 long totalNonHeapUsed = 0; 089 090 // Collect memory values 091 for (Map.Entry<String, Double> entry : allMetrics.entrySet()) { 092 String metricName = entry.getKey(); 093 Double value = entry.getValue(); 094 095 if (metricName.startsWith("jvm_memory_used_bytes")) { 096 if (metricName.contains(AREA_HEAP)) { 097 totalHeapUsed += value.longValue(); 098 } else if (metricName.contains("area=\"nonheap\"")) { 099 totalNonHeapUsed += value.longValue(); 100 } 101 } else if (metricName.startsWith("jvm_memory_committed_bytes") && metricName.contains(AREA_HEAP)) { 102 totalHeapCommitted += value.longValue(); 103 } else if (metricName.startsWith("jvm_memory_max_bytes") && metricName.contains(AREA_HEAP)) { 104 long maxValue = value.longValue(); 105 if (maxValue > 0) { 106 totalHeapMax += maxValue; 107 } 108 } 109 } 110 111 // Add memory metrics 112 addMemoryMetrics(systemMetrics, totalHeapUsed, totalHeapCommitted, totalHeapMax, totalNonHeapUsed); 113 } 114 115 private void addMemoryMetrics(Map<String, Object> systemMetrics, long totalHeapUsed, 116 long totalHeapCommitted, long totalHeapMax, long totalNonHeapUsed) { 117 if (totalHeapUsed > 0) { 118 systemMetrics.put("memory_heap_used_mb", totalHeapUsed / (1024 * 1024)); 119 } 120 if (totalNonHeapUsed > 0) { 121 systemMetrics.put("memory_nonheap_used_mb", totalNonHeapUsed / (1024 * 1024)); 122 } 123 if (totalHeapCommitted > 0 && totalHeapCommitted != totalHeapUsed) { 124 long diff = Math.abs(totalHeapCommitted - totalHeapUsed); 125 if (diff > totalHeapUsed * 0.1) { 126 systemMetrics.put("memory_heap_committed_mb", totalHeapCommitted / (1024 * 1024)); 127 } 128 } 129 if (totalHeapMax > 0) { 130 systemMetrics.put("memory_heap_max_mb", totalHeapMax / (1024 * 1024)); 131 } 132 if (totalHeapUsed > 0 && totalNonHeapUsed > 0) { 133 long totalMemoryUsed = totalHeapUsed + totalNonHeapUsed; 134 systemMetrics.put("memory_total_used_mb", totalMemoryUsed / (1024 * 1024)); 135 } 136 } 137 138 private Map<String, Object> createHttpServerRequestsMetrics(Map<String, Double> allMetrics) { 139 Map<String, Object> httpMetrics = new LinkedHashMap<>(); 140 141 double sumSeconds = 0; 142 double maxSeconds = 0; 143 144 for (Map.Entry<String, Double> entry : allMetrics.entrySet()) { 145 String metricName = entry.getKey(); 146 Double value = entry.getValue(); 147 148 if (metricName.startsWith("http_server_requests_seconds")) { 149 if (metricName.contains("_count")) { 150 long count = value.longValue(); 151 httpMetrics.put("total_requests", count); 152 } else if (metricName.contains("_sum")) { 153 sumSeconds = value; 154 } else if (metricName.contains("_max")) { 155 maxSeconds = value; 156 // Format the value for display 157 if (maxSeconds > 0) { 158 httpMetrics.put("max_duration_seconds", formatNumber(maxSeconds)); 159 } 160 } 161 } 162 } 163 164 // Only include the sum if it exists - format for display 165 if (sumSeconds > 0) { 166 httpMetrics.put("total_duration_seconds", formatNumber(sumSeconds)); 167 } 168 169 return httpMetrics; 170 } 171 172 private Map<String, Object> createJwtValidationSuccessMetrics(Map<String, Double> allMetrics) { 173 Map<String, Object> successMetrics = new LinkedHashMap<>(); 174 175 for (Map.Entry<String, Double> entry : allMetrics.entrySet()) { 176 String metricName = entry.getKey(); 177 Double value = entry.getValue(); 178 179 if (metricName.startsWith("sheriff_oauth_validation_success_operations_total")) { 180 String eventType = extractEventType(metricName); 181 if (eventType != null && value > 0) { 182 successMetrics.put(eventType, value.longValue()); 183 } 184 } 185 } 186 187 return successMetrics; 188 } 189 190 private Map<String, Object> createJwtValidationErrorsMetrics(Map<String, Double> allMetrics) { 191 Map<String, Object> errorsMap = new LinkedHashMap<>(); 192 193 for (Map.Entry<String, Double> entry : allMetrics.entrySet()) { 194 String metricName = entry.getKey(); 195 Double value = entry.getValue(); 196 197 if (metricName.startsWith("sheriff_oauth_validation_errors_total")) { 198 String category = extractCategory(metricName); 199 String eventType = extractEventType(metricName); 200 201 if (category != null && eventType != null) { 202 String key = category + "_" + eventType; 203 Map<String, Object> errorEntry = new LinkedHashMap<>(); 204 errorEntry.put("category", category); 205 errorEntry.put("event_type", eventType); 206 errorEntry.put("count", value.longValue()); 207 errorsMap.put(key, errorEntry); 208 } 209 } 210 } 211 212 return errorsMap; 213 } 214 215 private String extractCategory(String metricName) { 216 int catStart = metricName.indexOf("category=\""); 217 if (catStart != -1) { 218 catStart += "category=\"".length(); 219 int catEnd = metricName.indexOf("\"", catStart); 220 if (catEnd != -1) { 221 return metricName.substring(catStart, catEnd); 222 } 223 } 224 return null; 225 } 226 227 private String extractEventType(String metricName) { 228 int typeStart = metricName.indexOf("event_type=\""); 229 if (typeStart != -1) { 230 typeStart += "event_type=\"".length(); 231 int typeEnd = metricName.indexOf("\"", typeStart); 232 if (typeEnd != -1) { 233 return metricName.substring(typeStart, typeEnd); 234 } 235 } 236 return null; 237 } 238 239 /** 240 * Formats numeric values for consistent display using MetricConversionUtil rules. 241 * 242 * @param value the value to format 243 * @return formatted value as Object (String or Number) 244 */ 245 private Object formatNumber(double value) { 246 // Use MetricConversionUtil for consistent formatting 247 String formatted = MetricConversionUtil.formatForDisplay(value); 248 // Try to parse back to appropriate type 249 try { 250 if (formatted.contains(".")) { 251 return Double.parseDouble(formatted); 252 } else { 253 return Long.parseLong(formatted); 254 } 255 } catch (NumberFormatException e) { 256 // Fallback to string if parsing fails 257 return formatted; 258 } 259 } 260}