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

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new eda9884d6 fix(metrics): preserve unavailable broker runtime fields 
(#4521)
eda9884d6 is described below

commit eda9884d6be13432813545ea3b91c86537daa2c2
Author: zmuxuny <[email protected]>
AuthorDate: Mon Sep 21 17:29:26 2026 +0800

    fix(metrics): preserve unavailable broker runtime fields (#4521)
    
    ApacheRocketMqClusterMetricsCollector previously omitted 
broker.disk.usage_ratio, broker.jvm.heap.usage_ratio and 
broker.send_queue.usage_ratio when the Broker runtime table lacked those 
fields, and alert reconciliation then treated the missing sample as resolved. 
Each field is now emitted as an UNAVAILABLE sample (metricOrUnavailable) while 
broker availability itself stays AVAILABLE.
    
    Fixes #4519
---
 .../ApacheRocketMqClusterMetricsCollector.java     | 24 +++++++++-------
 .../ApacheRocketMqClusterMetricsCollectorTest.java | 33 ++++++++++++++++++++++
 .../studio/ops/alert/NativeAlertProcessorTest.java | 33 ++++++++++++++++++++++
 3 files changed, 80 insertions(+), 10 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/collectors/ApacheRocketMqClusterMetricsCollector.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/collectors/ApacheRocketMqClusterMetricsCollector.java
index df1435be8..9196ca196 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/collectors/ApacheRocketMqClusterMetricsCollector.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/collectors/ApacheRocketMqClusterMetricsCollector.java
@@ -113,16 +113,14 @@ public class ApacheRocketMqClusterMetricsCollector 
implements ClusterMetricsColl
                 return;
             }
             samples.add(available(BROKER_AVAILABILITY, instance, clusterId, 
labels, 1D, collectedAt));
-            parseDiskUsage(runtime.getTable().get("commitLogDiskRatio"))
-                    .ifPresent(value -> 
samples.add(available(BROKER_DISK_USAGE_RATIO, instance, clusterId,
-                            labels, value, collectedAt)));
-            parseHeapUsage(runtime.getTable().get("jvmMemoryHeapUsed"), 
runtime.getTable().get("jvmMemoryHeapMax"))
-                    .ifPresent(value -> 
samples.add(available(BROKER_JVM_HEAP_USAGE_RATIO, instance, clusterId,
-                            labels, value, collectedAt)));
-            parseUsageRatio(runtime.getTable().get("sendThreadPoolQueueSize"),
-                    runtime.getTable().get("sendThreadPoolQueueCapacity"))
-                    .ifPresent(value -> 
samples.add(available(BROKER_SEND_QUEUE_USAGE_RATIO, instance, clusterId,
-                            labels, value, collectedAt)));
+            samples.add(metricOrUnavailable(BROKER_DISK_USAGE_RATIO, instance, 
clusterId, labels,
+                    
parseDiskUsage(runtime.getTable().get("commitLogDiskRatio")), collectedAt));
+            samples.add(metricOrUnavailable(BROKER_JVM_HEAP_USAGE_RATIO, 
instance, clusterId, labels,
+                    parseHeapUsage(runtime.getTable().get("jvmMemoryHeapUsed"),
+                            runtime.getTable().get("jvmMemoryHeapMax")), 
collectedAt));
+            samples.add(metricOrUnavailable(BROKER_SEND_QUEUE_USAGE_RATIO, 
instance, clusterId, labels,
+                    
parseUsageRatio(runtime.getTable().get("sendThreadPoolQueueSize"),
+                            
runtime.getTable().get("sendThreadPoolQueueCapacity")), collectedAt));
         } catch (Exception error) {
             log.warn("Failed to collect runtime metrics for broker {} on 
instance {}: {}", brokerName,
                     instance.getName(), error.getMessage());
@@ -163,6 +161,12 @@ public class ApacheRocketMqClusterMetricsCollector 
implements ClusterMetricsColl
         }
     }
 
+    private static MetricSample metricOrUnavailable(String key, InstanceVO 
instance, String clusterId,
+            Map<String, String> labels, java.util.Optional<Double> value, 
Instant collectedAt) {
+        return value.map(metric -> available(key, instance, clusterId, labels, 
metric, collectedAt))
+                .orElseGet(() -> unavailable(key, instance, clusterId, labels, 
collectedAt));
+    }
+
     private static MetricSample available(String key, InstanceVO instance, 
String clusterId,
             Map<String, String> labels, double value, Instant collectedAt) {
         return new MetricSample(key, AlertDomain.CLUSTER, instance.getName(), 
clusterId, labels, value,
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/collectors/ApacheRocketMqClusterMetricsCollectorTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/collectors/ApacheRocketMqClusterMetricsCollectorTest.java
index 8aebb17b3..2ffaf2a4c 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/collectors/ApacheRocketMqClusterMetricsCollectorTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/collectors/ApacheRocketMqClusterMetricsCollectorTest.java
@@ -75,6 +75,29 @@ class ApacheRocketMqClusterMetricsCollectorTest {
                 
.singleElement().extracting(MetricSample::value).isEqualTo(0.25D);
     }
 
+    @Test
+    void emitsBrokerScopedUnavailableSamplesForMissingRuntimeFieldsTest() 
throws Exception {
+        RuntimeAdminClientResolver resolver = 
mock(RuntimeAdminClientResolver.class);
+        MQAdminExt admin = mock(MQAdminExt.class);
+        InstanceVO instance = apacheInstance();
+        ClusterInfo topology = new ClusterInfo();
+        topology.setBrokerAddrTable(Map.of("broker-a", new 
BrokerData("cluster-a", "broker-a",
+                new HashMap<>(Map.of(0L, "broker-a:10911")))));
+        KVTable runtime = new KVTable();
+        runtime.setTable(new HashMap<>());
+        when(admin.examineBrokerClusterInfo()).thenReturn(topology);
+        
when(admin.fetchBrokerRuntimeStats("broker-a:10911")).thenReturn(runtime);
+        when(resolver.execute(eq(instance), 
any(MqAdminExtFactory.AdminAction.class)))
+                .thenAnswer(invocation -> 
invocation.<MqAdminExtFactory.AdminAction<Object>>getArgument(1)
+                        .apply(admin));
+
+        List<MetricSample> samples = new 
ApacheRocketMqClusterMetricsCollector(resolver).collect(instance);
+
+        assertUnavailableBrokerMetric(samples, "broker.disk.usage_ratio");
+        assertUnavailableBrokerMetric(samples, "broker.jvm.heap.usage_ratio");
+        assertUnavailableBrokerMetric(samples, 
"broker.send_queue.usage_ratio");
+    }
+
     @Test
     void recordsUnavailableNameserverWhenTopologyCollectionFailsTest() {
         RuntimeAdminClientResolver resolver = 
mock(RuntimeAdminClientResolver.class);
@@ -100,6 +123,16 @@ class ApacheRocketMqClusterMetricsCollectorTest {
                 .isEmpty();
     }
 
+    private static void assertUnavailableBrokerMetric(List<MetricSample> 
samples, String metricKey) {
+        assertThat(samples).filteredOn(sample -> 
sample.metricKey().equals(metricKey))
+                .singleElement().satisfies(sample -> {
+                    
assertThat(sample.availability()).isEqualTo(MetricAvailability.UNAVAILABLE);
+                    assertThat(sample.value()).isNull();
+                    assertThat(sample.labels()).containsEntry("brokerName", 
"broker-a")
+                            .containsEntry("brokerAddr", "broker-a:10911");
+                });
+    }
+
     private static InstanceVO apacheInstance() {
         return 
InstanceVO.builder().name("local").endpoint("localhost:9876").vendor(InstanceVendor.APACHE).build();
     }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NativeAlertProcessorTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NativeAlertProcessorTest.java
index 6124bf166..869f2bbd6 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NativeAlertProcessorTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/NativeAlertProcessorTest.java
@@ -517,6 +517,39 @@ class NativeAlertProcessorTest {
         verify(alerts, never()).saveAlert(any(SystemAlertVO.class));
     }
 
+    @Test
+    void brokerScopedUnavailableSampleKeepsActiveFingerprintTest() {
+        AlertService service = mock(AlertService.class);
+        AlertRuleVO rule = 
AlertRuleVO.builder().id(1L).domain(AlertDomain.CLUSTER).name("Broker disk")
+                
.metric("broker.disk.usage_ratio").operator(">").threshold(0.8).enabled(true)
+                
.instanceId("local").brokerName("broker-a").consecutiveSamples(1).build();
+        when(service.listRules(AlertDomain.CLUSTER)).thenReturn(List.of(rule));
+        Map<String, String> labels = Map.of("brokerName", "broker-a", 
"brokerAddr", "broker-a:10911");
+        MetricSample unavailable = new MetricSample("broker.disk.usage_ratio", 
AlertDomain.CLUSTER, "local",
+                "cluster-a", labels, null, MetricAvailability.UNAVAILABLE, 
Instant.now());
+        AlertStateKey key = new AlertStateKey(rule.getId(), 
AlertFingerprint.of(rule.getId(), "local", labels));
+        AlertRuleState firing = new AlertRuleState(AlertStateStatus.FIRING, 1, 
0.9D,
+                unavailable.collectedAt().minusSeconds(60), 
unavailable.collectedAt().minusSeconds(60),
+                unavailable.collectedAt().minusSeconds(60), null);
+        ActiveAlertState active = new ActiveAlertState(key, firing, "local", 
labels);
+        AlertStateRepository states = mock(AlertStateRepository.class);
+        when(states.find(key)).thenReturn(Optional.of(firing));
+        when(states.save(eq(key), any(AlertRuleState.class))).thenReturn(true);
+        when(states.findActive(any(MetricCollectionScope.class), 
eq(List.of(rule)))).thenReturn(List.of(active));
+        AlertRepository alerts = mock(AlertRepository.class);
+        NotificationOutboxService outbox = 
mock(NotificationOutboxService.class);
+
+        NativeAlertProcessor processor = new NativeAlertProcessor(service,
+                new NativeAlertEvaluationService(new AlertRuleEvaluator(), new 
AlertStateMachine(), states,
+                        mock(MetricSnapshotRepository.class), alerts, outbox, 
suppression()),
+                new AlertStateMachine(), states, alerts, outbox, 
suppression(), mockTxManager());
+        processor.processSuccessfulCollection(new 
MetricCollectionScope(AlertDomain.CLUSTER, "local",
+                java.util.Set.of("broker.disk.usage_ratio")), 
List.of(unavailable));
+
+        verify(alerts, never()).saveAlert(any(SystemAlertVO.class));
+        verify(outbox, never()).enqueue(any(), any(), any());
+    }
+
     @Test
     void resolvesMissingMetricEvenWhenAnotherMetricSharesTheSameLabelsTest() {
         AlertService service = mock(AlertService.class);

Reply via email to