1#[cfg(feature = "metrics")]
2use solana_svm_timings::ExecuteDetailsTimings;
3use {
4 crate::loaded_programs::ForkGraph,
5 log::{debug, log_enabled, trace},
6 solana_pubkey::Pubkey,
7 std::{
8 collections::HashMap,
9 sync::atomic::{AtomicU64, Ordering},
10 },
11};
12
13#[derive(Debug, Default)]
14pub struct ProgramStatistics {
15 pub uses: AtomicU64,
16
17 pub compilations: AtomicU64,
18 pub total_compilation_time_us: AtomicU64,
19 pub compilation_time_ema: AtomicU64,
21
22 pub jit_invocations: AtomicU64,
23 pub total_jit_execution_time_us: AtomicU64,
24 pub jit_execution_time_ema: AtomicU64,
26
27 pub interpreted_invocations: AtomicU64,
28 pub total_interpretation_time_us: AtomicU64,
29 pub interpretation_time_ema: AtomicU64,
31}
32
33const COMPILATION_EMA_WINDOW_SIZE: u64 = 10;
35const EXECUTION_EMA_WINDOW_SIZE: u64 = 500;
37pub(crate) const EMA_SCALE: u64 = 1_000;
41
42impl ProgramStatistics {
43 fn observe_ema<const WINDOW_SIZE: u64>(counter: &AtomicU64, duration_us: u64) {
44 let duration_ema = duration_us.saturating_mul(EMA_SCALE);
45 counter
46 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |ema| {
47 let (numer, denom) = const { (2, 1 + WINDOW_SIZE) };
62 Some(if ema == 0 {
63 duration_ema
64 } else {
65 let weighted_observation = duration_ema.saturating_mul(numer);
66 let previous_observations = ema.saturating_mul(denom.saturating_sub(numer));
67 weighted_observation
68 .saturating_add(previous_observations)
69 .checked_div(denom)
70 .expect("unreachable: denom is >= 1")
71 })
72 })
73 .expect("unreachable: closure always returns a Some");
74 }
75
76 pub fn jit_compiled(&self, duration_us: u64) {
78 let ord = Ordering::Relaxed;
79 self.compilations.fetch_add(1, ord);
80 self.total_compilation_time_us.fetch_add(duration_us, ord);
81 Self::observe_ema::<COMPILATION_EMA_WINDOW_SIZE>(&self.compilation_time_ema, duration_us);
82 }
83
84 pub fn jit_executed(&self, duration_us: u64) {
86 let ord = Ordering::Relaxed;
87 self.jit_invocations.fetch_add(1, ord);
88 self.total_jit_execution_time_us.fetch_add(duration_us, ord);
89 Self::observe_ema::<EXECUTION_EMA_WINDOW_SIZE>(&self.jit_execution_time_ema, duration_us);
90 }
91
92 pub fn interpreter_executed(&self, duration_us: u64) {
94 let ord = Ordering::Relaxed;
95 self.interpreted_invocations.fetch_add(1, ord);
96 self.total_interpretation_time_us
97 .fetch_add(duration_us, ord);
98 Self::observe_ema::<EXECUTION_EMA_WINDOW_SIZE>(&self.interpretation_time_ema, duration_us);
99 }
100
101 pub fn merge_from(&self, other: &ProgramStatistics) {
102 let ord = Ordering::Relaxed;
103 self.uses.fetch_add(other.uses.load(ord), ord);
104 let other_compilations = other.compilations.load(ord);
105 let this_compilations = self.compilations.fetch_add(other_compilations, ord);
106 self.total_compilation_time_us
107 .fetch_add(other.total_compilation_time_us.load(ord), ord);
108 let other_jit_invocations = other.jit_invocations.load(ord);
109 let this_jit_invocations = self.jit_invocations.fetch_add(other_jit_invocations, ord);
110 self.total_jit_execution_time_us
111 .fetch_add(other.total_jit_execution_time_us.load(ord), ord);
112 let other_interpretations = other.interpreted_invocations.load(ord);
113 let this_interpretations = self
114 .interpreted_invocations
115 .fetch_add(other_interpretations, ord);
116 self.total_interpretation_time_us
117 .fetch_add(other.total_interpretation_time_us.load(ord), ord);
118 if let Some(comp_ema) = ProgramCacheStats::combined_ema::<
119 COMPILATION_EMA_WINDOW_SIZE,
120 COMPILATION_EMA_WINDOW_SIZE,
121 >(
122 &self.compilation_time_ema,
123 &other.compilation_time_ema,
124 this_compilations,
125 other_compilations,
126 ) {
127 self.compilation_time_ema.store(comp_ema, ord);
128 }
129 if let Some(exec_ema) =
130 ProgramCacheStats::combined_ema::<EXECUTION_EMA_WINDOW_SIZE, EXECUTION_EMA_WINDOW_SIZE>(
131 &self.jit_execution_time_ema,
132 &other.jit_execution_time_ema,
133 this_jit_invocations,
134 other_jit_invocations,
135 )
136 {
137 self.jit_execution_time_ema.store(exec_ema, ord);
138 }
139 if let Some(interp_ema) =
140 ProgramCacheStats::combined_ema::<EXECUTION_EMA_WINDOW_SIZE, EXECUTION_EMA_WINDOW_SIZE>(
141 &self.interpretation_time_ema,
142 &other.interpretation_time_ema,
143 this_interpretations,
144 other_interpretations,
145 )
146 {
147 self.interpretation_time_ema.store(interp_ema, ord);
148 }
149 }
150}
151
152#[derive(Debug, Default)]
154pub struct ProgramCacheStats {
155 pub hits: AtomicU64,
157 pub misses: AtomicU64,
159 pub evictions: HashMap<Pubkey, u64>,
161 pub reloads: AtomicU64,
163 pub insertions: AtomicU64,
165 pub lost_insertions: AtomicU64,
167 pub replacements: AtomicU64,
169 pub one_hit_wonders: AtomicU64,
171 pub prunes_orphan: AtomicU64,
173 pub prunes_environment: AtomicU64,
175 pub empty_entries: AtomicU64,
177 pub water_level: AtomicU64,
179}
180
181impl ProgramCacheStats {
182 pub fn reset(&mut self) {
183 *self = ProgramCacheStats::default();
184 }
185 pub fn log(&self) {
186 let hits = self.hits.load(Ordering::Relaxed);
187 let misses = self.misses.load(Ordering::Relaxed);
188 let evictions: u64 = self.evictions.values().sum();
189 let reloads = self.reloads.load(Ordering::Relaxed);
190 let insertions = self.insertions.load(Ordering::Relaxed);
191 let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
192 let replacements = self.replacements.load(Ordering::Relaxed);
193 let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
194 let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed);
195 let prunes_environment = self.prunes_environment.load(Ordering::Relaxed);
196 let empty_entries = self.empty_entries.load(Ordering::Relaxed);
197 let water_level = self.water_level.load(Ordering::Relaxed);
198 debug!(
199 "Loaded Programs Cache Stats -- Hits: {hits}, Misses: {misses}, Evictions: \
200 {evictions}, Reloads: {reloads}, Insertions: {insertions}, Lost-Insertions: \
201 {lost_insertions}, Replacements: {replacements}, One-Hit-Wonders: {one_hit_wonders}, \
202 Prunes-Orphan: {prunes_orphan}, Prunes-Environment: {prunes_environment}, Empty: \
203 {empty_entries}, Water-Level: {water_level}"
204 );
205
206 if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
207 let mut evictions = self.evictions.iter().collect::<Vec<_>>();
208 evictions.sort_by_key(|e| e.1);
209 let evictions = evictions
210 .into_iter()
211 .rev()
212 .map(|(program_id, evictions)| {
213 format!(" {:<44} {}", program_id.to_string(), evictions)
214 })
215 .collect::<Vec<_>>();
216 let evictions = evictions.join("\n");
217 trace!(
218 "Eviction Details:\n {:<44} {}\n{}",
219 "Program", "Count", evictions
220 );
221 }
222 }
223
224 fn combined_ema<const WINDOW1: u64, const WINDOW2: u64>(
225 into_ema: &AtomicU64,
226 from_ema: &AtomicU64,
227 into_observations: u64,
228 from_observations: u64,
229 ) -> Option<u64> {
230 let other_ema_val = from_ema.load(Ordering::Relaxed);
234 let other_ema_weight = std::cmp::max(WINDOW1, from_observations);
235 let this_ema_val = into_ema.load(Ordering::Relaxed);
236 let this_ema_weight = std::cmp::max(WINDOW2, into_observations);
237 other_ema_val
238 .wrapping_mul(other_ema_weight)
239 .wrapping_add(this_ema_val.wrapping_mul(this_ema_weight))
240 .checked_div(other_ema_weight.wrapping_add(this_ema_weight))
241 }
242}
243
244#[cfg(feature = "metrics")]
245#[derive(Debug, Default)]
247pub struct LoadProgramMetrics {
248 pub program_id: String,
250 pub register_syscalls_us: u64,
252 pub load_elf_us: u64,
254 pub verify_code_us: u64,
256 pub jit_compile_us: u64,
258}
259
260#[cfg(feature = "metrics")]
261impl LoadProgramMetrics {
262 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
263 timings.create_executor_register_syscalls_us += self.register_syscalls_us;
264 timings.create_executor_load_elf_us += self.load_elf_us;
265 timings.create_executor_verify_code_us += self.verify_code_us;
266 timings.create_executor_jit_compile_us += self.jit_compile_us;
267 }
268}
269
270impl<FG: ForkGraph> crate::loaded_programs::ProgramCache<FG> {
271 #[cfg(feature = "dev-context-only-utils")]
273 pub fn output_entry_stats(&self) {
274 use {crate::program_cache_entry::ProgramCacheEntryType, std::fmt::Write};
275 static ENTRY_STAT_PATH: std::sync::LazyLock<Option<std::ffi::OsString>> =
278 std::sync::LazyLock::new(|| std::env::var_os("AGAVE_PROGRAM_CACHE_ENTRY_STATS_PATH"));
279 let Some(stat_path) = &*ENTRY_STAT_PATH else {
280 log::trace!("Set AGAVE_PROGRAM_CACHE_ENTRY_STATS_PATH to write per-entry stats");
281 return;
282 };
283 let mut output = String::new();
284 let entries = self.get_flattened_entries_for_tests();
285 for (addr, entry) in entries {
286 let entry_ty = match &entry.program {
287 ProgramCacheEntryType::FailedVerification(_) => "FailedVerification",
288 ProgramCacheEntryType::Closed => "Closed",
289 ProgramCacheEntryType::DelayVisibility => "DelayVisibility",
290 ProgramCacheEntryType::Unloaded(_) => "Unloaded",
291 ProgramCacheEntryType::Builtin(_) => "Builtin",
292 #[cfg(not(all(not(target_os = "windows"), target_arch = "x86_64")))]
293 ProgramCacheEntryType::Loaded(_) => "Loaded",
294 #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
295 ProgramCacheEntryType::Loaded(executable) => {
296 if executable.get_compiled_program().is_some() {
297 "JitCompiled"
298 } else {
299 "Loaded"
300 }
301 }
302 };
303 let stats = &entry.stats;
304 let uses = stats.uses.load(Ordering::Relaxed);
305 let compiles = stats.compilations.load(Ordering::Relaxed);
306 let comptime = stats.total_compilation_time_us.load(Ordering::Relaxed);
307 let comptime_ema = stats.compilation_time_ema.load(Ordering::Relaxed) / EMA_SCALE;
308 let invokes = stats.jit_invocations.load(Ordering::Relaxed);
309 let jittime = stats.total_jit_execution_time_us.load(Ordering::Relaxed);
310 let jittime_ema = stats.jit_execution_time_ema.load(Ordering::Relaxed) / EMA_SCALE;
311 let interps = stats.interpreted_invocations.load(Ordering::Relaxed);
312 let interptime = stats.total_interpretation_time_us.load(Ordering::Relaxed);
313 let interpema = stats.interpretation_time_ema.load(Ordering::Relaxed) / EMA_SCALE;
314 let _ = writeln!(
315 &mut output,
316 "{addr},{entry_ty},{uses},{compiles},{comptime},{comptime_ema},{invokes},\
317 {jittime},{jittime_ema},{interps},{interptime},{interpema}"
318 );
319 }
320 if let Err(e) = std::fs::write(stat_path, output) {
321 log::info!("Writing entry stats to {stat_path:?} failed: {e:?}");
322 } else {
323 log::debug!("Entry stats written to {stat_path:?}");
324 }
325 }
326}