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 527f248106 Preserve resource related metric suggestions
527f248106 is described below
commit 527f248106854cd5a2f9a63ba17a9ba75f91cc23
Author: Logic <[email protected]>
AuthorDate: Sun Jun 14 13:03:27 2026 +0800
Preserve resource related metric suggestions
Keep PromQL-proven related metric candidates while retaining pod and host
resource-filter suggestions for cross-signal drilldowns.
Validation: ./mvnw -pl hertzbeat-observability -am
'-Dtest=OtlpIngestionWorkspaceServiceImplTest#relatedMetricsPrefersPromqlAvailableCandidatesWhenExecutorExists+relatedMetricsKeepsResourceSuggestionsWhenPromqlFindsServiceCandidates'
test -DskipITs -Dsurefire.failIfNoSpecifiedTests=false -DfailIfNoTests=false;
script/dev/run-three-signal-live-proof.sh; in-app Browser smoke for
/ingestion/otlp/metrics, /log/manage, and /trace/manage.
---
.../impl/OtlpIngestionWorkspaceServiceImpl.java | 35 +++++++++-
.../OtlpIngestionWorkspaceServiceImplTest.java | 79 ++++++++++++++++++++++
2 files changed, 113 insertions(+), 1 deletion(-)
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 b51d31b2eb..9b5a176be3 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
@@ -944,12 +944,45 @@ public class OtlpIngestionWorkspaceServiceImpl implements
OtlpIngestionWorkspace
List<OtlpRelatedMetricsDto.Candidate> availableCandidates =
filterPromqlAvailableRelatedMetricCandidates(rawCandidates, start, end, limit);
if (!availableCandidates.isEmpty()) {
- return availableCandidates;
+ return
mergeAvailableRelatedMetricCandidates(availableCandidates, rawCandidates,
limit);
}
}
return rawCandidates.stream().limit(limit).toList();
}
+ private List<OtlpRelatedMetricsDto.Candidate>
mergeAvailableRelatedMetricCandidates(
+ List<OtlpRelatedMetricsDto.Candidate> availableCandidates,
+ List<OtlpRelatedMetricsDto.Candidate> rawCandidates,
+ int limit) {
+ if (limit <= 0) {
+ return List.of();
+ }
+ LinkedHashMap<String, OtlpRelatedMetricsDto.Candidate> merged = new
LinkedHashMap<>();
+ for (OtlpRelatedMetricsDto.Candidate candidate : availableCandidates) {
+ putRelatedMetricCandidate(merged, candidate, limit);
+ }
+ for (OtlpRelatedMetricsDto.Candidate candidate : rawCandidates) {
+ if (!"resource-filter".equals(candidate.getReason())) {
+ continue;
+ }
+ putRelatedMetricCandidate(merged, candidate, limit);
+ }
+ return merged.values().stream().limit(limit).toList();
+ }
+
+ private void putRelatedMetricCandidate(LinkedHashMap<String,
OtlpRelatedMetricsDto.Candidate> candidates,
+ OtlpRelatedMetricsDto.Candidate
candidate,
+ int limit) {
+ if (candidate == null || candidates.size() >= limit) {
+ return;
+ }
+ String query = trimToNull(candidate.getQuery());
+ if (!StringUtils.hasText(query)) {
+ return;
+ }
+ candidates.putIfAbsent(candidate.getSource() + ":" +
normalizePromqlMetricName(query), candidate);
+ }
+
private List<String>
relatedMetricCandidateNames(OtlpMetricsConsoleDto.Context context, long start,
long end) {
List<String> candidates = new ArrayList<>();
if (metricQueryRepository.hasPromqlExecutor()) {
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 51b3c365c8..e9e6be6f97 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
@@ -1406,6 +1406,85 @@ class OtlpIngestionWorkspaceServiceImplTest {
);
}
+ @Test
+ void
relatedMetricsKeepsResourceSuggestionsWhenPromqlFindsServiceCandidates() {
+ observabilitySignalIntakeGateway.recordOtlpMetricIntake(
+ Map.of(
+ "service.name", "checkout",
+ "service.namespace", "commerce",
+ "deployment.environment.name", "prod"
+ ),
+ 2_000L,
+ "http.server.duration",
+ "histogram",
+ "ms",
+ 14.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()));
+ }
+ if (query.contains("__name__=\"http_server_duration\"")) {
+ 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__",
"http_server_duration"),
+ Map.of()
+ ),
+ Collections.singletonList(new Object[]
{2_000L, 14.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",
+ "k8s.pod.name=\"checkout-7d9\" and host.name=\"node-a\"",
+ null,
+ "8"
+ );
+
+ assertTrue(related.getCandidates().stream().anyMatch(candidate ->
+ "http_server_duration".equals(candidate.getQuery())
+ && "promql-series".equals(candidate.getReason())));
+ assertTrue(related.getCandidates().stream().anyMatch(candidate ->
+ "container.cpu.usage".equals(candidate.getQuery())
+ && "pod".equals(candidate.getSource())
+ && "resource-filter".equals(candidate.getReason())
+ &&
candidate.getMatchedLabels().contains("k8s_pod_name")));
+ assertTrue(related.getCandidates().stream().anyMatch(candidate ->
+ "system.cpu.utilization".equals(candidate.getQuery())
+ && "host".equals(candidate.getSource())
+ && "resource-filter".equals(candidate.getReason())
+ &&
candidate.getMatchedLabels().contains("host_name")));
+ }
+
@Test
void relatedMetricsDiscoversCandidateNamesFromPromqlSeriesInventory() {
observabilitySignalIntakeGateway.recordOtlpMetricIntake(
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]