solana_svm_timings/
lib.rs

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    // Sum of all units in `errored_txs_compute_consumed`
30    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        // Clones the entire vector, maybe not great...
48        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/// Used as an index for `Metrics`.
55#[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
101// The auxiliary variable that must always be provided to eager_macro_rules! must use the
102// identifier `eager_1`. Macros declared with `eager_macro_rules!` can then be used inside
103// an eager! block.
104eager_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        // Accumulate an erroring transaction
458        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        // Accumulate a non-erroring transaction
467        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        // Both error and success transactions count towards `accumulated_us`
481        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        // Acumulate an erroring transaction
499        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        // Acumulate an erroring transaction
508        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        // Construct another separate instance of ExecuteDetailsTimings with non default fields
514        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        // Accumulate the other instance into the current instance
525        execute_details_timings.accumulate(&other_execute_details_timings);
526
527        // Check that the two instances are equal
528        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}