PrometheusResultProcessor.java
/*
* Copyright (C) 2025 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.prometheus;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
@Component
public class PrometheusResultProcessor implements TagNames {
/**
* Processes the JSON response from a Prometheus query and groups the results by
* {@link TagNames#METRICS_APP_ID_TAG}. Each appId will have a map of metrics, where the key is the metric type
* (e.g., "total", "lastUpdated") and the value is the metric value. Additionally, the appName is included in the
* map for each appId.
*
* @param root the root JSON node containing the Prometheus query results (expected structure: root.data.result)
* where each result has a "metric" object with "appId", "type", and "value" fields, and a "value" array where
* the second element is the metric value. The "metric" object may also contain "appName".
* @return a map where the key is the appId and the value is another map containing metric types as keys and their
* corresponding values. The inner map also includes the appName under the key "appName". If an appId has no
* metrics, it will not be included in the results.
* @see TagNames#METRICS_APP_ID_TAG
*/
public Collection<Map<String, String>> processPrometheusResultsForApplications(JsonNode root) {
final Map<String, Map<String, String>> groupedResults = new HashMap<>();
for (JsonNode result : root.path("data").path("result")) {
String appId = result.path("metric").path(METRICS_APP_ID_TAG).asText();
String appName = result.path("metric").path(METRICS_APP_NAME_TAG).asText();
String type = result.path("metric").path("type").asText();
String value = result.path("value").get(1).asText();
// combine measurements
groupedResults.computeIfAbsent(appId, k -> new HashMap<>()).put(type, value);
// Add appName and appId to the map for this appId
groupedResults.get(appId).put(METRICS_APP_NAME_TAG, appName);
groupedResults.get(appId).put(METRICS_APP_ID_TAG, appId);
}
return groupedResults.values();
}
/**
* Processes the JSON response from a Prometheus query and groups the results by
* {@link TagNames#METRICS_APP_LAYER_ID_TAG}. Each appLayerId will have a map of metrics, where the key is the
* metric type (e.g., "total", "lastUpdated") and the value is the metric value. Additionally, the appName is
* included in the map for each appId.
*
* @param root the root JSON node containing the Prometheus query results (expected structure: root.data.result)
* where each result has a "metric" object with "appId", "type", and "value" fields, and a "value" array where
* the second element is the metric value. The "metric" object may also contain "appName".
* @return a map where the key is the appLayerId and the value is another map containing metric types as keys and
* their corresponding values. The inner map also includes the appName under the key "appName". If an appId has
* no metrics, it will not be included in the results.
* @see TagNames#METRICS_APP_LAYER_ID_TAG
*/
public Collection<Map<String, String>> processPrometheusResultsForApplicationLayers(JsonNode root) {
final Map<String, Map<String, String>> groupedResults = new HashMap<>();
for (JsonNode result : root.path("data").path("result")) {
String appLayerId =
result.path("metric").path(METRICS_APP_LAYER_ID_TAG).asText();
String appId = result.path("metric").path(METRICS_APP_ID_TAG).asText();
String appName = result.path("metric").path(METRICS_APP_NAME_TAG).asText();
String type = result.path("metric").path("type").asText();
String value = result.path("value").get(1).asText();
// combine all measurements
groupedResults.computeIfAbsent(appLayerId, k -> new HashMap<>()).put(type, value);
// Add appName and appId and appLayerId to the map for this appLayerId
groupedResults.get(appLayerId).put(METRICS_APP_NAME_TAG, appName);
groupedResults.get(appLayerId).put(METRICS_APP_ID_TAG, appId);
groupedResults.get(appLayerId).put(METRICS_APP_LAYER_ID_TAG, appLayerId);
}
return groupedResults.values();
}
/**
* Merges two Prometheus JSON responses and processes the results into an ArrayNode. Each appId will have a map of
* metrics, where the key is the metric type (e.g., "total", "lastUpdated") and the value is the metric value.
*
* @param jsonResponse1 the first JSON response from a Prometheus query
* @param jsonResponse2 the second JSON response from a Prometheus query
* @return an ArrayNode containing maps of metrics for each appId
* @throws IOException if there is an error processing the JSON responses
*/
public ArrayNode processPrometheusResultsToJsonArray(JsonNode jsonResponse1, JsonNode jsonResponse2)
throws IOException {
JsonNode mergedResults =
new ObjectMapper().readerForUpdating(jsonResponse1).readValue(jsonResponse2);
Collection<Map<String, String>> mergedResultsList = this.processPrometheusResultsForApplications(mergedResults);
return new ObjectMapper().valueToTree(mergedResultsList);
}
}