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..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,6 +14,7 @@ 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 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"; @@ -62,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/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..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,6 +14,7 @@ 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.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/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java b/common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java index 556db10feb5..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.", @@ -48,6 +49,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_LABEL); } private MetricsHistogram() { @@ -62,6 +68,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 384f1d8add1..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 @@ -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,9 @@ public class BlockChainMetricManager { private long failProcessBlockNum = 0; @Setter 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; public BlockChainInfo getBlockChainInfo() { BlockChainInfo blockChainInfo = new BlockChainInfo(); @@ -169,6 +175,46 @@ public void applyBlock(BlockCapsule block) { Metrics.counterInc(MetricKeys.Counter.TXS, block.getTransactions().size(), 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)); + + // SR set change detection + long nextMaintenanceTime = dbManager.getDynamicPropertiesStore().getNextMaintenanceTime(); + if (lastNextMaintenanceTime == -1) { + 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) { + 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); + } } private List getSrList() { 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..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 @@ -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; @@ -25,6 +26,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; @@ -38,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; @@ -65,7 +70,7 @@ protected static void initParameter(CommonParameter parameter) { parameter.setMetricsPrometheusEnable(true); } - protected void check() throws Exception { + protected void check(byte[] address, Map witnessAndAccount) throws Exception { Double memoryBytes = CollectorRegistry.defaultRegistry.getSampleValue( "system_total_physical_memory_bytes"); Assert.assertNotNull(memoryBytes); @@ -80,6 +85,56 @@ 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); + // 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"}); + + 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) + 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 from histogram bucket + Double 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); + totalNewWitnessEmptyBlocks += witnessEmptyBlock; + } + Assert.assertEquals(blocks, (int)totalNewWitnessEmptyBlocks); + Double errorLogs = CollectorRegistry.defaultRegistry.getSampleValue( "tron:error_info_total", new String[] {"net"}, new String[] {MetricLabels.UNDEFINED}); Assert.assertNull(errorLogs); @@ -130,10 +185,23 @@ 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(); + check(address, witnessAndAccount); } private Map addTestWitnessAndAccount() {