miraland_program_runtime/
timings.rs

1use {
2    core::fmt,
3    enum_iterator::Sequence,
4    miraland_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, Sequence)]
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::CARDINALITY]);
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::CARDINALITY])
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_create_executor_register_syscalls_us",
195                $self
196                    .details
197                    .create_executor_register_syscalls_us,
198                i64
199            ),
200            (
201                "execute_details_create_executor_load_elf_us",
202                $self.details.create_executor_load_elf_us,
203                i64
204            ),
205            (
206                "execute_details_create_executor_verify_code_us",
207                $self.details.create_executor_verify_code_us,
208                i64
209            ),
210            (
211                "execute_details_create_executor_jit_compile_us",
212                $self.details.create_executor_jit_compile_us,
213                i64
214            ),
215            (
216                "execute_accessories_feature_set_clone_us",
217                $self
218                    .execute_accessories
219                    .feature_set_clone_us,
220                i64
221            ),
222            (
223                "execute_accessories_compute_budget_process_transaction_us",
224                $self
225                    .execute_accessories
226                    .compute_budget_process_transaction_us,
227                i64
228            ),
229            (
230                "execute_accessories_get_executors_us",
231                $self.execute_accessories.get_executors_us,
232                i64
233            ),
234            (
235                "execute_accessories_process_message_us",
236                $self.execute_accessories.process_message_us,
237                i64
238            ),
239            (
240                "execute_accessories_update_executors_us",
241                $self.execute_accessories.update_executors_us,
242                i64
243            ),
244            (
245                "execute_accessories_process_instructions_total_us",
246                $self
247                    .execute_accessories
248                    .process_instructions
249                    .total_us,
250                i64
251            ),
252            (
253                "execute_accessories_process_instructions_verify_caller_us",
254                $self
255                    .execute_accessories
256                    .process_instructions
257                    .verify_caller_us,
258                i64
259            ),
260            (
261                "execute_accessories_process_instructions_process_executable_chain_us",
262                $self
263                    .execute_accessories
264                    .process_instructions
265                    .process_executable_chain_us,
266                i64
267            ),
268            (
269                "execute_accessories_process_instructions_verify_callee_us",
270                $self
271                    .execute_accessories
272                    .process_instructions
273                    .verify_callee_us,
274                i64
275            ),
276        }
277    }
278}
279
280#[derive(Debug, Default)]
281pub struct ThreadExecuteTimings {
282    pub total_thread_us: u64,
283    pub total_transactions_executed: u64,
284    pub execute_timings: ExecuteTimings,
285}
286
287impl ThreadExecuteTimings {
288    pub fn report_stats(&self, slot: Slot) {
289        lazy! {
290            datapoint_info!(
291                "replay-slot-end-to-end-stats",
292                ("slot", slot as i64, i64),
293                ("total_thread_us", self.total_thread_us as i64, i64),
294                ("total_transactions_executed", self.total_transactions_executed as i64, i64),
295                // Everything inside the `eager!` block will be eagerly expanded before
296                // evaluation of the rest of the surrounding macro.
297                eager!{report_execute_timings!(self.execute_timings)}
298            );
299        };
300    }
301
302    pub fn accumulate(&mut self, other: &ThreadExecuteTimings) {
303        self.execute_timings.saturating_add_in_place(
304            ExecuteTimingType::TotalBatchesLen,
305            *other
306                .execute_timings
307                .metrics
308                .index(ExecuteTimingType::TotalBatchesLen),
309        );
310        self.execute_timings.accumulate(&other.execute_timings);
311        saturating_add_assign!(self.total_thread_us, other.total_thread_us);
312        saturating_add_assign!(
313            self.total_transactions_executed,
314            other.total_transactions_executed
315        );
316    }
317}
318
319#[derive(Debug, Default)]
320pub struct ExecuteTimings {
321    pub metrics: Metrics,
322    pub details: ExecuteDetailsTimings,
323    pub execute_accessories: ExecuteAccessoryTimings,
324}
325
326impl ExecuteTimings {
327    pub fn accumulate(&mut self, other: &ExecuteTimings) {
328        for (t1, t2) in self.metrics.0.iter_mut().zip(other.metrics.0.iter()) {
329            saturating_add_assign!(*t1, *t2);
330        }
331        self.details.accumulate(&other.details);
332        self.execute_accessories
333            .accumulate(&other.execute_accessories);
334    }
335
336    pub fn saturating_add_in_place(&mut self, timing_type: ExecuteTimingType, value_to_add: u64) {
337        let idx = timing_type as usize;
338        match self.metrics.0.get_mut(idx) {
339            Some(elem) => *elem = elem.saturating_add(value_to_add),
340            None => debug_assert!(idx < ExecuteTimingType::CARDINALITY, "Index out of bounds"),
341        }
342    }
343}
344
345#[derive(Default, Debug)]
346pub struct ExecuteProcessInstructionTimings {
347    pub total_us: u64,
348    pub verify_caller_us: u64,
349    pub process_executable_chain_us: u64,
350    pub verify_callee_us: u64,
351}
352
353impl ExecuteProcessInstructionTimings {
354    pub fn accumulate(&mut self, other: &ExecuteProcessInstructionTimings) {
355        saturating_add_assign!(self.total_us, other.total_us);
356        saturating_add_assign!(self.verify_caller_us, other.verify_caller_us);
357        saturating_add_assign!(
358            self.process_executable_chain_us,
359            other.process_executable_chain_us
360        );
361        saturating_add_assign!(self.verify_callee_us, other.verify_callee_us);
362    }
363}
364
365#[derive(Default, Debug)]
366pub struct ExecuteAccessoryTimings {
367    pub feature_set_clone_us: u64,
368    pub compute_budget_process_transaction_us: u64,
369    pub get_executors_us: u64,
370    pub process_message_us: u64,
371    pub update_executors_us: u64,
372    pub process_instructions: ExecuteProcessInstructionTimings,
373}
374
375impl ExecuteAccessoryTimings {
376    pub fn accumulate(&mut self, other: &ExecuteAccessoryTimings) {
377        saturating_add_assign!(self.feature_set_clone_us, other.feature_set_clone_us);
378        saturating_add_assign!(
379            self.compute_budget_process_transaction_us,
380            other.compute_budget_process_transaction_us
381        );
382        saturating_add_assign!(self.get_executors_us, other.get_executors_us);
383        saturating_add_assign!(self.process_message_us, other.process_message_us);
384        saturating_add_assign!(self.update_executors_us, other.update_executors_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: u64,
393    pub create_vm_us: u64,
394    pub execute_us: u64,
395    pub deserialize_us: u64,
396    pub get_or_create_executor_us: u64,
397    pub changed_account_count: u64,
398    pub total_account_count: u64,
399    pub create_executor_register_syscalls_us: u64,
400    pub create_executor_load_elf_us: u64,
401    pub create_executor_verify_code_us: u64,
402    pub create_executor_jit_compile_us: u64,
403    pub per_program_timings: HashMap<Pubkey, ProgramTiming>,
404}
405
406impl ExecuteDetailsTimings {
407    pub fn accumulate(&mut self, other: &ExecuteDetailsTimings) {
408        saturating_add_assign!(self.serialize_us, other.serialize_us);
409        saturating_add_assign!(self.create_vm_us, other.create_vm_us);
410        saturating_add_assign!(self.execute_us, other.execute_us);
411        saturating_add_assign!(self.deserialize_us, other.deserialize_us);
412        saturating_add_assign!(
413            self.get_or_create_executor_us,
414            other.get_or_create_executor_us
415        );
416        saturating_add_assign!(self.changed_account_count, other.changed_account_count);
417        saturating_add_assign!(self.total_account_count, other.total_account_count);
418        saturating_add_assign!(
419            self.create_executor_register_syscalls_us,
420            other.create_executor_register_syscalls_us
421        );
422        saturating_add_assign!(
423            self.create_executor_load_elf_us,
424            other.create_executor_load_elf_us
425        );
426        saturating_add_assign!(
427            self.create_executor_verify_code_us,
428            other.create_executor_verify_code_us
429        );
430        saturating_add_assign!(
431            self.create_executor_jit_compile_us,
432            other.create_executor_jit_compile_us
433        );
434        for (id, other) in &other.per_program_timings {
435            let program_timing = self.per_program_timings.entry(*id).or_default();
436            program_timing.accumulate_program_timings(other);
437        }
438    }
439
440    pub fn accumulate_program(
441        &mut self,
442        program_id: &Pubkey,
443        us: u64,
444        compute_units_consumed: u64,
445        is_error: bool,
446    ) {
447        let program_timing = self.per_program_timings.entry(*program_id).or_default();
448        program_timing.accumulated_us = program_timing.accumulated_us.saturating_add(us);
449        if is_error {
450            program_timing
451                .errored_txs_compute_consumed
452                .push(compute_units_consumed);
453            program_timing.total_errored_units = program_timing
454                .total_errored_units
455                .saturating_add(compute_units_consumed);
456        } else {
457            program_timing.accumulated_units = program_timing
458                .accumulated_units
459                .saturating_add(compute_units_consumed);
460            program_timing.count = program_timing.count.saturating_add(1);
461        };
462    }
463}
464
465#[cfg(test)]
466mod tests {
467    use super::*;
468
469    fn construct_execute_timings_with_program(
470        program_id: &Pubkey,
471        us: u64,
472        compute_units_consumed: u64,
473    ) -> ExecuteDetailsTimings {
474        let mut execute_details_timings = ExecuteDetailsTimings::default();
475
476        // Accumulate an erroring transaction
477        let is_error = true;
478        execute_details_timings.accumulate_program(
479            program_id,
480            us,
481            compute_units_consumed,
482            is_error,
483        );
484
485        // Accumulate a non-erroring transaction
486        let is_error = false;
487        execute_details_timings.accumulate_program(
488            program_id,
489            us,
490            compute_units_consumed,
491            is_error,
492        );
493
494        let program_timings = execute_details_timings
495            .per_program_timings
496            .get(program_id)
497            .unwrap();
498
499        // Both error and success transactions count towards `accumulated_us`
500        assert_eq!(program_timings.accumulated_us, us.saturating_mul(2));
501        assert_eq!(program_timings.accumulated_units, compute_units_consumed);
502        assert_eq!(program_timings.count, 1,);
503        assert_eq!(
504            program_timings.errored_txs_compute_consumed,
505            vec![compute_units_consumed]
506        );
507        assert_eq!(program_timings.total_errored_units, compute_units_consumed,);
508
509        execute_details_timings
510    }
511
512    #[test]
513    fn test_execute_details_timing_acumulate_program() {
514        // Acumulate an erroring transaction
515        let program_id = Pubkey::new_unique();
516        let us = 100;
517        let compute_units_consumed = 1;
518        construct_execute_timings_with_program(&program_id, us, compute_units_consumed);
519    }
520
521    #[test]
522    fn test_execute_details_timing_acumulate() {
523        // Acumulate an erroring transaction
524        let program_id = Pubkey::new_unique();
525        let us = 100;
526        let compute_units_consumed = 1;
527        let mut execute_details_timings = ExecuteDetailsTimings::default();
528
529        // Construct another separate instance of ExecuteDetailsTimings with non default fields
530        let mut other_execute_details_timings =
531            construct_execute_timings_with_program(&program_id, us, compute_units_consumed);
532        let account_count = 1;
533        other_execute_details_timings.serialize_us = us;
534        other_execute_details_timings.create_vm_us = us;
535        other_execute_details_timings.execute_us = us;
536        other_execute_details_timings.deserialize_us = us;
537        other_execute_details_timings.changed_account_count = account_count;
538        other_execute_details_timings.total_account_count = account_count;
539
540        // Accumulate the other instance into the current instance
541        execute_details_timings.accumulate(&other_execute_details_timings);
542
543        // Check that the two instances are equal
544        assert_eq!(execute_details_timings, other_execute_details_timings);
545    }
546
547    #[test]
548    fn execute_timings_saturating_add_in_place() {
549        let mut timings = ExecuteTimings::default();
550        timings.saturating_add_in_place(ExecuteTimingType::CheckUs, 1);
551        let check_us = timings.metrics.index(ExecuteTimingType::CheckUs);
552        assert_eq!(1, *check_us);
553
554        timings.saturating_add_in_place(ExecuteTimingType::CheckUs, 2);
555        let check_us = timings.metrics.index(ExecuteTimingType::CheckUs);
556        assert_eq!(3, *check_us);
557    }
558}