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 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 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#[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
83eager_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 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 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 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 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 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 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 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 execute_details_timings.accumulate(&other_execute_details_timings);
562
563 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}