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