Skip to content

Commit 1a97a81

Browse files
authored
feat(metrics): add block transaction count and SR set change monitoring (tronprotocol#6624)
1 parent 980c707 commit 1a97a81

10 files changed

Lines changed: 309 additions & 7 deletions

File tree

common/src/main/java/org/tron/common/prometheus/MetricKeys.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ public static class Counter {
1414
public static final String TXS = "tron:txs";
1515
public static final String MINER = "tron:miner";
1616
public static final String BLOCK_FORK = "tron:block_fork";
17+
// witness label: bounded cardinality -- SR candidate pool is finite, rotation is
18+
// infrequent (at most once per maintenance interval); kept for at-a-glance SR
19+
// identification in dashboards rather than requiring log cross-referencing.
20+
public static final String SR_SET_CHANGE = "tron:sr_set_change";
1721
public static final String P2P_ERROR = "tron:p2p_error";
1822
public static final String P2P_DISCONNECT = "tron:p2p_disconnect";
1923
public static final String INTERNAL_SERVICE_FAIL = "tron:internal_service_fail";
@@ -62,6 +66,7 @@ public static class Histogram {
6266
public static final String MESSAGE_PROCESS_LATENCY = "tron:message_process_latency_seconds";
6367
public static final String BLOCK_FETCH_LATENCY = "tron:block_fetch_latency_seconds";
6468
public static final String BLOCK_RECEIVE_DELAY = "tron:block_receive_delay_seconds";
69+
public static final String BLOCK_TRANSACTION_COUNT = "tron:block_transaction_count";
6570

6671
private Histogram() {
6772
throw new IllegalStateException("Histogram");

common/src/main/java/org/tron/common/prometheus/MetricLabels.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public static class Counter {
3131
public static final String TXS_FAIL_SIG = "sig";
3232
public static final String TXS_FAIL_TAPOS = "tapos";
3333
public static final String TXS_FAIL_DUP = "dup";
34+
public static final String SR_ADD = "add";
35+
public static final String SR_REMOVE = "remove";
3436

3537
private Counter() {
3638
throw new IllegalStateException("Counter");
@@ -66,6 +68,7 @@ private Gauge() {
6668

6769
// Histogram
6870
public static class Histogram {
71+
public static final String MINER = "miner";
6972
public static final String TRAFFIC_IN = "in";
7073
public static final String TRAFFIC_OUT = "out";
7174

common/src/main/java/org/tron/common/prometheus/MetricsCounter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class MetricsCounter {
1414
init(MetricKeys.Counter.TXS, "tron txs info .", "type", "detail");
1515
init(MetricKeys.Counter.MINER, "tron miner info .", "miner", "type");
1616
init(MetricKeys.Counter.BLOCK_FORK, "tron block fork info .", "type");
17+
init(MetricKeys.Counter.SR_SET_CHANGE, "tron sr set change .", "action", "witness");
1718
init(MetricKeys.Counter.P2P_ERROR, "tron p2p error info .", "type");
1819
init(MetricKeys.Counter.P2P_DISCONNECT, "tron p2p disconnect .", "type");
1920
init(MetricKeys.Counter.INTERNAL_SERVICE_FAIL, "internal Service fail.",

common/src/main/java/org/tron/common/prometheus/MetricsHistogram.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class MetricsHistogram {
2020
init(MetricKeys.Histogram.JSONRPC_SERVICE_LATENCY, "JsonRpc Service latency.",
2121
"method");
2222
init(MetricKeys.Histogram.MINER_LATENCY, "miner latency.",
23-
"miner");
23+
MetricLabels.Histogram.MINER);
2424
init(MetricKeys.Histogram.PING_PONG_LATENCY, "node ping pong latency.");
2525
init(MetricKeys.Histogram.VERIFY_SIGN_LATENCY, "verify sign latency for trx , block.",
2626
"type");
@@ -36,7 +36,7 @@ public class MetricsHistogram {
3636
init(MetricKeys.Histogram.PROCESS_TRANSACTION_LATENCY, "process transaction latency.",
3737
"type", "contract");
3838
init(MetricKeys.Histogram.MINER_DELAY, "miner delay time, actualTime - planTime.",
39-
"miner");
39+
MetricLabels.Histogram.MINER);
4040
init(MetricKeys.Histogram.UDP_BYTES, "udp_bytes traffic.",
4141
"type");
4242
init(MetricKeys.Histogram.TCP_BYTES, "tcp_bytes traffic.",
@@ -48,6 +48,11 @@ public class MetricsHistogram {
4848
init(MetricKeys.Histogram.BLOCK_FETCH_LATENCY, "fetch block latency.");
4949
init(MetricKeys.Histogram.BLOCK_RECEIVE_DELAY,
5050
"receive block delay time, receiveTime - blockTime.");
51+
52+
init(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT,
53+
"Distribution of transaction counts per block.",
54+
new double[]{0, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000},
55+
MetricLabels.Histogram.MINER);
5156
}
5257

5358
private MetricsHistogram() {
@@ -62,6 +67,17 @@ private static void init(String name, String help, String... labels) {
6267
.register());
6368
}
6469

70+
private static void init(String name, String help, double[] buckets, String... labels) {
71+
Histogram.Builder builder = Histogram.build()
72+
.name(name)
73+
.help(help)
74+
.labelNames(labels);
75+
if (buckets != null && buckets.length > 0) {
76+
builder.buckets(buckets);
77+
}
78+
container.put(name, builder.register());
79+
}
80+
6581
static Histogram.Timer startTimer(String key, String... labels) {
6682
if (Metrics.enabled()) {
6783
Histogram histogram = container.get(key);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.tron.common.prometheus;
2+
3+
import com.google.protobuf.ByteString;
4+
import java.util.List;
5+
import org.tron.common.utils.StringUtil;
6+
7+
public class SRMetrics {
8+
9+
private SRMetrics() {
10+
throw new IllegalStateException("SRMetrics");
11+
}
12+
13+
public static void recordSrSetChange(List<ByteString> currentWits, List<ByteString> newWits) {
14+
if (!Metrics.enabled()) {
15+
return;
16+
}
17+
newWits.stream()
18+
.filter(w -> !currentWits.contains(w))
19+
.forEach(w -> Metrics.counterInc(MetricKeys.Counter.SR_SET_CHANGE, 1,
20+
MetricLabels.Counter.SR_ADD, StringUtil.encode58Check(w.toByteArray())));
21+
currentWits.stream()
22+
.filter(w -> !newWits.contains(w))
23+
.forEach(w -> Metrics.counterInc(MetricKeys.Counter.SR_SET_CHANGE, 1,
24+
MetricLabels.Counter.SR_REMOVE, StringUtil.encode58Check(w.toByteArray())));
25+
}
26+
}

consensus/src/main/java/org/tron/consensus/dpos/MaintenanceManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.bouncycastle.util.encoders.Hex;
1717
import org.springframework.beans.factory.annotation.Autowired;
1818
import org.springframework.stereotype.Component;
19+
import org.tron.common.prometheus.SRMetrics;
1920
import org.tron.consensus.ConsensusDelegate;
2021
import org.tron.consensus.pbft.PbftManager;
2122
import org.tron.core.capsule.AccountCapsule;
@@ -141,6 +142,8 @@ public void doMaintenance() {
141142
witnessCapsule.setIsJobs(true);
142143
consensusDelegate.saveWitness(witnessCapsule);
143144
});
145+
146+
SRMetrics.recordSrSetChange(currentWits, newWits);
144147
}
145148

146149
logger.info("Update witness success. \nbefore: {} \nafter: {}",

framework/src/main/java/org/tron/core/db/Manager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,11 @@ public void pushBlock(final BlockCapsule block)
12701270
synchronized (this) {
12711271
Metrics.histogramObserve(blockedTimer.get());
12721272
blockedTimer.remove();
1273+
if (Metrics.enabled()) {
1274+
Metrics.histogramObserve(MetricKeys.Histogram.BLOCK_TRANSACTION_COUNT,
1275+
block.getTransactions().size(),
1276+
StringUtil.encode58Check(block.getWitnessAddress().toByteArray()));
1277+
}
12731278
long headerNumber = getDynamicPropertiesStore().getLatestBlockHeaderNumber();
12741279
if (block.getNum() <= headerNumber && khaosDb.containBlockInMiniStore(block.getBlockId())) {
12751280
logger.info("Block {} is already exist.", block.getBlockId().getString());

framework/src/main/java/org/tron/core/metrics/blockchain/BlockChainMetricManager.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,10 @@ public void applyBlock(BlockCapsule block) {
164164
}
165165

166166
//TPS
167-
if (block.getTransactions().size() > 0) {
168-
MetricsUtil.meterMark(MetricsKey.BLOCKCHAIN_TPS, block.getTransactions().size());
169-
Metrics.counterInc(MetricKeys.Counter.TXS, block.getTransactions().size(),
167+
int txCount = block.getTransactions().size();
168+
if (txCount > 0) {
169+
MetricsUtil.meterMark(MetricsKey.BLOCKCHAIN_TPS, txCount);
170+
Metrics.counterInc(MetricKeys.Counter.TXS, txCount,
170171
MetricLabels.Counter.TXS_SUCCESS, MetricLabels.Counter.TXS_SUCCESS);
171172
}
172173
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package org.tron.common.prometheus;
2+
3+
import com.google.protobuf.ByteString;
4+
import io.prometheus.client.CollectorRegistry;
5+
import java.util.Arrays;
6+
import java.util.Collections;
7+
import java.util.concurrent.atomic.AtomicInteger;
8+
import javax.annotation.Resource;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.junit.After;
11+
import org.junit.Assert;
12+
import org.junit.Before;
13+
import org.junit.Test;
14+
import org.tron.common.BaseTest;
15+
import org.tron.common.TestConstants;
16+
import org.tron.common.utils.StringUtil;
17+
import org.tron.consensus.dpos.MaintenanceManager;
18+
import org.tron.core.capsule.AccountCapsule;
19+
import org.tron.core.capsule.VotesCapsule;
20+
import org.tron.core.capsule.WitnessCapsule;
21+
import org.tron.core.config.args.Args;
22+
import org.tron.core.consensus.ConsensusService;
23+
import org.tron.protos.Protocol;
24+
import org.tron.protos.Protocol.Vote;
25+
26+
@Slf4j(topic = "metric")
27+
public class SRMetricsTest extends BaseTest {
28+
29+
private static final AtomicInteger PORT = new AtomicInteger(0);
30+
private static final AtomicInteger UNIQUE = new AtomicInteger(0);
31+
32+
@Resource
33+
private MaintenanceManager maintenanceManager;
34+
@Resource
35+
private ConsensusService consensusService;
36+
37+
static {
38+
Args.setParam(new String[]{"-d", dbPath()}, TestConstants.TEST_CONF);
39+
Args.getInstance().setNodeListenPort(20000 + PORT.incrementAndGet());
40+
Args.getInstance().setMetricsPrometheusEnable(true);
41+
Metrics.init();
42+
}
43+
44+
@Before
45+
public void setUp() {
46+
Args.getInstance().setMetricsPrometheusEnable(true);
47+
consensusService.start();
48+
}
49+
50+
@After
51+
public void tearDown() {
52+
Args.getInstance().setMetricsPrometheusEnable(true);
53+
}
54+
55+
/**
56+
* Drive the full maintenance flow: starting with a single active witness while WitnessStore
57+
* contains additional ones, doMaintenance() should expand active witnesses to the full set and
58+
* emit SR_ADD for each newly active witness.
59+
*/
60+
@Test
61+
public void testSrAddViaMaintenance() {
62+
ByteString stableWit = registerWitness();
63+
ByteString newWit1 = registerWitness();
64+
ByteString newWit2 = registerWitness();
65+
66+
chainBaseManager.getWitnessScheduleStore()
67+
.saveActiveWitnesses(Collections.singletonList(stableWit));
68+
69+
seedVote(stableWit);
70+
71+
maintenanceManager.doMaintenance();
72+
73+
Assert.assertEquals(1, sample(MetricLabels.Counter.SR_ADD, newWit1).intValue());
74+
Assert.assertEquals(1, sample(MetricLabels.Counter.SR_ADD, newWit2).intValue());
75+
Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, stableWit));
76+
Assert.assertNull(sample(MetricLabels.Counter.SR_REMOVE, stableWit));
77+
}
78+
79+
/**
80+
* Active witness set already matches WitnessStore → no metric emitted.
81+
*/
82+
@Test
83+
public void testNoMetricWhenSetUnchanged() {
84+
ByteString witA = registerWitness();
85+
ByteString witB = registerWitness();
86+
87+
chainBaseManager.getWitnessScheduleStore()
88+
.saveActiveWitnesses(Arrays.asList(witA, witB));
89+
90+
seedVote(witA);
91+
92+
maintenanceManager.doMaintenance();
93+
94+
Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, witA));
95+
Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, witB));
96+
Assert.assertNull(sample(MetricLabels.Counter.SR_REMOVE, witA));
97+
Assert.assertNull(sample(MetricLabels.Counter.SR_REMOVE, witB));
98+
}
99+
100+
/**
101+
* Empty VotesStore → countVote() is empty → SR change check is skipped, even when the active
102+
* set differs from the full witness store.
103+
*/
104+
@Test
105+
public void testNoMetricWhenNoVotes() {
106+
ByteString stableWit = registerWitness();
107+
ByteString newWit = registerWitness();
108+
109+
chainBaseManager.getWitnessScheduleStore()
110+
.saveActiveWitnesses(Collections.singletonList(stableWit));
111+
112+
maintenanceManager.doMaintenance();
113+
114+
Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, newWit));
115+
}
116+
117+
/**
118+
* Metrics disabled → record() short-circuits even though the active set changes.
119+
*/
120+
@Test
121+
public void testNoMetricWhenMetricsDisabled() {
122+
Args.getInstance().setMetricsPrometheusEnable(false);
123+
try {
124+
ByteString stableWit = registerWitness();
125+
ByteString newWit = registerWitness();
126+
127+
chainBaseManager.getWitnessScheduleStore()
128+
.saveActiveWitnesses(Collections.singletonList(stableWit));
129+
130+
seedVote(stableWit);
131+
132+
maintenanceManager.doMaintenance();
133+
134+
Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, newWit));
135+
} finally {
136+
Args.getInstance().setMetricsPrometheusEnable(true);
137+
}
138+
}
139+
140+
/**
141+
* SR_REMOVE is verified by directly calling record() instead of going through doMaintenance(),
142+
* because driving a removal through the real flow is impractical here:
143+
*
144+
* <p>Inside doMaintenance(), the block before SRMetrics.recordSrSetChange() iterates currentWits
145+
* and calls setIsJobs(false) on each WitnessCapsule fetched from WitnessStore. If currentWits
146+
* contains any address that is not present in WitnessStore, getWitness() returns null and the
147+
* code NPEs — so SR_REMOVE cannot be triggered by simply pointing the active set at an
148+
* "obsolete" address.
149+
*
150+
* <p>The only other path to SR_REMOVE is rank-based eviction: with more than
151+
* MAX_ACTIVE_WITNESS_NUM (27) witnesses, sorting drops the lowest-ranked one. Building that
152+
* setup just to exercise this branch is heavy and adds little value, since SR_ADD and
153+
* SR_REMOVE share the exact same emit logic in record() — verifying SR_ADD via doMaintenance
154+
* already proves the wiring is correct, and this direct call covers the symmetric branch.
155+
*/
156+
@Test
157+
public void testSrRemoveDirect() {
158+
ByteString stableWit = uniqueAddress();
159+
ByteString removedWit = uniqueAddress();
160+
161+
SRMetrics.recordSrSetChange(
162+
Arrays.asList(stableWit, removedWit),
163+
Collections.singletonList(stableWit));
164+
165+
Assert.assertEquals(1, sample(MetricLabels.Counter.SR_REMOVE, removedWit).intValue());
166+
Assert.assertNull(sample(MetricLabels.Counter.SR_ADD, removedWit));
167+
Assert.assertNull(sample(MetricLabels.Counter.SR_REMOVE, stableWit));
168+
}
169+
170+
private ByteString registerWitness() {
171+
ByteString address = uniqueAddress();
172+
chainBaseManager.getWitnessStore().put(address.toByteArray(), new WitnessCapsule(address));
173+
chainBaseManager.addWitness(address);
174+
chainBaseManager.getAccountStore().put(address.toByteArray(),
175+
new AccountCapsule(Protocol.Account.newBuilder().setAddress(address).build()));
176+
return address;
177+
}
178+
179+
private void seedVote(ByteString voteFor) {
180+
ByteString voter = uniqueAddress();
181+
VotesCapsule votes = new VotesCapsule(voter, Collections.emptyList(),
182+
Collections.singletonList(Vote.newBuilder()
183+
.setVoteAddress(voteFor)
184+
.setVoteCount(1L)
185+
.build()));
186+
chainBaseManager.getVotesStore().put(voter.toByteArray(), votes);
187+
}
188+
189+
private ByteString uniqueAddress() {
190+
int n = UNIQUE.incrementAndGet();
191+
byte[] bytes = new byte[21];
192+
bytes[0] = 0x41;
193+
bytes[17] = (byte) ((n >> 16) & 0xFF);
194+
bytes[18] = (byte) ((n >> 8) & 0xFF);
195+
bytes[19] = (byte) (n & 0xFF);
196+
bytes[20] = 0x01;
197+
return ByteString.copyFrom(bytes);
198+
}
199+
200+
private Double sample(String action, ByteString witness) {
201+
return CollectorRegistry.defaultRegistry.getSampleValue(
202+
MetricKeys.Counter.SR_SET_CHANGE + "_total",
203+
new String[]{"action", "witness"},
204+
new String[]{action, StringUtil.encode58Check(witness.toByteArray())});
205+
}
206+
}

0 commit comments

Comments
 (0)