fortrangoingonforty/afs-ld / afcd90f

Browse files

Report parity matrix timings

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
afcd90fd76b1c6845c9b8d72e92d2cd087ab55c4
Parents
e41e4b6
Tree
49b10ac

2 changed files

StatusFile+-
M tests/common/harness.rs 16 14
M tests/parity_matrix.rs 159 58
tests/common/harness.rsmodified
@@ -116,11 +116,13 @@ struct ArtifactSpec {
116
 
116
 
117
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
117
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
118
 enum ArtifactKind {
118
 enum ArtifactKind {
119
-    ClangDylib,
119
+    Dylib,
120
-    ClangArchive,
120
+    Archive,
121
-    ClangReexportDylib,
121
+    ReexportDylib,
122
 }
122
 }
123
 
123
 
124
+type SymbolPartitions = (Vec<String>, Vec<String>, Vec<String>);
125
+
124
 pub struct LinkOutputs {
126
 pub struct LinkOutputs {
125
     pub ours: Vec<u8>,
127
     pub ours: Vec<u8>,
126
     pub theirs: Vec<u8>,
128
     pub theirs: Vec<u8>,
@@ -509,9 +511,9 @@ pub fn link_both(case: &LinkCase) -> Result<LinkOutputs, String> {
509
             .map_err(|e| format!("read artifact src {}: {e}", src.display()))?;
511
             .map_err(|e| format!("read artifact src {}: {e}", src.display()))?;
510
         let out = work_dir.join(&artifact.out_name);
512
         let out = work_dir.join(&artifact.out_name);
511
         match artifact.kind {
513
         match artifact.kind {
512
-            ArtifactKind::ClangDylib => compile_dylib_c(&src_contents, &out)?,
514
+            ArtifactKind::Dylib => compile_dylib_c(&src_contents, &out)?,
513
-            ArtifactKind::ClangArchive => compile_archive_c(&src_contents, &out)?,
515
+            ArtifactKind::Archive => compile_archive_c(&src_contents, &out)?,
514
-            ArtifactKind::ClangReexportDylib => {
516
+            ArtifactKind::ReexportDylib => {
515
                 let dep_name = artifact.dep_name.as_ref().ok_or_else(|| {
517
                 let dep_name = artifact.dep_name.as_ref().ok_or_else(|| {
516
                     format!(
518
                     format!(
517
                         "missing reexport dependency for artifact {}",
519
                         "missing reexport dependency for artifact {}",
@@ -1390,7 +1392,7 @@ fn read_artifacts(path: &Path) -> Result<Vec<ArtifactSpec>, String> {
1390
                         path.display()
1392
                         path.display()
1391
                     ));
1393
                     ));
1392
                 }
1394
                 }
1393
-                (ArtifactKind::ClangDylib, None)
1395
+                (ArtifactKind::Dylib, None)
1394
             }
1396
             }
1395
             "clang_archive" => {
1397
             "clang_archive" => {
1396
                 if dep_name.is_some() {
1398
                 if dep_name.is_some() {
@@ -1399,7 +1401,7 @@ fn read_artifacts(path: &Path) -> Result<Vec<ArtifactSpec>, String> {
1399
                         path.display()
1401
                         path.display()
1400
                     ));
1402
                     ));
1401
                 }
1403
                 }
1402
-                (ArtifactKind::ClangArchive, None)
1404
+                (ArtifactKind::Archive, None)
1403
             }
1405
             }
1404
             "clang_reexport_dylib" => {
1406
             "clang_reexport_dylib" => {
1405
                 let dep_name = dep_name.ok_or_else(|| {
1407
                 let dep_name = dep_name.ok_or_else(|| {
@@ -1408,7 +1410,7 @@ fn read_artifacts(path: &Path) -> Result<Vec<ArtifactSpec>, String> {
1408
                         path.display()
1410
                         path.display()
1409
                     )
1411
                     )
1410
                 })?;
1412
                 })?;
1411
-                (ArtifactKind::ClangReexportDylib, Some(dep_name))
1413
+                (ArtifactKind::ReexportDylib, Some(dep_name))
1412
             }
1414
             }
1413
             other => return Err(format!("unknown artifact kind `{other}`")),
1415
             other => return Err(format!("unknown artifact kind `{other}`")),
1414
         };
1416
         };
@@ -1592,10 +1594,10 @@ fn tolerated_mask(bytes: &[u8]) -> Vec<Option<&'static str>> {
1592
                 }
1594
                 }
1593
             }
1595
             }
1594
             LC_ID_DYLIB | LC_LOAD_DYLIB | LC_LOAD_WEAK_DYLIB | LC_REEXPORT_DYLIB
1596
             LC_ID_DYLIB | LC_LOAD_DYLIB | LC_LOAD_WEAK_DYLIB | LC_REEXPORT_DYLIB
1595
-            | LC_LOAD_UPWARD_DYLIB => {
1597
+            | LC_LOAD_UPWARD_DYLIB
1596
-                if cmdsize >= 16 {
1598
+                if cmdsize >= 16 =>
1597
-                    mark_range(&mut mask, cursor + 12, cursor + 16, "dylib timestamp");
1599
+            {
1598
-                }
1600
+                mark_range(&mut mask, cursor + 12, cursor + 16, "dylib timestamp");
1599
             }
1601
             }
1600
             _ => {}
1602
             _ => {}
1601
         }
1603
         }
@@ -1765,7 +1767,7 @@ fn canonical_export_records(bytes: &[u8]) -> Result<Vec<CanonicalExportRecord>,
1765
     Ok(out)
1767
     Ok(out)
1766
 }
1768
 }
1767
 
1769
 
1768
-fn symbol_partition_names(bytes: &[u8]) -> Result<(Vec<String>, Vec<String>, Vec<String>), String> {
1770
+fn symbol_partition_names(bytes: &[u8]) -> Result<SymbolPartitions, String> {
1769
     let (symtab, dysymtab) = symtab_and_dysymtab(bytes)?;
1771
     let (symtab, dysymtab) = symtab_and_dysymtab(bytes)?;
1770
     let symbols =
1772
     let symbols =
1771
         parse_nlist_table(bytes, symtab.symoff, symtab.nsyms).map_err(|e| e.to_string())?;
1773
         parse_nlist_table(bytes, symtab.symoff, symtab.nsyms).map_err(|e| e.to_string())?;
tests/parity_matrix.rsmodified
@@ -54,6 +54,7 @@ fn parity_corpus() {
54
         }
54
         }
55
         case_reports.push((case, report));
55
         case_reports.push((case, report));
56
     }
56
     }
57
+    print_timing_summary(started.elapsed(), &case_reports);
57
 
58
 
58
     if let Some(dir) = artifact_dir.as_ref() {
59
     if let Some(dir) = artifact_dir.as_ref() {
59
         write_index_artifact(dir, &case_reports).expect("write parity index");
60
         write_index_artifact(dir, &case_reports).expect("write parity index");
@@ -80,24 +81,40 @@ fn parity_corpus() {
80
 #[derive(Debug)]
81
 #[derive(Debug)]
81
 struct CaseStep {
82
 struct CaseStep {
82
     name: &'static str,
83
     name: &'static str,
84
+    duration: Duration,
83
     error: Option<String>,
85
     error: Option<String>,
84
 }
86
 }
85
 
87
 
86
 #[derive(Debug, Default)]
88
 #[derive(Debug, Default)]
87
 struct CaseReport {
89
 struct CaseReport {
88
     steps: Vec<CaseStep>,
90
     steps: Vec<CaseStep>,
91
+    elapsed: Duration,
89
 }
92
 }
90
 
93
 
91
 impl CaseReport {
94
 impl CaseReport {
92
     fn push(&mut self, name: &'static str, result: Result<(), String>) -> bool {
95
     fn push(&mut self, name: &'static str, result: Result<(), String>) -> bool {
96
+        self.push_timed(name, Duration::ZERO, result)
97
+    }
98
+
99
+    fn push_timed(
100
+        &mut self,
101
+        name: &'static str,
102
+        duration: Duration,
103
+        result: Result<(), String>,
104
+    ) -> bool {
93
         match result {
105
         match result {
94
             Ok(()) => {
106
             Ok(()) => {
95
-                self.steps.push(CaseStep { name, error: None });
107
+                self.steps.push(CaseStep {
108
+                    name,
109
+                    duration,
110
+                    error: None,
111
+                });
96
                 true
112
                 true
97
             }
113
             }
98
             Err(error) => {
114
             Err(error) => {
99
                 self.steps.push(CaseStep {
115
                 self.steps.push(CaseStep {
100
                     name,
116
                     name,
117
+                    duration,
101
                     error: Some(error),
118
                     error: Some(error),
102
                 });
119
                 });
103
                 false
120
                 false
@@ -105,15 +122,32 @@ impl CaseReport {
105
         }
122
         }
106
     }
123
     }
107
 
124
 
125
+    fn measure<F>(&mut self, name: &'static str, action: F) -> bool
126
+    where
127
+        F: FnOnce() -> Result<(), String>,
128
+    {
129
+        let started = Instant::now();
130
+        let result = action();
131
+        self.push_timed(name, started.elapsed(), result)
132
+    }
133
+
134
+    fn finish(&mut self, elapsed: Duration) {
135
+        self.elapsed = elapsed;
136
+    }
137
+
108
     fn passed(&self) -> bool {
138
     fn passed(&self) -> bool {
109
         self.steps.iter().all(|step| step.error.is_none())
139
         self.steps.iter().all(|step| step.error.is_none())
110
     }
140
     }
111
 
141
 
142
+    fn slowest_step(&self) -> Option<&CaseStep> {
143
+        self.steps.iter().max_by_key(|step| step.duration)
144
+    }
145
+
112
     fn error_message(&self, case_name: &str) -> Option<String> {
146
     fn error_message(&self, case_name: &str) -> Option<String> {
113
         self.steps.iter().find_map(|step| {
147
         self.steps.iter().find_map(|step| {
114
-            step.error.as_ref().map(|error| {
148
+            step.error
115
-                format!("[{case_name}] {} failed:\n{}", step.name, error)
149
+                .as_ref()
116
-            })
150
+                .map(|error| format!("[{case_name}] {} failed:\n{}", step.name, error))
117
         })
151
         })
118
     }
152
     }
119
 }
153
 }
@@ -132,85 +166,84 @@ fn case_report_error_message_includes_case_name() {
132
 }
166
 }
133
 
167
 
134
 fn run_case(case: &LinkCase) -> CaseReport {
168
 fn run_case(case: &LinkCase) -> CaseReport {
169
+    let case_started = Instant::now();
135
     let mut report = CaseReport::default();
170
     let mut report = CaseReport::default();
171
+    let link_started = Instant::now();
136
     let outputs = match link_both(case) {
172
     let outputs = match link_both(case) {
137
         Ok(outputs) => {
173
         Ok(outputs) => {
138
-            report.push("link", Ok(()));
174
+            report.push_timed("link", link_started.elapsed(), Ok(()));
139
             outputs
175
             outputs
140
         }
176
         }
141
         Err(error) => {
177
         Err(error) => {
142
-            report.push(
178
+            report.push_timed(
143
                 "link",
179
                 "link",
180
+                link_started.elapsed(),
144
                 Err(format!(
181
                 Err(format!(
145
                     "failed to link parity case from {}:\n{}",
182
                     "failed to link parity case from {}:\n{}",
146
                     case.dir.display(),
183
                     case.dir.display(),
147
                     error
184
                     error
148
                 )),
185
                 )),
149
             );
186
             );
150
-            return report;
187
+            return finish_case(report, case_started);
151
         }
188
         }
152
     };
189
     };
153
 
190
 
154
-    if !report.push(
191
+    if !report.measure("load-command ids", || {
155
-        "load-command ids",
192
+        compare_command_ids(&outputs.ours, &outputs.theirs, &case.ignored_load_commands)
156
-        compare_command_ids(&outputs.ours, &outputs.theirs, &case.ignored_load_commands),
193
+    }) {
157
-    ) {
194
+        return finish_case(report, case_started);
158
-        return report;
159
     }
195
     }
160
-    if !report.push(
196
+    if !report.measure("command details", || {
161
-        "command details",
197
+        compare_command_details(&outputs.ours, &outputs.theirs, &case.command_checks)
162
-        compare_command_details(&outputs.ours, &outputs.theirs, &case.command_checks),
198
+    }) {
163
-    ) {
199
+        return finish_case(report, case_started);
164
-        return report;
165
     }
200
     }
166
-    if !report.push(
201
+    if !report.measure("afs-ld absent commands", || {
167
-        "afs-ld absent commands",
202
+        ensure_absent_load_commands(&outputs.ours, &case.absent_load_commands, "afs-ld")
168
-        ensure_absent_load_commands(&outputs.ours, &case.absent_load_commands, "afs-ld"),
203
+    }) {
169
-    ) {
204
+        return finish_case(report, case_started);
170
-        return report;
171
     }
205
     }
172
-    if !report.push(
206
+    if !report.measure("Apple absent commands", || {
173
-        "Apple absent commands",
207
+        ensure_absent_load_commands(&outputs.theirs, &case.absent_load_commands, "Apple ld")
174
-        ensure_absent_load_commands(&outputs.theirs, &case.absent_load_commands, "Apple ld"),
208
+    }) {
175
-    ) {
209
+        return finish_case(report, case_started);
176
-        return report;
177
     }
210
     }
178
-    if !report.push(
211
+    if !report.measure("afs-ld absent sections", || {
179
-        "afs-ld absent sections",
212
+        ensure_absent_sections(&outputs.ours, &case.absent_sections, "afs-ld")
180
-        ensure_absent_sections(&outputs.ours, &case.absent_sections, "afs-ld"),
213
+    }) {
181
-    ) {
214
+        return finish_case(report, case_started);
182
-        return report;
183
     }
215
     }
184
-    if !report.push(
216
+    if !report.measure("Apple absent sections", || {
185
-        "Apple absent sections",
217
+        ensure_absent_sections(&outputs.theirs, &case.absent_sections, "Apple ld")
186
-        ensure_absent_sections(&outputs.theirs, &case.absent_sections, "Apple ld"),
218
+    }) {
187
-    ) {
219
+        return finish_case(report, case_started);
188
-        return report;
189
     }
220
     }
190
-    if !report.push(
221
+    if !report.measure("section parity", || {
191
-        "section parity",
192
         compare_sections(
222
         compare_sections(
193
             &outputs.ours,
223
             &outputs.ours,
194
             &outputs.theirs,
224
             &outputs.theirs,
195
             &case.section_checks,
225
             &case.section_checks,
196
             &case.case_tolerances,
226
             &case.case_tolerances,
197
-        ),
227
+        )
198
-    ) {
228
+    }) {
199
-        return report;
229
+        return finish_case(report, case_started);
200
     }
230
     }
201
-    if !report.push(
231
+    if !report.measure("page-ref parity", || {
202
-        "page-ref parity",
232
+        compare_page_refs(&outputs.ours, &outputs.theirs, &case.page_ref_checks)
203
-        compare_page_refs(&outputs.ours, &outputs.theirs, &case.page_ref_checks),
233
+    }) {
204
-    ) {
234
+        return finish_case(report, case_started);
205
-        return report;
206
     }
235
     }
207
     if !case.runtime_args.is_empty() || case.dir.join("runtime.txt").exists() {
236
     if !case.runtime_args.is_empty() || case.dir.join("runtime.txt").exists() {
208
-        report.push(
237
+        report.measure("runtime parity", || {
209
-            "runtime parity",
238
+            compare_runtime(&outputs.our_path, &outputs.their_path, &case.runtime_args)
210
-            compare_runtime(&outputs.our_path, &outputs.their_path, &case.runtime_args),
239
+        });
211
-        );
212
     }
240
     }
213
 
241
 
242
+    finish_case(report, case_started)
243
+}
244
+
245
+fn finish_case(mut report: CaseReport, started: Instant) -> CaseReport {
246
+    report.finish(started.elapsed());
214
     report
247
     report
215
 }
248
 }
216
 
249
 
@@ -228,16 +261,22 @@ fn write_case_artifact(dir: &Path, case: &LinkCase, report: &CaseReport) -> Resu
228
         if report.passed() { "ok" } else { "fail" },
261
         if report.passed() { "ok" } else { "fail" },
229
         if report.passed() { "PASS" } else { "FAIL" }
262
         if report.passed() { "PASS" } else { "FAIL" }
230
     ));
263
     ));
264
+    html.push_str(&format!(
265
+        "<p>Total: <strong>{}</strong></p>",
266
+        format_duration(report.elapsed)
267
+    ));
231
     html.push_str("<h2>Steps</h2><ul>");
268
     html.push_str("<h2>Steps</h2><ul>");
232
     for step in &report.steps {
269
     for step in &report.steps {
233
         match &step.error {
270
         match &step.error {
234
             None => html.push_str(&format!(
271
             None => html.push_str(&format!(
235
-                "<li><span class=\"ok\">PASS</span> {}</li>",
272
+                "<li><span class=\"ok\">PASS</span> {} <span class=\"time\">{}</span></li>",
236
-                escape_html(step.name)
273
+                escape_html(step.name),
274
+                format_duration(step.duration)
237
             )),
275
             )),
238
             Some(error) => html.push_str(&format!(
276
             Some(error) => html.push_str(&format!(
239
-                "<li><span class=\"fail\">FAIL</span> {}<pre>{}</pre></li>",
277
+                "<li><span class=\"fail\">FAIL</span> {} <span class=\"time\">{}</span><pre>{}</pre></li>",
240
                 escape_html(step.name),
278
                 escape_html(step.name),
279
+                format_duration(step.duration),
241
                 escape_html(error)
280
                 escape_html(error)
242
             )),
281
             )),
243
         }
282
         }
@@ -258,16 +297,32 @@ fn write_case_artifact(dir: &Path, case: &LinkCase, report: &CaseReport) -> Resu
258
 fn write_index_artifact(dir: &Path, cases: &[(LinkCase, CaseReport)]) -> Result<(), String> {
297
 fn write_index_artifact(dir: &Path, cases: &[(LinkCase, CaseReport)]) -> Result<(), String> {
259
     let mut html = String::new();
298
     let mut html = String::new();
260
     html.push_str("<!doctype html><html><head><meta charset=\"utf-8\">");
299
     html.push_str("<!doctype html><html><head><meta charset=\"utf-8\">");
261
-    html.push_str("<title>Parity Matrix</title><style>body{font-family:ui-monospace,Menlo,monospace;padding:2rem;} .ok{color:#0a0;} .fail{color:#a00;}</style></head><body>");
300
+    html.push_str("<title>Parity Matrix</title><style>body{font-family:ui-monospace,Menlo,monospace;padding:2rem;} .ok{color:#0a0;} .fail{color:#a00;} .time{color:#57606a;} table{border-collapse:collapse;margin:1rem 0;} td,th{border:1px solid #d0d7de;padding:.35rem .6rem;text-align:left;}</style></head><body>");
262
-    html.push_str("<h1>Parity Matrix</h1><ul>");
301
+    html.push_str("<h1>Parity Matrix</h1>");
302
+    html.push_str("<h2>Slowest Cases</h2><table><thead><tr><th>Case</th><th>Total</th><th>Slowest Step</th></tr></thead><tbody>");
303
+    for (case, report) in slowest_cases(cases, 10) {
304
+        let slowest = report
305
+            .slowest_step()
306
+            .map(|step| format!("{} {}", step.name, format_duration(step.duration)))
307
+            .unwrap_or_else(|| "n/a".to_string());
308
+        html.push_str(&format!(
309
+            "<tr><td><a href=\"{}.html\">{}</a></td><td>{}</td><td>{}</td></tr>",
310
+            slug(&case.name),
311
+            escape_html(&case.name),
312
+            format_duration(report.elapsed),
313
+            escape_html(&slowest)
314
+        ));
315
+    }
316
+    html.push_str("</tbody></table><h2>Cases</h2><ul>");
263
     for (case, report) in cases {
317
     for (case, report) in cases {
264
         let slug = slug(&case.name);
318
         let slug = slug(&case.name);
265
         html.push_str(&format!(
319
         html.push_str(&format!(
266
-            "<li><a href=\"{}.html\">{}</a> <strong class=\"{}\">{}</strong></li>",
320
+            "<li><a href=\"{}.html\">{}</a> <strong class=\"{}\">{}</strong> <span class=\"time\">{}</span></li>",
267
             slug,
321
             slug,
268
             escape_html(&case.name),
322
             escape_html(&case.name),
269
             if report.passed() { "ok" } else { "fail" },
323
             if report.passed() { "ok" } else { "fail" },
270
-            if report.passed() { "PASS" } else { "FAIL" }
324
+            if report.passed() { "PASS" } else { "FAIL" },
325
+            format_duration(report.elapsed)
271
         ));
326
         ));
272
     }
327
     }
273
     html.push_str("</ul></body></html>");
328
     html.push_str("</ul></body></html>");
@@ -275,6 +330,43 @@ fn write_index_artifact(dir: &Path, cases: &[(LinkCase, CaseReport)]) -> Result<
275
     fs::write(&path, html).map_err(|e| format!("write {}: {e}", path.display()))
330
     fs::write(&path, html).map_err(|e| format!("write {}: {e}", path.display()))
276
 }
331
 }
277
 
332
 
333
+fn print_timing_summary(elapsed: Duration, cases: &[(LinkCase, CaseReport)]) {
334
+    eprintln!(
335
+        "parity matrix timing: {} case(s) in {}",
336
+        cases.len(),
337
+        format_duration(elapsed)
338
+    );
339
+    for (case, report) in slowest_cases(cases, 10) {
340
+        let slowest = report
341
+            .slowest_step()
342
+            .map(|step| {
343
+                format!(
344
+                    "; slowest step: {} {}",
345
+                    step.name,
346
+                    format_duration(step.duration)
347
+                )
348
+            })
349
+            .unwrap_or_default();
350
+        eprintln!(
351
+            "  {:>9} {}{}",
352
+            format_duration(report.elapsed),
353
+            case.name,
354
+            slowest
355
+        );
356
+    }
357
+}
358
+
359
+fn slowest_cases(cases: &[(LinkCase, CaseReport)], limit: usize) -> Vec<(&LinkCase, &CaseReport)> {
360
+    let mut timed: Vec<_> = cases.iter().map(|(case, report)| (case, report)).collect();
361
+    timed.sort_by(|a, b| {
362
+        b.1.elapsed
363
+            .cmp(&a.1.elapsed)
364
+            .then_with(|| a.0.name.cmp(&b.0.name))
365
+    });
366
+    timed.truncate(limit);
367
+    timed
368
+}
369
+
278
 fn slug(name: &str) -> String {
370
 fn slug(name: &str) -> String {
279
     name.chars()
371
     name.chars()
280
         .map(|ch| {
372
         .map(|ch| {
@@ -293,6 +385,15 @@ fn parity_matrix_time_limit() -> Option<Duration> {
293
     Some(Duration::from_secs(seconds))
385
     Some(Duration::from_secs(seconds))
294
 }
386
 }
295
 
387
 
388
+fn format_duration(duration: Duration) -> String {
389
+    let millis = duration.as_secs_f64() * 1000.0;
390
+    if millis >= 1000.0 {
391
+        format!("{:.2}s", duration.as_secs_f64())
392
+    } else {
393
+        format!("{millis:.1}ms")
394
+    }
395
+}
396
+
296
 fn escape_html(text: &str) -> String {
397
 fn escape_html(text: &str) -> String {
297
     text.replace('&', "&amp;")
398
     text.replace('&', "&amp;")
298
         .replace('<', "&lt;")
399
         .replace('<', "&lt;")