Skip to main content

rialo_s_timings/
lib.rs

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