1use std::path::PathBuf;
4use std::time::Duration;
5
6use serde::{ser::SerializeStruct, Serialize};
7
8use crate::encode;
9use crate::tile::PixelProfile;
10use crate::time::duration_as_reported_micros;
11
12macro_rules! saturating_add_fields {
13 ($target:expr, $source:expr, [$($field:ident),+ $(,)?]) => {
14 $(
15 $target.$field = $target.$field.saturating_add($source.$field);
16 )+
17 };
18}
19
20macro_rules! saturating_add_values {
21 ($target:expr, [$($field:ident => $value:expr),+ $(,)?]) => {
22 $(
23 $target.$field = $target.$field.saturating_add($value);
24 )+
25 };
26}
27
28macro_rules! max_fields {
29 ($target:expr, $source:expr, [$($field:ident),+ $(,)?]) => {
30 $(
31 $target.$field = $target.$field.max($source.$field);
32 )+
33 };
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub(crate) enum JpegRetileRejectionReason {
38 SourceUnsupported,
39 GeometryMismatch,
40 ProfileUnsupported,
41 McuInvalid,
42}
43
44#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
46#[non_exhaustive]
47pub struct ExportReport {
48 pub output_dir: PathBuf,
50 pub instances: Vec<InstanceReport>,
52 pub metrics: ExportMetrics,
54}
55
56#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
58#[non_exhaustive]
59pub struct EncodedFrame {
60 pub transfer_syntax_uid: &'static str,
62 pub bytes: Vec<u8>,
64 pub used_device_encode: bool,
66 pub used_device_validation: bool,
68 pub encode_micros: u128,
70 pub validation_micros: u128,
72}
73
74#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
76#[non_exhaustive]
77pub struct RouteProfileReport {
78 pub source_path: PathBuf,
80 pub transfer_syntax_uid: &'static str,
82 pub level: u32,
84 pub requested_frames: u64,
86 pub available_frames: u64,
88 pub metrics: ExportMetrics,
90 pub elapsed_micros: u128,
92}
93
94#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
96#[non_exhaustive]
97pub struct RouteCoverageReport {
98 pub source_path: PathBuf,
100 pub transfer_syntax_uid: &'static str,
102 pub requested_frames_per_level: u64,
104 pub available_frames: u64,
106 pub complete_frame_coverage: bool,
108 pub levels: Vec<RouteProfileReport>,
110 pub metrics: ExportMetrics,
112 pub elapsed_micros: u128,
114}
115
116#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
118#[non_exhaustive]
119pub struct RouteCorpusCoverageFailure {
120 pub source_path: PathBuf,
122 pub message: String,
124}
125
126#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
128#[non_exhaustive]
129pub struct RouteCorpusCoverageReport {
130 pub source_root: PathBuf,
132 pub transfer_syntax_uid: Option<&'static str>,
134 pub transfer_syntax_uids: Vec<&'static str>,
136 pub requested_frames_per_level: u64,
138 pub max_levels: Option<u32>,
140 pub sources_considered: usize,
142 pub available_frames: u64,
144 pub complete_frame_coverage: bool,
146 pub reports: Vec<RouteCoverageReport>,
148 pub failures: Vec<RouteCorpusCoverageFailure>,
150 pub metrics: ExportMetrics,
152 pub elapsed_micros: u128,
154}
155
156#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
158#[serde(rename_all = "snake_case")]
159#[non_exhaustive]
160pub enum IccProfileSource {
161 Source,
163 SourceJpeg,
165 SynthesizedSrgb,
167 SynthesizedDisplayP3,
169 #[default]
171 OmittedMissing,
172}
173
174#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
176#[non_exhaustive]
177pub struct InstanceReport {
178 pub path: PathBuf,
180 pub sop_instance_uid: String,
182 pub series_instance_uid: String,
184 pub transfer_syntax_uid: &'static str,
186 pub icc_profile_source: IccProfileSource,
188 pub scene: usize,
190 pub series: usize,
192 pub level: u32,
194 pub z: u32,
196 pub c: u32,
198 pub t: u32,
200 pub frame_count: u32,
202 pub metrics: ExportMetrics,
204}
205
206#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
208#[non_exhaustive]
209pub struct RouteCounters {
210 pub total_frames: u64,
212 pub cpu_input_frames: u64,
214 pub gpu_input_decode_frames: u64,
216 pub gpu_encode_frames: u64,
218 pub gpu_validation_frames: u64,
220 pub gray_frames: u64,
222 pub rgb_like_frames: u64,
224 pub other_component_frames: u64,
226 pub unknown_pixel_profile_frames: u64,
228 pub bits8_frames: u64,
230 pub bits16_frames: u64,
232 pub other_bit_depth_frames: u64,
234 pub gpu_transcode_frames: u64,
236 pub resident_gpu_transcode_frames: u64,
238 pub partial_gpu_transcode_frames: u64,
240 pub gpu_input_decode_batches: u64,
242 pub gpu_compose_batches: u64,
244 pub gpu_encode_batches: u64,
246 pub auto_route_probe_frames: u64,
248 pub auto_route_probe_gpu_batches: u64,
250 pub auto_route_probe_cpu_micros: u128,
252 pub auto_route_probe_gpu_micros: u128,
254 pub auto_route_probe_selected_gpu_input_frames: u64,
256 pub cpu_fallback_frames: u64,
258 pub jpeg_passthrough_frames: u64,
260 pub j2k_passthrough_frames: u64,
262 pub j2k_direct_htj2k_frames: u64,
264 pub jpeg_retile_frames: u64,
266 pub jpeg_retile_rejected_frames: u64,
268 pub jpeg_retile_source_unsupported_frames: u64,
270 pub jpeg_retile_geometry_mismatch_frames: u64,
272 pub jpeg_retile_profile_unsupported_frames: u64,
274 pub jpeg_retile_mcu_invalid_frames: u64,
276 pub jpeg_retile_us: u128,
278 pub jpeg_retile_to_htj2k_53_frames: u64,
280 pub jpeg_cpu_encode_frames: u64,
282 pub jpeg_metal_encode_frames: u64,
284 pub jpeg_decode_fallback_frames: u64,
286}
287
288#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
290#[non_exhaustive]
291pub struct JpegDirectHtj2kMetrics {
292 pub jpeg_direct_htj2k_53_frames: u64,
294 pub jpeg_direct_htj2k_97_frames: u64,
296 pub jpeg_direct_htj2k_rejected_frames: u64,
298 pub jpeg_direct_htj2k_extract_micros: u128,
300 pub jpeg_direct_htj2k_repack_micros: u128,
302 pub jpeg_direct_htj2k_transform_micros: u128,
304 pub jpeg_direct_htj2k_accelerator_micros: u128,
306 pub jpeg_direct_htj2k_cpu_fallback_micros: u128,
308 pub jpeg_direct_htj2k_dwt_decompose_micros: u128,
310 pub jpeg_direct_htj2k_dwt97_pack_upload_micros: u128,
312 pub jpeg_direct_htj2k_dwt97_idct_row_lift_micros: u128,
314 pub jpeg_direct_htj2k_dwt97_column_lift_micros: u128,
316 pub jpeg_direct_htj2k_dwt97_quantize_codeblock_micros: u128,
318 pub jpeg_direct_htj2k_dwt97_ht_encode_micros: u128,
320 pub jpeg_direct_htj2k_dwt97_ht_kernel_micros: u128,
322 pub jpeg_direct_htj2k_dwt97_ht_status_readback_micros: u128,
324 pub jpeg_direct_htj2k_dwt97_ht_compact_micros: u128,
326 pub jpeg_direct_htj2k_dwt97_ht_output_readback_micros: u128,
328 pub jpeg_direct_htj2k_dwt97_ht_codeblock_dispatches: u64,
330 pub jpeg_direct_htj2k_dwt97_readback_micros: u128,
332 pub jpeg_direct_htj2k_htj2k_encode_micros: u128,
334 pub jpeg_direct_htj2k_encode_accelerator_dispatches: u64,
336 pub jpeg_direct_htj2k_encode_ht_code_block_dispatches: u64,
338 pub jpeg_direct_htj2k_encode_packetization_dispatches: u64,
340 pub jpeg_direct_htj2k_batch_count: u64,
342 pub jpeg_direct_htj2k_batch_jobs: u64,
344 pub jpeg_direct_htj2k_accelerator_attempts: u64,
346 pub jpeg_direct_htj2k_accelerator_jobs: u64,
348 pub jpeg_direct_htj2k_accelerator_dispatches: u64,
350 pub jpeg_direct_htj2k_accelerator_dispatched_jobs: u64,
352 pub jpeg_direct_htj2k_cpu_fallback_jobs: u64,
354}
355
356#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
358#[non_exhaustive]
359pub struct WriteTimings {
360 pub input_decode_micros: u128,
362 pub compose_micros: u128,
364 pub encode_micros: u128,
366 pub validation_micros: u128,
368 pub gpu_dispatch_micros: u128,
370 pub streaming_write_micros: u128,
372 pub pixel_data_patch_micros: u128,
374 pub writer_backpressure_micros: u128,
376 pub write_micros: u128,
378}
379
380#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
382#[non_exhaustive]
383pub struct GpuEncodeMetrics {
384 pub gpu_encode_configured_inflight_tiles: u64,
386 pub gpu_encode_effective_inflight_tiles: u64,
388 pub gpu_encode_max_observed_inflight_tiles: u64,
390 pub gpu_encode_configured_memory_mib: u64,
392 pub gpu_encode_effective_memory_mib: u64,
394 pub gpu_encode_wall_micros: u128,
396 pub gpu_encode_hardware_micros: u128,
398 pub gpu_encode_dispatch_overhead_micros: u128,
400 pub gpu_encode_plan_micros: u128,
402 pub gpu_encode_prepare_submit_micros: u128,
404 pub gpu_encode_ht_table_build_micros: u128,
406 pub gpu_encode_ht_buffer_allocation_micros: u128,
408 pub gpu_encode_ht_command_encode_micros: u128,
410 pub gpu_encode_codestream_wait_micros: u128,
412 pub gpu_encode_chunk_count: u64,
414 pub gpu_encode_tile_count: u64,
416 pub gpu_encode_code_block_count: u64,
418 pub gpu_pipeline_depth: u64,
420 pub gpu_row_batch_rows_max: u64,
422 pub gpu_row_batch_target_tiles: u64,
424}
425
426#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
428#[non_exhaustive]
429pub struct ExportMetrics {
430 #[serde(flatten)]
432 pub routes: RouteCounters,
433 #[serde(flatten)]
435 pub jpeg_direct_htj2k: JpegDirectHtj2kMetrics,
436 #[serde(flatten)]
438 pub gpu_encode: GpuEncodeMetrics,
439 #[serde(flatten)]
441 pub timings: WriteTimings,
442}
443
444impl Serialize for GpuEncodeMetrics {
445 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
446 where
447 S: serde::Serializer,
448 {
449 let mut state =
450 serializer.serialize_struct("GpuEncodeMetrics", Self::SERIALIZED_FIELD_COUNT)?;
451 state.serialize_field(
452 "gpu_encode_configured_inflight_tiles",
453 &self.gpu_encode_configured_inflight_tiles,
454 )?;
455 state.serialize_field(
456 "gpu_encode_effective_inflight_tiles",
457 &self.gpu_encode_effective_inflight_tiles,
458 )?;
459 state.serialize_field(
460 "gpu_encode_max_observed_inflight_tiles",
461 &self.gpu_encode_max_observed_inflight_tiles,
462 )?;
463 state.serialize_field(
464 "gpu_encode_configured_memory_mib",
465 &self.gpu_encode_configured_memory_mib,
466 )?;
467 state.serialize_field(
468 "gpu_encode_effective_memory_mib",
469 &self.gpu_encode_effective_memory_mib,
470 )?;
471 state.serialize_field("gpu_encode_wall_micros", &self.gpu_encode_wall_micros)?;
472 state.serialize_field(
473 "gpu_encode_effective_parallelism",
474 &self.effective_parallelism(),
475 )?;
476 state.serialize_field(
477 "gpu_encode_hardware_micros",
478 &self.gpu_encode_hardware_micros,
479 )?;
480 state.serialize_field(
481 "gpu_encode_dispatch_overhead_micros",
482 &self.gpu_encode_dispatch_overhead_micros,
483 )?;
484 state.serialize_field("gpu_encode_plan_micros", &self.gpu_encode_plan_micros)?;
485 state.serialize_field(
486 "gpu_encode_prepare_submit_micros",
487 &self.gpu_encode_prepare_submit_micros,
488 )?;
489 state.serialize_field(
490 "gpu_encode_ht_table_build_micros",
491 &self.gpu_encode_ht_table_build_micros,
492 )?;
493 state.serialize_field(
494 "gpu_encode_ht_buffer_allocation_micros",
495 &self.gpu_encode_ht_buffer_allocation_micros,
496 )?;
497 state.serialize_field(
498 "gpu_encode_ht_command_encode_micros",
499 &self.gpu_encode_ht_command_encode_micros,
500 )?;
501 state.serialize_field(
502 "gpu_encode_codestream_wait_micros",
503 &self.gpu_encode_codestream_wait_micros,
504 )?;
505 state.serialize_field("gpu_encode_chunk_count", &self.gpu_encode_chunk_count)?;
506 state.serialize_field("gpu_encode_tile_count", &self.gpu_encode_tile_count)?;
507 state.serialize_field(
508 "gpu_encode_code_block_count",
509 &self.gpu_encode_code_block_count,
510 )?;
511 state.serialize_field("gpu_pipeline_depth", &self.gpu_pipeline_depth)?;
512 state.serialize_field("gpu_row_batch_rows_max", &self.gpu_row_batch_rows_max)?;
513 state.serialize_field(
514 "gpu_row_batch_target_tiles",
515 &self.gpu_row_batch_target_tiles,
516 )?;
517 state.end()
518 }
519}
520
521impl GpuEncodeMetrics {
522 const SERIALIZED_FIELD_COUNT: usize = 21;
523
524 pub fn effective_parallelism(&self) -> f64 {
526 if self.gpu_encode_wall_micros == 0 {
527 0.0
528 } else {
529 self.gpu_encode_hardware_micros as f64 / self.gpu_encode_wall_micros as f64
530 }
531 }
532}
533
534impl ExportMetrics {
535 pub const SERIALIZED_FIELD_COUNT: usize = 99;
537
538 pub fn route_passthrough_frames(&self) -> u64 {
540 self.routes
541 .jpeg_passthrough_frames
542 .saturating_add(self.routes.j2k_passthrough_frames)
543 }
544
545 pub fn jpeg_retile_baseline_frames(&self) -> u64 {
547 self.routes
548 .jpeg_retile_frames
549 .saturating_sub(self.routes.jpeg_retile_to_htj2k_53_frames)
550 }
551
552 pub fn route_unclassified_frames(&self) -> u64 {
554 self.routes
555 .total_frames
556 .saturating_sub(self.route_passthrough_frames())
557 .saturating_sub(self.routes.j2k_direct_htj2k_frames)
558 .saturating_sub(self.jpeg_direct_htj2k.jpeg_direct_htj2k_53_frames)
559 .saturating_sub(self.jpeg_direct_htj2k.jpeg_direct_htj2k_97_frames)
560 .saturating_sub(self.jpeg_retile_baseline_frames())
561 .saturating_sub(self.routes.gpu_transcode_frames)
562 .saturating_sub(self.routes.cpu_fallback_frames)
563 }
564
565 pub(crate) fn add_assign(&mut self, other: Self) {
566 saturating_add_fields!(
567 self.routes,
568 other.routes,
569 [
570 total_frames,
571 cpu_input_frames,
572 gpu_input_decode_frames,
573 gpu_encode_frames,
574 gpu_validation_frames,
575 gray_frames,
576 rgb_like_frames,
577 other_component_frames,
578 unknown_pixel_profile_frames,
579 bits8_frames,
580 bits16_frames,
581 other_bit_depth_frames,
582 gpu_transcode_frames,
583 resident_gpu_transcode_frames,
584 partial_gpu_transcode_frames,
585 gpu_input_decode_batches,
586 gpu_compose_batches,
587 gpu_encode_batches,
588 auto_route_probe_frames,
589 auto_route_probe_gpu_batches,
590 auto_route_probe_cpu_micros,
591 auto_route_probe_gpu_micros,
592 auto_route_probe_selected_gpu_input_frames,
593 cpu_fallback_frames,
594 jpeg_passthrough_frames,
595 j2k_passthrough_frames,
596 j2k_direct_htj2k_frames,
597 jpeg_retile_frames,
598 jpeg_retile_rejected_frames,
599 jpeg_retile_source_unsupported_frames,
600 jpeg_retile_geometry_mismatch_frames,
601 jpeg_retile_profile_unsupported_frames,
602 jpeg_retile_mcu_invalid_frames,
603 jpeg_retile_us,
604 jpeg_retile_to_htj2k_53_frames,
605 jpeg_cpu_encode_frames,
606 jpeg_metal_encode_frames,
607 jpeg_decode_fallback_frames,
608 ]
609 );
610 saturating_add_fields!(
611 self.jpeg_direct_htj2k,
612 other.jpeg_direct_htj2k,
613 [
614 jpeg_direct_htj2k_53_frames,
615 jpeg_direct_htj2k_97_frames,
616 jpeg_direct_htj2k_rejected_frames,
617 jpeg_direct_htj2k_extract_micros,
618 jpeg_direct_htj2k_repack_micros,
619 jpeg_direct_htj2k_transform_micros,
620 jpeg_direct_htj2k_accelerator_micros,
621 jpeg_direct_htj2k_cpu_fallback_micros,
622 jpeg_direct_htj2k_dwt_decompose_micros,
623 jpeg_direct_htj2k_dwt97_pack_upload_micros,
624 jpeg_direct_htj2k_dwt97_idct_row_lift_micros,
625 jpeg_direct_htj2k_dwt97_column_lift_micros,
626 jpeg_direct_htj2k_dwt97_quantize_codeblock_micros,
627 jpeg_direct_htj2k_dwt97_ht_encode_micros,
628 jpeg_direct_htj2k_dwt97_ht_kernel_micros,
629 jpeg_direct_htj2k_dwt97_ht_status_readback_micros,
630 jpeg_direct_htj2k_dwt97_ht_compact_micros,
631 jpeg_direct_htj2k_dwt97_ht_output_readback_micros,
632 jpeg_direct_htj2k_dwt97_ht_codeblock_dispatches,
633 jpeg_direct_htj2k_dwt97_readback_micros,
634 jpeg_direct_htj2k_htj2k_encode_micros,
635 jpeg_direct_htj2k_encode_accelerator_dispatches,
636 jpeg_direct_htj2k_encode_ht_code_block_dispatches,
637 jpeg_direct_htj2k_encode_packetization_dispatches,
638 jpeg_direct_htj2k_batch_count,
639 jpeg_direct_htj2k_batch_jobs,
640 jpeg_direct_htj2k_accelerator_attempts,
641 jpeg_direct_htj2k_accelerator_jobs,
642 jpeg_direct_htj2k_accelerator_dispatches,
643 jpeg_direct_htj2k_accelerator_dispatched_jobs,
644 jpeg_direct_htj2k_cpu_fallback_jobs,
645 ]
646 );
647 saturating_add_fields!(
648 self.timings,
649 other.timings,
650 [
651 input_decode_micros,
652 compose_micros,
653 encode_micros,
654 validation_micros,
655 gpu_dispatch_micros,
656 streaming_write_micros,
657 pixel_data_patch_micros,
658 writer_backpressure_micros,
659 write_micros,
660 ]
661 );
662 saturating_add_fields!(
663 self.gpu_encode,
664 other.gpu_encode,
665 [
666 gpu_encode_wall_micros,
667 gpu_encode_hardware_micros,
668 gpu_encode_dispatch_overhead_micros,
669 gpu_encode_plan_micros,
670 gpu_encode_prepare_submit_micros,
671 gpu_encode_ht_table_build_micros,
672 gpu_encode_ht_buffer_allocation_micros,
673 gpu_encode_ht_command_encode_micros,
674 gpu_encode_codestream_wait_micros,
675 gpu_encode_chunk_count,
676 gpu_encode_tile_count,
677 gpu_encode_code_block_count,
678 ]
679 );
680 max_fields!(
681 self.gpu_encode,
682 other.gpu_encode,
683 [
684 gpu_encode_configured_inflight_tiles,
685 gpu_encode_effective_inflight_tiles,
686 gpu_encode_max_observed_inflight_tiles,
687 gpu_encode_configured_memory_mib,
688 gpu_encode_effective_memory_mib,
689 gpu_pipeline_depth,
690 gpu_row_batch_rows_max,
691 gpu_row_batch_target_tiles,
692 ]
693 );
694 }
695
696 pub(crate) fn record_cpu_input(&mut self) {
697 increment_u64(&mut self.routes.total_frames);
698 increment_u64(&mut self.routes.cpu_input_frames);
699 }
700
701 pub(crate) fn record_gpu_input(&mut self) {
702 increment_u64(&mut self.routes.total_frames);
703 increment_u64(&mut self.routes.gpu_input_decode_frames);
704 }
705
706 pub(crate) fn record_passthrough_frame(&mut self) {
707 increment_u64(&mut self.routes.total_frames);
708 increment_u64(&mut self.routes.jpeg_passthrough_frames);
709 }
710
711 pub(crate) fn record_j2k_passthrough_frame(&mut self) {
712 increment_u64(&mut self.routes.total_frames);
713 increment_u64(&mut self.routes.j2k_passthrough_frames);
714 }
715
716 pub(crate) fn record_j2k_direct_htj2k_frame(&mut self, transcode_micros: u128) {
717 increment_u64(&mut self.routes.total_frames);
718 increment_u64(&mut self.routes.j2k_direct_htj2k_frames);
719 self.timings.encode_micros = self.timings.encode_micros.saturating_add(transcode_micros);
720 }
721
722 pub(crate) fn record_jpeg_direct_htj2k_53_frame(&mut self, transcode_micros: u128) {
723 increment_u64(&mut self.routes.total_frames);
724 increment_u64(&mut self.jpeg_direct_htj2k.jpeg_direct_htj2k_53_frames);
725 self.timings.encode_micros = self.timings.encode_micros.saturating_add(transcode_micros);
726 }
727
728 pub(crate) fn record_jpeg_direct_htj2k_97_frame(&mut self, transcode_micros: u128) {
729 increment_u64(&mut self.routes.total_frames);
730 increment_u64(&mut self.jpeg_direct_htj2k.jpeg_direct_htj2k_97_frames);
731 self.timings.encode_micros = self.timings.encode_micros.saturating_add(transcode_micros);
732 }
733
734 pub(crate) fn record_jpeg_direct_htj2k_rejected_frame(&mut self) {
735 increment_u64(&mut self.jpeg_direct_htj2k.jpeg_direct_htj2k_rejected_frames);
736 }
737
738 pub(crate) fn record_jpeg_direct_htj2k_timings(
739 &mut self,
740 timings: j2k_transcode::TranscodeTimingReport,
741 ) {
742 saturating_add_values!(
743 self.jpeg_direct_htj2k,
744 [
745 jpeg_direct_htj2k_extract_micros => timings.jpeg_dct_extract_us,
746 jpeg_direct_htj2k_repack_micros => timings.jpeg_dct_repack_us,
747 jpeg_direct_htj2k_transform_micros => timings.dct_to_wavelet_total_us,
748 jpeg_direct_htj2k_accelerator_micros => timings.dct_to_wavelet_accelerator_us,
749 jpeg_direct_htj2k_cpu_fallback_micros => timings.dct_to_wavelet_cpu_fallback_us,
750 jpeg_direct_htj2k_dwt_decompose_micros => timings.dwt_decompose_us,
751 jpeg_direct_htj2k_dwt97_pack_upload_micros => timings.dwt97_batch_pack_upload_us,
752 jpeg_direct_htj2k_dwt97_idct_row_lift_micros => timings.dwt97_batch_idct_row_lift_us,
753 jpeg_direct_htj2k_dwt97_column_lift_micros => timings.dwt97_batch_column_lift_us,
754 jpeg_direct_htj2k_dwt97_quantize_codeblock_micros => timings.dwt97_batch_quantize_codeblock_us,
755 jpeg_direct_htj2k_dwt97_ht_encode_micros => timings.dwt97_batch_ht_encode_us,
756 jpeg_direct_htj2k_dwt97_ht_kernel_micros => timings.dwt97_batch_ht_kernel_us,
757 jpeg_direct_htj2k_dwt97_ht_status_readback_micros => timings.dwt97_batch_ht_status_readback_us,
758 jpeg_direct_htj2k_dwt97_ht_compact_micros => timings.dwt97_batch_ht_compact_us,
759 jpeg_direct_htj2k_dwt97_ht_output_readback_micros => timings.dwt97_batch_ht_output_readback_us,
760 jpeg_direct_htj2k_dwt97_ht_codeblock_dispatches => timings.dwt97_batch_ht_codeblock_dispatches as u64,
761 jpeg_direct_htj2k_dwt97_readback_micros => timings.dwt97_batch_readback_us,
762 jpeg_direct_htj2k_htj2k_encode_micros => timings.htj2k_encode_us,
763 jpeg_direct_htj2k_encode_accelerator_dispatches => timings.htj2k_encode_accelerator_dispatches as u64,
764 jpeg_direct_htj2k_encode_ht_code_block_dispatches => timings.htj2k_encode_ht_code_block_dispatches as u64,
765 jpeg_direct_htj2k_encode_packetization_dispatches => timings.htj2k_encode_packetization_dispatches as u64,
766 jpeg_direct_htj2k_batch_count => timings.batch_count as u64,
767 jpeg_direct_htj2k_batch_jobs => timings.batch_jobs as u64,
768 jpeg_direct_htj2k_accelerator_attempts => timings.accelerator_attempts as u64,
769 jpeg_direct_htj2k_accelerator_jobs => timings.accelerator_jobs as u64,
770 jpeg_direct_htj2k_accelerator_dispatches => timings.accelerator_dispatches as u64,
771 jpeg_direct_htj2k_accelerator_dispatched_jobs => timings.accelerator_dispatched_jobs as u64,
772 jpeg_direct_htj2k_cpu_fallback_jobs => timings.cpu_fallback_jobs as u64,
773 ]
774 );
775 }
776
777 pub(crate) fn record_jpeg_retile_baseline_frame(&mut self, duration: Duration) {
778 increment_u64(&mut self.routes.total_frames);
779 increment_u64(&mut self.routes.jpeg_retile_frames);
780 add_duration_micros(&mut self.routes.jpeg_retile_us, duration);
781 }
782
783 pub(crate) fn record_jpeg_retile_to_htj2k_53_frame(&mut self, duration: Duration) {
784 increment_u64(&mut self.routes.jpeg_retile_frames);
785 increment_u64(&mut self.routes.jpeg_retile_to_htj2k_53_frames);
786 add_duration_micros(&mut self.routes.jpeg_retile_us, duration);
787 }
788
789 pub(crate) fn record_jpeg_retile_rejected_frame(&mut self, reason: JpegRetileRejectionReason) {
790 increment_u64(&mut self.routes.jpeg_retile_rejected_frames);
791 match reason {
792 JpegRetileRejectionReason::SourceUnsupported => {
793 increment_u64(&mut self.routes.jpeg_retile_source_unsupported_frames);
794 }
795 JpegRetileRejectionReason::GeometryMismatch => {
796 increment_u64(&mut self.routes.jpeg_retile_geometry_mismatch_frames);
797 }
798 JpegRetileRejectionReason::ProfileUnsupported => {
799 increment_u64(&mut self.routes.jpeg_retile_profile_unsupported_frames);
800 }
801 JpegRetileRejectionReason::McuInvalid => {
802 increment_u64(&mut self.routes.jpeg_retile_mcu_invalid_frames);
803 }
804 }
805 }
806
807 pub(crate) fn record_pixel_profile(&mut self, profile: PixelProfile) {
808 match profile.components {
809 1 => self.routes.gray_frames = self.routes.gray_frames.saturating_add(1),
810 3 => self.routes.rgb_like_frames = self.routes.rgb_like_frames.saturating_add(1),
811 _ => {
812 self.routes.other_component_frames =
813 self.routes.other_component_frames.saturating_add(1);
814 }
815 }
816 match profile.bits_allocated {
817 8 => self.routes.bits8_frames = self.routes.bits8_frames.saturating_add(1),
818 16 => self.routes.bits16_frames = self.routes.bits16_frames.saturating_add(1),
819 _ => {
820 self.routes.other_bit_depth_frames =
821 self.routes.other_bit_depth_frames.saturating_add(1)
822 }
823 }
824 }
825
826 pub(crate) fn record_unknown_pixel_profile(&mut self) {
827 increment_u64(&mut self.routes.unknown_pixel_profile_frames);
828 }
829
830 pub(crate) fn record_transcode_route(&mut self, used_gpu_input: bool, used_gpu_encode: bool) {
831 if used_gpu_input || used_gpu_encode {
832 self.routes.gpu_transcode_frames = self.routes.gpu_transcode_frames.saturating_add(1);
833 if used_gpu_input && used_gpu_encode {
834 self.routes.resident_gpu_transcode_frames =
835 self.routes.resident_gpu_transcode_frames.saturating_add(1);
836 } else {
837 self.routes.partial_gpu_transcode_frames =
838 self.routes.partial_gpu_transcode_frames.saturating_add(1);
839 }
840 } else {
841 self.routes.cpu_fallback_frames = self.routes.cpu_fallback_frames.saturating_add(1);
842 }
843 }
844
845 pub(crate) fn record_gpu_batches(
846 &mut self,
847 input_decode_batches: u64,
848 compose_batches: u64,
849 encode_batches: u64,
850 ) {
851 self.routes.gpu_input_decode_batches = self
852 .routes
853 .gpu_input_decode_batches
854 .saturating_add(input_decode_batches);
855 self.routes.gpu_compose_batches = self
856 .routes
857 .gpu_compose_batches
858 .saturating_add(compose_batches);
859 self.routes.gpu_encode_batches = self
860 .routes
861 .gpu_encode_batches
862 .saturating_add(encode_batches);
863 }
864
865 #[cfg(all(feature = "metal", target_os = "macos"))]
866 pub(crate) fn record_gpu_encode_batch_stats(
867 &mut self,
868 stats: encode::DicomJ2kGpuEncodeBatchStats,
869 ) {
870 self.gpu_encode.gpu_encode_configured_inflight_tiles = self
871 .gpu_encode
872 .gpu_encode_configured_inflight_tiles
873 .max(stats.configured_inflight_tiles.unwrap_or(0) as u64);
874 self.gpu_encode.gpu_encode_effective_inflight_tiles = self
875 .gpu_encode
876 .gpu_encode_effective_inflight_tiles
877 .max(stats.effective_inflight_tiles as u64);
878 self.gpu_encode.gpu_encode_max_observed_inflight_tiles = self
879 .gpu_encode
880 .gpu_encode_max_observed_inflight_tiles
881 .max(stats.max_observed_inflight_tiles as u64);
882 self.gpu_encode.gpu_encode_configured_memory_mib = self
883 .gpu_encode
884 .gpu_encode_configured_memory_mib
885 .max(stats.configured_memory_mib.unwrap_or(0));
886 self.gpu_encode.gpu_encode_effective_memory_mib = self
887 .gpu_encode
888 .gpu_encode_effective_memory_mib
889 .max(stats.effective_memory_mib);
890 self.gpu_encode.gpu_encode_wall_micros = self
891 .gpu_encode
892 .gpu_encode_wall_micros
893 .saturating_add(duration_as_reported_micros(stats.encode_wall_duration));
894 self.gpu_encode.gpu_encode_plan_micros = self
895 .gpu_encode
896 .gpu_encode_plan_micros
897 .saturating_add(duration_as_reported_micros(stats.stage_stats.plan_duration));
898 self.gpu_encode.gpu_encode_prepare_submit_micros = self
899 .gpu_encode
900 .gpu_encode_prepare_submit_micros
901 .saturating_add(duration_as_reported_micros(
902 stats.stage_stats.prepare_submit_duration,
903 ));
904 self.gpu_encode.gpu_encode_ht_table_build_micros = self
905 .gpu_encode
906 .gpu_encode_ht_table_build_micros
907 .saturating_add(duration_as_reported_micros(
908 stats.stage_stats.ht_table_build_duration,
909 ));
910 self.gpu_encode.gpu_encode_ht_buffer_allocation_micros = self
911 .gpu_encode
912 .gpu_encode_ht_buffer_allocation_micros
913 .saturating_add(duration_as_reported_micros(
914 stats.stage_stats.ht_buffer_allocation_duration,
915 ));
916 self.gpu_encode.gpu_encode_ht_command_encode_micros = self
917 .gpu_encode
918 .gpu_encode_ht_command_encode_micros
919 .saturating_add(duration_as_reported_micros(
920 stats.stage_stats.ht_command_encode_duration,
921 ));
922 self.gpu_encode.gpu_encode_codestream_wait_micros = self
923 .gpu_encode
924 .gpu_encode_codestream_wait_micros
925 .saturating_add(duration_as_reported_micros(
926 stats.stage_stats.codestream_wait_duration,
927 ));
928 self.gpu_encode.gpu_encode_chunk_count = self
929 .gpu_encode
930 .gpu_encode_chunk_count
931 .saturating_add(stats.stage_stats.chunk_count as u64);
932 self.gpu_encode.gpu_encode_tile_count = self
933 .gpu_encode
934 .gpu_encode_tile_count
935 .saturating_add(stats.stage_stats.tile_count as u64);
936 self.gpu_encode.gpu_encode_code_block_count = self
937 .gpu_encode
938 .gpu_encode_code_block_count
939 .saturating_add(stats.stage_stats.code_block_count as u64);
940 }
941
942 #[cfg(all(feature = "metal", target_os = "macos"))]
943 pub(crate) fn record_auto_route_probe(
944 &mut self,
945 frames: u64,
946 cpu_duration: Duration,
947 gpu_duration: Duration,
948 gpu_batches: u64,
949 selected_gpu_input: bool,
950 ) {
951 self.routes.auto_route_probe_frames =
952 self.routes.auto_route_probe_frames.saturating_add(frames);
953 self.routes.auto_route_probe_gpu_batches = self
954 .routes
955 .auto_route_probe_gpu_batches
956 .saturating_add(gpu_batches);
957 self.routes.auto_route_probe_cpu_micros = self
958 .routes
959 .auto_route_probe_cpu_micros
960 .saturating_add(duration_as_reported_micros(cpu_duration));
961 self.routes.auto_route_probe_gpu_micros = self
962 .routes
963 .auto_route_probe_gpu_micros
964 .saturating_add(duration_as_reported_micros(gpu_duration));
965 if selected_gpu_input {
966 self.routes.auto_route_probe_selected_gpu_input_frames = self
967 .routes
968 .auto_route_probe_selected_gpu_input_frames
969 .saturating_add(frames);
970 }
971 }
972
973 pub(crate) fn record_jpeg_decode_fallback(&mut self) {
974 increment_u64(&mut self.routes.jpeg_decode_fallback_frames);
975 }
976
977 pub(crate) fn record_jpeg_cpu_fallback_route_classification(&mut self) {
978 increment_u64(&mut self.routes.total_frames);
979 increment_u64(&mut self.routes.cpu_fallback_frames);
980 increment_u64(&mut self.routes.jpeg_decode_fallback_frames);
981 self.record_unknown_pixel_profile();
982 }
983
984 pub(crate) fn record_j2k_passthrough_only_fallback_classification(&mut self) {
985 increment_u64(&mut self.routes.total_frames);
986 increment_u64(&mut self.routes.cpu_fallback_frames);
987 self.record_unknown_pixel_profile();
988 }
989
990 pub(crate) fn record_jpeg_cpu_encode(&mut self, duration: Duration) {
991 increment_u64(&mut self.routes.jpeg_cpu_encode_frames);
992 self.record_encode_duration(duration);
993 }
994
995 pub(crate) fn record_jpeg_metal_batch_encode(&mut self, frames: u64, duration: Duration) {
996 self.routes.jpeg_metal_encode_frames =
997 self.routes.jpeg_metal_encode_frames.saturating_add(frames);
998 self.record_encode_duration(duration);
999 self.record_gpu_dispatch_duration(duration);
1000 }
1001
1002 pub(crate) fn record_encoded_frame(&mut self, encoded: &encode::EncodedDicomJ2kFrame) {
1003 if encoded.used_device_encode {
1004 increment_u64(&mut self.routes.gpu_encode_frames);
1005 if let Some(duration) = encoded.gpu_encode_wall_duration {
1006 self.record_gpu_encode_wall_duration(duration);
1007 }
1008 self.record_gpu_dispatch_duration(encoded.encode_duration);
1009 self.record_gpu_encode_hardware_duration(
1010 encoded.device_gpu_duration,
1011 encoded.encode_duration,
1012 );
1013 }
1014 if encoded.used_device_validation {
1015 increment_u64(&mut self.routes.gpu_validation_frames);
1016 self.record_gpu_dispatch_duration(encoded.validation_duration);
1017 }
1018 self.record_encode_duration(encoded.encode_duration);
1019 self.record_validation_duration(encoded.validation_duration);
1020 }
1021
1022 pub(crate) fn record_input_decode_duration(&mut self, duration: Duration) {
1023 add_duration_micros(&mut self.timings.input_decode_micros, duration);
1024 }
1025
1026 pub(crate) fn record_gpu_input_decode_duration(&mut self, duration: Duration) {
1027 self.record_input_decode_duration(duration);
1028 self.record_gpu_dispatch_duration(duration);
1029 }
1030
1031 pub(crate) fn record_compose_duration(&mut self, duration: Duration) {
1032 add_duration_micros(&mut self.timings.compose_micros, duration);
1033 }
1034
1035 #[cfg(all(feature = "metal", target_os = "macos"))]
1036 pub(crate) fn record_gpu_compose_duration(&mut self, duration: Duration) {
1037 self.record_compose_duration(duration);
1038 self.record_gpu_dispatch_duration(duration);
1039 }
1040
1041 pub(crate) fn record_encode_duration(&mut self, duration: Duration) {
1042 add_duration_micros(&mut self.timings.encode_micros, duration);
1043 }
1044
1045 pub(crate) fn record_validation_duration(&mut self, duration: Duration) {
1046 add_duration_micros(&mut self.timings.validation_micros, duration);
1047 }
1048
1049 pub(crate) fn record_gpu_dispatch_duration(&mut self, duration: Duration) {
1050 add_duration_micros(&mut self.timings.gpu_dispatch_micros, duration);
1051 }
1052
1053 pub(crate) fn record_gpu_encode_hardware_duration(
1054 &mut self,
1055 gpu_duration: Option<Duration>,
1056 dispatch_duration: Duration,
1057 ) {
1058 let Some(gpu_duration) = gpu_duration else {
1059 return;
1060 };
1061 let hardware_micros = duration_as_reported_micros(gpu_duration);
1062 self.gpu_encode.gpu_encode_hardware_micros = self
1063 .gpu_encode
1064 .gpu_encode_hardware_micros
1065 .saturating_add(hardware_micros);
1066 let overhead = dispatch_duration.saturating_sub(gpu_duration);
1067 self.gpu_encode.gpu_encode_dispatch_overhead_micros = self
1068 .gpu_encode
1069 .gpu_encode_dispatch_overhead_micros
1070 .saturating_add(duration_as_reported_micros(overhead));
1071 }
1072
1073 pub(crate) fn record_gpu_encode_wall_duration(&mut self, duration: Duration) {
1074 add_duration_micros(&mut self.gpu_encode.gpu_encode_wall_micros, duration);
1075 }
1076
1077 pub(crate) fn record_write_duration(&mut self, duration: Duration) {
1078 add_duration_micros(&mut self.timings.write_micros, duration);
1079 }
1080
1081 pub(crate) fn record_streaming_write_duration(&mut self, duration: Duration) {
1082 add_duration_micros(&mut self.timings.streaming_write_micros, duration);
1083 }
1084
1085 pub(crate) fn record_pixel_data_patch_duration(&mut self, duration: Duration) {
1086 add_duration_micros(&mut self.timings.pixel_data_patch_micros, duration);
1087 }
1088
1089 #[cfg(all(feature = "metal", target_os = "macos"))]
1090 pub(crate) fn record_gpu_pipeline_depth(&mut self, depth: usize) {
1091 self.gpu_encode.gpu_pipeline_depth = self.gpu_encode.gpu_pipeline_depth.max(depth as u64);
1092 }
1093
1094 #[cfg(all(feature = "metal", target_os = "macos"))]
1095 pub(crate) fn record_gpu_row_batch_config(&mut self, rows: usize, target_tiles: Option<usize>) {
1096 self.gpu_encode.gpu_row_batch_rows_max =
1097 self.gpu_encode.gpu_row_batch_rows_max.max(rows as u64);
1098 self.gpu_encode.gpu_row_batch_target_tiles = self
1099 .gpu_encode
1100 .gpu_row_batch_target_tiles
1101 .max(target_tiles.unwrap_or(0) as u64);
1102 }
1103
1104 pub fn gpu_encode_effective_parallelism(&self) -> f64 {
1106 if self.gpu_encode.gpu_encode_wall_micros == 0 {
1107 0.0
1108 } else {
1109 self.gpu_encode.gpu_encode_hardware_micros as f64
1110 / self.gpu_encode.gpu_encode_wall_micros as f64
1111 }
1112 }
1113}
1114
1115fn increment_u64(value: &mut u64) {
1116 *value = value.saturating_add(1);
1117}
1118
1119fn add_duration_micros(value: &mut u128, duration: Duration) {
1120 *value = value.saturating_add(duration_as_reported_micros(duration));
1121}
1122
1123#[cfg(test)]
1124mod tests {
1125 use super::{
1126 ExportMetrics, GpuEncodeMetrics, JpegDirectHtj2kMetrics, JpegRetileRejectionReason,
1127 RouteCounters, WriteTimings,
1128 };
1129 use crate::tile::PixelProfile;
1130 use j2k_transcode::TranscodeTimingReport;
1131 use std::time::Duration;
1132
1133 #[test]
1134 fn dicom_export_metrics_serializes_stable_public_fields() {
1135 let metrics = ExportMetrics {
1136 routes: RouteCounters {
1137 total_frames: 10,
1138 cpu_fallback_frames: 4,
1139 ..RouteCounters::default()
1140 },
1141 jpeg_direct_htj2k: JpegDirectHtj2kMetrics {
1142 jpeg_direct_htj2k_rejected_frames: 3,
1143 ..JpegDirectHtj2kMetrics::default()
1144 },
1145 gpu_encode: GpuEncodeMetrics {
1146 gpu_encode_wall_micros: 5_000,
1147 gpu_encode_hardware_micros: 2_500,
1148 ..GpuEncodeMetrics::default()
1149 },
1150 ..ExportMetrics::default()
1151 };
1152
1153 let value = serde_json::to_value(metrics).expect("serialize metrics");
1154
1155 assert_eq!(value["total_frames"], 10);
1156 assert_eq!(value["jpeg_direct_htj2k_rejected_frames"], 3);
1157 assert_eq!(value["cpu_fallback_frames"], 4);
1158 assert_eq!(value["gpu_encode_wall_micros"], 5_000);
1159 assert_eq!(value["gpu_encode_hardware_micros"], 2_500);
1160 assert_eq!(value["gpu_encode_effective_parallelism"], 0.5);
1161 assert!(value.get("jpeg_retile_to_htj2k_53_frames").is_some());
1162 assert!(value.get("jpeg_retile_source_unsupported_frames").is_some());
1163 assert!(value.get("jpeg_retile_geometry_mismatch_frames").is_some());
1164 assert!(value
1165 .get("jpeg_retile_profile_unsupported_frames")
1166 .is_some());
1167 assert!(value.get("jpeg_retile_mcu_invalid_frames").is_some());
1168 assert!(value.get("writer_backpressure_micros").is_some());
1169 assert_eq!(
1170 value
1171 .as_object()
1172 .expect("metrics serialize as object")
1173 .len(),
1174 ExportMetrics::SERIALIZED_FIELD_COUNT
1175 );
1176 }
1177
1178 #[test]
1179 fn metrics_aggregation_saturates_counters_and_preserves_configuration_maxima() {
1180 let mut aggregate = ExportMetrics {
1181 routes: RouteCounters {
1182 total_frames: u64::MAX,
1183 jpeg_retile_frames: 2,
1184 ..RouteCounters::default()
1185 },
1186 jpeg_direct_htj2k: JpegDirectHtj2kMetrics {
1187 jpeg_direct_htj2k_53_frames: 3,
1188 ..JpegDirectHtj2kMetrics::default()
1189 },
1190 gpu_encode: GpuEncodeMetrics {
1191 gpu_encode_configured_inflight_tiles: 8,
1192 gpu_encode_wall_micros: 5,
1193 ..GpuEncodeMetrics::default()
1194 },
1195 ..ExportMetrics::default()
1196 };
1197 let mut next = ExportMetrics {
1198 routes: RouteCounters {
1199 total_frames: 1,
1200 jpeg_retile_frames: 7,
1201 ..RouteCounters::default()
1202 },
1203 jpeg_direct_htj2k: JpegDirectHtj2kMetrics {
1204 jpeg_direct_htj2k_53_frames: 11,
1205 ..JpegDirectHtj2kMetrics::default()
1206 },
1207 gpu_encode: GpuEncodeMetrics {
1208 gpu_encode_configured_inflight_tiles: 4,
1209 gpu_encode_effective_memory_mib: 32,
1210 gpu_encode_wall_micros: 13,
1211 ..GpuEncodeMetrics::default()
1212 },
1213 ..ExportMetrics::default()
1214 };
1215 next.timings.write_micros = 17;
1216
1217 aggregate.add_assign(next);
1218
1219 assert_eq!(aggregate.routes.total_frames, u64::MAX);
1220 assert_eq!(aggregate.routes.jpeg_retile_frames, 9);
1221 assert_eq!(aggregate.jpeg_direct_htj2k.jpeg_direct_htj2k_53_frames, 14);
1222 assert_eq!(aggregate.timings.write_micros, 17);
1223 assert_eq!(aggregate.gpu_encode.gpu_encode_wall_micros, 18);
1224 assert_eq!(aggregate.gpu_encode.gpu_encode_configured_inflight_tiles, 8);
1225 assert_eq!(aggregate.gpu_encode.gpu_encode_effective_memory_mib, 32);
1226 }
1227
1228 fn fully_populated_metrics() -> ExportMetrics {
1229 ExportMetrics {
1230 routes: RouteCounters {
1231 total_frames: 1,
1232 cpu_input_frames: 1,
1233 gpu_input_decode_frames: 1,
1234 gpu_encode_frames: 1,
1235 gpu_validation_frames: 1,
1236 gray_frames: 1,
1237 rgb_like_frames: 1,
1238 other_component_frames: 1,
1239 unknown_pixel_profile_frames: 1,
1240 bits8_frames: 1,
1241 bits16_frames: 1,
1242 other_bit_depth_frames: 1,
1243 gpu_transcode_frames: 1,
1244 resident_gpu_transcode_frames: 1,
1245 partial_gpu_transcode_frames: 1,
1246 gpu_input_decode_batches: 1,
1247 gpu_compose_batches: 1,
1248 gpu_encode_batches: 1,
1249 auto_route_probe_frames: 1,
1250 auto_route_probe_gpu_batches: 1,
1251 auto_route_probe_cpu_micros: 1,
1252 auto_route_probe_gpu_micros: 1,
1253 auto_route_probe_selected_gpu_input_frames: 1,
1254 cpu_fallback_frames: 1,
1255 jpeg_passthrough_frames: 1,
1256 j2k_passthrough_frames: 1,
1257 j2k_direct_htj2k_frames: 1,
1258 jpeg_retile_frames: 1,
1259 jpeg_retile_rejected_frames: 1,
1260 jpeg_retile_source_unsupported_frames: 1,
1261 jpeg_retile_geometry_mismatch_frames: 1,
1262 jpeg_retile_profile_unsupported_frames: 1,
1263 jpeg_retile_mcu_invalid_frames: 1,
1264 jpeg_retile_us: 1,
1265 jpeg_retile_to_htj2k_53_frames: 1,
1266 jpeg_cpu_encode_frames: 1,
1267 jpeg_metal_encode_frames: 1,
1268 jpeg_decode_fallback_frames: 1,
1269 },
1270 jpeg_direct_htj2k: JpegDirectHtj2kMetrics {
1271 jpeg_direct_htj2k_53_frames: 1,
1272 jpeg_direct_htj2k_97_frames: 1,
1273 jpeg_direct_htj2k_rejected_frames: 1,
1274 jpeg_direct_htj2k_extract_micros: 1,
1275 jpeg_direct_htj2k_repack_micros: 1,
1276 jpeg_direct_htj2k_transform_micros: 1,
1277 jpeg_direct_htj2k_accelerator_micros: 1,
1278 jpeg_direct_htj2k_cpu_fallback_micros: 1,
1279 jpeg_direct_htj2k_dwt_decompose_micros: 1,
1280 jpeg_direct_htj2k_dwt97_pack_upload_micros: 1,
1281 jpeg_direct_htj2k_dwt97_idct_row_lift_micros: 1,
1282 jpeg_direct_htj2k_dwt97_column_lift_micros: 1,
1283 jpeg_direct_htj2k_dwt97_quantize_codeblock_micros: 1,
1284 jpeg_direct_htj2k_dwt97_ht_encode_micros: 1,
1285 jpeg_direct_htj2k_dwt97_ht_kernel_micros: 1,
1286 jpeg_direct_htj2k_dwt97_ht_status_readback_micros: 1,
1287 jpeg_direct_htj2k_dwt97_ht_compact_micros: 1,
1288 jpeg_direct_htj2k_dwt97_ht_output_readback_micros: 1,
1289 jpeg_direct_htj2k_dwt97_ht_codeblock_dispatches: 1,
1290 jpeg_direct_htj2k_dwt97_readback_micros: 1,
1291 jpeg_direct_htj2k_htj2k_encode_micros: 1,
1292 jpeg_direct_htj2k_encode_accelerator_dispatches: 1,
1293 jpeg_direct_htj2k_encode_ht_code_block_dispatches: 1,
1294 jpeg_direct_htj2k_encode_packetization_dispatches: 1,
1295 jpeg_direct_htj2k_batch_count: 1,
1296 jpeg_direct_htj2k_batch_jobs: 1,
1297 jpeg_direct_htj2k_accelerator_attempts: 1,
1298 jpeg_direct_htj2k_accelerator_jobs: 1,
1299 jpeg_direct_htj2k_accelerator_dispatches: 1,
1300 jpeg_direct_htj2k_accelerator_dispatched_jobs: 1,
1301 jpeg_direct_htj2k_cpu_fallback_jobs: 1,
1302 },
1303 gpu_encode: GpuEncodeMetrics {
1304 gpu_encode_configured_inflight_tiles: 1,
1305 gpu_encode_effective_inflight_tiles: 1,
1306 gpu_encode_max_observed_inflight_tiles: 1,
1307 gpu_encode_configured_memory_mib: 1,
1308 gpu_encode_effective_memory_mib: 1,
1309 gpu_encode_wall_micros: 1,
1310 gpu_encode_hardware_micros: 1,
1311 gpu_encode_dispatch_overhead_micros: 1,
1312 gpu_encode_plan_micros: 1,
1313 gpu_encode_prepare_submit_micros: 1,
1314 gpu_encode_ht_table_build_micros: 1,
1315 gpu_encode_ht_buffer_allocation_micros: 1,
1316 gpu_encode_ht_command_encode_micros: 1,
1317 gpu_encode_codestream_wait_micros: 1,
1318 gpu_encode_chunk_count: 1,
1319 gpu_encode_tile_count: 1,
1320 gpu_encode_code_block_count: 1,
1321 gpu_pipeline_depth: 1,
1322 gpu_row_batch_rows_max: 1,
1323 gpu_row_batch_target_tiles: 1,
1324 },
1325 timings: WriteTimings {
1326 input_decode_micros: 1,
1327 compose_micros: 1,
1328 encode_micros: 1,
1329 validation_micros: 1,
1330 gpu_dispatch_micros: 1,
1331 streaming_write_micros: 1,
1332 pixel_data_patch_micros: 1,
1333 writer_backpressure_micros: 1,
1334 write_micros: 1,
1335 },
1336 }
1337 }
1338
1339 #[test]
1340 fn every_serialized_metric_field_has_aggregation_behavior() {
1341 let mut aggregate = ExportMetrics::default();
1342
1343 aggregate.add_assign(fully_populated_metrics());
1344
1345 let aggregated = serde_json::to_value(aggregate).expect("serialize aggregate metrics");
1346 for (field, value) in aggregated
1347 .as_object()
1348 .expect("metrics serialize as a flat object")
1349 {
1350 assert_eq!(
1351 value.as_f64(),
1352 Some(1.0),
1353 "serialized metric `{field}` was not aggregated"
1354 );
1355 }
1356 }
1357
1358 #[test]
1359 fn metrics_record_route_pixel_and_timing_counters() {
1360 let mut metrics = ExportMetrics::default();
1361
1362 metrics.record_cpu_input();
1363 metrics.record_gpu_input();
1364 metrics.record_passthrough_frame();
1365 metrics.record_j2k_passthrough_frame();
1366 metrics.record_j2k_direct_htj2k_frame(11);
1367 metrics.record_jpeg_direct_htj2k_53_frame(13);
1368 metrics.record_jpeg_direct_htj2k_97_frame(17);
1369 metrics.record_jpeg_direct_htj2k_rejected_frame();
1370 metrics.record_jpeg_retile_baseline_frame(Duration::from_micros(19));
1371 metrics.record_jpeg_retile_to_htj2k_53_frame(Duration::from_micros(23));
1372 metrics.record_jpeg_retile_rejected_frame(JpegRetileRejectionReason::SourceUnsupported);
1373 metrics.record_jpeg_retile_rejected_frame(JpegRetileRejectionReason::GeometryMismatch);
1374 metrics.record_jpeg_retile_rejected_frame(JpegRetileRejectionReason::ProfileUnsupported);
1375 metrics.record_jpeg_retile_rejected_frame(JpegRetileRejectionReason::McuInvalid);
1376 metrics.record_pixel_profile(PixelProfile {
1377 components: 1,
1378 bits_allocated: 8,
1379 photometric_interpretation: "MONOCHROME2",
1380 });
1381 metrics.record_pixel_profile(PixelProfile {
1382 components: 3,
1383 bits_allocated: 16,
1384 photometric_interpretation: "RGB",
1385 });
1386 metrics.record_pixel_profile(PixelProfile {
1387 components: 4,
1388 bits_allocated: 12,
1389 photometric_interpretation: "RGBA",
1390 });
1391 metrics.record_unknown_pixel_profile();
1392 metrics.record_transcode_route(true, true);
1393 metrics.record_transcode_route(true, false);
1394 metrics.record_transcode_route(false, false);
1395 metrics.record_gpu_batches(2, 3, 4);
1396 metrics.record_jpeg_decode_fallback();
1397 metrics.record_jpeg_cpu_fallback_route_classification();
1398 metrics.record_j2k_passthrough_only_fallback_classification();
1399 metrics.record_jpeg_cpu_encode(Duration::from_micros(29));
1400 metrics.record_jpeg_metal_batch_encode(5, Duration::from_micros(31));
1401 metrics.record_input_decode_duration(Duration::from_micros(37));
1402 metrics.record_gpu_input_decode_duration(Duration::from_micros(41));
1403 metrics.record_compose_duration(Duration::from_micros(43));
1404 metrics.record_encode_duration(Duration::from_micros(47));
1405 metrics.record_validation_duration(Duration::from_micros(53));
1406 metrics.record_gpu_dispatch_duration(Duration::from_micros(59));
1407 metrics.record_gpu_encode_hardware_duration(
1408 Some(Duration::from_micros(61)),
1409 Duration::from_micros(67),
1410 );
1411 metrics.record_gpu_encode_wall_duration(Duration::from_micros(69));
1412 metrics.record_write_duration(Duration::from_micros(71));
1413 metrics.record_streaming_write_duration(Duration::from_micros(73));
1414 metrics.record_pixel_data_patch_duration(Duration::from_micros(79));
1415
1416 metrics.record_jpeg_direct_htj2k_timings(TranscodeTimingReport {
1417 source_raw_probe_us: 0,
1418 read_region_decode_us: 0,
1419 compose_pad_us: 0,
1420 generated_jpeg_encode_us: 0,
1421 jpeg_dct_extract_us: 1,
1422 jpeg_dct_repack_us: 2,
1423 dct_to_wavelet_total_us: 3,
1424 dct_to_wavelet_accelerator_us: 4,
1425 dct_to_wavelet_cpu_fallback_us: 5,
1426 dwt_decompose_us: 6,
1427 dwt97_batch_pack_upload_us: 7,
1428 dwt97_batch_pack_upload_transfers: 0,
1429 dwt97_batch_pack_upload_bytes: 0,
1430 dwt97_batch_resident_dct_handoff_count: 0,
1431 dwt97_batch_idct_row_lift_us: 8,
1432 dwt97_batch_column_lift_us: 9,
1433 dwt97_batch_resident_dwt_handoff_count: 0,
1434 dwt97_batch_quantize_codeblock_us: 10,
1435 dwt97_batch_ht_encode_us: 11,
1436 dwt97_batch_ht_kernel_us: 12,
1437 dwt97_batch_ht_status_readback_us: 13,
1438 dwt97_batch_ht_status_readback_transfers: 0,
1439 dwt97_batch_ht_status_readback_bytes: 0,
1440 dwt97_batch_ht_compact_us: 14,
1441 dwt97_batch_ht_output_readback_us: 15,
1442 dwt97_batch_ht_output_readback_transfers: 0,
1443 dwt97_batch_ht_output_readback_bytes: 0,
1444 dwt97_batch_ht_codeblock_dispatches: 16,
1445 dwt97_batch_readback_us: 17,
1446 dwt97_batch_readback_transfers: 0,
1447 dwt97_batch_readback_bytes: 0,
1448 htj2k_encode_us: 18,
1449 htj2k_encode_accelerator_dispatches: 19,
1450 htj2k_encode_ht_code_block_dispatches: 20,
1451 htj2k_encode_packetization_dispatches: 21,
1452 dicom_spool_write_us: 0,
1453 dicom_final_write_us: 0,
1454 tile_count: 0,
1455 component_count: 0,
1456 batch_count: 22,
1457 batch_jobs: 23,
1458 accelerator_attempts: 24,
1459 accelerator_jobs: 25,
1460 accelerator_dispatches: 26,
1461 accelerator_dispatched_jobs: 27,
1462 cpu_fallback_jobs: 28,
1463 });
1464
1465 assert_eq!(metrics.routes.total_frames, 10);
1466 assert_eq!(metrics.route_passthrough_frames(), 2);
1467 assert_eq!(metrics.routes.j2k_direct_htj2k_frames, 1);
1468 assert_eq!(metrics.jpeg_direct_htj2k.jpeg_direct_htj2k_53_frames, 1);
1469 assert_eq!(metrics.jpeg_direct_htj2k.jpeg_direct_htj2k_97_frames, 1);
1470 assert_eq!(
1471 metrics.jpeg_direct_htj2k.jpeg_direct_htj2k_rejected_frames,
1472 1
1473 );
1474 assert_eq!(metrics.jpeg_retile_baseline_frames(), 1);
1475 assert_eq!(metrics.routes.jpeg_retile_to_htj2k_53_frames, 1);
1476 assert_eq!(metrics.routes.jpeg_retile_rejected_frames, 4);
1477 assert_eq!(metrics.routes.jpeg_retile_source_unsupported_frames, 1);
1478 assert_eq!(metrics.routes.jpeg_retile_geometry_mismatch_frames, 1);
1479 assert_eq!(metrics.routes.jpeg_retile_profile_unsupported_frames, 1);
1480 assert_eq!(metrics.routes.jpeg_retile_mcu_invalid_frames, 1);
1481 assert_eq!(metrics.routes.gray_frames, 1);
1482 assert_eq!(metrics.routes.rgb_like_frames, 1);
1483 assert_eq!(metrics.routes.other_component_frames, 1);
1484 assert_eq!(metrics.routes.bits8_frames, 1);
1485 assert_eq!(metrics.routes.bits16_frames, 1);
1486 assert_eq!(metrics.routes.other_bit_depth_frames, 1);
1487 assert_eq!(metrics.routes.unknown_pixel_profile_frames, 3);
1488 assert_eq!(metrics.routes.gpu_transcode_frames, 2);
1489 assert_eq!(metrics.routes.resident_gpu_transcode_frames, 1);
1490 assert_eq!(metrics.routes.partial_gpu_transcode_frames, 1);
1491 assert_eq!(metrics.routes.cpu_fallback_frames, 3);
1492 assert_eq!(metrics.routes.gpu_input_decode_batches, 2);
1493 assert_eq!(metrics.routes.gpu_compose_batches, 3);
1494 assert_eq!(metrics.routes.gpu_encode_batches, 4);
1495 assert_eq!(metrics.routes.jpeg_decode_fallback_frames, 2);
1496 assert_eq!(metrics.routes.jpeg_cpu_encode_frames, 1);
1497 assert_eq!(metrics.routes.jpeg_metal_encode_frames, 5);
1498 assert_eq!(
1499 metrics.jpeg_direct_htj2k.jpeg_direct_htj2k_extract_micros,
1500 1
1501 );
1502 assert_eq!(
1503 metrics
1504 .jpeg_direct_htj2k
1505 .jpeg_direct_htj2k_encode_packetization_dispatches,
1506 21
1507 );
1508 assert_eq!(
1509 metrics
1510 .jpeg_direct_htj2k
1511 .jpeg_direct_htj2k_dwt97_ht_encode_micros,
1512 11
1513 );
1514 assert_eq!(
1515 metrics
1516 .jpeg_direct_htj2k
1517 .jpeg_direct_htj2k_dwt97_ht_kernel_micros,
1518 12
1519 );
1520 assert_eq!(
1521 metrics
1522 .jpeg_direct_htj2k
1523 .jpeg_direct_htj2k_dwt97_ht_status_readback_micros,
1524 13
1525 );
1526 assert_eq!(
1527 metrics
1528 .jpeg_direct_htj2k
1529 .jpeg_direct_htj2k_dwt97_ht_compact_micros,
1530 14
1531 );
1532 assert_eq!(
1533 metrics
1534 .jpeg_direct_htj2k
1535 .jpeg_direct_htj2k_dwt97_ht_output_readback_micros,
1536 15
1537 );
1538 assert_eq!(
1539 metrics
1540 .jpeg_direct_htj2k
1541 .jpeg_direct_htj2k_dwt97_ht_codeblock_dispatches,
1542 16
1543 );
1544 assert_eq!(
1545 metrics
1546 .jpeg_direct_htj2k
1547 .jpeg_direct_htj2k_cpu_fallback_jobs,
1548 28
1549 );
1550 assert_eq!(metrics.gpu_encode.gpu_encode_wall_micros, 69);
1551 assert_eq!(metrics.gpu_encode.gpu_encode_hardware_micros, 61);
1552 assert_eq!(metrics.gpu_encode.gpu_encode_dispatch_overhead_micros, 6);
1553 assert_eq!(metrics.timings.streaming_write_micros, 73);
1554 assert_eq!(metrics.timings.pixel_data_patch_micros, 79);
1555 assert_eq!(metrics.route_unclassified_frames(), 0);
1556 }
1557}