|
| 1 | +use std::hint::black_box; |
| 2 | + |
| 3 | +use criterion::{criterion_group, criterion_main, Criterion}; |
| 4 | +use rand::distributions::{Alphanumeric, DistString, Distribution, Uniform, WeightedIndex}; |
| 5 | +use rand::rngs::SmallRng; |
| 6 | +use rand::seq::SliceRandom as _; |
| 7 | +use rand::{Rng, SeedableRng}; |
| 8 | +use test_results_parser::binary::*; |
| 9 | +use test_results_parser::{Outcome, Testrun}; |
| 10 | + |
| 11 | +criterion_group!(benches, binary); |
| 12 | +criterion_main!(benches); |
| 13 | + |
| 14 | +const NUM_UPLOADS: usize = 10; |
| 15 | +const NUM_TESTS_PER_UPLOAD: usize = 10_000; |
| 16 | +const DAY: u32 = 24 * 60 * 60; |
| 17 | + |
| 18 | +fn binary(c: &mut Criterion) { |
| 19 | + let rng = &mut SmallRng::seed_from_u64(0); |
| 20 | + |
| 21 | + let mut uploads = create_random_testcases(rng, NUM_UPLOADS, NUM_TESTS_PER_UPLOAD); |
| 22 | + randomize_test_data(rng, &mut uploads); |
| 23 | + |
| 24 | + let buf = write_tests(&uploads, 60, 0); |
| 25 | + let buf_1 = write_tests(&uploads[..NUM_UPLOADS - 1], 60, 0); |
| 26 | + randomize_test_data(rng, &mut uploads); |
| 27 | + let buf_2 = write_tests(&uploads[1..], 60, 1 * DAY); |
| 28 | + |
| 29 | + c.benchmark_group("binary") |
| 30 | + .throughput(criterion::Throughput::Elements(NUM_TESTS_PER_UPLOAD as u64)) |
| 31 | + .sample_size(10) // because with the configured `NUM_TESTS`, each iteration would run >100ms |
| 32 | + .bench_function("create_and_serialize", |b| { |
| 33 | + b.iter(|| { |
| 34 | + write_tests(&uploads, 60, 0); |
| 35 | + }) |
| 36 | + }) |
| 37 | + .bench_function("read_aggregation", |b| { |
| 38 | + b.iter(|| { |
| 39 | + let parsed = TestAnalytics::parse(&buf, 0).unwrap(); |
| 40 | + for test in parsed.tests(0..60, None).unwrap() { |
| 41 | + let test = test.unwrap(); |
| 42 | + let _name = black_box(test.name().unwrap()); |
| 43 | + let _aggregates = black_box(test.aggregates()); |
| 44 | + } |
| 45 | + }) |
| 46 | + }) |
| 47 | + .bench_function("update_same", |b| { |
| 48 | + b.iter(|| { |
| 49 | + let parsed = TestAnalytics::parse(&buf, 1).unwrap(); |
| 50 | + let mut writer = TestAnalyticsWriter::from_existing_format(&parsed).unwrap(); |
| 51 | + let mut flags = vec![]; |
| 52 | + for upload in &uploads { |
| 53 | + flags.clear(); |
| 54 | + flags.extend(upload.flags.iter().map(String::as_str)); |
| 55 | + let mut session = writer.start_session(1, CommitHash::default(), &flags); |
| 56 | + for test in &upload.tests { |
| 57 | + session.insert(test); |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + let mut buf = vec![]; |
| 62 | + writer.serialize(&mut buf).unwrap(); |
| 63 | + buf |
| 64 | + }) |
| 65 | + }) |
| 66 | + .bench_function("update_different", |b| { |
| 67 | + b.iter(|| { |
| 68 | + let parsed = TestAnalytics::parse(&buf_1, 1 * DAY).unwrap(); |
| 69 | + let mut writer = TestAnalyticsWriter::from_existing_format(&parsed).unwrap(); |
| 70 | + let mut flags = vec![]; |
| 71 | + for upload in uploads.iter().skip(1) { |
| 72 | + flags.clear(); |
| 73 | + flags.extend(upload.flags.iter().map(String::as_str)); |
| 74 | + let mut session = writer.start_session(1, CommitHash::default(), &flags); |
| 75 | + for test in &upload.tests { |
| 76 | + session.insert(test); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + let mut buf = vec![]; |
| 81 | + writer.serialize(&mut buf).unwrap(); |
| 82 | + buf |
| 83 | + }) |
| 84 | + }) |
| 85 | + .bench_function("merge", |b| { |
| 86 | + b.iter(|| { |
| 87 | + let parsed_1 = TestAnalytics::parse(&buf_1, 1 * DAY).unwrap(); |
| 88 | + let parsed_2 = TestAnalytics::parse(&buf_2, 1 * DAY).unwrap(); |
| 89 | + let writer = TestAnalyticsWriter::merge(&parsed_1, &parsed_2).unwrap(); |
| 90 | + |
| 91 | + let mut buf = vec![]; |
| 92 | + writer.serialize(&mut buf).unwrap(); |
| 93 | + buf |
| 94 | + }) |
| 95 | + }) |
| 96 | + .bench_function("merge_rewrite", |b| { |
| 97 | + b.iter(|| { |
| 98 | + let parsed_1 = TestAnalytics::parse(&buf_1, 1 * DAY).unwrap(); |
| 99 | + let parsed_2 = TestAnalytics::parse(&buf_2, 1 * DAY).unwrap(); |
| 100 | + let mut writer = TestAnalyticsWriter::merge(&parsed_1, &parsed_2).unwrap(); |
| 101 | + |
| 102 | + writer.rewrite(60, 1 * DAY, Some(0)).unwrap(); |
| 103 | + |
| 104 | + let mut buf = vec![]; |
| 105 | + writer.serialize(&mut buf).unwrap(); |
| 106 | + buf |
| 107 | + }) |
| 108 | + }); |
| 109 | +} |
| 110 | + |
| 111 | +fn write_tests(uploads: &[Upload], num_days: usize, timestamp: u32) -> Vec<u8> { |
| 112 | + let mut writer = TestAnalyticsWriter::new(num_days); |
| 113 | + let mut flags = vec![]; |
| 114 | + for upload in uploads { |
| 115 | + flags.clear(); |
| 116 | + flags.extend(upload.flags.iter().map(String::as_str)); |
| 117 | + let mut session = writer.start_session(timestamp, CommitHash::default(), &flags); |
| 118 | + for test in &upload.tests { |
| 119 | + session.insert(test); |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + let mut buf = vec![]; |
| 124 | + writer.serialize(&mut buf).unwrap(); |
| 125 | + buf |
| 126 | +} |
| 127 | + |
| 128 | +struct Upload { |
| 129 | + flags: Vec<String>, |
| 130 | + tests: Vec<Testrun>, |
| 131 | +} |
| 132 | + |
| 133 | +/// Generates a random set of `num_flags` flags. |
| 134 | +fn create_random_flags(rng: &mut impl Rng, num_flags: usize) -> Vec<String> { |
| 135 | + let flag_lens = Uniform::from(5usize..10); |
| 136 | + (0..num_flags) |
| 137 | + .map(|_| { |
| 138 | + let flag_len = flag_lens.sample(rng); |
| 139 | + Alphanumeric.sample_string(rng, flag_len) |
| 140 | + }) |
| 141 | + .collect() |
| 142 | +} |
| 143 | + |
| 144 | +/// Samples random combinations of flags with length `max_flags_in_set`. |
| 145 | +fn sample_flag_sets<'a>( |
| 146 | + rng: &'a mut impl Rng, |
| 147 | + flags: &'a [String], |
| 148 | + max_flags_in_set: usize, |
| 149 | +) -> impl Iterator<Item = Vec<String>> + 'a { |
| 150 | + let num_flags = Uniform::from(0..max_flags_in_set); |
| 151 | + std::iter::from_fn(move || { |
| 152 | + let num_flags = num_flags.sample(rng); |
| 153 | + let flags: Vec<_> = flags.choose_multiple(rng, num_flags).cloned().collect(); |
| 154 | + Some(flags) |
| 155 | + }) |
| 156 | +} |
| 157 | + |
| 158 | +fn create_random_testcases( |
| 159 | + rng: &mut impl Rng, |
| 160 | + num_uploads: usize, |
| 161 | + num_tests_per_upload: usize, |
| 162 | +) -> Vec<Upload> { |
| 163 | + let flags = create_random_flags(rng, 5); |
| 164 | + let flag_sets: Vec<_> = sample_flag_sets(rng, &flags, 3) |
| 165 | + .take(num_uploads / 3) |
| 166 | + .collect(); |
| 167 | + let name_lens = Uniform::from(5usize..50); |
| 168 | + |
| 169 | + (0..num_uploads) |
| 170 | + .map(|_| { |
| 171 | + let flags = flag_sets.choose(rng).cloned().unwrap_or_default(); |
| 172 | + let tests = (0..num_tests_per_upload) |
| 173 | + .map(|_| { |
| 174 | + let name_len = name_lens.sample(rng); |
| 175 | + let name = Alphanumeric.sample_string(rng, name_len); |
| 176 | + |
| 177 | + Testrun { |
| 178 | + name, |
| 179 | + classname: "".into(), |
| 180 | + duration: Some(1.0), |
| 181 | + outcome: Outcome::Pass, |
| 182 | + testsuite: "".into(), |
| 183 | + failure_message: None, |
| 184 | + filename: None, |
| 185 | + build_url: None, |
| 186 | + computed_name: None, |
| 187 | + } |
| 188 | + }) |
| 189 | + .collect(); |
| 190 | + Upload { flags, tests } |
| 191 | + }) |
| 192 | + .collect() |
| 193 | +} |
| 194 | + |
| 195 | +fn randomize_test_data(rng: &mut impl Rng, uploads: &mut [Upload]) { |
| 196 | + let durations = Uniform::from(0f64..10f64); |
| 197 | + let outcomes = WeightedIndex::new([1000, 10, 20]).unwrap(); |
| 198 | + |
| 199 | + for upload in uploads { |
| 200 | + for test in &mut upload.tests { |
| 201 | + test.duration = Some(durations.sample(rng)); |
| 202 | + test.outcome = match outcomes.sample(rng) { |
| 203 | + 0 => Outcome::Pass, |
| 204 | + 1 => Outcome::Skip, |
| 205 | + _ => Outcome::Failure, |
| 206 | + }; |
| 207 | + } |
| 208 | + } |
| 209 | +} |
0 commit comments