From a4621d8cd9b98cb6b03e2dead72f1f5bd7c1dd8c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:01:12 +0100 Subject: [PATCH] v1.17: Fix - LoadedPrograms statistics (backport of #35026) (#35050) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix - LoadedPrograms statistics (#35026) Fixes hits, misses. Adds reloads, lost_insertions. Removes prunes_expired. (cherry picked from commit 5dd9609aeae4d302298a23005291675feca88a25) Co-authored-by: Alexander Meißner --- program-runtime/src/loaded_programs.rs | 117 ++++++++++++++++--------- runtime/src/bank.rs | 4 +- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index dc6dcd9b03c798..f353e361bb6b2c 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -143,17 +143,30 @@ pub struct LoadedProgram { pub ix_usage_counter: AtomicU64, } +/// Global cache statistics for [LoadedPrograms]. #[derive(Debug, Default)] pub struct Stats { + /// a program was already in the cache pub hits: AtomicU64, + /// a program was not found and loaded instead pub misses: AtomicU64, + /// a compiled executable was unloaded pub evictions: HashMap, + /// an unloaded program was loaded again (opposite of eviction) + pub reloads: AtomicU64, + /// a program was loaded or un/re/deployed pub insertions: AtomicU64, + /// a program was loaded but can not be extracted on its own fork anymore + pub lost_insertions: AtomicU64, + /// a program which was already in the cache was reloaded by mistake pub replacements: AtomicU64, + /// a program was only used once before being unloaded pub one_hit_wonders: AtomicU64, + /// a program became unreachable in the fork graph because of rerooting pub prunes_orphan: AtomicU64, - pub prunes_expired: AtomicU64, + /// a program got pruned because it was not recompiled for the next epoch pub prunes_environment: AtomicU64, + /// the [SecondLevel] was empty because all slot versions got pruned pub empty_entries: AtomicU64, } @@ -162,12 +175,13 @@ impl Stats { pub fn submit(&self, slot: Slot) { let hits = self.hits.load(Ordering::Relaxed); let misses = self.misses.load(Ordering::Relaxed); + let evictions: u64 = self.evictions.values().sum(); + let reloads = self.reloads.load(Ordering::Relaxed); let insertions = self.insertions.load(Ordering::Relaxed); + let lost_insertions = self.insertions.load(Ordering::Relaxed); let replacements = self.replacements.load(Ordering::Relaxed); let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed); - let evictions: u64 = self.evictions.values().sum(); let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed); - let prunes_expired = self.prunes_expired.load(Ordering::Relaxed); let prunes_environment = self.prunes_environment.load(Ordering::Relaxed); let empty_entries = self.empty_entries.load(Ordering::Relaxed); datapoint_info!( @@ -176,17 +190,18 @@ impl Stats { ("hits", hits, i64), ("misses", misses, i64), ("evictions", evictions, i64), + ("reloads", reloads, i64), ("insertions", insertions, i64), + ("lost_insertions", lost_insertions, i64), ("replacements", replacements, i64), ("one_hit_wonders", one_hit_wonders, i64), ("prunes_orphan", prunes_orphan, i64), - ("prunes_expired", prunes_expired, i64), ("prunes_environment", prunes_environment, i64), ("empty_entries", empty_entries, i64), ); debug!( - "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Prunes-Orphan: {}, Prunes-Expired: {}, Prunes-Environment: {}, Empty: {}", - hits, misses, evictions, insertions, replacements, one_hit_wonders, prunes_orphan, prunes_expired, prunes_environment, empty_entries + "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Reloads: {}, Insertions: {} Lost-Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Prunes-Orphan: {}, Prunes-Environment: {}, Empty: {}", + hits, misses, evictions, reloads, insertions, lost_insertions, replacements, one_hit_wonders, prunes_orphan, prunes_environment, empty_entries ); if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() { let mut evictions = self.evictions.iter().collect::>(); @@ -640,9 +655,7 @@ impl LoadedPrograms { let index = slot_versions .iter() .position(|at| at.effective_slot >= entry.effective_slot); - if let Some((existing, entry_index)) = - index.and_then(|index| slot_versions.get(index).map(|value| (value, index))) - { + if let Some(existing) = index.and_then(|index| slot_versions.get_mut(index)) { if existing.deployment_slot == entry.deployment_slot && existing.effective_slot == entry.effective_slot { @@ -657,17 +670,19 @@ impl LoadedPrograms { existing.ix_usage_counter.load(Ordering::Relaxed), Ordering::Relaxed, ); - slot_versions.remove(entry_index); + self.stats.reloads.fetch_add(1, Ordering::Relaxed); } else if existing.is_tombstone() != entry.is_tombstone() { // Either the old entry is tombstone and the new one is not. // (Let's give the new entry a chance). // Or, the old entry is not a tombstone and the new one is a tombstone. // (Remove the old entry, as the tombstone makes it obsolete). - slot_versions.remove(entry_index); + self.stats.insertions.fetch_add(1, Ordering::Relaxed); } else { self.stats.replacements.fetch_add(1, Ordering::Relaxed); return (true, existing.clone()); } + *existing = entry.clone(); + return (false, entry); } } self.stats.insertions.fetch_add(1, Ordering::Relaxed); @@ -757,7 +772,6 @@ impl LoadedPrograms { // Remove expired if let Some(expiration) = entry.maybe_expiration_slot { if expiration <= new_root_slot { - self.stats.prunes_expired.fetch_add(1, Ordering::Relaxed); return false; } } @@ -831,6 +845,7 @@ impl LoadedPrograms { working_slot: &S, search_for: &mut Vec<(Pubkey, (LoadedProgramMatchCriteria, u64))>, loaded_programs_for_tx_batch: &mut LoadedProgramsForTxBatch, + is_first_round: bool, ) -> Option<(Pubkey, u64)> { let mut cooperative_loading_task = None; search_for.retain(|(key, (match_criteria, usage_count))| { @@ -910,13 +925,15 @@ impl LoadedPrograms { } true }); - self.stats - .misses - .fetch_add(search_for.len() as u64, Ordering::Relaxed); - self.stats.hits.fetch_add( - loaded_programs_for_tx_batch.entries.len() as u64, - Ordering::Relaxed, - ); + if is_first_round { + self.stats + .misses + .fetch_add(search_for.len() as u64, Ordering::Relaxed); + self.stats.hits.fetch_add( + loaded_programs_for_tx_batch.entries.len() as u64, + Ordering::Relaxed, + ); + } cooperative_loading_task } @@ -933,6 +950,22 @@ impl LoadedPrograms { Some((slot, std::thread::current().id())) ); second_level.cooperative_loading_lock = None; + // Check that it will be visible to our own fork once inserted + if loaded_program.deployment_slot > self.latest_root_slot + && !self + .fork_graph + .as_ref() + .and_then(|fork_graph| fork_graph.read().ok()) + .map(|fork_graph_r| { + matches!( + fork_graph_r.relationship(loaded_program.deployment_slot, slot), + BlockRelation::Equal | BlockRelation::Ancestor + ) + }) + .unwrap_or(true) + { + self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed); + } self.assign_program(key, loaded_program); self.loading_task_waiter.notify(); } @@ -1804,7 +1837,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 4)), ]; let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone()); - cache.extract(&TestWorkingSlot(22), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(22), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 20, 22)); assert!(match_slot(&extracted, &program4, 0, 22)); @@ -1820,7 +1853,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(15, cache.environments.clone()); - cache.extract(&TestWorkingSlot(15), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(15), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 15)); assert!(match_slot(&extracted, &program2, 11, 15)); @@ -1843,7 +1876,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(18, cache.environments.clone()); - cache.extract(&TestWorkingSlot(18), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(18), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 18)); assert!(match_slot(&extracted, &program2, 11, 18)); @@ -1861,7 +1894,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone()); - cache.extract(&TestWorkingSlot(23), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(23), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 23)); assert!(match_slot(&extracted, &program2, 11, 23)); @@ -1879,7 +1912,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(11, cache.environments.clone()); - cache.extract(&TestWorkingSlot(11), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(11), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 11)); // program2 was updated at slot 11, but is not effective till slot 12. The result should contain a tombstone. @@ -1912,7 +1945,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(19, cache.environments.clone()); - cache.extract(&TestWorkingSlot(19), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(19), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 19)); assert!(match_slot(&extracted, &program2, 11, 19)); @@ -1930,7 +1963,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(21, cache.environments.clone()); - cache.extract(&TestWorkingSlot(21), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(21), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 21)); assert!(match_slot(&extracted, &program2, 11, 21)); @@ -1968,7 +2001,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(21, cache.environments.clone()); - cache.extract(&TestWorkingSlot(21), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(21), &mut missing, &mut extracted, true); // Since the fork was pruned, we should not find the entry deployed at slot 20. assert!(match_slot(&extracted, &program1, 0, 21)); @@ -1985,7 +2018,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone()); - cache.extract(&TestWorkingSlot(27), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(27), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 27)); assert!(match_slot(&extracted, &program2, 11, 27)); @@ -2017,7 +2050,7 @@ mod tests { (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone()); - cache.extract(&TestWorkingSlot(23), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(23), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 23)); assert!(match_slot(&extracted, &program2, 11, 23)); @@ -2072,7 +2105,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone()); - cache.extract(&TestWorkingSlot(12), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(12), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 12)); assert!(match_slot(&extracted, &program2, 11, 12)); @@ -2092,7 +2125,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone()); - cache.extract(&TestWorkingSlot(12), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(12), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program2, 11, 12)); @@ -2162,7 +2195,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(19, cache.environments.clone()); - cache.extract(&TestWorkingSlot(19), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(19), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 19)); assert!(match_slot(&extracted, &program2, 11, 19)); @@ -2176,7 +2209,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone()); - cache.extract(&TestWorkingSlot(27), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(27), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 27)); assert!(match_slot(&extracted, &program2, 11, 27)); @@ -2190,7 +2223,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone()); - cache.extract(&TestWorkingSlot(22), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(22), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 20, 22)); @@ -2254,7 +2287,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone()); - cache.extract(&TestWorkingSlot(12), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(12), &mut missing, &mut extracted, true); // Program1 deployed at slot 11 should not be expired yet assert!(match_slot(&extracted, &program1, 11, 12)); @@ -2270,7 +2303,7 @@ mod tests { (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(15, cache.environments.clone()); - cache.extract(&TestWorkingSlot(15), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(15), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program2, 11, 15)); @@ -2336,7 +2369,7 @@ mod tests { let mut missing = vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))]; let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); - cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted, true); // The cache should have the program deployed at slot 0 assert_eq!( @@ -2380,7 +2413,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); - cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 20)); assert!(match_slot(&extracted, &program2, 10, 20)); @@ -2390,7 +2423,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone()); - cache.extract(&TestWorkingSlot(6), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(6), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 5, 6)); assert!(match_missing(&missing, &program2, false)); @@ -2404,7 +2437,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); - cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 20)); assert!(match_slot(&extracted, &program2, 10, 20)); @@ -2414,7 +2447,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone()); - cache.extract(&TestWorkingSlot(6), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(6), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 6)); assert!(match_missing(&missing, &program2, false)); @@ -2428,7 +2461,7 @@ mod tests { (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), ]; let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone()); - cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted); + cache.extract(&TestWorkingSlot(20), &mut missing, &mut extracted, true); assert!(match_slot(&extracted, &program1, 0, 20)); assert!(match_missing(&missing, &program2, false)); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index f30b368480f609..b53f037db4c3d3 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -5081,7 +5081,8 @@ impl Bank { // Lock the global cache. let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap(); // Initialize our local cache. - if loaded_programs_for_txs.is_none() { + let is_first_round = loaded_programs_for_txs.is_none(); + if is_first_round { loaded_programs_for_txs = Some(LoadedProgramsForTxBatch::new( self.slot, loaded_programs_cache @@ -5102,6 +5103,7 @@ impl Bank { self, &mut missing_programs, loaded_programs_for_txs.as_mut().unwrap(), + is_first_round, ); let task_waiter = Arc::clone(&loaded_programs_cache.loading_task_waiter); (program_to_load, task_waiter.cookie(), task_waiter)