1use molt::check_args;
16use molt::molt_ok;
17use molt::ContextID;
18use molt::Interp;
19use molt::MoltInt;
20use molt::MoltResult;
21use molt::Value;
22use std::env;
23use std::fs;
24use std::path::PathBuf;
25
26pub fn benchmark(interp: &mut Interp, args: &[String]) {
59 if args.is_empty() {
61 eprintln!("Missing benchmark script.");
62 write_usage();
63 return;
64 }
65
66 let mut output_csv = false;
68
69 let mut iter = args[1..].iter();
70 loop {
71 let opt = iter.next();
72 if opt.is_none() {
73 break;
74 }
75
76 let opt = opt.unwrap();
77
78 match opt.as_ref() {
79 "-csv" => {
80 output_csv = true;
81 }
82 _ => {
83 eprintln!("Unknown option: \"{}\"", opt);
84 write_usage();
85 return;
86 }
87 }
88 }
89
90 let path = PathBuf::from(&args[0]);
93
94 let context_id = interp.save_context(Context::new());
96
97 interp.add_command("ident", cmd_ident);
99 interp.add_context_command("measure", measure_cmd, context_id);
100 interp.add_command("ok", cmd_ok);
101
102 if let Err(exception) = interp.eval(include_str!("bench.tcl")) {
104 panic!(
105 "Error in benchmark Tcl library: {}",
106 exception.value().as_str()
107 );
108 }
109
110 match fs::read_to_string(&args[0]) {
112 Ok(script) => {
113 if let Some(parent) = path.parent() {
114 let _ = env::set_current_dir(parent);
115 }
116
117 match interp.eval(&script) {
118 Ok(_) => (),
119 Err(exception) => {
120 eprintln!("{}", exception.value());
121 std::process::exit(1);
122 }
123 }
124 }
125 Err(e) => println!("{}", e),
126 }
127
128 let ctx = interp.context::<Context>(context_id);
130
131 if output_csv {
132 write_csv(ctx);
133 } else {
134 write_formatted_text(ctx);
135 }
136}
137
138fn write_csv(ctx: &Context) {
139 println!("\"benchmark\",\"description\",\"nanos\",\"norm\"");
140
141 let baseline = ctx.baseline();
142
143 for record in &ctx.measurements {
144 println!(
145 "\"{}\",\"{}\",{},{}",
146 strip_quotes(&record.name),
147 strip_quotes(&record.description),
148 record.nanos,
149 record.nanos as f64 / (baseline as f64),
150 );
151 }
152}
153
154fn strip_quotes(string: &str) -> String {
155 let out: String = string
156 .chars()
157 .map(|ch| if ch == '\"' { '\'' } else { ch })
158 .collect();
159 out
160}
161
162fn write_formatted_text(ctx: &Context) {
163 write_version();
164 println!();
165 println!("{:>8} {:>8} -- Benchmark", "Nanos", "Norm");
166
167 let baseline = ctx.baseline();
168
169 for record in &ctx.measurements {
170 println!(
171 "{:>8} {:>8.2} -- {} {}",
172 record.nanos,
173 record.nanos as f64 / (baseline as f64),
174 record.name,
175 record.description
176 );
177 }
178}
179
180fn write_version() {
181 println!("Molt {} -- Benchmark", env!("CARGO_PKG_VERSION"));
182}
183
184fn write_usage() {
185 write_version();
186 println!();
187 println!("Usage: molt bench filename.tcl [-csv]");
188}
189
190struct Context {
191 baseline: Option<MoltInt>,
193
194 measurements: Vec<Measurement>,
196}
197
198impl Context {
199 fn new() -> Self {
200 Self {
201 baseline: None,
202 measurements: Vec::new(),
203 }
204 }
205
206 fn baseline(&self) -> MoltInt {
207 self.baseline.unwrap_or(1)
208 }
209}
210
211struct Measurement {
212 name: String,
214
215 description: String,
217
218 nanos: MoltInt,
220}
221
222fn measure_cmd(interp: &mut Interp, context_id: ContextID, argv: &[Value]) -> MoltResult {
226 molt::check_args(1, argv, 4, 4, "name description nanos")?;
227
228 let name = argv[1].to_string();
230 let description = argv[2].to_string();
231 let nanos = argv[3].as_int()?;
232
233 let ctx = interp.context::<Context>(context_id);
235
236 if ctx.baseline.is_none() {
237 ctx.baseline = Some(nanos);
238 }
239
240 let record = Measurement {
241 name,
242 description,
243 nanos,
244 };
245
246 ctx.measurements.push(record);
247
248 molt_ok!()
249}
250
251fn cmd_ident(_interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
255 check_args(1, argv, 2, 2, "value")?;
256
257 molt_ok!(argv[1].clone())
258}
259
260fn cmd_ok(_interp: &mut Interp, _: ContextID, _argv: &[Value]) -> MoltResult {
264 molt_ok!()
265}