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 c16d6c361 fix(cluster): read the legacy 4.x outbound TPS runtime stats 
key (#3992)
c16d6c361 is described below

commit c16d6c3618fe7e42086a8ae715c1d37268f08608
Author: Zhao Jianing <[email protected]>
AuthorDate: Mon Sep 7 19:09:18 2026 +0800

    fix(cluster): read the legacy 4.x outbound TPS runtime stats key (#3992)
    
    RocketMQ brokers publish the outbound TPS under different runtime-stats
    keys depending on the generation: 5.x emits "getTransferredTps" (double
    r, see release-5.3.1 StoreStatsService) while 4.x keeps the historical
    "getTransferedTps" spelling (see release-4.9.4 StoreStatsService).
    
    The dashboard providers only read the 5.x key, so clusters backed by
    4.x brokers report tpsOut = 0 on the Dashboard overview and the Cluster
    page even when the broker is actively transferring messages. Probe both
    spellings when resolving the outbound TPS entry.
    
    Affected read sites:
    - RocketMQDashboardProvider: stats.tpsOut and per-cluster tpsOut
    - RocketMQClusterProvider: broker tpsOut
    
    Regression covered by new tests that stub fetchBrokerRuntimeStats with
    only the legacy key.
    
    Signed-off-by: zjncs <[email protected]>
---
 .../studio/common/util/BrokerRuntimeStats.java     | 54 +++++++++++++++++++++
 .../provider/apache/RocketMQClusterProvider.java   |  3 +-
 .../provider/apache/RocketMQDashboardProvider.java |  5 +-
 .../studio/common/util/BrokerRuntimeStatsTest.java | 56 ++++++++++++++++++++++
 .../apache/RocketMQClusterProviderTest.java        | 17 +++++++
 .../apache/RocketMQDashboardProviderTest.java      | 19 ++++++++
 6 files changed, 151 insertions(+), 3 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/common/util/BrokerRuntimeStats.java
 
b/server/src/main/java/org/apache/rocketmq/studio/common/util/BrokerRuntimeStats.java
new file mode 100644
index 000000000..925440cbe
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/common/util/BrokerRuntimeStats.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.common.util;
+
+import org.springframework.util.StringUtils;
+
+import java.util.Map;
+
+/**
+ * Helpers for reading broker runtime-stats ({@code KVTable}) entries whose 
keys differ across
+ * broker generations. Consolidates the per-provider copies so the legacy-key 
fallback stays
+ * consistent everywhere runtime stats are parsed.
+ */
+public final class BrokerRuntimeStats {
+
+    /** 5.x brokers publish outbound TPS under this key (double r). */
+    private static final String OUTBOUND_TPS_5X = "getTransferredTps";
+    /** 4.x brokers use the historical single-r spelling. */
+    private static final String OUTBOUND_TPS_4X = "getTransferedTps";
+
+    private BrokerRuntimeStats() {
+    }
+
+    /**
+     * Resolves the outbound TPS entry: 5.x brokers emit {@code 
getTransferredTps} while 4.x brokers
+     * emit the historical {@code getTransferedTps} spelling. A 
present-but-blank 5.x value falls
+     * back to the 4.x key rather than being treated as the answer. Returns 
null when neither key
+     * carries a usable value.
+     */
+    public static String outboundTps(Map<String, String> runtimeStats) {
+        if (runtimeStats == null) {
+            return null;
+        }
+        String transferred = runtimeStats.get(OUTBOUND_TPS_5X);
+        if (StringUtils.hasText(transferred)) {
+            return transferred;
+        }
+        return runtimeStats.get(OUTBOUND_TPS_4X);
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
index e6d12fbdf..1e6332a02 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProvider.java
@@ -32,6 +32,7 @@ import 
org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.common.domain.enums.BrokerStatus;
 import org.apache.rocketmq.studio.common.domain.enums.ClusterStatus;
 import org.apache.rocketmq.studio.common.domain.enums.ClusterType;
+import org.apache.rocketmq.studio.common.util.BrokerRuntimeStats;
 import org.apache.rocketmq.tools.admin.MQAdminExt;
 import org.springframework.context.annotation.Primary;
 import org.springframework.stereotype.Service;
@@ -266,7 +267,7 @@ public class RocketMQClusterProvider implements 
ClusterProvider {
                 builder.tpsIn(parseTpsValue(putTps));
             }
 
-            String getTransferredTps = table.get("getTransferredTps");
+            String getTransferredTps = BrokerRuntimeStats.outboundTps(table);
             if (getTransferredTps != null && !getTransferredTps.isEmpty()) {
                 builder.tpsOut(parseTpsValue(getTransferredTps));
             }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
index 7299fb0d0..00979692c 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
@@ -44,6 +44,7 @@ import 
org.apache.rocketmq.studio.ops.dashboard.ClusterOverviewVO;
 import org.apache.rocketmq.studio.ops.dashboard.DashboardDataVO;
 import org.apache.rocketmq.studio.ops.dashboard.DashboardProvider;
 import org.apache.rocketmq.studio.ops.dashboard.DashboardStatsVO;
+import org.apache.rocketmq.studio.common.util.BrokerRuntimeStats;
 import org.apache.rocketmq.studio.common.util.SystemGroupFilter;
 import org.apache.rocketmq.studio.common.util.SystemTopicFilter;
 import org.apache.rocketmq.tools.admin.MQAdminExt;
@@ -342,7 +343,7 @@ public class RocketMQDashboardProvider implements 
DashboardProvider {
                     if (runtimeInfo != null && runtimeInfo.getTable() != null) 
{
                         Map<String, String> table = runtimeInfo.getTable();
                         tpsIn += parseTps(table.get("putTps"));
-                        tpsOut += parseTps(table.get("getTransferredTps"));
+                        tpsOut += 
parseTps(BrokerRuntimeStats.outboundTps(table));
 
                         messagesToday += parseMessagesToday(table);
                     }
@@ -374,7 +375,7 @@ public class RocketMQDashboardProvider implements 
DashboardProvider {
                             KVTable runtimeInfo = 
runtimeStatsByBroker.get(masterAddr);
                             if (runtimeInfo != null && runtimeInfo.getTable() 
!= null) {
                                 clusterTpsIn += 
parseTps(runtimeInfo.getTable().get("putTps"));
-                                clusterTpsOut += 
parseTps(runtimeInfo.getTable().get("getTransferredTps"));
+                                clusterTpsOut += 
parseTps(BrokerRuntimeStats.outboundTps(runtimeInfo.getTable()));
                                 String value = 
runtimeInfo.getTable().get("brokerVersionDesc");
                                 if (value != null && 
"unknown".equals(version)) {
                                     String brokerVersion = value.trim();
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/common/util/BrokerRuntimeStatsTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/common/util/BrokerRuntimeStatsTest.java
new file mode 100644
index 000000000..0ea1c4c78
--- /dev/null
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/common/util/BrokerRuntimeStatsTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.common.util;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class BrokerRuntimeStatsTest {
+
+    @Test
+    void prefersFiveXKeyWhenPresentTest() {
+        Map<String, String> stats = new HashMap<>();
+        stats.put("getTransferredTps", "5.0 6.0 7.0");
+        stats.put("getTransferedTps", "4.0 5.0 6.0");
+        assertThat(BrokerRuntimeStats.outboundTps(stats)).isEqualTo("5.0 6.0 
7.0");
+    }
+
+    @Test
+    void fallsBackToFourXKeyWhenFiveXAbsentTest() {
+        Map<String, String> stats = new HashMap<>();
+        stats.put("getTransferedTps", "4.0 5.0 6.0");
+        assertThat(BrokerRuntimeStats.outboundTps(stats)).isEqualTo("4.0 5.0 
6.0");
+    }
+
+    @Test
+    void fallsBackToFourXKeyWhenFiveXBlankTest() {
+        Map<String, String> stats = new HashMap<>();
+        stats.put("getTransferredTps", "   ");
+        stats.put("getTransferedTps", "4.0 5.0 6.0");
+        assertThat(BrokerRuntimeStats.outboundTps(stats)).isEqualTo("4.0 5.0 
6.0");
+    }
+
+    @Test
+    void returnsNullWhenNeitherKeyUsableTest() {
+        assertThat(BrokerRuntimeStats.outboundTps(new HashMap<>())).isNull();
+        assertThat(BrokerRuntimeStats.outboundTps(null)).isNull();
+    }
+}
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProviderTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProviderTest.java
index bdfe2a6f2..679bc5a0d 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProviderTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQClusterProviderTest.java
@@ -73,6 +73,23 @@ class RocketMQClusterProviderTest {
         
assertThat(clusters.get(0).getStatus()).isEqualTo(ClusterStatus.healthy);
     }
 
+    @Test
+    void discoverClustersShouldReadLegacy4xBrokerOutboundTpsKey() throws 
Exception {
+        DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
+        RocketMQClusterProvider provider = newProvider(adminExt);
+
+        when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo());
+        KVTable runtime = runtimeStats("  12.7   10.0  9.0", null);
+        runtime.getTable().put("getTransferedTps", "34.2 30.0 29.0");
+        
when(adminExt.fetchBrokerRuntimeStats("10.0.0.11:10911")).thenReturn(runtime);
+
+        List<ClusterVO> clusters = provider.discoverClusters();
+
+        assertThat(clusters).hasSize(1);
+        
assertThat(clusters.get(0).getBrokers().get(0).getTpsIn()).isEqualTo(10);
+        
assertThat(clusters.get(0).getBrokers().get(0).getTpsOut()).isEqualTo(30);
+    }
+
     @Test
     void discoverClustersShouldConvertDiskRatioToPercentage() throws Exception 
{
         DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProviderTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProviderTest.java
index 04c625f01..71dc46f8e 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProviderTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProviderTest.java
@@ -484,6 +484,25 @@ class RocketMQDashboardProviderTest {
         });
     }
 
+    @Test
+    void dashboardShouldReadLegacy4xBrokerOutboundTpsKey() throws Exception {
+        DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
+        when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo());
+        when(adminExt.fetchAllTopicList()).thenReturn(topicList());
+        KVTable runtime = runtimeStats("2.0", "5.0");
+        runtime.getTable().remove("getTransferredTps");
+        runtime.getTable().put("getTransferedTps", "4.0 5.0 6.0");
+        
when(adminExt.fetchBrokerRuntimeStats("10.0.0.11:10911")).thenReturn(runtime);
+
+        DashboardDataVO dashboard = newProvider(adminExt).getDashboardData();
+
+        assertThat(dashboard.getStats().getTpsIn()).isEqualTo(2);
+        assertThat(dashboard.getStats().getTpsOut()).isEqualTo(5);
+        assertThat(dashboard.getClusters()).singleElement()
+                .extracting(ClusterOverviewVO::getTpsOut)
+                .isEqualTo(5L);
+    }
+
     @Test
     void dashboardShouldIgnoreNonFiniteNegativeAndOverflowingTps() throws 
Exception {
         DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);

Reply via email to