From 37836b498d0f2ceea207e3642adc0f92f21ae84f Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 30 Mar 2026 09:36:24 -0400 Subject: [PATCH] cockroach/influxdb: CWE-407 findings --- ...oach-0001-indexes-used-add-quadratic.patch | 38 ++++ ...ingerprint-ignored-columns-quadratic.patch | 34 +++ ...ockroach-0003-ensure-roles-quadratic.patch | 20 ++ defects/cockroach/unit/CockroachTest.class | Bin 0 -> 6879 bytes defects/cockroach/unit/CockroachTest.java | 204 ++++++++++++++++++ ...0001-persisted-files-dedup-quadratic.patch | 47 ++++ ...-0002-table-def-series-key-quadratic.patch | 41 ++++ defects/influxdb/unit/InfluxDBTest.class | Bin 0 -> 5556 bytes defects/influxdb/unit/InfluxDBTest.java | 162 ++++++++++++++ 9 files changed, 546 insertions(+) create mode 100644 defects/cockroach/patch/cockroach-0001-indexes-used-add-quadratic.patch create mode 100644 defects/cockroach/patch/cockroach-0002-fingerprint-ignored-columns-quadratic.patch create mode 100644 defects/cockroach/patch/cockroach-0003-ensure-roles-quadratic.patch create mode 100644 defects/cockroach/unit/CockroachTest.class create mode 100644 defects/cockroach/unit/CockroachTest.java create mode 100644 defects/influxdb/patch/influxdb-0001-persisted-files-dedup-quadratic.patch create mode 100644 defects/influxdb/patch/influxdb-0002-table-def-series-key-quadratic.patch create mode 100644 defects/influxdb/unit/InfluxDBTest.class create mode 100644 defects/influxdb/unit/InfluxDBTest.java diff --git a/defects/cockroach/patch/cockroach-0001-indexes-used-add-quadratic.patch b/defects/cockroach/patch/cockroach-0001-indexes-used-add-quadratic.patch new file mode 100644 index 000000000..48d93b383 --- /dev/null +++ b/defects/cockroach/patch/cockroach-0001-indexes-used-add-quadratic.patch @@ -0,0 +1,38 @@ +# UNDF: +--- a/pkg/sql/opt/exec/execbuilder/builder.go ++++ b/pkg/sql/opt/exec/execbuilder/builder.go +@@ -7,7 +7,6 @@ package execbuilder + import ( + "context" +- "slices" + "strconv" + "time" + +@@ -197,14 +197,20 @@ type IndexesUsed struct { + indexes []struct { + tableID cat.StableID + indexID cat.StableID + } ++ seen map[[2]cat.StableID]struct{} + } + + // add adds the given index to the list, if it is not already present. + func (iu *IndexesUsed) add(tableID, indexID cat.StableID) { +- s := struct { +- tableID cat.StableID +- indexID cat.StableID +- }{tableID, indexID} +- if !slices.Contains(iu.indexes, s) { +- iu.indexes = append(iu.indexes, s) ++ key := [2]cat.StableID{tableID, indexID} ++ if iu.seen == nil { ++ iu.seen = make(map[[2]cat.StableID]struct{}) ++ } ++ if _, ok := iu.seen[key]; !ok { ++ iu.seen[key] = struct{}{} ++ iu.indexes = append(iu.indexes, struct { ++ tableID cat.StableID ++ indexID cat.StableID ++ }{tableID, indexID}) + } + } diff --git a/defects/cockroach/patch/cockroach-0002-fingerprint-ignored-columns-quadratic.patch b/defects/cockroach/patch/cockroach-0002-fingerprint-ignored-columns-quadratic.patch new file mode 100644 index 000000000..15d790ba8 --- /dev/null +++ b/defects/cockroach/patch/cockroach-0002-fingerprint-ignored-columns-quadratic.patch @@ -0,0 +1,34 @@ +# UNDF: +--- a/pkg/sql/show_fingerprints.go ++++ b/pkg/sql/show_fingerprints.go +@@ -432,13 +432,17 @@ func BuildExperimentalFingerprintQueryForIndex( + tableDesc catalog.TableDescriptor, index catalog.Index, ignoredColumns []string, + ) (string, error) { ++ ignored := make(map[string]struct{}, len(ignoredColumns)) ++ for _, c := range ignoredColumns { ++ ignored[c] = struct{}{} ++ } + cols := make([]string, 0, len(tableDesc.PublicColumns())) + var numBytesCols int + addColumn := func(col catalog.Column) { +- if slices.Contains(ignoredColumns, col.GetName()) { ++ if _, skip := ignored[col.GetName()]; skip { + return + } + // rest unchanged +@@ -502,12 +506,16 @@ func BuildFingerprintQueryForIndex( + tableDesc catalog.TableDescriptor, index catalog.Index, ignoredColumns []string, + ) (string, error) { ++ ignored := make(map[string]struct{}, len(ignoredColumns)) ++ for _, c := range ignoredColumns { ++ ignored[c] = struct{}{} ++ } + cols := make([]string, 0, len(tableDesc.PublicColumns())) + addColumn := func(col catalog.Column) { +- if slices.Contains(ignoredColumns, col.GetName()) { ++ if _, skip := ignored[col.GetName()]; skip { + return + } + + cols = append(cols, makeColumnNameOrExpr(col)) + } diff --git a/defects/cockroach/patch/cockroach-0003-ensure-roles-quadratic.patch b/defects/cockroach/patch/cockroach-0003-ensure-roles-quadratic.patch new file mode 100644 index 000000000..48bb18ecb --- /dev/null +++ b/defects/cockroach/patch/cockroach-0003-ensure-roles-quadratic.patch @@ -0,0 +1,20 @@ +# UNDF: +--- a/pkg/sql/authorization.go ++++ b/pkg/sql/authorization.go +@@ -564,10 +564,14 @@ func EnsureUserOnlyBelongsToRoles( + // Compute the differences between the current roles and the desired roles + // to determine which roles need to granted or revoked. + rolesToRevoke := make([]username.SQLUsername, 0, len(currentRoles)) + rolesToGrant := make([]username.SQLUsername, 0, len(roles)) ++ // Build a set for O(1) membership tests instead of O(D) slices.Contains. ++ desiredSet := make(map[username.SQLUsername]struct{}, len(roles)) ++ for _, r := range roles { ++ desiredSet[r] = struct{}{} ++ } + for role := range currentRoles { +- if !slices.Contains(roles, role) { ++ if _, ok := desiredSet[role]; !ok { + rolesToRevoke = append(rolesToRevoke, role) + } + } + for _, role := range roles { diff --git a/defects/cockroach/unit/CockroachTest.class b/defects/cockroach/unit/CockroachTest.class new file mode 100644 index 0000000000000000000000000000000000000000..650429d8f92767c7588771f6fb0b90e09bfd8e1b GIT binary patch literal 6879 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 B&)fh2 literal 0 HcmV?d00001 diff --git a/defects/cockroach/unit/CockroachTest.java b/defects/cockroach/unit/CockroachTest.java new file mode 100644 index 000000000..bfb16b908 --- /dev/null +++ b/defects/cockroach/unit/CockroachTest.java @@ -0,0 +1,204 @@ +import java.util.*; + +/** + * CWE-407 unit tests for CockroachDB — three defects: + * + * cockroach-0001: IndexesUsed.add() calls slices.Contains on a growing slice + * for each of N add() calls → O(N²) index deduplication during + * SQL query plan building. + * + * cockroach-0002: BuildFingerprintQueryForIndex / BuildExperimentalFingerprintQueryForIndex + * call slices.Contains(ignoredColumns, col) for every column in + * every index column loop → O(C × I) where C = columns, I = ignored list. + * + * cockroach-0003: EnsureUserOnlyBelongsToRoles iterates currentRoles (size R) and + * calls slices.Contains(roles, role) (size D) for each → O(R × D) + * during LDAP-driven role synchronisation. + */ +public class CockroachTest { + + // ── cockroach-0001 ──────────────────────────────────────────────────────── + + /** Defective: ArrayList.contains inside an accumulation loop → O(N²). */ + static List indexesUsedAdd_defective(int n) { + List indexes = new ArrayList<>(); + for (long i = 0; i < n; i++) { + long tableID = i % 50; + long indexID = i % 20; + long[] entry = new long[]{tableID, indexID}; + boolean found = false; + for (long[] e : indexes) { + if (e[0] == entry[0] && e[1] == entry[1]) { found = true; break; } + } + if (!found) indexes.add(entry); + } + return indexes; + } + + /** Fixed: HashMap set for O(1) membership → O(N) total. */ + static List indexesUsedAdd_fixed(int n) { + List indexes = new ArrayList<>(); + Set seen = new HashSet<>(); + for (long i = 0; i < n; i++) { + long tableID = i % 50; + long indexID = i % 20; + long key = tableID * 1_000_000L + indexID; + if (seen.add(key)) { + indexes.add(new long[]{tableID, indexID}); + } + } + return indexes; + } + + // ── cockroach-0002 ──────────────────────────────────────────────────────── + + /** Defective: linear scan of ignoredColumns for every column → O(C × I). */ + static List fingerprintColumns_defective(List columns, List ignored) { + List result = new ArrayList<>(); + for (String col : columns) { + if (ignored.contains(col)) continue; // O(I) per column + result.add(col); + } + return result; + } + + /** Fixed: build a HashSet once, then O(1) per column → O(C + I). */ + static List fingerprintColumns_fixed(List columns, List ignored) { + Set ignoredSet = new HashSet<>(ignored); + List result = new ArrayList<>(); + for (String col : columns) { + if (!ignoredSet.contains(col)) result.add(col); + } + return result; + } + + // ── cockroach-0003 ──────────────────────────────────────────────────────── + + /** Defective: for each currentRole call roles.contains → O(R × D). */ + static List rolesToRevoke_defective(Set currentRoles, List desiredRoles) { + List toRevoke = new ArrayList<>(); + for (String role : currentRoles) { + if (!desiredRoles.contains(role)) toRevoke.add(role); // O(D) per role + } + return toRevoke; + } + + /** Fixed: build desiredSet once → O(R + D). */ + static List rolesToRevoke_fixed(Set currentRoles, List desiredRoles) { + Set desiredSet = new HashSet<>(desiredRoles); + List toRevoke = new ArrayList<>(); + for (String role : currentRoles) { + if (!desiredSet.contains(role)) toRevoke.add(role); + } + return toRevoke; + } + + // ── helpers ─────────────────────────────────────────────────────────────── + + static long bench(Runnable r) { + long t0 = System.nanoTime(); + r.run(); + return System.nanoTime() - t0; + } + + // ── main ───────────────────────────────────────────────────────────────── + + public static void main(String[] args) { + int pass = 0, fail = 0; + + // --- cockroach-0001 correctness --- + { + List def = indexesUsedAdd_defective(500); + List fix = indexesUsedAdd_fixed(500); + if (def.size() == fix.size()) { + System.out.println("PASS cockroach-0001 correctness (size=" + def.size() + ")"); + pass++; + } else { + System.out.println("FAIL cockroach-0001 correctness def=" + def.size() + " fix=" + fix.size()); + fail++; + } + } + + // --- cockroach-0001 performance --- + { + int N = 2000; + long tDef = bench(() -> indexesUsedAdd_defective(N)); + long tFix = bench(() -> indexesUsedAdd_fixed(N)); + double ratio = (double) tDef / Math.max(tFix, 1); + System.out.printf("PASS cockroach-0001 perf defective=%dms fixed=%dms ratio=%.1fx%n", + tDef / 1_000_000, tFix / 1_000_000, ratio); + if (ratio >= 2.0) pass++; else { System.out.println("FAIL cockroach-0001 perf ratio too low"); fail++; } + } + + // --- cockroach-0002 correctness --- + { + List columns = new ArrayList<>(); + for (int i = 0; i < 200; i++) columns.add("col_" + i); + List ignored = new ArrayList<>(); + for (int i = 0; i < 50; i++) ignored.add("col_" + (i * 4)); + + List def = fingerprintColumns_defective(columns, ignored); + List fix = fingerprintColumns_fixed(columns, ignored); + if (def.equals(fix)) { + System.out.println("PASS cockroach-0002 correctness (kept=" + def.size() + ")"); + pass++; + } else { + System.out.println("FAIL cockroach-0002 correctness"); + fail++; + } + } + + // --- cockroach-0002 performance --- + { + List columns = new ArrayList<>(); + for (int i = 0; i < 1000; i++) columns.add("col_" + i); + List ignored = new ArrayList<>(); + for (int i = 0; i < 500; i++) ignored.add("col_" + (i * 2)); + + long tDef = bench(() -> fingerprintColumns_defective(columns, ignored)); + long tFix = bench(() -> fingerprintColumns_fixed(columns, ignored)); + double ratio = (double) tDef / Math.max(tFix, 1); + System.out.printf("PASS cockroach-0002 perf defective=%dms fixed=%dms ratio=%.1fx%n", + tDef / 1_000_000, tFix / 1_000_000, ratio); + if (ratio >= 1.5) pass++; else { System.out.println("FAIL cockroach-0002 perf ratio too low"); fail++; } + } + + // --- cockroach-0003 correctness --- + { + Set current = new HashSet<>(); + for (int i = 0; i < 100; i++) current.add("role_" + i); + List desired = new ArrayList<>(); + for (int i = 0; i < 60; i++) desired.add("role_" + i); + + List def = rolesToRevoke_defective(current, desired); + List fix = rolesToRevoke_fixed(current, desired); + Collections.sort(def); Collections.sort(fix); + if (def.equals(fix)) { + System.out.println("PASS cockroach-0003 correctness (toRevoke=" + def.size() + ")"); + pass++; + } else { + System.out.println("FAIL cockroach-0003 correctness def=" + def + " fix=" + fix); + fail++; + } + } + + // --- cockroach-0003 performance --- + { + Set current = new HashSet<>(); + for (int i = 0; i < 2000; i++) current.add("role_" + i); + List desired = new ArrayList<>(); + for (int i = 0; i < 1000; i++) desired.add("role_" + i); + + long tDef = bench(() -> rolesToRevoke_defective(current, desired)); + long tFix = bench(() -> rolesToRevoke_fixed(current, desired)); + double ratio = (double) tDef / Math.max(tFix, 1); + System.out.printf("PASS cockroach-0003 perf defective=%dms fixed=%dms ratio=%.1fx%n", + tDef / 1_000_000, tFix / 1_000_000, ratio); + if (ratio >= 2.0) pass++; else { System.out.println("FAIL cockroach-0003 perf ratio too low"); fail++; } + } + + System.out.println(); + System.out.println("Results: " + pass + " PASS, " + fail + " FAIL"); + if (fail > 0) System.exit(1); + } +} diff --git a/defects/influxdb/patch/influxdb-0001-persisted-files-dedup-quadratic.patch b/defects/influxdb/patch/influxdb-0001-persisted-files-dedup-quadratic.patch new file mode 100644 index 000000000..31ab9849f --- /dev/null +++ b/defects/influxdb/patch/influxdb-0001-persisted-files-dedup-quadratic.patch @@ -0,0 +1,47 @@ +# UNDF: +--- a/influxdb3_write/src/write_buffer/persisted_files.rs ++++ b/influxdb3_write/src/write_buffer/persisted_files.rs +@@ -505,10 +505,15 @@ fn update_persisted_files_with_snapshot( + if initial_load { + file_count += new_parquet_files.len() as u64; + table_files.extend(new_parquet_files.iter().cloned()); + } else { +- let mut filtered_files: Vec = new_parquet_files +- .iter() +- .filter(|file| !table_files.contains(file)) +- .cloned() +- .collect(); ++ // Build a HashSet of existing paths for O(1) dedup ++ // instead of O(T) Vec::contains per file → O(F×T) total. ++ let existing_paths: std::collections::HashSet<&str> = ++ table_files.iter().map(|f| f.path.as_str()).collect(); ++ let mut filtered_files: Vec = new_parquet_files ++ .iter() ++ .filter(|file| !existing_paths.contains(file.path.as_str())) ++ .cloned() ++ .collect(); + file_count += filtered_files.len() as u64; + table_files.append(&mut filtered_files); + } +@@ -423,10 +423,14 @@ impl Inner { + pub(crate) fn add_persisted_file( + &mut self, + db_id: &DbId, + table_id: &TableId, + parquet_file: &ParquetFile, + ) { + let existing_parquet_files = self + .files + .entry(*db_id) + .or_default() + .entry(*table_id) + .or_default(); +- if !existing_parquet_files.contains(parquet_file) { ++ // Vec::contains is O(N); use path-based guard instead. ++ let already_present = existing_parquet_files ++ .iter() ++ .any(|f| f.path == parquet_file.path); ++ if !already_present { + self.parquet_files_row_count += parquet_file.row_count; + self.parquet_files_size_mb += as_mb(parquet_file.size_bytes); + existing_parquet_files.push(parquet_file.clone()); diff --git a/defects/influxdb/patch/influxdb-0002-table-def-series-key-quadratic.patch b/defects/influxdb/patch/influxdb-0002-table-def-series-key-quadratic.patch new file mode 100644 index 000000000..e7d89ff1e --- /dev/null +++ b/defects/influxdb/patch/influxdb-0002-table-def-series-key-quadratic.patch @@ -0,0 +1,41 @@ +# UNDF: +--- a/influxdb3_catalog/src/catalog/versions/v1.rs ++++ b/influxdb3_catalog/src/catalog/versions/v1.rs +@@ -1442,10 +1442,16 @@ impl TableDefinitionV1 { + pub fn add_columns( + &mut self, + columns: Vec<(ColumnId, Arc, InfluxColumnType)>, + ) -> Result<()> { ++ // Pre-build a HashSet of existing series key IDs for O(1) membership ++ // checks instead of O(K) Vec::contains per column → O(C×K) total. ++ let mut series_key_set: std::collections::HashSet = ++ self.series_key.iter().copied().collect(); ++ + let mut cols = BTreeMap::new(); + for col_def in self.columns.resource_iter().cloned() { + cols.insert(Arc::clone(&col_def.name), col_def); + } + + let mut sort_key_changed = false; + + for (id, name, column_type) in columns { + let nullable = name.as_ref() != TIME_COLUMN_NAME; + assert!( + cols.insert( + Arc::clone(&name), + Arc::new(ColumnDefinition::new( + id, + Arc::clone(&name), + column_type, + nullable + )) + ) + .is_none(), + "attempted to add existing column" + ); + // add new tags to the series key in the order provided +- if matches!(column_type, InfluxColumnType::Tag) && !self.series_key.contains(&id) { ++ if matches!(column_type, InfluxColumnType::Tag) && series_key_set.insert(id) { + self.tag_column_name_to_position_id + .insert(Arc::clone(&name), self.series_key.len() as u8); + self.series_key.push(id); diff --git a/defects/influxdb/unit/InfluxDBTest.class b/defects/influxdb/unit/InfluxDBTest.class new file mode 100644 index 0000000000000000000000000000000000000000..2095665f5e5659cc1d4d0ac7d69af3310f528570 GIT binary patch literal 5556 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 literal 0 HcmV?d00001 diff --git a/defects/influxdb/unit/InfluxDBTest.java b/defects/influxdb/unit/InfluxDBTest.java new file mode 100644 index 000000000..dc24e9ebc --- /dev/null +++ b/defects/influxdb/unit/InfluxDBTest.java @@ -0,0 +1,162 @@ +import java.util.*; + +/** + * CWE-407 unit tests for InfluxDB 3 (Rust) — two defects: + * + * influxdb-0001: update_persisted_files_with_snapshot (runtime path) + * iterates new_parquet_files and calls Vec::contains on + * table_files for each → O(F × T) where F = new files, + * T = existing files per table. Also: add_persisted_file + * calls Vec::contains on the same growing Vec → O(N²) if + * called N times. + * Location: influxdb3_write/src/write_buffer/persisted_files.rs + * + * influxdb-0002: TableDefinitionV1::add_columns loops over incoming columns + * and calls Vec::contains on self.series_key for each tag + * column → O(C × K) where C = new columns, K = existing + * series key length. + * Location: influxdb3_catalog/src/catalog/versions/v1.rs + */ +public class InfluxDBTest { + + // ── influxdb-0001 ───────────────────────────────────────────────────────── + + /** Defective: List.contains inside a filter loop → O(F × T). */ + static List mergeFiles_defective(List tableFiles, List newFiles) { + List result = new ArrayList<>(tableFiles); + List filtered = new ArrayList<>(); + for (String f : newFiles) { + if (!result.contains(f)) { // O(T) per file + filtered.add(f); + } + } + result.addAll(filtered); + return result; + } + + /** Fixed: HashSet for O(1) membership per file → O(F + T). */ + static List mergeFiles_fixed(List tableFiles, List newFiles) { + Set existing = new HashSet<>(tableFiles); + List result = new ArrayList<>(tableFiles); + for (String f : newFiles) { + if (existing.add(f)) { // O(1): add returns false if already present + result.add(f); + } + } + return result; + } + + // ── influxdb-0002 ───────────────────────────────────────────────────────── + + /** + * Defective: for each tag column, call List.contains on growing seriesKey → O(C × K). + * seriesKey grows as tags are accepted, making this quadratic when all columns are tags. + */ + static List addColumns_defective(List seriesKey, List tagColumns) { + List key = new ArrayList<>(seriesKey); + for (int id : tagColumns) { + if (!key.contains(id)) { // O(K) per column + key.add(id); + } + } + return key; + } + + /** Fixed: pre-build HashSet → O(C + K). */ + static List addColumns_fixed(List seriesKey, List tagColumns) { + Set keySet = new HashSet<>(seriesKey); + List key = new ArrayList<>(seriesKey); + for (int id : tagColumns) { + if (keySet.add(id)) { // O(1) + key.add(id); + } + } + return key; + } + + // ── helpers ─────────────────────────────────────────────────────────────── + + static long bench(Runnable r) { + long t0 = System.nanoTime(); + r.run(); + return System.nanoTime() - t0; + } + + // ── main ───────────────────────────────────────────────────────────────── + + public static void main(String[] args) { + int pass = 0, fail = 0; + + // --- influxdb-0001 correctness --- + { + List existing = new ArrayList<>(); + for (int i = 0; i < 100; i++) existing.add("file-" + i + ".parquet"); + List incoming = new ArrayList<>(); + for (int i = 50; i < 150; i++) incoming.add("file-" + i + ".parquet"); + + List def = mergeFiles_defective(existing, incoming); + List fix = mergeFiles_fixed(existing, incoming); + Collections.sort(def); Collections.sort(fix); + if (def.equals(fix)) { + System.out.println("PASS influxdb-0001 correctness (total=" + def.size() + ")"); + pass++; + } else { + System.out.println("FAIL influxdb-0001 correctness def=" + def.size() + " fix=" + fix.size()); + fail++; + } + } + + // --- influxdb-0001 performance --- + { + List existing = new ArrayList<>(); + for (int i = 0; i < 2000; i++) existing.add("snap-" + i + ".parquet"); + List incoming = new ArrayList<>(); + for (int i = 1000; i < 3000; i++) incoming.add("snap-" + i + ".parquet"); + + long tDef = bench(() -> mergeFiles_defective(existing, incoming)); + long tFix = bench(() -> mergeFiles_fixed(existing, incoming)); + double ratio = (double) tDef / Math.max(tFix, 1); + System.out.printf("PASS influxdb-0001 perf defective=%dms fixed=%dms ratio=%.1fx%n", + tDef / 1_000_000, tFix / 1_000_000, ratio); + if (ratio >= 2.0) pass++; else { System.out.println("FAIL influxdb-0001 perf ratio too low"); fail++; } + } + + // --- influxdb-0002 correctness --- + { + List seriesKey = new ArrayList<>(); + for (int i = 0; i < 10; i++) seriesKey.add(i); + List tagCols = new ArrayList<>(); + // Mix of existing and new tag column ids + for (int i = 5; i < 50; i++) tagCols.add(i); + + List def = addColumns_defective(seriesKey, tagCols); + List fix = addColumns_fixed(seriesKey, tagCols); + if (def.equals(fix)) { + System.out.println("PASS influxdb-0002 correctness (keySize=" + def.size() + ")"); + pass++; + } else { + System.out.println("FAIL influxdb-0002 correctness def=" + def + " fix=" + fix); + fail++; + } + } + + // --- influxdb-0002 performance --- + { + List seriesKey = new ArrayList<>(); + for (int i = 0; i < 100; i++) seriesKey.add(i); + List tagCols = new ArrayList<>(); + for (int i = 0; i < 5000; i++) tagCols.add(i); // many duplicates + new + + long tDef = bench(() -> addColumns_defective(seriesKey, tagCols)); + long tFix = bench(() -> addColumns_fixed(seriesKey, tagCols)); + double ratio = (double) tDef / Math.max(tFix, 1); + System.out.printf("PASS influxdb-0002 perf defective=%dms fixed=%dms ratio=%.1fx%n", + tDef / 1_000_000, tFix / 1_000_000, ratio); + if (ratio >= 2.0) pass++; else { System.out.println("FAIL influxdb-0002 perf ratio too low"); fail++; } + } + + System.out.println(); + System.out.println("Results: " + pass + " PASS, " + fail + " FAIL"); + if (fail > 0) System.exit(1); + } +}