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 a48d4cbf7 fix(dashboard): classify traffic with unrounded metrics 
(#7241)
a48d4cbf7 is described below

commit a48d4cbf7dd54dd1c382c9632804296867da2522
Author: Loyal-Young <[email protected]>
AuthorDate: Mon Oct 26 21:22:45 2026 +0800

    fix(dashboard): classify traffic with unrounded metrics (#7241)
    
    * fix(dashboard): classify traffic with unrounded metrics
---
 web/src/utils/dashboardTrafficInsights.test.ts | 68 +++++++++++++++++++++++
 web/src/utils/dashboardTrafficInsights.ts      | 74 ++++++++++++++++----------
 2 files changed, 115 insertions(+), 27 deletions(-)

diff --git a/web/src/utils/dashboardTrafficInsights.test.ts 
b/web/src/utils/dashboardTrafficInsights.test.ts
index 008d3a51c..9f0c35d38 100644
--- a/web/src/utils/dashboardTrafficInsights.test.ts
+++ b/web/src/utils/dashboardTrafficInsights.test.ts
@@ -266,4 +266,72 @@ describe('buildDashboardTrafficInsights', () => {
       }),
     );
   });
+
+  it('does not raise concentration severity from a rounded display share', () 
=> {
+    const belowWarning = buildDashboardTrafficInsights(
+      dashboard([
+        baseCluster({ id: 'large', tpsIn: 1499, tpsOut: 0 }),
+        baseCluster({ id: 'small', tpsIn: 1001, tpsOut: 0 }),
+      ]),
+    );
+    expect(belowWarning.topClusterSharePercent).toBe(60);
+    expect(belowWarning.issues.some((item) => item.code === 
'TRAFFIC_CONCENTRATION')).toBe(false);
+
+    const belowCritical = buildDashboardTrafficInsights(
+      dashboard([
+        baseCluster({ id: 'large', tpsIn: 1874, tpsOut: 0 }),
+        baseCluster({ id: 'small', tpsIn: 626, tpsOut: 0 }),
+      ]),
+    );
+    expect(belowCritical.topClusterSharePercent).toBe(75);
+    expect(belowCritical.issues.find((item) => item.code === 
'TRAFFIC_CONCENTRATION')?.level).toBe(
+      'warning',
+    );
+  });
+
+  it('classifies unhealthy traffic using its unrounded share', () => {
+    const result = buildDashboardTrafficInsights(
+      dashboard([
+        baseCluster({ id: 'healthy', tpsIn: 1876, tpsOut: 0 }),
+        baseCluster({ id: 'degraded', status: 'warning', tpsIn: 624, tpsOut: 0 
}),
+      ]),
+    );
+    expect(result.unhealthyTrafficPercent).toBe(25);
+    const unhealthyIssues = result.issues.filter((item) => item.code === 
'UNHEALTHY_TRAFFIC');
+    expect(unhealthyIssues).toHaveLength(2);
+    expect(unhealthyIssues.every((item) => item.level === 
'warning')).toBe(true);
+    expect(unhealthyIssues.some((item) => item.clusterId === 
'degraded')).toBe(true);
+  });
+
+  it('compares unrounded trend changes with the warning and notice 
thresholds', () => {
+    const drop = buildDashboardTrafficInsights(
+      dashboard([baseCluster({ throughput: [1000, 1000, 1000, 700.4, 700.4, 
700.4] })]),
+    );
+    expect(drop.trendDeltaPercent).toBe(-30);
+    expect(drop.issues.some((item) => item.code === 
'RECENT_TRAFFIC_DROP')).toBe(false);
+
+    const spike = buildDashboardTrafficInsights(
+      dashboard([baseCluster({ throughput: [1000, 1000, 1000, 1499.6, 1499.6, 
1499.6] })]),
+    );
+    expect(spike.trendDeltaPercent).toBe(50);
+    expect(spike.issues.some((item) => item.code === 
'RECENT_TRAFFIC_SPIKE')).toBe(false);
+
+    const stable = buildDashboardTrafficInsights(
+      dashboard([baseCluster({ throughput: [1000, 1000, 1000, 1099.6, 1099.6, 
1099.6] })]),
+    );
+    expect(stable.trendDeltaPercent).toBe(10);
+    expect(stable.trendDirection).toBe('stable');
+  });
+
+  it('does not report broker skew caused only by rounded per-broker TPS', () 
=> {
+    const result = buildDashboardTrafficInsights(
+      dashboard([
+        baseCluster({ id: 'hot', brokers: 1, tpsIn: 249.99, tpsOut: 0 }),
+        baseCluster({ id: 'wide', brokers: 3, tpsIn: 150.01, tpsOut: 0 }),
+      ]),
+    );
+    expect(result.rows[0].perBrokerTps).toBe(250);
+    expect(result.averagePerBrokerTps).toBe(100);
+    expect(result.issues.some((item) => item.code === 
'BROKER_LOAD_SKEW')).toBe(false);
+  });
 });
diff --git a/web/src/utils/dashboardTrafficInsights.ts 
b/web/src/utils/dashboardTrafficInsights.ts
index a73ef436d..a91e67bce 100644
--- a/web/src/utils/dashboardTrafficInsights.ts
+++ b/web/src/utils/dashboardTrafficInsights.ts
@@ -115,11 +115,13 @@ const round = (value: number, precision = 1): number => {
   return Math.round(value * factor) / factor;
 };
 
-const percent = (value: number, total: number): number => {
+const unroundedPercent = (value: number, total: number): number => {
   if (total <= 0) return 0;
-  return round((value / total) * 100);
+  return (value / total) * 100;
 };
 
+const percent = (value: number, total: number): number => 
round(unroundedPercent(value, total));
+
 const average = (values: number[]): number => {
   if (values.length === 0) return 0;
   return values.reduce((sum, value) => sum + value, 0) / values.length;
@@ -137,29 +139,31 @@ const compareTrend = (
 ): {
   direction: TrafficTrendDirection;
   deltaPercent: number | null;
+  unroundedDeltaPercent: number | null;
 } => {
   if (series.length < 4) {
-    return { direction: 'unknown', deltaPercent: null };
+    return { direction: 'unknown', deltaPercent: null, unroundedDeltaPercent: 
null };
   }
 
   const midpoint = Math.floor(series.length / 2);
   const earlyAverage = average(series.slice(0, midpoint));
   const lateAverage = average(series.slice(midpoint));
   if (earlyAverage <= 0 && lateAverage <= 0) {
-    return { direction: 'stable', deltaPercent: 0 };
+    return { direction: 'stable', deltaPercent: 0, unroundedDeltaPercent: 0 };
   }
   if (earlyAverage <= 0) {
-    return { direction: 'rising', deltaPercent: 100 };
+    return { direction: 'rising', deltaPercent: 100, unroundedDeltaPercent: 
100 };
   }
 
-  const deltaPercent = round(((lateAverage - earlyAverage) / earlyAverage) * 
100);
-  if (deltaPercent >= TREND_THRESHOLD_PERCENT) {
-    return { direction: 'rising', deltaPercent };
+  const unroundedDeltaPercent = ((lateAverage - earlyAverage) / earlyAverage) 
* 100;
+  const deltaPercent = round(unroundedDeltaPercent);
+  if (unroundedDeltaPercent >= TREND_THRESHOLD_PERCENT) {
+    return { direction: 'rising', deltaPercent, unroundedDeltaPercent };
   }
-  if (deltaPercent <= -TREND_THRESHOLD_PERCENT) {
-    return { direction: 'falling', deltaPercent };
+  if (unroundedDeltaPercent <= -TREND_THRESHOLD_PERCENT) {
+    return { direction: 'falling', deltaPercent, unroundedDeltaPercent };
   }
-  return { direction: 'stable', deltaPercent };
+  return { direction: 'stable', deltaPercent, unroundedDeltaPercent };
 };
 
 const aggregateThroughput = (clusters: ClusterOverview[]): number[] => {
@@ -200,7 +204,7 @@ const buildClusterIssues = (
     issues.push({
       code: 'UNHEALTHY_TRAFFIC',
       level:
-        percent(clusterTps, totalTps) >= UNHEALTHY_TRAFFIC_CRITICAL_PERCENT
+        unroundedPercent(clusterTps, totalTps) >= 
UNHEALTHY_TRAFFIC_CRITICAL_PERCENT
           ? 'critical'
           : 'warning',
       clusterId: cluster.id,
@@ -259,13 +263,16 @@ const buildRows = (
 
 const findBrokerSkewIssue = (
   rows: DashboardTrafficClusterInsight[],
-  averagePerBrokerTps: number,
+  totalTps: number,
+  totalBrokers: number,
 ): DashboardTrafficIssue | null => {
+  const averagePerBrokerTps = totalBrokers > 0 ? totalTps / totalBrokers : 0;
   if (averagePerBrokerTps <= 0) return null;
   const skewed = rows.find(
     (row) =>
+      row.brokers > 0 &&
       row.totalTps >= MIN_TRAFFIC_FOR_SKEW &&
-      row.perBrokerTps >= averagePerBrokerTps * BROKER_SKEW_MULTIPLIER,
+      row.totalTps / row.brokers >= averagePerBrokerTps * 
BROKER_SKEW_MULTIPLIER,
   );
   if (!skewed) return null;
   return {
@@ -281,25 +288,24 @@ const findBrokerSkewIssue = (
 const buildGlobalIssues = (
   rows: DashboardTrafficClusterInsight[],
   topCluster: DashboardTrafficClusterInsight | null,
+  totalTps: number,
   unhealthyTrafficPercent: number,
+  unroundedUnhealthyTrafficPercent: number,
   trend: ReturnType<typeof compareTrend>,
-  averagePerBrokerTps: number,
+  totalBrokers: number,
 ): DashboardTrafficIssue[] => {
   const issues: DashboardTrafficIssue[] = [];
   const activeRows = rows.filter((row) => row.totalTps > 0);
+  const topClusterShare = topCluster ? unroundedPercent(topCluster.totalTps, 
totalTps) : 0;
 
   if (activeRows.length === 0) {
     issues.push({ code: 'NO_ACTIVE_TRAFFIC', level: 'notice' });
   }
 
-  if (
-    topCluster &&
-    activeRows.length > 1 &&
-    topCluster.sharePercent >= CONCENTRATION_WARNING_PERCENT
-  ) {
+  if (topCluster && activeRows.length > 1 && topClusterShare >= 
CONCENTRATION_WARNING_PERCENT) {
     issues.push({
       code: 'TRAFFIC_CONCENTRATION',
-      level: topCluster.sharePercent >= CONCENTRATION_CRITICAL_PERCENT ? 
'critical' : 'warning',
+      level: topClusterShare >= CONCENTRATION_CRITICAL_PERCENT ? 'critical' : 
'warning',
       clusterId: topCluster.id,
       clusterName: topCluster.name,
       value: topCluster.sharePercent,
@@ -307,22 +313,26 @@ const buildGlobalIssues = (
     });
   }
 
-  if (unhealthyTrafficPercent >= UNHEALTHY_TRAFFIC_WARNING_PERCENT) {
+  if (unroundedUnhealthyTrafficPercent >= UNHEALTHY_TRAFFIC_WARNING_PERCENT) {
     issues.push({
       code: 'UNHEALTHY_TRAFFIC',
-      level: unhealthyTrafficPercent >= UNHEALTHY_TRAFFIC_CRITICAL_PERCENT ? 
'critical' : 'warning',
+      level:
+        unroundedUnhealthyTrafficPercent >= UNHEALTHY_TRAFFIC_CRITICAL_PERCENT
+          ? 'critical'
+          : 'warning',
       value: unhealthyTrafficPercent,
       threshold: UNHEALTHY_TRAFFIC_WARNING_PERCENT,
     });
   }
 
-  const skewIssue = findBrokerSkewIssue(rows, averagePerBrokerTps);
+  const skewIssue = findBrokerSkewIssue(rows, totalTps, totalBrokers);
   if (skewIssue) issues.push(skewIssue);
 
   if (
     trend.direction === 'falling' &&
     trend.deltaPercent != null &&
-    trend.deltaPercent <= TRAFFIC_DROP_WARNING_PERCENT
+    trend.unroundedDeltaPercent != null &&
+    trend.unroundedDeltaPercent <= TRAFFIC_DROP_WARNING_PERCENT
   ) {
     issues.push({
       code: 'RECENT_TRAFFIC_DROP',
@@ -335,7 +345,8 @@ const buildGlobalIssues = (
   if (
     trend.direction === 'rising' &&
     trend.deltaPercent != null &&
-    trend.deltaPercent >= TRAFFIC_SPIKE_NOTICE_PERCENT
+    trend.unroundedDeltaPercent != null &&
+    trend.unroundedDeltaPercent >= TRAFFIC_SPIKE_NOTICE_PERCENT
   ) {
     issues.push({
       code: 'RECENT_TRAFFIC_SPIKE',
@@ -368,8 +379,17 @@ export function buildDashboardTrafficInsights(
   const aggregateSeries = aggregateThroughput(clusters);
   const trend = compareTrend(aggregateSeries);
   const unhealthyTrafficPercent = percent(unhealthyTrafficTps, totalTps);
+  const unroundedUnhealthyTrafficPercent = 
unroundedPercent(unhealthyTrafficTps, totalTps);
   const issues = [
-    ...buildGlobalIssues(rows, topCluster, unhealthyTrafficPercent, trend, 
averagePerBrokerTps),
+    ...buildGlobalIssues(
+      rows,
+      topCluster,
+      totalTps,
+      unhealthyTrafficPercent,
+      unroundedUnhealthyTrafficPercent,
+      trend,
+      totalBrokers,
+    ),
     ...rows.flatMap((row) => row.issues),
   ];
 

Reply via email to