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]