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