From 9b80f91455bc48c9f55f8564bfc46c1f26611e28 Mon Sep 17 00:00:00 2001 From: warku123 Date: Thu, 25 Dec 2025 17:05:45 +0800 Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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 04/17] 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 05/17] 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 06/17] 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 07/17] 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 08/17] 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 09/17] 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() From 7f351bf82856e69f107a4d8d791187ae1ff050f4 Mon Sep 17 00:00:00 2001 From: warku123 Date: Thu, 9 Apr 2026 14:21:09 +0800 Subject: [PATCH 10/17] refactor(metrics): reuse txCount variable to avoid repeated calls Co-Authored-By: Claude Opus 4.6 --- .../core/metrics/blockchain/BlockChainMetricManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 3f96d3a7903..45f2f67e550 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 @@ -170,13 +170,13 @@ public void applyBlock(BlockCapsule block) { } //TPS - if (block.getTransactions().size() > 0) { - MetricsUtil.meterMark(MetricsKey.BLOCKCHAIN_TPS, block.getTransactions().size()); - Metrics.counterInc(MetricKeys.Counter.TXS, block.getTransactions().size(), + int txCount = block.getTransactions().size(); + if (txCount > 0) { + MetricsUtil.meterMark(MetricsKey.BLOCKCHAIN_TPS, txCount); + Metrics.counterInc(MetricKeys.Counter.TXS, txCount, MetricLabels.Counter.TXS_SUCCESS, MetricLabels.Counter.TXS_SUCCESS); } // 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)); From 7fdb3c59f823a9faa21cdbeefc81b0139a5c5071 Mon Sep 17 00:00:00 2001 From: warku123 Date: Tue, 14 Apr 2026 11:37:21 +0800 Subject: [PATCH 11/17] fix(metrics): use HashSet and add fork rollback guard for SR set change - Replace ConcurrentHashMap.newKeySet() with HashSet since applyBlock() is only called from a single-threaded path (synchronized on blockLock) - Add fork rollback detection: when nextMaintenanceTime < lastNextMaintenanceTime, reinitialize in-memory state instead of diffing, preventing phantom SR changes Co-Authored-By: Claude Opus 4.6 --- .../metrics/blockchain/BlockChainMetricManager.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 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 45f2f67e550..a6d3fde0dea 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 @@ -45,7 +45,8 @@ public class BlockChainMetricManager { private long failProcessBlockNum = 0; @Setter private String failProcessBlockReason = ""; - private final Set lastActiveWitnesses = ConcurrentHashMap.newKeySet(); + // Only accessed from block processing thread (synchronized on blockLock) + private final Set lastActiveWitnesses = new HashSet<>(); // To control SR set change metric update logic, -1 means not initialized private long lastNextMaintenanceTime = -1; @@ -192,7 +193,13 @@ public void applyBlock(BlockCapsule block) { .stream() .map(w -> StringUtil.encode58Check(w.toByteArray())) .collect(Collectors.toSet()); - recordSrSetChange(currentWitnesses); + if (nextMaintenanceTime < lastNextMaintenanceTime) { + // Fork rollback detected — reinitialize instead of diffing + lastActiveWitnesses.clear(); + lastActiveWitnesses.addAll(currentWitnesses); + } else { + recordSrSetChange(currentWitnesses); + } lastNextMaintenanceTime = nextMaintenanceTime; } } From 779194485f646e349bd2be943f85ad6105943fe0 Mon Sep 17 00:00:00 2001 From: warku123 Date: Thu, 16 Apr 2026 12:08:59 +0800 Subject: [PATCH 12/17] perf(metrics): skip metrics computation when prometheus is disabled Wrap histogram observation and SR set change detection with Metrics.enabled() guard to avoid unnecessary computation (DB reads, encode58Check, stream mapping, HashSet diff) on every block when metrics are not enabled. --- .../blockchain/BlockChainMetricManager.java | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 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 a6d3fde0dea..86c50ebec0c 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 @@ -177,30 +177,33 @@ public void applyBlock(BlockCapsule block) { Metrics.counterInc(MetricKeys.Counter.TXS, txCount, MetricLabels.Counter.TXS_SUCCESS, MetricLabels.Counter.TXS_SUCCESS); } - // Record transaction count distribution for all blocks (including empty blocks) - Metrics.histogramObserve(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, txCount, - StringUtil.encode58Check(address)); - - // SR set change detection - long nextMaintenanceTime = dbManager.getDynamicPropertiesStore().getNextMaintenanceTime(); - if (lastNextMaintenanceTime == -1) { - lastNextMaintenanceTime = nextMaintenanceTime; - lastActiveWitnesses.addAll(chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() - .stream().map(w -> StringUtil.encode58Check(w.toByteArray())) - .collect(Collectors.toSet())); - } else if (nextMaintenanceTime != lastNextMaintenanceTime) { - Set currentWitnesses = chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() - .stream() - .map(w -> StringUtil.encode58Check(w.toByteArray())) - .collect(Collectors.toSet()); - if (nextMaintenanceTime < lastNextMaintenanceTime) { - // Fork rollback detected — reinitialize instead of diffing - lastActiveWitnesses.clear(); - lastActiveWitnesses.addAll(currentWitnesses); - } else { - recordSrSetChange(currentWitnesses); + if (Metrics.enabled()) { + // Record transaction count distribution for all blocks (including empty blocks) + Metrics.histogramObserve(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, txCount, + StringUtil.encode58Check(address)); + + // SR set change detection + long nextMaintenanceTime = dbManager.getDynamicPropertiesStore().getNextMaintenanceTime(); + if (lastNextMaintenanceTime == -1) { + lastNextMaintenanceTime = nextMaintenanceTime; + lastActiveWitnesses.addAll(chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() + .stream().map(w -> StringUtil.encode58Check(w.toByteArray())) + .collect(Collectors.toSet())); + } else if (nextMaintenanceTime != lastNextMaintenanceTime) { + Set currentWitnesses = + chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() + .stream() + .map(w -> StringUtil.encode58Check(w.toByteArray())) + .collect(Collectors.toSet()); + if (nextMaintenanceTime < lastNextMaintenanceTime) { + // Fork rollback detected — reinitialize instead of diffing + lastActiveWitnesses.clear(); + lastActiveWitnesses.addAll(currentWitnesses); + } else { + recordSrSetChange(currentWitnesses); + } + lastNextMaintenanceTime = nextMaintenanceTime; } - lastNextMaintenanceTime = nextMaintenanceTime; } } From a12378d9faf92a38eda5988701d82a3426130838 Mon Sep 17 00:00:00 2001 From: warku123 Date: Mon, 27 Apr 2026 15:48:10 +0800 Subject: [PATCH 13/17] refactor(metrics): move SR set change recording into MaintenanceManager The SR set change detection used to live in BlockChainMetricManager.applyBlock and inferred SR set transitions from nextMaintenanceTime changes plus a diff against a cached HashSet. That approach was indirect (re-derived state already known at the source) and required a fork-rollback guard. Hoist the recording into the consensus layer where the transition is explicit: MaintenanceManager.doMaintenance already computes currentWits and newWits when the active witness set changes, so it can call SrSetChangeMetric.record(currentWits, newWits) directly. The new helper lives in the common module so both consensus and framework can depend on it without inverting the existing module hierarchy. Tests: SrSetChangeMetricTest exercises SR_ADD via the real doMaintenance() flow, plus the no-vote / metrics-disabled / SR_REMOVE branches. PrometheusApiServiceTest drops the obsolete SR assertions. --- .../common/prometheus/SrSetChangeMetric.java | 26 +++ .../consensus/dpos/MaintenanceManager.java | 3 + .../blockchain/BlockChainMetricManager.java | 51 ----- .../prometheus/SrSetChangeMetricTest.java | 206 ++++++++++++++++++ .../prometheus/PrometheusApiServiceTest.java | 42 +--- 5 files changed, 241 insertions(+), 87 deletions(-) create mode 100644 common/src/main/java/org/tron/common/prometheus/SrSetChangeMetric.java create mode 100644 framework/src/test/java/org/tron/common/prometheus/SrSetChangeMetricTest.java diff --git a/common/src/main/java/org/tron/common/prometheus/SrSetChangeMetric.java b/common/src/main/java/org/tron/common/prometheus/SrSetChangeMetric.java new file mode 100644 index 00000000000..c913379c8d3 --- /dev/null +++ b/common/src/main/java/org/tron/common/prometheus/SrSetChangeMetric.java @@ -0,0 +1,26 @@ +package org.tron.common.prometheus; + +import com.google.protobuf.ByteString; +import java.util.List; +import org.tron.common.utils.StringUtil; + +public class SrSetChangeMetric { + + private SrSetChangeMetric() { + throw new IllegalStateException("SrSetChangeMetric"); + } + + public static void record(List currentWits, List newWits) { + if (!Metrics.enabled()) { + return; + } + newWits.stream() + .filter(w -> !currentWits.contains(w)) + .forEach(w -> Metrics.counterInc(MetricKeys.Counter.SR_SET_CHANGE, 1, + MetricLabels.Counter.SR_ADD, StringUtil.encode58Check(w.toByteArray()))); + currentWits.stream() + .filter(w -> !newWits.contains(w)) + .forEach(w -> Metrics.counterInc(MetricKeys.Counter.SR_SET_CHANGE, 1, + MetricLabels.Counter.SR_REMOVE, StringUtil.encode58Check(w.toByteArray()))); + } +} diff --git a/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java b/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java index 012169bdb87..4f90cc56a9a 100644 --- a/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java +++ b/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java @@ -16,6 +16,7 @@ import org.bouncycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.tron.common.prometheus.SrSetChangeMetric; import org.tron.consensus.ConsensusDelegate; import org.tron.consensus.pbft.PbftManager; import org.tron.core.capsule.AccountCapsule; @@ -141,6 +142,8 @@ public void doMaintenance() { witnessCapsule.setIsJobs(true); consensusDelegate.saveWitness(witnessCapsule); }); + + SrSetChangeMetric.record(currentWits, newWits); } logger.info("Update witness success. \nbefore: {} \nafter: {}", 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 86c50ebec0c..b7be7398ada 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,13 +3,10 @@ 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; @@ -45,10 +42,6 @@ public class BlockChainMetricManager { private long failProcessBlockNum = 0; @Setter private String failProcessBlockReason = ""; - // Only accessed from block processing thread (synchronized on blockLock) - private final Set lastActiveWitnesses = new HashSet<>(); - // To control SR set change metric update logic, -1 means not initialized - private long lastNextMaintenanceTime = -1; public BlockChainInfo getBlockChainInfo() { BlockChainInfo blockChainInfo = new BlockChainInfo(); @@ -181,50 +174,6 @@ public void applyBlock(BlockCapsule block) { // Record transaction count distribution for all blocks (including empty blocks) Metrics.histogramObserve(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, txCount, StringUtil.encode58Check(address)); - - // SR set change detection - long nextMaintenanceTime = dbManager.getDynamicPropertiesStore().getNextMaintenanceTime(); - if (lastNextMaintenanceTime == -1) { - lastNextMaintenanceTime = nextMaintenanceTime; - lastActiveWitnesses.addAll(chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() - .stream().map(w -> StringUtil.encode58Check(w.toByteArray())) - .collect(Collectors.toSet())); - } else if (nextMaintenanceTime != lastNextMaintenanceTime) { - Set currentWitnesses = - chainBaseManager.getWitnessScheduleStore().getActiveWitnesses() - .stream() - .map(w -> StringUtil.encode58Check(w.toByteArray())) - .collect(Collectors.toSet()); - if (nextMaintenanceTime < lastNextMaintenanceTime) { - // Fork rollback detected — reinitialize instead of diffing - lastActiveWitnesses.clear(); - lastActiveWitnesses.addAll(currentWitnesses); - } else { - recordSrSetChange(currentWitnesses); - } - lastNextMaintenanceTime = nextMaintenanceTime; - } - } - } - - private void recordSrSetChange(Set currentWitnesses) { - 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, address); - } - for (String address : removed) { - Metrics.counterInc(MetricKeys.Counter.SR_SET_CHANGE, 1, - MetricLabels.Counter.SR_REMOVE, address); - } - if (!added.isEmpty() || !removed.isEmpty()) { - lastActiveWitnesses.clear(); - lastActiveWitnesses.addAll(currentWitnesses); } } diff --git a/framework/src/test/java/org/tron/common/prometheus/SrSetChangeMetricTest.java b/framework/src/test/java/org/tron/common/prometheus/SrSetChangeMetricTest.java new file mode 100644 index 00000000000..0981472d00a --- /dev/null +++ b/framework/src/test/java/org/tron/common/prometheus/SrSetChangeMetricTest.java @@ -0,0 +1,206 @@ +package org.tron.common.prometheus; + +import com.google.protobuf.ByteString; +import io.prometheus.client.CollectorRegistry; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.tron.common.BaseTest; +import org.tron.common.TestConstants; +import org.tron.common.utils.StringUtil; +import org.tron.consensus.dpos.MaintenanceManager; +import org.tron.core.capsule.AccountCapsule; +import org.tron.core.capsule.VotesCapsule; +import org.tron.core.capsule.WitnessCapsule; +import org.tron.core.config.args.Args; +import org.tron.core.consensus.ConsensusService; +import org.tron.protos.Protocol; +import org.tron.protos.Protocol.Vote; + +@Slf4j(topic = "metric") +public class SrSetChangeMetricTest extends BaseTest { + + private static final AtomicInteger PORT = new AtomicInteger(0); + private static final AtomicInteger UNIQUE = new AtomicInteger(0); + + @Resource + private MaintenanceManager maintenanceManager; + @Resource + private ConsensusService consensusService; + + static { + Args.setParam(new String[]{"-d", dbPath()}, TestConstants.TEST_CONF); + Args.getInstance().setNodeListenPort(20000 + PORT.incrementAndGet()); + Args.getInstance().setMetricsPrometheusEnable(true); + Metrics.init(); + } + + @Before + public void setUp() { + Args.getInstance().setMetricsPrometheusEnable(true); + consensusService.start(); + } + + @After + public void tearDown() { + Args.getInstance().setMetricsPrometheusEnable(true); + } + + /** + * Drive the full maintenance flow: starting with a single active witness while WitnessStore + * contains additional ones, doMaintenance() should expand active witnesses to the full set and + * emit SR_ADD for each newly active witness. + */ + @Test + public void testSrAddViaMaintenance() { + ByteString stableWit = registerWitness(); + ByteString newWit1 = registerWitness(); + ByteString newWit2 = registerWitness(); + + chainBaseManager.getWitnessScheduleStore() + .saveActiveWitnesses(Collections.singletonList(stableWit)); + + seedVote(stableWit); + + maintenanceManager.doMaintenance(); + + Assert.assertEquals(1, sample(MetricLabels.Counter.SR_ADD, newWit1).intValue()); + Assert.assertEquals(1, sample(MetricLabels.Counter.SR_ADD, newWit2).intValue()); + Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, stableWit)); + Assert.assertNull(sample(MetricLabels.Counter.SR_REMOVE, stableWit)); + } + + /** + * Active witness set already matches WitnessStore → no metric emitted. + */ + @Test + public void testNoMetricWhenSetUnchanged() { + ByteString witA = registerWitness(); + ByteString witB = registerWitness(); + + chainBaseManager.getWitnessScheduleStore() + .saveActiveWitnesses(Arrays.asList(witA, witB)); + + seedVote(witA); + + maintenanceManager.doMaintenance(); + + Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, witA)); + Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, witB)); + Assert.assertNull(sample(MetricLabels.Counter.SR_REMOVE, witA)); + Assert.assertNull(sample(MetricLabels.Counter.SR_REMOVE, witB)); + } + + /** + * Empty VotesStore → countVote() is empty → SR change check is skipped, even when the active + * set differs from the full witness store. + */ + @Test + public void testNoMetricWhenNoVotes() { + ByteString stableWit = registerWitness(); + ByteString newWit = registerWitness(); + + chainBaseManager.getWitnessScheduleStore() + .saveActiveWitnesses(Collections.singletonList(stableWit)); + + maintenanceManager.doMaintenance(); + + Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, newWit)); + } + + /** + * Metrics disabled → record() short-circuits even though the active set changes. + */ + @Test + public void testNoMetricWhenMetricsDisabled() { + Args.getInstance().setMetricsPrometheusEnable(false); + try { + ByteString stableWit = registerWitness(); + ByteString newWit = registerWitness(); + + chainBaseManager.getWitnessScheduleStore() + .saveActiveWitnesses(Collections.singletonList(stableWit)); + + seedVote(stableWit); + + maintenanceManager.doMaintenance(); + + Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, newWit)); + } finally { + Args.getInstance().setMetricsPrometheusEnable(true); + } + } + + /** + * SR_REMOVE is verified by directly calling record() instead of going through doMaintenance(), + * because driving a removal through the real flow is impractical here: + * + *

Inside doMaintenance(), the block before SrSetChangeMetric.record() iterates currentWits + * and calls setIsJobs(false) on each WitnessCapsule fetched from WitnessStore. If currentWits + * contains any address that is not present in WitnessStore, getWitness() returns null and the + * code NPEs — so SR_REMOVE cannot be triggered by simply pointing the active set at an + * "obsolete" address. + * + *

The only other path to SR_REMOVE is rank-based eviction: with more than + * MAX_ACTIVE_WITNESS_NUM (27) witnesses, sorting drops the lowest-ranked one. Building that + * setup just to exercise this branch is heavy and adds little value, since SR_ADD and + * SR_REMOVE share the exact same emit logic in record() — verifying SR_ADD via doMaintenance + * already proves the wiring is correct, and this direct call covers the symmetric branch. + */ + @Test + public void testSrRemoveDirect() { + ByteString stableWit = uniqueAddress(); + ByteString removedWit = uniqueAddress(); + + SrSetChangeMetric.record( + Arrays.asList(stableWit, removedWit), + Collections.singletonList(stableWit)); + + Assert.assertEquals(1, sample(MetricLabels.Counter.SR_REMOVE, removedWit).intValue()); + Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, removedWit)); + Assert.assertNull(sample(MetricLabels.Counter.SR_REMOVE, stableWit)); + } + + private ByteString registerWitness() { + ByteString address = uniqueAddress(); + chainBaseManager.getWitnessStore().put(address.toByteArray(), new WitnessCapsule(address)); + chainBaseManager.addWitness(address); + chainBaseManager.getAccountStore().put(address.toByteArray(), + new AccountCapsule(Protocol.Account.newBuilder().setAddress(address).build())); + return address; + } + + private void seedVote(ByteString voteFor) { + ByteString voter = uniqueAddress(); + VotesCapsule votes = new VotesCapsule(voter, Collections.emptyList(), + Collections.singletonList(Vote.newBuilder() + .setVoteAddress(voteFor) + .setVoteCount(1L) + .build())); + chainBaseManager.getVotesStore().put(voter.toByteArray(), votes); + } + + private ByteString uniqueAddress() { + int n = UNIQUE.incrementAndGet(); + byte[] bytes = new byte[21]; + bytes[0] = 0x41; + bytes[17] = (byte) ((n >> 16) & 0xFF); + bytes[18] = (byte) ((n >> 8) & 0xFF); + bytes[19] = (byte) (n & 0xFF); + bytes[20] = 0x01; + return ByteString.copyFrom(bytes); + } + + private Double sample(String action, ByteString witness) { + return CollectorRegistry.defaultRegistry.getSampleValue( + MetricKeys.Counter.SR_SET_CHANGE + "_total", + new String[]{"action", "witness"}, + new String[]{action, StringUtil.encode58Check(witness.toByteArray())}); + } +} 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 ee42af4fa99..dd260a1b869 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 @@ -90,41 +90,18 @@ protected void check(byte[] address, Map witnessAndAccount) Double emptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( "tron:block_transaction_count_bucket", 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()); - // 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) + // Collect empty blocks for each new witness in witnessAndAccount (excluding initial address) ByteString addressByteString = ByteString.copyFrom(address); int totalNewWitnessEmptyBlocks = 0; for (ByteString witnessAddress : witnessAndAccount.keySet()) { if (witnessAddress.equals(addressByteString)) { - continue; // Skip initial address + continue; } 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 from histogram bucket int witnessEmptyBlock = CollectorRegistry.defaultRegistry.getSampleValue( "tron:block_transaction_count_bucket", new String[] {MetricLabels.Histogram.MINER, "le"}, new String[] {witnessBase58, "0.0"}) @@ -183,19 +160,12 @@ 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 + + // Schedule the new witnesses (excluding initial address) so dposSlot rotates blocks among them 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); } From 96bf315df135ce4bee96860af9faa5adcc392b3f Mon Sep 17 00:00:00 2001 From: warku123 Date: Mon, 27 Apr 2026 18:22:37 +0800 Subject: [PATCH 14/17] docs(metrics): add cardinality comment for SR_SET_CHANGE witness label --- .../src/main/java/org/tron/common/prometheus/MetricKeys.java | 3 +++ 1 file changed, 3 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 3293a67342a..95a38c4b479 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,9 @@ 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"; + // witness label: bounded cardinality -- SR candidate pool is finite, rotation is + // infrequent (at most once per maintenance interval); kept for at-a-glance SR + // identification in dashboards rather than requiring log cross-referencing. 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"; From 033019c175359617b8436e0aa62614ad93a17a4c Mon Sep 17 00:00:00 2001 From: warku123 Date: Tue, 28 Apr 2026 10:51:15 +0800 Subject: [PATCH 15/17] refactor(metrics): rename SrSetChangeMetric to SRMetrics for extensibility --- .../prometheus/{SrSetChangeMetric.java => SRMetrics.java} | 6 +++--- .../java/org/tron/consensus/dpos/MaintenanceManager.java | 4 ++-- .../{SrSetChangeMetricTest.java => SRMetricsTest.java} | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename common/src/main/java/org/tron/common/prometheus/{SrSetChangeMetric.java => SRMetrics.java} (86%) rename framework/src/test/java/org/tron/common/prometheus/{SrSetChangeMetricTest.java => SRMetricsTest.java} (97%) diff --git a/common/src/main/java/org/tron/common/prometheus/SrSetChangeMetric.java b/common/src/main/java/org/tron/common/prometheus/SRMetrics.java similarity index 86% rename from common/src/main/java/org/tron/common/prometheus/SrSetChangeMetric.java rename to common/src/main/java/org/tron/common/prometheus/SRMetrics.java index c913379c8d3..f67dd3f4e4d 100644 --- a/common/src/main/java/org/tron/common/prometheus/SrSetChangeMetric.java +++ b/common/src/main/java/org/tron/common/prometheus/SRMetrics.java @@ -4,10 +4,10 @@ import java.util.List; import org.tron.common.utils.StringUtil; -public class SrSetChangeMetric { +public class SRMetrics { - private SrSetChangeMetric() { - throw new IllegalStateException("SrSetChangeMetric"); + private SRMetrics() { + throw new IllegalStateException("SRMetrics"); } public static void record(List currentWits, List newWits) { diff --git a/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java b/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java index 4f90cc56a9a..4699bb3b045 100644 --- a/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java +++ b/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java @@ -16,7 +16,7 @@ import org.bouncycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.tron.common.prometheus.SrSetChangeMetric; +import org.tron.common.prometheus.SRMetrics; import org.tron.consensus.ConsensusDelegate; import org.tron.consensus.pbft.PbftManager; import org.tron.core.capsule.AccountCapsule; @@ -143,7 +143,7 @@ public void doMaintenance() { consensusDelegate.saveWitness(witnessCapsule); }); - SrSetChangeMetric.record(currentWits, newWits); + SRMetrics.record(currentWits, newWits); } logger.info("Update witness success. \nbefore: {} \nafter: {}", diff --git a/framework/src/test/java/org/tron/common/prometheus/SrSetChangeMetricTest.java b/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java similarity index 97% rename from framework/src/test/java/org/tron/common/prometheus/SrSetChangeMetricTest.java rename to framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java index 0981472d00a..79d5c4dfa79 100644 --- a/framework/src/test/java/org/tron/common/prometheus/SrSetChangeMetricTest.java +++ b/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java @@ -24,7 +24,7 @@ import org.tron.protos.Protocol.Vote; @Slf4j(topic = "metric") -public class SrSetChangeMetricTest extends BaseTest { +public class SRMetricsTest extends BaseTest { private static final AtomicInteger PORT = new AtomicInteger(0); private static final AtomicInteger UNIQUE = new AtomicInteger(0); @@ -141,7 +141,7 @@ public void testNoMetricWhenMetricsDisabled() { * SR_REMOVE is verified by directly calling record() instead of going through doMaintenance(), * because driving a removal through the real flow is impractical here: * - *

Inside doMaintenance(), the block before SrSetChangeMetric.record() iterates currentWits + *

Inside doMaintenance(), the block before SRMetrics.record() iterates currentWits * and calls setIsJobs(false) on each WitnessCapsule fetched from WitnessStore. If currentWits * contains any address that is not present in WitnessStore, getWitness() returns null and the * code NPEs — so SR_REMOVE cannot be triggered by simply pointing the active set at an @@ -158,7 +158,7 @@ public void testSrRemoveDirect() { ByteString stableWit = uniqueAddress(); ByteString removedWit = uniqueAddress(); - SrSetChangeMetric.record( + SRMetrics.record( Arrays.asList(stableWit, removedWit), Collections.singletonList(stableWit)); From 4ca7d6eaa024ce7951e43ca6706fe5f79981a21a Mon Sep 17 00:00:00 2001 From: warku123 Date: Tue, 28 Apr 2026 10:53:43 +0800 Subject: [PATCH 16/17] refactor(metrics): tighten metric placement and rename - move BLOCK_TRANSACTION_COUNT histogram recording from BlockChainMetricManager.applyBlock to Manager.applyBlock so that witness-produced blocks are also sampled (previously only peer-received blocks went through BlockChainMetricManager) - rename SRMetrics.record() to recordSrSetChange() to leave room for additional SR-related metrics --- .../src/main/java/org/tron/common/prometheus/SRMetrics.java | 2 +- .../java/org/tron/consensus/dpos/MaintenanceManager.java | 2 +- framework/src/main/java/org/tron/core/db/Manager.java | 6 ++++++ .../core/metrics/blockchain/BlockChainMetricManager.java | 5 ----- .../test/java/org/tron/common/prometheus/SRMetricsTest.java | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/common/src/main/java/org/tron/common/prometheus/SRMetrics.java b/common/src/main/java/org/tron/common/prometheus/SRMetrics.java index f67dd3f4e4d..0c547a38e2c 100644 --- a/common/src/main/java/org/tron/common/prometheus/SRMetrics.java +++ b/common/src/main/java/org/tron/common/prometheus/SRMetrics.java @@ -10,7 +10,7 @@ private SRMetrics() { throw new IllegalStateException("SRMetrics"); } - public static void record(List currentWits, List newWits) { + public static void recordSrSetChange(List currentWits, List newWits) { if (!Metrics.enabled()) { return; } diff --git a/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java b/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java index 4699bb3b045..fd5e4364d0d 100644 --- a/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java +++ b/consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java @@ -143,7 +143,7 @@ public void doMaintenance() { consensusDelegate.saveWitness(witnessCapsule); }); - SRMetrics.record(currentWits, newWits); + SRMetrics.recordSrSetChange(currentWits, newWits); } logger.info("Update witness success. \nbefore: {} \nafter: {}", diff --git a/framework/src/main/java/org/tron/core/db/Manager.java b/framework/src/main/java/org/tron/core/db/Manager.java index cd1a61c01fe..ba9a6102846 100644 --- a/framework/src/main/java/org/tron/core/db/Manager.java +++ b/framework/src/main/java/org/tron/core/db/Manager.java @@ -1073,6 +1073,12 @@ private void applyBlock(BlockCapsule block, List txs) .put(ByteArray.fromLong(block.getNum()), block.getResult()); } + if (Metrics.enabled()) { + Metrics.histogramObserve(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, + block.getTransactions().size(), + StringUtil.encode58Check(block.getWitnessAddress().toByteArray())); + } + updateFork(block); if (System.currentTimeMillis() - block.getTimeStamp() >= 60_000) { revokingStore.setMaxFlushCount(maxFlushCount); 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 b7be7398ada..f39cf66a8ad 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 @@ -170,11 +170,6 @@ public void applyBlock(BlockCapsule block) { Metrics.counterInc(MetricKeys.Counter.TXS, txCount, MetricLabels.Counter.TXS_SUCCESS, MetricLabels.Counter.TXS_SUCCESS); } - if (Metrics.enabled()) { - // Record transaction count distribution for all blocks (including empty blocks) - Metrics.histogramObserve(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, txCount, - StringUtil.encode58Check(address)); - } } private List getSrList() { diff --git a/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java b/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java index 79d5c4dfa79..4c2e9292d29 100644 --- a/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java +++ b/framework/src/test/java/org/tron/common/prometheus/SRMetricsTest.java @@ -141,7 +141,7 @@ public void testNoMetricWhenMetricsDisabled() { * SR_REMOVE is verified by directly calling record() instead of going through doMaintenance(), * because driving a removal through the real flow is impractical here: * - *

Inside doMaintenance(), the block before SRMetrics.record() iterates currentWits + *

Inside doMaintenance(), the block before SRMetrics.recordSrSetChange() iterates currentWits * and calls setIsJobs(false) on each WitnessCapsule fetched from WitnessStore. If currentWits * contains any address that is not present in WitnessStore, getWitness() returns null and the * code NPEs — so SR_REMOVE cannot be triggered by simply pointing the active set at an @@ -158,7 +158,7 @@ public void testSrRemoveDirect() { ByteString stableWit = uniqueAddress(); ByteString removedWit = uniqueAddress(); - SRMetrics.record( + SRMetrics.recordSrSetChange( Arrays.asList(stableWit, removedWit), Collections.singletonList(stableWit)); From 8ef3e91b95867ec8acace56ff36535e990a18ca6 Mon Sep 17 00:00:00 2001 From: warku123 Date: Tue, 28 Apr 2026 11:57:29 +0800 Subject: [PATCH 17/17] refactor(metrics): move BLOCK_TRANSACTION_COUNT to pushBlock entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Record the histogram at the start of pushBlock (inside the synchronized block, before any early returns) so abnormal pushes — duplicate, stale, fork-switched, or merkle-failed blocks — are also observable. --- framework/src/main/java/org/tron/core/db/Manager.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/framework/src/main/java/org/tron/core/db/Manager.java b/framework/src/main/java/org/tron/core/db/Manager.java index ba9a6102846..6ccd024091d 100644 --- a/framework/src/main/java/org/tron/core/db/Manager.java +++ b/framework/src/main/java/org/tron/core/db/Manager.java @@ -1073,12 +1073,6 @@ private void applyBlock(BlockCapsule block, List txs) .put(ByteArray.fromLong(block.getNum()), block.getResult()); } - if (Metrics.enabled()) { - Metrics.histogramObserve(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, - block.getTransactions().size(), - StringUtil.encode58Check(block.getWitnessAddress().toByteArray())); - } - updateFork(block); if (System.currentTimeMillis() - block.getTimeStamp() >= 60_000) { revokingStore.setMaxFlushCount(maxFlushCount); @@ -1276,6 +1270,11 @@ public void pushBlock(final BlockCapsule block) synchronized (this) { Metrics.histogramObserve(blockedTimer.get()); blockedTimer.remove(); + if (Metrics.enabled()) { + Metrics.histogramObserve(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT, + block.getTransactions().size(), + StringUtil.encode58Check(block.getWitnessAddress().toByteArray())); + } long headerNumber = getDynamicPropertiesStore().getLatestBlockHeaderNumber(); if (block.getNum() <= headerNumber && khaosDb.containBlockInMiniStore(block.getBlockId())) { logger.info("Block {} is already exist.", block.getBlockId().getString());