Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.17: Fix - LoadedPrograms statistics (backport of #35026) #35050

Merged
merged 1 commit into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading