From 9b80f91455bc48c9f55f8564bfc46c1f26611e28 Mon Sep 17 00:00:00 2001 From: warku123 Date: Thu, 25 Dec 2025 17:05:45 +0800 Subject: [PATCH 1/9] Add SR set change and empty block detection --- .../tron/common/prometheus/MetricKeys.java | 2 + .../tron/common/prometheus/MetricLabels.java | 2 + .../common/prometheus/MetricsCounter.java | 2 + .../blockchain/BlockChainMetricManager.java | 43 +++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/common/src/main/java/org/tron/common/prometheus/MetricKeys.java b/common/src/main/java/org/tron/common/prometheus/MetricKeys.java index 87ab6fae0a3..503d7c0a6f7 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricKeys.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricKeys.java @@ -14,6 +14,8 @@ public static class Counter { public static final String TXS = "tron:txs"; public static final String MINER = "tron:miner"; public static final String BLOCK_FORK = "tron:block_fork"; + public static final String BLOCK_EMPTY = "tron:block_empty"; + public static final String SR_SET_CHANGE = "tron:sr_set_change"; public static final String P2P_ERROR = "tron:p2p_error"; public static final String P2P_DISCONNECT = "tron:p2p_disconnect"; public static final String INTERNAL_SERVICE_FAIL = "tron:internal_service_fail"; diff --git a/common/src/main/java/org/tron/common/prometheus/MetricLabels.java b/common/src/main/java/org/tron/common/prometheus/MetricLabels.java index 2aa3c1e3378..875e03d8110 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricLabels.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricLabels.java @@ -31,6 +31,8 @@ public static class Counter { public static final String TXS_FAIL_SIG = "sig"; public static final String TXS_FAIL_TAPOS = "tapos"; public static final String TXS_FAIL_DUP = "dup"; + public static final String SR_ADD = "add"; + public static final String SR_REMOVE = "remove"; private Counter() { throw new IllegalStateException("Counter"); diff --git a/common/src/main/java/org/tron/common/prometheus/MetricsCounter.java b/common/src/main/java/org/tron/common/prometheus/MetricsCounter.java index 6acdf23b3bc..db25ead7a79 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricsCounter.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricsCounter.java @@ -14,6 +14,8 @@ class MetricsCounter { init(MetricKeys.Counter.TXS, "tron txs info .", "type", "detail"); init(MetricKeys.Counter.MINER, "tron miner info .", "miner", "type"); init(MetricKeys.Counter.BLOCK_FORK, "tron block fork info .", "type"); + init(MetricKeys.Counter.BLOCK_EMPTY, "tron empty block count .", "miner"); + init(MetricKeys.Counter.SR_SET_CHANGE, "tron sr set change .", "action", "witness"); init(MetricKeys.Counter.P2P_ERROR, "tron p2p error info .", "type"); init(MetricKeys.Counter.P2P_DISCONNECT, "tron p2p disconnect .", "type"); init(MetricKeys.Counter.INTERNAL_SERVICE_FAIL, "internal Service fail.", diff --git a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java index 384f1d8add1..f1f87ab8e23 100644 --- a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java +++ b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java @@ -3,10 +3,13 @@ import com.codahale.metrics.Counter; import com.google.protobuf.ByteString; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.SortedMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import lombok.Getter; import lombok.Setter; import org.bouncycastle.util.encoders.Hex; @@ -42,6 +45,7 @@ public class BlockChainMetricManager { private long failProcessBlockNum = 0; @Setter private String failProcessBlockReason = ""; + private final Set lastActiveWitnesses = ConcurrentHashMap.newKeySet(); public BlockChainInfo getBlockChainInfo() { BlockChainInfo blockChainInfo = new BlockChainInfo(); @@ -168,6 +172,45 @@ public void applyBlock(BlockCapsule block) { MetricsUtil.meterMark(MetricsKey.BLOCKCHAIN_TPS, block.getTransactions().size()); Metrics.counterInc(MetricKeys.Counter.TXS, block.getTransactions().size(), MetricLabels.Counter.TXS_SUCCESS, MetricLabels.Counter.TXS_SUCCESS); + } else { + // Empty block + Metrics.counterInc(MetricKeys.Counter.BLOCK_EMPTY, 1, + StringUtil.encode58Check(address)); + } + + // SR set change detection + Set currentWitnesses = chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() + .stream() + .map(w -> Hex.toHexString(w.toByteArray())) + .collect(Collectors.toSet()); + recordSrSetChange(currentWitnesses); + } + + private void recordSrSetChange(Set currentWitnesses) { + if (currentWitnesses.isEmpty()) { + return; + } + if (lastActiveWitnesses.isEmpty()) { + lastActiveWitnesses.addAll(currentWitnesses); + return; + } + Set added = new HashSet<>(currentWitnesses); + added.removeAll(lastActiveWitnesses); + + Set removed = new HashSet<>(lastActiveWitnesses); + removed.removeAll(currentWitnesses); + + for (String address : added) { + Metrics.counterInc(MetricKeys.Counter.SR_SET_CHANGE, 1, + MetricLabels.Counter.SR_ADD, StringUtil.encode58Check(Hex.decode(address))); + } + for (String address : removed) { + Metrics.counterInc(MetricKeys.Counter.SR_SET_CHANGE, 1, + MetricLabels.Counter.SR_REMOVE, StringUtil.encode58Check(Hex.decode(address))); + } + if (!added.isEmpty() || !removed.isEmpty()) { + lastActiveWitnesses.clear(); + lastActiveWitnesses.addAll(currentWitnesses); } } From 1d2de1a1598117d6618361bec3b0a9d7324672e9 Mon Sep 17 00:00:00 2001 From: warku123 Date: Fri, 9 Jan 2026 17:35:16 +0800 Subject: [PATCH 2/9] Add nextMaintenanceTime judge --- .../blockchain/BlockChainMetricManager.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java index f1f87ab8e23..f569767ef40 100644 --- a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java +++ b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java @@ -46,6 +46,7 @@ public class BlockChainMetricManager { @Setter private String failProcessBlockReason = ""; private final Set lastActiveWitnesses = ConcurrentHashMap.newKeySet(); + private long lastNextMaintenanceTime = 0; public BlockChainInfo getBlockChainInfo() { BlockChainInfo blockChainInfo = new BlockChainInfo(); @@ -179,11 +180,19 @@ public void applyBlock(BlockCapsule block) { } // SR set change detection - Set currentWitnesses = chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() - .stream() - .map(w -> Hex.toHexString(w.toByteArray())) - .collect(Collectors.toSet()); - recordSrSetChange(currentWitnesses); + long nextMaintenanceTime = dbManager.getDynamicPropertiesStore().getNextMaintenanceTime(); + if (lastNextMaintenanceTime == 0) { + lastNextMaintenanceTime = nextMaintenanceTime; + lastActiveWitnesses.addAll(chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() + .stream().map(w -> Hex.toHexString(w.toByteArray())).collect(Collectors.toSet())); + } else if (nextMaintenanceTime != lastNextMaintenanceTime) { + Set currentWitnesses = chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() + .stream() + .map(w -> Hex.toHexString(w.toByteArray())) + .collect(Collectors.toSet()); + recordSrSetChange(currentWitnesses); + lastNextMaintenanceTime = nextMaintenanceTime; + } } private void recordSrSetChange(Set currentWitnesses) { From 1c0c1b789713ff721bd975c11271fc69abbf14a7 Mon Sep 17 00:00:00 2001 From: warku123 Date: Fri, 16 Jan 2026 11:47:03 +0800 Subject: [PATCH 3/9] Empty Block check unit test done --- .../prometheus/PrometheusApiServiceTest.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java index d4d758b7a98..ca95d03281b 100644 --- a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java +++ b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java @@ -25,6 +25,7 @@ import org.tron.common.utils.ByteArray; import org.tron.common.utils.PublicMethod; import org.tron.common.utils.Sha256Hash; +import org.tron.common.utils.StringUtil; import org.tron.common.utils.Utils; import org.tron.consensus.dpos.DposSlot; import org.tron.core.ChainBaseManager; @@ -65,7 +66,7 @@ protected static void initParameter(CommonParameter parameter) { parameter.setMetricsPrometheusEnable(true); } - protected void check() throws Exception { + protected void check(byte[] address) throws Exception { Double memoryBytes = CollectorRegistry.defaultRegistry.getSampleValue( "system_total_physical_memory_bytes"); Assert.assertNotNull(memoryBytes); @@ -80,6 +81,17 @@ protected void check() throws Exception { new String[] {"sync"}, new String[] {"false"}); Assert.assertNotNull(pushBlock); Assert.assertEquals(pushBlock.intValue(), blocks + 1); + + String minerBase58 = StringUtil.encode58Check(address); + Double emptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( + "tron:block_empty_total", new String[] {"miner"}, new String[] {minerBase58}); + + Assert.assertNotNull(emptyBlock); + // The initial address is in the active witness list along with 2 randomly generated witnesses, + // so it produces blocks every 3 slots. Total empty blocks = 1 (first manual block) + blocks/3 + // (from the loop) + 1 if blocks%3 != 0 (partial round) + Assert.assertEquals(emptyBlock.intValue(), 1 + blocks / 3 + (blocks % 3 != 0 ? 1 : 0)); + Double errorLogs = CollectorRegistry.defaultRegistry.getSampleValue( "tron:error_info_total", new String[] {"net"}, new String[] {MetricLabels.UNDEFINED}); Assert.assertNull(errorLogs); @@ -133,7 +145,7 @@ public void testMetric() throws Exception { for (int i = 0; i < blocks; i++) { generateBlock(witnessAndAccount); } - check(); + check(address); } private Map addTestWitnessAndAccount() { From bc87e2e555533de5b8472ebe3feb53c322ba9e6e Mon Sep 17 00:00:00 2001 From: warku123 Date: Fri, 16 Jan 2026 16:35:20 +0800 Subject: [PATCH 4/9] SR change unit test --- .../blockchain/BlockChainMetricManager.java | 12 +--- .../prometheus/PrometheusApiServiceTest.java | 55 +++++++++++++++++-- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java index f569767ef40..7ef9e8f59e8 100644 --- a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java +++ b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java @@ -46,7 +46,8 @@ public class BlockChainMetricManager { @Setter private String failProcessBlockReason = ""; private final Set lastActiveWitnesses = ConcurrentHashMap.newKeySet(); - private long lastNextMaintenanceTime = 0; + // To control SR set change metric update logic, -1 means not initialized + private long lastNextMaintenanceTime = -1; public BlockChainInfo getBlockChainInfo() { BlockChainInfo blockChainInfo = new BlockChainInfo(); @@ -181,7 +182,7 @@ public void applyBlock(BlockCapsule block) { // SR set change detection long nextMaintenanceTime = dbManager.getDynamicPropertiesStore().getNextMaintenanceTime(); - if (lastNextMaintenanceTime == 0) { + if (lastNextMaintenanceTime == -1) { lastNextMaintenanceTime = nextMaintenanceTime; lastActiveWitnesses.addAll(chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() .stream().map(w -> Hex.toHexString(w.toByteArray())).collect(Collectors.toSet())); @@ -196,13 +197,6 @@ public void applyBlock(BlockCapsule block) { } private void recordSrSetChange(Set currentWitnesses) { - if (currentWitnesses.isEmpty()) { - return; - } - if (lastActiveWitnesses.isEmpty()) { - lastActiveWitnesses.addAll(currentWitnesses); - return; - } Set added = new HashSet<>(currentWitnesses); added.removeAll(lastActiveWitnesses); diff --git a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java index ca95d03281b..38cc171f152 100644 --- a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java +++ b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java @@ -7,6 +7,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -66,7 +67,7 @@ protected static void initParameter(CommonParameter parameter) { parameter.setMetricsPrometheusEnable(true); } - protected void check(byte[] address) throws Exception { + protected void check(byte[] address, Map witnessAndAccount) throws Exception { Double memoryBytes = CollectorRegistry.defaultRegistry.getSampleValue( "system_total_physical_memory_bytes"); Assert.assertNotNull(memoryBytes); @@ -87,10 +88,42 @@ protected void check(byte[] address) throws Exception { "tron:block_empty_total", new String[] {"miner"}, new String[] {minerBase58}); Assert.assertNotNull(emptyBlock); - // The initial address is in the active witness list along with 2 randomly generated witnesses, - // so it produces blocks every 3 slots. Total empty blocks = 1 (first manual block) + blocks/3 - // (from the loop) + 1 if blocks%3 != 0 (partial round) - Assert.assertEquals(emptyBlock.intValue(), 1 + blocks / 3 + (blocks % 3 != 0 ? 1 : 0)); + Assert.assertEquals(emptyBlock.intValue(), 1); + + // Check SR_REMOVE for initial address (removed when addTestWitnessAndAccount() is called) + Double srRemoveCount = CollectorRegistry.defaultRegistry.getSampleValue( + "tron:sr_set_change_total", + new String[] {"action", "witness"}, + new String[] {MetricLabels.Counter.SR_REMOVE, minerBase58} + ); + Assert.assertNotNull(srRemoveCount); + Assert.assertEquals(1, srRemoveCount.intValue()); + + // Check SR_ADD and empty blocks for each new witness in witnessAndAccount (excluding initial address) + ByteString addressByteString = ByteString.copyFrom(address); + double totalNewWitnessEmptyBlocks = 0; + for (ByteString witnessAddress : witnessAndAccount.keySet()) { + if (witnessAddress.equals(addressByteString)) { + continue; // Skip initial address + } + String witnessBase58 = StringUtil.encode58Check(witnessAddress.toByteArray()); + + // Check SR_ADD + Double srAddCount = CollectorRegistry.defaultRegistry.getSampleValue( + "tron:sr_set_change_total", + new String[] {"action", "witness"}, + new String[] {MetricLabels.Counter.SR_ADD, witnessBase58} + ); + Assert.assertNotNull("SR_ADD should be recorded for witness: " + witnessBase58, srAddCount); + Assert.assertEquals("Each new witness should have 1 SR_ADD record", 1, srAddCount.intValue()); + + // Collect empty blocks count + Double witnessEmptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( + "tron:block_empty_total", new String[] {"miner"}, new String[] {witnessBase58}); + Assert.assertNotNull(witnessEmptyBlock); + totalNewWitnessEmptyBlocks += witnessEmptyBlock; + } + Assert.assertEquals(blocks, (int)totalNewWitnessEmptyBlocks); Double errorLogs = CollectorRegistry.defaultRegistry.getSampleValue( "tron:error_info_total", new String[] {"net"}, new String[] {MetricLabels.UNDEFINED}); @@ -142,10 +175,20 @@ public void testMetric() throws Exception { Map witnessAndAccount = addTestWitnessAndAccount(); witnessAndAccount.put(ByteString.copyFrom(address), key); + + // Explicitly update WitnessScheduleStore to remove initial address, triggering SR_REMOVE metric + List newActiveWitnesses = new ArrayList<>(witnessAndAccount.keySet()); + newActiveWitnesses.remove(ByteString.copyFrom(address)); + chainBaseManager.getWitnessScheduleStore().saveActiveWitnesses(newActiveWitnesses); + + // Update nextMaintenanceTime to trigger SR set change detection + long nextMaintenanceTime = chainBaseManager.getDynamicPropertiesStore().getNextMaintenanceTime(); + chainBaseManager.getDynamicPropertiesStore().updateNextMaintenanceTime(nextMaintenanceTime + 3600_000L); + for (int i = 0; i < blocks; i++) { generateBlock(witnessAndAccount); } - check(address); + check(address, witnessAndAccount); } private Map addTestWitnessAndAccount() { From 6bece439b14efecd767fffa137e1801233d5032d Mon Sep 17 00:00:00 2001 From: warku123 Date: Tue, 24 Mar 2026 11:50:52 +0800 Subject: [PATCH 5/9] feat(metrics): add block transaction count histogram for empty block monitoring Replace the dedicated tron:block_empty_total counter with a more comprehensive tron:block_transaction_count histogram that tracks the distribution of transaction counts per block. Changes: - Add overloaded init() method in MetricsHistogram to support custom buckets - Add BLOCK_TRANSACTION_COUNT histogram with buckets [0, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000] - Record transaction count for all blocks (including empty blocks with txCount=0) - Empty blocks can be queried via bucket le=0.0 - Remove unused BLOCK_EMPTY counter This provides richer insights for network analysis while still supporting empty block monitoring via histogram bucket queries. Closes #6590 --- .../tron/common/prometheus/MetricKeys.java | 2 +- .../common/prometheus/MetricsCounter.java | 1 - .../common/prometheus/MetricsHistogram.java | 16 +++++++++ .../blockchain/BlockChainMetricManager.java | 8 ++--- .../prometheus/PrometheusApiServiceTest.java | 36 ++++++++++++------- 5 files changed, 44 insertions(+), 19 deletions(-) diff --git a/common/src/main/java/org/tron/common/prometheus/MetricKeys.java b/common/src/main/java/org/tron/common/prometheus/MetricKeys.java index 503d7c0a6f7..3293a67342a 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricKeys.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricKeys.java @@ -14,7 +14,6 @@ public static class Counter { public static final String TXS = "tron:txs"; public static final String MINER = "tron:miner"; public static final String BLOCK_FORK = "tron:block_fork"; - public static final String BLOCK_EMPTY = "tron:block_empty"; public static final String SR_SET_CHANGE = "tron:sr_set_change"; public static final String P2P_ERROR = "tron:p2p_error"; public static final String P2P_DISCONNECT = "tron:p2p_disconnect"; @@ -64,6 +63,7 @@ public static class Histogram { public static final String MESSAGE_PROCESS_LATENCY = "tron:message_process_latency_seconds"; public static final String BLOCK_FETCH_LATENCY = "tron:block_fetch_latency_seconds"; public static final String BLOCK_RECEIVE_DELAY = "tron:block_receive_delay_seconds"; + public static final String BLOCK_TRANSACTION_COUNT = "tron:block_transaction_count"; private Histogram() { throw new IllegalStateException("Histogram"); diff --git a/common/src/main/java/org/tron/common/prometheus/MetricsCounter.java b/common/src/main/java/org/tron/common/prometheus/MetricsCounter.java index db25ead7a79..7231baaba8f 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricsCounter.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricsCounter.java @@ -14,7 +14,6 @@ class MetricsCounter { init(MetricKeys.Counter.TXS, "tron txs info .", "type", "detail"); init(MetricKeys.Counter.MINER, "tron miner info .", "miner", "type"); init(MetricKeys.Counter.BLOCK_FORK, "tron block fork info .", "type"); - init(MetricKeys.Counter.BLOCK_EMPTY, "tron empty block count .", "miner"); init(MetricKeys.Counter.SR_SET_CHANGE, "tron sr set change .", "action", "witness"); init(MetricKeys.Counter.P2P_ERROR, "tron p2p error info .", "type"); init(MetricKeys.Counter.P2P_DISCONNECT, "tron p2p disconnect .", "type"); diff --git a/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java b/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java index 556db10feb5..9042f17f3f5 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java @@ -48,6 +48,11 @@ public class MetricsHistogram { init(MetricKeys.Histogram.BLOCK_FETCH_LATENCY, "fetch block latency."); init(MetricKeys.Histogram.BLOCK_RECEIVE_DELAY, "receive block delay time, receiveTime - blockTime."); + + init(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, + "Distribution of transaction counts per block.", + new double[]{0, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000}, + "miner"); } private MetricsHistogram() { @@ -62,6 +67,17 @@ private static void init(String name, String help, String... labels) { .register()); } + private static void init(String name, String help, double[] buckets, String... labels) { + Histogram.Builder builder = Histogram.build() + .name(name) + .help(help) + .labelNames(labels); + if (buckets != null && buckets.length > 0) { + builder.buckets(buckets); + } + container.put(name, builder.register()); + } + static Histogram.Timer startTimer(String key, String... labels) { if (Metrics.enabled()) { Histogram histogram = container.get(key); diff --git a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java index 7ef9e8f59e8..882114ba2a9 100644 --- a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java +++ b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java @@ -174,11 +174,11 @@ public void applyBlock(BlockCapsule block) { MetricsUtil.meterMark(MetricsKey.BLOCKCHAIN_TPS, block.getTransactions().size()); Metrics.counterInc(MetricKeys.Counter.TXS, block.getTransactions().size(), MetricLabels.Counter.TXS_SUCCESS, MetricLabels.Counter.TXS_SUCCESS); - } else { - // Empty block - Metrics.counterInc(MetricKeys.Counter.BLOCK_EMPTY, 1, - StringUtil.encode58Check(address)); } + // Record transaction count distribution for all blocks (including empty blocks) + int txCount = block.getTransactions().size(); + Metrics.histogramObserve(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, txCount, + StringUtil.encode58Check(address)); // SR set change detection long nextMaintenanceTime = dbManager.getDynamicPropertiesStore().getNextMaintenanceTime(); diff --git a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java index 38cc171f152..eb5f53be6d0 100644 --- a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java +++ b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java @@ -84,11 +84,13 @@ protected void check(byte[] address, Map witnessAndAccount) Assert.assertEquals(pushBlock.intValue(), blocks + 1); String minerBase58 = StringUtil.encode58Check(address); + // Query histogram bucket le="0.0" for empty blocks Double emptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( - "tron:block_empty_total", new String[] {"miner"}, new String[] {minerBase58}); - - Assert.assertNotNull(emptyBlock); - Assert.assertEquals(emptyBlock.intValue(), 1); + "tron:block_transaction_count_bucket", + new String[] {"miner", "le"}, new String[] {minerBase58, "0.0"}); + + Assert.assertNotNull("Empty block bucket should exist for miner: " + minerBase58, emptyBlock); + Assert.assertEquals("Should have 1 empty block", 1, emptyBlock.intValue()); // Check SR_REMOVE for initial address (removed when addTestWitnessAndAccount() is called) Double srRemoveCount = CollectorRegistry.defaultRegistry.getSampleValue( @@ -99,7 +101,8 @@ protected void check(byte[] address, Map witnessAndAccount) Assert.assertNotNull(srRemoveCount); Assert.assertEquals(1, srRemoveCount.intValue()); - // Check SR_ADD and empty blocks for each new witness in witnessAndAccount (excluding initial address) + // Check SR_ADD and empty blocks for each new witness in witnessAndAccount + // (excluding initial address) ByteString addressByteString = ByteString.copyFrom(address); double totalNewWitnessEmptyBlocks = 0; for (ByteString witnessAddress : witnessAndAccount.keySet()) { @@ -114,13 +117,17 @@ protected void check(byte[] address, Map witnessAndAccount) new String[] {"action", "witness"}, new String[] {MetricLabels.Counter.SR_ADD, witnessBase58} ); - Assert.assertNotNull("SR_ADD should be recorded for witness: " + witnessBase58, srAddCount); - Assert.assertEquals("Each new witness should have 1 SR_ADD record", 1, srAddCount.intValue()); + Assert.assertNotNull("SR_ADD should be recorded for witness: " + witnessBase58, + srAddCount); + Assert.assertEquals("Each new witness should have 1 SR_ADD record", 1, + srAddCount.intValue()); - // Collect empty blocks count + // Collect empty blocks count from histogram bucket Double witnessEmptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( - "tron:block_empty_total", new String[] {"miner"}, new String[] {witnessBase58}); - Assert.assertNotNull(witnessEmptyBlock); + "tron:block_transaction_count_bucket", + new String[] {"miner", "le"}, new String[] {witnessBase58, "0.0"}); + Assert.assertNotNull("Empty block bucket should exist for witness: " + witnessBase58, + witnessEmptyBlock); totalNewWitnessEmptyBlocks += witnessEmptyBlock; } Assert.assertEquals(blocks, (int)totalNewWitnessEmptyBlocks); @@ -176,14 +183,17 @@ public void testMetric() throws Exception { Map witnessAndAccount = addTestWitnessAndAccount(); witnessAndAccount.put(ByteString.copyFrom(address), key); - // Explicitly update WitnessScheduleStore to remove initial address, triggering SR_REMOVE metric + // Explicitly update WitnessScheduleStore to remove initial address, + // triggering SR_REMOVE metric List newActiveWitnesses = new ArrayList<>(witnessAndAccount.keySet()); newActiveWitnesses.remove(ByteString.copyFrom(address)); chainBaseManager.getWitnessScheduleStore().saveActiveWitnesses(newActiveWitnesses); // Update nextMaintenanceTime to trigger SR set change detection - long nextMaintenanceTime = chainBaseManager.getDynamicPropertiesStore().getNextMaintenanceTime(); - chainBaseManager.getDynamicPropertiesStore().updateNextMaintenanceTime(nextMaintenanceTime + 3600_000L); + long nextMaintenanceTime = + chainBaseManager.getDynamicPropertiesStore().getNextMaintenanceTime(); + chainBaseManager.getDynamicPropertiesStore().updateNextMaintenanceTime( + nextMaintenanceTime + 3600_000L); for (int i = 0; i < blocks; i++) { generateBlock(witnessAndAccount); From 39dd708d6b18fc335cbb2e27d3cecfff5fe3c0dc Mon Sep 17 00:00:00 2001 From: warku123 Date: Wed, 1 Apr 2026 20:45:26 +0800 Subject: [PATCH 6/9] Modify blank to passed the ci check --- .../tron/core/metrics/blockchain/BlockChainMetricManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java index 882114ba2a9..a57bfa8f073 100644 --- a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java +++ b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java @@ -47,7 +47,7 @@ public class BlockChainMetricManager { private String failProcessBlockReason = ""; private final Set lastActiveWitnesses = ConcurrentHashMap.newKeySet(); // To control SR set change metric update logic, -1 means not initialized - private long lastNextMaintenanceTime = -1; + private long lastNextMaintenanceTime = -1; public BlockChainInfo getBlockChainInfo() { BlockChainInfo blockChainInfo = new BlockChainInfo(); From e92b8e2e72e22347f33d01280352bd2466a3ce22 Mon Sep 17 00:00:00 2001 From: warku123 Date: Thu, 2 Apr 2026 11:20:24 +0800 Subject: [PATCH 7/9] refactor(metrics): extract MINER_LABEL constant for histogram labels Extract repeated 'miner' string literal into MINER_LABEL constant in MetricsHistogram to follow DRY principle and fix SonarQube warning. Also update PrometheusApiServiceTest to use MINER_LABEL constant for test assertions. Relates #6624 --- .../java/org/tron/common/prometheus/MetricsHistogram.java | 7 ++++--- .../core/metrics/prometheus/PrometheusApiServiceTest.java | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java b/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java index 9042f17f3f5..29363e1e428 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java @@ -9,6 +9,7 @@ public class MetricsHistogram { private static final Map container = new ConcurrentHashMap<>(); + private static final String MINER_LABEL = "miner"; static { init(MetricKeys.Histogram.INTERNAL_SERVICE_LATENCY, "Internal Service latency.", @@ -20,7 +21,7 @@ public class MetricsHistogram { init(MetricKeys.Histogram.JSONRPC_SERVICE_LATENCY, "JsonRpc Service latency.", "method"); init(MetricKeys.Histogram.MINER_LATENCY, "miner latency.", - "miner"); + MINER_LABEL); init(MetricKeys.Histogram.PING_PONG_LATENCY, "node ping pong latency."); init(MetricKeys.Histogram.VERIFY_SIGN_LATENCY, "verify sign latency for trx , block.", "type"); @@ -36,7 +37,7 @@ public class MetricsHistogram { init(MetricKeys.Histogram.PROCESS_TRANSACTION_LATENCY, "process transaction latency.", "type", "contract"); init(MetricKeys.Histogram.MINER_DELAY, "miner delay time, actualTime - planTime.", - "miner"); + MINER_LABEL); init(MetricKeys.Histogram.UDP_BYTES, "udp_bytes traffic.", "type"); init(MetricKeys.Histogram.TCP_BYTES, "tcp_bytes traffic.", @@ -52,7 +53,7 @@ public class MetricsHistogram { init(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, "Distribution of transaction counts per block.", new double[]{0, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000}, - "miner"); + MINER_LABEL); } private MetricsHistogram() { diff --git a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java index eb5f53be6d0..3d19654e820 100644 --- a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java +++ b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java @@ -40,6 +40,9 @@ @Slf4j(topic = "metric") public class PrometheusApiServiceTest extends BaseTest { + + private static final String MINER_LABEL = "miner"; + static LocalDateTime localDateTime = LocalDateTime.now(); @Resource private DposSlot dposSlot; @@ -87,7 +90,7 @@ protected void check(byte[] address, Map witnessAndAccount) // Query histogram bucket le="0.0" for empty blocks Double emptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( "tron:block_transaction_count_bucket", - new String[] {"miner", "le"}, new String[] {minerBase58, "0.0"}); + new String[] {MINER_LABEL, "le"}, new String[] {minerBase58, "0.0"}); Assert.assertNotNull("Empty block bucket should exist for miner: " + minerBase58, emptyBlock); Assert.assertEquals("Should have 1 empty block", 1, emptyBlock.intValue()); @@ -125,7 +128,7 @@ protected void check(byte[] address, Map witnessAndAccount) // Collect empty blocks count from histogram bucket Double witnessEmptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( "tron:block_transaction_count_bucket", - new String[] {"miner", "le"}, new String[] {witnessBase58, "0.0"}); + new String[] {MINER_LABEL, "le"}, new String[] {witnessBase58, "0.0"}); Assert.assertNotNull("Empty block bucket should exist for witness: " + witnessBase58, witnessEmptyBlock); totalNewWitnessEmptyBlocks += witnessEmptyBlock; From 846e3d3088f2914e2273b5de79dab6a5350cebf7 Mon Sep 17 00:00:00 2001 From: warku123 Date: Wed, 8 Apr 2026 16:40:50 +0800 Subject: [PATCH 8/9] refactor(metrics): address PR review feedback - Extract MINER_LABEL to MetricLabels.Histogram.MINER as shared constant - Use int instead of double for block count in tests - Store witness addresses as base58 directly, removing unnecessary hex round-trip Co-Authored-By: Claude Opus 4.6 --- .../org/tron/common/prometheus/MetricLabels.java | 1 + .../tron/common/prometheus/MetricsHistogram.java | 7 +++---- .../blockchain/BlockChainMetricManager.java | 8 ++++---- .../prometheus/PrometheusApiServiceTest.java | 14 ++++++-------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/common/src/main/java/org/tron/common/prometheus/MetricLabels.java b/common/src/main/java/org/tron/common/prometheus/MetricLabels.java index 875e03d8110..1f0da214085 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricLabels.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricLabels.java @@ -68,6 +68,7 @@ private Gauge() { // Histogram public static class Histogram { + public static final String MINER = "miner"; public static final String TRAFFIC_IN = "in"; public static final String TRAFFIC_OUT = "out"; diff --git a/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java b/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java index 29363e1e428..6a66dc76bb3 100644 --- a/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java +++ b/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java @@ -9,7 +9,6 @@ public class MetricsHistogram { private static final Map container = new ConcurrentHashMap<>(); - private static final String MINER_LABEL = "miner"; static { init(MetricKeys.Histogram.INTERNAL_SERVICE_LATENCY, "Internal Service latency.", @@ -21,7 +20,7 @@ public class MetricsHistogram { init(MetricKeys.Histogram.JSONRPC_SERVICE_LATENCY, "JsonRpc Service latency.", "method"); init(MetricKeys.Histogram.MINER_LATENCY, "miner latency.", - MINER_LABEL); + MetricLabels.Histogram.MINER); init(MetricKeys.Histogram.PING_PONG_LATENCY, "node ping pong latency."); init(MetricKeys.Histogram.VERIFY_SIGN_LATENCY, "verify sign latency for trx , block.", "type"); @@ -37,7 +36,7 @@ public class MetricsHistogram { init(MetricKeys.Histogram.PROCESS_TRANSACTION_LATENCY, "process transaction latency.", "type", "contract"); init(MetricKeys.Histogram.MINER_DELAY, "miner delay time, actualTime - planTime.", - MINER_LABEL); + MetricLabels.Histogram.MINER); init(MetricKeys.Histogram.UDP_BYTES, "udp_bytes traffic.", "type"); init(MetricKeys.Histogram.TCP_BYTES, "tcp_bytes traffic.", @@ -53,7 +52,7 @@ public class MetricsHistogram { init(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, "Distribution of transaction counts per block.", new double[]{0, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000}, - MINER_LABEL); + MetricLabels.Histogram.MINER); } private MetricsHistogram() { diff --git a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java index a57bfa8f073..a2de81fb4c9 100644 --- a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java +++ b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java @@ -185,11 +185,11 @@ public void applyBlock(BlockCapsule block) { if (lastNextMaintenanceTime == -1) { lastNextMaintenanceTime = nextMaintenanceTime; lastActiveWitnesses.addAll(chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() - .stream().map(w -> Hex.toHexString(w.toByteArray())).collect(Collectors.toSet())); + .stream().map(w -> StringUtil.encode58Check(w.toByteArray())).collect(Collectors.toSet())); } else if (nextMaintenanceTime != lastNextMaintenanceTime) { Set currentWitnesses = chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() .stream() - .map(w -> Hex.toHexString(w.toByteArray())) + .map(w -> StringUtil.encode58Check(w.toByteArray())) .collect(Collectors.toSet()); recordSrSetChange(currentWitnesses); lastNextMaintenanceTime = nextMaintenanceTime; @@ -205,11 +205,11 @@ private void recordSrSetChange(Set currentWitnesses) { for (String address : added) { Metrics.counterInc(MetricKeys.Counter.SR_SET_CHANGE, 1, - MetricLabels.Counter.SR_ADD, StringUtil.encode58Check(Hex.decode(address))); + MetricLabels.Counter.SR_ADD, address); } for (String address : removed) { Metrics.counterInc(MetricKeys.Counter.SR_SET_CHANGE, 1, - MetricLabels.Counter.SR_REMOVE, StringUtil.encode58Check(Hex.decode(address))); + MetricLabels.Counter.SR_REMOVE, address); } if (!added.isEmpty() || !removed.isEmpty()) { lastActiveWitnesses.clear(); diff --git a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java index 3d19654e820..ee42af4fa99 100644 --- a/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java +++ b/framework/src/test/java/org/tron/core/metrics/prometheus/PrometheusApiServiceTest.java @@ -41,7 +41,6 @@ @Slf4j(topic = "metric") public class PrometheusApiServiceTest extends BaseTest { - private static final String MINER_LABEL = "miner"; static LocalDateTime localDateTime = LocalDateTime.now(); @Resource @@ -90,7 +89,7 @@ protected void check(byte[] address, Map witnessAndAccount) // Query histogram bucket le="0.0" for empty blocks Double emptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( "tron:block_transaction_count_bucket", - new String[] {MINER_LABEL, "le"}, new String[] {minerBase58, "0.0"}); + new String[] {MetricLabels.Histogram.MINER, "le"}, new String[] {minerBase58, "0.0"}); Assert.assertNotNull("Empty block bucket should exist for miner: " + minerBase58, emptyBlock); Assert.assertEquals("Should have 1 empty block", 1, emptyBlock.intValue()); @@ -107,7 +106,7 @@ protected void check(byte[] address, Map witnessAndAccount) // Check SR_ADD and empty blocks for each new witness in witnessAndAccount // (excluding initial address) ByteString addressByteString = ByteString.copyFrom(address); - double totalNewWitnessEmptyBlocks = 0; + int totalNewWitnessEmptyBlocks = 0; for (ByteString witnessAddress : witnessAndAccount.keySet()) { if (witnessAddress.equals(addressByteString)) { continue; // Skip initial address @@ -126,14 +125,13 @@ protected void check(byte[] address, Map witnessAndAccount) srAddCount.intValue()); // Collect empty blocks count from histogram bucket - Double witnessEmptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( + int witnessEmptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( "tron:block_transaction_count_bucket", - new String[] {MINER_LABEL, "le"}, new String[] {witnessBase58, "0.0"}); - Assert.assertNotNull("Empty block bucket should exist for witness: " + witnessBase58, - witnessEmptyBlock); + new String[] {MetricLabels.Histogram.MINER, "le"}, new String[] {witnessBase58, "0.0"}) + .intValue(); totalNewWitnessEmptyBlocks += witnessEmptyBlock; } - Assert.assertEquals(blocks, (int)totalNewWitnessEmptyBlocks); + Assert.assertEquals(blocks, totalNewWitnessEmptyBlocks); Double errorLogs = CollectorRegistry.defaultRegistry.getSampleValue( "tron:error_info_total", new String[] {"net"}, new String[] {MetricLabels.UNDEFINED}); From ff17a1961b971de1149a89b9323d17703f73ef2a Mon Sep 17 00:00:00 2001 From: warku123 Date: Wed, 8 Apr 2026 16:47:47 +0800 Subject: [PATCH 9/9] fix(metrics): wrap long line to pass checkstyle 100-char limit Co-Authored-By: Claude Opus 4.6 --- .../tron/core/metrics/blockchain/BlockChainMetricManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java index a2de81fb4c9..3f96d3a7903 100644 --- a/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java +++ b/framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java @@ -185,7 +185,8 @@ public void applyBlock(BlockCapsule block) { if (lastNextMaintenanceTime == -1) { lastNextMaintenanceTime = nextMaintenanceTime; lastActiveWitnesses.addAll(chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() - .stream().map(w -> StringUtil.encode58Check(w.toByteArray())).collect(Collectors.toSet())); + .stream().map(w -> StringUtil.encode58Check(w.toByteArray())) + .collect(Collectors.toSet())); } else if (nextMaintenanceTime != lastNextMaintenanceTime) { Set currentWitnesses = chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() .stream()