solana_program_runtime/
timings.rs

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