1use crate::workloads::{
6 BURST_IO_PREALLOC_SIZE, RandomArgs, SequentialArgs, TransferArgs, WorkloadSubcommand,
7 get_effective_op_size,
8};
9use anyhow::Result;
10use argh::FromArgs;
11
12#[derive(FromArgs, Debug)]
13struct TopLevelArgs {
15 #[argh(option, default = "10")]
17 duration_secs: u64,
18
19 #[argh(subcommand)]
20 subcommand: WorkloadSubcommand,
21}
22
23#[derive(FromArgs, Debug)]
24struct FakeParent {
26 #[argh(subcommand)]
27 subcommand: WorkloadSubcommand,
28}
29
30pub fn parse_chained_workloads(args: &[String]) -> Result<(u64, Vec<WorkloadSubcommand>)> {
31 if args.is_empty() || args.len() == 1 {
32 anyhow::bail!(
33 "Error: No workload subcommand specified! (e.g. 'fx test io-stress-test -- random')"
34 );
35 }
36
37 let chunks: Vec<&[String]> =
38 args[1..].split(|arg| arg == "+").filter(|chunk| !chunk.is_empty()).collect();
39
40 if chunks.is_empty() {
41 anyhow::bail!(
42 "Error: No workload subcommand specified! (e.g. 'fx test io-stress-test -- random')"
43 );
44 }
45
46 let first_refs: Vec<&str> = chunks[0].iter().map(|s| s.as_str()).collect();
47 let top_args = TopLevelArgs::from_args(&["io_stress"], &first_refs)
48 .map_err(|err| anyhow::anyhow!("Failed to parse first subcommand: {:?}", err))?;
49
50 let duration_secs = top_args.duration_secs;
51 let mut subcommands = vec![top_args.subcommand];
52
53 for chunk in &chunks[1..] {
54 let refs: Vec<&str> = chunk.iter().map(|s| s.as_str()).collect();
55 let parent = FakeParent::from_args(&["workload"], &refs)
56 .map_err(|err| anyhow::anyhow!("Failed to parse chained subcommand: {:?}", err))?;
57 subcommands.push(parent.subcommand);
58 }
59
60 Ok((duration_secs, subcommands))
61}
62
63pub fn validate_workloads(subcommands: &[WorkloadSubcommand]) -> Result<()> {
64 for sub in subcommands {
65 match sub {
66 WorkloadSubcommand::Random(RandomArgs { op_size_bytes, file_size_bytes, .. })
67 | WorkloadSubcommand::Sequential(SequentialArgs {
68 op_size_bytes,
69 file_size_bytes,
70 ..
71 })
72 | WorkloadSubcommand::Transfer(TransferArgs {
73 op_size_bytes, file_size_bytes, ..
74 }) => {
75 let op_size = get_effective_op_size(*op_size_bytes);
76 if op_size > *file_size_bytes {
77 anyhow::bail!(
78 "Error: {} operation size ({} B) cannot exceed file size ({} B)!",
79 sub.name(),
80 op_size,
81 file_size_bytes
82 );
83 }
84 }
85 WorkloadSubcommand::Burst(args) => {
86 let op_size = get_effective_op_size(args.op_size_bytes);
87 if op_size > BURST_IO_PREALLOC_SIZE {
88 anyhow::bail!(
89 "Error: Burst op size ({} B) cannot exceed pre-allocated size ({} B)!",
90 op_size,
91 BURST_IO_PREALLOC_SIZE
92 );
93 }
94 }
95 }
96 }
97 Ok(())
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_parse_chained_workloads_single() {
106 let args = vec![
107 "io_stress".to_string(),
108 "random".to_string(),
109 "--op-size-bytes".to_string(),
110 "1024".to_string(),
111 ];
112 let (duration, subcmds) = parse_chained_workloads(&args).unwrap();
113 assert_eq!(duration, 10);
114 assert_eq!(subcmds.len(), 1);
115 match &subcmds[0] {
116 WorkloadSubcommand::Random(a) => assert_eq!(a.op_size_bytes, 1024),
117 _ => panic!("Expected Random subcommand"),
118 }
119 }
120
121 #[test]
122 fn test_parse_chained_workloads_chained() {
123 let args = vec![
124 "io_stress".to_string(),
125 "--duration-secs".to_string(),
126 "20".to_string(),
127 "random".to_string(),
128 "+".to_string(),
129 "sequential".to_string(),
130 "--op-size-bytes".to_string(),
131 "2048".to_string(),
132 ];
133 let (duration, subcmds) = parse_chained_workloads(&args).unwrap();
134 assert_eq!(duration, 20);
135 assert_eq!(subcmds.len(), 2);
136 match &subcmds[0] {
137 WorkloadSubcommand::Random(_) => {}
138 _ => panic!("Expected Random subcommand"),
139 }
140 match &subcmds[1] {
141 WorkloadSubcommand::Sequential(a) => assert_eq!(a.op_size_bytes, 2048),
142 _ => panic!("Expected Sequential subcommand"),
143 }
144 }
145
146 #[test]
147 fn test_parse_chained_workloads_invalid() {
148 let args = vec!["io_stress".to_string()];
149 assert!(parse_chained_workloads(&args).is_err());
150
151 let args = vec!["io_stress".to_string(), "+".to_string()];
152 assert!(parse_chained_workloads(&args).is_err());
153 }
154}