common/config.rs
1//! Configuration types for runtime and execution settings
2
3use serde::{Deserialize, Serialize};
4
5/// Dry-run mode for previewing operations without executing them
6#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Serialize, Deserialize)]
7pub enum DryRunMode {
8 /// show only what would be copied/linked/removed
9 #[value(name = "brief")]
10 Brief,
11 /// also show skipped files
12 #[value(name = "all")]
13 All,
14 /// show skipped files with the pattern that caused the skip
15 #[value(name = "explain")]
16 Explain,
17}
18
19/// Runtime configuration for tokio and thread pools
20#[derive(Debug, Clone, Copy, Default)]
21pub struct RuntimeConfig {
22 /// Number of worker threads (0 = number of CPU cores)
23 pub max_workers: usize,
24 /// Number of blocking threads (0 = tokio default of 512)
25 pub max_blocking_threads: usize,
26}
27
28/// Throttling configuration for resource control
29#[derive(Debug, Clone, Copy, Default)]
30pub struct ThrottleConfig {
31 /// Maximum number of open files (None = 80% of system limit)
32 pub max_open_files: Option<usize>,
33 /// Operations per second throttle (0 = no throttle)
34 pub ops_throttle: usize,
35 /// I/O operations per second throttle (0 = no throttle)
36 pub iops_throttle: usize,
37 /// Chunk size for I/O operations (bytes)
38 pub chunk_size: u64,
39}
40
41impl ThrottleConfig {
42 /// Validate configuration and return errors if invalid
43 pub fn validate(&self) -> Result<(), String> {
44 if self.iops_throttle > 0 && self.chunk_size == 0 {
45 return Err("chunk_size must be specified when using iops_throttle".to_string());
46 }
47 Ok(())
48 }
49}
50
51/// Output and logging configuration
52#[derive(Debug, Clone, Copy, Default)]
53pub struct OutputConfig {
54 /// Suppress error output
55 pub quiet: bool,
56 /// Verbosity level: 0=ERROR, 1=INFO, 2=DEBUG, 3=TRACE
57 pub verbose: u8,
58 /// Print summary statistics at the end
59 pub print_summary: bool,
60 /// When true, `run()` will not print text runtime stats after the summary.
61 /// Used when the summary itself includes runtime stats (e.g. JSON format).
62 pub suppress_runtime_stats: bool,
63}
64
65/// Warnings and adjustments for dry-run mode.
66///
67/// When dry-run is active, progress is suppressed (it interferes with stdout
68/// output) and `--summary` is suppressed unless `-v` is also active (verbose
69/// independently enables summary in `common::run()`). This struct collects
70/// warnings about the suppressed flags to print after the operation completes.
71pub struct DryRunWarnings {
72 warnings: Vec<String>,
73}
74impl DryRunWarnings {
75 /// Build dry-run warnings based on which flags were specified.
76 ///
77 /// `has_progress` — whether any progress flags were specified.
78 /// `has_summary` — whether --summary was specified.
79 /// `verbose` — verbosity level; when > 0 summary is printed by `common::run()`
80 /// regardless of `print_summary`, so we skip the "ignored" warning.
81 /// `has_overwrite` — whether --overwrite was specified (not applicable to rrm).
82 /// `has_filters` — whether --include/--exclude/--filter-file was specified.
83 /// `has_destination` — true for rcp/rlink (copy/link to destination), false for rrm.
84 #[must_use]
85 pub fn new(
86 has_progress: bool,
87 has_summary: bool,
88 verbose: u8,
89 has_overwrite: bool,
90 has_filters: bool,
91 has_destination: bool,
92 ) -> Self {
93 let mut warnings = Vec::new();
94 if has_progress {
95 warnings.push("dry-run: --progress was ignored".to_string());
96 }
97 if has_summary && verbose == 0 {
98 warnings.push("dry-run: --summary was ignored".to_string());
99 }
100 if has_overwrite {
101 warnings.push(
102 "dry-run: --overwrite was ignored; dry-run does not check destination state"
103 .to_string(),
104 );
105 }
106 if !has_filters {
107 if has_destination {
108 warnings.push(
109 "dry-run: no filtering specified. dry-run is primarily useful to preview \
110 --include/--exclude/--filter-file filtering; it does not check whether \
111 files already exist at the destination."
112 .to_string(),
113 );
114 } else {
115 warnings.push(
116 "dry-run: no filtering specified. dry-run is primarily useful to preview \
117 --include/--exclude/--filter-file filtering."
118 .to_string(),
119 );
120 }
121 }
122 Self { warnings }
123 }
124 /// Print all collected warnings to stderr.
125 pub fn print(&self) {
126 for warning in &self.warnings {
127 eprintln!("{warning}");
128 }
129 }
130}
131/// Tracing configuration for debugging and profiling
132#[derive(Debug)]
133pub struct TracingConfig {
134 /// Remote tracing layer for distributed tracing
135 pub remote_layer: Option<crate::remote_tracing::RemoteTracingLayer>,
136 /// Debug log file path
137 pub debug_log_file: Option<String>,
138 /// Chrome trace output prefix (produces JSON viewable in Perfetto UI)
139 pub chrome_trace_prefix: Option<String>,
140 /// Flamegraph output prefix (produces folded stacks for inferno)
141 pub flamegraph_prefix: Option<String>,
142 /// Identifier for trace filenames (e.g., "rcp-master", "rcpd-source", "rcpd-destination")
143 pub trace_identifier: String,
144 /// Log level for profiling layers (chrome trace, flamegraph)
145 /// Defaults to "trace" when profiling is enabled
146 pub profile_level: Option<String>,
147 /// Enable tokio-console for live async debugging
148 pub tokio_console: bool,
149 /// Port for tokio-console server (default: 6669)
150 pub tokio_console_port: Option<u16>,
151}
152
153impl Default for TracingConfig {
154 fn default() -> Self {
155 Self {
156 remote_layer: None,
157 debug_log_file: None,
158 chrome_trace_prefix: None,
159 flamegraph_prefix: None,
160 trace_identifier: "unknown".to_string(),
161 profile_level: None,
162 tokio_console: false,
163 tokio_console_port: None,
164 }
165 }
166}