Skip to content

Commit

Permalink
v1.17: Fix - LoadedPrograms statistics (backport of #35026) (#35050)
Browse files Browse the repository at this point in the history
Fix - LoadedPrograms statistics (#35026)

Fixes hits, misses.
Adds reloads, lost_insertions.
Removes prunes_expired.

(cherry picked from commit 5dd9609)

Co-authored-by: Alexander Meißner <[email protected]>
  • Loading branch information
mergify[bot] and Lichtso authored Feb 5, 2024
1 parent b8ac425 commit a4621d8
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 43 deletions.
117 changes: 75 additions & 42 deletions program-runtime/src/loaded_programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pubkey, u64>,
/// 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,
}

Expand All @@ -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!(
Expand All @@ -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::<Vec<_>>();
Expand Down Expand Up @@ -640,9 +655,7 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
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
{
Expand All @@ -657,17 +670,19 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
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);
Expand Down Expand Up @@ -757,7 +772,6 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
// 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;
}
}
Expand Down Expand Up @@ -831,6 +845,7 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
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))| {
Expand Down Expand Up @@ -910,13 +925,15 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
}
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
}

Expand All @@ -933,6 +950,22 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {
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();
}
Expand Down Expand Up @@ -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));
Expand All @@ -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));
Expand All @@ -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));
Expand All @@ -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));
Expand All @@ -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.
Expand Down Expand Up @@ -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));
Expand All @@ -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));
Expand Down Expand Up @@ -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));
Expand All @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand All @@ -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));

Expand Down Expand Up @@ -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));
Expand All @@ -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));
Expand All @@ -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));

Expand Down Expand Up @@ -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));
Expand All @@ -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));

Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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));
Expand All @@ -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));
Expand All @@ -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));
Expand All @@ -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));
Expand All @@ -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));
Expand Down
4 changes: 3 additions & 1 deletion runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit a4621d8

Please sign in to comment.