1#![cfg_attr(
2 not(feature = "agave-unstable-api"),
3 deprecated(
4 since = "3.1.0",
5 note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6 v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7 acknowledge use of an interface that may break without warning."
8 )
9)]
10#[macro_use]
11extern crate eager;
12use {
13 core::fmt,
14 enum_iterator::Sequence,
15 solana_pubkey::Pubkey,
16 std::{
17 collections::HashMap,
18 num::Saturating,
19 ops::{Index, IndexMut},
20 },
21};
22
23#[derive(Default, Debug, PartialEq, Eq)]
24pub struct ProgramTiming {
25 pub accumulated_us: Saturating<u64>,
26 pub accumulated_units: Saturating<u64>,
27 pub count: Saturating<u32>,
28 pub errored_txs_compute_consumed: Vec<u64>,
29 pub total_errored_units: Saturating<u64>,
31}
32
33impl ProgramTiming {
34 pub fn coalesce_error_timings(&mut self, current_estimated_program_cost: u64) {
35 for tx_error_compute_consumed in self.errored_txs_compute_consumed.drain(..) {
36 let compute_units_update =
37 std::cmp::max(current_estimated_program_cost, tx_error_compute_consumed);
38 self.accumulated_units += compute_units_update;
39 self.count += 1;
40 }
41 }
42
43 pub fn accumulate_program_timings(&mut self, other: &ProgramTiming) {
44 self.accumulated_us += other.accumulated_us;
45 self.accumulated_units += other.accumulated_units;
46 self.count += other.count;
47 self.errored_txs_compute_consumed
49 .extend(other.errored_txs_compute_consumed.clone());
50 self.total_errored_units += other.total_errored_units;
51 }
52}
53
54#[derive(Debug, Sequence)]
56pub enum ExecuteTimingType {
57 CheckUs,
58 ValidateFeesUs,
59 LoadUs,
60 ExecuteUs,
61 StoreUs,
62 UpdateStakesCacheUs,
63 UpdateExecutorsUs,
64 NumExecuteBatches,
65 CollectLogsUs,
66 TotalBatchesLen,
67 UpdateTransactionStatuses,
68 ProgramCacheUs,
69 CheckBlockLimitsUs,
70 FilterExecutableUs,
71 CollectBalancesUs,
72}
73
74pub struct Metrics([Saturating<u64>; ExecuteTimingType::CARDINALITY]);
75
76impl Index<ExecuteTimingType> for Metrics {
77 type Output = Saturating<u64>;
78 fn index(&self, index: ExecuteTimingType) -> &Self::Output {
79 self.0.index(index as usize)
80 }
81}
82
83impl IndexMut<ExecuteTimingType> for Metrics {
84 fn index_mut(&mut self, index: ExecuteTimingType) -> &mut Self::Output {
85 self.0.index_mut(index as usize)
86 }
87}
88
89impl Default for Metrics {
90 fn default() -> Self {
91 Metrics([Saturating(0); ExecuteTimingType::CARDINALITY])
92 }
93}
94
95impl core::fmt::Debug for Metrics {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 self.0.fmt(f)
98 }
99}
100
101eager_macro_rules! { $eager_1
105 #[macro_export]
106 macro_rules! report_execute_timings {
107 ($self: expr, $is_unified_scheduler_enabled: expr) => {
108 (
109 "validate_transactions_us",
110 $self
111 .metrics
112 .index(ExecuteTimingType::CheckUs).0,
113 i64
114 ),
115 (
116 "validate_fees_us",
117 $self
118 .metrics
119 .index(ExecuteTimingType::ValidateFeesUs).0,
120 i64
121 ),
122 (
123 "filter_executable_us",
124 $self
125 .metrics
126 .index(ExecuteTimingType::FilterExecutableUs).0,
127 i64
128 ),
129 (
130 "program_cache_us",
131 $self
132 .metrics
133 .index(ExecuteTimingType::ProgramCacheUs).0,
134 i64
135 ),
136 (
137 "load_us",
138 $self
139 .metrics
140 .index(ExecuteTimingType::LoadUs).0,
141 i64
142 ),
143 (
144 "execute_us",
145 $self
146 .metrics
147 .index(ExecuteTimingType::ExecuteUs).0,
148 i64
149 ),
150 (
151 "collect_balances_us",
152 $self
153 .metrics
154 .index(ExecuteTimingType::CollectBalancesUs).0,
155 i64
156 ),
157 (
158 "collect_logs_us",
159 $self
160 .metrics
161 .index(ExecuteTimingType::CollectLogsUs).0,
162 i64
163 ),
164 (
165 "store_us",
166 $self
167
168 .metrics
169 .index(ExecuteTimingType::StoreUs).0,
170 i64
171 ),
172 (
173 "update_stakes_cache_us",
174 $self
175
176 .metrics
177 .index(ExecuteTimingType::UpdateStakesCacheUs).0,
178 i64
179 ),
180 (
181 "execute_accessories_update_executors_us",
182 $self.metrics.index(ExecuteTimingType::UpdateExecutorsUs).0,
183 i64
184 ),
185 (
186 "total_batches_len",
187 (if $is_unified_scheduler_enabled {
188 None
189 } else {
190 Some($self
191 .metrics
192 .index(ExecuteTimingType::TotalBatchesLen).0)
193 }),
194 Option<i64>
195 ),
196 (
197 "num_execute_batches",
198 (if $is_unified_scheduler_enabled {
199 None
200 } else {
201 Some($self
202 .metrics
203 .index(ExecuteTimingType::NumExecuteBatches).0)
204 }),
205 Option<i64>
206 ),
207 (
208 "update_transaction_statuses",
209 $self
210 .metrics
211 .index(ExecuteTimingType::UpdateTransactionStatuses).0,
212 i64
213 ),
214 (
215 "check_block_limits_us",
216 $self.metrics.index(ExecuteTimingType::CheckBlockLimitsUs).0,
217 i64
218 ),
219 (
220 "execute_details_serialize_us",
221 $self.details.serialize_us.0,
222 i64
223 ),
224 (
225 "execute_details_create_vm_us",
226 $self.details.create_vm_us.0,
227 i64
228 ),
229 (
230 "execute_details_execute_inner_us",
231 $self.details.execute_us.0,
232 i64
233 ),
234 (
235 "execute_details_deserialize_us",
236 $self.details.deserialize_us.0,
237 i64
238 ),
239 (
240 "execute_details_get_or_create_executor_us",
241 $self.details.get_or_create_executor_us.0,
242 i64
243 ),
244 (
245 "execute_details_changed_account_count",
246 $self.details.changed_account_count.0,
247 i64
248 ),
249 (
250 "execute_details_total_account_count",
251 $self.details.total_account_count.0,
252 i64
253 ),
254 (
255 "execute_details_create_executor_register_syscalls_us",
256 $self
257 .details
258 .create_executor_register_syscalls_us.0,
259 i64
260 ),
261 (
262 "execute_details_create_executor_load_elf_us",
263 $self.details.create_executor_load_elf_us.0,
264 i64
265 ),
266 (
267 "execute_details_create_executor_verify_code_us",
268 $self.details.create_executor_verify_code_us.0,
269 i64
270 ),
271 (
272 "execute_details_create_executor_jit_compile_us",
273 $self.details.create_executor_jit_compile_us.0,
274 i64
275 ),
276 (
277 "execute_accessories_feature_set_clone_us",
278 $self
279 .execute_accessories
280 .feature_set_clone_us.0,
281 i64
282 ),
283 (
284 "execute_accessories_get_executors_us",
285 $self.execute_accessories.get_executors_us.0,
286 i64
287 ),
288 (
289 "execute_accessories_process_message_us",
290 $self.execute_accessories.process_message_us.0,
291 i64
292 ),
293 (
294 "execute_accessories_process_instructions_total_us",
295 $self
296 .execute_accessories
297 .process_instructions
298 .total_us.0,
299 i64
300 ),
301 (
302 "execute_accessories_process_instructions_verify_caller_us",
303 $self
304 .execute_accessories
305 .process_instructions
306 .verify_caller_us.0,
307 i64
308 ),
309 (
310 "execute_accessories_process_instructions_process_executable_chain_us",
311 $self
312 .execute_accessories
313 .process_instructions
314 .process_executable_chain_us.0,
315 i64
316 ),
317 (
318 "execute_accessories_process_instructions_verify_callee_us",
319 $self
320 .execute_accessories
321 .process_instructions
322 .verify_callee_us.0,
323 i64
324 ),
325 }
326 }
327}
328
329#[derive(Debug, Default)]
330pub struct ExecuteTimings {
331 pub metrics: Metrics,
332 pub details: ExecuteDetailsTimings,
333 pub execute_accessories: ExecuteAccessoryTimings,
334}
335
336impl ExecuteTimings {
337 pub fn accumulate(&mut self, other: &ExecuteTimings) {
338 for (t1, t2) in self.metrics.0.iter_mut().zip(other.metrics.0.iter()) {
339 *t1 += *t2;
340 }
341 self.details.accumulate(&other.details);
342 self.execute_accessories
343 .accumulate(&other.execute_accessories);
344 }
345
346 pub fn saturating_add_in_place(&mut self, timing_type: ExecuteTimingType, value_to_add: u64) {
347 let idx = timing_type as usize;
348 match self.metrics.0.get_mut(idx) {
349 Some(elem) => *elem += value_to_add,
350 None => debug_assert!(idx < ExecuteTimingType::CARDINALITY, "Index out of bounds"),
351 }
352 }
353}
354
355#[derive(Default, Debug)]
356pub struct ExecuteProcessInstructionTimings {
357 pub total_us: Saturating<u64>,
358 pub verify_caller_us: Saturating<u64>,
359 pub process_executable_chain_us: Saturating<u64>,
360 pub verify_callee_us: Saturating<u64>,
361}
362
363impl ExecuteProcessInstructionTimings {
364 pub fn accumulate(&mut self, other: &ExecuteProcessInstructionTimings) {
365 self.total_us += other.total_us;
366 self.verify_caller_us += other.verify_caller_us;
367 self.process_executable_chain_us += other.process_executable_chain_us;
368 self.verify_callee_us += other.verify_callee_us;
369 }
370}
371
372#[derive(Default, Debug)]
373pub struct ExecuteAccessoryTimings {
374 pub feature_set_clone_us: Saturating<u64>,
375 pub get_executors_us: Saturating<u64>,
376 pub process_message_us: Saturating<u64>,
377 pub process_instructions: ExecuteProcessInstructionTimings,
378}
379
380impl ExecuteAccessoryTimings {
381 pub fn accumulate(&mut self, other: &ExecuteAccessoryTimings) {
382 self.feature_set_clone_us += other.feature_set_clone_us;
383 self.get_executors_us += other.get_executors_us;
384 self.process_message_us += other.process_message_us;
385 self.process_instructions
386 .accumulate(&other.process_instructions);
387 }
388}
389
390#[derive(Default, Debug, PartialEq, Eq)]
391pub struct ExecuteDetailsTimings {
392 pub serialize_us: Saturating<u64>,
393 pub create_vm_us: Saturating<u64>,
394 pub execute_us: Saturating<u64>,
395 pub deserialize_us: Saturating<u64>,
396 pub get_or_create_executor_us: Saturating<u64>,
397 pub changed_account_count: Saturating<u64>,
398 pub total_account_count: Saturating<u64>,
399 pub create_executor_register_syscalls_us: Saturating<u64>,
400 pub create_executor_load_elf_us: Saturating<u64>,
401 pub create_executor_verify_code_us: Saturating<u64>,
402 pub create_executor_jit_compile_us: Saturating<u64>,
403 pub per_program_timings: HashMap<Pubkey, ProgramTiming>,
404}
405
406impl ExecuteDetailsTimings {
407 pub fn accumulate(&mut self, other: &ExecuteDetailsTimings) {
408 self.serialize_us += other.serialize_us;
409 self.create_vm_us += other.create_vm_us;
410 self.execute_us += other.execute_us;
411 self.deserialize_us += other.deserialize_us;
412 self.get_or_create_executor_us += other.get_or_create_executor_us;
413 self.changed_account_count += other.changed_account_count;
414 self.total_account_count += other.total_account_count;
415 self.create_executor_register_syscalls_us += other.create_executor_register_syscalls_us;
416 self.create_executor_load_elf_us += other.create_executor_load_elf_us;
417 self.create_executor_verify_code_us += other.create_executor_verify_code_us;
418 self.create_executor_jit_compile_us += other.create_executor_jit_compile_us;
419 for (id, other) in &other.per_program_timings {
420 let program_timing = self.per_program_timings.entry(*id).or_default();
421 program_timing.accumulate_program_timings(other);
422 }
423 }
424
425 pub fn accumulate_program(
426 &mut self,
427 program_id: &Pubkey,
428 us: u64,
429 compute_units_consumed: u64,
430 is_error: bool,
431 ) {
432 let program_timing = self.per_program_timings.entry(*program_id).or_default();
433 program_timing.accumulated_us += us;
434 if is_error {
435 program_timing
436 .errored_txs_compute_consumed
437 .push(compute_units_consumed);
438 program_timing.total_errored_units += compute_units_consumed;
439 } else {
440 program_timing.accumulated_units += compute_units_consumed;
441 program_timing.count += 1;
442 };
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 fn construct_execute_timings_with_program(
451 program_id: &Pubkey,
452 us: u64,
453 compute_units_consumed: u64,
454 ) -> ExecuteDetailsTimings {
455 let mut execute_details_timings = ExecuteDetailsTimings::default();
456
457 let is_error = true;
459 execute_details_timings.accumulate_program(
460 program_id,
461 us,
462 compute_units_consumed,
463 is_error,
464 );
465
466 let is_error = false;
468 execute_details_timings.accumulate_program(
469 program_id,
470 us,
471 compute_units_consumed,
472 is_error,
473 );
474
475 let program_timings = execute_details_timings
476 .per_program_timings
477 .get(program_id)
478 .unwrap();
479
480 assert_eq!(program_timings.accumulated_us.0, us.saturating_mul(2));
482 assert_eq!(program_timings.accumulated_units.0, compute_units_consumed);
483 assert_eq!(program_timings.count.0, 1,);
484 assert_eq!(
485 program_timings.errored_txs_compute_consumed,
486 vec![compute_units_consumed]
487 );
488 assert_eq!(
489 program_timings.total_errored_units.0,
490 compute_units_consumed,
491 );
492
493 execute_details_timings
494 }
495
496 #[test]
497 fn test_execute_details_timing_acumulate_program() {
498 let program_id = Pubkey::new_unique();
500 let us = 100;
501 let compute_units_consumed = 1;
502 construct_execute_timings_with_program(&program_id, us, compute_units_consumed);
503 }
504
505 #[test]
506 fn test_execute_details_timing_acumulate() {
507 let program_id = Pubkey::new_unique();
509 let us = 100;
510 let compute_units_consumed = 1;
511 let mut execute_details_timings = ExecuteDetailsTimings::default();
512
513 let mut other_execute_details_timings =
515 construct_execute_timings_with_program(&program_id, us, compute_units_consumed);
516 let account_count = 1;
517 other_execute_details_timings.serialize_us.0 = us;
518 other_execute_details_timings.create_vm_us.0 = us;
519 other_execute_details_timings.execute_us.0 = us;
520 other_execute_details_timings.deserialize_us.0 = us;
521 other_execute_details_timings.changed_account_count.0 = account_count;
522 other_execute_details_timings.total_account_count.0 = account_count;
523
524 execute_details_timings.accumulate(&other_execute_details_timings);
526
527 assert_eq!(execute_details_timings, other_execute_details_timings);
529 }
530
531 #[test]
532 fn execute_timings_saturating_add_in_place() {
533 let mut timings = ExecuteTimings::default();
534 timings.saturating_add_in_place(ExecuteTimingType::CheckUs, 1);
535 let check_us = timings.metrics.index(ExecuteTimingType::CheckUs);
536 assert_eq!(1, check_us.0);
537
538 timings.saturating_add_in_place(ExecuteTimingType::CheckUs, 2);
539 let check_us = timings.metrics.index(ExecuteTimingType::CheckUs);
540 assert_eq!(3, check_us.0);
541 }
542}