This is an automated email from the ASF dual-hosted git repository.

zqr10159 pushed a commit to branch 2.0.0
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git


The following commit(s) were added to refs/heads/2.0.0 by this push:
     new 04a3cdfef6 Discover related metric names from PromQL inventory
04a3cdfef6 is described below

commit 04a3cdfef635688d397d90fa036dffc13a789e9f
Author: Logic <[email protected]>
AuthorDate: Wed Jun 10 00:13:08 2026 +0800

    Discover related metric names from PromQL inventory
---
 .../impl/OtlpIngestionWorkspaceServiceImpl.java    | 75 +++++++++++++++++-
 .../OtlpIngestionWorkspaceServiceImplTest.java     | 92 +++++++++++++++++++++-
 2 files changed, 165 insertions(+), 2 deletions(-)

diff --git 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
index 1161040571..58ae8ba0ed 100644
--- 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
+++ 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
@@ -92,6 +92,7 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
     private static final String DEFAULT_METRICS_AGGREGATION = "sum";
     private static final String METRICS_CONSOLE_REF_ID = 
"otlp-metrics-console";
     private static final String RELATED_METRICS_REF_ID = 
"otlp-related-metrics";
+    private static final String RELATED_METRICS_INVENTORY_REF_ID = 
"otlp-related-metrics-inventory";
     private static final Pattern METRICS_FILTER_MATCHER = Pattern.compile(
             
"\\s*([A-Za-z_:][A-Za-z0-9_.:-]*)\\s*(=~|!~|!=|=)\\s*(?:\"((?:\\\\.|[^\"\\\\])*)\"|'((?:\\\\.|[^'\\\\])*)'|([^,\\s]+))\\s*"
     );
@@ -848,7 +849,7 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
             addRelatedMetricCandidate(candidates, "system.memory.usage", 
"host", "memory",
                     "resource-filter", hostLabels, resourceMatch);
         }
-        for (String metricName : candidateMetricNames(context)) {
+        for (String metricName : relatedMetricCandidateNames(context, start, 
end)) {
             addRelatedMetricCandidate(
                     candidates,
                     normalizePromqlMetricName(metricName),
@@ -876,6 +877,75 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
         return rawCandidates.stream().limit(limit).toList();
     }
 
+    private List<String> 
relatedMetricCandidateNames(OtlpMetricsConsoleDto.Context context, long start, 
long end) {
+        List<String> candidates = new ArrayList<>();
+        if (metricQueryRepository.hasPromqlExecutor()) {
+            candidates.addAll(collectPromqlRelatedMetricNames(context, start, 
end));
+        }
+        candidates.addAll(candidateMetricNames(context));
+        return normalizeCandidateMetricNames(candidates);
+    }
+
+    private List<String> 
collectPromqlRelatedMetricNames(OtlpMetricsConsoleDto.Context context, long 
start, long end) {
+        String query = buildRelatedMetricInventoryQuery(context);
+        if (!StringUtils.hasText(query)) {
+            return List.of();
+        }
+        MetricQueryRepository.PromqlRangeQueryResult result = 
metricQueryRepository.queryPromqlRange(
+                RELATED_METRICS_INVENTORY_REF_ID,
+                query,
+                start,
+                end,
+                resolvePromqlStep(start, end, null)
+        );
+        if (result == null) {
+            return List.of();
+        }
+        if (result.errorMessage() != null) {
+            log.debug("query related metric inventory failed: {}", 
result.errorMessage());
+            return List.of();
+        }
+        return extractMetricNamesFromQueryData(result.results());
+    }
+
+    private String 
buildRelatedMetricInventoryQuery(OtlpMetricsConsoleDto.Context context) {
+        if (context == null || !StringUtils.hasText(context.getServiceName())) 
{
+            return null;
+        }
+        List<String> matchers = new ArrayList<>();
+        matchers.add("service_name=\"" + 
escapePromqlLabelValue(context.getServiceName()) + "\"");
+        if (StringUtils.hasText(context.getServiceNamespace())) {
+            matchers.add("service_namespace=\"" + 
escapePromqlLabelValue(context.getServiceNamespace()) + "\"");
+        }
+        if (StringUtils.hasText(context.getEnvironment())) {
+            matchers.add("deployment_environment_name=\"" + 
escapePromqlLabelValue(context.getEnvironment()) + "\"");
+        }
+        if (context.getEntityId() != null) {
+            matchers.add("hertzbeat_entity_id=\"" + 
escapePromqlLabelValue(String.valueOf(context.getEntityId())) + "\"");
+        }
+        if (StringUtils.hasText(context.getEntityType())) {
+            matchers.add("hertzbeat_entity_type=\"" + 
escapePromqlLabelValue(context.getEntityType()) + "\"");
+        }
+        return "sum by (__name__) ({" + String.join(", ", matchers) + "})";
+    }
+
+    private List<String> extractMetricNamesFromQueryData(DatasourceQueryData 
results) {
+        if (results == null || CollectionUtils.isEmpty(results.getFrames())) {
+            return List.of();
+        }
+        LinkedHashSet<String> metricNames = new LinkedHashSet<>();
+        for (DatasourceQueryData.SchemaData frame : results.getFrames()) {
+            if (frame == null || frame.getSchema() == null || 
CollectionUtils.isEmpty(frame.getSchema().getLabels())) {
+                continue;
+            }
+            String metricName = 
trimToNull(frame.getSchema().getLabels().get("__name__"));
+            if (StringUtils.hasText(metricName)) {
+                metricNames.add(metricName);
+            }
+        }
+        return List.copyOf(metricNames);
+    }
+
     private List<OtlpRelatedMetricsDto.Candidate> 
filterPromqlAvailableRelatedMetricCandidates(
             List<OtlpRelatedMetricsDto.Candidate> candidates,
             long start,
@@ -895,6 +965,9 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                         end,
                         step
                 );
+                if (result == null) {
+                    continue;
+                }
                 if (result.errorMessage() != null) {
                     log.debug("query related metric candidate failed: {}", 
result.errorMessage());
                     continue;
diff --git 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
index 36816bcd21..92524b7ee6 100644
--- 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
+++ 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
@@ -1186,7 +1186,7 @@ class OtlpIngestionWorkspaceServiceImplTest {
         
when(workspaceQueryGateway.findIdentitiesByEntityId(42L)).thenReturn(List.of());
         when(metricQueryRepository.hasPromqlExecutor()).thenReturn(true);
         when(metricQueryRepository.queryPromqlRange(
-                eq("otlp-related-metrics"),
+                anyString(),
                 anyString(),
                 anyLong(),
                 anyLong(),
@@ -1241,6 +1241,96 @@ class OtlpIngestionWorkspaceServiceImplTest {
         );
     }
 
+    @Test
+    void relatedMetricsDiscoversCandidateNamesFromPromqlSeriesInventory() {
+        observabilitySignalIntakeGateway.recordOtlpMetricIntake(
+                Map.of(
+                        "service.name", "checkout",
+                        "service.namespace", "commerce",
+                        "deployment.environment.name", "prod"
+                ),
+                2_000L,
+                "stale.intake.metric",
+                "gauge",
+                "1",
+                1.0,
+                Map.of()
+        );
+        
when(workspaceQueryGateway.findEntityById(42L)).thenReturn(java.util.Optional.empty());
+        
when(workspaceQueryGateway.findIdentitiesByEntityId(42L)).thenReturn(List.of());
+        when(metricQueryRepository.hasPromqlExecutor()).thenReturn(true);
+        when(metricQueryRepository.queryPromqlRange(
+                anyString(),
+                anyString(),
+                anyLong(),
+                anyLong(),
+                anyString()
+        )).thenAnswer(invocation -> {
+            String refId = invocation.getArgument(0);
+            String query = invocation.getArgument(1);
+            if ("otlp-related-metrics-inventory".equals(refId)) {
+                return promqlSuccess(new DatasourceQueryData(
+                        "otlp-related-metrics-inventory",
+                        200,
+                        null,
+                        List.of(new DatasourceQueryData.SchemaData(
+                                new DatasourceQueryData.MetricSchema(
+                                        List.of(new 
DatasourceQueryData.MetricField("__value__", "number", null)),
+                                        Map.of("__name__", 
"rpc_server_duration_milliseconds"),
+                                        Map.of()
+                                ),
+                                Collections.singletonList(new Object[] {7.0})
+                        ))
+                ));
+            }
+            if 
(query.contains("__name__=\"rpc_server_duration_milliseconds\"")) {
+                return promqlSuccess(new DatasourceQueryData(
+                        "otlp-related-metrics",
+                        200,
+                        null,
+                        List.of(new DatasourceQueryData.SchemaData(
+                                new DatasourceQueryData.MetricSchema(
+                                        List.of(
+                                                new 
DatasourceQueryData.MetricField("__ts__", "time", null),
+                                                new 
DatasourceQueryData.MetricField("__value__", "number", null)
+                                        ),
+                                        Map.of("__name__", 
"rpc_server_duration_milliseconds"),
+                                        Map.of()
+                                ),
+                                Collections.singletonList(new Object[] 
{2_000L, 7.0})
+                        ))
+                ));
+            }
+            return promqlSuccess(new 
DatasourceQueryData("otlp-related-metrics", 200, null, List.of()));
+        });
+
+        OtlpRelatedMetricsDto related = 
otlpIngestionWorkspaceService.getRelatedMetrics(
+                42L,
+                "service",
+                1_000L,
+                2_000L,
+                "checkout",
+                "commerce",
+                "prod",
+                null,
+                null,
+                "8"
+        );
+
+        assertFalse(related.getCandidates().isEmpty());
+        assertEquals("rpc_server_duration_milliseconds", 
related.getCandidates().getFirst().getQuery());
+        assertEquals("promql-series", 
related.getCandidates().getFirst().getReason());
+        verify(metricQueryRepository).queryPromqlRange(
+                eq("otlp-related-metrics-inventory"),
+                argThat(query -> query.contains("sum by (__name__)")
+                        && query.contains("service_name=\"checkout\"")
+                        && query.contains("service_namespace=\"commerce\"")),
+                eq(1_000L),
+                eq(2_000L),
+                anyString()
+        );
+    }
+
     @Test
     void metricsConsoleAppliesFilterWhenQueryIsExplicitMetricName() {
         String expectedQuery = 
groupedMetricPromql("__name__=\"http_server_request_duration_count\", "


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to