solana_svm_timings/
lib.rs

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