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}