Add op pool metrics for attestations (#2758)

## Proposed Changes

Add several metrics for the number of attestations in the op pool. These give us a way to observe the number of valid, non-trivial attestations during block packing rather than just the size of the entire op pool.
This commit is contained in:
Michael Sproul 2021-11-01 05:52:31 +00:00
parent e8a557fdd8
commit ffb04e1a9e
4 changed files with 100 additions and 25 deletions

View File

@ -365,6 +365,10 @@ lazy_static! {
*/ */
pub static ref OP_POOL_NUM_ATTESTATIONS: Result<IntGauge> = pub static ref OP_POOL_NUM_ATTESTATIONS: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_attestations_total", "Count of attestations in the op pool"); try_create_int_gauge("beacon_op_pool_attestations_total", "Count of attestations in the op pool");
pub static ref OP_POOL_NUM_ATTESTATION_DATA: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_attestation_data_total", "Count of attestation data in the op pool");
pub static ref OP_POOL_MAX_AGGREGATES_PER_DATA: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_max_aggregates_per_data", "Max aggregates per AttestationData");
pub static ref OP_POOL_NUM_ATTESTER_SLASHINGS: Result<IntGauge> = pub static ref OP_POOL_NUM_ATTESTER_SLASHINGS: Result<IntGauge> =
try_create_int_gauge("beacon_op_pool_attester_slashings_total", "Count of attester slashings in the op pool"); try_create_int_gauge("beacon_op_pool_attester_slashings_total", "Count of attester slashings in the op pool");
pub static ref OP_POOL_NUM_PROPOSER_SLASHINGS: Result<IntGauge> = pub static ref OP_POOL_NUM_PROPOSER_SLASHINGS: Result<IntGauge> =
@ -886,9 +890,19 @@ pub fn scrape_for_metrics<T: BeaconChainTypes>(beacon_chain: &BeaconChain<T>) {
scrape_sync_committee_observation(slot, beacon_chain); scrape_sync_committee_observation(slot, beacon_chain);
} }
let attestation_stats = beacon_chain.op_pool.attestation_stats();
set_gauge_by_usize( set_gauge_by_usize(
&OP_POOL_NUM_ATTESTATIONS, &OP_POOL_NUM_ATTESTATIONS,
beacon_chain.op_pool.num_attestations(), attestation_stats.num_attestations,
);
set_gauge_by_usize(
&OP_POOL_NUM_ATTESTATION_DATA,
attestation_stats.num_attestation_data,
);
set_gauge_by_usize(
&OP_POOL_MAX_AGGREGATES_PER_DATA,
attestation_stats.max_aggregates_per_data,
); );
set_gauge_by_usize( set_gauge_by_usize(
&OP_POOL_NUM_ATTESTER_SLASHINGS, &OP_POOL_NUM_ATTESTER_SLASHINGS,

View File

@ -57,6 +57,15 @@ pub enum OpPoolError {
IncorrectOpPoolVariant, IncorrectOpPoolVariant,
} }
pub struct AttestationStats {
/// Total number of attestations for all committeees/indices/votes.
pub num_attestations: usize,
/// Number of unique `AttestationData` attested to.
pub num_attestation_data: usize,
/// Maximum number of aggregates for a single `AttestationData`.
pub max_aggregates_per_data: usize,
}
impl From<SyncAggregateError> for OpPoolError { impl From<SyncAggregateError> for OpPoolError {
fn from(e: SyncAggregateError) -> Self { fn from(e: SyncAggregateError) -> Self {
OpPoolError::SyncAggregateError(e) OpPoolError::SyncAggregateError(e)
@ -207,6 +216,23 @@ impl<T: EthSpec> OperationPool<T> {
self.attestations.read().values().map(Vec::len).sum() self.attestations.read().values().map(Vec::len).sum()
} }
pub fn attestation_stats(&self) -> AttestationStats {
let mut num_attestations = 0;
let mut num_attestation_data = 0;
let mut max_aggregates_per_data = 0;
for aggregates in self.attestations.read().values() {
num_attestations += aggregates.len();
num_attestation_data += 1;
max_aggregates_per_data = std::cmp::max(max_aggregates_per_data, aggregates.len());
}
AttestationStats {
num_attestations,
num_attestation_data,
max_aggregates_per_data,
}
}
/// Return all valid attestations for the given epoch, for use in max cover. /// Return all valid attestations for the given epoch, for use in max cover.
fn get_valid_attestations_for_epoch<'a>( fn get_valid_attestations_for_epoch<'a>(
&'a self, &'a self,
@ -265,22 +291,29 @@ impl<T: EthSpec> OperationPool<T> {
// Split attestations for the previous & current epochs, so that we // Split attestations for the previous & current epochs, so that we
// can optimise them individually in parallel. // can optimise them individually in parallel.
let prev_epoch_att = self.get_valid_attestations_for_epoch( let mut num_prev_valid = 0_i64;
prev_epoch, let mut num_curr_valid = 0_i64;
&*all_attestations,
state, let prev_epoch_att = self
total_active_balance, .get_valid_attestations_for_epoch(
prev_epoch_validity_filter, prev_epoch,
spec, &*all_attestations,
); state,
let curr_epoch_att = self.get_valid_attestations_for_epoch( total_active_balance,
current_epoch, prev_epoch_validity_filter,
&*all_attestations, spec,
state, )
total_active_balance, .inspect(|_| num_prev_valid += 1);
curr_epoch_validity_filter, let curr_epoch_att = self
spec, .get_valid_attestations_for_epoch(
); current_epoch,
&*all_attestations,
state,
total_active_balance,
curr_epoch_validity_filter,
spec,
)
.inspect(|_| num_curr_valid += 1);
let prev_epoch_limit = if let BeaconState::Base(base_state) = state { let prev_epoch_limit = if let BeaconState::Base(base_state) = state {
std::cmp::min( std::cmp::min(
@ -299,15 +332,22 @@ impl<T: EthSpec> OperationPool<T> {
if prev_epoch == current_epoch { if prev_epoch == current_epoch {
vec![] vec![]
} else { } else {
maximum_cover(prev_epoch_att, prev_epoch_limit) maximum_cover(prev_epoch_att, prev_epoch_limit, "prev_epoch_attestations")
} }
}, },
move || { move || {
let _timer = metrics::start_timer(&metrics::ATTESTATION_CURR_EPOCH_PACKING_TIME); let _timer = metrics::start_timer(&metrics::ATTESTATION_CURR_EPOCH_PACKING_TIME);
maximum_cover(curr_epoch_att, T::MaxAttestations::to_usize()) maximum_cover(
curr_epoch_att,
T::MaxAttestations::to_usize(),
"curr_epoch_attestations",
)
}, },
); );
metrics::set_gauge(&metrics::NUM_PREV_EPOCH_ATTESTATIONS, num_prev_valid);
metrics::set_gauge(&metrics::NUM_CURR_EPOCH_ATTESTATIONS, num_curr_valid);
Ok(max_cover::merge_solutions( Ok(max_cover::merge_solutions(
curr_cover, curr_cover,
prev_cover, prev_cover,
@ -394,6 +434,7 @@ impl<T: EthSpec> OperationPool<T> {
let attester_slashings = maximum_cover( let attester_slashings = maximum_cover(
relevant_attester_slashings, relevant_attester_slashings,
T::MaxAttesterSlashings::to_usize(), T::MaxAttesterSlashings::to_usize(),
"attester_slashings",
) )
.into_iter() .into_iter()
.map(|cover| { .map(|cover| {

View File

@ -1,3 +1,4 @@
use crate::metrics;
use itertools::Itertools; use itertools::Itertools;
/// Trait for types that we can compute a maximum cover for. /// Trait for types that we can compute a maximum cover for.
@ -44,7 +45,7 @@ impl<T> MaxCoverItem<T> {
/// ///
/// * Time complexity: `O(limit * items_iter.len())` /// * Time complexity: `O(limit * items_iter.len())`
/// * Space complexity: `O(item_iter.len())` /// * Space complexity: `O(item_iter.len())`
pub fn maximum_cover<I, T>(items_iter: I, limit: usize) -> Vec<T> pub fn maximum_cover<I, T>(items_iter: I, limit: usize, label: &str) -> Vec<T>
where where
I: IntoIterator<Item = T>, I: IntoIterator<Item = T>,
T: MaxCover, T: MaxCover,
@ -56,6 +57,12 @@ where
.filter(|x| x.item.score() != 0) .filter(|x| x.item.score() != 0)
.collect(); .collect();
metrics::set_int_gauge(
&metrics::MAX_COVER_NON_ZERO_ITEMS,
&[label],
all_items.len() as i64,
);
let mut result = vec![]; let mut result = vec![];
for _ in 0..limit { for _ in 0..limit {
@ -146,14 +153,14 @@ mod test {
#[test] #[test]
fn zero_limit() { fn zero_limit() {
let cover = maximum_cover(example_system(), 0); let cover = maximum_cover(example_system(), 0, "test");
assert_eq!(cover.len(), 0); assert_eq!(cover.len(), 0);
} }
#[test] #[test]
fn one_limit() { fn one_limit() {
let sets = example_system(); let sets = example_system();
let cover = maximum_cover(sets.clone(), 1); let cover = maximum_cover(sets.clone(), 1, "test");
assert_eq!(cover.len(), 1); assert_eq!(cover.len(), 1);
assert_eq!(cover[0], sets[1]); assert_eq!(cover[0], sets[1]);
} }
@ -163,7 +170,7 @@ mod test {
fn exclude_zero_score() { fn exclude_zero_score() {
let sets = example_system(); let sets = example_system();
for k in 2..10 { for k in 2..10 {
let cover = maximum_cover(sets.clone(), k); let cover = maximum_cover(sets.clone(), k, "test");
assert_eq!(cover.len(), 2); assert_eq!(cover.len(), 2);
assert_eq!(cover[0], sets[1]); assert_eq!(cover[0], sets[1]);
assert_eq!(cover[1], sets[0]); assert_eq!(cover[1], sets[0]);
@ -187,7 +194,7 @@ mod test {
HashSet::from_iter(vec![5, 6, 7, 8]), // 4, 4* HashSet::from_iter(vec![5, 6, 7, 8]), // 4, 4*
HashSet::from_iter(vec![0, 1, 2, 3, 4]), // 5* HashSet::from_iter(vec![0, 1, 2, 3, 4]), // 5*
]; ];
let cover = maximum_cover(sets, 3); let cover = maximum_cover(sets, 3, "test");
assert_eq!(quality(&cover), 11); assert_eq!(quality(&cover), 11);
} }
@ -202,7 +209,7 @@ mod test {
HashSet::from_iter(vec![1, 5, 6, 8]), HashSet::from_iter(vec![1, 5, 6, 8]),
HashSet::from_iter(vec![1, 7, 11, 19]), HashSet::from_iter(vec![1, 7, 11, 19]),
]; ];
let cover = maximum_cover(sets, 5); let cover = maximum_cover(sets, 5, "test");
assert_eq!(quality(&cover), 19); assert_eq!(quality(&cover), 19);
assert_eq!(cover.len(), 5); assert_eq!(cover.len(), 5);
} }

View File

@ -11,4 +11,17 @@ lazy_static! {
"op_pool_attestation_curr_epoch_packing_time", "op_pool_attestation_curr_epoch_packing_time",
"Time to pack current epoch attestations" "Time to pack current epoch attestations"
); );
pub static ref NUM_PREV_EPOCH_ATTESTATIONS: Result<IntGauge> = try_create_int_gauge(
"op_pool_prev_epoch_attestations",
"Number of valid attestations considered for packing from the previous epoch"
);
pub static ref NUM_CURR_EPOCH_ATTESTATIONS: Result<IntGauge> = try_create_int_gauge(
"op_pool_curr_epoch_attestations",
"Number of valid attestations considered for packing from the current epoch"
);
pub static ref MAX_COVER_NON_ZERO_ITEMS: Result<IntGaugeVec> = try_create_int_gauge_vec(
"op_pool_max_cover_non_zero_items",
"Number of non-trivial items considered in a max coverage optimisation",
&["label"]
);
} }