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