Skip to content

Commit 86dfc95

Browse files
authored
Merge pull request #4199 from ProvableHQ/feat/individual-participation-metrics
[Feature] Export individual validator participation scores as gauges
2 parents c2f52ba + d2390e4 commit 86dfc95

7 files changed

Lines changed: 119 additions & 90 deletions

File tree

Cargo.lock

Lines changed: 61 additions & 61 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/bft/src/gateway.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,13 +1031,17 @@ impl<N: Network> Gateway<N> {
10311031
// Logs the validator participation scores.
10321032
#[cfg(feature = "telemetry")]
10331033
fn log_participation_scores(&self) {
1034-
if let Ok(current_committee) = self.ledger.current_committee() {
1034+
if let Ok(committee_lookback) = self.ledger.get_committee_lookback_for_round(self.storage.current_round()) {
10351035
// Retrieve the participation scores.
1036-
let participation_scores = self.validator_telemetry().get_participation_scores(&current_committee);
1036+
let participation_scores = self.validator_telemetry().get_participation_scores(&committee_lookback);
1037+
10371038
// Log the participation scores.
10381039
debug!("Participation Scores (in the last {} rounds):", self.storage.max_gc_rounds());
1039-
for (address, score) in participation_scores {
1040-
debug!("{}", format!(" {address} - {score:.2}%").dimmed());
1040+
for (address, (cert_score, sig_score)) in participation_scores {
1041+
debug!(
1042+
"{}",
1043+
format!(" {address} - certificates: {cert_score:.2}% signatures: {sig_score:.2}%").dimmed()
1044+
);
10411045
}
10421046
}
10431047
}

node/bft/src/helpers/telemetry.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,21 @@ impl<N: Network> Telemetry<N> {
8484
}
8585
}
8686

87-
// TODO (raychu86): Consider using committee lookback here.
88-
/// Fetch the participation scores for each validator in the committee set.
89-
pub fn get_participation_scores(&self, committee: &Committee<N>) -> IndexMap<Address<N>, f64> {
87+
/// Fetch the certificate and signature participation scores for each validator in the committee set.
88+
/// Returns a map of `address` to `(certificate_score, signature_score)`.
89+
pub fn get_participation_scores(&self, committee: &Committee<N>) -> IndexMap<Address<N>, (f64, f64)> {
9090
// Fetch the participation scores.
9191
let participation_scores = self.participation_scores.read();
92-
// Calculate the weighted score for each validator.
92+
// Return the individual scores for each validator.
9393
committee
9494
.members()
9595
.iter()
9696
.map(|(address, _)| {
97-
let score =
98-
participation_scores.get(address).map(|(_, _, combined_score)| *combined_score).unwrap_or(0.0);
99-
(*address, score)
97+
let scores = participation_scores
98+
.get(address)
99+
.map(|(cert_score, sig_score, _)| (*cert_score, *sig_score))
100+
.unwrap_or((0.0, 0.0));
101+
(*address, scores)
100102
})
101103
.collect()
102104
}
@@ -326,7 +328,7 @@ mod tests {
326328
let participation_scores = telemetry.get_participation_scores(&committee);
327329
assert_eq!(participation_scores.len(), committee.members().len());
328330
for (address, _) in committee.members() {
329-
assert_eq!(*participation_scores.get(address).unwrap(), 0.0);
331+
assert_eq!(*participation_scores.get(address).unwrap(), (0.0, 0.0));
330332
}
331333

332334
// Calculate the participation scores.
@@ -335,7 +337,8 @@ mod tests {
335337
// Ensure that the participation scores are updated.
336338
let participation_scores = telemetry.get_participation_scores(&committee);
337339
for (address, _) in committee.members() {
338-
assert!(*participation_scores.get(address).unwrap() > 0.0);
340+
let (cert_score, sig_score) = *participation_scores.get(address).unwrap();
341+
assert!(cert_score > 0.0 || sig_score > 0.0);
339342
}
340343

341344
println!("{participation_scores:?}");

node/consensus/src/lib.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -594,10 +594,10 @@ impl<N: Network> Consensus<N> {
594594
ledger_update.advance_to_next_block(&block)?;
595595

596596
#[cfg(feature = "telemetry")]
597-
// Fetch the latest committee.
597+
// Fetch the committee lookback for the latest round.
598598
// Note: Do not abort here if this returns an error, because the committee is only needed for telemetry,
599599
// not for block advancement itself.
600-
let latest_committee = self.ledger.current_committee();
600+
let latest_committee = self.ledger.get_committee_lookback_for_round(self.ledger.latest_round());
601601

602602
// If the next block starts a new epoch, clear the existing solutions.
603603
if block_height.is_multiple_of(N::NUM_BLOCKS_PER_EPOCH) {
@@ -650,22 +650,29 @@ impl<N: Network> Consensus<N> {
650650
#[cfg(feature = "telemetry")]
651651
match latest_committee {
652652
Ok(latest_committee) => {
653-
// Retrieve the latest participation scores.
653+
// Retrieve the individual participation scores.
654654
let participation_scores = self
655655
.bft()
656656
.primary()
657657
.gateway()
658658
.validator_telemetry()
659659
.get_participation_scores(&latest_committee);
660660

661-
// Log the participation scores.
662-
for (address, participation_score) in participation_scores {
663-
metrics::histogram_label(
664-
metrics::consensus::VALIDATOR_PARTICIPATION,
661+
// Export the certificate and signature participation scores as individual gauges.
662+
for (address, (certificate_score, signature_score)) in participation_scores {
663+
let address_str = address.to_string();
664+
metrics::gauge_label(
665+
metrics::consensus::VALIDATOR_CERTIFICATE_PARTICIPATION,
665666
"validator_address",
666-
address.to_string(),
667-
participation_score,
668-
)
667+
address_str.clone(),
668+
certificate_score,
669+
);
670+
metrics::gauge_label(
671+
metrics::consensus::VALIDATOR_SIGNATURE_PARTICIPATION,
672+
"validator_address",
673+
address_str,
674+
signature_score,
675+
);
669676
}
670677
}
671678
Err(err) => warn!("{}", flatten_error(err.context("Failed to get latest committee for telemetry"))),

node/metrics/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ pub fn add_transmission_latency_metric<N: Network>(
164164
}
165165
}
166166

167+
pub fn gauge_label<V: Into<f64>>(name: &'static str, label_key: &'static str, label_value: String, value: V) {
168+
::metrics::gauge!(name, label_key => label_value).set(value.into());
169+
}
170+
167171
// Include the generated build information
168172
mod built_info {
169173
include!(concat!(env!("OUT_DIR"), "/built.rs"));

node/metrics/src/names.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ pub mod consensus {
104104
pub const TRANSMISSION_LATENCY: &str = "snarkos_consensus_transmission_latency";
105105
pub const STALE_UNCONFIRMED_TRANSACTIONS: &str = "snarkos_consensus_stale_unconfirmed_transactions";
106106
pub const STALE_UNCONFIRMED_SOLUTIONS: &str = "snarkos_consensus_stale_unconfirmed_solutions";
107-
pub const VALIDATOR_PARTICIPATION: &str = "snarkos_consensus_validator_participation";
107+
pub const VALIDATOR_CERTIFICATE_PARTICIPATION: &str = "snarkos_consensus_validator_certificate_participation";
108+
pub const VALIDATOR_SIGNATURE_PARTICIPATION: &str = "snarkos_consensus_validator_signature_participation";
108109
}
109110

110111
pub mod router {

node/rest/src/routes.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,15 +1048,25 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
10481048
) -> Result<impl axum::response::IntoResponse, RestError> {
10491049
match rest.consensus {
10501050
Some(consensus) => {
1051-
// Retrieve the latest committee.
1052-
let latest_committee = rest.ledger.latest_committee()?;
1053-
// Retrieve the latest participation scores.
1054-
let participation_scores = consensus
1051+
// Retrieve the committee lookback for the latest round.
1052+
let latest_round = rest.ledger.latest_round();
1053+
let committee_lookback = rest
1054+
.ledger
1055+
.get_committee_lookback_for_round(latest_round)?
1056+
.ok_or_else(|| RestError::not_found(anyhow!("No committee found for round {latest_round}")))?;
1057+
// Retrieve the latest participation scores, combining certificate and signature scores.
1058+
let participation_scores: IndexMap<_, _> = consensus
10551059
.bft()
10561060
.primary()
10571061
.gateway()
10581062
.validator_telemetry()
1059-
.get_participation_scores(&latest_committee);
1063+
.get_participation_scores(&committee_lookback)
1064+
.into_iter()
1065+
.map(|(address, (cert_score, sig_score))| {
1066+
let combined = ((0.9 * cert_score + 0.1 * sig_score) * 100.0).round() / 100.0;
1067+
(address, combined)
1068+
})
1069+
.collect();
10601070

10611071
// Check if metadata is requested and return the participation scores with metadata if so.
10621072
if metadata.metadata.unwrap_or(false) {

0 commit comments

Comments
 (0)