From 09c8ed57b5e3d0e17858b407cb088753f74ba8e1 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 17:09:49 +0000 Subject: [PATCH 01/24] Add libm Rust dependency The histogram statistics need erfc() and lgamma(); the std equivalents (float_erf, float_gamma) are unstable. libm has no dependencies. --- deps/rust/Cargo.lock | 7 +++++++ deps/rust/Cargo.toml | 2 ++ 2 files changed, 9 insertions(+) diff --git a/deps/rust/Cargo.lock b/deps/rust/Cargo.lock index 2b6a6f641ae..1907d77f73e 100644 --- a/deps/rust/Cargo.lock +++ b/deps/rust/Cargo.lock @@ -584,6 +584,7 @@ dependencies = [ "hyper-util", "ipnet", "libc", + "libm", "libz-rs-sys", "lol_html_c_api", "nix", @@ -1327,6 +1328,12 @@ version = "0.2.190" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libz-rs-sys" version = "0.6.8" diff --git a/deps/rust/Cargo.toml b/deps/rust/Cargo.toml index 52d582ca696..850f5de9a21 100644 --- a/deps/rust/Cargo.toml +++ b/deps/rust/Cargo.toml @@ -64,6 +64,8 @@ hyper = { version = "1", default-features = false, features = ["client", "http1" # kj-hyper supplies; tower-service is the trait that connector implements. hyper-util = { version = "0", default-features = false, features = ["client-legacy", "http1", "tokio"] } libc = "0" +# erfc() and lgamma() for histogram statistics; the std equivalents are unstable. +libm = "0" lol_html_c_api = { git = "https://github.com/cloudflare/lol-html" } hashlink = "0" # kj-rs-io: `fs` for fcntl (KJ's fd flags), `signal` for ignoring SIGPIPE once per process like From bac5181238226094cea50c3154b2493ae7efb15f Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 17:18:25 +0000 Subject: [PATCH 02/24] Add histogram crate with an HdrHistogram core Adds src/rust/histogram, which provides HDR histograms that behave like Node.js's native perf_hooks histograms. It will back the statistics of the `workerd bench` harness and node:perf_hooks createHistogram(). `hdr` ports the parts of HdrHistogram_c (0.12.0, as vendored by Node.js; public domain under CC0) that Node.js uses: bucket layout, recording, merging, the internal-counter reset, the value-equivalence helpers, percentiles, mean, standard deviation, and the recorded, percentile, linear, and logarithmic iterators. It follows the C code closely, including the round-half-up percentile target, the iterators' boundary steps, and the order of floating-point operations, so that results match Node.js exactly. The shift operations, and with them a nonzero normalizing index offset, are not ported because Node.js never uses them. `Histogram` adds the state Node.js keeps alongside the HDR data: the exceeds count, the reset count, the recordDelta() timestamp, and the EWMA, with Node.js's snapshot(), diff(), add(), subtract(), and reset() semantics. --- src/rust/histogram/BUILD.bazel | 9 + src/rust/histogram/hdr-test.rs | 194 +++++++ src/rust/histogram/hdr.rs | 837 +++++++++++++++++++++++++++ src/rust/histogram/histogram-test.rs | 172 ++++++ src/rust/histogram/histogram.rs | 398 +++++++++++++ src/rust/histogram/lib.rs | 41 ++ 6 files changed, 1651 insertions(+) create mode 100644 src/rust/histogram/BUILD.bazel create mode 100644 src/rust/histogram/hdr-test.rs create mode 100644 src/rust/histogram/hdr.rs create mode 100644 src/rust/histogram/histogram-test.rs create mode 100644 src/rust/histogram/histogram.rs create mode 100644 src/rust/histogram/lib.rs diff --git a/src/rust/histogram/BUILD.bazel b/src/rust/histogram/BUILD.bazel new file mode 100644 index 00000000000..78d531c5bc9 --- /dev/null +++ b/src/rust/histogram/BUILD.bazel @@ -0,0 +1,9 @@ +load("//:build/wd_rust_crate.bzl", "wd_rust_crate") + +wd_rust_crate( + name = "histogram", + visibility = ["//visibility:public"], + deps = [ + "@crates_vendor//:thiserror", + ], +) diff --git a/src/rust/histogram/hdr-test.rs b/src/rust/histogram/hdr-test.rs new file mode 100644 index 00000000000..2ce2f94f79c --- /dev/null +++ b/src/rust/histogram/hdr-test.rs @@ -0,0 +1,194 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +use super::*; + +const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1; + +#[test] +fn layout_matches_hdr_histogram_c() { + // Node.js documents 352 KiB for the default createHistogram() options; that is the counts + // array of (1 + 43 buckets) * 1024 half sub-buckets. + let h = Hdr::new(1, MAX_SAFE_INTEGER, 3).unwrap(); + assert_eq!(h.counts_len(), 45056); + assert_eq!(h.counts().len(), 45056); + + let h = Hdr::new(1, i64::MAX, 3).unwrap(); + assert_eq!(h.counts_len(), 54 * 1024); + + let h = Hdr::new(1, 1_000_000, 1).unwrap(); + // 32 sub-buckets; 16 buckets cover 32 * 2^15 = 1048576. + assert_eq!(h.sub_bucket_count, 32); + assert_eq!(h.counts_len(), 17 * 16); +} + +#[test] +fn invalid_layouts_are_rejected() { + assert_eq!(Hdr::new(0, 100, 3).unwrap_err(), Error::InvalidOptions); + assert_eq!(Hdr::new(1, 100, 0).unwrap_err(), Error::InvalidOptions); + assert_eq!(Hdr::new(1, 100, 6).unwrap_err(), Error::InvalidOptions); + assert_eq!(Hdr::new(51, 100, 3).unwrap_err(), Error::InvalidOptions); + assert!(Hdr::new(50, 100, 3).is_ok()); + // unit_magnitude + sub_bucket_half_count_magnitude must not exceed 61. + assert_eq!( + Hdr::new(1 << 50, i64::MAX, 5).unwrap_err(), + Error::InvalidOptions + ); +} + +#[test] +fn equivalent_value_ranges() { + let h = Hdr::new(1, MAX_SAFE_INTEGER, 3).unwrap(); + // Unit resolution up to 2048, then 2 up to 4096, then 4. + assert_eq!(h.size_of_equivalent_value_range(2047), 1); + assert_eq!(h.size_of_equivalent_value_range(2048), 2); + assert_eq!(h.lowest_equivalent_value(2049), 2048); + assert_eq!(h.highest_equivalent_value(2048), 2049); + assert_eq!(h.median_equivalent_value(2048), 2049); + assert_eq!(h.size_of_equivalent_value_range(4096), 4); + assert_eq!(h.lowest_equivalent_value(4099), 4096); + assert_eq!(h.highest_equivalent_value(4096), 4099); + assert_eq!(h.highest_equivalent_value(i64::MAX), i64::MAX); +} + +#[test] +fn index_round_trip() { + let h = Hdr::new(1, MAX_SAFE_INTEGER, 2).unwrap(); + for value in [ + 0, + 1, + 2, + 199, + 200, + 255, + 256, + 1000, + 123_456_789, + MAX_SAFE_INTEGER, + ] { + let index = h.counts_index_for(value); + assert_eq!( + h.value_at_index(index), + h.lowest_equivalent_value(value), + "{value}" + ); + } +} + +#[test] +fn lowest_power_of_two_sets_unit_magnitude() { + let h = Hdr::new(1024, 1 << 40, 3).unwrap(); + assert_eq!(h.unit_magnitude, 10); + assert_eq!(h.size_of_equivalent_value_range(0), 1024); +} + +#[test] +fn record_tracks_raw_min_and_max() { + let mut h = Hdr::new(1, 1_000_000, 3).unwrap(); + assert_eq!(h.min(), i64::MAX); + assert_eq!(h.max(), 0); + assert!(h.mean().is_nan()); + assert!(h.stddev().is_nan()); + + assert!(h.record_value(5000)); + assert!(h.record_value(7)); + assert_eq!(h.min_value(), 7); + assert_eq!(h.max_value(), 5000); + assert_eq!(h.min(), 7); + // 5000 is in a bucket of width 4. + assert_eq!(h.max(), 5003); + + // Recording 0 makes the minimum 0 without changing the raw field. + assert!(h.record_value(0)); + assert_eq!(h.min(), 0); + assert_eq!(h.min_value(), 7); + + assert!(!h.record_value(-1)); + assert!(!h.record_value(1_000_001)); + assert!(!h.record_values(1, -1)); + assert_eq!(h.total_count(), 3); +} + +#[test] +fn reset_internal_counters_derives_min_and_max_from_counts() { + let mut h = Hdr::new(1, 1_000_000, 3).unwrap(); + h.record_value(5001); + h.record_value(9); + h.reset_internal_counters(); + assert_eq!(h.total_count(), 2); + assert_eq!(h.min_value(), 9); + assert_eq!(h.max_value(), 5003); + + h.reset(); + h.reset_internal_counters(); + assert_eq!(h.min_value(), i64::MAX); + assert_eq!(h.max_value(), 0); +} + +#[test] +fn percentile_rounds_target_count_half_up() { + let mut h = Hdr::new(1, 1_000, 3).unwrap(); + for value in 1..=10 { + h.record_value(value); + } + // 12% of 10 values is 1.2, which rounds to the first value; rounding up would give the second. + assert_eq!(h.value_at_percentile(12.0), 1); + assert_eq!(h.value_at_percentile(15.0), 2); + assert_eq!(h.value_at_percentile(50.0), 5); + assert_eq!(h.value_at_percentile(100.0), 10); + assert_eq!(h.value_at_percentile(150.0), 10); + assert_eq!( + h.value_at_percentiles(&[12.0, 15.0, 50.0, 100.0]), + [1, 2, 5, 10] + ); +} + +#[test] +fn value_at_percentiles_of_empty_histogram_keeps_target_counts() { + let h = Hdr::new(1, 1_000, 3).unwrap(); + assert_eq!(h.value_at_percentiles(&[50.0, 100.0]), [1, 1]); + assert_eq!(h.value_at_percentile(50.0), 0); +} + +#[test] +fn record_corrected_value_backfills() { + let mut h = Hdr::new(1, 1_000, 3).unwrap(); + assert!(h.record_corrected_value(100, 30)); + let values: Vec<_> = h.recorded().map(|b| b.value).collect(); + assert_eq!(values, [40, 70, 100]); +} + +#[test] +fn add_reports_dropped_values() { + let mut small = Hdr::new(1, 1_000, 3).unwrap(); + let mut big = Hdr::new(1, 1_000_000, 3).unwrap(); + big.record_values(10, 3); + big.record_values(500_000, 2); + assert_eq!(small.add(&big), 2); + assert_eq!(small.total_count(), 3); +} + +#[test] +fn linear_and_log_buckets() { + let mut h = Hdr::new(1, 1_000, 3).unwrap(); + for value in [1, 2, 3, 15, 25] { + h.record_value(value); + } + assert_eq!(h.linear_buckets(10), [(10, 3), (20, 1), (30, 1)]); + assert_eq!( + h.log_buckets(1, 2.0), + [(1, 1), (2, 1), (4, 1), (8, 0), (16, 1), (32, 1)] + ); + // A non-positive step never advances; everything lands in one step. + assert_eq!( + h.linear_buckets(0), + [(h.value_at_index(h.counts_len() - 1), 5)] + ); +} + +#[test] +fn percentile_iteration_of_empty_histogram() { + let h = Hdr::new(1, 1_000, 3).unwrap(); + assert_eq!(h.percentiles(1), [(100.0, 0)]); +} diff --git a/src/rust/histogram/hdr.rs b/src/rust/histogram/hdr.rs new file mode 100644 index 00000000000..41474726393 --- /dev/null +++ b/src/rust/histogram/hdr.rs @@ -0,0 +1,837 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! A port of the core of `HdrHistogram_c` (`hdr_histogram.c`, version 0.12.0 as vendored by +//! Node.js in `deps/histogram`), which is released to the public domain under CC0. +//! +//! Only what Node.js's `Histogram` uses is ported: bucket layout, recording, merging, the +//! internal-counter reset, the value-equivalence helpers, percentiles, mean, standard deviation, +//! and the all-values, recorded, percentile, linear, and logarithmic iterators. The behavior, +//! including edge cases and the order of floating-point operations, follows the C code so that +//! results match Node.js exactly. +//! +//! Two parts of the C structure are left out. `normalizing_index_offset` is only changed by the +//! shift operations, which Node.js never calls, so it is always zero here. `conversion_ratio` is +//! only read by the double-histogram API; it is kept as an opaque value so that an import followed +//! by an export round-trips it. + +#![expect( + clippy::cast_precision_loss, + reason = "counts and values are converted to f64 exactly where HdrHistogram_c converts them to double" +)] +#![expect( + clippy::suboptimal_flops, + reason = "fused or specialized operations would round differently from the C code" +)] + +use crate::Error; + +/// The bucket layout and recorded counts of a histogram: `struct hdr_histogram`. +#[derive(Debug)] +pub struct Hdr { + lowest_discernible_value: i64, + highest_trackable_value: i64, + unit_magnitude: i32, + significant_figures: i32, + sub_bucket_half_count_magnitude: i32, + sub_bucket_half_count: i32, + sub_bucket_mask: i64, + sub_bucket_count: i32, + min_value: i64, + max_value: i64, + counts_len: i32, + total_count: i64, + conversion_ratio: f64, + counts: Vec, +} + +/// The position and derived values of an iterator over the counts array: the common fields of +/// `struct hdr_iter`. +#[derive(Debug, Clone, Copy)] +pub struct IterState { + counts_index: i32, + total_count: i64, + /// The count at the current index. + pub count: i64, + /// The sum of the counts up to and including the current index. + pub cumulative_count: i64, + /// The lowest value of the current index. + pub value: i64, + pub highest_equivalent_value: i64, + pub lowest_equivalent_value: i64, + pub median_equivalent_value: i64, + pub value_iterated_from: i64, + pub value_iterated_to: i64, +} + +/// One step of a recorded-values iteration. +#[derive(Debug, Clone, Copy)] +pub struct RecordedBucket { + pub value: i64, + pub count: i64, + pub cumulative_count: i64, + pub median_equivalent_value: i64, +} + +fn power(base: i64, mut exp: i64) -> i64 { + let mut result = 1_i64; + while exp != 0 { + result *= base; + exp -= 1; + } + result +} + +fn buckets_needed_to_cover_value(value: i64, sub_bucket_count: i32, unit_magnitude: i32) -> i32 { + let mut smallest_untrackable_value = i64::from(sub_bucket_count) << unit_magnitude; + let mut buckets_needed = 1; + while smallest_untrackable_value <= value { + if smallest_untrackable_value > i64::MAX / 2 { + return buckets_needed + 1; + } + smallest_untrackable_value <<= 1; + buckets_needed += 1; + } + buckets_needed +} + +fn value_from_index(bucket_index: i32, sub_bucket_index: i32, unit_magnitude: i32) -> i64 { + i64::from(sub_bucket_index) << (bucket_index + unit_magnitude) +} + +impl Hdr { + /// `hdr_init()`: validates the layout and allocates the counts array. + pub fn new( + lowest_discernible_value: i64, + highest_trackable_value: i64, + significant_figures: i32, + ) -> Result { + if lowest_discernible_value < 1 + || !(1..=5).contains(&significant_figures) + || lowest_discernible_value > highest_trackable_value / 2 + { + return Err(Error::InvalidOptions); + } + + let largest_value_with_single_unit_resolution = + 2 * power(10, i64::from(significant_figures)); + // C computes both magnitudes as log(x) / log(2), then rounds up and truncates + // respectively. log2() would round differently for exact powers of two. + let sub_bucket_count_magnitude = + ((largest_value_with_single_unit_resolution as f64).ln() / 2_f64.ln()).ceil() as i32; + let sub_bucket_half_count_magnitude = sub_bucket_count_magnitude.max(1) - 1; + let unit_magnitude = (lowest_discernible_value as f64).ln() / 2_f64.ln(); + // A positive i64 has a magnitude below 63, so the truncation cannot overflow. + let unit_magnitude = unit_magnitude as i32; + + let sub_bucket_count = 1_i32 << (sub_bucket_half_count_magnitude + 1); + let sub_bucket_half_count = sub_bucket_count / 2; + + if unit_magnitude + sub_bucket_half_count_magnitude > 61 { + return Err(Error::InvalidOptions); + } + + let sub_bucket_mask = (i64::from(sub_bucket_count) - 1) << unit_magnitude; + let bucket_count = buckets_needed_to_cover_value( + highest_trackable_value, + sub_bucket_count, + unit_magnitude, + ); + let counts_len = (bucket_count + 1) * (sub_bucket_count / 2); + + let len = usize::try_from(counts_len).map_err(|_| Error::InvalidOptions)?; + let mut counts = Vec::new(); + counts + .try_reserve_exact(len) + .map_err(|_| Error::OutOfMemory)?; + counts.resize(len, 0); + + Ok(Self { + lowest_discernible_value, + highest_trackable_value, + unit_magnitude, + significant_figures, + sub_bucket_half_count_magnitude, + sub_bucket_half_count, + sub_bucket_mask, + sub_bucket_count, + min_value: i64::MAX, + max_value: 0, + counts_len, + total_count: 0, + conversion_ratio: 1.0, + counts, + }) + } + + /// A copy of the layout and recorded data. Unlike `Clone`, reports allocation failure. + pub fn try_clone(&self) -> Result { + let mut counts = Vec::new(); + counts + .try_reserve_exact(self.counts.len()) + .map_err(|_| Error::OutOfMemory)?; + counts.extend_from_slice(&self.counts); + Ok(Self { counts, ..*self }) + } + + /// An empty histogram with the same layout. + pub fn with_same_layout(&self) -> Result { + Self::new( + self.lowest_discernible_value, + self.highest_trackable_value, + self.significant_figures, + ) + } + + pub fn lowest_discernible_value(&self) -> i64 { + self.lowest_discernible_value + } + + pub fn highest_trackable_value(&self) -> i64 { + self.highest_trackable_value + } + + pub fn significant_figures(&self) -> i32 { + self.significant_figures + } + + pub fn counts_len(&self) -> i32 { + self.counts_len + } + + pub fn total_count(&self) -> i64 { + self.total_count + } + + pub fn counts(&self) -> &[i64] { + &self.counts + } + + /// The raw `min_value` field: the smallest nonzero value recorded, or `i64::MAX`. + pub fn min_value(&self) -> i64 { + self.min_value + } + + /// The raw `max_value` field: the largest value recorded, or 0. + pub fn max_value(&self) -> i64 { + self.max_value + } + + pub fn conversion_ratio(&self) -> f64 { + self.conversion_ratio + } + + pub fn set_conversion_ratio(&mut self, ratio: f64) { + self.conversion_ratio = ratio; + } + + /// Overwrites the raw `min_value` and `max_value` fields, as an import does. + pub fn set_min_max(&mut self, min_value: i64, max_value: i64) { + self.min_value = min_value; + self.max_value = max_value; + } + + /// The count at an index; out-of-range indexes read as 0, as in `hdr_count_at_index()`. + pub fn count_at_index(&self, index: i32) -> i64 { + usize::try_from(index) + .ok() + .and_then(|i| self.counts.get(i)) + .copied() + .unwrap_or(0) + } + + /// Mutable access to the counts. A caller that changes them must call + /// `reset_internal_counters()` afterwards. + pub fn counts_mut(&mut self) -> &mut [i64] { + &mut self.counts + } + + /// `hdr_get_memory_size()`, with this struct in place of the C one. + pub fn memory_size(&self) -> usize { + size_of::() + self.counts.len() * size_of::() + } + + // Index arithmetic. + + fn bucket_index(&self, value: i64) -> i32 { + // The smallest power of 2 containing the value. Callers pass non-negative values. + let leading_zeros = (value | self.sub_bucket_mask) + .cast_unsigned() + .leading_zeros() + .cast_signed(); + let pow2ceiling = 64 - leading_zeros; + pow2ceiling - self.unit_magnitude - (self.sub_bucket_half_count_magnitude + 1) + } + + fn sub_bucket_index(&self, value: i64, bucket_index: i32) -> i32 { + // Bounded by sub_bucket_count, so the truncation is lossless. + (value >> (bucket_index + self.unit_magnitude)) as i32 + } + + fn counts_index(&self, bucket_index: i32, sub_bucket_index: i32) -> i32 { + let bucket_base_index = (bucket_index + 1) << self.sub_bucket_half_count_magnitude; + let offset_in_bucket = sub_bucket_index - self.sub_bucket_half_count; + bucket_base_index + offset_in_bucket + } + + fn counts_index_for(&self, value: i64) -> i32 { + let bucket_index = self.bucket_index(value); + let sub_bucket_index = self.sub_bucket_index(value, bucket_index); + self.counts_index(bucket_index, sub_bucket_index) + } + + /// `hdr_value_at_index()`. + pub fn value_at_index(&self, index: i32) -> i64 { + let mut bucket_index = (index >> self.sub_bucket_half_count_magnitude) - 1; + let mut sub_bucket_index = + (index & (self.sub_bucket_half_count - 1)) + self.sub_bucket_half_count; + if bucket_index < 0 { + sub_bucket_index -= self.sub_bucket_half_count; + bucket_index = 0; + } + value_from_index(bucket_index, sub_bucket_index, self.unit_magnitude) + } + + fn size_of_range_given_indices(&self, bucket_index: i32, sub_bucket_index: i32) -> i64 { + let adjusted_bucket = if sub_bucket_index >= self.sub_bucket_count { + bucket_index + 1 + } else { + bucket_index + }; + 1_i64 << (self.unit_magnitude + adjusted_bucket) + } + + /// `hdr_size_of_equivalent_value_range()`. + pub fn size_of_equivalent_value_range(&self, value: i64) -> i64 { + let bucket_index = self.bucket_index(value); + let sub_bucket_index = self.sub_bucket_index(value, bucket_index); + self.size_of_range_given_indices(bucket_index, sub_bucket_index) + } + + /// `hdr_lowest_equivalent_value()`. + pub fn lowest_equivalent_value(&self, value: i64) -> i64 { + let bucket_index = self.bucket_index(value); + let sub_bucket_index = self.sub_bucket_index(value, bucket_index); + value_from_index(bucket_index, sub_bucket_index, self.unit_magnitude) + } + + /// `highest_equivalent_value()`, saturating at the top of the range. + pub fn highest_equivalent_value(&self, value: i64) -> i64 { + let low = self.lowest_equivalent_value(value); + let size = self.size_of_equivalent_value_range(value); + if low > i64::MAX - size { + return i64::MAX; + } + low + size - 1 + } + + /// `hdr_median_equivalent_value()`. + pub fn median_equivalent_value(&self, value: i64) -> i64 { + self.lowest_equivalent_value(value) + (self.size_of_equivalent_value_range(value) >> 1) + } + + // Recording. + + fn update_min_max(&mut self, value: i64) { + if value > self.max_value { + self.max_value = value; + } + if value != 0 && value < self.min_value { + self.min_value = value; + } + } + + fn record_value_counted(&mut self, value: i64, count: i64) -> bool { + if value < 0 || self.highest_trackable_value < value { + return false; + } + let Ok(index) = usize::try_from(self.counts_index_for(value)) else { + return false; + }; + let Some(slot) = self.counts.get_mut(index) else { + return false; + }; + // The C code overflows here only with more than 2^63 values recorded; wrap as it does. + *slot = slot.wrapping_add(count); + self.total_count = self.total_count.wrapping_add(count); + self.update_min_max(value); + true + } + + /// `hdr_record_value()`. + pub fn record_value(&mut self, value: i64) -> bool { + self.record_value_counted(value, 1) + } + + /// `hdr_record_values()`. + pub fn record_values(&mut self, value: i64, count: i64) -> bool { + if count < 0 { + return false; + } + self.record_value_counted(value, count) + } + + /// `hdr_record_corrected_value()`. + pub fn record_corrected_value(&mut self, value: i64, expected_interval: i64) -> bool { + if !self.record_values(value, 1) { + return false; + } + if expected_interval <= 0 || value <= expected_interval { + return true; + } + let mut missing_value = value - expected_interval; + while missing_value >= expected_interval { + if !self.record_values(missing_value, 1) { + return false; + } + missing_value -= expected_interval; + } + true + } + + /// `hdr_add()`: records every value of `from`. Returns the number of values that did not fit. + pub fn add(&mut self, from: &Self) -> i64 { + let mut dropped = 0_i64; + for bucket in from.recorded() { + if !self.record_values(bucket.value, bucket.count) { + dropped = dropped.wrapping_add(bucket.count); + } + } + dropped + } + + /// `hdr_reset()`. + pub fn reset(&mut self) { + self.total_count = 0; + self.min_value = i64::MAX; + self.max_value = 0; + self.counts.fill(0); + } + + /// `hdr_reset_internal_counters()`: recomputes the total count, `min_value`, and `max_value` + /// from the counts. + pub fn reset_internal_counters(&mut self) { + let mut min_non_zero_index = -1_i32; + let mut max_index = -1_i32; + let mut observed_total_count = 0_i64; + + for (i, &count) in (0_i32..).zip(self.counts.iter()) { + if count > 0 { + observed_total_count = if count > i64::MAX - observed_total_count { + i64::MAX + } else { + observed_total_count + count + }; + max_index = i; + if min_non_zero_index == -1 && i != 0 { + min_non_zero_index = i; + } + } + } + + self.max_value = if max_index == -1 { + 0 + } else { + self.highest_equivalent_value(self.value_at_index(max_index)) + }; + self.min_value = if min_non_zero_index == -1 { + i64::MAX + } else { + self.value_at_index(min_non_zero_index) + }; + self.total_count = observed_total_count; + } + + // Queries. + + /// `hdr_min()`. + pub fn min(&self) -> i64 { + if self.count_at_index(0) > 0 { + return 0; + } + if self.min_value == i64::MAX { + return i64::MAX; + } + self.lowest_equivalent_value(self.min_value) + } + + /// `hdr_max()`. + pub fn max(&self) -> i64 { + if self.max_value == 0 { + return 0; + } + self.highest_equivalent_value(self.max_value) + } + + /// `hdr_mean()`. NaN for an empty histogram. + pub fn mean(&self) -> f64 { + let mut total = 0.0_f64; + let mut count = 0_i64; + let mut iter = self.iter_all(); + while self.move_next(&mut iter) && count < self.total_count { + if iter.count != 0 { + count += iter.count; + total += iter.count as f64 * self.median_equivalent_value(iter.value) as f64; + } + } + total / self.total_count as f64 + } + + /// `hdr_stddev()`: the population standard deviation. NaN for an empty histogram. + pub fn stddev(&self) -> f64 { + let mean = self.mean(); + let mut geometric_dev_total = 0.0_f64; + let mut iter = self.iter_all(); + while self.move_next(&mut iter) { + if iter.count != 0 { + let dev = self.median_equivalent_value(iter.value) as f64 - mean; + geometric_dev_total += (dev * dev) * iter.count as f64; + } + } + (geometric_dev_total / self.total_count as f64).sqrt() + } + + /// `hdr_count_at_value()`. + pub fn count_at_value(&self, value: i64) -> i64 { + if value < 0 { + return 0; + } + self.count_at_index(self.counts_index_for(value)) + } + + fn value_from_index_up_to_count(&self, count_at_percentile: i64) -> i64 { + let count_at_percentile = count_at_percentile.max(1); + let mut running = 0_i64; + for (index, &count) in (0_i32..).zip(self.counts.iter()) { + running += count; + if running >= count_at_percentile { + return self.value_at_index(index); + } + } + 0 + } + + /// `hdr_value_at_percentile()`. The target count is rounded half up, not up. + pub fn value_at_percentile(&self, percentile: f64) -> i64 { + let requested_percentile = percentile.min(100.0); + let count_at_percentile = + ((requested_percentile / 100.0) * self.total_count as f64 + 0.5) as i64; + let value = self.value_from_index_up_to_count(count_at_percentile); + if percentile == 0.0 { + return self.lowest_equivalent_value(value); + } + self.highest_equivalent_value(value) + } + + /// `hdr_value_at_percentiles()`: the values at each percentile in one pass. The percentiles + /// must be ascending. A position the scan does not reach, which only happens for an empty + /// histogram or descending input, keeps its target count, as in C. + pub fn value_at_percentiles(&self, percentiles: &[f64]) -> Vec { + let mut values: Vec = percentiles + .iter() + .map(|&p| { + let requested_percentile = if p < 100.0 { p } else { 100.0 }; + let count_at_percentile = + ((requested_percentile / 100.0) * self.total_count as f64 + 0.5) as i64; + count_at_percentile.max(1) + }) + .collect(); + + let mut total = 0_u64; + let mut at_pos = 0; + for (index, &count) in (0_i32..).zip(self.counts.iter()) { + if at_pos >= values.len() { + break; + } + total = total.wrapping_add(count.cast_unsigned()); + while at_pos < values.len() && total.cast_signed() >= values[at_pos] { + values[at_pos] = self.highest_equivalent_value(self.value_at_index(index)); + at_pos += 1; + } + } + values + } + + // Iteration. `IterState` mirrors the fields of `struct hdr_iter` and the functions below + // mirror the C iterator functions one to one, so that the iterators built on them visit the + // same values in the same order. + + /// `hdr_iter_init()`. + pub fn iter_all(&self) -> IterState { + IterState { + counts_index: -1, + total_count: self.total_count, + count: 0, + cumulative_count: 0, + value: 0, + highest_equivalent_value: 0, + lowest_equivalent_value: 0, + median_equivalent_value: 0, + value_iterated_from: 0, + value_iterated_to: 0, + } + } + + fn has_buckets(&self, iter: &IterState) -> bool { + iter.counts_index < self.counts_len + } + + fn has_next(iter: &IterState) -> bool { + iter.cumulative_count < iter.total_count + } + + /// `move_next()`: advances to the next index. Returns false, leaving the derived values of + /// the previous index in place, when there is none. + pub fn move_next(&self, iter: &mut IterState) -> bool { + iter.counts_index += 1; + if !self.has_buckets(iter) { + return false; + } + iter.count = self.count_at_index(iter.counts_index); + iter.cumulative_count += iter.count; + let value = self.value_at_index(iter.counts_index); + let bucket_index = self.bucket_index(value); + let sub_bucket_index = self.sub_bucket_index(value, bucket_index); + let leq = value_from_index(bucket_index, sub_bucket_index, self.unit_magnitude); + let size = self.size_of_range_given_indices(bucket_index, sub_bucket_index); + iter.lowest_equivalent_value = leq; + iter.value = value; + iter.highest_equivalent_value = if leq > i64::MAX - size { + i64::MAX + } else { + leq + size - 1 + }; + iter.median_equivalent_value = leq + (size >> 1); + true + } + + fn peek_next_value_from_index(&self, iter: &IterState) -> i64 { + let index = iter.counts_index + 1; + let mut bucket_index = + (index.cast_unsigned() >> self.sub_bucket_half_count_magnitude).cast_signed() - 1; + let mut sub_bucket_index = + (index & (self.sub_bucket_half_count - 1)) + self.sub_bucket_half_count; + if bucket_index < 0 { + sub_bucket_index -= self.sub_bucket_half_count; + bucket_index = 0; + } + let shift = bucket_index + self.unit_magnitude; + if shift >= 63 + || u64::from(sub_bucket_index.cast_unsigned()) > (i64::MAX.cast_unsigned() >> shift) + { + return i64::MAX; + } + value_from_index(bucket_index, sub_bucket_index, self.unit_magnitude) + } + + fn next_value_greater_than_reporting_level_upper_bound( + &self, + iter: &IterState, + reporting_level_upper_bound: i64, + ) -> bool { + if iter.counts_index >= self.counts_len { + return false; + } + self.peek_next_value_from_index(iter) > reporting_level_upper_bound + } + + fn basic_iter_next(&self, iter: &mut IterState) -> bool { + if !Self::has_next(iter) || iter.counts_index >= self.counts_len { + return false; + } + self.move_next(iter); + true + } + + fn update_iterated_values(iter: &mut IterState, new_value_iterated_to: i64) { + iter.value_iterated_from = iter.value_iterated_to; + iter.value_iterated_to = new_value_iterated_to; + } + + /// The recorded-values iterator (`hdr_iter_recorded_init()`): every index with a nonzero + /// count, in ascending order. + pub fn recorded(&self) -> impl Iterator + '_ { + let mut iter = self.iter_all(); + std::iter::from_fn(move || { + while self.basic_iter_next(&mut iter) { + if iter.count != 0 { + let value = iter.value; + Self::update_iterated_values(&mut iter, value); + return Some(RecordedBucket { + value: iter.value, + count: iter.count, + cumulative_count: iter.cumulative_count, + median_equivalent_value: iter.median_equivalent_value, + }); + } + } + None + }) + } + + /// The percentile iterator (`hdr_iter_percentile_init()`), as `(percentile, value)` pairs + /// where `value` is the lowest value of the index the iterator is on, which is what Node.js + /// reports. Like the C iterator, it can report the last index more than once. + pub fn percentiles(&self, ticks_per_half_distance: i32) -> Vec<(f64, i64)> { + let mut out = Vec::new(); + let mut iter = self.iter_all(); + let mut seen_last_value = false; + let mut percentile_to_iterate_to = 0.0_f64; + + loop { + // percentile_iter_next() + if !Self::has_next(&iter) { + if seen_last_value { + break; + } + seen_last_value = true; + out.push((100.0, iter.value)); + continue; + } + + if iter.counts_index == -1 && !self.basic_iter_next(&mut iter) { + break; + } + + let mut percentile = None; + loop { + let current_percentile = + (100.0 * iter.cumulative_count as f64) / self.total_count as f64; + if iter.count != 0 && percentile_to_iterate_to <= current_percentile { + let highest = self.highest_equivalent_value(iter.value); + Self::update_iterated_values(&mut iter, highest); + percentile = Some(percentile_to_iterate_to); + let temp = + ((100.0 / (100.0 - percentile_to_iterate_to)).ln() / 2_f64.ln()) as i64; + // `as` saturates where the C conversions are undefined; that only happens + // when percentile_to_iterate_to rounds to 100. + let temp = temp.saturating_add(1); + let half_distance = 2_f64.powf(temp as f64) as i64; + let percentile_reporting_ticks = + i64::from(ticks_per_half_distance).saturating_mul(half_distance); + percentile_to_iterate_to += 100.0 / percentile_reporting_ticks as f64; + break; + } + if !self.basic_iter_next(&mut iter) { + break; + } + } + // When the inner loop runs out, C still reports a step, with the percentile left at + // its previous value. + let reported = percentile.unwrap_or_else(|| out.last().map_or(0.0, |&(p, _)| p)); + out.push((reported, iter.value)); + } + out + } + + /// The linear iterator (`hdr_iter_linear_init()`), as `(value, count)` pairs where `value` is + /// the lowest value of the index the iterator stopped on and `count` is the number of values + /// added in the step, as Node.js reports them. + pub fn linear_buckets(&self, value_units_per_bucket: i64) -> Vec<(i64, i64)> { + let (mut next_level, mut next_level_lowest_equivalent) = if value_units_per_bucket <= 0 { + (i64::MAX, i64::MAX) + } else { + ( + value_units_per_bucket, + self.lowest_equivalent_value(value_units_per_bucket), + ) + }; + + let mut out = Vec::new(); + let mut iter = self.iter_all(); + loop { + // iter_linear_next() + let mut count_added = 0_i64; + if !(Self::has_next(&iter) + || self.next_value_greater_than_reporting_level_upper_bound( + &iter, + next_level_lowest_equivalent, + )) + { + break; + } + loop { + if iter.value >= next_level_lowest_equivalent { + Self::update_iterated_values(&mut iter, next_level); + if next_level == i64::MAX { + next_level_lowest_equivalent = i64::MAX; + } else if value_units_per_bucket <= 0 + || next_level > i64::MAX - value_units_per_bucket + { + next_level = i64::MAX; + next_level_lowest_equivalent = self.lowest_equivalent_value(i64::MAX); + } else { + next_level += value_units_per_bucket; + next_level_lowest_equivalent = self.lowest_equivalent_value(next_level); + } + break; + } + if !self.move_next(&mut iter) { + break; + } + count_added += iter.count; + } + out.push((iter.value, count_added)); + } + out + } + + /// The logarithmic iterator (`hdr_iter_log_init()`), reported as `linear_buckets()` is. + pub fn log_buckets(&self, value_units_first_bucket: i64, log_base: f64) -> Vec<(i64, i64)> { + let (mut next_level, mut next_level_lowest_equivalent) = if value_units_first_bucket <= 0 + || !log_base.is_finite() + || log_base <= 1.0 + || log_base >= i64::MAX as f64 + { + (i64::MAX, i64::MAX) + } else { + ( + value_units_first_bucket, + self.lowest_equivalent_value(value_units_first_bucket), + ) + }; + + let mut out = Vec::new(); + let mut iter = self.iter_all(); + loop { + // log_iter_next() + let mut count_added = 0_i64; + if !(Self::has_next(&iter) + || self.next_value_greater_than_reporting_level_upper_bound( + &iter, + next_level_lowest_equivalent, + )) + { + break; + } + loop { + if iter.value >= next_level_lowest_equivalent { + Self::update_iterated_values(&mut iter, next_level); + // A finite base below i64::MAX, checked above, so the truncation is defined. + let base = log_base as i64; + if next_level == i64::MAX { + next_level_lowest_equivalent = i64::MAX; + } else if base <= 1 || next_level <= 0 || next_level > i64::MAX / base { + next_level = i64::MAX; + next_level_lowest_equivalent = self.lowest_equivalent_value(i64::MAX); + } else { + next_level *= base; + next_level_lowest_equivalent = self.lowest_equivalent_value(next_level); + } + break; + } + if !self.move_next(&mut iter) { + break; + } + count_added += iter.count; + } + out.push((iter.value, count_added)); + } + out + } +} + +#[cfg(test)] +#[path = "hdr-test.rs"] +mod tests; diff --git a/src/rust/histogram/histogram-test.rs b/src/rust/histogram/histogram-test.rs new file mode 100644 index 00000000000..afaaed295ab --- /dev/null +++ b/src/rust/histogram/histogram-test.rs @@ -0,0 +1,172 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +#![expect( + clippy::float_cmp, + reason = "the expected values are exactly what these operations compute" +)] + +use super::*; + +fn histogram_of(values: &[i64]) -> Histogram { + let mut h = Histogram::new(&Options::default()).unwrap(); + for &value in values { + assert!(h.record(value)); + } + h +} + +#[test] +fn default_options_match_node() { + let h = Histogram::new(&Options::default()).unwrap(); + assert_eq!(h.lowest(), 1); + assert_eq!(h.highest(), i64::MAX); + assert_eq!(h.figures(), 3); + assert_eq!(h.count(), 0); + assert_eq!(h.min(), i64::MAX); + assert_eq!(h.max(), 0); + assert!(h.mean().is_nan()); + assert!(h.stddev().is_nan()); + assert_eq!(h.cdf(10), 0.0); +} + +#[test] +fn out_of_range_values_count_as_exceeds() { + let mut h = Histogram::new(&Options { + highest: 1000, + ..Options::default() + }) + .unwrap(); + assert!(h.record(1000)); + assert!(!h.record(1001)); + assert!(!h.record_corrected(5000, 10)); + assert_eq!(h.count(), 1); + assert_eq!(h.exceeds(), 2); +} + +#[test] +fn percentile_rejects_out_of_range_arguments() { + let h = histogram_of(&[1, 2, 3]); + assert!(h.percentile(0.0).is_err()); + assert!(h.percentile(100.5).is_err()); + assert!(h.percentile(f64::NAN).is_err()); + assert_eq!(h.percentile(100.0), Ok(3)); +} + +#[test] +fn cdf_and_count_at() { + let h = histogram_of(&[1, 2, 2, 3]); + assert_eq!(h.cdf(0), 0.0); + assert_eq!(h.cdf(1), 0.25); + assert_eq!(h.cdf(2), 0.75); + assert_eq!(h.cdf(1000), 1.0); + assert_eq!(h.count_at(2), 2); + assert_eq!(h.count_at(-5), 0); +} + +#[test] +fn ewma_follows_half_life() { + let mut h = Histogram::new(&Options { + half_life: 1.0, + threshold: 15, + ..Options::default() + }) + .unwrap(); + assert_eq!(h.ewma_mean(), 0.0); + // With a half-life of 1 sample, alpha is 1/2. + h.record(10); + assert_eq!(h.ewma_mean(), 10.0); + assert_eq!(h.ewma_error_rate(), 0.0); + h.record(20); + assert_eq!(h.ewma_mean(), 15.0); + assert_eq!(h.ewma_stddev(), 25.0_f64.sqrt()); + assert_eq!(h.ewma_error_rate(), 0.5); + + h.reset(); + assert_eq!(h.ewma_mean(), 0.0); + assert_eq!(h.reset_count(), 1); +} + +#[test] +fn snapshot_is_independent() { + let mut h = histogram_of(&[1, 2, 3]); + let snapshot = h.snapshot().unwrap(); + h.record(4); + h.reset(); + assert_eq!(snapshot.count(), 3); + assert_eq!(snapshot.max(), 3); + assert_eq!(snapshot.reset_count(), 0); +} + +#[test] +fn diff_returns_values_recorded_after_snapshot() { + let mut h = histogram_of(&[1, 2, 3]); + let earlier = h.snapshot().unwrap(); + h.record(10); + h.record(20); + h.record(1_i64 << 62); + let diff = h.diff(&earlier).unwrap(); + assert_eq!(diff.count(), 3); + assert_eq!(diff.min(), 10); + assert_eq!(diff.reset_count(), 0); + assert_eq!(diff.exceeds(), 0); + + // Wrong order. + assert_eq!(earlier.diff(&h).unwrap_err(), Error::NotEarlier); + + // Reset in between. + let mut reset = h.snapshot().unwrap(); + reset.reset(); + assert_eq!(reset.diff(&earlier).unwrap_err(), Error::Reset); + + // Different layout. + let other = Histogram::new(&Options { + figures: 2, + ..Options::default() + }) + .unwrap(); + assert_eq!(h.diff(&other).unwrap_err(), Error::Incompatible); +} + +#[test] +fn subtract_clamps_at_zero() { + let mut h = histogram_of(&[1, 1, 5]); + let other = histogram_of(&[1, 5, 5, 9]); + assert_eq!(h.subtract(&other), 2); + assert_eq!(h.count(), 1); + assert_eq!(h.min(), 1); + assert_eq!(h.max(), 1); + assert_eq!(h.reset_count(), 1); +} + +#[test] +fn add_merges_counts_and_exceeds() { + let mut h = histogram_of(&[1]); + let mut other = Histogram::new(&Options { + highest: 1000, + ..Options::default() + }) + .unwrap(); + other.record(7); + other.record(5000); + assert_eq!(h.add(&other), 0); + assert_eq!(h.count(), 2); + assert_eq!(h.exceeds(), 1); +} + +#[test] +fn record_delta_records_time_between_calls() { + let mut h = histogram_of(&[]); + assert_eq!(h.record_delta(1000), 0); + assert_eq!(h.count(), 0); + assert_eq!(h.record_delta(1250), 250); + assert_eq!(h.count(), 1); + assert_eq!(h.max(), 250); +} + +#[test] +fn memory_size_includes_counts() { + let h = histogram_of(&[]); + assert!(h.memory_size() > 45056 * 8); +} diff --git a/src/rust/histogram/histogram.rs b/src/rust/histogram/histogram.rs new file mode 100644 index 00000000000..e6e1f15405d --- /dev/null +++ b/src/rust/histogram/histogram.rs @@ -0,0 +1,398 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! The equivalent of Node.js's native `node::Histogram` class (`src/histogram.{h,cc}`): an HDR +//! histogram plus the state Node.js keeps next to it (the `exceeds` count, the reset count, the +//! `recordDelta()` timestamp, and the EWMA). + +#![expect( + clippy::cast_precision_loss, + reason = "values are converted to f64 exactly where Node.js converts them to double" +)] +#![expect( + clippy::suboptimal_flops, + reason = "fused operations would round differently from Node.js" +)] + +use crate::Error; +use crate::hdr::Hdr; +use crate::hdr::RecordedBucket; + +/// Options for a new histogram: `node::Histogram::Options`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Options { + /// The lowest discernible value, at least 1. + pub lowest: i64, + /// The highest trackable value, at least twice `lowest`. + pub highest: i64, + /// Significant figures of precision, 1 to 5. + pub figures: i32, + /// The EWMA half-life in samples. 0 disables the EWMA. + pub half_life: f64, + /// Values above this count as errors in the EWMA error rate. 0 disables the error rate. + pub threshold: i64, +} + +impl Default for Options { + fn default() -> Self { + Self { + lowest: 1, + highest: i64::MAX, + figures: 3, + half_life: 0.0, + threshold: 0, + } + } +} + +/// A recordable HDR histogram with the semantics of Node.js's `Histogram`. +/// +/// Methods that compare two histograms walk the counts arrays index by index, as Node.js does, so +/// their results are only meaningful for histograms with the same layout (`is_compatible()`). +#[derive(Debug)] +pub struct Histogram { + pub(crate) hdr: Hdr, + pub(crate) prev: u64, + pub(crate) exceeds: u64, + pub(crate) reset_count: u64, + pub(crate) ewma_alpha: f64, + pub(crate) ewma_mean: f64, + pub(crate) ewma_variance: f64, + pub(crate) ewma_error_rate: f64, + pub(crate) ewma_initialized: bool, + pub(crate) threshold: i64, +} + +impl Histogram { + /// `Histogram::Create()`. + pub fn new(options: &Options) -> Result { + let hdr = Hdr::new(options.lowest, options.highest, options.figures)?; + Ok(Self::from_hdr(hdr, options)) + } + + fn from_hdr(hdr: Hdr, options: &Options) -> Self { + // alpha = 1 - 2^(-1/halfLife). With halfLife <= 0, the EWMA is disabled. + let ewma_alpha = if options.half_life > 0.0 { + 1.0 - (-(2.0_f64.ln()) / options.half_life).exp() + } else { + 0.0 + }; + Self { + hdr, + prev: 0, + exceeds: 0, + reset_count: 0, + ewma_alpha, + ewma_mean: 0.0, + ewma_variance: 0.0, + ewma_error_rate: 0.0, + ewma_initialized: false, + threshold: options.threshold, + } + } + + /// `Histogram::CreateWithSameLayout()`: an empty histogram with the same layout and default + /// values for everything else. + fn with_same_layout(&self) -> Result { + Ok(Self::from_hdr( + self.hdr.with_same_layout()?, + &Options::default(), + )) + } + + /// `Histogram::Clone()`, which backs `histogram.snapshot()`: an independent copy of the + /// configuration, recorded values, and all statistical state. + pub fn snapshot(&self) -> Result { + Ok(Self { + hdr: self.hdr.try_clone()?, + ..*self + }) + } + + /// `Histogram::IsCompatible()`: whether both histograms map values to the same indexes. + pub fn is_compatible(&self, other: &Self) -> bool { + self.hdr.counts_len() == other.hdr.counts_len() + && self.hdr.lowest_discernible_value() == other.hdr.lowest_discernible_value() + && self.hdr.highest_trackable_value() == other.hdr.highest_trackable_value() + && self.hdr.significant_figures() == other.hdr.significant_figures() + } + + /// `Histogram::Diff()`: the values recorded in this histogram after `other`, an earlier + /// snapshot of it, was taken. The result has no EWMA state and a reset count of 0. + pub fn diff(&self, other: &Self) -> Result { + if !self.is_compatible(other) { + return Err(Error::Incompatible); + } + let mut diff = self.with_same_layout()?; + diff.hdr.counts_mut().copy_from_slice(self.hdr.counts()); + diff.exceeds = self.exceeds; + + if self.reset_count != other.reset_count { + return Err(Error::Reset); + } + if diff.exceeds < other.exceeds { + return Err(Error::NotEarlier); + } + for (target, &source) in diff.hdr.counts_mut().iter_mut().zip(other.hdr.counts()) { + if *target < source { + return Err(Error::NotEarlier); + } + *target -= source; + } + diff.exceeds -= other.exceeds; + diff.hdr.reset_internal_counters(); + Ok(diff) + } + + fn update_ewma(&mut self, value: f64) { + if self.ewma_alpha <= 0.0 { + return; + } + if !self.ewma_initialized { + self.ewma_mean = value; + self.ewma_variance = 0.0; + self.ewma_initialized = true; + if self.threshold > 0 { + self.ewma_error_rate = if value > self.threshold as f64 { + 1.0 + } else { + 0.0 + }; + } + return; + } + let diff = value - self.ewma_mean; + self.ewma_mean += self.ewma_alpha * diff; + self.ewma_variance = + (1.0 - self.ewma_alpha) * (self.ewma_variance + self.ewma_alpha * diff * diff); + + // Binary EWMA for the SLO error rate: 1 if over the threshold, 0 otherwise. + if self.threshold > 0 { + let exceeded = if value > self.threshold as f64 { + 1.0 + } else { + 0.0 + }; + self.ewma_error_rate += self.ewma_alpha * (exceeded - self.ewma_error_rate); + } + } + + /// Records a value. Returns false, and counts it in `exceeds()`, if it is out of range. + pub fn record(&mut self, value: i64) -> bool { + let recorded = self.hdr.record_value(value); + if recorded { + self.update_ewma(value as f64); + } else { + self.exceeds += 1; + } + recorded + } + + /// Records a value with coordinated omission correction: when `value` exceeds + /// `expected_interval`, the values that a stall prevented from being recorded are backfilled + /// at `expected_interval` steps. The EWMA sees only `value`. + pub fn record_corrected(&mut self, value: i64, expected_interval: i64) -> bool { + let recorded = self.hdr.record_corrected_value(value, expected_interval); + if recorded { + self.update_ewma(value as f64); + } else { + self.exceeds += 1; + } + recorded + } + + /// `recordDelta()`, with the current time passed in: records the time since the previous call + /// and returns it. The first call records nothing and returns 0. + pub fn record_delta(&mut self, now: u64) -> u64 { + let mut delta = 0; + if self.prev > 0 { + delta = now.saturating_sub(self.prev); + match i64::try_from(delta) { + Ok(value) if self.hdr.record_value(value) => self.update_ewma(value as f64), + _ => self.exceeds += 1, + } + } + self.prev = now; + delta + } + + /// Adds the values of `other` to this histogram. Returns the number of values that did not + /// fit; they are not counted in `exceeds()`. + pub fn add(&mut self, other: &Self) -> u64 { + self.exceeds += other.exceeds; + if other.prev > self.prev { + self.prev = other.prev; + } + self.hdr.add(&other.hdr).cast_unsigned() + } + + /// Subtracts the counts of `other` index by index, clamping at zero. Returns the number of + /// values that would have gone below zero, and increments the reset count. + pub fn subtract(&mut self, other: &Self) -> u64 { + let mut dropped = 0_i64; + for (target, &source) in self.hdr.counts_mut().iter_mut().zip(other.hdr.counts()) { + let mut count = *target - source; + if count < 0 { + dropped += -count; + count = 0; + } + *target = count; + } + self.hdr.reset_internal_counters(); + self.reset_count += 1; + self.exceeds = self.exceeds.saturating_sub(other.exceeds); + dropped.cast_unsigned() + } + + /// Clears the recorded values and statistical state, and increments the reset count. + pub fn reset(&mut self) { + self.hdr.reset(); + self.reset_count += 1; + self.exceeds = 0; + self.prev = 0; + self.ewma_mean = 0.0; + self.ewma_variance = 0.0; + self.ewma_error_rate = 0.0; + self.ewma_initialized = false; + } + + pub fn lowest(&self) -> i64 { + self.hdr.lowest_discernible_value() + } + + pub fn highest(&self) -> i64 { + self.hdr.highest_trackable_value() + } + + pub fn figures(&self) -> i32 { + self.hdr.significant_figures() + } + + pub fn count(&self) -> u64 { + self.hdr.total_count().cast_unsigned() + } + + pub fn exceeds(&self) -> u64 { + self.exceeds + } + + /// The number of calls to `reset()` and `subtract()`. A snapshot carries the reset count of + /// its source, so comparing two snapshots shows whether the source was reset in between. + pub fn reset_count(&self) -> u64 { + self.reset_count + } + + /// The smallest value recorded, rounded down to its bucket. `i64::MAX` when empty. + pub fn min(&self) -> i64 { + self.hdr.min() + } + + /// The largest value recorded, rounded up to its bucket. 0 when empty. + pub fn max(&self) -> i64 { + self.hdr.max() + } + + /// The mean of the bucket midpoints. NaN when empty. + pub fn mean(&self) -> f64 { + self.hdr.mean() + } + + /// The population standard deviation of the bucket midpoints. NaN when empty. + pub fn stddev(&self) -> f64 { + self.hdr.stddev() + } + + /// The value at a percentile in (0, 100]. + pub fn percentile(&self, percentile: f64) -> Result { + if !(percentile > 0.0 && percentile <= 100.0) { + return Err(Error::InvalidArgument("percentile must be in (0, 100]")); + } + Ok(self.hdr.value_at_percentile(percentile)) + } + + /// The values at several ascending percentiles, in one pass. + pub fn percentiles_at(&self, percentiles: &[f64]) -> Vec { + self.hdr.value_at_percentiles(percentiles) + } + + /// The percentile distribution as Node.js's `histogram.percentiles` reports it, as + /// `(percentile, value)` pairs in iteration order. A key can repeat; a `Map` built from the + /// pairs keeps the last value for each key. + pub fn percentiles(&self) -> Vec<(f64, i64)> { + self.hdr.percentiles(1) + } + + /// The number of values recorded in the bucket that `value` falls into. + pub fn count_at(&self, value: i64) -> i64 { + self.hdr.count_at_value(value) + } + + /// The fraction of values up to and including the first bucket whose highest equivalent value + /// is at least `value`. 0 when empty. + pub fn cdf(&self, value: i64) -> f64 { + let total = self.hdr.total_count(); + if total == 0 { + return 0.0; + } + let mut iter = self.hdr.iter_all(); + while self.hdr.move_next(&mut iter) { + if iter.highest_equivalent_value >= value { + return iter.cumulative_count as f64 / total as f64; + } + // All recorded data is accounted for; the remaining buckets are empty. + if iter.cumulative_count >= total { + break; + } + } + 1.0 + } + + /// The counts rebucketed into steps of `step_size`, as `(value, count)` pairs. + pub fn linear_buckets(&self, step_size: i64) -> Vec<(i64, i64)> { + self.hdr.linear_buckets(step_size) + } + + /// The counts rebucketed into buckets that grow by `base`, as `(value, count)` pairs. + pub fn log_buckets(&self, first_bucket: i64, base: f64) -> Vec<(i64, i64)> { + self.hdr.log_buckets(first_bucket, base) + } + + /// The buckets with a nonzero count, in ascending order. + pub fn recorded(&self) -> impl Iterator + '_ { + self.hdr.recorded() + } + + pub fn ewma_mean(&self) -> f64 { + if self.ewma_initialized { + self.ewma_mean + } else { + 0.0 + } + } + + pub fn ewma_stddev(&self) -> f64 { + if self.ewma_initialized { + self.ewma_variance.sqrt() + } else { + 0.0 + } + } + + pub fn ewma_error_rate(&self) -> f64 { + if self.ewma_initialized { + self.ewma_error_rate + } else { + 0.0 + } + } + + /// The memory used by the counts array and this struct. + pub fn memory_size(&self) -> usize { + size_of::() - size_of::() + self.hdr.memory_size() + } +} + +#[cfg(test)] +#[path = "histogram-test.rs"] +mod tests; diff --git a/src/rust/histogram/lib.rs b/src/rust/histogram/lib.rs new file mode 100644 index 00000000000..9bf1ee92324 --- /dev/null +++ b/src/rust/histogram/lib.rs @@ -0,0 +1,41 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! HDR histograms with the behavior of Node.js's `perf_hooks` histograms. +//! +//! [`Histogram`] matches Node.js's native histogram (`src/histogram.{h,cc}` in Node.js): the same +//! bucket layout, the same results for every query including edge cases, and the same rounding. +//! It backs the statistics of the `workerd bench` harness and `node:perf_hooks`' +//! `createHistogram()`. +//! +//! [`hdr`] is a port of the core of `HdrHistogram_c`, the C library Node.js builds on. + +pub mod hdr; +mod histogram; + +pub use histogram::Histogram; +pub use histogram::Options; + +/// Errors from creating, combining, or querying histograms. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum Error { + /// The `lowest`, `highest`, and `figures` options do not describe a valid layout. + #[error("Invalid histogram options")] + InvalidOptions, + /// The counts array could not be allocated. + #[error("Out of memory allocating histogram")] + OutOfMemory, + /// An argument is out of range. + #[error("{0}")] + InvalidArgument(&'static str), + /// Two histograms that must have the same layout do not. + #[error("other must have the same configuration as the histogram")] + Incompatible, + /// Values were removed from a histogram after an earlier snapshot of it was taken. + #[error("Values were removed from the histogram after other was taken")] + Reset, + /// A supposedly earlier snapshot holds values that the histogram does not. + #[error("other contains values that are not in the histogram")] + NotEarlier, +} From b6b856b5191d581613c19ce1517620bed243301d Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 17:21:28 +0000 Subject: [PATCH 03/24] Add statistical analysis to the histogram crate Ports the analysis functions of Node.js's src/histogram.cc: skewness and excess kurtosis, a Student's t confidence interval for the mean, an exact binomial confidence interval for percentiles, Welch's t-test, the Mann-Whitney U test with tie correction, Cohen's d, Cliff's delta, and the two-sample Kolmogorov-Smirnov statistic, with the numerical helpers they rely on (the regularized incomplete beta function and the normal, Student's t, and binomial distributions). The port keeps Node.js's operation order, so results match to the last bit except where libm's lgamma() and erfc() differ from the platform libm by an ulp. The two-sample functions walk the counts arrays by index, as Node.js does, without checking that the layouts match. QRDE and the asymptotic incomplete beta approximations it uses are not ported yet. --- src/rust/histogram/BUILD.bazel | 1 + src/rust/histogram/lib.rs | 5 + src/rust/histogram/stats-test.rs | 133 ++++++++ src/rust/histogram/stats.rs | 548 +++++++++++++++++++++++++++++++ 4 files changed, 687 insertions(+) create mode 100644 src/rust/histogram/stats-test.rs create mode 100644 src/rust/histogram/stats.rs diff --git a/src/rust/histogram/BUILD.bazel b/src/rust/histogram/BUILD.bazel index 78d531c5bc9..4a439a69c91 100644 --- a/src/rust/histogram/BUILD.bazel +++ b/src/rust/histogram/BUILD.bazel @@ -4,6 +4,7 @@ wd_rust_crate( name = "histogram", visibility = ["//visibility:public"], deps = [ + "@crates_vendor//:libm", "@crates_vendor//:thiserror", ], ) diff --git a/src/rust/histogram/lib.rs b/src/rust/histogram/lib.rs index 9bf1ee92324..53c0bd90ee0 100644 --- a/src/rust/histogram/lib.rs +++ b/src/rust/histogram/lib.rs @@ -13,9 +13,14 @@ pub mod hdr; mod histogram; +mod stats; pub use histogram::Histogram; pub use histogram::Options; +pub use stats::MannWhitneyTest; +pub use stats::MeanCi; +pub use stats::PercentileCi; +pub use stats::WelchTest; /// Errors from creating, combining, or querying histograms. #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] diff --git a/src/rust/histogram/stats-test.rs b/src/rust/histogram/stats-test.rs new file mode 100644 index 00000000000..59c702fd51f --- /dev/null +++ b/src/rust/histogram/stats-test.rs @@ -0,0 +1,133 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +#![expect( + clippy::float_cmp, + reason = "exact cases, such as zero skewness, are compared exactly; others use assert_close()" +)] + +use super::*; +use crate::Options; + +fn histogram_of(values: &[i64]) -> Histogram { + let mut h = Histogram::new(&Options::default()).unwrap(); + for &value in values { + assert!(h.record(value)); + } + h +} + +#[track_caller] +fn assert_close(actual: f64, expected: f64) { + let tolerance = 1e-12 * expected.abs().max(1.0); + assert!( + (actual - expected).abs() <= tolerance, + "{actual} != {expected}" + ); +} + +// The expected values in the tests below are from Node.js 26.10. +fn a() -> Histogram { + histogram_of(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +} + +fn b() -> Histogram { + histogram_of(&[3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 100]) +} + +#[test] +fn numerical_helpers() { + assert_close(normal_cdf(0.0), 0.5); + assert!((normal_cdf(1.96) - 0.975).abs() < 1e-4); + // t(0.975, 10) = 2.228138851986... + assert!((student_t_upper_quantile(0.025, 10.0) - 2.228_138_851_986).abs() < 1e-9); + assert_close(binomial_cdf(-1, 10, 0.5), 0.0); + assert_close(binomial_cdf(10, 10, 0.5), 1.0); + // P(X <= 5) for X ~ Binomial(10, 1/2) is 638/1024. + assert_close(binomial_cdf(5, 10, 0.5), 638.0 / 1024.0); +} + +#[test] +fn shape() { + assert_close(b().skewness(), 2.615_536_842_180_938); + assert_close(b().kurtosis(), 5.332_898_195_589_218); + assert_eq!(histogram_of(&[1, 2]).skewness(), 0.0); + assert_eq!(histogram_of(&[1, 2, 3]).kurtosis(), 0.0); + assert_eq!(histogram_of(&[7, 7, 7, 7]).kurtosis(), 0.0); +} + +#[test] +fn mean_ci() { + let ci = a().mean_ci(0.95); + assert_close(ci.mean, 5.5); + assert_close(ci.lower, 3.334_149_410_331_883); + assert_close(ci.upper, 7.665_850_589_668_117); + + let one = histogram_of(&[3]).mean_ci(0.95); + assert_eq!(one.mean, 3.0); + assert!(one.lower.is_nan() && one.upper.is_nan()); + + let flat = histogram_of(&[3, 3, 3]).mean_ci(0.95); + assert_eq!((flat.lower, flat.upper), (3.0, 3.0)); +} + +#[test] +fn percentile_ci() { + assert_eq!( + a().percentile_ci(50.0, 0.95), + PercentileCi { + value: 5, + lower: 2, + upper: 9 + } + ); + assert_eq!( + histogram_of(&[4]).percentile_ci(50.0, 0.95), + PercentileCi { + value: 4, + lower: 4, + upper: 4 + } + ); +} + +#[test] +fn welch_test() { + let w = a().welch_test(&b(), 0.95); + assert_close(w.t_statistic, -1.759_461_713_556_845_8); + assert_close(w.degrees_of_freedom, 10.273_367_000_323_427); + assert_close(w.p_value, 0.108_192_935_213_515_03); + assert_close(w.ci_lower, -32.796_426_246_981_98); + assert_close(w.ci_upper, 3.796_426_246_981_983_6); + + let h = a(); + assert_eq!(h.welch_test(&h, 0.95).p_value, 1.0); + assert_eq!(h.welch_test(&histogram_of(&[1]), 0.95).t_statistic, 0.0); +} + +#[test] +fn mann_whitney_test() { + let mw = a().mann_whitney_test(&b()); + assert_close(mw.u_statistic, 18.0); + assert_close(mw.z_score, -2.608_851_846_032_169); + assert_close(mw.p_value, 0.009_084_656_491_886_27); + + let empty = histogram_of(&[]); + assert_eq!(a().mann_whitney_test(&empty).p_value, 1.0); +} + +#[test] +fn effect_sizes() { + assert_close(a().cohens_d(&b()), -0.732_139_452_880_120_9); + assert_close(a().cliffs_d(&b()), -0.672_727_272_727_272_7); + assert_close(a().ks_test(&b()), 0.636_363_636_363_636_4); + + let h = a(); + assert_eq!(h.cohens_d(&h), 0.0); + assert_eq!(h.cliffs_d(&h), 0.0); + assert_eq!(h.ks_test(&h), 0.0); + // An identical but distinct histogram has no difference either. + assert_eq!(h.ks_test(&a()), 0.0); + assert_eq!(h.cliffs_d(&a()), 0.0); +} diff --git a/src/rust/histogram/stats.rs b/src/rust/histogram/stats.rs new file mode 100644 index 00000000000..ef1bb32f5b3 --- /dev/null +++ b/src/rust/histogram/stats.rs @@ -0,0 +1,548 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! Statistical analysis of histograms, ported from Node.js's `src/histogram.cc`: distribution +//! shape, confidence intervals, hypothesis tests, and effect sizes. +//! +//! All of them work on the bucketed data, so they reflect the histogram's precision: values are +//! represented by their bucket midpoints, and values in the same bucket are ties. + +#![expect( + clippy::cast_precision_loss, + reason = "counts and values are converted to f64 exactly where Node.js converts them to double" +)] +#![expect( + clippy::suboptimal_flops, + reason = "fused operations would round differently from Node.js" +)] + +use std::f64::consts::SQRT_2; + +use crate::Histogram; + +/// A confidence interval for the mean. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct MeanCi { + pub mean: f64, + pub lower: f64, + pub upper: f64, +} + +/// The result of Welch's t-test. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct WelchTest { + pub t_statistic: f64, + pub degrees_of_freedom: f64, + /// Two-tailed. + pub p_value: f64, + /// The confidence interval on the difference of the means. + pub ci_lower: f64, + pub ci_upper: f64, +} + +/// The result of the Mann-Whitney U test. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct MannWhitneyTest { + pub u_statistic: f64, + pub z_score: f64, + /// Two-tailed, from the normal approximation with tie correction. + pub p_value: f64, +} + +/// A confidence interval for a percentile. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PercentileCi { + pub value: i64, + pub lower: i64, + pub upper: i64, +} + +// Numerical helpers. + +/// The continued fraction for the regularized incomplete beta function, by Lentz's modified +/// method. Reference: Numerical Recipes in C, 2nd edition, section 6.4. +#[expect( + clippy::many_single_char_names, + reason = "the names follow Numerical Recipes and Node.js" +)] +fn beta_continued_fraction(a: f64, b: f64, x: f64) -> f64 { + const FPMIN: f64 = 1e-30; + const MAXIT: i32 = 200; + const EPS: f64 = 3e-12; + + let qab = a + b; + let qap = a + 1.0; + let qam = a - 1.0; + let mut c = 1.0; + let mut d = 1.0 - qab * x / qap; + if d.abs() < FPMIN { + d = FPMIN; + } + d = 1.0 / d; + let mut h = d; + + for m in 1..=MAXIT { + let m = f64::from(m); + let m2 = 2.0 * m; + // Even step. + let mut aa = m * (b - m) * x / ((qam + m2) * (a + m2)); + d = 1.0 + aa * d; + if d.abs() < FPMIN { + d = FPMIN; + } + c = 1.0 + aa / c; + if c.abs() < FPMIN { + c = FPMIN; + } + d = 1.0 / d; + h *= d * c; + // Odd step. + aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2)); + d = 1.0 + aa * d; + if d.abs() < FPMIN { + d = FPMIN; + } + c = 1.0 + aa / c; + if c.abs() < FPMIN { + c = FPMIN; + } + d = 1.0 / d; + let del = d * c; + h *= del; + if (del - 1.0).abs() <= EPS { + break; + } + } + h +} + +/// The regularized incomplete beta function `I_x(a, b)`: the probability that a Beta(a, b) +/// random variable is at most `x`. +/// +/// Node.js's QRDE adds asymptotic approximations for very concentrated shapes; the functions +/// here never request them, so only the exact path is ported. +fn regularized_incomplete_beta(a: f64, b: f64, x: f64) -> f64 { + if x <= 0.0 { + return 0.0; + } + if x >= 1.0 { + return 1.0; + } + let sum = a + b; + let log_normalization = libm::lgamma(sum) - libm::lgamma(a) - libm::lgamma(b); + let front = (log_normalization + a * x.ln() + b * (1.0 - x).ln()).exp(); + let symmetry_point = (a + 1.0) / (sum + 2.0); + + // The symmetry relation keeps the continued fraction in the region where it converges best. + if x < symmetry_point { + return front * beta_continued_fraction(a, b, x) / a; + } + 1.0 - front * beta_continued_fraction(b, a, 1.0 - x) / b +} + +/// The standard normal CDF: P(Z <= x). +fn normal_cdf(x: f64) -> f64 { + 0.5 * libm::erfc(-x * SQRT_2 / 2.0) +} + +/// The CDF of Student's t-distribution with `df` degrees of freedom: P(T <= t). +fn student_t_cdf(t: f64, df: f64) -> f64 { + let squared = t * t; + let denominator = df + squared; + if squared < df { + let x = squared / denominator; + let ibeta = regularized_incomplete_beta(0.5, df / 2.0, x); + return if t >= 0.0 { + 0.5 + 0.5 * ibeta + } else { + 0.5 - 0.5 * ibeta + }; + } + let x = df / denominator; + let ibeta = regularized_incomplete_beta(df / 2.0, 0.5, x); + if t >= 0.0 { + 1.0 - 0.5 * ibeta + } else { + 0.5 * ibeta + } +} + +/// The positive t quantile with upper-tail probability `p`, by bisection on the lower tail, which +/// avoids losing precision for probabilities near 1. +#[expect( + clippy::while_float, + reason = "the bound doubles until it brackets the quantile, which takes few steps" +)] +#[expect( + clippy::manual_midpoint, + reason = "f64::midpoint rounds differently from Node.js's (lo + hi) / 2 for some inputs" +)] +fn student_t_upper_quantile(p: f64, df: f64) -> f64 { + let mut lo = 0.0; + let mut hi = 1.0; + while student_t_cdf(-hi, df) > p { + hi *= 2.0; + } + for _ in 0..100 { + let mid = (lo + hi) / 2.0; + if student_t_cdf(-mid, df) > p { + lo = mid; + } else { + hi = mid; + } + } + (lo + hi) / 2.0 +} + +/// The binomial CDF P(X <= k) for X ~ Binomial(n, p), from P(X <= k) = I_{1-p}(n - k, k + 1). +fn binomial_cdf(k: i64, n: i64, p: f64) -> f64 { + if k < 0 { + return 0.0; + } + if k >= n { + return 1.0; + } + regularized_incomplete_beta((n - k) as f64, (k + 1) as f64, 1.0 - p) +} + +/// The counts of two histograms paired by index, over the longer of the two counts arrays. +fn paired_counts<'a>(a: &'a Histogram, b: &'a Histogram) -> impl Iterator + 'a { + let a = a.hdr.counts(); + let b = b.hdr.counts(); + (0..a.len().max(b.len())).map(move |i| { + ( + a.get(i).copied().unwrap_or(0), + b.get(i).copied().unwrap_or(0), + ) + }) +} + +/// The pairs of values from `a` and `b` where `a`'s is greater, and where they are tied, from one +/// walk over the paired counts. +fn concordant_and_tied(a: &Histogram, b: &Histogram) -> (f64, f64) { + let mut cum2 = 0_i64; + let mut concordant = 0.0; + let mut tied = 0.0; + for (c1, c2) in paired_counts(a, b) { + concordant += c1 as f64 * cum2 as f64; + tied += c1 as f64 * c2 as f64; + cum2 += c2; + } + (concordant, tied) +} + +/// The population variance converted to the sample variance (Bessel's correction). +fn sample_variance(stddev: f64, n: i64) -> f64 { + stddev * stddev * n as f64 / (n - 1) as f64 +} + +impl Histogram { + /// The skewness of the bucket midpoints: positive for a longer right tail. 0 with fewer than 3 + /// values or no variance. + pub fn skewness(&self) -> f64 { + let total = self.hdr.total_count(); + if total < 3 { + return 0.0; + } + let mean = self.hdr.mean(); + let mut m2 = 0.0; + let mut m3 = 0.0; + for bucket in self.hdr.recorded() { + let dev = self.hdr.median_equivalent_value(bucket.value) as f64 - mean; + let d2 = dev * dev; + m2 += bucket.count as f64 * d2; + m3 += bucket.count as f64 * d2 * dev; + } + let n = total as f64; + let variance = m2 / n; + if variance == 0.0 { + return 0.0; + } + let s3 = variance * variance.sqrt(); + (m3 / n) / s3 + } + + /// The excess kurtosis of the bucket midpoints: positive for heavier tails than a normal + /// distribution. 0 with fewer than 4 values or no variance. + pub fn kurtosis(&self) -> f64 { + let total = self.hdr.total_count(); + if total < 4 { + return 0.0; + } + let mean = self.hdr.mean(); + let mut m2 = 0.0; + let mut m4 = 0.0; + for bucket in self.hdr.recorded() { + let dev = self.hdr.median_equivalent_value(bucket.value) as f64 - mean; + let d2 = dev * dev; + m2 += bucket.count as f64 * d2; + m4 += bucket.count as f64 * d2 * d2; + } + let n = total as f64; + let variance = m2 / n; + if variance == 0.0 { + return 0.0; + } + let s4 = variance * variance; + (m4 / n) / s4 - 3.0 + } + + /// A two-sided confidence interval for the mean from Student's t-distribution. The bounds + /// are NaN with fewer than 2 values, and equal to the mean when all values are equal. + pub fn mean_ci(&self, confidence: f64) -> MeanCi { + let count = self.hdr.total_count(); + let mean = self.hdr.mean(); + if count < 2 { + return MeanCi { + mean, + lower: f64::NAN, + upper: f64::NAN, + }; + } + let stddev = self.hdr.stddev(); + if stddev == 0.0 { + return MeanCi { + mean, + lower: mean, + upper: mean, + }; + } + let variance = sample_variance(stddev, count); + let standard_error = (variance / count as f64).sqrt(); + let alpha = 1.0 - confidence; + let t_crit = student_t_upper_quantile(alpha / 2.0, (count - 1) as f64); + let margin = t_crit * standard_error; + MeanCi { + mean, + lower: mean - margin, + upper: mean + margin, + } + } + + /// A distribution-free confidence interval for a percentile, from the exact binomial + /// distribution of order statistics. With fewer than 2 values, the bounds equal the value. + pub fn percentile_ci(&self, percentile: f64, confidence: f64) -> PercentileCi { + let value = self.hdr.value_at_percentile(percentile); + let n = self.hdr.total_count(); + if n < 2 { + return PercentileCi { + value, + lower: value, + upper: value, + }; + } + + let p = percentile / 100.0; + let alpha = 1.0 - confidence; + + // Lower rank: the largest j with BinomialCdf(j - 1, n, p) <= alpha / 2. + let mut lo = 0_i64; + let mut hi = n; + while lo < hi { + let mid = lo + (hi - lo + 1) / 2; + if binomial_cdf(mid - 1, n, p) <= alpha / 2.0 { + lo = mid; + } else { + hi = mid - 1; + } + } + let lower_pct = lo as f64 / n as f64 * 100.0; + + // Upper rank: the smallest k with BinomialCdf(k - 1, n, p) >= 1 - alpha / 2. + lo = 0; + hi = n; + while lo < hi { + let mid = lo + (hi - lo) / 2; + if binomial_cdf(mid - 1, n, p) >= 1.0 - alpha / 2.0 { + hi = mid; + } else { + lo = mid + 1; + } + } + let upper_pct = lo as f64 / n as f64 * 100.0; + + PercentileCi { + value, + lower: self.hdr.value_at_percentile(lower_pct), + upper: self.hdr.value_at_percentile(upper_pct), + } + } + + /// Welch's t-test comparing the means of this histogram and `other`. With fewer than 2 values + /// in either, or no variance, the result has p-value 1 and every other field 0. + pub fn welch_test(&self, other: &Self, confidence: f64) -> WelchTest { + const NO_RESULT: WelchTest = WelchTest { + t_statistic: 0.0, + degrees_of_freedom: 0.0, + p_value: 1.0, + ci_lower: 0.0, + ci_upper: 0.0, + }; + if std::ptr::eq(self, other) { + return NO_RESULT; + } + let n1 = self.hdr.total_count(); + let n2 = other.hdr.total_count(); + if n1 < 2 || n2 < 2 { + return NO_RESULT; + } + + let mean1 = self.hdr.mean(); + let mean2 = other.hdr.mean(); + let var1 = sample_variance(self.hdr.stddev(), n1); + let var2 = sample_variance(other.hdr.stddev(), n2); + + let se1 = var1 / n1 as f64; + let se2 = var2 / n2 as f64; + let se_sum = se1 + se2; + if se_sum == 0.0 { + return NO_RESULT; + } + + let t = (mean1 - mean2) / se_sum.sqrt(); + // Welch-Satterthwaite degrees of freedom. + let df = (se_sum * se_sum) / (se1 * se1 / (n1 - 1) as f64 + se2 * se2 / (n2 - 1) as f64); + let p = 2.0 * student_t_cdf(-t.abs(), df); + + let alpha = 1.0 - confidence; + let t_crit = student_t_upper_quantile(alpha / 2.0, df); + let margin = t_crit * se_sum.sqrt(); + let diff = mean1 - mean2; + + WelchTest { + t_statistic: t, + degrees_of_freedom: df, + p_value: p, + ci_lower: diff - margin, + ci_upper: diff + margin, + } + } + + /// The Mann-Whitney U test of whether this histogram tends to hold larger or smaller values + /// than `other`. Values in the same bucket are ties. + pub fn mann_whitney_test(&self, other: &Self) -> MannWhitneyTest { + const NO_RESULT: MannWhitneyTest = MannWhitneyTest { + u_statistic: 0.0, + z_score: 0.0, + p_value: 1.0, + }; + if std::ptr::eq(self, other) { + return NO_RESULT; + } + let n1 = self.hdr.total_count(); + let n2 = other.hdr.total_count(); + if n1 == 0 || n2 == 0 { + return NO_RESULT; + } + + // Values of `self` at index i exceed every value of `other` below index i. + let (concordant, tied) = concordant_and_tied(self, other); + let u = concordant + 0.5 * tied; + let dn1 = n1 as f64; + let dn2 = n2 as f64; + let mu = dn1 * dn2 / 2.0; + + // sigma^2 = n1 * n2 / 12 * (N + 1 - sum(t^3 - t) / (N * (N - 1))), where t is the number + // of values tied in a bucket. + let n_total = dn1 + dn2; + let mut tie_correction = 0.0; + for (c1, c2) in paired_counts(self, other) { + let tk = (c1 + c2) as f64; + if tk > 1.0 { + tie_correction += tk * tk * tk - tk; + } + } + let sigma_sq = + (dn1 * dn2 / 12.0) * (n_total + 1.0 - tie_correction / (n_total * (n_total - 1.0))); + if sigma_sq <= 0.0 { + return MannWhitneyTest { + u_statistic: u, + z_score: 0.0, + p_value: 1.0, + }; + } + + let z = (u - mu) / sigma_sq.sqrt(); + MannWhitneyTest { + u_statistic: u, + z_score: z, + p_value: 2.0 * normal_cdf(-z.abs()), + } + } + + /// Cohen's d: the difference of the means in units of the pooled sample standard deviation. + /// 0 with fewer than 2 values in either histogram or no variance. + pub fn cohens_d(&self, other: &Self) -> f64 { + if std::ptr::eq(self, other) { + return 0.0; + } + let n1 = self.hdr.total_count(); + let n2 = other.hdr.total_count(); + if n1 < 2 || n2 < 2 { + return 0.0; + } + let mean1 = self.hdr.mean(); + let mean2 = other.hdr.mean(); + let var1 = sample_variance(self.hdr.stddev(), n1); + let var2 = sample_variance(other.hdr.stddev(), n2); + let pooled_sd = + (((n1 - 1) as f64 * var1 + (n2 - 1) as f64 * var2) / (n1 + n2 - 2) as f64).sqrt(); + if pooled_sd == 0.0 { + return 0.0; + } + (mean1 - mean2) / pooled_sd + } + + /// Cliff's delta: the probability that a value of this histogram exceeds a value of `other`, + /// minus the reverse, in [-1, 1]. + pub fn cliffs_d(&self, other: &Self) -> f64 { + if std::ptr::eq(self, other) { + return 0.0; + } + let n1 = self.hdr.total_count(); + let n2 = other.hdr.total_count(); + if n1 == 0 || n2 == 0 { + return 0.0; + } + let (concordant, tied) = concordant_and_tied(self, other); + let pairs = n1 as f64 * n2 as f64; + let discordant = pairs - concordant - tied; + (concordant - discordant) / pairs + } + + /// The Kolmogorov-Smirnov statistic: the largest difference between the two cumulative + /// distributions, in [0, 1]. + pub fn ks_test(&self, other: &Self) -> f64 { + if std::ptr::eq(self, other) { + return 0.0; + } + let n1 = self.hdr.total_count(); + let n2 = other.hdr.total_count(); + if n1 == 0 || n2 == 0 { + return 0.0; + } + let mut max_d = 0.0_f64; + let mut cum1 = 0_i64; + let mut cum2 = 0_i64; + for (c1, c2) in paired_counts(self, other) { + cum1 += c1; + cum2 += c2; + let cdf1 = cum1 as f64 / n1 as f64; + let cdf2 = cum2 as f64 / n2 as f64; + let d = if cdf1 > cdf2 { + cdf1 - cdf2 + } else { + cdf2 - cdf1 + }; + if d > max_d { + max_d = d; + } + } + max_d + } +} + +#[cfg(test)] +#[path = "stats-test.rs"] +mod tests; From 2eb74d979bf30a5f02f0a9e7b716d3778f5134ff Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 17:24:03 +0000 Subject: [PATCH 04/24] Add histogram export and import Implements Node.js's histogram export format, the CBOR map behind histogram.export() and perf_hooks.importHistogram(). Export writes format version 2, byte-for-byte what Node.js writes. Import accepts versions 1 and 2 with Node.js's validation: duplicate keys, indexes that are not strictly increasing, a counts length that does not match the layout, counts that overflow, and a total count that does not match the counts are rejected, and unknown keys are skipped in version 2 data only. Unlike Node.js, import rejects a nonzero normalizing index offset. Node.js never writes one, and the HDR core does not port the index normalization it would require. The tests check exports against bytes produced by Node.js 26.10, which writes version 1; the only difference is the version value. They also accept bytes from Node.js 26.11 and later, which write version 2. --- src/rust/histogram/cbor-test.rs | 256 +++++++++++++++++ src/rust/histogram/cbor.rs | 489 ++++++++++++++++++++++++++++++++ src/rust/histogram/lib.rs | 4 + 3 files changed, 749 insertions(+) create mode 100644 src/rust/histogram/cbor-test.rs create mode 100644 src/rust/histogram/cbor.rs diff --git a/src/rust/histogram/cbor-test.rs b/src/rust/histogram/cbor-test.rs new file mode 100644 index 00000000000..646e7f0e8fd --- /dev/null +++ b/src/rust/histogram/cbor-test.rs @@ -0,0 +1,256 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +#![expect( + clippy::float_cmp, + reason = "imported floats must round-trip bit for bit" +)] + +use super::*; + +const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1; + +fn from_hex(hex: &str) -> Vec { + (0..hex.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) + .collect() +} + +/// Node.js 26.10 exports version 1, and later versions version 2; the two differ only in the +/// version value: the third byte, after the map header and key 0. +fn as_version_2(mut bytes: Vec) -> Vec { + assert!(matches!(bytes[1..3], [0x00, 0x01 | 0x02])); + bytes[2] = 0x02; + bytes +} + +fn new_histogram(options: Options, values: &[i64]) -> Histogram { + let mut h = Histogram::new(&options).unwrap(); + for &value in values { + assert!(h.record(value)); + } + h +} + +// Exports of the same histograms by Node.js 26.10. To regenerate one, pass its options to +// createHistogram() (`halfLife` for `half_life`) and record its values, as for `values()`: +// +// const h = require('node:perf_hooks').createHistogram({ highest: 1_000_000, figures: 2 }); +// for (const v of [1, 5, 5, 300, 4000, 999_999]) h.record(v); +// console.log(Buffer.from(h.export()).toString('hex')); +// +// Node.js documents the format at https://nodejs.org/api/perf_hooks.html#histogramexport. +const EMPTY: &str = "ab00010101021b001fffffffffffff03030400051b7fffffffffffffff0600070008fb3ff00000000000000919b0000a80"; +const VALUES: &str = "ab00010101021a000f4240030204060501061a000f423f070008fb3ff0000000000000091907000a8a01010402190111011901e4011903fa01"; +const EWMA: &str = "ac00010101021b001fffffffffffff03030403051832061896070008fb3ff00000000000000919b0000a86183201182801183c010ba500fb3fc45d819a94b14c01fb40516f8f0fcf2c8202fb4092caafef090d5403fb3fc12004cb6e5f84041864"; + +fn empty() -> Histogram { + new_histogram( + Options { + highest: MAX_SAFE_INTEGER, + ..Options::default() + }, + &[], + ) +} + +fn values() -> Histogram { + new_histogram( + Options { + highest: 1_000_000, + figures: 2, + ..Options::default() + }, + &[1, 5, 5, 300, 4000, 999_999], + ) +} + +fn ewma() -> Histogram { + new_histogram( + Options { + highest: MAX_SAFE_INTEGER, + half_life: 4.0, + threshold: 100, + ..Options::default() + }, + &[50, 150, 90], + ) +} + +#[test] +fn export_matches_node() { + assert_eq!(empty().export(), as_version_2(from_hex(EMPTY))); + assert_eq!(values().export(), as_version_2(from_hex(VALUES))); + assert_eq!(ewma().export(), as_version_2(from_hex(EWMA))); +} + +#[test] +fn import_node_exports() { + let h = Histogram::import(&from_hex(VALUES)).unwrap(); + assert_eq!(h.count(), 6); + assert_eq!(h.figures(), 2); + assert_eq!(h.highest(), 1_000_000); + assert_eq!(h.min(), 1); + assert_eq!(h.percentile(50.0), values().percentile(50.0)); + assert_eq!(h.export(), values().export()); + + let h = Histogram::import(&from_hex(EMPTY)).unwrap(); + assert_eq!(h.count(), 0); + assert_eq!(h.min(), i64::MAX); + + // Node.js reports these EWMA values for the same data. + let h = Histogram::import(&from_hex(EWMA)).unwrap(); + assert_eq!(h.ewma_mean(), 69.743_106_796_568_13); + assert_eq!(h.ewma_stddev(), 34.679_558_969_009_87); + assert_eq!(h.ewma_error_rate(), 0.133_789_634_067_167_04); + assert_eq!(h.export(), ewma().export()); +} + +#[test] +fn round_trip() { + let mut h = values(); + h.record(0); + let imported = Histogram::import(&h.export()).unwrap(); + assert_eq!(imported.export(), h.export()); + assert_eq!(imported.min(), 0); + assert_eq!(imported.max(), h.max()); +} + +/// Builds export data from (key, encoded value) pairs. +fn map(entries: &[(u64, &[u8])]) -> Vec { + let mut out = Vec::new(); + write_uint(&mut out, MAJOR_MAP, entries.len() as u64); + for (key, value) in entries { + write_uint(&mut out, MAJOR_UINT, *key); + out.extend_from_slice(value); + } + out +} + +fn uint(value: u64) -> Vec { + let mut out = Vec::new(); + write_uint(&mut out, MAJOR_UINT, value); + out +} + +fn counts(pairs: &[u64]) -> Vec { + let mut out = Vec::new(); + write_uint(&mut out, MAJOR_ARRAY, pairs.len() as u64); + for &value in pairs { + write_uint(&mut out, MAJOR_UINT, value); + } + out +} + +// The counts array length of the default layout (1, i64::MAX, 3). +const DEFAULT_COUNTS_LEN: u64 = 54 * 1024; + +#[test] +fn absent_fields_are_derived() { + let data = map(&[ + (KEY_COUNTS_LEN, &uint(DEFAULT_COUNTS_LEN)), + (KEY_COUNTS, &counts(&[3, 2, 4, 1])), + ]); + let h = Histogram::import(&data).unwrap(); + assert_eq!(h.count(), 3); + assert_eq!(h.min(), 3); + assert_eq!(h.max(), 7); + assert_eq!(h.highest(), i64::MAX); +} + +#[test] +fn unknown_keys_depend_on_version() { + let len = uint(DEFAULT_COUNTS_LEN); + let unknown: &[u8] = &[0x82, 0x01, 0x61, b'x']; + let v1 = map(&[(KEY_COUNTS_LEN, &len), (40, unknown)]); + assert_eq!( + Histogram::import(&v1).unwrap_err(), + Error::InvalidExportData + ); + let v2 = map(&[ + (KEY_COUNTS_LEN, &len), + (40, unknown), + (KEY_VERSION, &uint(2)), + ]); + assert!(Histogram::import(&v2).is_ok()); + let v3 = map(&[(KEY_COUNTS_LEN, &len), (KEY_VERSION, &uint(3))]); + assert!(Histogram::import(&v3).is_err()); +} + +#[test] +fn invalid_data_is_rejected() { + let len = uint(DEFAULT_COUNTS_LEN); + let cases: Vec<(&str, Vec)> = vec![ + ("empty input", vec![]), + ("not a map", vec![0x80]), + ("truncated", from_hex(VALUES)[..20].to_vec()), + ("indefinite map", vec![0xbf, 0xff]), + ( + "duplicate key", + map(&[(KEY_COUNTS_LEN, &len), (KEY_COUNTS_LEN, &len)]), + ), + ("counts_len mismatch", map(&[(KEY_COUNTS_LEN, &uint(100))])), + ( + "nonzero offset", + map(&[(KEY_COUNTS_LEN, &len), (KEY_NORM_OFFSET, &uint(1))]), + ), + ( + "index out of range", + map(&[ + (KEY_COUNTS_LEN, &len), + (KEY_COUNTS, &counts(&[DEFAULT_COUNTS_LEN, 1])), + ]), + ), + ( + "repeated index", + map(&[(KEY_COUNTS_LEN, &len), (KEY_COUNTS, &counts(&[3, 1, 0, 1]))]), + ), + ( + "odd counts array", + map(&[(KEY_COUNTS_LEN, &len), (KEY_COUNTS, &counts(&[3]))]), + ), + ( + "oversized counts array", + map(&[ + (KEY_COUNTS_LEN, &len), + (KEY_COUNTS, &[0x9b, 0x10, 0, 0, 0, 0, 0, 0, 0]), + ]), + ), + ( + "total count mismatch", + map(&[ + (KEY_COUNTS_LEN, &len), + (KEY_TOTAL_COUNT, &uint(2)), + (KEY_COUNTS, &counts(&[3, 1])), + ]), + ), + ( + "count overflow", + map(&[ + (KEY_COUNTS_LEN, &len), + (KEY_COUNTS, &counts(&[1, i64::MAX as u64, 1, 1])), + ]), + ), + ( + "count above i64::MAX", + map(&[ + (KEY_COUNTS_LEN, &len), + (KEY_COUNTS, &counts(&[1, u64::MAX])), + ]), + ), + ("invalid layout", map(&[(KEY_FIGURES, &uint(6))])), + ( + "deeply nested unknown value", + map(&[(KEY_VERSION, &uint(2)), (40, &[0x81; 40])]), + ), + ]; + for (name, data) in cases { + assert_eq!( + Histogram::import(&data).unwrap_err(), + Error::InvalidExportData, + "{name}" + ); + } +} diff --git a/src/rust/histogram/cbor.rs b/src/rust/histogram/cbor.rs new file mode 100644 index 00000000000..43b2fc0e3c0 --- /dev/null +++ b/src/rust/histogram/cbor.rs @@ -0,0 +1,489 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! Node.js's histogram export format (`histogram.export()` and `perf_hooks.importHistogram()`): +//! a CBOR (RFC 8949) map with integer keys. +//! +//! ```text +//! 0 -> uint format version (2) +//! 1 -> uint lowest discernible value +//! 2 -> uint highest trackable value +//! 3 -> uint significant figures +//! 4 -> uint total count +//! 5 -> uint min value (the raw field: smallest nonzero value, or i64::MAX) +//! 6 -> uint max value (the raw field) +//! 7 -> uint normalizing index offset +//! 8 -> float64 conversion ratio +//! 9 -> uint counts array length +//! 10 -> array sparse counts as flat [delta, count, ...]; the first delta is the absolute +//! index, and each later one the distance from the previous index +//! 11 -> map EWMA state, omitted when the EWMA is disabled: +//! 0 -> float64 alpha +//! 1 -> float64 mean +//! 2 -> float64 variance +//! 3 -> float64 error rate +//! 4 -> uint threshold +//! ``` +//! +//! Import accepts versions 1 and 2, whose layouts are identical. Version 2 data may contain keys +//! the importer does not know, which are skipped; version 1 data may not. Any field may be +//! absent; the total count, min, and max are then derived from the counts. +//! +//! The one difference from Node.js is that import rejects a nonzero normalizing index offset. +//! Node.js never writes one, and [`crate::hdr`] does not port the index normalization it implies. + +use crate::Error; +use crate::Histogram; +use crate::Options; + +const MAJOR_UINT: u8 = 0 << 5; +const MAJOR_ARRAY: u8 = 4 << 5; +const MAJOR_MAP: u8 = 5 << 5; +const FLOAT64: u8 = 0xfb; + +// Additional information values for arguments that follow the initial byte in 1, 2, 4, or 8 bytes. +const INFO_U8: u8 = 0x18; +const INFO_U16: u8 = 0x19; +const INFO_U32: u8 = 0x1a; +const INFO_U64: u8 = 0x1b; + +const EXPORT_VERSION: u64 = 2; +const STRICT_EXPORT_VERSION: u64 = 1; + +const KEY_VERSION: u64 = 0; +const KEY_LOWEST: u64 = 1; +const KEY_HIGHEST: u64 = 2; +const KEY_FIGURES: u64 = 3; +const KEY_TOTAL_COUNT: u64 = 4; +const KEY_MIN: u64 = 5; +const KEY_MAX: u64 = 6; +const KEY_NORM_OFFSET: u64 = 7; +const KEY_CONV_RATIO: u64 = 8; +const KEY_COUNTS_LEN: u64 = 9; +const KEY_COUNTS: u64 = 10; +const KEY_EWMA: u64 = 11; + +const EWMA_ALPHA: u64 = 0; +const EWMA_MEAN: u64 = 1; +const EWMA_VARIANCE: u64 = 2; +const EWMA_ERROR_RATE: u64 = 3; +const EWMA_THRESHOLD: u64 = 4; + +/// The maximum nesting depth of the unknown values that import skips over. +const MAX_SKIP_DEPTH: u32 = 16; + +fn write_uint(out: &mut Vec, major: u8, value: u64) { + let bytes = value.to_be_bytes(); + match value { + 0..=23 => out.push(major | bytes[7]), + 24..=0xff => out.extend_from_slice(&[major | INFO_U8, bytes[7]]), + 0x100..=0xffff => { + out.push(major | INFO_U16); + out.extend_from_slice(&bytes[6..]); + } + 0x1_0000..=0xffff_ffff => { + out.push(major | INFO_U32); + out.extend_from_slice(&bytes[4..]); + } + _ => { + out.push(major | INFO_U64); + out.extend_from_slice(&bytes); + } + } +} + +fn write_float64(out: &mut Vec, value: f64) { + out.push(FLOAT64); + out.extend_from_slice(&value.to_bits().to_be_bytes()); +} + +/// A cursor over CBOR input. Every read returns `None` on malformed or truncated input. +struct Reader<'a> { + data: &'a [u8], +} + +impl Reader<'_> { + fn remaining(&self) -> usize { + self.data.len() + } + + fn peek(&self) -> Option { + self.data.first().copied() + } + + fn take(&mut self, n: usize) -> Option<&[u8]> { + if n > self.data.len() { + return None; + } + let (head, tail) = self.data.split_at(n); + self.data = tail; + Some(head) + } + + fn take_be(&mut self, n: usize) -> Option { + Some( + self.take(n)? + .iter() + .fold(0_u64, |acc, &byte| (acc << 8) | u64::from(byte)), + ) + } + + /// Reads the argument of a data item of any major type. + fn read_argument_any(&mut self) -> Option { + let info = self.take(1)?[0] & 0x1f; + match info { + 0..=23 => Some(u64::from(info)), + 24 => self.take_be(1), + 25 => self.take_be(2), + 26 => self.take_be(4), + 27 => self.take_be(8), + // Indefinite lengths and reserved values are not supported. + _ => None, + } + } + + /// Reads the argument of a data item that must have the given major type. + fn read_argument(&mut self, major: u8) -> Option { + if self.peek()? & 0xe0 != major { + return None; + } + self.read_argument_any() + } + + /// Reads an unsigned integer that must fit into a non-negative i64. + fn read_i64(&mut self) -> Option { + i64::try_from(self.read_argument(MAJOR_UINT)?).ok() + } + + /// Reads an unsigned integer that must fit into a non-negative i32. + fn read_i32(&mut self) -> Option { + i32::try_from(self.read_argument(MAJOR_UINT)?).ok() + } + + /// Reads a float64, or an unsigned integer converted to f64. + fn read_number(&mut self) -> Option { + if self.peek()? == FLOAT64 { + self.take(1)?; + return Some(f64::from_bits(self.take_be(8)?)); + } + #[expect( + clippy::cast_precision_loss, + reason = "Node.js converts the integer to double the same way" + )] + Some(self.read_argument(MAJOR_UINT)? as f64) + } + + /// Skips one well-formed data item, including any items nested within it. + fn skip_item(&mut self, depth: u32) -> Option<()> { + if depth > MAX_SKIP_DEPTH { + return None; + } + let initial = self.peek()?; + let major = initial >> 5; + let info = initial & 0x1f; + + if major == 7 { + // Simple values and floats: 24 to 27 are followed by 1, 2, 4, or 8 bytes; 28 to 30 + // are reserved, and 31 is a break. + let extra = match info { + 0..=23 => 0, + 24..=27 => 1_usize << (info - 24), + _ => return None, + }; + self.take(1 + extra)?; + return Some(()); + } + + let arg = self.read_argument_any()?; + match major { + // Unsigned and negative integers. + 0 | 1 => Some(()), + // Byte and text strings. + 2 | 3 => { + self.take(usize::try_from(arg).ok()?)?; + Some(()) + } + // Arrays and maps. Each item takes at least one byte, which also bounds the loop. + 4 | 5 => { + if arg > self.remaining() as u64 { + return None; + } + let items = if major == 4 { arg } else { arg * 2 }; + for _ in 0..items { + self.skip_item(depth + 1)?; + } + Some(()) + } + // Tags. + 6 => self.skip_item(depth + 1), + _ => None, + } + } +} + +/// Records which keys of a map have been read, to reject duplicates. All known keys are below 64. +#[derive(Default)] +struct SeenKeys(u64); + +impl SeenKeys { + fn mark(&mut self, key: u64) -> Option<()> { + let bit = 1_u64 << key; + if self.0 & bit != 0 { + return None; + } + self.0 |= bit; + Some(()) + } + + fn has(&self, key: u64) -> bool { + self.0 & (1_u64 << key) != 0 + } +} + +impl Histogram { + /// Serializes the histogram in Node.js's export format, version 2. + pub fn export(&self) -> Vec { + let hdr = &self.hdr; + let non_zero = hdr.counts().iter().filter(|&&count| count != 0).count(); + let has_ewma = self.ewma_alpha > 0.0; + + let mut out = Vec::with_capacity(64 + non_zero * 10); + write_uint(&mut out, MAJOR_MAP, if has_ewma { 12 } else { 11 }); + + let fields = [ + (KEY_VERSION, EXPORT_VERSION), + (KEY_LOWEST, hdr.lowest_discernible_value().cast_unsigned()), + (KEY_HIGHEST, hdr.highest_trackable_value().cast_unsigned()), + ( + KEY_FIGURES, + u64::from(hdr.significant_figures().cast_unsigned()), + ), + (KEY_TOTAL_COUNT, hdr.total_count().cast_unsigned()), + (KEY_MIN, hdr.min_value().cast_unsigned()), + (KEY_MAX, hdr.max_value().cast_unsigned()), + (KEY_NORM_OFFSET, 0), + ]; + for (key, value) in fields { + write_uint(&mut out, MAJOR_UINT, key); + write_uint(&mut out, MAJOR_UINT, value); + } + write_uint(&mut out, MAJOR_UINT, KEY_CONV_RATIO); + write_float64(&mut out, hdr.conversion_ratio()); + write_uint(&mut out, MAJOR_UINT, KEY_COUNTS_LEN); + write_uint( + &mut out, + MAJOR_UINT, + u64::from(hdr.counts_len().cast_unsigned()), + ); + + write_uint(&mut out, MAJOR_UINT, KEY_COUNTS); + write_uint(&mut out, MAJOR_ARRAY, non_zero as u64 * 2); + let mut prev_index = 0_u64; + for (index, &count) in (0_u64..).zip(hdr.counts()) { + if count != 0 { + write_uint(&mut out, MAJOR_UINT, index - prev_index); + write_uint(&mut out, MAJOR_UINT, count.cast_unsigned()); + prev_index = index; + } + } + + if has_ewma { + write_uint(&mut out, MAJOR_UINT, KEY_EWMA); + write_uint(&mut out, MAJOR_MAP, 5); + for (key, value) in [ + (EWMA_ALPHA, self.ewma_alpha), + (EWMA_MEAN, self.ewma_mean), + (EWMA_VARIANCE, self.ewma_variance), + (EWMA_ERROR_RATE, self.ewma_error_rate), + ] { + write_uint(&mut out, MAJOR_UINT, key); + write_float64(&mut out, value); + } + write_uint(&mut out, MAJOR_UINT, EWMA_THRESHOLD); + write_uint(&mut out, MAJOR_UINT, self.threshold.cast_unsigned()); + } + out + } + + /// Deserializes a histogram from Node.js's export format, versions 1 and 2. + pub fn import(data: &[u8]) -> Result { + import(data).ok_or(Error::InvalidExportData) + } +} + +#[expect( + clippy::too_many_lines, + reason = "a single pass over the map, as in Node.js" +)] +fn import(data: &[u8]) -> Option { + let mut r = Reader { data }; + let map_size = r.read_argument(MAJOR_MAP)?; + + let mut lowest = 1_i64; + let mut highest = i64::MAX; + let mut figures = 3_i32; + let mut total_count = 0_i64; + let mut min_value = i64::MAX; + let mut max_value = 0_i64; + let mut norm_offset = 0_i32; + let mut conv_ratio = 1.0_f64; + let mut counts_len = 0_i32; + // Data without a version key is imported as version 1. + let mut version = STRICT_EXPORT_VERSION; + + let mut seen_keys = SeenKeys::default(); + let mut seen_ewma_keys = SeenKeys::default(); + // Unknown keys are skipped while parsing, because the version key that determines whether they + // are allowed can appear anywhere in the map. + let mut has_unknown_keys = false; + + let mut sparse_counts: Vec<(i32, i64)> = Vec::new(); + + let mut ewma_alpha = 0.0_f64; + let mut ewma_mean = 0.0_f64; + let mut ewma_variance = 0.0_f64; + let mut ewma_error_rate = 0.0_f64; + let mut threshold = 0_i64; + + for _ in 0..map_size { + let key = r.read_argument(MAJOR_UINT)?; + if key > KEY_EWMA { + r.skip_item(0)?; + has_unknown_keys = true; + continue; + } + seen_keys.mark(key)?; + + match key { + KEY_VERSION => { + version = r.read_argument(MAJOR_UINT)?; + if !(STRICT_EXPORT_VERSION..=EXPORT_VERSION).contains(&version) { + return None; + } + } + KEY_LOWEST => lowest = r.read_i64()?, + KEY_HIGHEST => highest = r.read_i64()?, + KEY_FIGURES => figures = r.read_i32()?, + KEY_TOTAL_COUNT => total_count = r.read_i64()?, + KEY_MIN => min_value = r.read_i64()?, + KEY_MAX => max_value = r.read_i64()?, + KEY_NORM_OFFSET => norm_offset = r.read_i32()?, + KEY_CONV_RATIO => conv_ratio = r.read_number()?, + KEY_COUNTS_LEN => counts_len = r.read_i32()?, + KEY_COUNTS => { + let arr_len = r.read_argument(MAJOR_ARRAY)?; + if arr_len % 2 != 0 { + return None; + } + // Each element takes at least one byte, so a length beyond the remaining input + // is invalid; checking first avoids reserving memory for it. + if arr_len > r.remaining() as u64 { + return None; + } + sparse_counts.reserve(usize::try_from(arr_len / 2).ok()?); + let mut index = 0_i64; + for j in (0..arr_len).step_by(2) { + let delta = r.read_i32()?; + let count = r.read_i64()?; + // Indexes are strictly increasing, so only the first delta may be zero. + if j > 0 && delta == 0 { + return None; + } + index += i64::from(delta); + sparse_counts.push((i32::try_from(index).ok()?, count)); + } + } + KEY_EWMA => { + let sub_size = r.read_argument(MAJOR_MAP)?; + for _ in 0..sub_size { + let sub_key = r.read_argument(MAJOR_UINT)?; + if sub_key > EWMA_THRESHOLD { + r.skip_item(0)?; + has_unknown_keys = true; + continue; + } + seen_ewma_keys.mark(sub_key)?; + match sub_key { + EWMA_ALPHA => ewma_alpha = r.read_number()?, + EWMA_MEAN => ewma_mean = r.read_number()?, + EWMA_VARIANCE => ewma_variance = r.read_number()?, + EWMA_ERROR_RATE => ewma_error_rate = r.read_number()?, + _ => threshold = r.read_i64()?, + } + } + } + _ => unreachable!("keys above KEY_EWMA are skipped"), + } + } + + // Version 1 data keeps its original semantics: unknown keys are rejected. + if version == STRICT_EXPORT_VERSION && has_unknown_keys { + return None; + } + + // alpha = 1 - 2^(-1/halfLife), so halfLife = -1 / log2(1 - alpha). Like Node.js, the + // histogram recomputes alpha from the half-life rather than keeping the imported value. + let half_life = if ewma_alpha > 0.0 && ewma_alpha < 1.0 { + -1.0 / (1.0 - ewma_alpha).log2() + } else { + 0.0 + }; + let mut histogram = Histogram::new(&Options { + lowest, + highest, + figures, + half_life, + threshold, + }) + .ok()?; + + if histogram.hdr.counts_len() != counts_len { + return None; + } + if norm_offset != 0 { + return None; + } + + let mut observed_total_count = 0_i64; + { + let counts = histogram.hdr.counts_mut(); + for (index, count) in sparse_counts { + let slot = counts.get_mut(usize::try_from(index).ok()?)?; + // The counts must add up without overflowing i64. + observed_total_count = observed_total_count.checked_add(count)?; + *slot = count; + } + } + histogram.hdr.set_conversion_ratio(conv_ratio); + + // Derive the total count, min, and max from the counts. A total count that is present must + // match them; min and max values that are present are restored as recorded. + histogram.hdr.reset_internal_counters(); + if seen_keys.has(KEY_TOTAL_COUNT) && total_count != histogram.hdr.total_count() { + return None; + } + let min = if seen_keys.has(KEY_MIN) { + min_value + } else { + histogram.hdr.min_value() + }; + let max = if seen_keys.has(KEY_MAX) { + max_value + } else { + histogram.hdr.max_value() + }; + histogram.hdr.set_min_max(min, max); + + if ewma_alpha > 0.0 { + histogram.ewma_mean = ewma_mean; + histogram.ewma_variance = ewma_variance; + histogram.ewma_error_rate = ewma_error_rate; + histogram.ewma_initialized = true; + } + Some(histogram) +} + +#[cfg(test)] +#[path = "cbor-test.rs"] +mod tests; diff --git a/src/rust/histogram/lib.rs b/src/rust/histogram/lib.rs index 53c0bd90ee0..007c66c3a71 100644 --- a/src/rust/histogram/lib.rs +++ b/src/rust/histogram/lib.rs @@ -11,6 +11,7 @@ //! //! [`hdr`] is a port of the core of `HdrHistogram_c`, the C library Node.js builds on. +mod cbor; pub mod hdr; mod histogram; mod stats; @@ -43,4 +44,7 @@ pub enum Error { /// A supposedly earlier snapshot holds values that the histogram does not. #[error("other contains values that are not in the histogram")] NotEarlier, + /// The input to `Histogram::import()` is not valid export data. + #[error("Invalid histogram export data")] + InvalidExportData, } From e2ebc8224e37a216cf947970e986bf8717b67b31 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 17:28:48 +0000 Subject: [PATCH 05/24] Add quantile-respectful density estimation to the histogram crate Ports histogram.qrde() from Node.js: Harrell-Davis quantiles at the requested probability boundaries, with the "none", "hdr", and "all" dequantization modes, the tail-trimming support search for histograms with many buckets, and the Edgeworth approximation for concentrated beta weights. The incomplete beta function gains the asymptotic paths QRDE needs; the exact path that the other statistics use is unchanged. Node.js computes QRDE on its thread pool and returns a promise. Here it is synchronous, since the computation is linear in the occupied buckets per boundary, and there is accordingly no equivalent of the snapshot cache. --- src/rust/histogram/lib.rs | 3 + src/rust/histogram/qrde-test.rs | 204 ++++++++++++++++ src/rust/histogram/qrde.rs | 412 ++++++++++++++++++++++++++++++++ src/rust/histogram/stats.rs | 195 +++++++++++++-- 4 files changed, 795 insertions(+), 19 deletions(-) create mode 100644 src/rust/histogram/qrde-test.rs create mode 100644 src/rust/histogram/qrde.rs diff --git a/src/rust/histogram/lib.rs b/src/rust/histogram/lib.rs index 007c66c3a71..984718f2b4a 100644 --- a/src/rust/histogram/lib.rs +++ b/src/rust/histogram/lib.rs @@ -14,10 +14,13 @@ mod cbor; pub mod hdr; mod histogram; +mod qrde; mod stats; pub use histogram::Histogram; pub use histogram::Options; +pub use qrde::Dequantization; +pub use qrde::Qrde; pub use stats::MannWhitneyTest; pub use stats::MeanCi; pub use stats::PercentileCi; diff --git a/src/rust/histogram/qrde-test.rs b/src/rust/histogram/qrde-test.rs new file mode 100644 index 00000000000..f7b642074cf --- /dev/null +++ b/src/rust/histogram/qrde-test.rs @@ -0,0 +1,204 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +use super::*; +use crate::Options; + +fn histogram_of(values: impl IntoIterator) -> Histogram { + let mut h = Histogram::new(&Options::default()).unwrap(); + for value in values { + assert!(h.record(value)); + } + h +} + +#[track_caller] +fn assert_all_close(actual: &[f64], expected: &[f64]) { + assert_eq!(actual.len(), expected.len(), "{actual:?} != {expected:?}"); + for (&a, &e) in actual.iter().zip(expected) { + let tolerance = 1e-12 * e.abs().max(1.0); + assert!((a - e).abs() <= tolerance, "{actual:?} != {expected:?}"); + } +} + +// The expected values in the tests below are from Node.js 26.10. + +fn small() -> Histogram { + histogram_of([1, 2, 2, 3, 10, 10, 10, 50, 4000, 4001, 4003, 9000]) +} + +#[test] +fn dequantization_modes() { + let probabilities = Qrde::uniform_probabilities(4).unwrap(); + let cases = [ + ( + Dequantization::None, + [ + 1.0, + 7.901_545_315_850_485, + 464.444_939_154_141_6, + 3_344.996_260_152_889_6, + 9004.0, + ], + [ + 0.036_223_771_424_906_195, + 0.000_547_593_073_022_431_4, + 0.000_086_788_941_470_176_53, + 0.000_044_177_387_309_299_474, + ], + ), + ( + Dequantization::Hdr, + [ + 1.0, + 7.900_992_940_269_17, + 464.406_433_915_480_93, + 3_345.029_772_886_711_3, + 9004.0, + ], + [ + 0.036_226_670_881_110_75, + 0.000_547_638_598_711_849_8, + 0.000_086_786_771_674_662_48, + 0.000_044_177_648_930_223_854, + ], + ), + ( + Dequantization::All, + [ + 1.0, + 7.880_707_017_028_898, + 464.436_052_572_011_9, + 3_345.051_412_877_351_6, + 9004.0, + ], + [ + 0.036_333_475_525_302_986, + 0.000_547_578_738_117_945_2, + 0.000_086_787_012_054_778_62, + 0.000_044_177_817_866_890_21, + ], + ), + ]; + for (dequantization, quantiles, densities) in cases { + let result = small().qrde(&probabilities, dequantization).unwrap(); + assert_all_close(&result.quantiles, &quantiles); + assert_all_close(&result.densities, &densities); + assert_eq!(result.corrections, 0); + assert_eq!(result.bucket_count, 8); + assert_eq!(result.count, 12); + } +} + +/// The MINSTD sequence, which the Node.js script that produced the expected values computes +/// exactly in doubles. +fn minstd(seed: i64) -> impl FnMut() -> i64 { + let mut state = seed; + move || { + state = state * 48271 % 2_147_483_647; + state + } +} + +#[test] +fn many_buckets_use_the_exact_support() { + let mut next = minstd(12345); + let h = histogram_of((0..20_000).map(|_| { + let s = next(); + 1 + (s >> (s % 21)) + })); + let result = h + .qrde( + &[0.0, 0.01, 0.25, 0.5, 0.75, 0.99, 0.999, 1.0], + Dequantization::Hdr, + ) + .unwrap(); + assert_eq!(result.bucket_count, 13234); + assert_eq!(result.corrections, 0); + assert_all_close( + &result.quantiles, + &[ + 1.0, + 207.509_565_749_821_06, + 20_021.716_189_524_934, + 780_440.176_928_827_5, + 29_248_602.941_716_09, + 1_675_808_889.639_776, + 2_089_650_581.838_535, + 2_146_959_360.0, + ], + ); +} + +#[test] +fn large_counts_use_the_asymptotic_cdf() { + let h = histogram_of((1..=200).flat_map(|v| std::iter::repeat_n(v * 37, 1000))); + let result = h + .qrde( + &Qrde::uniform_probabilities(5).unwrap(), + Dequantization::Hdr, + ) + .unwrap(); + assert_eq!(result.count, 200_000); + assert_eq!(result.bucket_count, 200); + assert_all_close( + &result.quantiles, + &[ + 37.0, + 1_498.483_497_180_411_2, + 2_978.995_877_823_859, + 4_460.003_869_660_284, + 5_940.014_272_653_952_5, + 7402.0, + ], + ); +} + +#[test] +fn single_bucket_spreads_both_ways() { + let result = histogram_of([7, 7, 7]) + .qrde(&[0.0, 0.5, 1.0], Dequantization::All) + .unwrap(); + assert_eq!(result.quantiles, [6.5, 7.0, 7.5]); + assert_eq!(result.densities, [1.0, 1.0]); + + // Without dequantization, every bin has zero width. + let result = histogram_of([7, 7, 7]) + .qrde(&[0.0, 0.5, 1.0], Dequantization::None) + .unwrap(); + assert_eq!(result.densities, [f64::INFINITY, f64::INFINITY]); +} + +#[test] +fn empty_histogram() { + let result = histogram_of([]) + .qrde(&[0.0, 1.0], Dequantization::Hdr) + .unwrap(); + assert!(result.quantiles.is_empty()); + assert!(result.densities.is_empty()); + assert_eq!(result.bucket_count, 0); +} + +#[test] +fn invalid_probabilities() { + let h = small(); + for probabilities in [ + &[0.0][..], + &[0.0, 0.5], + &[0.1, 1.0], + &[0.0, 0.5, 0.5, 1.0], + &[0.0, 0.7, 0.3, 1.0], + &[0.0, f64::NAN, 1.0], + &[-0.0, 1.5], + ] { + assert!( + h.qrde(probabilities, Dequantization::Hdr).is_err(), + "{probabilities:?}" + ); + } + assert!(h.qrde(&vec![0.0; 1002], Dequantization::Hdr).is_err()); + assert!(Qrde::uniform_probabilities(0).is_err()); + assert!(Qrde::uniform_probabilities(1001).is_err()); + assert_eq!(Qrde::uniform_probabilities(2).unwrap(), [0.0, 0.5, 1.0]); +} diff --git a/src/rust/histogram/qrde.rs b/src/rust/histogram/qrde.rs new file mode 100644 index 00000000000..b15909615b7 --- /dev/null +++ b/src/rust/histogram/qrde.rs @@ -0,0 +1,412 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! Quantile-respectful density estimation (QRDE), ported from Node.js's `histogram.qrde()`. +//! +//! The estimate places Harrell-Davis quantiles at the requested probability boundaries; the density +//! of each bin is its probability mass over its width. Values can be spread deterministically +//! within their buckets ("dequantized") to reduce quantization artifacts. +//! +//! Node.js runs the computation on its thread pool and returns a promise. Here it is synchronous: +//! it is linear in the number of occupied buckets per boundary, which is fast enough to run inline. +//! For the same reason there is no equivalent of Node.js's `cache` option, which reuses the +//! snapshot of occupied buckets between calls. + +#![expect( + clippy::cast_precision_loss, + reason = "counts and values are converted to f64 exactly where Node.js converts them to double" +)] +#![expect( + clippy::suboptimal_flops, + reason = "fused operations would round differently from Node.js" +)] + +use crate::Error; +use crate::Histogram; +use crate::stats::BetaParameters; +use crate::stats::std_clamp; +use crate::stats::std_max; +use crate::stats::std_min; + +/// Which values QRDE spreads across their bucket's range rather than treating as the bucket +/// midpoint. Only buckets holding more than one value are spread. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Dequantization { + /// No values: every value is its bucket midpoint. + None, + /// Values in buckets wider than one unit, whose midpoint is not exact. The default. + Hdr, + /// Values in every bucket, including unit-wide ones, as for integer data with ties. + All, +} + +/// The result of [`Histogram::qrde()`]. +#[derive(Debug, Clone, PartialEq)] +pub struct Qrde { + /// The quantile at each requested probability. Empty for an empty histogram. + pub quantiles: Vec, + /// The density of each bin between consecutive quantiles; infinite for a bin of zero width. + pub densities: Vec, + /// The number of values. + pub count: u64, + /// The number of occupied buckets. + pub bucket_count: usize, + /// How many quantiles were raised to the previous one to keep the sequence non-decreasing. + pub corrections: usize, +} + +/// One occupied bucket: `Histogram::RecordedBucket` in Node.js. +struct Bucket { + /// The bucket midpoint. + value: f64, + /// The bucket width. + resolution: f64, + count: i64, + cumulative_count: i64, +} + +struct Snapshot { + buckets: Vec, + total_count: i64, +} + +impl Snapshot { + fn new(histogram: &Histogram) -> Self { + let hdr = &histogram.hdr; + Self { + buckets: hdr + .recorded() + .map(|bucket| Bucket { + value: bucket.median_equivalent_value as f64, + resolution: hdr.size_of_equivalent_value_range(bucket.value) as f64, + count: bucket.count, + cumulative_count: bucket.cumulative_count, + }) + .collect(), + total_count: hdr.total_count(), + } + } + + fn should_dequantize(&self, index: usize, dequantization: Dequantization) -> bool { + let bucket = &self.buckets[index]; + bucket.count > 1 + && (dequantization == Dequantization::All + || (dequantization == Dequantization::Hdr && bucket.resolution > 1.0)) + } + + /// The range a bucket's values are spread over. The first and last buckets only spread + /// inward, so the estimate stays within the recorded range. + fn bucket_range(&self, index: usize, dequantization: Dequantization) -> (f64, f64) { + let bucket = &self.buckets[index]; + if !self.should_dequantize(index, dequantization) { + return (bucket.value, bucket.value); + } + let half_resolution = bucket.resolution / 2.0; + if self.buckets.len() == 1 { + return ( + bucket.value - half_resolution, + bucket.value + half_resolution, + ); + } + if index == 0 { + return (bucket.value, bucket.value + half_resolution); + } + if index + 1 == self.buckets.len() { + return (bucket.value - half_resolution, bucket.value); + } + ( + bucket.value - half_resolution, + bucket.value + half_resolution, + ) + } + + /// The range of buckets outside of which the Harrell-Davis weights are negligible, or `None` + /// if the weights cannot be evaluated. + fn exact_support( + &self, + params: &BetaParameters, + reflected: &BetaParameters, + count: f64, + ) -> Option<(usize, usize)> { + // Collapsing both tails changes a quantile by at most 2^-79 times the histogram range, + // which is less than 2^-16 over the i64 value domain. + const TAIL_MASS: f64 = 1.0 / (1_u128 << 80) as f64; + let len = self.buckets.len(); + + let mut lo = 0; + let mut hi = len; + while lo < hi { + let mid = lo + (hi - lo) / 2; + let rank = self.buckets[mid].cumulative_count as f64 / count; + let cdf = params.cdf(rank); + if !cdf.is_finite() { + return None; + } + if cdf < TAIL_MASS { + lo = mid + 1; + } else { + hi = mid; + } + } + if lo == len { + return None; + } + let first = lo; + + hi = len; + while lo < hi { + let mid = lo + (hi - lo) / 2; + let remaining_rank = + (self.total_count - self.buckets[mid].cumulative_count) as f64 / count; + let survival = reflected.cdf(remaining_rank); + if !survival.is_finite() { + return None; + } + if survival > TAIL_MASS { + lo = mid + 1; + } else { + hi = mid; + } + } + if lo == len || lo < first { + return None; + } + Some((first, lo + 1)) + } + + /// The Harrell-Davis estimate of the quantile at `p`: the expected value of the order + /// statistic at rank (n + 1) p, weighting each bucket by the Beta((n + 1) p, (n + 1)(1 - p)) + /// mass of its rank interval. + #[expect( + clippy::too_many_lines, + reason = "a single pass over the buckets, as in Node.js" + )] + fn quantile(&self, p: f64, dequantization: Dequantization) -> f64 { + let count = self.total_count as f64; + let a = (count + 1.0) * p; + let b = (count + 1.0) * (1.0 - p); + let mass_params = BetaParameters::new(a, b, true); + let reflected_params = mass_params.reflect(); + let len = self.buckets.len(); + + let mut begin_index = 0; + let mut end_index = len; + if mass_params.use_asymptotic_cdf { + // Outside this interval the normal-tail mass is below 2e-33. + const SUPPORT_DEVIATIONS: f64 = 12.0; + let lower_rank = std_max( + 0.0, + mass_params.mean - SUPPORT_DEVIATIONS * mass_params.standard_deviation, + ); + let upper_rank = std_min( + 1.0, + mass_params.mean + SUPPORT_DEVIATIONS * mass_params.standard_deviation, + ); + let lower = lower_rank * count; + let upper = upper_rank * count; + let first = self + .buckets + .partition_point(|bucket| (bucket.cumulative_count as f64) < lower); + let last = first + + self.buckets[first..] + .partition_point(|bucket| (bucket.cumulative_count as f64) < upper); + begin_index = first; + if last != len { + end_index = last + 1; + } + } else if len >= 512 + && let Some((begin, end)) = self.exact_support(&mass_params, &reflected_params, count) + { + begin_index = begin; + end_index = end; + } + + let dequantize = dequantization != Dequantization::None; + let lower_endpoint = self.bucket_range(0, dequantization).0; + let upper_endpoint = self.bucket_range(len - 1, dequantization).1; + + let mut previous_cdf = 0.0; + let mut previous_survival = 0.0; + let mut previous_front = 0.0; + let mut previous_count = if begin_index == 0 { + 0 + } else { + self.buckets[begin_index - 1].cumulative_count + }; + if previous_count != 0 { + let previous_rank = previous_count as f64 / count; + let (cdf, front) = mass_params.cdf_and_front(previous_rank); + let front = if dequantize { front } else { 0.0 }; + if !cdf.is_finite() || (dequantize && !front.is_finite()) { + begin_index = 0; + end_index = len; + previous_count = 0; + previous_cdf = 0.0; + previous_front = 0.0; + } else { + previous_cdf = std_clamp(cdf, 0.0, 1.0); + previous_front = front; + } + } + + let mut quantile = lower_endpoint * previous_cdf; + let mut using_survival = false; + + for i in begin_index..end_index { + let bucket = &self.buckets[i]; + let u0 = previous_count as f64 / count; + let u1 = bucket.cumulative_count as f64 / count; + let mut front = 0.0; + let mass; + // Use the reflected CDF above the symmetry point so that small upper-tail interval + // weights are not lost to subtraction from one. + if u1 <= mass_params.symmetry_point { + let (cdf, cdf_front) = mass_params.cdf_and_front(u1); + if dequantize { + front = cdf_front; + } + let cdf = std_clamp(cdf, previous_cdf, 1.0); + mass = cdf - previous_cdf; + previous_cdf = cdf; + } else { + let remaining_rank = (self.total_count - bucket.cumulative_count) as f64 / count; + let (mut survival, survival_front) = reflected_params.cdf_and_front(remaining_rank); + if dequantize { + front = survival_front; + } + if using_survival { + survival = std_clamp(survival, 0.0, previous_survival); + mass = previous_survival - survival; + } else { + survival = std_clamp(survival, 0.0, 1.0 - previous_cdf); + mass = 1.0 - previous_cdf - survival; + using_survival = true; + } + previous_survival = survival; + } + + if self.should_dequantize(i, dequantization) { + // I_x(a + 1, b) = I_x(a, b) - front / a, so the first moment within this rank + // interval does not need a second beta CDF. + let sum = a + b; + let rank_width = bucket.count as f64 / count; + let local_moment = std_clamp( + (a / sum - u0) * mass - (front - previous_front) / sum, + 0.0, + rank_width * mass, + ); + let (lower, upper) = self.bucket_range(i, dequantization); + quantile += lower * mass + (upper - lower) * local_moment / rank_width; + } else { + quantile += bucket.value * mass; + } + + previous_front = front; + previous_count = bucket.cumulative_count; + } + + let remaining_mass = if using_survival { + previous_survival + } else { + 1.0 - previous_cdf + }; + quantile += upper_endpoint * std_clamp(remaining_mass, 0.0, 1.0); + quantile + } +} + +impl Qrde { + /// `bins + 1` evenly spaced probability boundaries from 0 to 1, as Node.js's `bins` option + /// produces. `bins` must be 1 to 1000. + pub fn uniform_probabilities(bins: u32) -> Result, Error> { + if !(1..=1000).contains(&bins) { + return Err(Error::InvalidArgument("bins must be 1 to 1000")); + } + Ok((0..=bins).map(|i| f64::from(i) / f64::from(bins)).collect()) + } +} + +impl Histogram { + /// A quantile-respectful density estimate at the given probability boundaries, which must be + /// 2 to 1001 strictly increasing values starting with 0 and ending with 1. + pub fn qrde( + &self, + probabilities: &[f64], + dequantization: Dequantization, + ) -> Result { + if !(2..=1001).contains(&probabilities.len()) { + return Err(Error::InvalidArgument( + "probabilities must have 2 to 1001 elements", + )); + } + let mut previous = -1.0; + for &probability in probabilities { + if !(0.0..=1.0).contains(&probability) { + return Err(Error::InvalidArgument("probabilities must be in [0, 1]")); + } + if probability <= previous { + return Err(Error::InvalidArgument( + "probabilities must be strictly increasing", + )); + } + previous = probability; + } + if probabilities.first() != Some(&0.0) || probabilities.last() != Some(&1.0) { + return Err(Error::InvalidArgument( + "probabilities must start with 0 and end with 1", + )); + } + + let snapshot = Snapshot::new(self); + let mut result = Qrde { + quantiles: Vec::new(), + densities: Vec::new(), + count: self.count(), + bucket_count: snapshot.buckets.len(), + corrections: 0, + }; + if snapshot.buckets.is_empty() { + return Ok(result); + } + + let n = probabilities.len(); + let lowest = snapshot.bucket_range(0, dequantization).0; + let highest = snapshot + .bucket_range(snapshot.buckets.len() - 1, dequantization) + .1; + let mut quantiles = vec![0.0; n]; + quantiles[0] = lowest; + quantiles[n - 1] = highest; + for i in 1..n - 1 { + let mut value = std_clamp( + snapshot.quantile(probabilities[i], dequantization), + lowest, + highest, + ); + if value < quantiles[i - 1] { + value = quantiles[i - 1]; + result.corrections += 1; + } + quantiles[i] = value; + } + + result.densities = (0..n - 1) + .map(|i| { + let width = quantiles[i + 1] - quantiles[i]; + let probability_mass = probabilities[i + 1] - probabilities[i]; + if width == 0.0 { + f64::INFINITY + } else { + probability_mass / width + } + }) + .collect(); + result.quantiles = quantiles; + Ok(result) + } +} + +#[cfg(test)] +#[path = "qrde-test.rs"] +mod tests; diff --git a/src/rust/histogram/stats.rs b/src/rust/histogram/stats.rs index ef1bb32f5b3..4536f318a65 100644 --- a/src/rust/histogram/stats.rs +++ b/src/rust/histogram/stats.rs @@ -17,6 +17,7 @@ reason = "fused operations would round differently from Node.js" )] +use std::f64::consts::PI; use std::f64::consts::SQRT_2; use crate::Histogram; @@ -117,28 +118,184 @@ fn beta_continued_fraction(a: f64, b: f64, x: f64) -> f64 { h } -/// The regularized incomplete beta function `I_x(a, b)`: the probability that a Beta(a, b) -/// random variable is at most `x`. -/// -/// Node.js's QRDE adds asymptotic approximations for very concentrated shapes; the functions -/// here never request them, so only the exact path is ported. -fn regularized_incomplete_beta(a: f64, b: f64, x: f64) -> f64 { - if x <= 0.0 { - return 0.0; +/// The shape of a beta distribution and the values derived from it that `I_x(a, b)` needs: +/// `BetaParameters` in Node.js. +#[derive(Debug, Clone, Copy)] +pub struct BetaParameters { + pub a: f64, + pub b: f64, + log_normalization: f64, + pub mean: f64, + pub standard_deviation: f64, + skewness: f64, + excess_kurtosis: f64, + log_scale: f64, + pub symmetry_point: f64, + use_asymptotic_front: bool, + pub use_asymptotic_cdf: bool, +} + +fn stirling_correction(x: f64) -> f64 { + let inverse = 1.0 / x; + let inverse_squared = inverse * inverse; + inverse + * (1.0 / 12.0 + + inverse_squared + * (-1.0 / 360.0 + + inverse_squared + * (1.0 / 1260.0 + + inverse_squared * (-1.0 / 1680.0 + inverse_squared / 1188.0)))) +} + +impl BetaParameters { + /// With `approximate_large_shapes`, very concentrated shapes use a Stirling approximation of + /// the normalization and an Edgeworth expansion of the CDF, which are more accurate there + /// than the continued fraction. Only QRDE asks for them. + pub fn new(a: f64, b: f64, approximate_large_shapes: bool) -> Self { + let sum = a + b; + let mean = a / sum; + let use_asymptotic_front = approximate_large_shapes && a >= 8.0 && b >= 8.0; + // The continued fraction needs progressively more iterations as both shapes grow. At this + // concentration the Edgeworth error is smaller than the continued-fraction truncation + // error. + let use_asymptotic_cdf = approximate_large_shapes && a * b / sum >= 25_000.0; + let variance = mean * (1.0 - mean) / (sum + 1.0); + let skewness = 2.0 * (b - a) * (sum + 1.0).sqrt() / ((sum + 2.0) * (a * b).sqrt()); + let excess_kurtosis = 6.0 * ((a - b) * (a - b) * (sum + 1.0) - a * b * (sum + 2.0)) + / (a * b * (sum + 2.0) * (sum + 3.0)); + Self { + a, + b, + log_normalization: if use_asymptotic_front { + 0.0 + } else { + libm::lgamma(sum) - libm::lgamma(a) - libm::lgamma(b) + }, + mean, + standard_deviation: variance.sqrt(), + skewness, + excess_kurtosis, + log_scale: if use_asymptotic_front { + 0.5 * (sum * mean * (1.0 - mean) / (2.0 * PI)).ln() + stirling_correction(sum) + - stirling_correction(a) + - stirling_correction(b) + } else { + 0.0 + }, + symmetry_point: (a + 1.0) / (sum + 2.0), + use_asymptotic_front, + use_asymptotic_cdf, + } } - if x >= 1.0 { - return 1.0; + + /// The parameters of Beta(b, a), whose CDF at 1 - x is the survival function at x. + pub fn reflect(&self) -> Self { + Self { + a: self.b, + b: self.a, + mean: 1.0 - self.mean, + skewness: -self.skewness, + symmetry_point: 1.0 - self.symmetry_point, + ..*self + } } - let sum = a + b; - let log_normalization = libm::lgamma(sum) - libm::lgamma(a) - libm::lgamma(b); - let front = (log_normalization + a * x.ln() + b * (1.0 - x).ln()).exp(); - let symmetry_point = (a + 1.0) / (sum + 2.0); - - // The symmetry relation keeps the continued fraction in the region where it converges best. - if x < symmetry_point { - return front * beta_continued_fraction(a, b, x) / a; + + /// `x^a (1 - x)^b / B(a, b)`. + fn front(&self, x: f64) -> f64 { + if x <= 0.0 || x >= 1.0 { + return 0.0; + } + let log_front = if self.use_asymptotic_front { + self.a * ((x - self.mean) / self.mean).ln_1p() + + self.b * ((self.mean - x) / (1.0 - self.mean)).ln_1p() + + self.log_scale + } else { + self.log_normalization + self.a * x.ln() + self.b * (1.0 - x).ln() + }; + log_front.exp() } - 1.0 - front * beta_continued_fraction(b, a, 1.0 - x) / b + + /// The CDF from an Edgeworth expansion around the normal approximation, and the front. + #[expect(clippy::similar_names, reason = "the names follow Node.js")] + fn asymptotic_cdf(&self, x: f64) -> (f64, f64) { + let z = (x - self.mean) / self.standard_deviation; + let normal_cdf = 0.5 * libm::erfc(-z / SQRT_2); + if z.abs() >= 8.0 { + return (normal_cdf, 0.0); + } + + let z2 = z * z; + let z3 = z2 * z; + let normal_pdf = (-0.5 * z2).exp() / (2.0 * PI).sqrt(); + let z4 = z2 * z2; + let z6 = z3 * z3; + let density_correction = 1.0 + + self.skewness / 6.0 * (z3 - 3.0 * z) + + self.excess_kurtosis / 24.0 * (z4 - 6.0 * z2 + 3.0) + + self.skewness * self.skewness / 72.0 * (z6 - 15.0 * z4 + 45.0 * z2 - 15.0); + let front = + x * (1.0 - x) * normal_pdf / self.standard_deviation * std_max(0.0, density_correction); + let correction = self.skewness / 6.0 * (1.0 - z2) + - self.excess_kurtosis / 24.0 * (z3 - 3.0 * z) + - self.skewness * self.skewness / 72.0 * (z3 * z2 - 10.0 * z3 + 15.0 * z); + ( + std_clamp(normal_cdf + normal_pdf * correction, 0.0, 1.0), + front, + ) + } + + /// The regularized incomplete beta function `I_x(a, b)`, the probability that a Beta(a, b) + /// random variable is at most `x`, and the front at `x` (0 outside (0, 1)). + pub fn cdf_and_front(&self, x: f64) -> (f64, f64) { + if x <= 0.0 { + return (0.0, 0.0); + } + if x >= 1.0 { + return (1.0, 0.0); + } + if self.use_asymptotic_cdf { + return self.asymptotic_cdf(x); + } + let front = self.front(x); + // The symmetry relation keeps the continued fraction in the region where it converges + // best. + let cdf = if x < self.symmetry_point { + front * beta_continued_fraction(self.a, self.b, x) / self.a + } else { + 1.0 - front * beta_continued_fraction(self.b, self.a, 1.0 - x) / self.b + }; + (cdf, front) + } + + pub fn cdf(&self, x: f64) -> f64 { + self.cdf_and_front(x).0 + } +} + +/// `std::clamp()`: unlike `f64::clamp()`, never panics, and returns `v` when it is NaN. +pub fn std_clamp(v: f64, lo: f64, hi: f64) -> f64 { + if v < lo { + lo + } else if hi < v { + hi + } else { + v + } +} + +/// `std::max()`: returns `a` unless `a < b`. +pub fn std_max(a: f64, b: f64) -> f64 { + if a < b { b } else { a } +} + +/// `std::min()`: returns `a` unless `b < a`. +pub fn std_min(a: f64, b: f64) -> f64 { + if b < a { b } else { a } +} + +/// The regularized incomplete beta function `I_x(a, b)`, computed exactly. +fn regularized_incomplete_beta(a: f64, b: f64, x: f64) -> f64 { + BetaParameters::new(a, b, false).cdf(x) } /// The standard normal CDF: P(Z <= x). From b80a3105cec78b8d231048d2c8d1d5d58aaf2e49 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 17:35:52 +0000 Subject: [PATCH 06/24] Let wd_rust_crate tests embed data files Adds a test_compile_data attribute for files that a crate's unit tests read at compile time with include_str! or include_bytes!, such as test vectors. Like the *-test.rs files, they are inputs of the test target only. --- build/wd_rust_crate.bzl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/wd_rust_crate.bzl b/build/wd_rust_crate.bzl index 81ab075ff65..6878644f528 100644 --- a/build/wd_rust_crate.bzl +++ b/build/wd_rust_crate.bzl @@ -71,6 +71,7 @@ def wd_rust_crate( test_tags = [], test_deps = [], test_proc_macro_deps = [], + test_compile_data = [], test_size = "medium", cxx_bridge_deps = [], cxx_bridge_hdrs = None, @@ -100,6 +101,8 @@ def wd_rust_crate( test_tags: additional test tags. test_deps: test-only dependencies. test_proc_macro_deps: test-only proc_macro dependencies. + test_compile_data: files the unit tests read at compile time (include_str!, include_bytes!), + such as test vectors. Like the *-test.rs files, they are inputs of _test only. test_size: Bazel test size (default "medium"); affects test timeout. Use "large" for crates with many subtests, since RUST_TEST_THREADS=1 forces serial execution. cxx_bridge_deps: either a flat dependency list applied to every bridge source, or a dict of @@ -189,7 +192,7 @@ def wd_rust_crate( wd_rust_test( name = name + "_test", - compile_data = test_srcs, + compile_data = test_srcs + test_compile_data, crate = ":" + name, env = test_env, size = test_size, From f3c77a88b4aa8d9e2d002ff24b937f7b64e82fec Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 17:35:52 +0000 Subject: [PATCH 07/24] Test the histogram crate for parity with Node.js Adds node-golden.json, the results of every histogram query that the crate implements, from Node.js 26.10, over 11 datasets (empty, tiny, constant, uniform, wide, bimodal, heavy-tailed, and out-of-range) in 7 layouts (figures 1 to 5, lowest 1, 1000, and 1024), plus the two-sample tests over 6 pairs. generate-node-golden.mjs produces it; the datasets use integer arithmetic only and are stored in the output. The tests require identical results where the crate performs the same operations as Node.js: counts, min and max, mean, standard deviation, percentiles, the percentile, linear, and logarithmic iterations, cdf(), percentile confidence intervals, and export bytes. Results that go through lgamma() or erfc() are compared to 1e-12 relative, since libm can differ from glibc by an ulp. Every export is also imported and checked again. Floats in the file are strings: serde_json's default float parsing is not correctly rounded, which showed up as one-ulp mismatches, while str::parse::() is. The file is excluded from prettier, which would reflow it into about a hundred thousand lines. --- .prettierignore | 2 + src/rust/histogram/BUILD.bazel | 2 + src/rust/histogram/lib-test.rs | 339 ++++++++++++++++++ src/rust/histogram/lib.rs | 4 + .../testdata/generate-node-golden.mjs | 170 +++++++++ src/rust/histogram/testdata/node-golden.json | 1 + 6 files changed, 518 insertions(+) create mode 100644 src/rust/histogram/lib-test.rs create mode 100644 src/rust/histogram/testdata/generate-node-golden.mjs create mode 100644 src/rust/histogram/testdata/node-golden.json diff --git a/.prettierignore b/.prettierignore index 73498bef083..c96c2ab6c33 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,3 @@ samples/nodejs-compat-streams-split2/split2.js +# Generated by src/rust/histogram/testdata/generate-node-golden.mjs. +src/rust/histogram/testdata/node-golden.json diff --git a/src/rust/histogram/BUILD.bazel b/src/rust/histogram/BUILD.bazel index 4a439a69c91..062bb6286a0 100644 --- a/src/rust/histogram/BUILD.bazel +++ b/src/rust/histogram/BUILD.bazel @@ -2,6 +2,8 @@ load("//:build/wd_rust_crate.bzl", "wd_rust_crate") wd_rust_crate( name = "histogram", + test_compile_data = ["testdata/node-golden.json"], + test_deps = ["@crates_vendor//:serde_json"], visibility = ["//visibility:public"], deps = [ "@crates_vendor//:libm", diff --git a/src/rust/histogram/lib-test.rs b/src/rust/histogram/lib-test.rs new file mode 100644 index 00000000000..a49abbe02ee --- /dev/null +++ b/src/rust/histogram/lib-test.rs @@ -0,0 +1,339 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! Parity with Node.js: every query on every dataset and layout in testdata/node-golden.json, +//! which testdata/generate-node-golden.mjs produces with Node.js. +//! +//! Results computed with the same operations as Node.js must be identical. Results that go through +//! `lgamma()` or `erfc()`, where libm can differ from the platform's C library by an ulp, are +//! compared with a relative tolerance of 1e-12. + +use serde_json::Value; + +use super::*; + +const GOLDEN: &str = include_str!("testdata/node-golden.json"); + +fn golden() -> Value { + serde_json::from_str(GOLDEN).unwrap() +} + +/// A float in the golden data, which stores them as strings in JavaScript's format. +fn float(value: &Value) -> f64 { + match value.as_str().unwrap() { + "NaN" => f64::NAN, + "Infinity" => f64::INFINITY, + "-Infinity" => f64::NEG_INFINITY, + s => s.parse().unwrap(), + } +} + +/// An integer in the golden data, as a JSON number or a decimal string. +fn int(value: &Value) -> i64 { + match value { + Value::String(s) => s.parse().unwrap(), + _ => value + .as_i64() + .unwrap_or_else(|| value.as_f64().unwrap() as i64), + } +} + +fn floats(value: &Value) -> Vec { + value.as_array().unwrap().iter().map(float).collect() +} + +#[track_caller] +fn assert_same(actual: f64, expected: f64, what: &str) { + assert!( + actual.to_bits() == expected.to_bits() || (actual.is_nan() && expected.is_nan()), + "{what}: {actual} != {expected}" + ); +} + +#[track_caller] +fn assert_close(actual: f64, expected: f64, what: &str) { + if actual.is_nan() || expected.is_nan() || expected.is_infinite() { + assert_same(actual, expected, what); + return; + } + let tolerance = 1e-12 * expected.abs().max(1.0); + assert!( + (actual - expected).abs() <= tolerance, + "{what}: {actual} != {expected}" + ); +} + +#[track_caller] +fn assert_all_close(actual: &[f64], expected: &[f64], what: &str) { + assert_eq!( + actual.len(), + expected.len(), + "{what}: {actual:?} != {expected:?}" + ); + for (&a, &e) in actual.iter().zip(expected) { + assert_close(a, e, what); + } +} + +/// The entries of a JavaScript `Map` built from the pairs: in order of first insertion, with the +/// last value for each key. +fn as_map(pairs: &[(K, V)]) -> Vec<(K, V)> { + let mut map: Vec<(K, V)> = Vec::new(); + for &(key, value) in pairs { + match map.iter_mut().find(|(k, _)| *k == key) { + Some(entry) => entry.1 = value, + None => map.push((key, value)), + } + } + map +} + +fn int_entries(value: &Value) -> Vec<(i64, i64)> { + value + .as_array() + .unwrap() + .iter() + .map(|e| (int(&e[0]), int(&e[1]))) + .collect() +} + +fn options(layout: &Value) -> Options { + Options { + lowest: int(&layout["lowest"]), + highest: int(&layout["highest"]), + figures: i32::try_from(int(&layout["figures"])).unwrap(), + ..Options::default() + } +} + +fn build(options: &Options, values: &Value) -> Histogram { + let mut h = Histogram::new(options).unwrap(); + for value in values.as_array().unwrap() { + h.record(int(value)); + } + h +} + +fn from_hex(hex: &str) -> Vec { + (0..hex.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) + .collect() +} + +#[expect( + clippy::too_many_lines, + reason = "one check per query in the golden data" +)] +fn check_histogram(h: &Histogram, expected: &Value, what: &str) { + assert_eq!( + h.count(), + expected["count"].as_u64().unwrap(), + "{what} count" + ); + assert_eq!(h.min(), int(&expected["min"]), "{what} min"); + assert_eq!(h.max(), int(&expected["max"]), "{what} max"); + assert_same(h.mean(), float(&expected["mean"]), &format!("{what} mean")); + assert_same( + h.stddev(), + float(&expected["stddev"]), + &format!("{what} stddev"), + ); + + let percentiles = [0.1, 1.0, 12.0, 25.0, 50.0, 75.0, 90.0, 99.0, 99.9, 100.0]; + for (&p, value) in percentiles + .iter() + .zip(expected["percentile"].as_array().unwrap()) + { + assert_eq!( + h.percentile(p).unwrap(), + int(value), + "{what} percentile({p})" + ); + } + let at: Vec<(f64, i64)> = percentiles + .iter() + .copied() + .zip(h.percentiles_at(&percentiles)) + .collect(); + let expected_at: Vec<(f64, i64)> = expected["percentilesAt"] + .as_array() + .unwrap() + .iter() + .map(|e| (float(&e[0]), int(&e[1]))) + .collect(); + assert_eq!(at, expected_at, "{what} percentilesAt"); + + let expected_percentiles: Vec<(f64, i64)> = expected["percentiles"] + .as_array() + .unwrap() + .iter() + .map(|e| (float(&e[0]), int(&e[1]))) + .collect(); + assert_eq!( + as_map(&h.percentiles()), + expected_percentiles, + "{what} percentiles" + ); + + for probe in expected["cdf"].as_array().unwrap() { + let value = int(&probe[0]); + assert_same( + h.cdf(value), + float(&probe[1]), + &format!("{what} cdf({value})"), + ); + } + for probe in expected["countAt"].as_array().unwrap() { + let value = int(&probe[0]); + assert_eq!(h.count_at(value), int(&probe[1]), "{what} countAt({value})"); + } + + let step = int(&expected["linearStep"]); + assert_eq!( + as_map(&h.linear_buckets(step)), + int_entries(&expected["linearBuckets"]), + "{what} linearBuckets({step})" + ); + assert_eq!( + as_map(&h.log_buckets(1, 2.0)), + int_entries(&expected["logBuckets"]), + "{what} logBuckets(1, 2)" + ); + assert_eq!( + as_map(&h.log_buckets(10, 10.0)), + int_entries(&expected["logBuckets10"]), + "{what} logBuckets(10, 10)" + ); + + assert_close( + h.skewness(), + float(&expected["skewness"]), + &format!("{what} skewness"), + ); + assert_close( + h.kurtosis(), + float(&expected["kurtosis"]), + &format!("{what} kurtosis"), + ); + for (key, confidence) in [("meanCI95", 0.95), ("meanCI99", 0.99)] { + let ci = h.mean_ci(confidence); + assert_all_close( + &[ci.mean, ci.lower, ci.upper], + &floats(&expected[key]), + &format!("{what} {key}"), + ); + } + for case in expected["percentileCI"].as_array().unwrap() { + let (p, confidence) = (float(&case[0]), float(&case[1])); + assert_eq!( + h.percentile_ci(p, confidence), + PercentileCi { + value: int(&case[2]), + lower: int(&case[3]), + upper: int(&case[4]), + }, + "{what} percentileCI({p}, {confidence})" + ); + } + + // Node.js 26.10 exports version 1, and later versions version 2; the two differ only in the + // version value. + let mut export = from_hex(expected["export"].as_str().unwrap()); + assert!( + matches!(export[1..3], [0x00, 0x01 | 0x02]), + "{what} export version" + ); + export[2] = 0x02; + assert_eq!(h.export(), export, "{what} export"); + + if let Some(cases) = expected.get("qrde") { + for case in cases.as_array().unwrap() { + let dequantization = match case["dequantize"].as_str().unwrap() { + "none" => Dequantization::None, + "hdr" => Dequantization::Hdr, + "all" => Dequantization::All, + mode => panic!("unexpected mode {mode}"), + }; + let result = h + .qrde(&floats(&case["probabilities"]), dequantization) + .unwrap(); + let what = format!("{what} qrde({dequantization:?})"); + assert_all_close(&result.quantiles, &floats(&case["quantiles"]), &what); + assert_all_close(&result.densities, &floats(&case["densities"]), &what); + assert_eq!( + result.corrections as u64, + case["corrections"].as_u64().unwrap(), + "{what}" + ); + assert_eq!( + result.bucket_count as u64, + case["bucketCount"].as_u64().unwrap(), + "{what}" + ); + } + } +} + +#[test] +fn matches_node() { + let golden = golden(); + let datasets = &golden["datasets"]; + for layout in golden["layouts"].as_array().unwrap() { + let options = options(&layout["options"]); + for (name, expected) in layout["histograms"].as_object().unwrap() { + let h = build(&options, &datasets[name]); + let what = format!("{options:?} {name}"); + assert_eq!( + h.exceeds(), + expected["exceeds"].as_u64().unwrap(), + "{what} exceeds" + ); + check_histogram(&h, expected, &what); + } + + for comparison in layout["comparisons"].as_array().unwrap() { + let a_name = comparison["a"].as_str().unwrap(); + let b_name = comparison["b"].as_str().unwrap(); + let a = build(&options, &datasets[a_name]); + let b = build(&options, &datasets[b_name]); + let what = format!("{options:?} {a_name} vs {b_name}"); + + let welch = a.welch_test(&b, 0.95); + assert_all_close( + &[ + welch.t_statistic, + welch.degrees_of_freedom, + welch.p_value, + welch.ci_lower, + welch.ci_upper, + ], + &floats(&comparison["welch"]), + &format!("{what} welch"), + ); + let mw = a.mann_whitney_test(&b); + assert_all_close( + &[mw.u_statistic, mw.z_score, mw.p_value], + &floats(&comparison["mannWhitney"]), + &format!("{what} mannWhitney"), + ); + assert_close(a.cohens_d(&b), float(&comparison["cohensD"]), &what); + assert_close(a.cliffs_d(&b), float(&comparison["cliffsD"]), &what); + assert_close(a.ks_test(&b), float(&comparison["ksTest"]), &what); + } + } +} + +#[test] +fn imports_node_exports() { + let golden = golden(); + for layout in golden["layouts"].as_array().unwrap() { + for (name, expected) in layout["histograms"].as_object().unwrap() { + let data = from_hex(expected["export"].as_str().unwrap()); + // The export format does not include the exceeds count. + let h = Histogram::import(&data).unwrap(); + check_histogram(&h, expected, &format!("imported {name}")); + } + } +} diff --git a/src/rust/histogram/lib.rs b/src/rust/histogram/lib.rs index 984718f2b4a..7f6c7f658ee 100644 --- a/src/rust/histogram/lib.rs +++ b/src/rust/histogram/lib.rs @@ -51,3 +51,7 @@ pub enum Error { #[error("Invalid histogram export data")] InvalidExportData, } + +#[cfg(test)] +#[path = "lib-test.rs"] +mod tests; diff --git a/src/rust/histogram/testdata/generate-node-golden.mjs b/src/rust/histogram/testdata/generate-node-golden.mjs new file mode 100644 index 00000000000..a18458bc914 --- /dev/null +++ b/src/rust/histogram/testdata/generate-node-golden.mjs @@ -0,0 +1,170 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +// Generates node-golden.json, the expected results of Node.js's perf_hooks histograms that the +// histogram crate's parity tests (lib-test.rs) compare against: +// +// node src/rust/histogram/testdata/generate-node-golden.mjs \ +// > src/rust/histogram/testdata/node-golden.json +// +// Run it with Node.js 26.10 or later. The datasets are generated with integer arithmetic only, so +// they are the same on every platform, and they are stored in the output so that the tests do not +// need to reproduce them. + +import { createHistogram } from 'node:perf_hooks'; + +const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; + +// MINSTD: exact in doubles, since the product stays below 2^53. +function minstd(seed) { + let state = seed; + return () => { + state = (state * 48271) % 2147483647; + return state; + }; +} + +function generate(seed, n, f) { + const next = minstd(seed); + return Array.from({ length: n }, () => f(next())); +} + +const datasets = { + empty: [], + one: [7], + two: [3, 9], + three: [1, 5, 1000], + four: [2, 2, 8, 64], + constant: Array(100).fill(42), + uniform: generate(1, 2000, (s) => 1 + (s % 10000)), + wide: generate(2, 5000, (s) => 1 + Math.floor(s / 2 ** (s % 31))), + bimodal: generate(3, 3000, (s) => + s % 2 === 0 ? 100 + (s % 50) : 5000 + (s % 500) + ), + heavy: generate(4, 3000, (s) => Math.floor(1_000_000 / (1 + (s % 1000)))), + huge: generate(5, 1000, (s) => s * 4096 + (s % 4096)), +}; + +const layouts = [ + { lowest: 1, highest: MAX_SAFE_INTEGER, figures: 3 }, + { lowest: 1, highest: 1e9, figures: 1 }, + { lowest: 1, highest: 1e9, figures: 2 }, + { lowest: 1, highest: 1e12, figures: 4 }, + { lowest: 1, highest: 1e9, figures: 5 }, + { lowest: 1000, highest: 1e12, figures: 3 }, + { lowest: 1024, highest: 1e12, figures: 2 }, +]; + +const pairs = [ + ['uniform', 'wide'], + ['bimodal', 'heavy'], + ['uniform', 'constant'], + ['three', 'four'], + ['empty', 'uniform'], + ['one', 'two'], +]; + +const percentiles = [0.1, 1, 12, 25, 50, 75, 90, 99, 99.9, 100]; + +// Floats are written as strings: JSON has no NaN or infinities, and serde_json's default float +// parsing is not correctly rounded, while Rust's str::parse::() is. +function num(x) { + return String(x); +} + +function entries(map) { + return [...map].map(([k, v]) => [num(k), num(v)]); +} + +function build(layout, values) { + const h = createHistogram(layout); + for (const v of values) h.record(v); + return h; +} + +async function describe(h) { + const max = h.count > 0 ? h.max : 1; + const step = Math.max(1, Math.floor(max / 20)); + const probes = [0, 1, 2, 7, 42, 100, 999, 1000, 5000, 1e6, max, max + 1]; + const result = { + count: h.count, + exceeds: h.exceeds, + min: String(h.minBigInt), + max: String(h.maxBigInt), + mean: num(h.mean), + stddev: num(h.stddev), + percentile: percentiles.map((p) => String(h.percentileBigInt(p))), + percentilesAt: entries(h.percentilesAt(percentiles)), + percentiles: entries(h.percentiles), + cdf: probes.map((v) => [v, num(h.cdf(v))]), + countAt: probes.map((v) => [v, h.countAt(v)]), + linearStep: step, + linearBuckets: entries(h.linearBuckets(step)), + logBuckets: entries(h.logBuckets(1, 2)), + logBuckets10: entries(h.logBuckets(10, 10)), + skewness: num(h.skewness), + kurtosis: num(h.kurtosis), + meanCI95: Object.values(h.meanCI({ confidence: 0.95 })).map(num), + meanCI99: Object.values(h.meanCI({ confidence: 0.99 })).map(num), + percentileCI: [ + [50, 0.95], + [99, 0.95], + [90, 0.99], + ].map(([p, confidence]) => { + const ci = h.percentileCI(p, { confidence }); + return [num(p), num(confidence), ci.value, ci.lower, ci.upper]; + }), + export: Buffer.from(h.export()).toString('hex'), + }; + if (h.count > 0) { + result.qrde = []; + for (const options of [ + { bins: 10, dequantize: 'hdr' }, + { bins: 7, dequantize: 'none' }, + { probabilities: [0, 0.05, 0.5, 0.95, 0.999, 1], dequantize: 'all' }, + ]) { + const r = await h.qrde(options); + result.qrde.push({ + probabilities: [...r.probabilities].map(num), + dequantize: r.dequantize, + quantiles: [...r.quantiles].map(num), + densities: [...r.densities].map(num), + corrections: r.corrections, + bucketCount: r.bucketCount, + }); + } + } + return result; +} + +const out = { node: process.version, datasets, layouts: [] }; +for (const layout of layouts) { + const histograms = {}; + for (const [name, values] of Object.entries(datasets)) { + histograms[name] = await describe(build(layout, values)); + } + const comparisons = pairs.map(([a, b]) => { + const ha = build(layout, datasets[a]); + const hb = build(layout, datasets[b]); + const welch = ha.welchTest(hb, { confidence: 0.95 }); + const mw = ha.mannWhitneyTest(hb); + return { + a, + b, + welch: [ + welch.tStatistic, + welch.degreesOfFreedom, + welch.pValue, + welch.confidenceInterval.lower, + welch.confidenceInterval.upper, + ].map(num), + mannWhitney: [mw.uStatistic, mw.zScore, mw.pValue].map(num), + cohensD: num(ha.cohensD(hb)), + cliffsD: num(ha.cliffsD(hb)), + ksTest: num(ha.ksTest(hb)), + }; + }); + out.layouts.push({ options: layout, histograms, comparisons }); +} +process.stdout.write(JSON.stringify(out) + '\n'); diff --git a/src/rust/histogram/testdata/node-golden.json b/src/rust/histogram/testdata/node-golden.json new file mode 100644 index 00000000000..21e888afba3 --- /dev/null +++ b/src/rust/histogram/testdata/node-golden.json @@ -0,0 +1 @@ +{"node":"v26.10.0","datasets":{"empty":[],"one":[7],"two":[3,9],"three":[1,5,1000],"four":[2,2,8,64],"constant":[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42],"uniform":[8272,5795,4887,638,9042,5684,2162,6506,6692,832,2372,8208,8748,1150,5914,4340,6970,7795,7824,2096,2373,7186,6581,4088,138,6629,8331,3781,3876,6348,7814,2453,5788,4095,8937,9368,9877,6942,5028,8706,8263,263,2348,7858,2353,5144,2772,3969,4462,4861,8218,1517,6646,6900,9745,2136,9125,204,4772,9758,2495,690,3656,960,5429,7342,99,7160,9762,957,2477,5605,7071,5968,9678,4145,5332,1537,5952,911,2610,5997,7848,3331,3609,477,2554,8784,2451,9203,8820,5395,4753,4339,5547,3882,4520,8599,8449,4308,5700,5844,7060,6654,7070,1941,7509,1760,2189,7266,726,26,702,3028,176,2929,7088,7414,4553,1580,5915,5283,8269,2256,6181,5545,4532,1149,9365,3141,7141,5058,6666,79,1205,5947,5147,5389,1093,511,3996,4388,9140,163,763,2547,2571,6767,4118,2231,939,4787,1299,2012,5772,2005,9000,3008,6054,1048,1188,6636,3775,8131,1508,1284,9311,6048,8530,3985,7640,444,5763,7492,757,2914,5728,5566,8700,2562,6228,4938,8234,6309,6968,6380,2006,2159,3430,5993,1341,4517,598,8924,9968,6808,1596,6295,5375,1850,7175,1779,9832,7347,4086,9687,802,6494,1237,8425,9381,8186,9643,386,3071,6879,9666,5123,1936,981,4819,7805,6557,5782,3290,6401,5838,4362,9095,8683,7028,3552,7782,5486,5513,4215,6860,3766,6641,3910,208,2246,9828,911,3551,8664,2011,2357,6883,5206,6753,7502,493,4751,3980,7714,7590,7899,5230,1226,1420,9872,9632,1343,7346,9244,4837,1349,4014,4150,3721,2016,8127,5523,5691,9907,6912,6548,3699,7694,2184,5723,5605,7540,3242,3360,5920,4967,4233,5197,669,2099,8252,9694,2194,8517,2896,8033,9301,5766,2822,4973,3396,314,2456,210,278,3306,9770,2054,6969,7402,4768,9759,6353,5444,3631,2903,4401,4305,7929,8854,5856,3857,1484,1313,1662,6747,1881,5469,3423,6892,7669,4125,4135,6170,9297,1243,7420,9818,314,5002,9149,9937,2324,6394,1689,6423,6036,751,9743,1576,7769,6393,980,5338,9453,6934,6827,3551,156,9498,6824,7913,41,2120,9815,720,6666,4871,4928,9949,8782,7928,3705,6207,2553,28,182,3043,6836,1073,9401,2512,1549,8801,6108,4467,1961,4491,9653,7854,3441,3648,7550,2992,2443,7692,8007,7462,5866,5774,1646,5817,9000,69,1954,8014,96,8113,6637,1575,1758,1598,2986,1507,9152,4170,6828,460,4171,9682,7793,1254,8759,7174,1389,612,4035,5803,9119,8275,4341,3045,4112,6715,2861,7716,4275,6209,9,6350,5707,8378,895,676,613,6912,93,8464,6367,2075,7162,8947,6155,6681,9069,723,9543,1853,2090,6717,4592,1808,829,7516,7234,8218,421,5073,245,8528,8281,5171,1899,7661,6664,8000,9801,8877,3652,716,2443,1253,8431,8185,7845,8222,5542,6376,8757,9125,7855,4193,7080,6804,3064,3524,9020,2466,4713,9130,1977,5975,7849,6648,4940,1120,8454,8611,3590,2453,3043,8356,6008,2912,5713,1203,6684,6730,4971,6466,2349,9670,4574,3357,6683,8227,6667,8333,3201,5375,5515,6351,6776,1820,3232,7945,3168,5379,653,2528,9026,8917,9097,3699,9005,7796,8829,8534,7398,1834,157,1746,8038,5405,7238,3805,7823,933,58,7159,8234,3,7018,7521,5167,2405,9470,8957,1253,449,6912,930,1910,1448,4672,124,503,7129,7301,7662,5598,4928,9735,9240,4518,1406,3969,7660,5126,1121,9578,8879,1644,5519,2368,7079,40,7631,1815,9705,3840,5440,5058,7319,6788,7268,2529,3492,4656,8371,4473,70,9037,40,283,8660,7106,4318,9423,6020,7383,835,9174,1391,3083,1575,6709,6381,3394,282,5835,6191,5596,9656,1882,1683,7288,3549,8183,5755,8772,6958,5163,6891,8805,924,2926,6341,1145,137,7308,1335,3812,6524,9177,7834,6971,553,8780,4626,7735,8859,5163,8629,1525,5293,520,4364,5052,8100,4304,498,297,7767,2379,7175,2620,6239,8523,129,3003,6275,2323,6590,6481,5590,8489,2908,4040,1125,6927,1898,7131,531,761,7313,1973,2476,7547,2530,6611,409,6446,3820,1520,6384,1787,4013,3345,1581,6262,8245,2444,3429,1152,8422,3312,523,8744,5687,1166,2252,1348,9741,2225,922,5823,6969,1589,7505,4985,9848,6882,6422,4520,4447,5384,5792,2078,7308,9316,3985,4747,2042,5669,725,5846,6695,5008,3132,526,834,9957,5082,1334,6615,4639,6130,3412,8558,3222,6642,7227,8973,8096,1426,3880,9015,47,7295,7038,2126,9242,3452,7360,7720,4662,2370,5930,2211,9555,4364,7412,7461,3801,3989,9320,9059,9157,1024,1265,6690,5722,4188,4951,7621,1157,1980,9587,6098,3452,3985,7905,8719,1915,3538,2048,9149,9160,6318,6879,5174,9592,3167,538,1331,734,5603,1697,3175,107,4721,812,8640,6621,2580,8712,7053,2581,3156,3952,7581,8707,4910,5912,4991,9122,2940,9400,7160,6567,7099,6672,1756,506,7621,9485,5453,394,5758,7050,289,547,82,8245,2869,2514,1096,682,443,3146,9246,9559,3842,5326,8355,3804,8542,286,615,576,8123,1547,5790,9328,3862,1160,9520,5998,2303,6496,7577,9227,5091,594,3303,302,1974,4182,5264,630,8142,1509,9418,1614,1446,8153,2018,2886,2950,1163,3575,6255,5226,6485,1458,5609,1925,8871,3807,6236,8609,2515,791,1287,3109,2926,2506,4643,9766,6981,9727,3418,9050,3427,5478,3990,3570,7652,4274,7460,8893,5562,73,622,7994,7072,3112,2151,6426,597,550,8184,1805,2313,8666,6272,3226,2926,693,1179,9881,2514,2589,3502,787,2014,6130,3796,9163,6624,5983,7643,3085,7174,7781,8872,8095,5386,7090,8347,7637,4076,372,2278,1280,9034,7302,1875,602,264,9614,5476,5557,3437,4901,9709,5568,5350,6772,1683,932,9658,8936,2497,9275,4595,3499,9350,2032,1971,4865,6161,9942,304,3587,7530,6561,9784,564,4735,1947,250,5622,3375,464,3331,4996,7693,3483,3286,4108,1441,5224,1757,8912,7617,1170,2128,6427,9194,7727,4007,2031,4491,8731,8179,5880,4403,1692,670,8076,8017,8859,730,4106,4982,5489,6793,6469,8691,1525,4182,5174,6598,2977,7092,8877,2351,7699,5187,1601,2105,5501,8433,8228,3248,9060,8024,7776,7805,687,8422,6768,9500,2824,4564,850,3005,9353,9748,8103,2774,2896,7111,5739,5467,4830,1893,3467,8267,8193,1621,448,1507,1668,6322,1459,6277,4920,6123,3496,1720,4300,3881,9577,3311,3303,4867,645,7720,541,5681,1040,4452,5313,7160,9938,6010,2476,1292,9818,1642,4367,6757,8313,7210,3644,8776,9634,4223,7186,9175,5398,2750,6357,7744,2409,3194,5017,7160,928,753,3316,4574,4526,4648,9160,8107,479,6506,5810,3619,8952,7338,2728,9098,4474,7119,2391,1031,7708,8406,9470,475,6949,9154,5779,4549,6044,2528,3518,851,4808,7490,3180,6508,1380,1206,9550,9036,3562,3861,2012,5002,5093,6946,8950,2541,7901,1873,2276,8842,3984,4256,9032,9624,9749,9208,295,6719,4963,2506,9689,7745,9919,7782,419,4512,4342,4734,8081,9946,1118,8718,8888,4748,1253,3375,7337,9678,1689,8658,7120,4566,1816,64,2942,7228,2955,9855,5792,3008,8672,1882,2084,6464,1278,7372,3149,4904,8289,9732,2049,6569,1351,4261,4815,177,7877,5416,1995,9236,9712,4761,2825,8673,5507,6743,5930,9479,6655,6532,1138,6326,9980,4694,7070,6964,8169,4041,1397,9435,237,3526,4296,3230,1423,6458,3148,4037,6659,2795,6529,5961,9425,4621,7523,4613,6314,1727,1928,3348,4490,620,6547,5473,5712,6593,1828,6320,3695,8462,3340,6880,7565,6357,3135,7184,7271,2520,8088,7877,298,6939,1239,2542,2478,7472,7534,8447,2001,5628,1767,2806,4769,3075,9026,4576,8876,5392,3949,394,2983,9292,1568,9237,2247,1033,1140,4612,4022,8827,5171,6680,5068,1109,5088,7368,2722,4124,1093,6273,9715,9256,3029,4309,3049,1818,9545,6528,9475,678,8841,5179,346,715,6997,2680,3098,6319,9381,5681,5002,8865,310,556,1388,9101,1647,1391,8710,5713,2499,408,8435,6077,3795,1240,3959,5017,2504,6389,5347,5740,5813,8572,7453,8055,6673,4593,9713,7810,5732,8168,2979,3167,5101,8851,1056,8243,6923,5526,639,1536,5678,8378,9924,3838,451,2335,8489,7857,4587,1774,2642,689,4432,415,3720,6975,2076,1592,3078,838,298,2689,6195,1754,6461,6274,6596,3207,2434,5457,4026,3410,6473,3946,6460,513,6498,5683,8657,7112,8642,7766,9704,783,4531,4587,9995,3567,5873,8054,4817,4328,2602,5902,9748,514,7226,2062,6522,7804,8400,7526,8842,3551,6388,9145,1902,9507,9882,7904,7152,525,6437,2517,4405,8733,6124,7482,5754,3692,1721,3365,4861,1026,5135,6170,6621,61,5190,659,6633,364,6775,5166,9755,1917,8371,809,3020,4070,8029,9123,1489,6113,7866,2213,8995,596,5633,9138,2265,532,282,8177,6326,3154,7811,4333,7475,8988,4234,632,360,9435,5622,9034,293,1966,9641,3684,9278,1994,3218,881,1869,6979,4226,343,5489,557,2076,5339,1440,7090,6983,6619,4568,6370,543,6919,9679,713,8592,7234,2816,4757,7632,2714,1889,756,801,5264,3949,2407,2981,5885,4006,6759,6590,2445,1069,5140,4545,6848,6123,8822,6220,1718,1978,4387,5008,7546,1667,7211,1189,8463,4292,4320,6410,9100,3157,4660,5245,4176,5545,6922,7297,243,9833,4745,9544,3548,5743,6645,6028,3028,9074,182,870,8594,9128,5051,6008,6493,8539,1998,4075,3341,1011,3628,2809,2884,3880,8055,5778,9159,5959,5061,473,996,9006,7289,9874,4479,2822,4592,6387,3199,8458,99,8982,5580,19,5632,6174,1626,109,5255,177,9933,9051,2710,8961,4944,1139,3111,8485,6633,3804,4961,3963,4293,3229,9926,430,2478,3688,3112,5519,8980,3539,9222,2515,1070,3211,3370,6859,5176,1498,3604,8638,4322,6328,3560,2342,8373,1525,4501,7044,3149,4331,3429,8321,2493,5105,5402,9375,6010,263,6991,674,2265,5065,3072,3074,8292,7073,4743,445,3308,9700,6546,6138,5112,3471,1411,3527,8084,5842,3437,9520,849,3342,1768,4323,5770,1890,4909,3133,4270,6093,1069,3407,1168,8619,4070,3189,1394,3181,3120,408,7041,5851,2953,7606,3916,2764,346,2457,2728,86,7486,5383,5199,96,1671,9528,6331,2652,5832,8465,5084,71,3178,6791,7455,9203,4827,8158,1879,6910,6065,7268,8936,6451,3634,8646,336,718,1776,1390,4913,4167,4840,2351,2683,4190,1826,8470,8546,6077,1829,6864,9540,6483,1620,7254,113,4302,8072,411,7938,5784,5983,4782,7948,339,3028,7548,6879,1680,4486,8337,1522,6037,543,1472,7487,1064,3620,9214,5561,7289,4400,4254,4526,8900,637,9817,7781,9659,7215,8854,297,3566,8559,9927,9187,2986,4434,6326,3697,9766,3821,4051,1457,650,4765,6882,7976,9667,609,5832,8208,7277,8510,4590,5075,6778,3194,1055,9155,6153,2878,6542,3423,1634,4545,2677,5253,806,6942,511,8764,4605,1516,7722,4252,3956,693,4608,630,2518,9527,3991,1854,2398,7872,2238,94,1909,3603,5912,6441,1261,4813,7721,8828,7915,6943,2787,5324,4229,3535,254,5659,9879,7292,5180,4453,6373,5020,3670,4016,5082,1277,6853,9880,2651,9920,2912,2258,3036,7969,9205,931,4415,2054,842,7562,5347,7740,6804,2584,229,5094,8412,2004,861,4173,8974,8400,2481,3184,6072,9529,4687,9393,5492,3455,1244,6600,1148,3300,9324,6202,3047,4764,9562,4097,7387,6010,4072,302,5341,6195,8940,4625,9191,2365,168,2304,2071,6971,7720,6468,8792,4677,4575,3013,5681,3073,6952],"wide":[378,1,108826532,1605,1004927218,25,1963,1669369,34,31918,1468,15831,43,5,3012,1,3159435,8306141,7590407,122320,609,55844,107239345,160431,215572079,4,86358,184,109211507,19270,228718,111305307,10350091,124931,51,75,775,1451,387585,6137,14351,1856540525,7,723366,8260800,1118,3,804715,29,9,176093,2,501209644,1297,8,10540503,1798,4,210,4033,646409,11000647,1665320,3,2,19981,33764,179,926475,306,34925,15683838,1325666,1,383,137,1428947016,7,98,2710,7693132,1053994173,466,3,16024,5793261,3826731,45,10,133701,19592,49953393,1625,1139677,106273887,3,207812,804722,77639,8688,2098639,19828,6,920,3,6,1638,3852,16258628,4,7790363,456,423,586,8,7017535,45670886,406,131013639,86343,6,45522,785648269,33,844776181,348876861,66564,9,1615616,334548,2,69903234,72,10,6,2398,60724583,845,1218198538,302,77376086,1,164194570,21588339,17,1211,217860747,4021092,23295072,3,1767138230,635,4923746,259082547,4171,8,14912469,115,15272765,2546059,2,4,989930,2532681,4,16,30,105,350,1029947969,3507057,224840444,241103485,5,6,11,1405965,346009,4,42632661,54337,2004284,54529780,4,23726,930,73452006,406,199046,152124,15788,16,563,160,15616,4,80555,15,169943388,3329,3,31208,259479502,8541493,25213381,1704,89404745,10,4461,22,8,13544006,8,721,20958,8963441,358,10754,22188132,26,4122,2,52232285,188,24932867,66,17994188,57,327,3296787,233319257,1,11,174,53545,155,1298353719,2555,16,8,39417,134076,2169140,8,5012450,1629,53893,45528,16065108,3,54885,393,54147,267,16,901,16118,408540,1142976,70,19652,4181406,5,473,2,53254678,453,27,384474014,2987,235,51694,239,1168,39,28,876,1938,30529747,776787694,345,1,3892789,2027691432,82,63596,1702160,40444,494124,902,27,50,14103695,4,250082194,43455,46920,40808003,61,45980,2097,58,6574,105,3365909,1939,4,64096,48574,397157,194,94508895,30837905,19,29011137,4545,2,28717,11494,13,11316632,264307214,10821498,5760,6,297993,3151,412806,13347,117,504,330,5260,4760,4,31834136,217298593,55555,16,7062,64309245,41,10966,1085836226,366,9511,893156,71639106,13,102127594,451,50136974,465110,2,5894,621907028,100049727,173236278,31184,9488777,7378425,12783,101,1,24090,562150,2,2,33368711,16,5764111,2,207897196,104702,1,8,8,141117507,16,2,2950,9677165,231,65211,780481,66079,20817066,46,499945069,7826662,6073745,7748626,2668546,2,11272,1,3,10656491,2500766,10,3409,11961,54909,405930,4,2,901506,334,178985,1,18269676,69,110,5,37735698,31710754,709287,16,3001,15,1547028341,33826,83,198936,965386935,54457324,221007848,210733,85,3,10792,3845,4538,9,180580,383098358,5558880,251497716,78405,1,4920001,2958417,9,363163,15666362,47,99364849,69,234,16,3,28266,2,136364281,262455,31068341,27422953,172062,4,906133,54,324784893,66307673,95047498,18,290131,4,5064475,7,4677,6,37316,16198,131990,4165,233696,7744,7323721,2136,877595,15291464,234,5163130,952,692214,16023012,8559782,65305,954816,394995,9,965776,11043404,2,14294145,6434961,442,10237371,92,788060642,1999307,27,868516,493,58747252,8893,188125779,58217640,107124579,16,6,104352,74551403,764,7093881,1402,405493889,88947801,118456,4748,3,7986,12223,3566516,129248072,364070767,498174,173838,58353711,3,2,20596,2538,29,3601758,51035,254073,1,567269,117375379,3,755988,47,23,26,11464747,12,1,101209,12336067,164905,21926876,7,57779,63447255,80383,4,236900,18675,223,94532,116335667,13,3141211,568170388,319547521,138,32658901,6,412559,574229,41010,1937,6297,5321,3894,3704788,683759,363174340,3,1,387635,44,123763,5961,22,1194,184860352,125181985,11089870,50509071,445,1039899155,1689297757,124401228,2203179,6102441,2464591,8205882,1894531614,672625,4346,11199126,248695904,47,4,54761561,707687268,12,341179,17067,214641637,1274619,4,2,62784,6877789,4969,829917106,2651,13,9478,93242,119938627,302944587,1131,18987,14,327093355,4374,2,29504393,177777918,661,1561437,1,8,4,3,39,821,862,153552,3517,336,249,10,565,474,19,11055800,31,5328104,841439511,6,6440989,93373,1263,1709143,2116643,426576,1355,171898,570047035,63015,241,717,3470,57030,4,2,4,4,906714,1596,4910,6985774,6757,12866578,3570582,3193100,452126,3108,8048175,6220581,1271888,3,660,424148333,245963227,1978,292711,12534768,11945,987674261,15,152354469,239051,2401,175972663,74,55052,283,841,1530768,3,102003712,43829548,176479963,13599751,447,966,3979,130257281,668001,36041,865,15,4306354,1,3,6121428,109,570080856,494,325545,850040802,334,6070750,165063485,4776,15750981,19618594,74403,1,7655,436,231218612,36052837,78814474,25985016,222,29262,8701006,344829493,1,8,217154451,96384,64,85,2,1,8,62687,15178,156,347854,102052,30873,330480794,27,5025982,5173,2107,13,939656191,545,25,15715,296,388,144814,57628,162,197799,54,77,2002490,15263,30776,239,186379433,3352769,9,390101,573334,336872165,1565,51,1151,19725,60,305,110035,467392364,65593,3108,1065561157,4,10,5,110591726,16218,25449,783,7,1,181,2022634650,170974,124413062,52440941,8,8400619,950362,184,553,1450641467,28591487,5730092,251810,1,2215253,88,563568640,12,1,2768,919351,4782524,3,89,3,91105,25422,387834088,215,42134,15,26419394,55536584,138106567,12,13,147930439,1541363,1,8,610,7773728,99914,15399798,1895,4,787076516,1794196,98485131,2,11389,38,9062010,16,7706828,16,875641,129103376,26847,636086,4,4676,6,4384,3098692,1588799446,2002007932,6465,19,1,38691,409,1282,1076034,387,1,1629646534,94884,15388,47,63011269,26616,9162,56,957928,1,3678468,6340,414,127,6,6705,31800,74396,2167,5,228474,977092969,1089,115260,3959962,21237047,765403,10,129934653,310,3,1,25398,39544116,118,13864643,318977942,7631,6,41,146626,143,1892950955,82630,24398180,7586,70367,8,1165037785,5408623,63470037,23542832,3634125,4295,27,7,5,3323208,30402830,2,2,22,46759178,304,65985,3,10,147251,5256405,321860345,7,39261355,467,48200095,49453,1,7139,3,1635267,29509,3,47027675,6,457,62527381,19,2862,74,132249,15,1162380,8156879,3,524,1565382,62263749,9,1009,5022,1782,7571514,1239,2,2113015770,4,1922839,3134,291866,1025540932,1,62729,3632271,24,136,2,333877,1913,11898,258,6,20616192,109124240,71,3830,66556,7177473,2989834,298261456,6966624,6556,24206426,9328,317580927,145,31508850,2042,47123,664662,102054,6994,61,244,27827,79,1456,55711288,13295769,17,5853090,377,1399,31201,96565,1338,176,5837,9418,7506,7,50,5370524,59175671,2876,85041,4150,15,2,301,534060763,1403142,2828557,1086673195,91546489,27,89260,17423,665195397,225,11,901255,71426938,8,29,308711424,51282,3431,2625414,7153,601121373,64943538,64,43039,178432824,316227,469004,1988,3067,231407,64583007,22267847,18914778,612897111,679559,173,100,30335,2828,7355,46153,393692,47580,19092,8,3,2,4135,7119296,31212769,40824,7666560,41628599,24,21049,14,436,2,2098,2311,1614444,76811,499493,56667226,1,14792896,2,24894274,77,20165081,80,44,14288321,2,5272487,779117,63750,25353,2175,2,1,14253,743,20,8021506,73862,2,811586387,12892,1,3,21042689,1016652,194,1,96002,88465,8,2,5637004,311,2794794,1902102,1,7,8156,11049289,195556,140640142,3284,17518309,21,265,12587332,32626,194,43270,12,56,2396,1133805,160,4521,4185452,257722,1,253042,1932,949909550,100,54560,877,48273003,324920,3453,408912561,348103,27377,8653039,655004,18314,3206085,4,57297027,13589033,1166738,1862,6934,12242945,430,984087,137066253,222,38,22532490,99563245,81251,5302,6340,476,55,361219061,3424,87447112,191754128,1,2,114328944,7909440,78,153,38,253,16028297,15,26,7924,96174426,108,10387008,55562237,76154,103879834,71448,117774,14,64,20,1635382650,890409,61030,6,1,1,9,255,25843,19,280,1677887184,231071,2336052,4,384421,779547,7320,62107106,2172,22331,430903,1,2,833362275,68,48857039,1381149387,6350,60,19781,118545278,7557025,14554,379838413,2028074,64704,3740,14,110828659,19528,7423005,173310,708169,17640460,23601623,541519425,16,25601429,232,52002040,6310340,8020079,742796,31,588663,4101706,80893,13190179,237,261,1000562,2622,195763319,27618,101,7,4536130,517731311,131147,3,55,117246011,726782,327245,42212,871769415,2,52864,3,64093596,8904,168609,29009102,1170,908492806,177,3999478,815,2676791,39067,198,683997,64,846155,305497468,121473946,6185058,2,5,3404,414,229542198,2262023,31568449,33347084,2983380,3382,73301632,694,98,117,2115882185,43,27,14633,7115,1500168617,5822993,36686875,169614,18121085,13,4,4,111,2,2,731451,3031264,3913,3106,13,1,562445,932096,14756,20,56972,736938201,871,55,2163,164516,45166,1,232,376428506,180306898,1483886,6472,1157,3349659,911,5054183,9,6683,77584594,16890,488993,2836459,16038,692639713,228,350,1465,5683797,147789,1750,628,1577522111,29597902,5,7,29,23993107,1,99276048,43,916083838,713,75396542,9,2,1,119382,15,6691,2595,3810236,1793,1012,62573,3110,2,8727,8,4599,1034677,305845877,4568895,97,41646,18026,337,112,18,732,117469119,1517284244,14,960972,140278,77001,279383417,2,1149437283,8010,1,96460392,30212,44113,770,168247936,121982,242,7,131951791,65,1271126,135206981,104935129,411,3,3343329,1377,832,71931,62209,4809597,275922282,1403534642,1178282,128707,3,45730494,5471,3283,130175,29985,2220,12062,7,220,12587,59463736,5,23911541,63187,1440,3,2,126124578,1354415,579027,3170,28,25428,63,244684,3457265,649,89993094,110,1701,3835,37,260,297541489,486,58516,2044,26458625,6261,490271,1788,944,740697416,2,18233523,2148,7685709,594231,172773584,25110234,9119,35,3640855,16924,5,13366114,10772,9749508,50051966,2126,11300741,113260,7277594,55905713,41983,111956,54,102758557,843087,1398992,2569,73526510,19228,185086,2352287,7,243053,2,1,12843574,4,26370,1767381425,5673,3585339,59770,18,3841358,26030445,57,57,10,92755860,715405,268785292,489,7,5509251,84872,5501,2263599,9,20795415,66442,169,3442158,102776750,11300566,191904974,727875,238665126,2,7,47000,7504339,324819683,6988029,14645,24080448,1762588,1502,3,622865672,385193,8070,156294,393715098,27029291,1362,551,2,80342,1,165,94579,2134626,45,189,136,324725435,82596,4010,3315548,13875131,5,79,161,2531,60,69258649,712361804,227115247,2,60,3005650,264797,475,2,4,11505583,13,2,16310,32275622,682965310,42,1896474,30486393,381843199,49253,2640079,472483579,7,135,7,381,2262,502,8021,1,1454,794,792850886,317656,103,8104,218786221,452279,526159,13,5421,10576,155,1007668,418589963,5152826,813,1,5,256723424,15124800,47424483,1201308,401280346,213829,3261,660691,96,6185949,69162460,9302723,2851,185,254,189904,651,24981768,30674,1285563,1429,7315,396626254,3,4,240306,3050521,550,61548711,10085,10,7306672,75,514427,16597,130433,3,1,328019,33,20264645,247628,132557235,828,189,8767737,21356467,77506199,912112,2,17969246,3,67249,2,2725,2,2,134563,1,177430772,14774457,6,112203034,31,123798948,3847153,3,56480880,47635399,8,81348920,115,435,16341,3921,21251286,3449,45786,6,374,212535,8348679,491016,233,4,2,22,539829847,2187296,62900907,16223802,11449779,436823,427375,1871315,207,115066,6,4219,31618,1739,10010,82491,20,8708,508,50536,6766620,7,358,253819724,401579976,12387,493943089,21,883383,158921176,922757,62011512,33,3090886,21258546,1460517,5,904106864,979709540,365247,13,62270679,123827529,26308120,53,19,72407,1288265,3,76381,1416255895,136730085,2354854,3,6,47473,2,128291,10339,795,12137,39,119850,25102127,16,529507,58324723,650291,128,36261,13216,1487557,45,31,213337979,19324,5,277,63389905,32,11147634,2944681,1,475026,3490,298552251,370727,3,4153469,68067,417970,9794708,4598163,14491,23756,7698433,194807,9574,604235,55946705,4142,1865643,2143,6961,3411686,579,439082,11087,1420,325,102496712,1413139620,484368,52698293,5,102622246,119469618,29057,1004784,86217,2001484,63865738,977,1619,352,30201,2,447631259,1,1831,1,429584795,5883,2436,10,102,6976904,2,89120,942675,485647,10960185,12651177,122793262,1,16164703,1416,73568,7,4384534,9789104,7964,1223,66820949,15171292,16,3,41,396946,1,947842,6,139002470,243997707,556938096,41796,5,120296,21647247,192187,66,3939,108,333,505197562,19560,1693,1,3,610390,314650187,11,63017512,15036,5752631,22,506,1803130,24415,14,11207357,1572,3,75326709,94,9,14779,667,2,2866,3,3,2306,266258869,858194097,3539,21700444,128,433110,4019743,17,7,429921401,1030642,2,2000,3672620,43,18,22948367,8,36441,991636,2,87656,33847,82,263390,53670,85,412,911891,1386,50701020,982205,853096503,1497198259,242913,47,8,3483,106165,3845,14,62099590,249551555,15,1564,486,869,997,160469,38744956,26,11412604,69,3334,208431388,900812,177,45,31,529,2,59181,115,539181141,364622737,222,4647548,6639,7025,14079743,248266,1013883149,1,3601,576841073,1612,4,1,201545114,2286935,43980537,31329760,5,5141,5737507,14874,998,897087,1900119,998375,16,302,6,9,1374509,159,1683,2603,2136240939,308278277,246617684,21,233733193,5881,3116,716024640,66,63,2,16,15,251,1506381141,87,1421366,1,256,822,1530802,84,2207914,14684,181926911,1499,2,111699,707250,1,1162,181383,2783522,47,483188445,11,172,215,147477045,15046,304324,774218,242,69936,1467267666,39911939,2,750,2,5344,1192,945,100997,64,1371090,3839322,20,28166488,496,414,28717719,1008719,160280,943853,67,1515713,483,55466,8,3,470,71,39,3,33657019,346147691,1557143,51572308,13831533,25174713,1191,22094,822132556,6,14075,2788233,30742,12158,94,180106,10,2,5273,237640638,734624625,45,4,38131,11784623,2,20434203,12985,22098142,29609097,74,16610,1601410,2545637,15925,23388,2,2671492,46983645,3376,42,44,9849,1582111,660,177987447,623676043,461,6,8437356,962,41,51201,8192,446854,166145,41,450597277,2083463,490,112,752706925,4884,252309373,37731,35529,64,88898771,45,12884,7,92959282,281635792,95723,80738474,361811851,3,58309753,49094451,11179,8,2,41552939,25480,1924624,3482931,2071089,28210,1934,11,335667,139,2,193330234,2,1706735,328614718,21635244,204,1349,1332261,50707030,2642075,1751,19,55934328,2506,60695591,1033325,238534174,6181,2133575218,1,35798348,99,1978,470328847,11655313,1673264,2,2656,238607,15278620,3813988,920998,114427,287,1674875224,6047,237764995,977946,17475603,13,29,11479257,247028279,63787,62677057,5886355,80514,109898,7440365,2,1938662873,587127,2339037,7628162,65710538,153,1716,4883582,945,12754,43587656,1414223,58187669,1250,29,13,42159055,7,34679695,1920730582,247852,30012761,4182508,63701,1732717,84,978,1470860752,29945,1761022,22581588,10,376,32160,62,13,501619,107,50527068,3415259,1696903,309446899,117,2611,16155728,233166865,943737682,141,764257,7,4112496,127786,764,1189113,4686,4,26872526,126,26,12226,63639231,336949820,28606,100,11,12278,262704,6339203,397,253536973,33146169,2776,344190,819411,6,1,1846,19,7105,40602834,6103,9467154,5578891,58714,3,1305,9363,1302,142453929,36,101699,3034,175237,57,2,63,9,63165,15921911,8953746,5,8,196643409,54,19197,98,3,1,132560,1636146,118,97390957,38,644917,39830,602,167967243,244482803,29365519,472529537,123,2615,28,45432,1739,21,127,48,3072,1370,654765788,6572154,129915650,57,3,4,12,1711523858,1124717511,21511,8,15,472505783,4,6889,1434176189,86055327,12900199,566,4372,842577,14434091,3495262,8068011,43834,974739,7822,1511,20569731,357946,60250,67612,234822753,5423258,348776,27313,20727813,7,4911,36742,1,2471,167540,1,3324,734167,6918733,105,17818,48540,27310,47529,24102712,3644508,37551197,365288,2003534806,2744256,847,4,1,2,14,464,13898997,60,633271039,33,201485071,1533,191,29057924,155833,507805049,83565,226970546,41,103876731,1584804259,3,126370958,489095,52312512,151,374405,30456477,83407,3859525,245,1380,137,7112,827829,8,31049,5310518,2448081,492145,8,3,338,2757126,3933308,3,672982535,17337334,4675040,976447968,18747220,390,55344146,23230,11214512,64876119,33543938,32,285307222,61,77309,30209,12525772,13894,6,42373,29094908,2386,68,67,47501254,14,326,202510617,5418,40905,2,3800597,15655312,1,3091,5979,66737700,515,511,171,18066,3,469510,73224613,22089888,1149046466,7,1,751870,83228832,81881,204975004,167824,65283567,5016345,237394,206605980,4980,116543,389920,122364,14,448,2380384,1677407521,169,1368601238,678,11128999,133849,1492,80448,1,128543,105,262365,29,12,6326540,11,34,277,176,23645533,11602,1932283,5,2,42871690,3,30026,211391,162,108858390,2419,2,407671872,1,74108678,15760,4024967,1124781,3187070,67666,2,47,465,2514,2,5,467,889418614,1235980,323,2,22,1611344816,1446,3,75828000,20,4347891,1,8228391,162,3,676,2940,3020,858722,1729998,531395,1358306,87,1530,324357,56031,606,7646,94688168,413725,399924600,962783,258,1,102,27517,23857,117898,5938581,5682738,63034177,67201,30153,13870,699003517,40975,456758411,30975,10856505,5,244692708,1065421672,31467,233530,40468,92277,3657,2813185,14,469982,1,17368699,52,27,87856950,4077599,62678165,52298,49054,106309,3,144649,25955,2692141,24,33,202442397,241021234,1170115,64,225700221,206,11034,3658315,486532818,4154,32,4,1025,827185,147693200,3,505,890468274,50,12098283,464998,1,552547117,8,167,1147724688,31,640,46,7430,9779024,267460084,3,197940,2,11946062,30887285,19217,8004,2872,488,3703600,6659,571,2945267,6647230,11,2951,4,1,4551298,12863808,937416658,803759,28,512654937,366704,34653,51837677,9,382271766,11815,786903,8,3565,32952,755417720,947,140,4,2669,1019788944,4204447,3545,32,21173100,6118437,438716845,26,2,1,3515092,786,673,26011064,104,4030305,3,44815,40224441,412788,19,1,1,30438,7859017,25270,1072862,5,1,42,657030,63,100,82,7533905,12333004,3587,89,305,73,1,1904087860,505966,2,1563,1284,1,1096963521,1949,5953,23,498,1,4,407,531425174,44700,8,41,11,1539,20742492,15066544,1561,8,24,503,419,118951401,16759,186,1562865,2,75367,774927,604,100,8349211,315667824,75313036,14,13,3341228,52,82,10488365,233361073,118093,8,544883266,2,339744,1,9,8669,219975,7859114,3530990,3,162,1858,5258015,498700380,3,2,249,8185049,6902150,13,29,15267,1979,7885,15,899100891,3626161,1,4855632,4235272,4,45316,44232,198536277,368136471,12978,53,1237603,210764,3661,3,213,2,10,1465,1021279470,16683,4170,226,20329883,338667,14596,16620,185204371,12860905,45,2715,3462,115961,12517248,21519930,325926,2024498,92682747,582431,3,2,2,2601651,109288224,734,21369,2,1,56776324,14,6,32905028,3175,14676172,60,1705479,902570333,7015872,204,56503,81424,17821,7198326,12,34,43721,49634,8,112443,629942197,6714005,96,25034,56308,142113933,454146746,9224,663,13481,962,11329186,15479,30,1,53889,3302,43039,10175,1925,722650937,27044476,62265500,2,894694,5,41,2952767,2,11497527,6787610,19,1860757,471399,14539,4773925,14,85509,1889212696,40404078,27435428,73546075,2,368,3000,651,22250,47,328,2,741,81523719,195,44803450,228260332,1193423,16545468,1,3,1,10669,33628,445516,20361,8424,2039,1088,1488,1196063515,11,497457474,35260,1,308312,495,460743,25245730,2,1962,5,7,8,1595,106,182603,90454,47024,4,3900,62938063,2028859,1,2,14474,1618463,855559,399133456,1608,59112839,31,109917121,755151,2449,160,155143341,54610,369440711,15,80,1999086,943216154,438983,4113039,4,245771,1992,708,139183902,574006262,244,7833,83,94,584,278,764,481842627,80,2,1065521957,2,2,66,2,48224,40846214,4,126936,19,38209539,63737,1316,5744,9264,20,784642,7007519,3040853,2227586,5100,14768,186,4046201,331078,1188224,29688,1603,190,294737,10724625,84568609,14,2,3,2,25,817467,30076,3,1270328,7659314,1610152,199467,11,118792656,1488193,690644,26781,57272,2810,756000876,11,513178829,13,659342,1363598,16,11315516,373,8083,4,238823,19,240,8,360,675550994,64487,117,339,4,1193,7353661,203074,94878,279,8,60164779,2649,1404502,8181,70613,10,2427123,475576,13013929,2,550,18012795,253,895,1347303,2,2,2,2,510,37105440,368,46621,7320994,152775480,20788631,146323527,4,794,1018222,222177869,1,2491,544,5,43,96961111,2,1362,15275872,49387848,3116,81040166,891735,14315545,2833423,136998859,314,1417135210,35986,1234676,315,1016840,55997,231806,56779996,404657323,1515827,26,38,1,169225343,45309,5238,102874,1966,25585107,27799,58710,504965170,10,1712,125,13805,2,5635,171219,279813,17968649,190,5300,2,929,447,99221410,178243,2,58,8056,17689337,13970348,616933,12819130,24,949,16,277647,22173,85377,1226,117,2,3,988923267,974725747,2646,480,274319451,566414,93,458,2312,3,25,23415522,19165205,1,1139436,3570763,13793,497886,4,74322,1,3,100593,58,59,315396,13,260023,388067,37027036,1355,47160,7577,63255110,1,8,2221532,123674,1304,2992,882829,109796,24162,14,500626,199,270296,4,7,234,439,28367,30,3687,15,13994,114061153,112,28,3,7298393,2167555,48891,1255063,76554,2,1743,110842159,6196,8,620368,11,20,2540943,16,32745084,11416482,468902,60717500,24,24,51,13644,9240,253906877,6883,88699480,268118412,3066,1,127,900988651,11968,27,6,649955642,2644,1,383088112,8068238,56937,174750,315,9,450495031,813,5459,357,375,29283,3009,127839,80503141,93217,1844164488,1,3904,116826,2,3,3069,318479222,365,3018,25253044,6,351813,248,207698579,679,177075,432,13449601,1771,5489,1666,4,112,25179952,2406702,14,17809146,43320,14924176,227804,1647691355,2939451,3,16,6138378,6382635,4016,2146995831,585201,2973,511775,3530,97,56144372,10062,3,15151312,506,3,2182,1874,163556,31998683,15,654,451560261,4675,5,229611,670,2,1,8162,6738419,3878523,1,6591,755,7496,21,2359475,8181163,2632,22782836,3676,124448,41913,98944359,162,244335739,2673,131,726409144,183,27,2,15,108433,750,731,12,3815,101677,2805,741003,486258832,2,1077,42712,226244,20,6251115,143,31250766,7979,1,31526010,31387118,295593,251,29,6,1149,2,2,15843041,4972,94844,213608508,1734093,82,2,14359,80,6320,10,13195458,10941941,465131,47029682,1,710,94840,722471,6453,3,489972524,19065446,2767607,288,96,1616987684,8941,15079,125926416,448,12991780,85107686,48424471,230184813,652,2294,316041,4,2160058,113323,1346,154412,3,261988,14368,24634606,195,269805,6506383,55499,5898,173476,882299,7663409,997,19258,2,17165,104508,3640118,5,6143,50436,865,1985348,18359761,17,26085023,209494,95380,90,320989,334319501,3328,7221,11,11615769,78,689,801250,5480,51136,1,156629369,21,4,95620,9,1674700353,414,263,6564,73,434221899,11,319474,6672311,26955,9813369,206401,3382,48,336598776,1,3555,153565,41926,35,212,1167,1940046031,966867,6408916,154676086,26385349,107725,409681989,238465145,856724,18266416,113405795,2,696,2857,42481,2468,15339,12849872,43901,8,320712206,481501146,156,1371,16,2659088,1,23,31,10559,615162,6873,187848884,165,3031931,4786,97,549,5,6164929,169,58636734,4393244,1,10526147,2,28033084,57141,2,3,9,1,42235,53738,126833,1610834,1829627,82293,4,405,24829,10,1893,64812317,2268,122744,1926,6334,23217,10,3,7,1288903,51,126828,31818007,131063,1904554,29728314,13297015,55,40,8784,66084,36,974996439,487233674,7869754,43,118,18860757,14019,2,1667005471,3532792,1741,15168,2093412,2,254890061,26377,219,3697,747019,117128287,855,1026679872,2423355,14007315,9,14422290,486131,15355,2,1,3975630,169330,4,160,19,16,226180,20,237891874,71617,794754891,3683,139180,1,455861,909,1038884432,251,35716,215539,270072,353284,1405548,71,1876281,6726247,1216,6249326,875826091,564373725,468,222024,52,33445775,1406,3372510,216455,2,19,247,19,2023,1,43330772,4712528,2266339,40,32973428,711,285259,29686,100316,42763160,7635931,1693,28592,15,3,8151,1599975349,3,219926370,252561,20925,14,740581430,32476843,106329590,2285,202876130,16,18728,35218919,49464,207,43627,239969,14,6637,2,620232858,112,198627673,15448979,229602532,1010850,517844,27,451034,493,28393,765524,9,36,281,44521,1,99330,3318,2,35,275,12572206,25,1432,5,1730941855,1,1431014,145716,66305795,23960065,169,222725,183,121487590,330,247436871,1969,4328448,23634027,670941852,52075576,678210,967549,1,47,33517241,384,24,18779,241,177168,1197199,214823,11011,170,166,24419,482,84812,311242,16458759,777441,2,2,37296482,1122674,532,27172,7,29489,786817,125111103,250984,14,789,518,2,1014272168,4108370,315,28,139694,1634805151,187,12729,9099,72,5,3621,15920996,2,16051,3479,2274199,7,1006,1041311,138511155,3,362624,4868,23990363,16072,142173170,77,380,142,30167,25330,23350,1196555361,49711,4,74722,1272,56,1,87,220083,1830,111771369,418403800,107,4944856,1,7659035,812586,58,232909,26,856549,1101313,2972,51713,29511,61,2456,1729134927,6446133,1347,373361754,317913,101067,597388,1286846,28,493278,14011,363,935,2033262,1702362,64,2,2487,13,238731,1217,1537,429059,207,29161,415612,4848185,43036999,17,428,3,28763,1774129,896938067,165919,6582000,13172444,922383720,18496,11854713,95,3,14087695,415202299,838991131,75746,1,88251208,41938,11099,223,2,114533051,500974587,4183795,6528,4,638,62,2,267617669,529169730,37569,761049,6012961,12,7774,26,6543060,4206,599113705,1423972,285816436,73,1940,55686023,5075,125,45545148,23452475,80171,248651,141,5,1092414,17954917,5943,3,76824437,5673447,1,905745,208906,46,348759,20,4,6024,835816,154,138938,220028,189,16,273,101861,1205,46465949,44295,2863,434309,611,1442760027,5,5,2025537,9900501,776394,8648,30490,16012,1688427,15094491,2,140,3,1469702840,929426,7661381,3,423602,27,47,53371871,536595,98686,110681,2,2,232,1361070036,12120396,142770931,9507968,12489,20728,3103,25,430,1701123,7145203,2,90536,2,8770,1222,2831742,371463,922137,808,1342376,103,51,558,12433576,3,18251,29,81,10532,120501,20,201,2158,2441523,98,143181879,15,164,146192,1410489,857292446,3,2238,258482775,1336012,25309368,3147822,5699,3590,2,43,464,81,2840,122297554,485,496,93108520,414362,800839,14471558,10185597,6158,4,301806817,4,199511661,15,1024,22339143,110010048,6549603,2994047,1217,1721,261468,41209082,29060,16464,19,562,183151,26,951732830,2,907627193,2,1,17,35888691,3,60903438,15533,2,3394,3306881,1026389758,16,54593040,2,1,47,13273,8,28018636,4430,3788360,18,91357,326420081,64,12928,11030,2,82488,92,2062877269,3745125,797899,9,3040,1570,558,256534,30402,1330,12326137,4,44,446192100,61055520,1859237556,7,110258,2,6553,87,77673973,109706,134,2065653,2021,2887236,44838486,54046,132691767,51714278,115824,322,7196765,47865186,148980809,150897748,257491759,17,4,94091736,499131,51665284,9,4,1176,21639640,41345,2271,17077,16399466,5,23,1990703,13713711,101594489,3,1,88558174,430,26868,98477,22934337,3854,119242,140117,254279,3039285,8,126597786,5142,7035,2763,67831,1492347906,4,5642239,5970511,54,496,627934033,10551,145,14853985,818467953,7705,2,163214807,8,10,1039609,35617,48060475,2,16046,2668,15556,12,45,3427,1,115989,253,333451,10681566,12163,2433679,1020660,3880707,487,5416930,14991,4194,400,6371478,31,1807476,28487195,683,219,25516850,944,594050118,172107,97304014,2,499400965,1,2410,20094406,2388049,1417,441266626,13596,2,15317,2604,523439,2,33756498,110668,145677693,283862,4,1980,11541,65048852,514264,541,53245,42720,2,2338060,16982638,15378,544298,41041940,229601,39664,4338,2,366,107,15,3,188323,181250007,17810338,91164816,4712,285,2665158,530893,102230737,31958,68477,851544,349430560,250477,8,134938,123246005,55,24426,14807774,76026951,92,126,220,1715832,2079052,1566,7645940,2982339,1813407187,87333753,671049,430799863,62,22324,1427717,21685783,1,43994,496,139,1039892785,77017416,1107,6864949,84,219974,7058306,16,1455145,11,10700087,240640,2967,22127,2,2236,11698,909217477,2722,170,2404,186,855,21167447,20189,342,629,3488,22164,1003687,2,1469884,4560,3333,55,50231076,2979,13638985,15,79964,14,870842918,4217363,6327401,35010177,1275732,14943923,3,670967,122963078,1,511005,8,116880,7774,14584,218440431,3020813,325,23158,1224,890368,7737861,190,60179,4,394837484,68,13,5179,4,11091,241,236369669,214504,6,31574009,8080,13017,65016101,184673050,37035413,730204,51968366,1088,46471113,20,1959182920,101787,247674175,325,3553,1390246461,2,417,25310689,10905521,26,1635004,1120749,48789274,3398,6,1618,15626,4,6618,43132,177962,438,493,1627775,10,1145995,212587,2,15354146,37702,1654,131017023,14353,26225412,3963407,87,5301,8,2,26721615,1425,23058540,189,30189,26148701,427284,511136061,18,36632,1257,169497,8,386,74256,22484213,2,675904378,2,56709556,93081,437148113,12569471,18918,47379,5992543,204,7026,1176,10403941,2109740527,5138370,8246430,1790632,88,107207,16601056,20987931,263765005,1610,309733665,3,2154615,1,870,330,14,18899439,129,10946279,1,7264112,65,7,98723,745642970,539958,3444,23470,14,31264,87704048,531994008,522,3249615,109,3470,1939,102,124680321,483308485,7126,882,368574,4,9,6648192,1,1,1237575,225,84885305,797976891,228976,2594,822703,249,375088,3060,38952,52378,35109,192,13,96543,20,1,235733495,1,27004830,3364,63,2247986,90,189,164651115,36,1020,1485,133312662,4,45,47188124,197,45412344,423,2,14621,1194447,17219,145119,485,61,11125,95759,14164453,2600636,485,796741,3310372,2802231,66,4,607100033,183,1162379,951,4748,4,18432,381343153,26443,5855,8,25455786,25261,63,16251,18,13640744,7,48,20,1589289,2537650,1138751552,1645205,89,1,43,235,4,64,752330,346,384592,1268216,69,68042,674,126513578,3695,19261,10,7110,6600,7,5748,3,407679,101,105,59594707,2,311746,340,297,5,37,237,5956,184889,38124,1,206814,9534450,616825043,123,1,532269,14079,279979818,7947159,778227,49787,586200221,152544925,4,9082,3,59700145,124857,13817,232951245,216,20867,38029693,4429971,157,1,2084423,1,60475405,4,3780,3,3165,2964770,21301,4,15298777,579905841,2830,3473,1515792,19,29087511,121,915,355739,769482451,6329574,79572773,112260,221,46164488,382513,24325,10907656,2547,10101,6734379,8,744,9084,1,589,1523782,49294,148,18943563,503,66585808,46445635,1,47,2,111428621,518891424,444,1307,2,4428,164,15,3854,228,114671,11928,1453,7948497,3,1973,206865,4,40505,2073,13815706,439,1437078,3370380,3,21,4681,137166,3,14658178,1252,954933,3,182502697,1,125594,3,1,398,1264,2879273,5812080,25082851,57,7,9767,1,1393108,3,722,24,4,7498,3322,7,122850,342473,92864,224429829,12364,13852347,5329536,6,1043,361490001,13,219476,254,7337008,97139811,638,2115,92602,12833,2638,1,2,665646292,371967,207773890,641,1227,2,8,160,100516,1870,6,806108191,39815227,1744,204,1,26764751,115,9,4,10148088,447195756,2520937,1,890365,14643456,26,1955160887,1028952,79447,1311706473,4,57117,6,21738598,1895697927,49372,2030090,22772,1610320607,358371,1154476,13663752,5,9450,42845484,32600,14059,179601206,4221,48801969,2014386,1897,21325,123,6,6,3310733,1149,836231,1480,14,13580,29,7,14148,71,1722552604,917371221,20,327678999,4,68490,57,412,11679902,688447211,96,6100,6,14,4,368,1533223,7,103142380,95453013,51999106,2693969,1,61,361,17,10247,116298865,35,255,260153,1,321048076,8161,14770967,3,20096,39662,1508,3,378775,2650,40534400,19956987,1008990946,17772,28,2170274],"bimodal":[5313,132,5011,5117,5329,5049,136,118,5073,5199,5113,124,144,5153,142,120,5113,138,5175,5491,116,108,146,114,5117,140,140,146,5331,5041,5145,5209,5361,5135,5161,5307,134,126,134,5115,5139,5139,144,5277,5409,5135,5313,104,5383,136,5151,148,138,100,138,5405,5225,112,5019,5477,5335,5067,5171,130,5137,5023,144,5477,136,118,5281,5165,5063,5107,5031,132,5199,5461,5353,130,5033,144,5041,146,5177,134,112,102,100,106,5163,5035,112,114,138,5143,110,144,5197,124,5097,5029,5177,5165,110,120,5377,5483,5417,148,5175,5075,5103,134,5025,5137,5261,5445,106,140,142,146,5157,5471,5393,138,146,100,148,120,126,124,5201,134,5465,144,144,120,132,136,138,5161,5417,5339,142,138,5063,5151,5057,5043,120,108,100,5239,5313,118,5497,124,112,5141,5267,5405,122,146,124,5055,5283,5141,5293,102,120,5329,142,126,118,142,5387,5195,100,5183,5387,5017,5199,130,104,5343,5015,124,5493,5329,126,148,5291,5475,5401,5127,5285,132,128,100,128,5187,5199,144,5255,114,106,5479,5061,128,146,5261,132,5155,110,5487,5495,116,108,5293,104,5265,5021,5343,120,100,5217,136,138,146,134,5153,146,5161,142,148,5077,5295,120,130,5121,5235,5481,136,100,5195,136,124,5499,5115,106,5003,132,100,5437,142,120,5047,5187,5175,5257,5113,146,5379,5241,5435,114,144,5039,5447,116,148,134,116,126,124,5233,144,100,132,102,116,118,5323,126,5077,110,5251,102,144,5357,5147,5459,5285,5079,148,138,146,5253,5001,5463,5269,138,142,118,130,5331,5121,5307,5159,5257,5409,5007,124,5409,132,140,112,5053,5265,5137,112,5065,124,5449,142,136,144,146,110,122,5173,110,122,108,5007,138,126,5463,5451,142,106,144,114,5469,5385,5417,116,5105,5103,132,128,5157,126,140,114,106,5005,5331,106,5171,144,122,136,5473,110,142,110,148,116,5487,100,146,134,118,118,106,5081,5043,132,108,5069,100,136,5497,5253,5321,5251,136,126,106,112,120,144,100,126,126,5073,118,5383,148,5025,5141,148,5203,5057,112,5039,5285,5189,5261,122,124,5291,5161,118,106,5213,5187,5083,5363,5043,126,112,130,122,5017,5333,102,5259,110,128,120,5485,5039,5495,5433,5145,122,5477,124,5253,118,5131,132,5025,142,136,5129,142,104,5075,5483,144,112,140,110,116,5479,5409,5473,5001,5479,5127,134,5045,5199,104,110,116,132,5081,140,110,144,136,5195,5203,100,134,106,5145,132,112,5143,108,138,116,5123,5331,118,128,112,132,5443,5115,5189,5275,5263,5101,5489,140,134,5275,100,5147,5023,5357,5065,130,5267,112,132,118,124,5233,142,106,102,5187,110,5101,100,5213,122,124,5399,134,5351,146,106,5475,142,100,5031,110,5399,138,5001,134,5309,5287,128,104,5141,100,112,5385,5337,5099,5191,102,5321,5441,5111,112,114,118,122,146,5171,130,102,5359,104,110,104,112,5113,5221,106,144,136,140,5227,5047,5219,5075,112,134,100,5483,144,134,5055,5423,5051,118,104,130,128,5213,5231,5487,5135,110,104,140,120,146,142,5465,5223,5023,5171,104,5067,5301,5437,126,5171,116,116,5207,5461,120,102,5183,118,5157,116,5057,102,108,5019,120,146,128,124,146,5385,5343,102,120,138,5465,5143,102,5361,5497,5399,118,5019,5077,136,5023,5265,122,128,120,132,114,5421,108,5433,122,134,5499,5263,106,5043,5081,5055,130,142,5237,5425,132,5263,142,5359,5003,112,144,138,148,134,5375,5357,120,122,5237,112,5175,116,5267,146,5267,120,124,5323,122,134,5397,140,5443,5133,142,116,5131,5491,5087,130,124,5335,110,5263,5149,5211,5389,5385,140,5283,5085,5329,5137,5159,5469,5433,5419,5229,108,5495,106,144,5073,128,5263,5319,104,5117,5365,102,144,5143,5263,110,5191,5149,5373,5437,5127,148,5305,138,126,104,128,5241,5435,5227,5099,5075,5205,5221,5449,5499,148,120,5387,5439,124,116,126,5031,122,5285,5481,140,148,144,5235,5111,128,5429,5059,5283,5157,136,5107,140,5483,118,5089,136,5233,100,5317,5163,124,124,5069,148,120,5163,5267,5203,5213,5321,140,114,5291,5353,108,5065,5007,142,114,144,100,5183,5157,140,5225,5273,148,5111,146,102,112,5441,5375,118,110,136,5417,116,140,136,112,5093,118,106,5093,124,130,5439,120,5363,5023,100,5183,148,5147,116,5265,5221,110,108,5209,5385,5477,100,120,5491,5449,5085,110,142,5491,5249,132,5141,5235,130,5023,5181,5415,5115,126,5355,148,5431,116,144,5073,5481,5289,130,110,144,106,138,5081,134,126,5279,112,5403,5125,146,5289,5093,126,124,5457,5045,138,112,104,5155,5347,136,128,118,5381,102,124,124,128,5463,124,5205,124,5395,126,108,124,5275,5221,126,148,5293,5031,5251,5147,134,134,5173,5413,5159,5025,5083,126,5389,116,116,5479,5213,5039,5303,5275,138,100,102,112,136,5201,5313,5175,5275,126,5387,140,5245,114,5209,114,142,140,5385,136,122,5299,5279,5105,122,146,5319,5135,5361,120,138,5261,128,5319,5037,5337,5099,5109,122,5009,142,142,5131,124,108,5053,130,104,5253,5019,5399,146,5471,5305,144,122,138,144,5047,5299,116,148,130,5029,112,108,5293,136,5349,5395,108,138,5247,116,5475,5389,140,5191,5429,5299,5355,5027,5173,5169,124,136,104,5007,5087,134,5079,128,118,140,5323,146,140,5137,112,5073,5213,128,104,130,140,118,5443,5317,126,110,120,5425,5043,5019,5291,128,126,128,100,144,108,106,112,100,146,134,144,5383,122,5031,112,108,5469,5301,100,5469,5395,5253,118,106,144,5159,5319,5185,130,5067,5251,5193,132,5251,104,5429,116,5341,124,5001,116,130,128,5257,122,5485,110,5397,146,134,136,112,5451,5285,5363,5473,5393,120,5353,5289,5183,114,130,5425,5373,5451,5423,104,5121,136,5333,5135,5325,102,116,5055,128,5191,5247,118,132,130,5079,5401,5477,5487,106,5151,5219,5075,5147,130,124,5287,118,130,104,5353,5217,5181,5497,5419,5207,126,146,5327,5215,110,5275,100,5165,140,100,132,134,5051,100,124,5173,5037,5021,5343,5321,100,5105,136,130,5033,5003,5129,5041,100,120,5053,5469,5325,5023,102,5471,5093,122,144,5327,138,104,5239,5015,120,132,104,5049,104,5033,5229,102,146,5041,104,5357,114,144,5109,128,5361,5237,120,5471,5357,148,5445,5395,5323,5387,118,5415,126,5227,116,5143,102,5095,5037,5113,100,112,120,146,5497,5057,100,130,5295,128,134,5451,132,5205,136,5133,5325,122,124,126,5287,140,112,146,5411,5475,140,5079,5413,142,110,126,144,108,114,128,138,5393,5119,5077,5441,108,130,132,140,130,122,110,122,5189,5439,128,5487,5247,5467,5357,5491,5269,5133,5129,5187,5163,138,5089,5017,5343,5045,124,108,5049,110,110,114,5481,144,120,120,5123,134,116,102,138,106,5381,148,118,104,122,5075,5225,5331,5379,144,5385,102,126,104,114,144,5449,5417,136,116,128,110,140,104,5177,114,5307,116,122,5129,116,142,5471,134,5277,5497,5157,138,5287,128,5031,126,5387,5035,148,144,140,5291,110,146,140,5209,5445,5133,5165,114,100,5291,120,5127,142,5347,124,5155,5081,132,5423,124,5401,112,114,138,5423,142,5419,106,118,116,132,136,5427,5193,104,134,5351,106,5403,118,132,116,128,5267,5311,134,134,5269,114,106,102,114,124,5111,5319,5129,5417,5499,5095,110,128,5225,5273,134,5217,144,120,138,5259,5233,5025,5285,118,5299,118,128,5433,116,5041,5377,5389,5491,146,124,136,5423,148,112,146,5443,5111,138,104,122,5159,104,134,5303,106,5447,5039,5381,5389,5063,5115,5403,5075,126,100,5367,138,5409,124,146,112,5159,5425,114,5401,118,5049,5075,5149,5465,126,116,5445,136,5281,102,110,5213,130,5273,124,102,142,5175,5201,5115,148,5463,130,110,110,140,116,114,136,148,142,132,5491,102,5411,142,5299,5343,5381,5475,112,5283,146,5275,114,5199,5099,5077,102,5363,5099,132,5395,120,5049,134,132,5357,146,110,140,5175,126,120,118,5225,120,120,5473,5299,110,5201,5107,132,104,140,5489,126,102,5151,118,146,142,120,5471,5253,5495,5197,124,146,102,5015,124,5267,5185,104,120,132,5041,116,5169,110,5357,5431,5011,5227,5135,5351,136,120,136,5373,110,5433,100,118,130,138,5231,132,5469,5241,126,5349,5085,5129,144,126,138,5081,5287,122,5249,5107,5279,5087,100,5227,132,114,144,5075,5373,136,5087,130,5355,140,118,5331,5327,5227,136,122,138,5015,5217,122,5287,5463,126,114,144,5371,144,5149,140,110,5393,122,5081,130,112,134,5149,5003,5333,130,132,5267,130,108,146,5115,5233,5239,126,140,128,5287,134,5061,5333,110,5143,120,116,148,110,130,5107,130,5025,5197,112,114,5463,5187,130,5023,122,128,100,5129,144,146,134,5313,5329,118,5203,5475,5233,136,120,5019,142,148,5419,122,5373,5069,5079,138,5127,5303,138,114,5333,110,136,134,5455,5023,108,5057,144,5023,5301,122,110,5167,130,5249,5307,132,104,124,104,110,110,114,5385,146,5063,5427,126,106,112,118,5451,142,138,5221,5387,5255,5161,102,144,5491,116,5287,146,106,146,5245,5455,5063,134,5223,112,106,5331,5177,5487,5433,142,5301,5011,5203,5399,138,108,5357,5031,5373,142,148,120,100,5399,120,5475,110,5135,134,134,142,120,102,110,5465,136,106,5419,130,114,5349,146,5343,5341,114,134,5347,134,5037,5161,108,5269,108,132,5413,5311,5189,5357,142,5033,114,5403,5259,5075,100,114,5301,146,124,5495,5059,144,148,124,134,108,5455,5005,5181,138,5295,5313,5003,5221,5447,142,5349,128,148,130,5493,5327,5181,5027,5267,5075,5037,5285,5015,112,5309,5337,126,116,5105,138,5381,112,5121,126,5383,142,5165,5251,5369,5253,118,5429,124,5387,104,5431,5323,5059,5191,5113,114,132,5077,5159,5439,126,136,5289,5013,5481,148,5179,114,5175,140,102,5259,124,5487,5373,140,112,122,5057,5007,5251,5243,134,5409,5137,106,5463,5233,5271,5311,104,118,146,142,5159,126,5183,144,5423,112,102,140,5279,136,5215,5433,116,5125,5403,5293,5255,5419,5437,108,5029,5179,112,5229,5003,5441,5397,122,106,138,5289,5389,5141,5011,5233,116,5109,5373,132,5023,128,126,142,5001,5115,110,110,110,104,5373,128,5075,142,140,122,5353,5171,5321,100,5187,108,118,5231,134,140,5317,134,5359,5447,5125,100,5387,5329,104,5057,118,5417,114,5085,5017,132,5481,5059,5143,134,100,100,146,5071,5451,118,132,132,140,110,5097,5407,132,5151,124,5443,5391,122,5467,118,116,130,5335,108,134,148,5237,5283,5095,122,120,118,102,108,148,108,130,5411,140,5313,132,132,104,5435,110,128,102,142,5157,122,5133,5095,5489,5347,132,148,126,112,5063,130,138,112,5071,120,106,5269,132,5159,5463,5181,104,5355,5461,5109,5261,5235,5469,5447,120,5355,5491,5449,5267,124,5263,5399,104,5475,124,112,5047,132,116,5083,5175,110,5135,5365,100,106,102,124,128,5067,148,5305,128,5295,124,5037,5473,5039,5481,114,5057,5495,5057,122,132,140,142,5055,5063,5039,5433,144,5343,5265,5103,5205,5353,5455,126,5493,136,134,110,5325,5225,126,144,124,5305,142,5419,5231,5171,136,5377,5129,5017,114,110,124,130,5471,5123,104,5371,148,5131,5081,110,138,104,5497,5447,132,5175,136,5253,5307,128,116,106,114,120,118,108,5125,5433,5235,5069,5349,130,5065,5357,134,5121,5115,146,5165,132,106,5401,140,5287,5179,5109,5381,138,5039,114,128,108,124,5095,106,100,146,122,102,120,110,5483,142,116,106,5301,5357,128,5453,5311,5351,5043,5405,126,5219,104,102,148,114,112,5025,104,122,5081,5451,5251,144,5193,134,5429,5089,5439,5011,5089,132,5271,5015,5187,5231,5347,142,5465,118,106,142,144,5293,5261,5161,5313,136,5297,100,5491,142,5133,5371,124,126,130,5269,134,5199,134,132,5093,5005,148,5133,5185,5473,126,136,112,148,5303,134,5255,130,5201,138,5303,5313,5081,5355,5117,5105,5087,5243,5391,5063,5199,5349,5365,148,110,138,106,5033,5317,142,140,5491,126,5165,5471,5135,140,5429,132,5443,114,146,5169,5063,138,5291,108,112,114,5269,5401,136,5229,136,126,5075,5177,5183,100,116,140,136,140,5223,104,142,5191,5325,5345,5133,5433,5123,5141,5271,5491,5011,5323,5151,104,130,148,120,140,5457,5179,124,138,130,114,104,104,132,128,5407,5315,5481,144,5103,5081,5423,5311,5003,5383,5413,106,5179,108,104,120,138,124,5469,5219,132,112,5265,128,138,5361,124,5135,128,5135,5237,118,142,5163,146,146,140,5247,5341,132,126,132,136,5243,5039,5359,5211,5193,128,5437,5175,5401,116,142,5311,5145,5357,108,5269,110,140,5027,5431,106,5035,106,136,5223,120,5115,134,5265,126,5481,118,5355,116,124,5375,5293,142,136,5193,5131,5091,5173,108,144,5419,5227,5375,5205,140,5079,5171,144,5285,148,132,5061,5381,112,5041,5167,5257,130,5017,5485,5037,5133,104,5217,130,104,5139,5469,148,108,130,118,106,104,106,102,144,5205,5037,5211,128,5237,5007,126,5107,5261,106,5463,118,124,5491,5297,112,5395,120,114,146,148,126,122,5055,114,116,5365,120,5403,122,5217,5321,120,102,124,5313,138,104,124,122,5071,5169,118,5369,5395,146,102,146,5389,112,5153,130,5107,140,126,138,134,5133,148,132,5349,5283,110,112,120,5335,134,128,5389,5367,5113,134,5289,148,5317,5435,5295,5409,148,5229,102,5093,5071,146,104,5113,120,140,5409,5113,5093,108,5243,5217,5041,5035,112,5181,118,5011,5383,134,5029,5405,5017,130,144,136,116,5399,5111,5247,144,142,102,126,104,5223,5261,5421,5077,5005,5425,108,116,110,5293,5135,5045,5097,136,100,124,138,5447,132,110,5325,110,5429,5357,5281,130,122,106,5029,120,5151,148,116,138,146,132,5369,140,5183,104,124,5297,5109,106,136,5435,5085,132,5279,5041,102,5279,5397,5267,122,5277,5247,110,5061,140,5007,114,5065,5111,5483,5121,138,148,120,136,5025,5129,128,128,5111,136,5061,122,124,5067,5497,5123,5159,5007,124,148,138,5271,5207,5483,100,5161,5335,5073,108,134,5113,134,5251,126,136,5377,5071,5035,5145,146,120,134,5079,5493,5353,5311,144,5457,144,112,5165,140,122,5169,120,104,108,118,138,5329,112,112,102,110,5367,126,122,122,132,5263,138,5183,118,5287,142,5231,5125,142,142,5087,124,102,5013,5271,108,144,5147,134,5475,128,122,5255,102,5343,5259,5469,132,124,104,5483,5465,5499,148,134,140,126,104,5371,136,5465,5107,112,5187,5381,102,130,138,146,148,5011,5253,5307,5347,5019,5257,5389,142,146,116,104,106,132,118,136,5097,112,134,5319,134,126,118,138,104,5001,130,5127,108,102,5429,120,108,100,5395,116,132,138,148,5295,124,5305,5159,138,112,5123,120,5465,122,5173,124,108,5315,144,148,112,5439,136,5071,5323,5225,116,142,5267,5023,5107,136,5031,124,5117,144,5115,114,126,130,134,5087,114,5465,5423,132,122,5051,5107,130,124,102,5013,114,5251,126,5049,120,104,5491,116,5277,5035,104,134,118,142,5263,5257,5333,146,5203,132,5045,5361,100,120,5423,5451,100,104,108,5201,116,5237,5131,124,138,106,5171,5211,118,5209,130,5131,144,122,5189,122,144,5041,5159,5047,122,134,5413,140,5171,144,5361,108,5087,102,5235,5359,5195,5417,5237,5175,5161,5011,110,5245,5059,124,5333,140,146,5455,5119,132,110,126],"heavy":[11764,5649,3984,1644,4464,1364,2849,2673,8474,32258,2061,1869,1438,1524,2785,15873,1068,4237,1001,2272,1187,10638,2631,18181,1644,1748,3115,5555,1785,1347,3205,6172,1992,12048,10204,1893,4739,2123,1226,1218,1324,2487,10526,2049,1312,1582,11764,1145,1183,2000,1150,15384,3484,3300,1464,1118,1176,6024,6944,11363,1464,1321,3058,5263,15151,2739,2544,1569,2512,1212,1636,1298,1013,1739,1410,1733,2604,2008,1242,1560,2016,22727,2570,2631,7194,1636,1088,1191,1248,6172,2976,3533,14705,1416,5405,1138,2325,2544,2004,1069,1254,2680,4219,1488,1017,1314,1353,1345,9433,2415,1109,9900,1242,1226,1426,2386,2865,2785,4784,43478,1522,7751,2347,12500,13513,4237,1203,1533,6134,1094,1612,1069,1388,3194,1912,1184,1552,1633,2336,10000,1455,1821,1795,2816,9259,1858,1013,1297,1893,1594,1231,2008,3968,9615,11764,13157,1003,2617,1088,1845,1237,1848,10309,1724,2617,5208,1055,5291,5681,1067,3802,1293,9259,1490,40000,2785,1033,1628,6666,1672,1626,1237,1071,1064,4504,1736,47619,1579,1288,1481,2380,15384,2570,1329,1150,3472,1362,5649,1798,1333,1322,1221,2604,2252,2932,1243,1097,1027,1536,1322,4405,1250,3636,1848,1577,4566,1512,1187,2237,3649,1597,1754,1730,8000,1158,1048,2450,6622,2293,1371,2164,4878,2092,1545,9259,1091,1265,16393,1782,1010,5494,2994,1510,1428,1805,2785,10000,2066,1133,1218,111111,200000,1481,1000000,1090,4854,1408,1057,1090,1109,33333,2061,4329,1385,2272,1472,2475,2544,18867,1675,1063,2415,4739,11235,1219,3021,1550,1845,1173,7936,2277,1124,2100,4629,1490,2288,33333,1751,1574,1184,38461,10101,15625,1201,1293,2392,1070,2074,1805,1303,3508,4132,3484,1650,5747,1841,9174,1078,12987,4694,4424,3215,7812,30303,1312,2087,1144,1497,3257,1754,2386,1305,1291,7633,1071,3246,2849,22727,1724,1727,2531,1089,1366,2012,4115,1477,5405,1031,2610,3717,1042,1406,1686,1243,3412,1582,9433,1451,7092,2824,35714,1529,1283,1084,1605,18181,1236,1262,1519,3846,1470,1438,1547,1540,1945,5464,3891,1715,2724,5347,2409,2004,2092,16129,1912,1212,4784,2164,1379,4385,1440,1005,1663,1331,1831,1102,1278,1751,1111,50000,1642,8403,1314,1061,1818,1490,1300,1307,40000,1183,1228,6578,1562,3773,1422,1597,6024,2463,2624,6451,3984,3367,2617,2570,1000,40000,3215,1358,2717,1116,29411,1379,1915,2732,10869,21739,1103,2247,7299,1779,1879,1245,1400,1132,1984,4761,6802,1161,10309,1855,30303,2192,5617,1964,1733,1426,1968,2849,1385,4854,5847,1538,1550,1184,1030,1386,1021,1124,1915,8695,15873,1751,2358,3472,1501,2415,1071,1739,1468,3460,1023,9174,8264,1468,1686,1428,2785,17857,4975,1773,1043,1161,1207,1398,2341,2257,2293,4201,6060,4830,2645,4926,1298,1207,2659,3676,3952,6578,7352,1086,4950,1149,1037,4000,2212,1543,1225,2096,1146,2267,1408,1152,4385,7874,1360,1550,1801,1236,11627,3703,4273,1086,10101,1358,1547,2066,2298,1037,2695,3039,1972,4926,2439,2493,1239,1587,1584,2070,45454,1949,1039,5952,2202,2695,1355,2004,58823,5524,52631,7518,1061,25641,1022,25000,6711,1030,1526,3623,2873,1371,4366,1445,3496,2762,1290,12345,1381,1620,1715,5617,111111,1261,1002,14285,1569,1179,2557,1811,14705,1154,4975,1550,1347,16129,1555,62500,2369,1026,1145,2915,1170,1199,3236,1154,1582,7575,1216,2688,1158,1724,3891,1912,15873,1225,4366,3663,4807,2369,2145,1490,1470,1851,4132,3610,1175,1960,1197,1436,1291,3048,1451,2325,1700,2525,21739,1782,1466,333333,5376,1724,1582,8000,2967,1314,1362,1610,1904,1269,6711,1112,12195,13157,6944,1126,500000,1094,1083,2506,18518,2770,1075,3984,1718,2525,1672,2242,1308,3003,1703,1779,5681,1785,1555,2032,1412,1154,2222,4385,7352,1240,3787,2192,1766,1438,5405,2392,1949,20000,2096,1517,1257,4566,14705,2222,3460,1408,1020,2801,83333,1018,4629,1176,1308,1543,1919,2109,1338,1047,1124,1041,1122,8547,2267,1579,1280,1017,7352,1876,1175,2487,1369,3115,22222,3030,7936,2386,1506,2557,4081,1257,1027,1342,1512,1406,10526,1499,1658,1459,1005,1145,16949,1383,3448,10526,1904,1459,2325,7246,1128,1930,66666,3472,1034,1555,1015,1148,1485,1658,2272,7692,11363,1712,9900,25641,5617,2604,3003,1937,1633,1934,1420,1069,1692,3690,1636,1054,2624,1315,4484,8620,4098,1132,6711,1170,41666,1157,6993,1140,1002,2096,14285,1828,3623,2207,2849,1828,1805,1517,1017,4291,1461,2242,8620,2159,1129,2197,1972,1199,1022,1605,2475,1347,1242,1555,3095,1727,1522,1169,1845,1533,1436,3048,1748,2506,1392,1503,6711,2631,3496,8695,7246,20000,2352,4273,1672,1098,1851,43478,5050,3731,37037,3058,6329,37037,1131,1010,2849,1040,1193,1225,3300,1436,3773,1340,2557,2673,1375,2079,1004,1941,3584,1360,1818,4716,1122,32258,3030,1879,2463,2272,1275,1207,1562,1019,1064,2739,142857,1298,3676,1148,7092,1937,2777,2044,4098,4629,3236,1984,2915,1277,1438,1779,1455,1519,1636,2380,2680,3731,4878,1669,12820,18867,4484,1089,30303,1373,6289,7462,3174,2369,1848,1254,1540,2808,13157,1647,1067,1869,2309,1322,5347,3521,1062,2309,1310,4545,6896,2309,1426,12500,1757,1303,3649,1636,1494,1818,1308,3816,62500,2976,1506,6578,1841,1084,3289,3460,1193,1027,3508,6622,3257,18518,2597,1818,2277,4608,1661,1388,2283,3937,18518,8196,2386,1919,8928,2832,15625,1175,1317,1149,5524,1540,5025,1574,1084,23255,2506,1209,5235,1369,1666,1408,1355,3984,142857,1838,5952,2127,7518,3787,2012,2155,2463,6289,1041,3521,1342,1048,1121,1025,2192,6944,2298,12820,1589,1349,1447,10309,2293,1007,2518,5434,1703,1937,1560,1213,1088,1432,1215,3333,7518,1042,1004,1273,1003,1193,4926,1172,1483,25000,2105,1574,7092,7407,2141,1119,1367,1002,1908,1477,4651,2433,1293,1104,40000,8264,3184,1020,1295,1934,3174,1307,2610,142857,8064,2032,3703,1291,1081,1517,5917,1072,1314,2222,1379,1443,1347,1104,14084,1980,2493,1261,1342,2173,2398,2824,1371,3816,1438,1060,2242,6250,4608,1342,1344,14492,1422,3412,3205,2192,13157,2444,2923,1312,10752,1070,1259,1517,4587,2659,1592,4587,8064,8196,1851,1267,11904,1494,1009,1121,9523,1477,5128,1019,1715,5076,1724,2747,3333,1092,1222,3533,1068,1153,2341,1960,1242,1047,1436,9803,1345,1109,6060,1607,1769,1908,1367,1661,1116,3584,9900,4184,1124,1349,1322,1697,1003,2352,1472,1445,1293,2392,1569,1302,111111,3125,3412,9900,3389,1010,7633,3759,2673,1060,2114,1242,18181,1100,2232,4065,1210,1612,1209,1126,1026,1715,1658,1173,1488,5813,3968,1901,1226,14492,2493,1069,62500,1394,34482,1736,1136,1818,7092,1672,2267,22222,2793,13333,1190,1988,6211,3257,5128,9900,2739,3496,12500,8000,5025,1007,1869,4237,1145,4950,47619,1231,3030,1485,5434,38461,2512,14084,3496,37037,1190,1216,1077,3921,1438,1398,1798,19607,1302,1231,1018,2096,1034,3831,1042,1307,1033,1141,8130,1930,11363,26315,1904,25641,1096,1226,2061,1533,3134,4716,3597,1112,1021,1326,24390,1038,1418,1773,1388,1023,3401,5050,1338,333333,1336,11904,1031,1394,1028,1620,1203,1821,3322,1605,7936,1017,4854,1366,4545,3436,1256,1536,2202,1872,1024,2531,1869,1061,6896,1445,5649,5813,1189,20000,2079,6756,1246,3952,1104,1302,10526,1044,1204,1858,1680,1183,13333,2717,2976,1196,1814,2801,1736,1038,2066,4115,1364,2304,2325,1424,4739,1117,1231,83333,6060,3816,1692,2057,1273,16666,1964,15384,1742,13698,3367,9900,3322,1785,1602,1261,1582,1574,1148,1607,250000,22727,1197,1795,1253,1264,3278,1468,14285,1607,1272,18181,1893,1692,1182,1385,2262,1166,12500,8849,1706,1831,3048,4237,5952,1046,1410,14925,15151,2624,1776,22727,2364,2570,3012,1724,1386,15625,6134,3378,1742,3921,2493,1066,1782,5263,1101,2890,2985,11111,90909,5649,62500,1200,1297,2732,1808,1355,62500,3246,2906,1236,3623,1451,2336,4950,4219,1081,45454,1095,2695,2173,9345,1742,1481,23809,2202,3861,1180,2409,1160,1443,1424,1972,1449,1049,2066,19607,10752,1602,9433,1275,100000,4347,1658,3322,2739,66666,2450,1117,1231,2070,2732,1828,6578,1362,1212,1364,1212,1239,2915,1124,1190,1194,1324,2923,12195,1461,1253,1089,2415,1926,7751,2109,19607,27777,3086,1824,4694,1029,1510,2469,3215,20833,18867,1041,3289,11764,3676,1524,9900,14084,4975,1644,1572,1506,11904,4329,1035,1506,2222,2217,1297,1479,3546,1811,1016,13888,2123,1063,6134,2000,6250,1862,2610,1199,4149,1225,1579,1700,6289,1245,1388,2702,55555,5347,3424,1277,1587,5813,1187,1049,2227,1228,1101,1023,2272,1700,1821,17543,1203,8000,2433,3322,3134,1055,1466,4000,1526,1071,1712,1265,1356,2061,7518,4385,1161,1782,1364,1226,3067,1077,1724,1879,1028,1109,2710,83333,4444,1529,2427,9090,2403,3546,1879,1607,2096,4385,1485,1295,4950,14084,1564,3125,2645,1138,1788,3861,12500,1102,1317,6849,1461,1020,1862,2673,30303,2801,2070,1597,3802,5649,2570,2044,1009,4347,1077,1100,3984,11363,5524,55555,1111,1231,1177,6060,1587,1436,9708,1022,2915,27777,2457,5649,2557,1858,1031,28571,3030,5780,1117,3105,1572,9174,5952,1547,1275,1204,1377,1760,4975,1360,35714,6535,1438,333333,14925,10000,1760,3424,1689,1149,3623,9174,1014,5376,1055,1680,2994,47619,1976,1182,1615,1567,1392,1655,1261,1206,1340,1016,1492,7575,1904,2506,1785,2032,58823,2433,12195,1805,1116,1189,2087,3861,2267,1004,1890,1088,1828,1801,5917,1028,18518,1394,3816,1335,2247,7575,1024,4716,1692,8620,1587,1189,2096,7194,1426,20833,8403,1108,3508,2717,1694,1392,1824,1245,1000000,5780,1686,2631,1402,1577,3105,2100,1043,1176,10416,20408,1040,21739,17543,3164,1009,1547,6060,1005,1481,1196,1069,1168,1872,1108,1253,1135,1428,6097,2551,1392,1342,2325,2544,1392,14492,1005,1277,1795,1445,4255,12987,2336,3663,1461,45454,1879,1587,1328,1582,1282,1865,1453,1153,2173,1152,1291,2724,2463,11494,5617,1033,2932,1000,1700,1261,2272,1351,5952,2631,3215,1474,1776,2551,1013,2409,1945,1223,6172,1519,1453,4566,1436,1639,2369,2237,6493,1876,1070,1440,1077,6250,1623,2816,55555,15873,2493,12195,9090,1529,1715,5524,2747,3194,1240,1960,1012,5464,1390,2227,1097,2906,1006,2197,7518,1076,8000,7042,1416,1226,4032,1949,1394,1545,2898,6944,6896,4385,4201,3355,3952,2096,1788,1683,1976,1524,2732,9900,1052,1655,1029,1209,1014,1776,2421,4098,1628,1706,1308,1342,1062,1262,2777,1273,16393,1013,1102,5617,1675,2439,4329,3937,1503,2032,1474,1126,2183,27027,2801,333333,5952,1201,1083,1620,3174,1760,1148,1451,1689,1132,17241,14705,1078,8474,2538,2463,8130,1303,1692,200000,5747,2105,1278,1149,2369,1233,3184,2421,1697,2061,1526,12658,1014,1312,1420,2141,1338,1661,1703,3236,1396,2105,4901,2840,1028,7299,2732,1579,4566,1930,14285,1152,5128,2325,33333,1303,3076,6097,1312,1149,1515,1358,1002,2617,1180,1145,1912,1282,1522,4694,14084,4081,2252,62500,1088,25641,1028,2680,1002,13888,6802,1451,1050,1524,3649,2277,2906,1221,1342,3636,41666,1223,1027,2192,1697,5076,1001,1956,5405,18867,1051,1355,1113,10416,1567,3787,14925,7692,1225,1798,1219,2188,1494,3676,3558,1135,4347,1739,6060,1418,333333,9259,13513,2873,1242,1470,1136,1333,1221,1256,1600,3236,2747,1149,4807,2164,1398,6849,20000,2212,15151,1148,4504,1021,5747,1795,1655,3030,3861,1886,4739,1157,2341,16129,1818,2857,1474,2331,1222,2352,1023,1481,1748,3558,4032,2347,12658,2392,5494,43478,1562,3367,1044,1375,1557,24390,1280,1180,6097,1396,1492,1683,1228,1176,1158,1742,5319,5102,1156,5102,1694,1000,2127,11764,1592,3030,13698,31250,4347,9523,3496,1101,2347,21739,1183,1096,166666,1117,1079,2754,1031,14705,1577,24390,4237,1926,1136,4716,1043,1680,35714,8695,3508,142857,5714,7092,1053,1097,1424,1886,1344,20833,1098,7092,11494,2604,2801,1461,1703,1016,1082,1529,1461,58823,3344,1137,7142,1116,2840,1135,3584,1218,1173,3846,2040,1278,1322,4807,1038,1191,142857,16393,5376,1394,2403,6622,1315,1328,1893,71428,1893,1669,1733,1144,2463,1191,1577,1984,6802,1122,1736,1754,1107,1062,1242,2040,1392,1288,2000,1949,2304,9900,1176,2380,22727,1360,1349,2785,4065,3236,7633,1428,1194,13333,1103,1328,3558,1069,1060,1883,1623,3663,1206,1034,1964,1519,12345,1273,1908,1831,6849,2652,1109,2227,9708,1597,6410,4132,1515,1808,1040,3623,1004,20000,1672,1019,1838,1712,3424,3095,2638,1226,1206,2702,1466,4524,1733,6329,1297,43478,5952,1745,2762,1647,2293,1984,3311,2808,1180,1131,2169,16666,9708,3546,1251,11627,2159,1879,10416,1788,1009,1779,1923,2096,3278,1422,5000,2132,12820,2257,14925,2557,3663,1269,1038,1808,8695,4000,8064,1001,1519,3717,1494,10526,1209,1037,2105,500000,6451,1250,500000,1023,1730,2118,1221,1315,1117,1240,1390,4444,1162,1317,1552,5681,2237,10204,1013,1540,1057,3300,1122,1968,1183,2645,1461,3105,1189,6944,66666,1197,4739,1137,8000,1262,1937,1338,1472,1945,1490,1672,5882,2415,1168,2164,2932,1694,1388,4237,3257,11764,1785,1449,1644,1449,1760,3076,1845,1577,3496,7518,1088,2754,2079,1709,1956,2016,1321,17543,1215,1011,50000,1488,1375,5524,1386,8196,3584,2109,3311,1724,2262,1013,27777,3436,1058,1035,2475,21739,1034,3333,1027,6622,1219,333333,1453,4081,1533,7142,2688,4950,43478,1153,1025,1166,1697,9900,100000,2538,1436,14925,1355,34482,2272,3984,4629,4504,2369,1121,1503,15873,1149,1587,1745,1461,5405,2032,1526,3086,1145,5649,3278,4347,2375,1315,1552,2824,90909,8771,1335,1805,5050,1129,1102,8196,10638,1739,9090,2597,3521,1547,1344,35714,14705,2673,1239,1709,1432,7633,1369,2808,1369,4566,13157,1788,4201,2092,1724,1386,1831,1267,1481,8474,1137,1331,1144,1984,1230,2500,2659,9523,2717,2840,3125,1451,4761,1335,1310,2096,21276,1443,3012,1386,1064,2777,111111,3533,3257,2512,2403,1094,1218,1037,47619,1153,1165,7874,1689,1009,30303,1669,5681,1968,1177,2109,1041,4166,1769,1113,1347,2906,11363,1050,1186,1149,1481,3058,2036,2624,1503,1023,3355,1964,2398,3436,9433,33333,1560,3861,1019,1394,5050,38461,1044,3424,2347,3289,5128,3773,1256,2032,13157,111111,4504,1956,7299,1552,2551,3759,1053,142857,1154,7812,14925,2638,2865,1236,2610,4444,1131,1858,1179,1298,4672,15873,9803,1282,2150,5917,1001,1555,1243,26315,1383,3225,1524,2506,1164,1136,3802,1862,2985,1865,1066,1290,3521,1669,3021,1009,3058,1182,2439,1584,7299,32258,1324,1088,9345,1694,2096,1221,1089,1127,8849,2538,4587,1207,7518,1055,7575,3731,2770,1137,3816,2481,1172,1644,1715,1949,2463,3773,4807,31250,1953,8695,1503,1027,1012,2092,1897,1076,1145,1193,1231,2688,8695,2061,37037,1180,2364,1164,2570,2624,1497,3816,2673,2865,3378,2409,1213,1848,1237,10869,1007,1002,4201,1016,1432,1003,2890,6289,3448,14084,1025,2403,1250,1718,1869,2932,2840,2898,2941,1366,5681,1369,1038,2890,10000,2857,2985,1132,6849,1592,4273,9900,1072,41666,1077,6250,4149,2506,2444,1213,2178,3125,2392,1451,1572,12820,3584,2028,1492,2915,1029,1200,1179,3787,1642,1672,1721,1464,12658,1213,2053,1457,1377,3389,1103,1114,2049,2232,12345,9803,52631,7812,1328,1146,1432,10416,1206,2597,1310,2770,4016,6578,3690,6410,9523,1432,4950,3355,1669,30303,4807,2232,1400,4694,1477,1200,1222,1273,1074,1373,1345,1422,10204,3030,1307,1440,1773,1068,90909,4587,1392,20408,1194,1022,2624,1161,1020,3802,8849,1481,3076,3906,3355,2544,1373,7042,3174,4524,8264,1182,2906,3802,1607,1113,2100,1572,4464,3731,4608,1123,4347,6369,50000,1547,3367,7936,7352,1872,2915,13157,1394,9259,1165,20000,1317,1522,1501,1360,2057,1703,2083,1113,7042,1818,1226,3322,9523,10309,4115,7299,1736,76923,1344,3584,1579,3086,1828,1912,1457,1303,4651,1085,1153,13698,5494,1177,13888,38461,2398,2145,1160,1564,2538,1464,1138,71428,1113,200000,1893,2890,1443,4424,3289,4761,1371,2283,3086,1792,1472,1742,20408,4032,3134,6097,1838,1949,4545,1600,2597,1607,8333,1010,1069,1545,2040,1745,1633,1256,1663,1161,1451,1733,27777,8620,2415,11904,1555,4694,4237,2702,6060,1408,1610,6756,1769,1579,1256,1926,1831,5988,2732,2849,1253,1897,3184,2506,1351,1166,2801,1094,1077,2881,1636,2066,6369,1122,3676,1808,8474,3424,2164,2127,1808,5882,1769,2087,1669,1029,38461,1064,7407,3448,1277,3267,1494,1757,1574,1040,2114,1858,1351,7407,1201,1607,1949,1078,166666,4273,1727,1253,1138,17857,3584,1953,6711,3300,1408,1769,2724,1000000,1145,2237,3717,4201,1302,1964,1461,1362,3846,1470,3311,2645,15625,1010,1041,1447,3367,2849,2242,2544,1190,1506,1057,1054,3759,1017,5813,4366,1117,6711,1118,8547,1862,12658,3787,6134,2178,4566,1109,10204,2237,3012,1512,1157,4291,2247,1492,3546,1186,2028,1472,12345,3144],"huge":[988593867,3739766663018,59488214113,4029106575221,7386769890489,8342644389295,5056690221975,8708501004734,2766642416592,6311744365694,3938352558511,7054023645485,8014546703193,420745860741,8440677464399,4913170029585,3370394653393,8579909150462,5550850762702,7327645235983,4270605164149,1145727515367,4375954686872,2330908651446,4465525133473,7104301270440,7244151398690,2550310143304,4699073690083,4135313772168,5992056739485,644075384358,4769977502984,5053043412408,8595091592729,8346811984950,3920480009495,6345184917475,8462163317713,4117887079205,417138152945,1418788931216,8776271632601,1975929641305,4063004536594,7601867876902,3151723951663,8438046067664,1038289546770,7932614081103,3692917084205,7975557448780,734340235628,7878726458129,6126992503651,5019553909668,1708352826422,527088525001,4788999240083,8457683751877,7787090644056,7209383713813,4333024601670,5930685023740,2453339530982,3352111945489,5664565208666,7675641019827,1837389873031,1640467580876,4581151075870,2964933537014,7873200358648,3259510353742,4108281387633,2933640071472,1438277906611,8346722440913,8394259479979,7274309839478,6972942162905,8391661129648,4994618308403,3106742527118,978604439019,3195295495999,617725232304,8255392242573,6636798976186,2419557842475,8749437559266,8490228433100,4250784450548,3154280627307,8705956783970,3099794465374,8636370734324,4219056088430,2114729163491,1431841154594,5501454316463,6653047666863,3907728571487,6547040981205,6185238740819,1873754933236,6595871730038,5941280173646,3718322775278,2880589953191,319093249801,991299494110,271850940738,7541955959217,5058153985931,200964912915,7482717944811,4310163967148,1936544206969,2844905285914,1818745121410,7637304978842,7293983511741,6662800731630,8504923152069,1095856669276,7189935560929,6733229259727,4072431123703,5235843291461,1250718157256,5829664731527,7934494247776,6489499198646,8151198622918,8471874720085,6704091717654,4949165474816,8076306511383,8548972992505,7567370983257,413785848047,6725409569375,4840175685436,7093669354053,4204253524123,63741580469,7033309335160,2072518646733,4581555638256,4901355940166,4838565003051,8509257929917,8029902714875,2798914991447,7232820972440,976942482676,2135849949501,606532911836,4552587625956,5165348240371,2472071888759,1584067476599,84402433134,1598628830017,8084159805331,607214017641,2245755844523,1829921004713,1750574175649,6696431212149,4604732274407,6557057933758,5928428182961,7862492283124,5339534422231,1548468420239,5716558554898,1763844920729,5173777031750,4718101277698,7822295437889,138065995262,5941168534487,7125611268212,6756136169867,1504297062777,2175634599638,3503032553517,7588301479994,7995277773584,2676009290068,3018354630473,511426672251,5239807327659,7880701736663,4718691651336,1135811885727,727622789930,280133432335,2725893254754,837664561965,8062421269527,6797458592173,8261920464217,5101215097298,2925990152616,1604945777553,5146373680230,1344747651916,5943484249159,4558259558161,6276289613877,7740219084837,5268405057175,7335186826056,7670981939829,5637496684491,2872560578366,8557245454645,2267096856587,2839026590398,8319382052093,8060227198889,6440546924591,2528863653969,7394624491380,463957459024,837676574370,8642328211058,1521327477489,6214114324795,5944546339114,3049785279972,4772425616316,80635831378,4499049055112,6856371509609,2113124569249,3141436420923,4429895222604,2450954560618,2576447439808,8531230568314,4343742886919,4343571964166,4889089894262,2082517781432,3464671709292,3051421802137,4604042282208,8434841623505,4686239824527,358292483670,2017428472248,1743959126279,4040788589643,8339275135107,749060691953,5966258690989,4391622443400,2165209942300,1671770989838,2700039851688,1913250355641,4327199843162,6238779769864,301560190517,7874027788817,8015956972617,6923198516380,8549484253068,5858056967801,6265559492394,8755927080645,5086407022525,608874122140,3215832848689,6813834863438,7112518935202,8094898673909,13633989218,7215281898760,7569420728867,2599998525392,1873441377516,256255188989,2387246653393,5964473218283,6166083142432,803782845198,8631472934412,5291854508475,4567711243493,5121683313109,5784817393605,6947351873292,4575873078433,3277534028806,3316031016348,5628371374393,2189547791230,6603774696017,397247577295,54865843237,805069229528,357653458154,6355417433696,1018670471133,2082243647049,7824184089449,3344122119008,7015789558351,800353447046,1420552054786,5923061440401,3891136527607,6076948161890,8254688078850,7830478331409,8106280333146,4059860875531,5387860494380,3331556717587,7401707172359,8100682216595,6512193585091,4120162889094,4719512833580,5590738534335,6405800630153,5344259312344,925096968504,6387591660060,5988432811665,1635316524894,2225137584272,524380420131,6007546489546,681842438046,7032153178079,7836293986383,7358856151068,6720916438371,7854537178719,8366616675184,1138651737834,5868675523870,8660501197199,7936491945095,6163309047178,7632790441012,478977635499,4596933892553,8353331615185,1970281451216,4098036777068,997376316461,3335032161924,8039083252537,6147957505323,5470331663055,8463361919716,402865822409,7370410203881,1496513143334,4670050593614,1740196474030,7131611263615,6110918283729,3157089051326,3534023881679,8234845216866,8769970130756,5659310404347,311627900806,1271245865608,2764253136184,5328080508297,2810391029182,7038773365088,1943979701123,1123792294873,1072175573582,7571759485069,1145008373011,4846579255848,8337223828540,7283610468412,7322983886246,7960978474164,680092977972,1748857651360,3002986258141,6332637587922,924319804430,4057342814114,6983837512311,6555607165486,6267032802361,709271682607,2859189362549,5230074420133,4256477515080,5285249246838,2384416957750,1313761162311,5530445489106,7507150745819,5029573701442,1588560853423,5877997039310,1222416349971,3067527110097,7967186879872,1298872446282,8116750176301,8072545309473,2913892546683,6979656531755,7045477184163,88671867925,5378479363489,7891855844410,6576984894866,252389669259,512776793614,42677898448,1818911418650,6868563408003,1290080267370,5921957597917,3384271066213,1109050489079,1953939324351,6895615687571,5308813340398,5150673203391,6577034734874,2658222487171,6448742563880,2316481155221,2927390240995,7616069161357,2566776712454,7908365825375,3085797264792,1480432085518,2477396649358,3529011287917,3766398524809,1376432442436,4879711459197,6772915360621,2212033017619,1272559208200,4587906660543,3608385988874,166012584432,352699676831,4726003731552,2253548093167,8533558754255,2378442535823,3193530340317,3372867291368,4791239332325,2240062030240,8458863003748,3138049475888,8064414599444,6260501036811,2073631756633,5536054683946,5589645274402,6409640805840,5995447806102,6004553348175,5733576707288,5210356909968,2450640738593,5020072245751,340695539862,5816546587415,8027237812872,6102794157579,6821430394615,4320353530419,1215873264410,3885592307094,2334761507349,5728848609223,5678717435639,4722427382196,5541481705671,3674724055539,592958730627,224186480867,2511095840667,2945342411113,3372057666762,894272929055,5019956398972,3544729912800,6056159855644,7536977079670,2217124281979,742285101628,4357151446893,677245136714,5018150084826,4313201347928,7816455245981,8497252277057,250999171958,3760802018379,3906468330115,7286603818730,2281333231849,3947929595662,3154088768883,8240937931481,3804204233695,5504670752541,3583818019635,1718199061072,825681794949,1388365996286,382289466277,8087722642780,5463101682627,2512592605961,4827111109603,976273831751,5043744500118,8328091438824,6258545518110,4435922041067,3100394245724,2403847743450,6871325138509,2660057283663,7055202217254,3332684347255,260825851626,3115489483341,990392405964,466079643695,6520532361278,2021141157481,5037069675514,2786952086688,1517500358942,6193250867273,1599105180042,4689892857437,772035563132,6678464235472,8133993143385,4778822287380,991233167773,5866296125426,8153733195246,7672899024571,1420018256624,6544333212288,7419801147849,1905575416405,3286206071380,8507847201629,1505305641812,6880265178160,3196179501645,8105250810246,7140391098265,7709788763015,5313832608174,1145307249179,1681481408930,5238792164745,2858338304980,8129283297457,6128412011810,3171767669085,8398157316848,1912879544343,4019973890522,6347666958066,5127454064960,2869810602154,7754719143034,1513219305058,1852474319150,8502316005479,7187757579211,7152953955449,7201088259308,8523507505713,979760145430,6205864191306,3526488351070,5127091103514,2941412371822,7179028920191,8024372370901,8522492273150,4750128528275,5697342210288,6557609955377,6186803860465,7055041881150,4389303348702,4569246569753,68676396778,7747288622638,3474361962949,4616808321925,141757265475,8200609293942,1037053940661,1065357763670,3924772653792,2449212712770,6456605010526,3612517248234,6071938870310,3945602102115,5153001184481,4601650764837,7343026009935,7844177417865,871906339502,7281783480802,7093374771542,7576757571352,4912940134611,1069366305582,3907067176350,1009198841603,2273963211855,33274332384,5296308971990,8482786977810,5684020010981,5596332701379,3763065471716,7612525473286,7431116066953,2730382669779,6440065846628,5694964012265,6108773332027,5171211830248,4038541651314,5430417031488,8088571774760,2471163055292,1694266020349,6638254312612,2301211090409,4697740522107,1354705050473,2811853740209,7276516758309,7950095247782,3101392266790,6598958995893,5433105909136,5941936160743,199084119401,4655884973547,4046872196627,2933982093149,355743928280,2141394862408,4382331630879,2289098305090,543615291376,2108060037815,4962018301289,3972471479301,342833969139,3487459011606,3505619256076,509589958362,4540617928370,7918106459843,7082917665961,4178976163388,2557121500441,8034654649287,3481075602533,3235430574858,2837605980145,1317691406451,1733162420348,1842392511083,5628459496771,6443253189908,1221541017675,4794861305876,1154509394421,6073684176026,232318538809,8025629596881,7637408977097,3518148640767,7181033127643,8012356143684,233278531518,1588836048930,1569891343629,1883536009194,3749166803990,5212891416744,1648501552535,5360931501743,5263938208428,2822211813099,5893674255812,1513446258387,4011738850382,4658199860625,1439581449178,901143733666,2429123621071,4306371296179,3578489264461,8376573201080,6761000304728,7602547286550,763234628931,4061148255433,5958377234261,2177779326005,1478035287114,1130722514181,1349242618376,3017700429668,4116870384012,4116723486068,5821857577995,511162919666,1304244138019,3531053998978,5613129365839,5314266484596,4496599774135,2976336435101,4148699860096,1241048872047,5276594452635,6821284352945,6067008409112,3441797063030,7177178387121,6658280101561,1395910585362,3927198114810,5179517523157,342353788419,6696859614979,7691873531451,2544772154328,1257616137269,4550493276304,825986370044,7294386931064,8544059808357,7897540534674,8303817888217,4430516946488,6073897719873,1744254286467,696162785425,3398380205035,4872134061342,1644087686195,3405439025083,2560662918773,3060581793343,6961331227340,2074063596846,8789081354535,4587600942385,6443294745782,3227588006857,2501095423607,4000340602390,8607110310922,7960145263127,4440561533308,7151090615156,5216731756311,2307598768557,5174149096730,5085828939888,7888709442453,4230506442070,680791877050,301110651262,3766463851478,4529837077831,6485274539971,6532801438962,4923499710264,617168581075,7773595203095,6681913704828,7517639279488,1148154705319,7189696500965,3989707989514,5533798088988,2214765806916,1245658439798,7882762998941,7460887386285,6058475594898,4970811676525,6224997719153,3531155768464,1729538172962,2818211649553,6315910793704,2745792712700,2630240307360,1523146566082,6062325386244,6086044354608,7532321203526,6171846581137,6331744699980,1804234587183,2090635028718,8264257118658,3544795604102,431068379863,5341642582870,6554337722251,6562323259173,5004088147543,2828422472158,6621396352931,6587252989437,3322368526728,3683129690672,1721090461636,8455015760736,2145797404639,5791133796240,3983269250452,2592636728079,7152094626036,904813728157,3661617006104,1221320402406,2941664267688,1746105434087,2091912756087,8368752494672,7479710799893,8685900337169,2525373341617,6039334295041,4590875465733,6178102004020,424036255868,145549014619,6514177684467,3137746637921,2242226102642,7367687923390,2030758341138,3075166790550,7306421689013,135563817915,8303896022108,8201988102501,6020834515773,8789453583395,4963237330935,1243469965662,7795934141993,3085292297221,3493440972014,1989848945621,7458741955234,8049465366625,5925719754428,269326791179,48030989803,5131273030476,2497017494899,668702887108,6091622391761,4110774781012,147005158155,6434979101259,6443321093590,4499310288041,1874047381210,3120356600818,7232581342993,7002031430025,5184819955104,1209338975109,5128355806519,2417164452776,7567462534824,4832981221904,2857510678154,3363148493911,1848153675953,2250594836093,6714380325905,213223137646,1065228454148,6478907904017,7072268030049,283947870666,2134621734380,2892052917398,8290022755784,7028601181293,3503765908366,7803560712045,1791631735083,668816824685,2795451424313,7168629422531,7407494857112,6003041706674,3133893892637,984241018209,2599732675144,6632937303538,730547478622,720426417195,4747733462902,4434302795952,4102752166238,8711589806964,2332612409483,7542880754597,5718462549724,5710552733561,2127907787521,4258601813804,2274232233182,4223102888130,3943709955813,1778093873781,6889674344824,8785353215416,548302603804,8467114247524,5609689045085,6372322390067,7797338106134,487363904112,4790254839554,7494106405069,8684800476968,2210492074372,6054494724126,6324300069515,3084238683959,5410993825238,2896786132616,8068612033552,6564666567142,3768762711091,1148875732295,6810022223689,7790390945730,8188628161629,3237981178748,2812498599853,3220078254573,637580782698,7928430855392,4074635350805,6083095641107,5932412056835,6654128918488,3324179171404,3123843918262,8443141949344,731041359710,6968287876397,3627004999043,1722808194541,3410892230737,1909752975965,2631043790563,5123733665075,8000292333906,7239360632909,8789678041650]},"layouts":[{"options":{"lowest":1,"highest":9007199254740991,"figures":3},"histograms":{"empty":{"count":0,"exceeds":0,"min":"9223372036854775807","max":"0","mean":"NaN","stddev":"NaN","percentile":["0","0","0","0","0","0","0","0","0","0"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","1"],["75","1"],["90","1"],["99","1"],["99.9","1"],["100","1"]],"percentiles":[["100","0"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[1,"0"],[2,"0"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[1,0],[2,0]],"linearStep":1,"linearBuckets":[],"logBuckets":[],"logBuckets10":[],"skewness":"0","kurtosis":"0","meanCI95":["NaN","NaN","NaN"],"meanCI99":["NaN","NaN","NaN"],"percentileCI":[["50","0.95",0,0,0],["99","0.95",0,0,0],["90","0.99",0,0,0]],"export":"ab00010101021b001fffffffffffff03030400051b7fffffffffffffff0600070008fb3ff00000000000000919b0000a80"},"one":{"count":1,"exceeds":0,"min":"7","max":"7","mean":"7","stddev":"0","percentile":["7","7","7","7","7","7","7","7","7","7"],"percentilesAt":[["0.1","7"],["1","7"],["12","7"],["25","7"],["50","7"],["75","7"],["90","7"],["99","7"],["99.9","7"],["100","7"]],"percentiles":[["0","7"],["100","7"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[7,"1"],[8,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,1],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[7,1],[8,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","0"],["4","0"],["5","0"],["6","0"],["7","1"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","1"]],"logBuckets10":[["10","1"]],"skewness":"0","kurtosis":"0","meanCI95":["7","NaN","NaN"],"meanCI99":["7","NaN","NaN"],"percentileCI":[["50","0.95",7,7,7],["99","0.95",7,7,7],["90","0.99",7,7,7]],"export":"ab00010101021b001fffffffffffff0303040105070607070008fb3ff00000000000000919b0000a820701","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["7","7","7","7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]},"two":{"count":2,"exceeds":0,"min":"3","max":"9","mean":"6","stddev":"3","percentile":["3","3","3","3","3","9","9","9","9","9"],"percentilesAt":[["0.1","3"],["1","3"],["12","3"],["25","3"],["50","3"],["75","9"],["90","9"],["99","9"],["99.9","9"],["100","9"]],"percentiles":[["0","3"],["50","3"],["75","9"],["100","9"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.5"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[9,"1"],[10,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[9,1],[10,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","1"],["4","0"],["5","0"],["6","0"],["7","0"],["8","0"],["9","1"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"]],"logBuckets10":[["10","2"]],"skewness":"0","kurtosis":"0","meanCI95":["6","-32.11861420852408","44.11861420852408"],"meanCI99":["6","-184.97022348861458","196.97022348861458"],"percentileCI":[["50","0.95",3,3,9],["99","0.95",9,9,9],["90","0.99",9,3,9]],"export":"ab00010101021b001fffffffffffff0303040205030609070008fb3ff00000000000000919b0000a8403010601","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","3.20631895627745","3.614082426485135","4.244224543345755","5.066378477103939","6.000000000000661","6.933621522896061","7.755775456654244","8.385917573514865","8.79368104372255","9"],"densities":["0.4846864379515554","0.24524021229529785","0.15869436008848586","0.12163172356651711","0.10710978436929422","0.10710978436944585","0.12163172356651718","0.1586943600884858","0.24524021229529752","0.48468643795155636"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","3.354130868400228","4.141148961265419","5.326286696880615","6.673713303119386","7.858851038734582","8.645869131599772","9"],"densities":["0.4034021193986685","0.18151697419949023","0.12054054019551301","0.10602220721759137","0.12054054019551305","0.18151697419949037","0.40340211939866866"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","3.080949106595003","6.000000000000661","8.919050893404997","8.998766051082084","9"],"densities":["0.6176720423877602","0.15415969657006728","0.1541596965701371","0.6146886166679947","0.8104063186740486"],"corrections":0,"bucketCount":2}]},"three":{"count":3,"exceeds":0,"min":"1","max":"1000","mean":"335.3333333333333","stddev":"469.99314415803514","percentile":["1","1","1","1","5","5","1000","1000","1000","1000"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","5"],["75","5"],["90","1000"],["99","1000"],["99.9","1000"],["100","1000"]],"percentiles":[["0","1"],["50","5"],["75","1000"],["100","1000"]],"cdf":[[0,"0"],[1,"0.3333333333333333"],[2,"0.3333333333333333"],[7,"0.6666666666666666"],[42,"0.6666666666666666"],[100,"0.6666666666666666"],[999,"0.6666666666666666"],[1000,"1"],[5000,"1"],[1000000,"1"],[1000,"1"],[1001,"1"]],"countAt":[[0,0],[1,1],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,1],[5000,0],[1000000,0],[1000,1],[1001,0]],"linearStep":50,"linearBuckets":[["50","2"],["100","0"],["150","0"],["200","0"],["250","0"],["300","0"],["350","0"],["400","0"],["450","0"],["500","0"],["550","0"],["600","0"],["650","0"],["700","0"],["750","0"],["800","0"],["850","0"],["900","0"],["950","0"],["1000","1"]],"logBuckets":[["1","1"],["2","0"],["4","0"],["8","1"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","1"]],"logBuckets10":[["10","2"],["100","0"],["1000","1"]],"skewness":"0.707068367939628","kurtosis":"0","meanCI95":["335.3333333333333","-1094.5902216930783","1765.2568883597448"],"meanCI99":["335.3333333333333","-2963.0427997930706","3633.7094664597375"],"percentileCI":[["50","0.95",5,1,1000],["99","0.95",1000,5,1000],["90","0.99",1000,1,1000]],"export":"ab00010101021b001fffffffffffff030304030501061903e8070008fb3ff00000000000000919b0000a86010104011903e301","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","5.932607603243072","22.700394847776252","62.922989940120665","139.7725442831563","261.92592592592587","426.13620879717644","612.9672969674574","790.2991406883924","925.3771141512873","1000"],"densities":["0.0202732526167807","0.005963816128010755","0.00248616479793053","0.0013012437203425678","0.0008186429115195856","0.0006089752617892097","0.0005352428280504276","0.0005639145113574124","0.0007403131497784085","0.0013400714655117433"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","11.075754645242279","55.227569135094825","169.9105532246607","375.7563621894388","639.7111592472341","874.319812866049","1000"],"densities":["0.014178307023841562","0.003235589397803251","0.0012456699133811633","0.0006940007356748608","0.0005412182102750836","0.0006089167669375607","0.001136671945792733"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.5568893265228576","261.92592592592587","970.4876086277768","999.5554699188299","1000"],"densities":["0.03211532068992312","0.001734979648688859","0.0006350893803403017","0.0016857105347162903","0.002249566547595048"],"corrections":0,"bucketCount":3}]},"four":{"count":4,"exceeds":0,"min":"2","max":"64","mean":"19","stddev":"26.095976701399778","percentile":["2","2","2","2","2","8","64","64","64","64"],"percentilesAt":[["0.1","2"],["1","2"],["12","2"],["25","2"],["50","2"],["75","8"],["90","64"],["99","64"],["99.9","64"],["100","64"]],"percentiles":[["0","2"],["50","2"],["75","8"],["87.5","64"],["100","64"]],"cdf":[[0,"0"],[1,"0"],[2,"0.5"],[7,"0.5"],[42,"0.75"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[64,"1"],[65,"1"]],"countAt":[[0,0],[1,0],[2,2],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[64,1],[65,0]],"linearStep":3,"linearBuckets":[["3","2"],["6","0"],["9","1"],["12","0"],["15","0"],["18","0"],["21","0"],["24","0"],["27","0"],["30","0"],["33","0"],["36","0"],["39","0"],["42","0"],["45","0"],["48","0"],["51","0"],["54","0"],["57","0"],["60","0"],["63","0"],["66","1"]],"logBuckets":[["1","0"],["2","2"],["4","0"],["8","1"],["16","0"],["32","0"],["64","1"]],"logBuckets10":[["10","3"],["100","1"]],"skewness":"1.1249561006949362","kurtosis":"-0.6915329232082903","meanCI95":["19","-28.948388276616278","66.94838827661627"],"meanCI99":["19","-69.00217210471634","107.00217210471634"],"percentileCI":[["50","0.95",2,2,64],["99","0.95",64,8,64],["90","0.99",64,2,64]],"export":"ab00010101021b001fffffffffffff030304040502061840070008fb3ff00000000000000919b0000a8602020601183801","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["2","2.1214881848725766","2.59375","3.8766929625087334","6.718750000000001","12.088759862808779","20.781250000000007","32.65059402055783","45.90625000000002","57.34108084774391","64"],"densities":["0.8231253113616391","0.21174695221339987","0.07794578786609092","0.03518578222774577","0.018621939727256866","0.01150418331476101","0.008425065431316082","0.007543949552937039","0.008745210255535187","0.015017452189086762"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["2","2.260178895464949","3.6186449301918753","7.9516758986192855","17.940490621956265","34.523454456253454","52.88675172594847","64"],"densities":["0.5490727547361286","0.10516062912523202","0.03296933345228088","0.01430171114530577","0.00861469302379368","0.007779493015826749","0.012854670329889233"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["2","2.083206032793557","12.253877226493122","61.37877112059458","63.96152982082045","64"],"densities":["0.6009179661774692","0.04424486756378103","0.009160325129048932","0.01897196203257965","0.025994160186590367"],"corrections":0,"bucketCount":3}]},"constant":{"count":100,"exceeds":0,"min":"42","max":"42","mean":"42","stddev":"0","percentile":["42","42","42","42","42","42","42","42","42","42"],"percentilesAt":[["0.1","42"],["1","42"],["12","42"],["25","42"],["50","42"],["75","42"],["90","42"],["99","42"],["99.9","42"],["100","42"]],"percentiles":[["0","42"],["100","42"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[42,"1"],[43,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,100],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[42,100],[43,0]],"linearStep":2,"linearBuckets":[["2","0"],["4","0"],["6","0"],["8","0"],["10","0"],["12","0"],["14","0"],["16","0"],["18","0"],["20","0"],["22","0"],["24","0"],["26","0"],["28","0"],["30","0"],["32","0"],["34","0"],["36","0"],["38","0"],["40","0"],["42","100"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","100"]],"logBuckets10":[["10","0"],["100","100"]],"skewness":"0","kurtosis":"0","meanCI95":["42","42","42"],"meanCI99":["42","42","42"],"percentileCI":[["50","0.95",42,42,42],["99","0.95",42,42,42],["90","0.99",42,42,42]],"export":"ab00010101021b001fffffffffffff030304186405182a06182a070008fb3ff00000000000000919b0000a82182a1864","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["42","42","42","42","42","42","42","42","42","42","42"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["42","42","42","42","42","42","42","42"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["41.5","41.55","42","42.45","42.499","42.5"],"densities":["1.0000000000000568","0.9999999999999937","0.9999999999999936","1.0000000000000113","1.0000000000023315"],"corrections":0,"bucketCount":1}]},"uniform":{"count":2000,"exceeds":0,"min":"3","max":"9999","mean":"4966.017","stddev":"2878.915686975046","percentile":["9","93","1149","2457","5019","7343","8959","9879","9975","9999"],"percentilesAt":[["0.1","9"],["1","93"],["12","1149"],["25","2457"],["50","5019"],["75","7343"],["90","8959"],["99","9879"],["99.9","9975"],["100","9999"]],"percentiles":[["0","3"],["50","5016"],["75","7340"],["87.5","8712"],["93.75","9288"],["96.875","9672"],["98.4375","9808"],["99.21875","9904"],["99.609375","9936"],["99.8046875","9952"],["99.90234375","9976"],["99.951171875","9992"],["100","9992"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.0005"],[42,"0.004"],[100,"0.0125"],[999,"0.106"],[1000,"0.106"],[5000,"0.498"],[1000000,"1"],[9999,"1"],[10000,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,3],[1000000,0],[9999,1],[10000,0]],"linearStep":499,"linearBuckets":[["499","104"],["998","108"],["1497","93"],["1996","109"],["2494","96"],["2994","84"],["3492","110"],["3992","90"],["4488","99"],["4988","99"],["5488","97"],["5988","104"],["6484","105"],["6984","128"],["7484","96"],["7984","96"],["8480","92"],["8976","95"],["9480","98"],["9976","96"],["10472","1"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"],["32","3"],["64","7"],["128","17"],["256","21"],["512","58"],["1024","106"],["2048","216"],["4096","386"],["8192","840"],["16384","344"]],"logBuckets10":[["10","2"],["100","23"],["1000","187"],["10000","1788"]],"skewness":"-0.016383070173703333","kurtosis":"-1.1892983333107883","meanCI95":["4966.017","4839.7372609264985","5092.296739073501"],"meanCI99":["4966.017","4799.999264779586","5132.0347352204135"],"percentileCI":[["50","0.95",5019,4783,5227],["99","0.95",9879,9823,9927],["90","0.99",8959,8807,9127]],"export":"ab00010101021b001fffffffffffff0303041907d005030619270b070008fb3ff00000000000000919b0000a990b86030106010a01070102010c02010106010b0103010301050101010101020106010301040107010101020203020801020104010b010501080101011201010106010501080101020502160104010201130108010601020105010401090201010e0104020101030103010401020102020102040202010601040216010301040103020e01040108010e0108020e020101020104010401020109010d01010101010301010102010901040109010201020102010e010501050103010502020101010601030102010101050101010601030102020401030103010301010107010c01120102010101010104010701030101010201050102010802020105010101010106010501030106010a0101010401020102010401050102010101030209010b010201010102010201030102010101040104011101020103010101040102011401040104010a0101010401030103011101030102010101030104010701010101010a0109010b010e0110020b01020104010201010101010101060112010301140101010f010f010d0102010501020107010801070101010801050201010301140203010d0109010201010104010d0101010101050103010101010102010501030103010301020102010901090101010e010201010114010b01020101010301010109030101070104010c010101020104010301050107010e0112010301010106010201050101010201181d01080101010101010203010301090105010901030103010e010101050102010901010101010d010c0105010901090201010101070101010301020103030b0101010a01020113010702010104010101080103010401020103010d01060101010501080108010201020101010f0105010101030109010302060203010501150102010101060113010801020101010101020107010101060102010301080112010301070101010201020106010201010105011001030101010f01040102010401020101020701010103010501010103010701010105010201080103010801050106010701070105010501020101010301010102010e01010103010301030101010101050101020201020102010d0101010a0106020302040104010201010201010301030103010101030108010301010104010701040102010b01020103010801010106010301040104020301020101010302060101010c01010104010501010105010401030201020101020104010201010101020301060104010301010101010d01040201020301010202020501050301020101060101010101010103010102030101040101010101010403010105010101020103010101040104010502020102010701040105010b01040101010c010201010103010b010201040103020b010701040101010601040106010101040103020102120104010501030101010502030103010202010106030101060101010401010101010b01010101010101020203010501010102020201040104040401030201010101010108010301010201020201020101010701050101010102040106020101030103010103030101020502010103010201010202010201030202010101030102010401020102010101010101010501030113010201050101020201010101010101020107020502010101010201040102010201030102020a010101050102010101030102020201010201010302020106020101060102010601050102010101020108010301010204010202050201030101040101010202020102010601020106010101020105010101040101010201050102010201020107010701020102010101010101020301080217010401030107010101020102030101030104020901010101010701020101010701020301010e0103010f010102020102010101020103020601020402010102030105020301010101010301020104010101020205010a0201010101010105010101030101010201010101010101020202010301010104010102010101010102020201010501020101010101010205020101010102010102050201010102010201020201010302020101010101030501010305010101030301010201050203010202020101010202010102010103030103010102010201020101010103020101010103020101010301010202010101040301010101020201010203010101020102010102030101010201020105010201030202010102010201010101010101020102010102010101060101020102020101010201010105020102010104010401010101020101020102020201010101010101030201010101010102010101010102010103020202020101020105010101010201010101010101020202010101020102020101010201020101010201020101020402010201020102010201010101010102020201050201010401020203020401030105010302020101020101010201010602010101010102010101020101010101010301030103010101020101010201010101010102020101020101020101010202010201010101040101030301010201010101030102010202010101020101030202010102060103010201010401020101030101010203020202010101010101020302010101010101010101020102010101010102010302010201010101010101030301010101010201010201020102010201010104010304020102020401020101010101020101010202030101020305030102010202020101010101030102010102040101010301010102020101010202010402020102020101020102010102020101010101030102010202040102010304010104010401010101020102010301020301020301010102020101010101010101020102010201020201020103020102030101010101010102010102010201020101020201010202010202020101030101010102010102010102010102010101010101010301020201010102010102010101010101020101010101020102010201020101010201010204020202030101010101020401010101020301030101010201010202010101010201020302010103020101040201010201030101010101010101010303010402010101020102010103010101020101020101010103010101010101010102010106010101010102020102010501030102010101010101010101020102030102010101010203010101010201010101010101010102020301030101010105030403030602010103020101010201010401030101030201030301020101010202020101010105020101010204010201010102010101030101010302020108020203030202010102010101010201010101010201010201010101010102010102010203010101030101010201040103010102020101010101010203010203020106030101030101010101010501010201020101020502010102010104030201010203010201010201010102010301010201020102020102010102050101010102020204020201020101010501020104010201010102010101020101010101010104030501010101010101010101010101010201010201020101010101030103010101030202020101010302010202010301020102010301010101010201020101010203010101010201010101010202030102030301010102010201020101010201030103020101020201010101010103010201010102020103030202010101010101010302030102010201010101010101030103020202020102010101010103010101010102020201030203010301020102010502020101020201010204010201020101010201020101010101040101010101020105010101020102010101010201010402010104010201020103010501030102010101020101010301020101010101020104020102020201010201010101010201010201010101010102010202010102020201010102020102020102010102020101020301020102010301020101020201010101020102010102010201010103010301030102010201010103010301010102010201040103010301010201020101010103010101020202030401030301010101040101010301020101020101010201","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","916.7549974278079","1933.838834389005","3014.2656568983502","4027.5350342253223","5024.033741612692","6022.830070040757","6904.332536957135","7877.213716309022","8960.73414632476","9996"],"densities":["0.0001094385259522486","0.00009832031182284448","0.00009255601389804908","0.00009869043932207083","0.00010035135947359225","0.00010012051221431993","0.00011344267742076133","0.00010278747510216815","0.0000922917530946302","0.00009659354613599537"],"corrections":0,"bucketCount":1475},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","1381.9174917125915","2863.44818475745","4315.271430962768","5744.974537331526","7021.333244457209","8521.773546576613","9996"],"densities":["0.00010360093603549605","0.00009642536838946028","0.00009839844018928172","0.00009992084525855134","0.00011192554417468774","0.0000952101477515327","0.00009690311995515072"],"corrections":0,"bucketCount":1475},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","475.5251795489272","5024.033741612692","9465.99547724197","9977.006021459596","9996"],"densities":["0.00010581446696180303","0.0000989335281795808","0.00010130659082236555","0.00009588843235127491","0.00005264826417871333"],"corrections":0,"bucketCount":1475}]},"wide":{"count":5000,"exceeds":0,"min":"1","max":"2147483647","mean":"68253263.186","stddev":"249173964.29637536","percentile":["1","1","7","110","21327","5373951","121503743","1506803711","2110783487","2147483647"],"percentilesAt":[["0.1","1"],["1","1"],["12","7"],["25","110"],["50","21327"],["75","5373951"],["90","121503743"],["99","1506803711"],["99.9","2110783487"],["100","2147483647"]],"percentiles":[["0","1"],["50","21312"],["75","5369856"],["87.5","64913408"],["93.75","308543488"],["96.875","740294656"],["98.4375","1137704960"],["99.21875","1634729984"],["99.609375","1892679680"],["99.8046875","2002780160"],["99.90234375","2112880640"],["99.951171875","2132803584"],["99.9755859375","2135949312"],["99.98779296875","2146435072"],["100","2146435072"]],"cdf":[[0,"0"],[1,"0.029"],[2,"0.0634"],[7,"0.1246"],[42,"0.2048"],[100,"0.2454"],[999,"0.35"],[1000,"0.35"],[5000,"0.432"],[1000000,"0.6762"],[2147483647,"1"],[2147483648,"1"]],"countAt":[[0,0],[1,145],[2,172],[7,39],[42,3],[100,5],[999,0],[1000,0],[5000,0],[1000000,0],[2147483647,1],[2147483648,0]],"linearStep":107374182,"linearBuckets":[["107347968","4470"],["214695936","137"],["321912832","92"],["429391872","51"],["536608768","46"],["643825664","28"],["751304704","22"],["858783744","24"],["966262784","23"],["1073217536","24"],["1180696576","9"],["1287651328","3"],["1395654656","6"],["1502609408","14"],["1609564160","8"],["1717567488","13"],["1824522240","6"],["1932525568","9"],["2039480320","8"],["2146435072","7"]],"logBuckets":[["1","145"],["2","172"],["4","192"],["8","164"],["16","172"],["32","139"],["64","149"],["128","149"],["256","162"],["512","173"],["1024","138"],["2048","171"],["4096","190"],["8192","172"],["16384","158"],["32768","158"],["65536","169"],["131072","176"],["262144","144"],["524288","140"],["1048576","164"],["2097152","148"],["4194304","165"],["8388608","173"],["16777216","167"],["33554432","168"],["67108864","166"],["134217728","144"],["268435456","143"],["536870912","125"],["1073741824","121"],["2147483648","83"]],"logBuckets10":[["10","720"],["100","507"],["1000","523"],["10000","566"],["99968","557"],["999936","508"],["9994240","524"],["99942400","548"],["999817216","447"],["9999220736","100"]],"skewness":"5.1870933076730585","kurtosis":"29.754006897872756","meanCI95":["68253263.186","61344276.61280106","75162249.75919895"],"meanCI99":["68253263.186","59172046.80726144","77334479.56473857"],"percentileCI":[["50","0.95",21327,15767,29519],["99","0.95",1506803711,1404043263,1667235839],["90","0.99",121503743,97189887,154796031]],"export":"ab00010101021b001fffffffffffff0303041913880501061a7ff88e77070008fb3ff00000000000000919b0000a991b2e0118910118ac01186c01185401182501182601182701183201181901160111010b0116011819011701181b010801070111011001070106010401090106010c010c0108010b01030109010401050103010401040102010501040102010801030108010401090103010c010302030105010301020105010601030107010401010106010601030106010a0102010501020103010401010104010201030103010202030102010201040102010501020103010302050102010301020203010101030101010401030104010101050103010301020101010501010103010201020102010101050304020501030301020302020102010301020101020103010101010201020101010201020102010101020202030103010202010101020102010102010105010101050202010201010101020401020101010101010101020201020201020102030102010101030101010101050103010101010203010202010101010102010304020101030301020101010202010103020102010101030102020201010202030101030101010301020202020201010103010202020101020101010103020302030102010201010202020101010201020102010601020102020101010101010101020102010201010108010101040101020201010201010401010103010103070101010203010101010101020303010102020101010101010101010201030101010402020105010102020101010201020101020203050101010101010101010101020101010201010102010101010102010301040101010201050101020101020102010102020403010201040205010203020103010102020101020301020101010202010203010201030101010101030103020101010101020101020103010101010101010401020101010203010201010101010101010303010101010104020104010102010101010102020102010101040103010401020105010301090103010101040101020101020105020401010102010101050108010501020103010d01020102010301010101011101010106010302020101010801020201010201060201010201040103010301010102010201010104010601050102010c0102010101020104010401010109010101020107010201010106020501090306010501080103010301050201010d010502020106010101060104010901040102010802070103020401010101010501010105010d010601010107010201040105010901010105010902010202010201020101010a0204010b0101011302010108010301030108010401010112011822010b02010112010b010d01120202010601050105010101020106020f010101010101010b01060105010102050101010101020101010c010b01020105010601010108010a0102010d01050102010101020109010e01080108010101020106020702080101010601030106010d01030104010a0101010301050104010301080106010501020101010201090203010c0105010301040107010301060103011301030104010201160102010101010101010401020117010101070102010301020102010601010106010401090110010c0111010a020801030108010401050112020201020101010601010114010b0106010501050118200101010f010c0104010801040113010201020110010c01010106010201030101010102010109010d0101010301030104010502010101010801040108011501020110010301020110010c0101010401040106010501030103010501020102010301010104011301080101010c010301010107010501060102010101182501050101010101020103010401090106010401060101010801020108010401080104010401040107010d020401010103010201040105010301030101010101010203010602020113010201040101011301030104010e01030109010101050105010301030202010301020118200105020801030203010401030104020401020103010101070103010a0103020101020109010601020101020101030209010801070103010201182b010b01010109010801020101010202020101010f0106010302060102010301020108010101020107010201020105010402010103010201030101010d010701040103010401010105010b01020105010a011201020108010301020104010101160114011101080102010502040101021401030102010401040109011401100103010801182601030102010201010103010102060103010301010112010b010201070203010b0208010f0104010201040109011301010201010101070109020301040102011501040106020f01010102010a010d0107010a02080101010f010601030107030501060112010101090103010301020103011821010a0106010c0101010301130104010702030101010b0103010101010104010c0105010e0209010101040106010401100109010601030102020201181a01030102010e010601010102010201040103010401050205010601020104010d01181d01020102010b01070108010802020107010b01010101020301030104011101170102010801130111020201120102010b01040102010d010a0107020c0103010d010a010a010301020105010101030109010301030206010b010201010205010301181d01181c010201030102010201060102010d0102010401120202010201060108010201030108010401070104010301040108011818010b011401060103010201090109010c011818010201010103010b010b01020103011501060102010101060101010101030107010c010e010d01060108010c010e010b0104010201020101010b010a01020101010701010106010b0103010d010c0112010301030107010601010105010601010104011819010701090111010c0102010601130101010201060103010d01020101010501020209010d010c01010202010d010201060102010401010103010201010105010901020101010c010e010601010104010c0101010a010101060103010201030101010b0107010301080101010b010601030105010c010b010201010101010101030105010a0103010401070104010b0108010102040105010801020109010101050104010c01160103020d0103010b010401080104010b0103010301090104010701060102020202010103010d010201020104010401040103010901080105010b010f010801090103010201060110010101030109011824010201030205010502181c0118180104020801020105011001020107010e0105010a01050101021819010d010e010101040101010301010101010101020117010701181a0204010b010a010401020105010e01080101010401080107010b0102011818010301060102010d020701030113020601060102010d0101020b0210010301020103010501010101010101010207010501020103010c01040102010601060105010901010204010c010a010b01070103010d01181b0102010e0115010501020218190109010601040109010301030108010201070105010601040112010801040101010c021201080103010b01080205011301010101010a010301020101010b010901050103010102010105010302050103010701010109020301050101010401060103010301020103010401040102010701050104010b0104010101030103020801060106010e010a01020101010301010107010301020101011501090101010a0105010701010102010302060102010201150103010f010401020102010d0101011201020110010b010a0104010201050204010401060107010101090205010d0101010201090104010101090106010e01010102010201010104010b010501170106020f0112010d0102011819011825010b010301020102010701040101010d0103010101010101010a010c0107010f0103010801070101020601010101010a020601010102010402060101011601070104010d010301040108011201050106010101010205010a01060106010401030104010301040106010c01100108010301030202010102050106010301070106010302020101010c010201140101010201060102010b01020214010d010a01020113010101090104010e01050105010301020202011302040201020801040101010101040106010202181e01030101010a010901060103010101070101010201070101010101010103020d01170102010301170103010e0108010b0114010101020102010301070210010401050102010d01010111010401060103010901020101010801050101010e010201030105010d01020107010701030117010602060102010d0101010b01060102010a01130202010d01010107010401030116010501090101010701020102010901010102010401030111010e01020104010301010118230101010201050104010301050104011822010b0207010b010401181f01010218180108010301080102010a01030106010501020101010a0102010501020209010201030107010401060108010101060102010601090103010701090104010e010101181a010c0112011401060112010101080101010301181c01181a01030101010701090104010a02050109020a0105010301050108011701040301010f010601181b020c0105010201020105020c010201030109010501010118190104010a0101020201070103010301110101010d0109010e010201030103010e0104010701060103010801020112010901120101010a01040102010101010102010601130101010101181d0109010f010601130107010301080103010a0118180110010b0102010f010201010105010101060106010e01020102010201050103010c0109010201020105010b01050105010501070107010701010103020c010601090109010101110102010802060107010901030101010a0103010e010f010701010102010a02010108020e010501080101011822010701030110010102040102010501090116010c01030207010701080105010a01080101011819010501100105020e01130111030f0101010201010106010e010201182301050101010c02050103010401040104010e0101010401020104010401110114010301090101010d011601070107010201010102010901060109011822010101080101010c010201090107010501040103010b0106010e010c01090103010701181f01110103010801090104010401030108010601060203010b01030108020d010301181e01020102010c010e010101070102010501020105010e010301090109010101050102010a01060102010101110101010301020101010201010102010801040214010201060205010202100109010401060109010101100101010c01010106010b01080101010101040113010e0104010901010101010d010102020103010301050107010102090201010b020e010301030101010701060105011501020108010501080101010501060104010601020101010e010701080104010b0103010d010501060102010601020104010b0101010201050110010301060102010a0103011826010301100109011301020102010901050204010301080108020401030108010a01010104010101030104011820010201010211010d01020101010101020101010a010101020218240107010301060105010701040105010701040112010601040101010101010104010401070102010401030106011201050109010e0103010101181b0308010602030108010f01040102010201110107010b010901010103010101030109010702010109011401040104010e0109010401010203010101030106010e0103010101181a0102010b0110010401080105011501181f010401060105011401030102010201120102010701183401060108020201010102010a010a0101010201010101010301182001050108010401010109010401090109010a010201040101020901070103010301030103010a0107010101010104010601181801010101010601010103010a01040109010801020103010401030108011201090106010301010102010101181a0101010b010801010108010301020101010301070101011818010701050108010201030104010501080101010101010115010401181a010201010103010301030109020301020106010801050204020501140104011401040105010e01030106011501170105010202020104010901010103010201060102010101130102010d0108010c01060106010401080211010202070108010c0103010101030202010701070103010c02140115010601050102010601060101010301060109010101060114010d01020106010b010a01010102010201080110010b010401020214010d0101020101070103010501110105010501090102010901181a010401040107010c01070109010f010201070109010201070109010101050110010101030106010301120104010201170204010901040101010a0109010201020115010c0105010e010101080102020b01020104010301070104010301070109010c010801060105010e020701040101010401070105020801070101010e010402010102010801030106010701010101010f0109010201050102011001010105010b010301020101010701050113010301060104010b010301020101010401020101010a01080107010601080105021001030105010201040101010301040106010b01040110010701050104010501090102010201030201020101050102010101020108010201060104010901080203010a01090212020601050216010601010105010501050103010b010b020b0104010d0102010c0106010801160102010c01181d0114010301020103011201090103010101010102010b01181e01050106010e01040103010a01040102010e010301030103010b0105010602040101010101050106020101040105010201070101020b02020201010b02040102010201020101010d01050103010d0109010b01120103010f010a0101020c010a01020101010402020108011401030101010101010204010b010301130103020d0208010a010c010501020104010101020107010c01020102010201010103010901040109010101020108010f0203010d01010105011501020102010c020201020106010701010103010f0103010401030102010d02010101010101070105010601181a010101020108010b010a020c01010105010b01010107010401110108010a01070118220116010201060103010701030107010101080101020201010107010601020101010501181801070102010101020107010601181c0115010801040106010401070108010301070101010201010101010b010301070101010401010206010a010401030201010201010102010c010a020601050104010901030103010c010901010107010e010801020105010401020101011001030102020501020106010c010e011001020103010601020204020201020101010302090104010401010117010201010104010301050105010501020102011001030107010801010101011701010118240101010801130107010701120203010202110108010601010107011101181801030102010301130103010b01090102010501030108010101020101010801070101010a0110010d010b0105010a0104010b010c01010105010401020104010301181c010a01060112010301050113010a01010102010601070106010901060110011001080109010301090106010401020106010101060105010b01020110010f0104010201010105010901080107010501181901010111010401040102010d01090101020801070102020501070102010401070106010401030104010b010101010108010901110103010301020109020602181a01030102010301040101020301050102010401080111010301182101030102010501110106010101040101010602010106010a010601010102021001181b01010103020101090103010b0102010901040103010f01090107020101050105010f010e0103020503080104020701010102010102070104010202060107010601070108010701020102010301010107010d01120209010401030118280101010a01170104011818010101020101010101080107010c020101070103010c010301050102010101020111010c010e010301050105010201181a011501050103010f01130102010401020106010501020103010101070109011201050112010101030102010f0107010201060106010b01040108010301020102011001070104010101020103010701181801060102010101040102010201010105010c021001140101010c01020118180101010401010101010a01010109010402070102010501070112010a0104010301181b0101010c010201020101011001040103010701080117020d01070103010401090209020401070101010b01030104010201010118260103010a0205010c020e01090102010a01150109010301020208010301040101010b01040108020201030103011301050109010101020108010f010b01010102010f0103010b0112011821010701040103011601020108010501160103011501040107010301010104010901050107010501050110010201020109010c010201181b0102010a01130107010e01010107010f0207010101020110010d0108010101010104011825010201080107010c0101010501040103010801090109011101030106010a01010109010a02050108010d010101020203010801070105010a010102050201011201010116010301010101010a0105010301030102010801060104010b0106010a010201010107010e01060108010401030118210104010f010901010104010401150106010d01030106010e010201182d01020101010d0104010a01010109010201030101011301040107010301020102010501010103010b020101060102010201040107010f0109010102181e0105010a010201182d01010102010501030102010e0105010f010b010d01060102010201030205010d0116010501070109010301050101010c01030109010401030115010d0215011501020103010d010b0106010a011301030102010b0204010a010a011828010c010803182201020105020b01010103010b010f010d0105010301020104020f0101010a010c0106010201110105011827010901020108010f010101030107010e0109010c010201070104020701060106010c010f01090104010b010c01070107010301020101010801040106011820010901140101010a010401050101010d010b0108010c011001090107010d0107011001040107020a010d0105010101181a010e011101030102010901030107010f010a010e0107010e0107010b01040107010a0106010801010118190101010801181a0102010c0104010401030103010701010102010d0102010a01181c010501070101010b010401181a0112020301010105010f010301181e010801090101010b010201090101010101060111010202181b011603181b0101010a01181a010d010901010101010f01181d0101011401184d010c0118300107010c0108010d010901030101010b0105010801080110010201010115010401030106010a01181d01181d01070104010a010a01010106010c0105020c0112010802020101010b0115010a0107010101182302182c01181d010c010301181c0104010101010108011001110102010e010401182901010112010501182201182d0103010201110103010a01","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","4.348681337991257","36.75738449067154","349.1608244891508","2804.2778804125055","21874.96314301389","178354.499931398","1755826.815109678","14211419.530023392","121082046.90549038","2146959360"],"densities":["0.02986250105839754","0.0030855909145420324","0.0003200989080033394","0.00004073125546447342","0.000005243650064117267","6.390611964504693e-7","6.339255468245625e-8","8.028521989183623e-9","9.35710797773007e-10","4.936133069541653e-11"],"corrections":0,"bucketCount":3479},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","10.383070213282933","252.10114881366763","4691.6049624124","94556.18731790125","2424356.3799273274","49175029.991236694","2146959360"],"densities":["0.015224989221002561","0.0005910072746081951","0.00003217862825560692","0.000001589693504522444","6.131733670136744e-8","3.0557237323439227e-9","6.809906090610665e-11"],"corrections":0,"bucketCount":3479},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.1104654732166996","21874.96314301389","428906355.1630517","2094327495.8831866","2146959360"],"densities":["0.0450261635376779","0.00002057344812924497","1.0492335833421835e-9","2.942198750930485e-11","1.8999897054388154e-11"],"corrections":0,"bucketCount":3479}]},"bimodal":{"count":3000,"exceeds":0,"min":"100","max":"5499","mean":"2659.655333333333","stddev":"2562.600715394074","percentile":["100","100","112","124","148","5243","5395","5491","5499","5499"],"percentilesAt":[["0.1","100"],["1","100"],["12","112"],["25","124"],["50","148"],["75","5243"],["90","5395"],["99","5491"],["99.9","5499"],["100","5499"]],"percentiles":[["0","100"],["50","148"],["75","5240"],["87.5","5368"],["93.75","5436"],["96.875","5468"],["98.4375","5484"],["99.21875","5488"],["99.609375","5496"],["100","5496"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0.018"],[999,"0.505"],[1000,"0.505"],[5000,"0.5093333333333333"],[1000000,"1"],[5499,"1"],[5500,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,54],[999,0],[1000,0],[5000,13],[1000000,0],[5499,13],[5500,0]],"linearStep":274,"linearBuckets":[["274","1515"],["548","0"],["822","0"],["1096","0"],["1370","0"],["1644","0"],["1918","0"],["2192","0"],["2466","0"],["2740","0"],["3014","0"],["3288","0"],["3562","0"],["3836","0"],["4108","0"],["4384","0"],["4656","0"],["4932","0"],["5204","643"],["5480","792"],["5752","50"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","883"],["256","632"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","1485"]],"logBuckets10":[["10","0"],["100","54"],["1000","1461"],["10000","1485"]],"skewness":"0.02470217398320308","kurtosis":"-1.9930039546175604","meanCI95":["2659.655333333333","2567.9032124266378","2751.4074542400285"],"meanCI99":["2659.655333333333","2539.044511791164","2780.2661548755023"],"percentileCI":[["50","0.95",148,146,5015],["99","0.95",5491,5487,5495],["90","0.99",5395,5383,5411]],"export":"ab00010101021b001fffffffffffff030304190bb80518640619157b070008fb3ff00000000000000919b0000a99012c1864183602183302184102183202183002184402183f02183302183a02183c02184202183a02184b02184102183302183c02184602184602183f02183f02183d02183e02184202183e021837190c4e0d010c010a0109010e010e010b010d010b01110110010a01090109010e010e010a010a0113010e010e010d0105010c01090106010f010e01150107010c010b011001160109010e010b010c01060113010e010a010f0112010a010f010c010b0109010b010c010901090109010b010b010e010b010e010b01070109010d010e010b01110113010d010a010a01090112010d010e0109010b010b0109010c010b010d010a010e010c0104010c0107010b010e0113010b01070107010d0106010e0111010b010c010c010d0106010b01070110010b0107010c010f010b010a010b010d01060104010c010c0112010f01090111010a0111010a010d","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["100","109.98699073456697","119.98252823585753","129.5824353622775","138.70176685308016","1565.9078177195875","5093.23876271285","5186.694201087791","5288.925510223478","5395.383221720669","5498"],"densities":["0.010013026211577427","0.010004464490987972","0.010416767441925491","0.01096571608355891","0.00007006696751270529","0.000028350047545706258","0.0010700286868143766","0.0009781739160483049","0.0009393401247653047","0.0009744995085286361"],"corrections":0,"bucketCount":150},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["100","114.14631642493161","127.98723858589582","141.5209929268492","5065.41231923334","5202.320403735921","5351.05679093052","5498"],"densities":["0.010098540041517095","0.010321360180757702","0.010555618142473196","0.00002901305763876451","0.0010434529368822608","0.0009604720509328753","0.0009721928884076227"],"corrections":0,"bucketCount":150},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["100","104.2446148394385","1565.9717277421887","5450.433382666298","5497.538399849717","5498"],"densities":["0.011779631813805347","0.0003078549997655676","0.00011584616865236932","0.0010402288955590824","0.0021663771109856054"],"corrections":0,"bucketCount":150}]},"heavy":{"count":3000,"exceeds":0,"min":"1000","max":"1000447","mean":"7747.794333333333","stddev":"40693.48054383365","percentile":["1000","1009","1145","1342","1960","4081","10423","100031","500223","1000447"],"percentilesAt":[["0.1","1000"],["1","1009"],["12","1145"],["25","1342"],["50","1960"],["75","4081"],["90","10423"],["99","100031"],["99.9","500223"],["100","1000447"]],"percentiles":[["0","1000"],["50","1960"],["75","4080"],["87.5","8472"],["93.75","15872"],["96.875","34464"],["98.4375","62496"],["99.21875","142848"],["99.609375","333312"],["99.8046875","499968"],["99.90234375","999936"],["100","999936"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0.001"],[5000,"0.7953333333333333"],[1000000,"1"],[1000447,"1"],[1000448,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,3],[5000,1],[1000000,3],[1000447,3],[1000448,0]],"linearStep":50022,"linearBuckets":[["50016","2943"],["100032","28"],["150016","11"],["200064","5"],["249984","1"],["300032","0"],["349952","6"],["400128","0"],["450048","0"],["499968","3"],["549888","0"],["600064","0"],["650240","0"],["699904","0"],["750080","0"],["800256","0"],["849920","0"],["900096","0"],["950272","0"],["999936","3"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","87"],["2048","1462"],["4096","704"],["8192","362"],["16384","207"],["32768","80"],["65536","55"],["131072","19"],["262144","12"],["524288","9"],["1048576","3"]],"logBuckets10":[["10","0"],["100","0"],["1000","3"],["10000","2684"],["99968","284"],["999936","29"]],"skewness":"17.66501107247276","kurtosis":"381.8715208861686","meanCI95":["7747.794333333333","6290.79280963013","9204.795857036537"],"meanCI99":["7747.794333333333","5832.523612335628","9663.065054331038"],"percentileCI":[["50","0.95",1960,1893,2061],["99","0.95",100031,66687,142975],["90","0.99",10423,9439,12503]],"export":"ab00010101021b001fffffffffffff030304190bb8051903e8061a000f4240070008fb3ff00000000000000919b0000a9907701903e80301040106010401040104010101030206010501010102010601030101010401050102010401040103010401060102010301020106010501040102010402030104010202040105010101040105010301030103020101020102010201020101010101020102010402030101020301030103010201040202010201030107010301040102020101010102010601030101020201010102010301010102020701040102010101010204010101020103010202020103010401030103030101020106020201020105010102040106010201010203010501010105020301010101010202030104010102030104010401040201010103030108010202050108010202030105010402010103010302020105010102020102010302020101010202020103020301050103020301050204010501030202010302040104010302040103020201030203010301030203010202040104020401010204010402020102020401030205010301020205010802030201010702010304010302030103020701030203010102010203010102050102020501020201020501030201010202020202030101050203020401030202020301010502020201040204020202040104020102040105020102040104020302050204010402040402010502030201020401010202020202030101020402020207020401030205020302030201020501010203020502040204020301020205020402030203020202030201020202030105020502020207020702020203020202010402020602020201040202020202020402030204020404050407020702030304020402020203020802030202020202020209030402030203020402050203030402010208020102030303020502040205030202010202020503050402020303020204020503040205020403040404030202010204030202030206030402040306020103030203020203020203030402050305020603060202030602010303020103040302020203030207030202020301050303020301020205030306030103020205030203010503030403030202030103060307030205030302030303030305020403040304030503020302030303060302030103090303030203050305030403050303030303030303030203040601030504030303030403040305030404010304030303020405030503020301040703030302040503050703030204040304030304010305040403020405030304020306040103020401030604020401030404030405030204020401030304030402030404010403040704020303040304050403040304020401040504010401080304030402040204020c02040604010403040204020201020202060305020302010203020102030304020a0203020402040302020102020203030104020202030102010202030502010203030202010301020502020303020103020201030402030303020503040203030202020302020403070203030203010204030203020203080603010303020303040303030103020206030103030304030503030204030403060302030103030303030203020301030703010303030103020305040103070303030103020302040403060303030507060d04040403040304040603050402030404010303070604030403030304030401030404030406030404030402040204030303040604010406040304020403040104040407040204030402040105040402040504060402050404010d0105030403050204020503040205070401050305040401050305040402050305020504050305010a01050405030502050205040501060505020505050106030504060405030605050306010504060506020502060106030605060306030603060306020606060406040603070406030d07060107010605070206030704070507020602070307040e0307040705080507060701080307050f02080107020802080308020806080308010803100208030803040305030402040105010405040205070401050404020a03040605030506050105020503050205020504050205030506050406030504050206010505060405050603060205050601060306020601060306060604070106020604070206020704140106010702070107010703080307050702080207030805070110050807080508010803090208040801090209030907090109020907090409040a0309010a040a040a020a020a020a020b010a010b050b040b010b060c020b030c040c030c050c010c030d060c020d020d020d050e040d030e010e070e040f040f020e0210030f021004100610031002100409030801090209040902090409060a010a030a0114030a040b050b020b040b050b010c030c030c0b0d040c020d040d040e040d050e020f020e02181e0110011005100211021106120412011204130413051404140415021507160317021703181803181806181904181a03181b06181b06181c03181e03181e04181f0618200318200311021201120113031402140415041604182e03181906181903181b03181b01181d05181e02182005182101182301182403182602182804182a02182d01182f04183101183501183706183c02183f03183104182402182704182904182c05183104183403183805183e03184404184a03185202185c03186603187306187003184b02185501186503187603188e0218ae051901940618ba0219010403190187011901750619028b0319040003","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1000","1115.6005988741867","1251.266686571931","1435.5325178934097","1656.824308093357","1966.2102049421471","2515.9902158833916","3390.6801043858086","5211.779154432703","10584.800297221043","1000192"],"densities":["0.00086504742167326","0.0007371038827536175","0.0005426942113078756","0.00045189204673903806","0.0003232209386999764","0.00018189093457362363","0.00011432623300494875","0.000054911895098416004","0.00001861150316414068","1.0105019449134386e-7"],"corrections":0,"bucketCount":952},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1000","1172.4579899444116","1406.2934331673869","1732.3966090498104","2345.5055333430932","3552.1364988239757","7452.312818442061","1000192"],"densities":["0.0008283590856137778","0.0006109302374701191","0.00043807344859667964","0.00023300450735048612","0.00011839340025573569","0.0000366283806551422","1.4390191578088523e-7"],"corrections":0,"bucketCount":952},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1000","1046.319804018247","1966.2116878156164","20210.616164708317","778056.845184036","1000192"],"densities":["0.0010794518901743044","0.0004891879229789187","0.00002466509666401793","6.465691603876856e-8","4.5017638060418094e-9"],"corrections":0,"bucketCount":952}]},"huge":{"count":1000,"exceeds":0,"min":"988282880","max":"8791798054911","mean":"4487605591474.176","stddev":"2564286487275.303","percentile":["988807167","80664854527","1109175304191","2244120412159","4578435137535","6700148981759","8001524072447","8710193676287","8791798054911","8791798054911"],"percentilesAt":[["0.1","988807167"],["1","80664854527"],["12","1109175304191"],["25","2244120412159"],["50","4578435137535"],["75","6700148981759"],["90","8001524072447"],["99","8710193676287"],["99.9","8791798054911"],["100","8791798054911"]],"percentiles":[["0","988282880"],["50","4574140170240"],["75","6695854014464"],["87.5","7829725380608"],["93.75","8289286881280"],["96.875","8499740278784"],["98.4375","8632884264960"],["99.21875","8748848381952"],["99.609375","8783208120320"],["99.8046875","8787503087616"],["100","8787503087616"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[8791798054911,"1"],[8791798054912,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[8791798054911,3],[8791798054912,0]],"linearStep":439589902745,"linearBuckets":[["439428841472","51"],["878857682944","45"],["1318554959872","51"],["1757715365888","49"],["2196875771904","47"],["2637109919744","52"],["3075196583936","47"],["3515430731776","55"],["3955664879616","38"],["4393751543808","48"],["4831838208000","50"],["5274219839488","54"],["5712306503680","41"],["6150393167872","53"],["6592774799360","55"],["7030861463552","45"],["7468948127744","54"],["7911329759232","53"],["8349416423424","60"],["8787503087616","52"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","0"],["16384","0"],["32768","0"],["65536","0"],["131072","0"],["262144","0"],["524288","0"],["1048576","0"],["2097152","0"],["4194304","0"],["8388608","0"],["16777216","0"],["33554432","0"],["67108864","0"],["134217728","0"],["268435456","0"],["536870912","0"],["1073741824","1"],["2147483648","0"],["4294967296","0"],["8589934592","0"],["17179869184","1"],["34359738368","1"],["68719476736","6"],["137438953472","4"],["274877906944","17"],["549755813888","33"],["1099511627776","56"],["2199023255552","124"],["4398046511104","240"],["8796093022208","517"]],"logBuckets10":[["10","0"],["100","0"],["1000","0"],["10000","0"],["99968","0"],["999936","0"],["9994240","0"],["99942400","0"],["999817216","1"],["9999220736","0"],["99992207360","11"],["999653638144","98"],["9998683865088","890"]],"skewness":"-0.04131096624244306","kurtosis":"-1.2361487710687884","meanCI95":["4487605591474.176","4328399976893.3403","4646811206055.012"],"meanCI99":["4487605591474.176","4278227441812.3574","4696983741135.994"],"percentileCI":[["50","0.95",4578435137535,4260607557631,4827543240703],["99","0.95",8710193676287,8607114461183,8787503087615],["90","0.99",8001524072447,7834020347903,8130373091327]],"export":"ab00010101021b001fffffffffffff0303041903e8051a3aecbecb061b000007fe81a32a32070008fb3ff00000000000000919b0000a99069a19535d01190efc01190566011901380118a00118cc01188901187f0118930118b3011838011840011902bb01182001181c01181c010b01188d0118f7010e01185b01185201183c0108011884010a01181d01182201183f011301182a010e011840010201182501181c01185101060102011824010c0107010201185a0118370115011829010c010e010c01181a01187b010801183001181f01185301060101010501182b010a01183e011101185601181901020103010f0101011825010c01182e021001050102010201181a0118190114010e010501010106010f010d01181a01110118340107010201182601010116021840011829010d0107011824010201185f0101010301020109010b0102020b0116011201182201020118330207010601182c010f010e010701040103010602010102020601183301060105020101110103010201040107010c010201100108010501090104011819010401050114010c010701150101020b010601010118240102010f0107010101080204010301020118180114010d010402090101010501181d0104010401040115010a010b010e0109010201020106010401060104020201020102010c010d010d010c010d020b0107010401060104011301010209011401040103021601070109010f0106010d011819010401090118270101020802080101010f01050101010c01070101010501040112010a0102010b010e01010101010101040107010101010103010101060103010101030103010601030104010701010101011401030101010801060101010501090101020101080101010201090102010501010105010201080102010301020103010401080103020e0101010c01010108010b010c010201070109010101090104010201050101020301020103010401010102010601010205010101040105010201080106010101030203020201090105010d0107020f02050103010301050307020101020104010301010105010202010105010102020106010b020101090102010301040101010a01090104010e010302040201010501030106010301010205010701030103010e010b01040104010201030105020101060104010101010201010502100102010c010201060111010601040104010c010a010401060101010102010111011826010201080306010201010105010301010101010b01050103010501060103010501040101010301050101010102050101010b01020103010101020101020101070106010e010c010701020103010a010301010105011101020101010301040102010502060109010301030102010a02010201010601070101020701030102020201020202010102020302010102010102010a02030104020201010104030101010105020501010101010303010107010201010202010601020102010301020203010601030202010501030103040301010102010201010207020301050201020102040101020301020301010201060201010301020306010101020102010201010103010102030103020101040104010201050105020701020107010101060101020201020109020101040204020201050101010401010102010101030101010101020101010c010201060101010201060102010101020104010601010201020101020401010301010101010501010202010102030105010301010201010101010101030201010101010201020204020501040201010201020201010301020103010301050201010101020108010101020102020302020104010401040101010601010201030101020105010101010106020201030102010101020401010101030202010201010101010401030101020402010101010301010104020101030101010101020106010101020106010301010102020801030202010301010106010601030102010101020101010401030103010102010102010203040103010202030102010301010102010201010204010203020201010201020101010401010101010101070101010102010102020301040101010101020104010201010103010201020101010301030106010101040101010201030103010301010101010101050201020201020103010101020101010401010208020101030105010701010102010401040202020101030102020201010102010201020103020102010101010101010103010201020101010102040102020201010105010101030101020203010101010101030102010102010101010101020101020101020101020201030101010401010108010302080101010301010101010101060103020401020102020101010201010402020103010101010108010101010203010102010201010101030101010201010101020101030202020301010202010501040102010601010102010401060205020101090101010301020102010103","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["988545024","917929860577.1102","1810611672477.5442","2703647874466.603","3553829999677.5986","4562016213314.733","5424457756667.285","6321619693786.376","7171476085103.17","7992647400762.968","8789650571264"],"densities":["1.0905823339379007e-13","1.1202199783493919e-13","1.1197754332609368e-13","1.1762185658182632e-13","9.918802563193139e-14","1.159498875845799e-13","1.1146259762324916e-13","1.176669388166358e-13","1.2177726875378201e-13","1.254700153038732e-13"],"corrections":0,"bucketCount":845},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["988545024","1300845126465.9744","2563840959569.5825","3878305161521.1616","5165191240629.676","6443869008140.341","7669636273127.756","8789650571264"],"densities":["1.0990223452087817e-13","1.1310974993962926e-13","1.0868089267478223e-13","1.1100993722467388e-13","1.117225515973878e-13","1.165450791008108e-13","1.275493920879973e-13"],"corrections":0,"bucketCount":845},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["988545024","442511935914.1889","4562016213314.733","8381436380769.523","8788601100043.572","8789650571264"],"densities":["1.1324428338709575e-13","1.0923644441120846e-13","1.178189306938372e-13","1.2034441512359965e-13","9.528608126980648e-13"],"corrections":0,"bucketCount":845}]}},"comparisons":[{"a":"uniform","b":"wide","welch":["-19.36558971405772","4999.000003337613","0","-75157283.74335232","-61339310.59464767"],"mannWhitney":["4151297.5","-11.111499716338367","1.1029143272966058e-28"],"cohensD":"-0.3240344052657084","cliffsD":"-0.1697405","ksTest":"0.5367999999999999"},{"a":"bimodal","b":"heavy","welch":["-6.833810633893188","3022.7854758238605","9.960476887727054e-12","-6548.021991831127","-3628.2560081688734"],"mannWhitney":["3569062.5","-13.876636949170436","8.776627274891161e-44"],"cohensD":"-0.17644823184135647","cliffsD":"-0.206875","ksTest":"0.505"},{"a":"uniform","b":"constant","welch":["76.47103964051733","1999.0000000000002","0","4797.7372609264985","5050.296739073501"],"mannWhitney":["199200","16.76477955000128","4.4164919975970766e-63"],"cohensD":"1.7517750704471724","cliffsD":"0.992","ksTest":"0.996"},{"a":"three","b":"four","welch":["0.9508731633956237","2.0082239472623944","0.44167566755067034","-1109.4563086478202","1742.1229753144867"],"mannWhitney":["6","0","1"],"cohensD":"0.867135644459936","cliffsD":"0","ksTest":"0.33333333333333337"},{"a":"empty","b":"uniform","welch":["0","0","1","0","0"],"mannWhitney":["0","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"},{"a":"one","b":"two","welch":["0","0","1","0","0"],"mannWhitney":["1","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0.5"}]},{"options":{"lowest":1,"highest":1000000000,"figures":1},"histograms":{"empty":{"count":0,"exceeds":0,"min":"9223372036854775807","max":"0","mean":"NaN","stddev":"NaN","percentile":["0","0","0","0","0","0","0","0","0","0"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","1"],["75","1"],["90","1"],["99","1"],["99.9","1"],["100","1"]],"percentiles":[["100","0"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[1,"0"],[2,"0"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[1,0],[2,0]],"linearStep":1,"linearBuckets":[],"logBuckets":[],"logBuckets10":[],"skewness":"0","kurtosis":"0","meanCI95":["NaN","NaN","NaN"],"meanCI99":["NaN","NaN","NaN"],"percentileCI":[["50","0.95",0,0,0],["99","0.95",0,0,0],["90","0.99",0,0,0]],"export":"ab00010101021a3b9aca0003010400051b7fffffffffffffff0600070008fb3ff0000000000000091901b00a80"},"one":{"count":1,"exceeds":0,"min":"7","max":"7","mean":"7","stddev":"0","percentile":["7","7","7","7","7","7","7","7","7","7"],"percentilesAt":[["0.1","7"],["1","7"],["12","7"],["25","7"],["50","7"],["75","7"],["90","7"],["99","7"],["99.9","7"],["100","7"]],"percentiles":[["0","7"],["100","7"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[7,"1"],[8,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,1],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[7,1],[8,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","0"],["4","0"],["5","0"],["6","0"],["7","1"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","1"]],"logBuckets10":[["10","1"]],"skewness":"0","kurtosis":"0","meanCI95":["7","NaN","NaN"],"meanCI99":["7","NaN","NaN"],"percentileCI":[["50","0.95",7,7,7],["99","0.95",7,7,7],["90","0.99",7,7,7]],"export":"ab00010101021a3b9aca000301040105070607070008fb3ff0000000000000091901b00a820701","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["7","7","7","7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]},"two":{"count":2,"exceeds":0,"min":"3","max":"9","mean":"6","stddev":"3","percentile":["3","3","3","3","3","9","9","9","9","9"],"percentilesAt":[["0.1","3"],["1","3"],["12","3"],["25","3"],["50","3"],["75","9"],["90","9"],["99","9"],["99.9","9"],["100","9"]],"percentiles":[["0","3"],["50","3"],["75","9"],["100","9"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.5"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[9,"1"],[10,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[9,1],[10,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","1"],["4","0"],["5","0"],["6","0"],["7","0"],["8","0"],["9","1"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"]],"logBuckets10":[["10","2"]],"skewness":"0","kurtosis":"0","meanCI95":["6","-32.11861420852408","44.11861420852408"],"meanCI99":["6","-184.97022348861458","196.97022348861458"],"percentileCI":[["50","0.95",3,3,9],["99","0.95",9,9,9],["90","0.99",9,3,9]],"export":"ab00010101021a3b9aca000301040205030609070008fb3ff0000000000000091901b00a8403010601","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","3.20631895627745","3.614082426485135","4.244224543345755","5.066378477103939","6.000000000000661","6.933621522896061","7.755775456654244","8.385917573514865","8.79368104372255","9"],"densities":["0.4846864379515554","0.24524021229529785","0.15869436008848586","0.12163172356651711","0.10710978436929422","0.10710978436944585","0.12163172356651718","0.1586943600884858","0.24524021229529752","0.48468643795155636"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","3.354130868400228","4.141148961265419","5.326286696880615","6.673713303119386","7.858851038734582","8.645869131599772","9"],"densities":["0.4034021193986685","0.18151697419949023","0.12054054019551301","0.10602220721759137","0.12054054019551305","0.18151697419949037","0.40340211939866866"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","3.080949106595003","6.000000000000661","8.919050893404997","8.998766051082084","9"],"densities":["0.6176720423877602","0.15415969657006728","0.1541596965701371","0.6146886166679947","0.8104063186740486"],"corrections":0,"bucketCount":2}]},"three":{"count":3,"exceeds":0,"min":"1","max":"1023","mean":"338","stddev":"473.76435774197563","percentile":["1","1","1","1","5","5","1023","1023","1023","1023"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","5"],["75","5"],["90","1023"],["99","1023"],["99.9","1023"],["100","1023"]],"percentiles":[["0","1"],["50","5"],["75","992"],["100","992"]],"cdf":[[0,"0"],[1,"0.3333333333333333"],[2,"0.3333333333333333"],[7,"0.6666666666666666"],[42,"0.6666666666666666"],[100,"0.6666666666666666"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[1023,"1"],[1024,"1"]],"countAt":[[0,0],[1,1],[2,0],[7,0],[42,0],[100,0],[999,1],[1000,1],[5000,0],[1000000,0],[1023,1],[1024,0]],"linearStep":51,"linearBuckets":[["50","2"],["100","0"],["152","0"],["200","0"],["248","0"],["304","0"],["352","0"],["400","0"],["448","0"],["496","0"],["544","0"],["608","0"],["640","0"],["704","0"],["736","0"],["800","0"],["864","0"],["896","0"],["960","0"],["992","1"]],"logBuckets":[["1","1"],["2","0"],["4","0"],["8","1"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","1"]],"logBuckets10":[["10","2"],["100","0"],["992","1"]],"skewness":"0.7070689770495091","kurtosis":"0","meanCI95":["338","-1103.3972269336314","1779.3972269336314"],"meanCI99":["338","-2986.8422231807067","3662.8422231807067"],"percentileCI":[["50","0.95",5,1,1023],["99","0.95",1023,5,1023],["90","0.99",1023,1,1023]],"export":"ab00010101021a3b9aca00030104030501061903e8070008fb3ff0000000000000091901b00a8601010401186a01","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","5.969855369853938","22.868095056047416","63.408361238185954","140.86977250146083","263.99999999999994","429.5266395341117","617.8574274809569","796.613777800292","932.7772808892539","1008"],"densities":["0.020121309888931228","0.00591777616231257","0.002466683359964187","0.0012909653770717046","0.0008121482598672729","0.0006041323637177564","0.0005309806277039746","0.0005594206853147168","0.000734411187516712","0.0013293856056010914"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","11.152704694168948","55.65193114333037","171.24847258424063","378.74269643562354","644.816070525637","881.3096282460733","1008"],"densities":["0.014070845864274047","0.003210328678867067","0.0012358254068541269","0.0006884873236734717","0.000536908825791843","0.0006040635704166496","0.0011276085220952488"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.568453304744481","263.99999999999994","978.2503695573598","1007.5518963580809","1008"],"densities":["0.031878539098838886","0.0017212918857285205","0.0006300311755930586","0.0016722678082015262","0.002231626584682913"],"corrections":0,"bucketCount":3}]},"four":{"count":4,"exceeds":0,"min":"2","max":"67","mean":"19.5","stddev":"26.95830113341714","percentile":["2","2","2","2","2","8","67","67","67","67"],"percentilesAt":[["0.1","2"],["1","2"],["12","2"],["25","2"],["50","2"],["75","8"],["90","67"],["99","67"],["99.9","67"],["100","67"]],"percentiles":[["0","2"],["50","2"],["75","8"],["87.5","64"],["100","64"]],"cdf":[[0,"0"],[1,"0"],[2,"0.5"],[7,"0.5"],[42,"0.75"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[67,"1"],[68,"1"]],"countAt":[[0,0],[1,0],[2,2],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[67,1],[68,0]],"linearStep":3,"linearBuckets":[["3","2"],["6","0"],["9","1"],["12","0"],["15","0"],["18","0"],["21","0"],["24","0"],["27","0"],["30","0"],["32","0"],["36","0"],["38","0"],["42","0"],["44","0"],["48","0"],["50","0"],["54","0"],["56","0"],["60","0"],["62","0"],["64","1"]],"logBuckets":[["1","0"],["2","2"],["4","0"],["8","1"],["16","0"],["32","0"],["64","1"]],"logBuckets10":[["10","3"],["100","1"]],"skewness":"1.1268023469375672","kurtosis":"-0.6899320987062652","meanCI95":["19.5","-30.03281131469185","69.03281131469186"],"meanCI99":["19.5","-71.41014615546047","110.41014615546047"],"percentileCI":[["50","0.95",2,2,67],["99","0.95",67,8,67],["90","0.99",67,2,67]],"export":"ab00010101021a3b9aca00030104040502061840070008fb3ff0000000000000091901b00a8602020601182801","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["2","2.1226221134943657","2.6015625","3.9093313994403394","6.820312500000001","12.3419298579091","21.304687500000007","33.565358690164324","47.27343750000002","59.10646724171557","66"],"densities":["0.8155135900882581","0.20879425251564915","0.07646610960300024","0.034352679232707545","0.018110635619609017","0.01115728038102692","0.00815616033159925","0.007294968272888028","0.008450921039052674","0.014506350155487858"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["2","2.263013477834404","3.645806465759435","8.08627811654467","18.37346577976572","35.501367898773225","54.49630752291521","66"],"densities":["0.5431552178747554","0.10331057801465214","0.03217161465987077","0.013886899659455854","0.008340609484135756","0.007520800049060212","0.012418372895634381"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["2","2.083482446632761","12.507047221593444","63.28609244068954","65.96016682463531","66"],"densities":["0.5989283018974013","0.0431714110973803","0.008861923221643636","0.018324097599595364","0.02510470206918055"],"corrections":0,"bucketCount":3}]},"constant":{"count":100,"exceeds":0,"min":"42","max":"43","mean":"43","stddev":"0","percentile":["43","43","43","43","43","43","43","43","43","43"],"percentilesAt":[["0.1","43"],["1","43"],["12","43"],["25","43"],["50","43"],["75","43"],["90","43"],["99","43"],["99.9","43"],["100","43"]],"percentiles":[["0","42"],["100","42"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[43,"1"],[44,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,100],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[43,100],[44,0]],"linearStep":2,"linearBuckets":[["2","0"],["4","0"],["6","0"],["8","0"],["10","0"],["12","0"],["14","0"],["16","0"],["18","0"],["20","0"],["22","0"],["24","0"],["26","0"],["28","0"],["30","0"],["32","0"],["34","0"],["36","0"],["38","0"],["40","0"],["42","100"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","100"]],"logBuckets10":[["10","0"],["100","100"]],"skewness":"0","kurtosis":"0","meanCI95":["43","43","43"],"meanCI99":["43","43","43"],"percentileCI":[["50","0.95",43,43,43],["99","0.95",43,43,43],["90","0.99",43,43,43]],"export":"ab00010101021a3b9aca00030104186405182a06182a070008fb3ff0000000000000091901b00a8218251864","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["42","42.2","42.4","42.6","42.8","43","43.2","43.4","43.6","43.8","44"],"densities":["0.49999999999999295","0.5000000000000107","0.4999999999999928","0.5000000000000109","0.4999999999999928","0.4999999999999928","0.5000000000000105","0.49999999999999334","0.5000000000000105","0.4999999999999928"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["43","43","43","43","43","43","43","43"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["42","42.1","43","43.9","43.998","44"],"densities":["0.49999999999999295","0.5000000000000008","0.5000000000000008","0.5000000000000057","0.4999999999993894"],"corrections":0,"bucketCount":1}]},"uniform":{"count":2000,"exceeds":0,"min":"3","max":"10239","mean":"4965.8495","stddev":"2880.967125437177","percentile":["9","95","1151","2559","5119","7423","9215","10239","10239","10239"],"percentilesAt":[["0.1","9"],["1","95"],["12","1151"],["25","2559"],["50","5119"],["75","7423"],["90","9215"],["99","10239"],["99.9","10239"],["100","10239"]],"percentiles":[["0","3"],["50","4864"],["75","7168"],["87.5","8704"],["93.75","9216"],["96.875","9216"],["98.4375","9728"],["100","9728"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.0005"],[42,"0.004"],[100,"0.0125"],[999,"0.1065"],[1000,"0.1065"],[5000,"0.5105"],[1000000,"1"],[10239,"1"],[10240,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,2],[1000,2],[5000,53],[1000000,0],[10239,49],[10240,0]],"linearStep":511,"linearBuckets":[["496","108"],["992","105"],["1472","104"],["1984","111"],["2432","104"],["2944","80"],["3456","114"],["3968","89"],["4352","107"],["4864","99"],["5376","97"],["5888","104"],["6400","121"],["6912","120"],["7424","94"],["7936","98"],["8192","90"],["8704","118"],["9216","88"],["9728","49"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"],["32","3"],["64","7"],["128","18"],["256","23"],["512","67"],["1024","106"],["2048","224"],["4096","418"],["8192","877"],["16384","255"]],"logBuckets10":[["10","2"],["100","23"],["992","188"],["9728","1787"]],"skewness":"-0.012374035249624082","kurtosis":"-1.185480232159072","meanCI95":["4965.8495","4839.479777355637","5092.219222644363"],"meanCI99":["4965.8495","4799.713464973529","5131.985535026472"],"percentileCI":[["50","0.95",5119,4863,5375],["99","0.95",10239,10239,10239],["90","0.99",9215,9215,9215]],"export":"ab00010101021a3b9aca000301041907d005030619270b070008fb3ff0000000000000091901b00a98e8030106010a01070102010803030106010101020101030101010101010101020201040201010101010301010101020202010101010105030101020201010101020102010301050109010402050102010101030105010301030104010501010105010c01060106010c0106010a010a010601030106010901030104010701030102010d010f010d010e010b010c010b010f010e010b010e010c010d0111010e0110011601120117011829010f010e011701181c011824011501181f01181a01170110011301181f01183501183601182e01183501182c01183501183b01182d01183701184201183801184001183101182d01183801182a01185a011876011858011831","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","919.6944780814778","1931.9235250968793","3010.4916118460246","4028.158276390453","5020.055792002014","6019.2469907616905","6911.627829239531","7880.15324623505","8942.643757468326","9984"],"densities":["0.00010908759940311519","0.00009879186958214058","0.00009271551905582952","0.00009826400282529282","0.00010081686709170184","0.00010008094559292825","0.0001120597795113719","0.00010324974259344892","0.00009411848759376296","0.00009602861721641656"],"corrections":0,"bucketCount":116},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","1379.4190366067123","2860.4718834112323","4298.700339277674","5742.129070297584","7032.364750241886","8459.301195431628","9984"],"densities":["0.00010378899089432005","0.0000964564790279885","0.00009932854705692806","0.0000989706937288145","0.00011072174260698622","0.00010011457997216328","0.00009369532030136563"],"corrections":0,"bucketCount":116},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","474.896642325384","5020.055792002014","9431.27393761352","9973.551020408162","9984"],"densities":["0.00010595540530573178","0.00009900643413817881","0.00010201263803915066","0.0000903597101088561","0.00009570312499998852"],"corrections":0,"bucketCount":116}]},"wide":{"count":4900,"exceeds":100,"min":"1","max":"1006632959","mean":"38642475.81734694","stddev":"130621072.63541365","percentile":["1","1","7","103","17407","3801087","83886079","805306367","1006632959","1006632959"],"percentilesAt":[["0.1","1"],["1","1"],["12","7"],["25","103"],["50","17407"],["75","3801087"],["90","83886079"],["99","805306367"],["99.9","1006632959"],["100","1006632959"]],"percentiles":[["0","1"],["50","16384"],["75","3670016"],["87.5","48234496"],["93.75","201326592"],["96.875","402653184"],["98.4375","603979776"],["99.21875","805306368"],["99.609375","905969664"],["99.8046875","939524096"],["99.90234375","973078528"],["100","973078528"]],"cdf":[[0,"0"],[1,"0.02959183673469388"],[2,"0.0646938775510204"],[7,"0.12714285714285714"],[42,"0.21061224489795918"],[100,"0.25204081632653064"],[999,"0.3579591836734694"],[1000,"0.3579591836734694"],[5000,"0.44142857142857145"],[1000000,"0.6912244897959183"],[1006632959,"1"],[1006632960,"1"]],"countAt":[[0,0],[1,145],[2,172],[7,39],[42,11],[100,13],[999,7],[1000,7],[5000,10],[1000000,10],[1006632959,7],[1006632960,0]],"linearStep":50331647,"linearBuckets":[["48234496","4292"],["96468992","162"],["142606336","97"],["192937984","42"],["243269632","62"],["285212672","28"],["335544320","32"],["385875968","25"],["436207616","22"],["486539264","22"],["536870912","23"],["570425344","7"],["637534208","15"],["671088640","7"],["738197504","14"],["771751936","7"],["838860800","15"],["872415232","8"],["939524096","13"],["973078528","7"]],"logBuckets":[["1","145"],["2","172"],["4","192"],["8","164"],["16","172"],["32","144"],["64","153"],["128","144"],["256","165"],["512","173"],["1024","134"],["2048","182"],["4096","191"],["8192","161"],["16384","166"],["32768","152"],["65536","178"],["131072","172"],["262144","141"],["524288","140"],["1048576","160"],["2097152","157"],["4194304","161"],["8388608","171"],["16777216","169"],["33554432","164"],["67108864","164"],["134217728","155"],["268435456","135"],["536870912","130"],["1040187392","93"]],"logBuckets10":[["10","720"],["100","515"],["992","519"],["9728","567"],["98304","564"],["983040","502"],["9961472","524"],["96468992","543"],["973078528","446"]],"skewness":"4.644956072964218","kurtosis":"23.336419175626176","meanCI95":["38642475.81734694","34983875.83245061","42301075.80224326"],"meanCI99":["38642475.81734694","33833574.75108084","43451376.883613035"],"percentileCI":[["50","0.95",17407,13823,24575],["99","0.95",805306367,738197503,872415231],["90","0.99",83886079,62914559,104857599]],"export":"ab00010101021a3b9aca000301041913240501061a3af1c583070008fb3ff0000000000000091901b00a99035a0118910118ac01186c01185401182501182601182701183201181901160111010b0116011819011701181b010801070111011001070106010401090106010c010c0108010b010301090109010701060109010a010b010d010f010301080105010b010a0105010c01090113010c010a0107010d010b01070108010c010d010a010701090108010401070106010e010401090111010a01090111010901090105010b0106010d010a010e0108010b0106010a010e010a010a010c0108010d0108010c0108010c0111010f0107010e01070108010d0109010a010901080107010701090106010b0105010701040109010d010f010a010f010f010a010b010b0109010a0105010801130108010e0109010a010d01130109010b0114010e01070112010f010b0105010b0104010f010b010b010a010c0107010a010d010a010d0108010e01040109010801100105010c010a01070107010c0106010d010701090108010a0111010c010b010d010c0108010f010b0109010801070109010b0106010a0108010c010f010b01060106010c0107010b01100111010d010b010c0108010d010e010601040108010a01100108010f010d010c0107010b010e011101060108010a010c010a01090108010c010a01050106010d010e010701040106010b010a0108010a010a010a0108010901070105010d010a010b010b010a01070108010a0106010a0108010a01060108010b01070105010c0106010c010f010b010a010a010f010a010c010a010a0104010e010a010b010a010d0106010c010c01090109010601060105010d0107010e010b010a0109010b010c010c01090106010d010a010c0107010a010a010a0109010701090108010a010c0109010c010e010b010c010d010901130109010a01070104010b0106010f01150106010c010b010a010f010a010d010b010a01050109010c0106010d0111010c010d01090110010b010501080107010a010a010a0105010a0106010b010a0106010f010b010e0105010d010b010b010d011001090103010c010a010601080109010b010b010b0107010c01090108010b01090107010e0109010701040107010a01080106010b01070108010d010a010d010a010601060106010b010e01070107010a01080108010501090103010a010901080104010b0107010a01050107010701070107010601090108010701060107","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","4.186208696018375","33.059229260359515","305.2697884743773","2418.903805062617","17395.80908909345","131780.18379050118","1281283.561322101","10583817.024599481","81285350.63009177","989855744"],"densities":["0.0313852636598991","0.0034634408886025027","0.00036736267795320105","0.00004731188049358557","0.000006676946812678668","8.742452827236488e-7","8.699408975616602e-8","1.0749759771868537e-8","1.4143964762913105e-9","1.100630184845642e-10"],"corrections":0,"bucketCount":429},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","9.695833830730308","224.7255565626656","3908.7642999874543","74971.57891633725","1751585.007378766","32122816.163496446","989855744"],"densities":["0.01642822823411118","0.0006643599826207947","0.00003877731826575166","0.0000020102939016473312","8.52057727988931e-8","4.703699435917238e-9","1.491617743370836e-10"],"corrections":0,"bucketCount":429},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.0813954317436707","17395.80908909345","255151772.90388113","977253778.2173499","989855744"],"densities":["0.046236555595004385","0.000025871395018100722","1.7637764268544963e-9","6.785744900227615e-11","7.935269919370526e-11"],"corrections":0,"bucketCount":429}]},"bimodal":{"count":3000,"exceeds":0,"min":"100","max":"5631","mean":"2660.637333333333","stddev":"2563.524743097458","percentile":["103","103","115","127","151","5375","5631","5631","5631","5631"],"percentilesAt":[["0.1","103"],["1","103"],["12","115"],["25","127"],["50","151"],["75","5375"],["90","5631"],["99","5631"],["99.9","5631"],["100","5631"]],"percentiles":[["0","100"],["50","144"],["75","5120"],["87.5","5120"],["93.75","5376"],["100","5376"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0.035"],[999,"0.505"],[1000,"0.505"],[5000,"0.6266666666666667"],[1000000,"1"],[5631,"1"],[5632,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,105],[999,0],[1000,0],[5000,365],[1000000,0],[5631,358],[5632,0]],"linearStep":281,"linearBuckets":[["272","1515"],["544","0"],["832","0"],["1088","0"],["1344","0"],["1664","0"],["1920","0"],["2176","0"],["2432","0"],["2688","0"],["3072","0"],["3328","0"],["3584","0"],["3840","0"],["4096","0"],["4352","0"],["4608","0"],["4864","365"],["5120","762"],["5376","358"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","1083"],["256","432"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","1485"]],"logBuckets10":[["10","0"],["100","105"],["992","1410"],["9728","1485"]],"skewness":"0.02715658730292046","kurtosis":"-1.9896384636837328","meanCI95":["2660.637333333333","2568.852128262939","2752.4225384037272"],"meanCI99":["2660.637333333333","2539.9830216993355","2781.2916449673307"],"percentileCI":[["50","0.95",151,151,5119],["99","0.95",5631,5631,5631],["90","0.99",5631,5631,5631]],"export":"ab00010101021a3b9aca00030104190bb80518640619157b070008fb3ff0000000000000091901b00a981a1839186901187301187401187201187601187c01188c0118fb0118f90118b7185119016d011902fa01190166","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["102","110.75868372025104","121.0311739992529","130.1674036213862","139.7590361848508","1530.1779398955282","5063.886784419444","5193.910761154855","5294.698162729659","5396.737469434764","5504"],"densities":["0.011417240671539375","0.009734737856545985","0.010945434181923515","0.010425753836829522","0.00007192077131080799","0.000028298879279476302","0.0007690889212186772","0.0009921874999999937","0.0009800144986186726","0.0009322920079643341"],"corrections":0,"bucketCount":13},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["102","114.52931146305471","131.07190180413352","140.35289602655075","4992.000000071814","5248","5248.014622227501","5504"],"densities":["0.011401835071175854","0.008635717859880617","0.015392439584984001","0.000029445081184497063","0.0005580357144422559","9.769861866145503","0.0005580675900328326"],"corrections":0,"bucketCount":13},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["102","105.565217852881","1530.1779398955282","5450.368715083799","5502.927374301676","5504"],"densities":["0.014024388428212223","0.00031587532038516276","0.00011479033185021163","0.0009322916666666693","0.0009322916666663628"],"corrections":0,"bucketCount":13}]},"heavy":{"count":3000,"exceeds":0,"min":"992","max":"1015807","mean":"7766.672","stddev":"40733.328232187","percentile":["1023","1023","1151","1343","1983","4095","10751","102399","507903","1015807"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","1151"],["25","1343"],["50","1983"],["75","4095"],["90","10751"],["99","102399"],["99.9","507903"],["100","1015807"]],"percentiles":[["0","992"],["50","1920"],["75","3968"],["87.5","8192"],["93.75","15872"],["96.875","32768"],["98.4375","61440"],["99.21875","139264"],["99.609375","327680"],["99.8046875","491520"],["99.90234375","983040"],["100","983040"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0.028333333333333332"],[1000,"0.028333333333333332"],[5000,"0.7986666666666666"],[1000000,"1"],[1015807,"1"],[1015808,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,85],[1000,85],[5000,27],[1000000,3],[1015807,3],[1015808,0]],"linearStep":50790,"linearBuckets":[["49152","2943"],["98304","28"],["147456","11"],["196608","5"],["245760","1"],["294912","0"],["344064","6"],["393216","0"],["442368","0"],["491520","3"],["557056","0"],["589824","0"],["655360","0"],["688128","0"],["753664","0"],["786432","0"],["851968","0"],["884736","0"],["950272","0"],["983040","3"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","236"],["2048","1389"],["4096","668"],["8192","344"],["16384","189"],["32768","82"],["65536","52"],["131072","16"],["262144","12"],["524288","9"],["1048576","3"]],"logBuckets10":[["10","0"],["100","0"],["992","85"],["9728","2608"],["98304","278"],["983040","29"]],"skewness":"17.60198595474343","kurtosis":"379.4461691612938","meanCI95":["7766.672","6308.243757769135","9225.100242230863"],"meanCI99":["7766.672","5849.525816157075","9683.818183842925"],"percentileCI":[["50","0.95",1983,1919,2175],["99","0.95",102399,69631,147455],["90","0.99",10751,9727,12799]],"export":"ab00010101021a3b9aca00030104190bb8051903e8061a000f4240070008fb3ff0000000000000091901b00a98d8186f185501189701189401188c01187a01186801186c01186901185e01185901183b01184d01184401183e01183801182e01182101184e01184501184301184001183401183801183101182001182001182701182001182001182001181f0112011401182b01182601182601181b010d01181a01181901181c0110010f01130111011001100109010f01181a010c0114011818010f0106010f0109010d010d0108010d0109010901070109010701090108010c01040107010601040102010601010105010101060102010301060104010901040103010501030104010301020203010302060203010201010203020302020305060603020403060106060a031003","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1008","1115.6756761769657","1255.8834523587825","1433.6117751058587","1656.526894488578","1967.7015097415358","2511.24143159748","3390.9602683212242","5223.0206960585365","10607.682738916725","999424"],"densities":["0.0009287148551140682","0.0007132272026789962","0.0005626565223501784","0.0004486012446213292","0.0003213629746716604","0.00018397912642468829","0.00011367268248162195","0.000054583352429867817","0.000018571267649495753","1.0113101721155797e-7"],"corrections":0,"bucketCount":108},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1008","1183.453797359566","1406.9691477192048","1731.9336416750443","2344.2807769438564","3559.4998224148617","7451.306933336528","999424"],"densities":["0.0008142151666536966","0.000639137950155477","0.0004396084665069783","0.00023329437606404497","0.00011755670172349304","0.00003670714883485349","1.4401318086237126e-7"],"corrections":0,"bucketCount":108},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1008","1051.549668874193","1967.7015097415358","20279.78749167015","772959.9830408919","999424"],"densities":["0.0011481143552305947","0.0004911849542036332","0.000024573934419272874","6.51006898942589e-8","4.415712542008684e-9"],"corrections":0,"bucketCount":108}]},"huge":{"count":1,"exceeds":999,"min":"973078528","max":"1006632959","mean":"989855744","stddev":"0","percentile":["1006632959","1006632959","1006632959","1006632959","1006632959","1006632959","1006632959","1006632959","1006632959","1006632959"],"percentilesAt":[["0.1","1006632959"],["1","1006632959"],["12","1006632959"],["25","1006632959"],["50","1006632959"],["75","1006632959"],["90","1006632959"],["99","1006632959"],["99.9","1006632959"],["100","1006632959"]],"percentiles":[["0","973078528"],["100","973078528"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[1006632959,"1"],[1006632960,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[1006632959,1],[1006632960,0]],"linearStep":50331647,"linearBuckets":[["48234496","0"],["96468992","0"],["142606336","0"],["192937984","0"],["243269632","0"],["285212672","0"],["335544320","0"],["385875968","0"],["436207616","0"],["486539264","0"],["536870912","0"],["570425344","0"],["637534208","0"],["671088640","0"],["738197504","0"],["771751936","0"],["838860800","0"],["872415232","0"],["939524096","0"],["973078528","1"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","0"],["16384","0"],["32768","0"],["65536","0"],["131072","0"],["262144","0"],["524288","0"],["1048576","0"],["2097152","0"],["4194304","0"],["8388608","0"],["16777216","0"],["33554432","0"],["67108864","0"],["134217728","0"],["268435456","0"],["536870912","0"],["1040187392","1"]],"logBuckets10":[["10","0"],["100","0"],["992","0"],["9728","0"],["98304","0"],["983040","0"],["9961472","0"],["96468992","0"],["973078528","1"]],"skewness":"0","kurtosis":"0","meanCI95":["989855744","NaN","NaN"],"meanCI99":["989855744","NaN","NaN"],"percentileCI":[["50","0.95",1006632959,1006632959,1006632959],["99","0.95",1006632959,1006632959,1006632959],["90","0.99",1006632959,1006632959,1006632959]],"export":"ab00010101021a3b9aca0003010401051a3aecbecb061a3aecbecb070008fb3ff0000000000000091901b00a821901ad01","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["989855744","989855744","989855744","989855744","989855744","989855744","989855744","989855744","989855744","989855744","989855744"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["989855744","989855744","989855744","989855744","989855744","989855744","989855744","989855744"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["989855744","989855744","989855744","989855744","989855744","989855744"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]}},"comparisons":[{"a":"uniform","b":"wide","welch":["-20.703778075604504","4899.000011681059","0","-42296109.954919815","-34978909.98077406"],"mannWhitney":["4150735.5","-9.98091282070508","1.8475914047410394e-23"],"cohensD":"-0.35096146906821196","cliffsD":"-0.1529111224489796","ksTest":"0.5263265306122449"},{"a":"bimodal","b":"heavy","welch":["-6.851154041338736","3022.756085634971","8.840705945090122e-12","-6567.343638050579","-3644.725695282754"],"mannWhitney":["3567393.5","-13.92236562439515","4.633116129156094e-44"],"cohensD":"-0.17689603669604376","cliffsD":"-0.2072458888888889","ksTest":"0.505"},{"a":"uniform","b":"constant","welch":["76.39846860605205","1999.0000000000002","0","4796.479777355637","5049.219222644363"],"mannWhitney":["199200","16.770160373796383","4.034184798335755e-63"],"cohensD":"1.7501126354964973","cliffsD":"0.992","ksTest":"0.996"},{"a":"three","b":"four","welch":["0.9497161522188302","2.0086374166514203","0.44212625683761464","-1118.5222617222403","1755.5222617222403"],"mannWhitney":["6","0","1"],"cohensD":"0.8660360753908358","cliffsD":"0","ksTest":"0.33333333333333337"},{"a":"empty","b":"uniform","welch":["0","0","1","0","0"],"mannWhitney":["0","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"},{"a":"one","b":"two","welch":["0","0","1","0","0"],"mannWhitney":["1","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0.5"}]},{"options":{"lowest":1,"highest":1000000000,"figures":2},"histograms":{"empty":{"count":0,"exceeds":0,"min":"9223372036854775807","max":"0","mean":"NaN","stddev":"NaN","percentile":["0","0","0","0","0","0","0","0","0","0"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","1"],["75","1"],["90","1"],["99","1"],["99.9","1"],["100","1"]],"percentiles":[["100","0"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[1,"0"],[2,"0"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[1,0],[2,0]],"linearStep":1,"linearBuckets":[],"logBuckets":[],"logBuckets10":[],"skewness":"0","kurtosis":"0","meanCI95":["NaN","NaN","NaN"],"meanCI99":["NaN","NaN","NaN"],"percentileCI":[["50","0.95",0,0,0],["99","0.95",0,0,0],["90","0.99",0,0,0]],"export":"ab00010101021a3b9aca0003020400051b7fffffffffffffff0600070008fb3ff000000000000009190c000a80"},"one":{"count":1,"exceeds":0,"min":"7","max":"7","mean":"7","stddev":"0","percentile":["7","7","7","7","7","7","7","7","7","7"],"percentilesAt":[["0.1","7"],["1","7"],["12","7"],["25","7"],["50","7"],["75","7"],["90","7"],["99","7"],["99.9","7"],["100","7"]],"percentiles":[["0","7"],["100","7"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[7,"1"],[8,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,1],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[7,1],[8,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","0"],["4","0"],["5","0"],["6","0"],["7","1"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","1"]],"logBuckets10":[["10","1"]],"skewness":"0","kurtosis":"0","meanCI95":["7","NaN","NaN"],"meanCI99":["7","NaN","NaN"],"percentileCI":[["50","0.95",7,7,7],["99","0.95",7,7,7],["90","0.99",7,7,7]],"export":"ab00010101021a3b9aca000302040105070607070008fb3ff000000000000009190c000a820701","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["7","7","7","7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]},"two":{"count":2,"exceeds":0,"min":"3","max":"9","mean":"6","stddev":"3","percentile":["3","3","3","3","3","9","9","9","9","9"],"percentilesAt":[["0.1","3"],["1","3"],["12","3"],["25","3"],["50","3"],["75","9"],["90","9"],["99","9"],["99.9","9"],["100","9"]],"percentiles":[["0","3"],["50","3"],["75","9"],["100","9"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.5"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[9,"1"],[10,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[9,1],[10,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","1"],["4","0"],["5","0"],["6","0"],["7","0"],["8","0"],["9","1"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"]],"logBuckets10":[["10","2"]],"skewness":"0","kurtosis":"0","meanCI95":["6","-32.11861420852408","44.11861420852408"],"meanCI99":["6","-184.97022348861458","196.97022348861458"],"percentileCI":[["50","0.95",3,3,9],["99","0.95",9,9,9],["90","0.99",9,3,9]],"export":"ab00010101021a3b9aca000302040205030609070008fb3ff000000000000009190c000a8403010601","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","3.20631895627745","3.614082426485135","4.244224543345755","5.066378477103939","6.000000000000661","6.933621522896061","7.755775456654244","8.385917573514865","8.79368104372255","9"],"densities":["0.4846864379515554","0.24524021229529785","0.15869436008848586","0.12163172356651711","0.10710978436929422","0.10710978436944585","0.12163172356651718","0.1586943600884858","0.24524021229529752","0.48468643795155636"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","3.354130868400228","4.141148961265419","5.326286696880615","6.673713303119386","7.858851038734582","8.645869131599772","9"],"densities":["0.4034021193986685","0.18151697419949023","0.12054054019551301","0.10602220721759137","0.12054054019551305","0.18151697419949037","0.40340211939866866"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","3.080949106595003","6.000000000000661","8.919050893404997","8.998766051082084","9"],"densities":["0.6176720423877602","0.15415969657006728","0.1541596965701371","0.6146886166679947","0.8104063186740486"],"corrections":0,"bucketCount":2}]},"three":{"count":3,"exceeds":0,"min":"1","max":"1003","mean":"336","stddev":"470.935947520113","percentile":["1","1","1","1","5","5","1003","1003","1003","1003"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","5"],["75","5"],["90","1003"],["99","1003"],["99.9","1003"],["100","1003"]],"percentiles":[["0","1"],["50","5"],["75","1000"],["100","1000"]],"cdf":[[0,"0"],[1,"0.3333333333333333"],[2,"0.3333333333333333"],[7,"0.6666666666666666"],[42,"0.6666666666666666"],[100,"0.6666666666666666"],[999,"0.6666666666666666"],[1000,"1"],[5000,"1"],[1000000,"1"],[1003,"1"],[1004,"1"]],"countAt":[[0,0],[1,1],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,1],[5000,0],[1000000,0],[1003,1],[1004,0]],"linearStep":50,"linearBuckets":[["50","2"],["100","0"],["150","0"],["200","0"],["250","0"],["300","0"],["350","0"],["400","0"],["450","0"],["500","0"],["548","0"],["600","0"],["648","0"],["700","0"],["748","0"],["800","0"],["848","0"],["900","0"],["948","0"],["1000","1"]],"logBuckets":[["1","1"],["2","0"],["4","0"],["8","1"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","1"]],"logBuckets10":[["10","2"],["100","0"],["1000","1"]],"skewness":"0.7070685215898443","kurtosis":"0","meanCI95":["336","-1096.7919729000562","1768.7919729000562"],"meanCI99":["336","-2968.9926554020217","3640.9926554020217"],"percentileCI":[["50","0.95",5,1,1003],["99","0.95",1003,5,1003],["90","0.99",1003,1,1003]],"export":"ab00010101021a3b9aca00030204030501061903e8070008fb3ff000000000000009190c000a86010104011901f501","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","5.941919544895788","22.74231989984404","63.044332764636984","140.04685133773242","262.4444444444444","426.98381648141026","614.1898295958323","791.8777999663673","927.227155835779","1002"],"densities":["0.020235052208262678","0.005952239106644076","0.0024812656463458686","0.0012986588212056205","0.0008170095298590984","0.0006077572726941835","0.0005341708759049265","0.0005627842998682961","0.0007388287839100057","0.0013373839275175007"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","11.094992157473946","55.333659637153715","170.24503306455568","376.50294575098496","640.9873870668348","876.0672667110551","1002"],"densities":["0.014151288146507067","0.0032292370226287147","0.0012431941120901865","0.0006926141208183676","0.0005401343918243627","0.0006076961715028472","0.0011343924579907754"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.5597803210782635","262.4444444444444","972.4282988601725","1001.5545765286427","1002"],"densities":["0.032055796142776954","0.001731537339911627","0.0006338172300697198","0.0016823296322909","0.0022450545700986283"],"corrections":0,"bucketCount":3}]},"four":{"count":4,"exceeds":0,"min":"2","max":"64","mean":"19","stddev":"26.095976701399778","percentile":["2","2","2","2","2","8","64","64","64","64"],"percentilesAt":[["0.1","2"],["1","2"],["12","2"],["25","2"],["50","2"],["75","8"],["90","64"],["99","64"],["99.9","64"],["100","64"]],"percentiles":[["0","2"],["50","2"],["75","8"],["87.5","64"],["100","64"]],"cdf":[[0,"0"],[1,"0"],[2,"0.5"],[7,"0.5"],[42,"0.75"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[64,"1"],[65,"1"]],"countAt":[[0,0],[1,0],[2,2],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[64,1],[65,0]],"linearStep":3,"linearBuckets":[["3","2"],["6","0"],["9","1"],["12","0"],["15","0"],["18","0"],["21","0"],["24","0"],["27","0"],["30","0"],["33","0"],["36","0"],["39","0"],["42","0"],["45","0"],["48","0"],["51","0"],["54","0"],["57","0"],["60","0"],["63","0"],["66","1"]],"logBuckets":[["1","0"],["2","2"],["4","0"],["8","1"],["16","0"],["32","0"],["64","1"]],"logBuckets10":[["10","3"],["100","1"]],"skewness":"1.1249561006949362","kurtosis":"-0.6915329232082903","meanCI95":["19","-28.948388276616278","66.94838827661627"],"meanCI99":["19","-69.00217210471634","107.00217210471634"],"percentileCI":[["50","0.95",2,2,64],["99","0.95",64,8,64],["90","0.99",64,2,64]],"export":"ab00010101021a3b9aca00030204040502061840070008fb3ff000000000000009190c000a8602020601183801","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["2","2.1214881848725766","2.59375","3.8766929625087334","6.718750000000001","12.088759862808779","20.781250000000007","32.65059402055783","45.90625000000002","57.34108084774391","64"],"densities":["0.8231253113616391","0.21174695221339987","0.07794578786609092","0.03518578222774577","0.018621939727256866","0.01150418331476101","0.008425065431316082","0.007543949552937039","0.008745210255535187","0.015017452189086762"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["2","2.260178895464949","3.6186449301918753","7.9516758986192855","17.940490621956265","34.523454456253454","52.88675172594847","64"],"densities":["0.5490727547361286","0.10516062912523202","0.03296933345228088","0.01430171114530577","0.00861469302379368","0.007779493015826749","0.012854670329889233"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["2","2.083206032793557","12.253877226493122","61.37877112059458","63.96152982082045","64"],"densities":["0.6009179661774692","0.04424486756378103","0.009160325129048932","0.01897196203257965","0.025994160186590367"],"corrections":0,"bucketCount":3}]},"constant":{"count":100,"exceeds":0,"min":"42","max":"42","mean":"42","stddev":"0","percentile":["42","42","42","42","42","42","42","42","42","42"],"percentilesAt":[["0.1","42"],["1","42"],["12","42"],["25","42"],["50","42"],["75","42"],["90","42"],["99","42"],["99.9","42"],["100","42"]],"percentiles":[["0","42"],["100","42"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[42,"1"],[43,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,100],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[42,100],[43,0]],"linearStep":2,"linearBuckets":[["2","0"],["4","0"],["6","0"],["8","0"],["10","0"],["12","0"],["14","0"],["16","0"],["18","0"],["20","0"],["22","0"],["24","0"],["26","0"],["28","0"],["30","0"],["32","0"],["34","0"],["36","0"],["38","0"],["40","0"],["42","100"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","100"]],"logBuckets10":[["10","0"],["100","100"]],"skewness":"0","kurtosis":"0","meanCI95":["42","42","42"],"meanCI99":["42","42","42"],"percentileCI":[["50","0.95",42,42,42],["99","0.95",42,42,42],["90","0.99",42,42,42]],"export":"ab00010101021a3b9aca00030204186405182a06182a070008fb3ff000000000000009190c000a82182a1864","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["42","42","42","42","42","42","42","42","42","42","42"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["42","42","42","42","42","42","42","42"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["41.5","41.55","42","42.45","42.499","42.5"],"densities":["1.0000000000000568","0.9999999999999937","0.9999999999999936","1.0000000000000113","1.0000000000023315"],"corrections":0,"bucketCount":1}]},"uniform":{"count":2000,"exceeds":0,"min":"3","max":"10047","mean":"4966.35","stddev":"2879.053865682267","percentile":["9","93","1151","2463","5023","7359","8959","9919","9983","10047"],"percentilesAt":[["0.1","9"],["1","93"],["12","1151"],["25","2463"],["50","5023"],["75","7359"],["90","8959"],["99","9919"],["99.9","9983"],["100","10047"]],"percentiles":[["0","3"],["50","4992"],["75","7328"],["87.5","8704"],["93.75","9280"],["96.875","9664"],["98.4375","9792"],["99.21875","9856"],["99.609375","9920"],["99.8046875","9920"],["99.90234375","9920"],["99.951171875","9984"],["100","9984"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.0005"],[42,"0.004"],[100,"0.0125"],[999,"0.106"],[1000,"0.106"],[5000,"0.5005"],[1000000,"1"],[10047,"1"],[10048,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,1],[1000,0],[5000,9],[1000000,0],[10047,1],[10048,0]],"linearStep":502,"linearBuckets":[["502","105"],["1004","107"],["1504","98"],["2008","113"],["2496","92"],["3008","85"],["3504","108"],["4016","94"],["4512","101"],["4992","98"],["5504","97"],["6016","108"],["6496","101"],["7008","124"],["7520","107"],["8032","92"],["8512","94"],["9024","103"],["9536","96"],["9984","77"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"],["32","3"],["64","7"],["128","17"],["256","21"],["512","60"],["1024","106"],["2048","217"],["4096","389"],["8192","847"],["16384","331"]],"logBuckets10":[["10","2"],["100","23"],["1000","187"],["9984","1788"]],"skewness":"-0.01608365801403515","kurtosis":"-1.1890355359442557","meanCI95":["4966.35","4840.0641999045765","5092.635800095424"],"meanCI99":["4966.35","4800.324296461469","5132.3757035385315"],"percentileCI":[["50","0.95",5023,4799,5247],["99","0.95",9919,9855,9983],["90","0.99",8959,8831,9151]],"export":"ab00010101021a3b9aca000302041907d005030619270b070008fb3ff000000000000009190c000a990434030106010a01070102010c02010106010b0103010301050101010101020106010301040107010101020203020801020104010b010501080101011201010106010501080101020502160104010201130108010601020105010401050201010701020302010101020101010102010202020101030102020b01010102010202070102010401070104020703010102010201010105010601010202020101050102010401010101010101070103010201020102020102020201020101010101010103010101010101010202010301040101030101020101030202020201010103020101010101010103020101010201010101010201020201030201020102010201010101040101010102010205010101010103020101010101010401010301010101020303010201030103010402030101010103010201010501010105020401030104030101010101020101010401010202010101010101010302030104010201030102010101020203030101020103010401010101010201030101010102010203010201030401010501020101010101020101020301010103020101010101010101040102010502020102030301030101010301010101010201010101010402010103020301030101020101030301010401020102010201010201010201030103010102030201010301030103010301020102010101010102010101010102010301030203010401040102010101010101010501050102010301020102010201010102010101020103010301040102010101020103010501030105010201030205010501060103010501080106010301020104010101020203020301010102010301010101010201020102010502010102010201040106010301030206010301040105010501020107010101040104010501050105010501030105010201010202010601020105010301050203010501060104010201010104010101040107010501020102010301040102010301010104010401020302010101070103010101020103010502020203010401030108010401030104010101050103010701030109010301060105010a010a010401070103010701050109010a01090107010501040104010a010501070104010401060106010801090103010a01070106010b010501040104010201070105010a01020106010801070108010501070103010901070108010b0108010801050105010401070108010501060103010701060106010501020106010b0108010b01060107010a0107010801060109010d010b0107010301090106010401080108010c010c010501030108010901070108010701060104010a0107010501050105010201080106010a01040102010801050105010a0105010b010601080105010601030103010601050106010701040108010e0109010c010b010c010b0108010d010d010d0111010c010d010e01110113010b0108010a0108010b010c010a01120110010a0109010d0101","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","917.3162797359732","1934.4336257994942","3014.8423499876903","4027.7209233419153","5024.956775414257","6020.865724006727","6904.67261394536","7879.298153335168","8959.4645514588","10016"],"densities":["0.00010937134361085309","0.00009831707264360706","0.0000925575643376432","0.0000987285175446474","0.00010027718096194733","0.00010041078568611237","0.00011314688891703865","0.00010260350868971483","0.00009257832883314185","0.00009464897759755608"],"corrections":0,"bucketCount":538},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","1381.86938388738","2863.8818079123826","4316.180755622795","5744.540961385512","7020.848895092376","8523.960139763794","10016"],"densities":["0.00010360455060245995","0.0000963940251385725","0.00009836620971347593","0.00010001478778307178","0.00011192999673850937","0.00009504096477461419","0.00009574619731307118"],"corrections":0,"bucketCount":538},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","475.8869275346162","5024.956775414257","9464.970695544318","9987.014317497194","10016"],"densities":["0.00010573352124719056","0.00009892132129159299","0.00010135103359919604","0.0000938618880481661","0.00003449979140229704"],"corrections":0,"bucketCount":538}]},"wide":{"count":4900,"exceeds":100,"min":"1","max":"989855743","mean":"38630357.7255102","stddev":"130493732.30629274","percentile":["1","1","7","100","16639","3686399","80740351","780140543","977272831","989855743"],"percentilesAt":[["0.1","1"],["1","1"],["12","7"],["25","100"],["50","16639"],["75","3686399"],["90","80740351"],["99","780140543"],["99.9","977272831"],["100","989855743"]],"percentiles":[["0","1"],["50","16512"],["75","3670016"],["87.5","49283072"],["93.75","201326592"],["96.875","417333248"],["98.4375","629145600"],["99.21875","826277888"],["99.609375","905969664"],["99.8046875","947912704"],["99.90234375","973078528"],["99.951171875","977272832"],["99.9755859375","985661440"],["100","985661440"]],"cdf":[[0,"0"],[1,"0.02959183673469388"],[2,"0.0646938775510204"],[7,"0.12714285714285714"],[42,"0.2089795918367347"],[100,"0.2504081632653061"],[999,"0.35714285714285715"],[1000,"0.35714285714285715"],[5000,"0.4410204081632653"],[1000000,"0.690204081632653"],[989855743,"1"],[989855744,"1"]],"countAt":[[0,0],[1,145],[2,172],[7,39],[42,3],[100,5],[999,3],[1000,0],[5000,1],[1000000,1],[989855743,2],[989855744,0]],"linearStep":49492787,"linearBuckets":[["49283072","4288"],["98566144","161"],["147849216","100"],["197132288","41"],["246415360","60"],["295698432","30"],["346030080","33"],["394264576","22"],["444596224","22"],["492830720","21"],["541065216","22"],["591396864","12"],["641728512","12"],["692060160","11"],["738197504","10"],["788529152","9"],["838860800","11"],["889192448","10"],["939524096","14"],["985661440","11"]],"logBuckets":[["1","145"],["2","172"],["4","192"],["8","164"],["16","172"],["32","139"],["64","149"],["128","149"],["256","162"],["512","174"],["1024","138"],["2048","170"],["4096","191"],["8192","171"],["16384","159"],["32768","158"],["65536","169"],["131072","176"],["262144","146"],["524288","138"],["1048576","163"],["2097152","148"],["4194304","167"],["8388608","173"],["16777216","165"],["33554432","170"],["67108864","164"],["134217728","145"],["268435456","143"],["536870912","126"],["1069547520","102"]],"logBuckets10":[["10","720"],["100","507"],["1000","523"],["9984","567"],["99840","557"],["999424","508"],["9961472","523"],["99614720","549"],["998244352","446"]],"skewness":"4.633289703309938","kurtosis":"23.176561774618747","meanCI95":["38630357.7255102","34975324.449390635","42285391.00162977"],"meanCI99":["38630357.7255102","33826144.77757091","43434570.673449494"],"percentileCI":[["50","0.95",16639,13823,24191],["99","0.95",780140543,708837375,843055103],["90","0.99",80740351,62390271,104333311]],"export":"ab00010101021a3b9aca000302041913240501061a3af1c583070008fb3ff000000000000009190c000a9911100118910118ac01186c01185401182501182601182701183201181901160111010b0116011819011701181b010801070111011001070106010401090106010c010c0108010b01030109010401050103010401040102010501040102010801030108010401090103010c010302030105010301020105010601030107010401010106010601030106010a010201050102010301040101010401020103010301020203010201020104010201050102010301030205010201030102020301010103010101040103010401010105010301030102010101050101010301020102010201010105030402050103030102030202010201030102010102010301010101020102010101020102010201010102020203010301020201010102010201010201010501010105020201020101010102040102010101010101010102020102020102010203010201010103010101010105010301010101020301020201010101010201030402010103030102010101020201010302010201010103010202020101020203010103010101030102020202020101010301020202010102010101010302030203010201020101010201020101010101010301010101020102010201010101010101010402020101020103010102020204040201030102010101030101010201020102010101010101010102020101020101020102010101010102010302010102010201010102010101010102010101010101020101010101020101030101010101020104010101010202030101030101010101020103020101020102010201010101020201010101020301030101010101010102010102010102010301030102010101030102010401010203010201020101010201010101010101010101010102010102010401010102010201020101020102020101030101020103050201010102010202030102020401010101010201030101020101010101030301010101010202010102020201010102010103030101010102010101010101030401010302020201010102010102020202010102010302020101030102020202010102010202010103050102010102020101030205030201010101010201010202010401020302010101020102030101010201020102010101030101010101050103010102020102010101010102020201030101020101010103010201020102010201010101010202030102010101020103010101030202010201020102030201020305010203020102010301020102010102010201020101020101010101020101020401030301010101010102040202010202010101010202010102010102010201050101020301020104010101010101020202010102020102030102010201020104010102010102020301010101010305030103010101010102010201020101010101020102010102040103010201050103010102020102020101020202010201010104010304010102010301020103010401010101010401010102010601010101010101020501020201010103010402010103010301010103010301030103010201010101010201030202010101010202010101030101020103010201010201050302010201010101020102010101010104010104010301040301010201020101010201010202010201010305010101030102030201020203010102010101010201020201010201030101010102020102010305010101010101020101020201020102010101040202020201020101010202010101010201030301010202030103010201020102010202010301010201010101010101030101020501020201030201010301020302010101010201010101020302020201010101010102010301010103010101020103010103010403010201020101010202020102010101030101010101020102010103010101030101030101020101010303010101010203030101030104010102010101020101010101010102010101010301010101010201020102020202010101020101020101010201030101030101010101020102020101020301010101010201040201010102040202010301040101010302010202010202020102010301020101010101010102010101020101010102050102020301010101010101030102010101010102010202010201010201010101010101010102010101020101010101020106020201020103020101020201010101010203010102030201040201030102030103010102010201010202010102020204030102010203010501010201010104030101020301010201010301010101020203020102020201020303010202030102020101030104010301020101010101020102010101030101010102010101010202010302020103020102030101010102010101020102010101010102020101010102010203010102010101020101020101020102010201010101010501030102010202030103010201030102010201010101010201040101010101010201010301020104030101020101010101030103010203020202010201010101020202010101020301020102010101020102010101020101010301010101020301030202030101020201020102020301050101020102010302030102010101010101020101010102010401010102010501020301010202020101010102010101010502020101010201030101020102010104010401010102010101040101010102020102010101030301010102020202010101010202010201030202010401030102010304030201010201020103010401020101030101020302020101010101030401010102020201020202020201010101010301010102020301010101010101020101030301020202020201010101020302020101010203020101010201020103010202010202010305030102010201010401010201010202040303010101010101020102010101030101010201020103010101020101010201020101010101010102020203010201020103020302010304010302010202020102010101020101010201010101030502010101030202010103010201020101010101020302020401020101020201010201010101030201010201010101010202010101020101010102010401010203040101010201010102010102010101010101030102010101010102030103010102030102010101010101010302020101010101020102010201010101010101020302010201020203010101010101020101020101010301020202010205010102020301030201020102030101010101010101010101020301010102030201020103030101010301010101010104010102010201020102010102040102030102020202010103010101010402020101050201020101010101010102010202020201020101020101040102020101010101020101010101030101010201020202040201010101020201020102010101010101010101020102010101030305010301010102020201020103020101010102010101010202010201010101010101030301010101010103020401020102010201030102020201020102010103020101010101030101010201020101010101010101010101010101010101020103020301010202010201010101040101010201010102010102010101040101010201020101020401020402010202010101010501010103040101010102010101020101010203020104010201020102010102010101010102030403010301010101020301010201010101010102010301010103010103020101020101040103010102030301010101010201010101020301040201010101020201020202010107010101010501010205010104020101010201010101010101010102010301010102010302010104010103030102010101020101010101020102010102010101010201030302020101020103010201020301010201010102010201030101020101010303010302040101010101040101020101010201010202020101030103020104010101030103030202010101010101010102010202030101010102030103010101020202020103020102010101030101010201010301010201010201010401010201020201020201020401020101020101020101010101010302010101010101010101010202010101010101010101030201010201020201010203030203010102030301010101010202010301010103010101020101010102010102010102020103010101030101010202010104010201010103020101020102020201010101010301010101020201010101010201030102010101010101010101030201010301030101010101010101020201010102010101070103010101020102010101020101010101020202010101020201010201010102010101010102020202020202010103020201030103020102020101010104020103010101010101010102020302020102020201030103010301020102010201050204010302020101010101010201020202010203010101010104010101010202010502020201010201020101020101010102010301010101010401010102010201010101020302030303020401010102010101010102010202050102010103010102010101010102020201010102010101010201010101010101040103020102010201020101010401010103010103020103010101010301030101020101010201010101010502020102010402020106020101020102010101020101010101010102020101020101010302020104010102010101020101020103010501020102010203030102010201030201010101010103030204020101030101010101020503010103010102010301010303020201010201020104010101020201020101010202020201010202010301020401010101010202010102010203010101020202020102010101010101010101010301020101010101020201020101030101010101010102030101010202010202010103010101030102010201010102010202030101010102020102010201020301010301040102010101010202040101030303010201030101010101040201030101040101010101020101010202010101010301020201020301030101010301040102010301010101010101010103010201010101020202030501010101010302030201030101010101030101010101020103010102010101020201010102030103020101020102020102010201030101010102010101020201020201010201010101010201010103020101010104010103010103010301020202020102020202030201030102010101020102010101010201010101020302020301010203010101010302020101020101010201030102020201030502010201010102020101010101010102010204010101030202010301020201010101020101010101010203020202010201040101010102030201010101020101010201010101030101010201010101010102030101020203010101010102010101020201010202010302010201010405020101010102030102010101010101030201010102010101020101010102050101010101010103010302020301010301010103010101020101010101010101010201010101010101010102040101010101020301010201010102020101020205010102010101010102010101030302010201030102010301020102010102010103040101010102060301030201010102010101020201040101010103010101010101020102010101010102010102010202030102010102020101010101010102010102010201010101010105010201010304020102010201010101020102020104020202010101010202010101050101020101020201020201010102020103010101010101010102010101010101010201010201030101010204010201020201010101010201010201010101010201010101010201010102020102010102010102030102010203020201010201010102010101010101010202010101010101010203020101040201010102010201030202010104010102010101020401020401010202","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","4.186208696018375","32.85092149535828","304.98071488951854","2424.235470321353","17399.33351134058","131847.77172275868","1282802.6033267244","10588210.547392957","81211857.12078609","987758592"],"densities":["0.0313852636598991","0.003488609870261907","0.0003674717080873141","0.000047186398777066","0.000006677752608102045","8.737559163129178e-7","8.688438264830984e-8","1.0746439124548749e-8","1.4159563383077154e-9","1.1030870903012379e-10"],"corrections":0,"bucketCount":2184},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","9.695833830730308","224.34733483620164","3901.3020388219634","74868.93587529098","1755249.6953915507","32097342.08698788","987758592"],"densities":["0.01642822823411118","0.0006655306028048762","0.00003885202684229097","0.0000020129900792004577","8.501474564506914e-8","4.708216592759077e-9","1.494851265238036e-10"],"corrections":0,"bucketCount":2184},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.0813954317436707","17399.33351134058","255246030.33898863","973469101.3688715","987758592"],"densities":["0.046236555595004385","0.000025866153861649197","1.7631250781983891e-9","6.822392927275002e-11","6.998150079762664e-11"],"corrections":0,"bucketCount":2184}]},"bimodal":{"count":3000,"exceeds":0,"min":"100","max":"5503","mean":"2659.654666666667","stddev":"2562.607864021511","percentile":["100","100","112","124","148","5247","5407","5503","5503","5503"],"percentilesAt":[["0.1","100"],["1","100"],["12","112"],["25","124"],["50","148"],["75","5247"],["90","5407"],["99","5503"],["99.9","5503"],["100","5503"]],"percentiles":[["0","100"],["50","148"],["75","5216"],["87.5","5344"],["93.75","5408"],["96.875","5440"],["98.4375","5472"],["100","5472"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0.018"],[999,"0.505"],[1000,"0.505"],[5000,"0.529"],[1000000,"1"],[5503,"1"],[5504,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,54],[999,0],[1000,0],[5000,72],[1000000,0],[5503,91],[5504,0]],"linearStep":275,"linearBuckets":[["274","1515"],["548","0"],["824","0"],["1096","0"],["1368","0"],["1648","0"],["1920","0"],["2192","0"],["2464","0"],["2736","0"],["3024","0"],["3296","0"],["3568","0"],["3840","0"],["4096","0"],["4384","0"],["4672","0"],["4928","0"],["5216","749"],["5472","736"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","883"],["256","632"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","1485"]],"logBuckets10":[["10","0"],["100","54"],["1000","1461"],["9984","1485"]],"skewness":"0.024719736289207213","kurtosis":"-1.9929816831753002","meanCI95":["2659.654666666667","2567.902289808381","2751.4070435249528"],"meanCI99":["2659.654666666667","2539.0435086687157","2780.265824664618"],"percentileCI":[["50","0.95",148,146,5023],["99","0.95",5503,5503,5503],["90","0.99",5407,5407,5439]],"export":"ab00010101021a3b9aca00030204190bb80518640619157b070008fb3ff000000000000009190c000a98521864183602183302184102183202183002184402183f02183302183a02183c02184202183a02184b02184102183302183c02184602184602183f02183f02183d02183e02184202183e021837190288184801186001186c01185901186b01186b01185201185801186b01185e01185801185901185b01185a01185601185b","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["100","109.98699073456697","119.98252823585753","129.5824353622775","138.70176685308016","1564.151680163676","5091.66592874371","5187.00758752157","5289.913470155005","5396.396014364695","5488"],"densities":["0.010013026211577427","0.010004464490987972","0.010416767441925491","0.01096571608355891","0.0000701532891939716","0.00002834857436515076","0.001048859452225322","0.0009717617442358864","0.0009391210619749637","0.0010916555574134168"],"corrections":0,"bucketCount":41},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["100","114.14631642493161","127.98723858589582","141.5209929268492","5068.094241846232","5202.058852019357","5354.631406836335","5488"],"densities":["0.010098540041517095","0.010321360180757702","0.010555618142473196","0.000028997263541849866","0.0010663797153033616","0.00093632267630643","0.0010711453084148103"],"corrections":0,"bucketCount":41},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["100","104.2446148394385","1564.2155901862773","5450.04769214779","5487.472527472527","5488"],"densities":["0.011779631813805347","0.0003082253055702669","0.00011580531227091523","0.001309290998205475","0.001895833333331503"],"corrections":0,"bucketCount":41}]},"heavy":{"count":3000,"exceeds":0,"min":"1000","max":"1003519","mean":"7750.383333333333","stddev":"40723.97881407286","percentile":["1003","1011","1151","1343","1967","4095","10431","100351","501759","1003519"],"percentilesAt":[["0.1","1003"],["1","1011"],["12","1151"],["25","1343"],["50","1967"],["75","4095"],["90","10431"],["99","100351"],["99.9","501759"],["100","1003519"]],"percentiles":[["0","1000"],["50","1960"],["75","4080"],["87.5","8448"],["93.75","15872"],["96.875","34304"],["98.4375","62464"],["99.21875","142336"],["99.609375","331776"],["99.8046875","499712"],["99.90234375","999424"],["100","999424"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0.005666666666666667"],[5000,"0.7953333333333333"],[1000000,"1"],[1003519,"1"],[1003520,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,17],[5000,1],[1000000,3],[1003519,3],[1003520,0]],"linearStep":50175,"linearBuckets":[["49920","2943"],["99840","28"],["149504","11"],["199680","5"],["249856","1"],["299008","0"],["350208","6"],["399360","0"],["450560","0"],["499712","3"],["548864","0"],["598016","0"],["651264","0"],["700416","0"],["749568","0"],["798720","0"],["851968","0"],["901120","0"],["950272","0"],["999424","3"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","113"],["2048","1445"],["4096","698"],["8192","359"],["16384","207"],["32768","80"],["65536","55"],["131072","19"],["262144","12"],["524288","9"],["1048576","3"]],"logBuckets10":[["10","0"],["100","0"],["1000","17"],["9984","2670"],["99840","284"],["999424","29"]],"skewness":"17.68258749998954","kurtosis":"382.58890890242577","meanCI95":["7750.383333333333","6292.289840459271","9208.476826207396"],"meanCI99":["7750.383333333333","5833.677187219624","9667.089479447042"],"percentileCI":[["50","0.95",1967,1895,2063],["99","0.95",100351,67071,143359],["90","0.99",10431,9471,12543]],"export":"ab00010101021a3b9aca00030204190bb8051903e8061a000f4240070008fb3ff000000000000009190c000a9903b81901fa11010c010c010c010f011101181c011301150110010f0118190110010b01140113010e01150112010e010e01181c0113010f010d0118190113010f0113010f0113011818010b0111010c0110010701100106010e0112010c0111010b010c010e0112010a0112010f0108010f011301050109010a010b0113010d0110010d010e010d010c0111010a0107010e010d01080109010d010b010a010c011101090108010e010501050103010a010901040109010701090108010c010d0104010b010d0107010e0109010501050108010b0109010801070108010a010c01030106010801090107010b01030107010301070109010401050105010a010501080106010301060101010601040102010201070105010b010c0108011501080103010601090104010b0107010b01090108010c010701050107010a0106010a010c010e01030108010a01040108010801060106010e010602080108010d0105010301090109010501090107010401060107010901050105010901050105010701080105020101060104010501080103010501070102010501070101010101040105010601010107010501040104010701050103010501050104010101030108010301030105010601040107010401030208010101050102010701050102010201030104010301040105010501060101010301050202010101020102010301020106010301010103020201030106010501020107010801040102010901030107010501020106010201090104010701020106010401080102010601030102010401060104010101060102010201040201010101020101010101030108010201020103010501010205010701050101010301020104010101020103020701010102010701040104010302010104010401020202010201020101020101050104020101060202010302040103020501010203010602020102020202050104020302010107020402040202020202030202020402060203020202040103010101020104010201040106020101030101030301040105020201040105020101030203010b02040102020402040104020502020202010204010201020502020202020603040201020402040305020403040202030703030302030303030306030403030306040603030403040404060403040302020201020103030202030402040304060303060303030304010305040204050401050104030502050405020601060406010601070608020803060404020504050406050604060307050803090409030a020c030c030f060e0309020b010c030f03120216051832061702182103183101182e06185203188003","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1002","1116.0115940758749","1252.0176405876346","1436.2926920789575","1657.1070793784008","1967.6981501395692","2515.452679247887","3391.0837972605036","5210.810804506772","10585.370239486867","1001472"],"densities":["0.0008771037788792766","0.0007352614281847651","0.0005426670576983055","0.0004528690418364426","0.000321966757624194","0.0001825635292560495","0.00011420334195861585","0.000054953297720918435","0.000018606176228911744","1.0091971876153878e-7"],"corrections":0,"bucketCount":476},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1002","1173.1293989871858","1407.7295628365227","1732.8913538042768","2347.1503939468034","3553.0828345309737","7453.392476591346","1001472"],"densities":["0.0008347901862720853","0.0006089388025700078","0.00043934172718131516","0.00023256823835103132","0.00011846197850680667","0.00003662712860450671","1.4371676925955202e-7"],"corrections":0,"bucketCount":476},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1002","1047.2631040984786","1967.6981501395692","20215.536864873528","778468.934289276","1001472"],"densities":["0.00110465247569445","0.0004888992459984089","0.000024660454700131352","6.462219644045224e-8","4.4842432852344055e-9"],"corrections":0,"bucketCount":476}]},"huge":{"count":1,"exceeds":999,"min":"985661440","max":"989855743","mean":"987758592","stddev":"0","percentile":["989855743","989855743","989855743","989855743","989855743","989855743","989855743","989855743","989855743","989855743"],"percentilesAt":[["0.1","989855743"],["1","989855743"],["12","989855743"],["25","989855743"],["50","989855743"],["75","989855743"],["90","989855743"],["99","989855743"],["99.9","989855743"],["100","989855743"]],"percentiles":[["0","985661440"],["100","985661440"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[989855743,"1"],[989855744,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[989855743,1],[989855744,0]],"linearStep":49492787,"linearBuckets":[["49283072","0"],["98566144","0"],["147849216","0"],["197132288","0"],["246415360","0"],["295698432","0"],["346030080","0"],["394264576","0"],["444596224","0"],["492830720","0"],["541065216","0"],["591396864","0"],["641728512","0"],["692060160","0"],["738197504","0"],["788529152","0"],["838860800","0"],["889192448","0"],["939524096","0"],["985661440","1"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","0"],["16384","0"],["32768","0"],["65536","0"],["131072","0"],["262144","0"],["524288","0"],["1048576","0"],["2097152","0"],["4194304","0"],["8388608","0"],["16777216","0"],["33554432","0"],["67108864","0"],["134217728","0"],["268435456","0"],["536870912","0"],["1069547520","1"]],"logBuckets10":[["10","0"],["100","0"],["1000","0"],["9984","0"],["99840","0"],["999424","0"],["9961472","0"],["99614720","0"],["998244352","1"]],"skewness":"0","kurtosis":"0","meanCI95":["987758592","NaN","NaN"],"meanCI99":["987758592","NaN","NaN"],"percentileCI":[["50","0.95",989855743,989855743,989855743],["99","0.95",989855743,989855743,989855743],["90","0.99",989855743,989855743,989855743]],"export":"ab00010101021a3b9aca0003020401051a3aecbecb061a3aecbecb070008fb3ff000000000000009190c000a82190beb01","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["987758592","987758592","987758592","987758592","987758592","987758592","987758592","987758592","987758592","987758592","987758592"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["987758592","987758592","987758592","987758592","987758592","987758592","987758592","987758592"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["987758592","987758592","987758592","987758592","987758592","987758592"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]}},"comparisons":[{"a":"uniform","b":"wide","welch":["-20.717481500162236","4899.000011688327","0","-42280424.653807744","-34970358.09721266"],"mannWhitney":["4151253","-9.973869941657583","1.9834639267968655e-23"],"cohensD":"-0.35119376357980936","cliffsD":"-0.15280551020408165","ksTest":"0.5271428571428571"},{"a":"bimodal","b":"heavy","welch":["-6.8321884485733735","3022.7499968007596","1.0072165324004345e-11","-6551.701490007625","-3629.7558433257072"],"mannWhitney":["3569124","-13.876182514923276","8.832433131162552e-44"],"cohensD":"-0.17640634719650242","cliffsD":"-0.20686133333333334","ksTest":"0.505"},{"a":"uniform","b":"constant","welch":["76.47254076554512","1998.9999999999998","0","4798.064199904529","5050.6358000954715"],"mannWhitney":["199200","16.76488473467945","4.4086833356732847e-63"],"cohensD":"1.7518094577578935","cliffsD":"0.992","ksTest":"0.996"},{"a":"three","b":"four","welch":["0.9509733731183238","2.008191040866679","0.44163654477063086","-1111.6662195630793","1745.6662195630793"],"mannWhitney":["6","0","1"],"cohensD":"0.8672305722651706","cliffsD":"0","ksTest":"0.33333333333333337"},{"a":"empty","b":"uniform","welch":["0","0","1","0","0"],"mannWhitney":["0","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"},{"a":"one","b":"two","welch":["0","0","1","0","0"],"mannWhitney":["1","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0.5"}]},{"options":{"lowest":1,"highest":1000000000000,"figures":4},"histograms":{"empty":{"count":0,"exceeds":0,"min":"9223372036854775807","max":"0","mean":"NaN","stddev":"NaN","percentile":["0","0","0","0","0","0","0","0","0","0"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","1"],["75","1"],["90","1"],["99","1"],["99.9","1"],["100","1"]],"percentiles":[["100","0"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[1,"0"],[2,"0"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[1,0],[2,0]],"linearStep":1,"linearBuckets":[],"logBuckets":[],"logBuckets10":[],"skewness":"0","kurtosis":"0","meanCI95":["NaN","NaN","NaN"],"meanCI99":["NaN","NaN","NaN"],"percentileCI":[["50","0.95",0,0,0],["99","0.95",0,0,0],["90","0.99",0,0,0]],"export":"ab00010101021b000000e8d4a5100003040400051b7fffffffffffffff0600070008fb3ff0000000000000091a0006c0000a80"},"one":{"count":1,"exceeds":0,"min":"7","max":"7","mean":"7","stddev":"0","percentile":["7","7","7","7","7","7","7","7","7","7"],"percentilesAt":[["0.1","7"],["1","7"],["12","7"],["25","7"],["50","7"],["75","7"],["90","7"],["99","7"],["99.9","7"],["100","7"]],"percentiles":[["0","7"],["100","7"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[7,"1"],[8,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,1],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[7,1],[8,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","0"],["4","0"],["5","0"],["6","0"],["7","1"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","1"]],"logBuckets10":[["10","1"]],"skewness":"0","kurtosis":"0","meanCI95":["7","NaN","NaN"],"meanCI99":["7","NaN","NaN"],"percentileCI":[["50","0.95",7,7,7],["99","0.95",7,7,7],["90","0.99",7,7,7]],"export":"ab00010101021b000000e8d4a510000304040105070607070008fb3ff0000000000000091a0006c0000a820701","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["7","7","7","7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]},"two":{"count":2,"exceeds":0,"min":"3","max":"9","mean":"6","stddev":"3","percentile":["3","3","3","3","3","9","9","9","9","9"],"percentilesAt":[["0.1","3"],["1","3"],["12","3"],["25","3"],["50","3"],["75","9"],["90","9"],["99","9"],["99.9","9"],["100","9"]],"percentiles":[["0","3"],["50","3"],["75","9"],["100","9"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.5"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[9,"1"],[10,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[9,1],[10,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","1"],["4","0"],["5","0"],["6","0"],["7","0"],["8","0"],["9","1"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"]],"logBuckets10":[["10","2"]],"skewness":"0","kurtosis":"0","meanCI95":["6","-32.11861420852408","44.11861420852408"],"meanCI99":["6","-184.97022348861458","196.97022348861458"],"percentileCI":[["50","0.95",3,3,9],["99","0.95",9,9,9],["90","0.99",9,3,9]],"export":"ab00010101021b000000e8d4a510000304040205030609070008fb3ff0000000000000091a0006c0000a8403010601","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","3.20631895627745","3.614082426485135","4.244224543345755","5.066378477103939","6.000000000000661","6.933621522896061","7.755775456654244","8.385917573514865","8.79368104372255","9"],"densities":["0.4846864379515554","0.24524021229529785","0.15869436008848586","0.12163172356651711","0.10710978436929422","0.10710978436944585","0.12163172356651718","0.1586943600884858","0.24524021229529752","0.48468643795155636"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","3.354130868400228","4.141148961265419","5.326286696880615","6.673713303119386","7.858851038734582","8.645869131599772","9"],"densities":["0.4034021193986685","0.18151697419949023","0.12054054019551301","0.10602220721759137","0.12054054019551305","0.18151697419949037","0.40340211939866866"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","3.080949106595003","6.000000000000661","8.919050893404997","8.998766051082084","9"],"densities":["0.6176720423877602","0.15415969657006728","0.1541596965701371","0.6146886166679947","0.8104063186740486"],"corrections":0,"bucketCount":2}]},"three":{"count":3,"exceeds":0,"min":"1","max":"1000","mean":"335.3333333333333","stddev":"469.99314415803514","percentile":["1","1","1","1","5","5","1000","1000","1000","1000"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","5"],["75","5"],["90","1000"],["99","1000"],["99.9","1000"],["100","1000"]],"percentiles":[["0","1"],["50","5"],["75","1000"],["100","1000"]],"cdf":[[0,"0"],[1,"0.3333333333333333"],[2,"0.3333333333333333"],[7,"0.6666666666666666"],[42,"0.6666666666666666"],[100,"0.6666666666666666"],[999,"0.6666666666666666"],[1000,"1"],[5000,"1"],[1000000,"1"],[1000,"1"],[1001,"1"]],"countAt":[[0,0],[1,1],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,1],[5000,0],[1000000,0],[1000,1],[1001,0]],"linearStep":50,"linearBuckets":[["50","2"],["100","0"],["150","0"],["200","0"],["250","0"],["300","0"],["350","0"],["400","0"],["450","0"],["500","0"],["550","0"],["600","0"],["650","0"],["700","0"],["750","0"],["800","0"],["850","0"],["900","0"],["950","0"],["1000","1"]],"logBuckets":[["1","1"],["2","0"],["4","0"],["8","1"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","1"]],"logBuckets10":[["10","2"],["100","0"],["1000","1"]],"skewness":"0.707068367939628","kurtosis":"0","meanCI95":["335.3333333333333","-1094.5902216930783","1765.2568883597448"],"meanCI99":["335.3333333333333","-2963.0427997930706","3633.7094664597375"],"percentileCI":[["50","0.95",5,1,1000],["99","0.95",1000,5,1000],["90","0.99",1000,1,1000]],"export":"ab00010101021b000000e8d4a51000030404030501061903e8070008fb3ff0000000000000091a0006c0000a86010104011903e301","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","5.932607603243072","22.700394847776252","62.922989940120665","139.7725442831563","261.92592592592587","426.13620879717644","612.9672969674574","790.2991406883924","925.3771141512873","1000"],"densities":["0.0202732526167807","0.005963816128010755","0.00248616479793053","0.0013012437203425678","0.0008186429115195856","0.0006089752617892097","0.0005352428280504276","0.0005639145113574124","0.0007403131497784085","0.0013400714655117433"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","11.075754645242279","55.227569135094825","169.9105532246607","375.7563621894388","639.7111592472341","874.319812866049","1000"],"densities":["0.014178307023841562","0.003235589397803251","0.0012456699133811633","0.0006940007356748608","0.0005412182102750836","0.0006089167669375607","0.001136671945792733"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.5568893265228576","261.92592592592587","970.4876086277768","999.5554699188299","1000"],"densities":["0.03211532068992312","0.001734979648688859","0.0006350893803403017","0.0016857105347162903","0.002249566547595048"],"corrections":0,"bucketCount":3}]},"four":{"count":4,"exceeds":0,"min":"2","max":"64","mean":"19","stddev":"26.095976701399778","percentile":["2","2","2","2","2","8","64","64","64","64"],"percentilesAt":[["0.1","2"],["1","2"],["12","2"],["25","2"],["50","2"],["75","8"],["90","64"],["99","64"],["99.9","64"],["100","64"]],"percentiles":[["0","2"],["50","2"],["75","8"],["87.5","64"],["100","64"]],"cdf":[[0,"0"],[1,"0"],[2,"0.5"],[7,"0.5"],[42,"0.75"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[64,"1"],[65,"1"]],"countAt":[[0,0],[1,0],[2,2],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[64,1],[65,0]],"linearStep":3,"linearBuckets":[["3","2"],["6","0"],["9","1"],["12","0"],["15","0"],["18","0"],["21","0"],["24","0"],["27","0"],["30","0"],["33","0"],["36","0"],["39","0"],["42","0"],["45","0"],["48","0"],["51","0"],["54","0"],["57","0"],["60","0"],["63","0"],["66","1"]],"logBuckets":[["1","0"],["2","2"],["4","0"],["8","1"],["16","0"],["32","0"],["64","1"]],"logBuckets10":[["10","3"],["100","1"]],"skewness":"1.1249561006949362","kurtosis":"-0.6915329232082903","meanCI95":["19","-28.948388276616278","66.94838827661627"],"meanCI99":["19","-69.00217210471634","107.00217210471634"],"percentileCI":[["50","0.95",2,2,64],["99","0.95",64,8,64],["90","0.99",64,2,64]],"export":"ab00010101021b000000e8d4a51000030404040502061840070008fb3ff0000000000000091a0006c0000a8602020601183801","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["2","2.1214881848725766","2.59375","3.8766929625087334","6.718750000000001","12.088759862808779","20.781250000000007","32.65059402055783","45.90625000000002","57.34108084774391","64"],"densities":["0.8231253113616391","0.21174695221339987","0.07794578786609092","0.03518578222774577","0.018621939727256866","0.01150418331476101","0.008425065431316082","0.007543949552937039","0.008745210255535187","0.015017452189086762"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["2","2.260178895464949","3.6186449301918753","7.9516758986192855","17.940490621956265","34.523454456253454","52.88675172594847","64"],"densities":["0.5490727547361286","0.10516062912523202","0.03296933345228088","0.01430171114530577","0.00861469302379368","0.007779493015826749","0.012854670329889233"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["2","2.083206032793557","12.253877226493122","61.37877112059458","63.96152982082045","64"],"densities":["0.6009179661774692","0.04424486756378103","0.009160325129048932","0.01897196203257965","0.025994160186590367"],"corrections":0,"bucketCount":3}]},"constant":{"count":100,"exceeds":0,"min":"42","max":"42","mean":"42","stddev":"0","percentile":["42","42","42","42","42","42","42","42","42","42"],"percentilesAt":[["0.1","42"],["1","42"],["12","42"],["25","42"],["50","42"],["75","42"],["90","42"],["99","42"],["99.9","42"],["100","42"]],"percentiles":[["0","42"],["100","42"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[42,"1"],[43,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,100],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[42,100],[43,0]],"linearStep":2,"linearBuckets":[["2","0"],["4","0"],["6","0"],["8","0"],["10","0"],["12","0"],["14","0"],["16","0"],["18","0"],["20","0"],["22","0"],["24","0"],["26","0"],["28","0"],["30","0"],["32","0"],["34","0"],["36","0"],["38","0"],["40","0"],["42","100"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","100"]],"logBuckets10":[["10","0"],["100","100"]],"skewness":"0","kurtosis":"0","meanCI95":["42","42","42"],"meanCI99":["42","42","42"],"percentileCI":[["50","0.95",42,42,42],["99","0.95",42,42,42],["90","0.99",42,42,42]],"export":"ab00010101021b000000e8d4a51000030404186405182a06182a070008fb3ff0000000000000091a0006c0000a82182a1864","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["42","42","42","42","42","42","42","42","42","42","42"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["42","42","42","42","42","42","42","42"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["41.5","41.55","42","42.45","42.499","42.5"],"densities":["1.0000000000000568","0.9999999999999937","0.9999999999999936","1.0000000000000113","1.0000000000023315"],"corrections":0,"bucketCount":1}]},"uniform":{"count":2000,"exceeds":0,"min":"3","max":"9995","mean":"4965.5585","stddev":"2878.7129715513056","percentile":["9","93","1149","2456","5017","7342","8957","9877","9968","9995"],"percentilesAt":[["0.1","9"],["1","93"],["12","1149"],["25","2456"],["50","5017"],["75","7342"],["90","8957"],["99","9877"],["99.9","9968"],["100","9995"]],"percentiles":[["0","3"],["50","5017"],["75","7342"],["87.5","8718"],["93.75","9292"],["96.875","9678"],["98.4375","9815"],["99.21875","9907"],["99.609375","9938"],["99.8046875","9957"],["99.90234375","9980"],["99.951171875","9995"],["100","9995"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.0005"],[42,"0.004"],[100,"0.0125"],[999,"0.106"],[1000,"0.106"],[5000,"0.4965"],[1000000,"1"],[9995,"1"],[9996,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[9995,1],[9996,0]],"linearStep":499,"linearBuckets":[["499","104"],["998","108"],["1497","93"],["1996","109"],["2495","96"],["2994","84"],["3493","110"],["3992","90"],["4491","99"],["4990","98"],["5489","98"],["5988","104"],["6487","105"],["6986","128"],["7485","94"],["7984","98"],["8483","91"],["8982","96"],["9481","97"],["9980","97"],["10479","1"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"],["32","3"],["64","7"],["128","17"],["256","21"],["512","58"],["1024","106"],["2048","215"],["4096","386"],["8192","840"],["16384","345"]],"logBuckets10":[["10","2"],["100","23"],["1000","187"],["10000","1788"]],"skewness":"-0.016305416482334096","kurtosis":"-1.189348249612471","meanCI95":["4965.5585","4839.287652764121","5091.829347235879"],"meanCI99":["4965.5585","4799.552454720941","5131.5645452790595"],"percentileCI":[["50","0.95",5017,4782,5226],["99","0.95",9877,9818,9926],["90","0.99",8957,8801,9123]],"export":"ab00010101021b000000e8d4a510000304041907d005030619270b070008fb3ff0000000000000091a0006c0000a990e2c030106010a01070102010c02010106010b0103010301050101010101020106010301040107010101020203020801020104010b010501080101011201010106010501080101020502160104010201130108010601020105010401090201010e0104020101030103010401020102020102040202010601040216010301040103020e01040108010e0108020e020101020104010401020109010d01010101010301010102010901040109010201020102010e010501050103010502020101010601030102010101050101010601030102020401030103010301010107010c01120102010101010104010701030101010201050102010802020105010101010106010501030106010a0101010401020102010401050102010101030209010b010201010102010201030102010101040104011101020103010101040102011401040104010a0101010401030103011101030102010101030104010701010101010a0109010b010e0110020b01020104010201010101010101060112010301140101010f010f010d0102010501020107010801070101010801050201010301140203010d0109010201010104010d0101010101050103010101010102010501030103010301020102010901090101010e010201010114010b01020101010301010109030101070104010c010101020104010301050107010e0112010301010106010201050101010201181d01080101010101010203010301090105010901030103010e010101050102010901010101010d010c0105010901090201010101070101010301020103030b0101010a01020113010702010104010101080103010401020103010d01060101010501080108010201020101010f0105010101030109010302060203010501150102010101060113010801020101010101020107010101060102010301080112010301070101010201020106010201010105011001030101010f01040102010401020101020701010103010501010103010701010105010201080103010801050106010701070105010501020101010301010102010e01010103010301030101010101050101020201020102010d0101010a0106010101050208010901040101020201060106010601030106010f010601020108010f0108010301160105010501110102010c01060107010801010105010401020107020b010201181901010109010a0101010b010701060101010202020104010801030102010201010106010c010701070102010201181901090201010101060102020301010109010a020101010203010c01020102010201050102020601020201020201010102010802010101010b0101010501060101010801090109010101030105010d0108010a01160109010101181901030103010601150104010801060216010e01080102010d0108010b010301070106020201010118240108010901060102010a0207010501040202010c0303010b0102010801030102011601020102010201030206010b01020103020501070108030101070107020201020102010f01070101010101010101010301050102010d010b010201010208010c010101020106010501020101020501020101010a020101070103010201010103010501050205010201060104010701040104010301010102010a01060118260104010a010302030102010301010104010f020901010101010301030109010301050105010502130102010b010301020106010502040102020101070204010b0203010c0104010c010901040103010301100106010201010108010301010109010101020301010801020104010101030105010c0103010d01010105010a0101010801030103010a010401040104010e010e010401040103010201020206010f010101182d01090106010e01010105010302010102010501080101011101020102010f01040101010e01040201010101181c010601181e010302030104010301040106020b010401010304010101010105010a0101010601010102010601040109010201030101010a0113020201030101010a010201070102010901020104010601060101010a010a010501110103010101020103010602060102010301160108010301030104010101120102010201050109010401010111010101030104010201020101010301010109010201020101010501030102010601010101010101140102020301140101010c010101020102010a01110102010d0105010101090105010601010105010701040101020a010b010501010102020602050101010d02040104010b01020102010602010101010b0203010202010102010a010301040101010801040101010d01040105010801040102010a0105010a010701130108010d01010108010201020101010301020104010401030101010301010103010a010501150105010201020102010801030107010301150204010201040110010e010301050101010301070108020a010201040107010a010201040104010201090103010601050106030602090203010801170101010602030104010301050102010702020104010301020101010701040107010b010301090105010401030110020301010104020302020103010101070103010701020107011201020104010f0108010201090213010a0114010b01020106010601010102010602030118190204010401010102010301030103010301040103010b010d010b010401090104010a01020104010301020108010302030109010601060102010402040103011001030202010a0104010101040102010c010a01060102010501020204010d02060104010101181a010a01090103030201010103010401090107010501010209010101050104010701010103010b0101010301050103010401020102010401010103010201040102010202030108010701030104010601090203010301040102010201050105010a0107010701050111010a020201010105010a0211010501070102010701070108020a01040101010a0202030a010801080101010701040106010b0107010502100105010a0105010a020101060208010f01020106010902040107010a0104020701050102010b01080108010301100107010a01010101010101020112010e01050104010101010102010403020103010a01070102010101020104020a0103010301030104010101030103010101010104010101070109010c010101030101010a01040105010501070102010101030102010201010104010801020102010801010102010201080202010e0102010401010103010a0104010101010109010401060102010c01090203010301020102010b0104010401020203010501040203010101040101010301010102010601010104010501020201010501010107010101020101010601020103010e010601020102010b010d0104010601040102010801010104010301010102010a01030102010b02040110010301010108010c0105010601010104010f030101020201010801010108010a0102030701030101010401070105010302010103010301030106010601040101020101010204010401020102010801060115010a010a01030103010601030107010a0201010101010106010101080102020201070107010501010107010101090102010a010b010701010402010c02010209010202181801010104010b01010101010602040110010c010202030106010b01010203010301020104010101060205010601120101010401040101010d01080104010b0104010b0104010a010201060118210102010501010101010a010301070104010101030102010a0103010401070105010201030104010401060106010101010102010c0103010c010401090110010b0104020a010101050103010301090108010101010107011701010101010501090106010201040301010101050108010501040101011501010102010701050201020b010201010108010102050101010301090101010a010b010301010105010101020101010801060105021601020103010101080102010d0101010901070103011501070112010601070107010301070105010401050110010102110104010501030104010701010104010301040106010a01040104010b010b0105010a01010108010201040101010101010107010f020a0204010501010106020901020207010b010401020103010301060108010301150108010a01020104010a01080101010f02020105021602060106010a0203010601020102010c01020105010401040101010101010105010f0104021501070106010501020104010501030104010c0101010d011401020105010a01020108010a0109010201020104010b010101020104010201060101010a01080109010601010103010201060101010c0102010b010401090102010501080104010401020102010801090104010f0102010501010101010c0101020901030205020601060101010401010202010901050107010c01050107010c0201010301070103010201050104010c01010106010201060107010502050101010901050106020601020202010101050108010101080101010901050115010201010102010101120103010101020203010201080102010501040203010201010102010201010203010b01010102010a01040103010902020103010601080105010901010103010201020102010a01130103010e01050104010a010501040104010401160103010c010301070106020c01070101011101050102010a02120111020501040106010d01020107010d020701010101010b0103010101010105010501040103010f0101010901050116010a0108010201070102010a0103010201010107010101030108020101030105010201050106010401010104010301010102010c0105010301060102010201030201010601030101010301040204010e0111010e01020101020a01040101010f01070111010201030102010101010101011819010c01010104010201010106010401010104010401030108010b010c010f01","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","916.7549974278079","1933.7984004506598","3013.7353696379046","4026.9818637232156","5023.466593408129","6022.204810477519","6903.869397967374","7876.523366774738","8960.007546081224","9995"],"densities":["0.0001094385259522486","0.00009832422068004222","0.00009259799678425629","0.00009869266815502099","0.00010035276710323482","0.00010012633770381916","0.0001134218175697689","0.00010281148610601653","0.00009229484094913849","0.00009661906192781553"],"corrections":0,"bucketCount":1814},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","1381.9174917125915","2862.801260325292","4314.6514446898","5744.502357539123","7020.901868738615","8521.227700374795","9995"],"densities":["0.00010360093603549605","0.00009646749183494136","0.00009839661446864305","0.0000999105162456871","0.00011192196612712068","0.00009521741200799694","0.00009693298136589546"],"corrections":0,"bucketCount":1814},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","475.5251795489272","5023.4664011498235","9465.399685784294","9975.93305996863","9995"],"densities":["0.00010581446696180303","0.00009894586980646991","0.00010130723970047892","0.00009597805447740996","0.00005244680050153288"],"corrections":0,"bucketCount":1814}]},"wide":{"count":5000,"exceeds":0,"min":"1","max":"2147024895","mean":"68253245.4556","stddev":"249173797.508986","percentile":["1","1","7","110","21325","5370623","121475071","1506410495","2109800447","2147024895"],"percentilesAt":[["0.1","1"],["1","1"],["12","7"],["25","110"],["50","21325"],["75","5370623"],["90","121475071"],["99","1506410495"],["99.9","2109800447"],["100","2147024895"]],"percentiles":[["0","1"],["50","21325"],["75","5370368"],["87.5","64942080"],["93.75","308707328"],["96.875","740556800"],["98.4375","1138688000"],["99.21875","1634795520"],["99.609375","1892941824"],["99.8046875","2003501056"],["99.90234375","2113011712"],["99.951171875","2133524480"],["99.9755859375","2136211456"],["99.98779296875","2146959360"],["100","2146959360"]],"cdf":[[0,"0"],[1,"0.029"],[2,"0.0634"],[7,"0.1246"],[42,"0.2048"],[100,"0.2454"],[999,"0.35"],[1000,"0.35"],[5000,"0.432"],[1000000,"0.6762"],[2147024895,"1"],[2147024896,"1"]],"countAt":[[0,0],[1,145],[2,172],[7,39],[42,3],[100,5],[999,0],[1000,0],[5000,0],[1000000,0],[2147024895,1],[2147024896,0]],"linearStep":107351244,"linearBuckets":[["107347968","4470"],["214695936","137"],["322043904","92"],["429391872","50"],["536739840","47"],["644087808","28"],["751435776","22"],["858783744","24"],["966131712","23"],["1073479680","24"],["1180827648","9"],["1288175616","3"],["1395523584","6"],["1502871552","14"],["1610219520","7"],["1717567488","14"],["1824915456","6"],["1932263424","9"],["2039611392","8"],["2146959360","7"]],"logBuckets":[["1","145"],["2","172"],["4","192"],["8","164"],["16","172"],["32","139"],["64","149"],["128","149"],["256","162"],["512","173"],["1024","138"],["2048","171"],["4096","190"],["8192","172"],["16384","158"],["32768","158"],["65536","168"],["131072","176"],["262144","144"],["524288","141"],["1048576","164"],["2097152","147"],["4194304","166"],["8388608","173"],["16777216","167"],["33554432","168"],["67108864","166"],["134217728","144"],["268435456","143"],["536870912","125"],["1073741824","121"],["2147483648","83"]],"logBuckets10":[["10","720"],["100","507"],["1000","523"],["10000","566"],["100000","557"],["1000000","508"],["9999872","524"],["99999744","548"],["999981056","447"],["9999745024","100"]],"skewness":"5.187111961434111","kurtosis":"29.754364093326828","meanCI95":["68253245.4556","61344263.507008746","75162227.40419124"],"meanCI99":["68253245.4556","59172035.15547553","77334455.75572446"],"percentileCI":[["50","0.95",21325,15760,29511],["99","0.95",1506410495,1403584511,1667039231],["90","0.99",121475071,97140735,154681343]],"export":"ab00010101021b000000e8d4a510000304041913880501061a7ff88e77070008fb3ff0000000000000091a0006c0000a991cb40118910118ac01186c01185401182501182601182701183201181901160111010b0116011819011701181b010801070111011001070106010401090106010c010c0108010b01030109010401050103010401040102010501040102010801030108010401090103010c010302030105010301020105010601030107010401010106010601030106010a0102010501020103010401010104010201030103010202030102010201040102010501020103010302050102010301020203010101030101010401030104010101050103010301020101010501010103010201020102010101050304020501030301020302020102010301020101020103010101010201020101010201020102010101020202030103010202010101020102010102010105010101050202010201010101020401020101010101010101020201020201020102030102010101030101010101050103010101010203010202010101010102010304020101030301020101010202010103020102010101030102020201010202030101030101010301020202020201010103010202020101020101010103020302030102010201010202020101010201020102010601020102020101010101010101020102010201010108010101040101020201010201010401010103010103070101010203010101010101020303010102020101010101010101010201030101010402020105010102020101010201020101020203050101010101010101010101020101010201010102010101010102010301040101010201050101020101020102010102020403010201040205010203020103010102020101020301020101010202010203010201030101010101030103020101010101020101020103010101010101010401020101010203010201010101010101010303010101010104020104010102010101010102020102010101040103010401020105010301090103010101040101020101020105020401010102010101050108010501020103010d01020102010301010101011101010106010302020101010801020201010201060201010201040103010301010102010201010104010601050102010c0102010101020104010401010109010101020107010201010106020501090306010501080103010301050201010d010502020106010101060104010901040102010802070103020401010101010501010105010d010601010107010201040105010901010105010902010202010201020101010a0204010b0101011302010108010301030108010401010112011822010b02010112010b010d01120202010601050105010101020106020f010101010101010b01060105010102050101010101020101010c010b01020105010601010108010a0102010d01050102010101020109010e01080108010101020106020702080101010601030106010d01030104010a0101010301050104010301080106010501020101010201090203010c0105010301040107010301060103011301030104010201160102010101010101010401020117010101070102010301020102010601010106010401090110010c0111010a020801030108010401050112020201020101010601010114010b0106010501050118200101010f010c0104010801040113010201020110010c01010106010201030101010102010109010d01010103010301040105020101010108010401080115010201100103010201181d011818010101090108010b010a01070105010a010501040105010301070118260110010201181801060103010e0109010c0105010101184a010a010201030103010601090111010d0107010c010301100104010f01080111010701090108010e011819010101080101010701040107010a0106010601020103010101010105010c010101040118250105010701030118260105010801181d010501120102010a010b010601050101010301060104011840010a0101011001050101010601080105010801010108010301060102010e0106011401060101010201030113010c010301020202010602120111010e01050105011856011601010112011001040102010401010104010101181e010c0106020c010401060105010f01030104010d01050104010901080203010601040105010201181b010d0109010601080102010a01160103010b01140118240104010f010701040108010201182b011828011823010f0105010a020701020218280106010401090108011201182801181f01060111011859010d010701080104010b010501010117010c010d010201184a01182b010801181a0102010a01182c010201181f01183c01110107010f01182701184c010101010104010501181a011824020c0110010a011852011001181a010101183a0103010801182a011835011819011829010101181f010601183b0116010d01181b010101010113011701184a0103011826010c01090109010c01188601182601181a01182d0104010c01184d011201181a0102010b010401182d010a01030105011201182d011701183501030118220106010f0117010f0118410118240117010e0106020a011867010c010701183801181901030108010a0111010901120113010201140118180108010e011834011874010a010601182d01181b01182101181f0101010901181b01182b010501020103010b010d010e01184401185e010501182301184b01184201020108011847010901182d010f010901183201182701181e021830010b011834011827011828010f010701120106010b011823010e010a0103011501182f0105010501010113010b0118e80118e0011501130114011301182b010e01186d010b01182501188d0102010f011401182b01183e011001181801184001182301183701182001181c01182101183f0118c10118520118a10118340117011001184a01184801185c0118c101130108011101185d011855011201140118ae01182d011301040118350104010801181a01183601185d01187501186901182f01183d01186001187501185301181e01110110010701185e01184b0115010501183c0103011834011856011701186601186201188e01181901181d011832011833010801182401183201070118200118c701183901184a0118860118630110011830011895010c010c011835011818011864011101080118280110010401184501186901186201020106010901186a0111011830010f01181e010c011819010c010c011827011848010c010b01185f01187501182d010a011821011859010a0118550104011832011601100117010a01185b011836011701183c010a01185901182d01181c01182b01185e011857010c010e01080105011501182e011850011401182101183b01181f01187b011885010d010a01183f01184c01188301182201188f010a0118580118360118cc0119015d01182e01030118cd0118280118b901183f0118760118400118b301183501183301188b0118450118690118690114010b01181e010301090118360118cc01182001182001183c01184901183801182f01189901187301185d0118ac0118eb01188401188b01183a01182101185b0118fc01181801182c01188e01190247011821011825010901184d01184a0107011901b90119018201183b010d0118780118260118520119010001181e0118650118e90118480118a301185a01040107011901930118cd0118e3010901183c011701182d0118180106011501181f0119016b0118700119019f01070118420118ad0118a501184201150118570118d901188a010301184001188c0118650118b501181c0119017f01183801186501181a0118c7010e01186f01182e01190126010301186501186501150118ce011401020118af01020119010101182801182901183201184d010e0116010c0108010301187b0118430118240118340118b801184401182201186101186601184a011887011101070118380118cb0118970118b60118760118280118ca011901b801181a0118ea0119015201184401181f010a0119019301188801185c01184c01188601182c01183201188701181b01186e01185a0118600118370119011f01187e011843010e0118c50103011901180118830118390118af01187b010101185301190133010c01120118a001182801182301120118a701189701184b01183a01070106011601184c011827010b011845011836011874010401189f02182f01184501181901184301185601182f01183901182101182f01183b01183c01181f01187101185a0118390118b0011847010401183a01182d01030118810118610118560118ea011896011828010c011831011301186d01182f01181c01181a0119014201189e011101189e01185201186301150118270118280106011855011826011826011901450118320118f90118330118200118290118ce0109011901250118280118f30118be01189601183f01182201184b010201184d01183201185f011870011819011889010c0118480118cf011001181c01189101184c011101188b0118610118d9011201181d01182b010c0118420118b201184b0119017101186101020118e9011901270118cc011824011901860119024d0118b6011839011501181c01187301184b010b0118cd01183401120107011201189b0118c301186d0118fd01182f0118820118620117010201185901181d010201189f010c01185b010d011829011835010601186701030119016a01186b01183e0118d101182a01184e011877011901230118490118630110011301010118500118a101185f01186601183901182b01184001183001184d0118520118c00119010401188201183301182b010a01110110010a01185501185901182c01187201186701183102181b01080118c201182801190138010f01182a0118540118210118b101181f0104011901450118ca0118a40118230119012a011501188e01183f0118e6011851011842011836011822010601182101190122010b01184201010108010201187c01183c011819010901183d0118600118270106011901de0118340109011898011892011865011832011301186501110118240118750105011601130118300218cd011901720118270118300119016e0118240118e00118820118b10119013e011701181901182201183801186701030118fe01184101184c01182d0118cd010f0119011401183d01186301183101188d011822010701188a011847010e0118df01181f01183101185b0118c401182301187501187001183301190172011855010b01185f01181a0118ce01160118ac01186601120118a601190135010101181a0118d4010d01187101183f0118290119016f01184101189d010c01186901182101182701188e011301181c01183d01182f011901160118de01181e011840011835011401190223011401182601184b01183c01183601184e0118430119021e0118b3010101186a0118b2011839011901f3011201050119018201187801183101187e01181d0118ae01182401186201185a0115010e0118aa01181f01185501140106011896011501182d01187201183d01186b01187b010c01186301182301185d01188c01183b0118640118990118440118da011818011901950118c60119011d0119014701185e01190118011201187d010d011835011901c3011901a0011833010701187601188901184901189b010401184e01188f010701189b01185501182701185a0118720119017a01183e020701070118f3011857011901b001080118c301185401181e01182101184e01010118b601182a01183201188a01184e01150119019001183e0118970110010b01181d01187301182a01182a0119011c01110118cc0118880118e801181c0118340118300118e401184001186701185e01183c0118810118190119011a0118950119011f01110118a40118410118210106011001182a011858011901390111010e011901cb0118880118fd0118570119013101186c01183501187f0118350118960119018c0118f90118b70118200118e4011828010c011859011001186201185e0118d30118230118270118180118520118310118bf01189401181b01182a0118460118bb01184401185901185101186b011872011873011001182901010118be01185c01189901188a01181b0119010a011821011882010301185901187401188701182e01181f01189901182b0118e60118ea011877010b011825011896010301181b011874010b0118e001185201187a010d0119022401186d0118360118fb010f010101183901182801184e011894011901600118ba01182c010501186a01187301188a01184b01189d01188701060119019201185401190105011844010a0118e0011901310119010a010701010118ec010601182001181d0118590118e30118220119022601184f01181f0118b3010601184901182f0118470118460118350118eb011201183c011701184701183e0119010f0119013b01183001189c010a0118d60119015d01187001186901182b011001181b0118870118690118880119022e010901187c01181b0118bd01181c01189601186a01185701183c0118300118ae0118630118d60118c001189501183801186b011901eb0119011401182f01187901189301184001184801182a01187c01186901185c01030118310118af01182a01188301070118d0011831011901d601181d0118230118bf0118dd01181c01186a0118230118480118270118550118d6011838011884011897010f01185801181a01189e011865011823010401190110011601182e01182101181801181c010d01181e01188201184401020119013401182401185c010d01184e01181e0218f601189801183d01186701188c01140118fa010d0118c601100118600118a801187e01181f010501183f011901320118de01183d011893011101110118da0101010101182901182d01183001184a011875010e0108011884010c01120118a201070118e2011834011823011401187401185c0118540119014a01182501187d01184e01188c010301185e01185f01183801185e01182201150118e101186401188501183b0118b70118350118d301184401186201182201185a0118210118430118b5010601182b01184c0119010301183501185401182a01189a0118350119025f0118320118ff01188c0119012f01181e01182101188d011858010401183401182f01188401187c02184401183501187f01189c010d011844011001182b011840011902090115011819021901110118cd011821010d010c01182a011201189901140117010a0119023e01186701183b01186301184d01186f01183d0118530118750118350119012301185c01184101150110010701184601183b01186f01182901183b01183301185f0119011a0118540118920118db01183a010a011901ae010102187d01186d02182601187f0118f70118430116011828011901050118700118be011888010b011838011301182c0118910118710105010d01188e0119013a01183f01183d0118ed0118850118420110010301183101130118260118680118de01182a0116011901a40118190118b40119010201183801188b0118440119015a011901e701184c01185901184d0119013c01183901181f0118260119011e01181c0118770119033e0118530118830104011822010f01181c01189e01189e0111011827010c0114011831011901fa01185501187d011845010f01188c01184601188d01188c01189c01182b01183b010c010901188501187c01182501183901183101183001189f01186e010c011501183e011863011901800110010701186801140118240118a301183c01189201188201181d01183401183d0118330118810119011b01189d01185c01182701181a0118240104011901aa01080118ba011872011001188501182f01181b0117011829011878010e0119018901186501185101187d01182501183301183a011856011878011818010e01170119014f01183e01190198011829010401183b01182c011832011889010801183301182001185b011877011851010501183a010c01184c0119013b01183d0119014c01183401185b0118d7011830011865011901540119017101184f01181b010301182501183c01188d011001183201181801186701182301100119013201181c0118d20118760118c901186001185c01183f01187c010e0119010801182001010118720118800118bf011830010e01182f010601181c01186c01187001182e0118c401090119013b0119015201186001184b01181d011864011862011001182d011861011894011201185e0119013c0118d101181b01185f0118ba01189f010a01181f01182901187c0118f60118bc01183401182001050119013b0118db0108010a010d01186d0118330118460119011501185701184b01189001182201188f0119019f01183b0118450118720118c101186b0118930118f001182101186a01189701181d01186d01188e010f01185201190108011001182501186e011829011901200118390118280119016c010701183801189501184401060118a0011895011820011819011901500118c201184e0118e3011401187a01182501040118b501181d01183b01182d01187101184a01182b0118750118820118cc01187d0118560118500118e1010c01187001183f010b01184301186701184f010301188801187001070118e70118400103010801182601187e01182c01186601186601181801140118eb01188f01181a0118580118260118ff010401185e0118a3011830011820010f01186f0118520119012e01183201185f0118410118bb011828011823010801184d011820010701189f01188b01186301186501187f01184b010601190101011835011851011820011838010b0118340118410118600118af0118430118fa01187401185a01183901184a01189301181e011827011834010101080108010c01184b01181d011501182001187a01182a01186201184101188d01187f02182a01189b011893010501190118010601186801184d01010119015a01185f010f0118520118580118460118380118b10118a701020118b10118480118cb0118240118b601185e0118820119016b01130118c1011901d60119014101182a0118260118330119011701188e0118390114010b0118240118aa011901e40118490118650118dd0118480118210118a501184901181c0118e30118310118240118300118bd01184501185f0105011842010901181b01184f011854010b010d01184201184d0118240118650110010e0118a802181d01020118190118a3010701184101181e01181c011824010f0118d801184d0118300118cc0118890118b30119012901182b0118f00118a2010d01060118bf0118a301110111011844010601181d01187d01190148011830010c0115010601060118410118b301182b01190136011823010a0118c401020118870118a30118b9011858011501184c010401182d0118610118c701181f011829011818011401182f01188b01184901188d011001181f0118760118f2010c01182a0118d001170118490119015001181d0118230118b9010701182401181d01185a01187201170118270118ef01183701183b0118340118270118c601060106011819010e01186c0118590118600119019301160118220118830118b401189801020118c5010b0118480118b10111011874011844011901130118740118a901186c011902210119015a01181f01186901182901187801182f011875010101188e010d0101011818011201186a01186e01182001030118580119017a01186f011826010f01181c011877011862011901ba0119014b01188601184501186101184001186601188401182e01186d010e01182d010701150118a701183501187a010501183f010e010701185f0118a001184301182e01040107011826010e0118260118b70118a0010801185801184d01184601188e01182f0118300118c4011894010e01186c0118e7011875011824011859011839011820010b0119010401182f01181e010201185601150118660118bb0118e70118fd0118260118300118620113010801183f01050118210118200107011837010101188e01183b01184301100119017601181a011301183501183e01184b01185101184b0118220118260119010101182a011869011881011818010f01190172010c01190239010f0118820119013901186601187b0119011d010201182d01181d01070119010901188701185c010b011874011901160119017d01183401181d01182b0119012d0118300118b101188d01182501184d0118380118770111011829010501188601186901100118a00119010f0118c40118b201185401189c0118430118a90118c4011501184b01184601181a01183d011830011901c30118a10118660119011b01182c011850011901360118a6010401182201185e01187c01185a011890011858011901050118fc01188a01188501182f01189801185801184001182101186401130118600118510118a8011825011901030118e7011840011828010d01185101188f01187e0118760118490119019201110119011801184101183e01181d0118d4011889010a010201188d01186e0115010101184e01187301182501184201187001186001184401182301184a0118b2010701181a0118740118900119011401183001182901182e01188801090118550103011901a601182e01181801183c011840010f010101182401184d01182701183e01187e011901100118390119021201182301181f0118530119011c01185e010c01183d010c01185f010e01060118610118a401185b011501182001020118fd011901b2010f01182d0107010f01188a0118360118b201181f01188d01183f0118340118e301189801186b010b010301185801184a0118f10118e201182b010301184d0101010201187d01184a02186d01120118240103010801186c01184201181c010b01185201186f01186f01186901188601187001181f01182101182401100118720118d101190122010101188801184a01182901190282011701189e011901740118340119018301120118250112010501188901186d0118b9010401110118690118310118c201183001185701182001130116011901160118b90118e401183901184a01184b01182b011901a00119014701184d0118370118e70119013801181c01183f01182501186001184b011823011830010c01187001188f0119011f01185d0119011501120118320118240118f001186601182c0118570118630118b101184501187b01182b01182801160119010b011870011843010e01150118310118770119017901186401181e01181901184101181f011821010401185a0118b301010119010101190147010e0118c201181c0119018301080118410115011301189a011601188e01183a010401187101181e01185301186a011901250118a0011842011832011901af01090118c101181d01182001160119010701183d01182601187f0118720119017701040118c501187901182a011845011887010701188c010301184101186901120118b501183101183c0118230114011902640118230118a3010501184f0118b801010118e40118940118210118980119015501188e01182c011821010801187f01183201183c01160118b101183b01187901080118220118270118320119013001184f01188d01181a01181d0118810118ea0118b101181801181c0118e80118390118b5011901180119020c01187801183801183201190162011823011877011858011901590118390119014e01183d01187501182a01181901183701188e01185701187301185201184701190109011601182a0118900118b3011822011901bb01120118ae0119012901186c0118e7010b01186c0118f1010701186e010801182d011901000118c7011885010a010f0118410119025701182101187e0118710118c2011101184501184701182a01188b01188401188f0119011301183201186901189b011201188b01189d010701184701187e0118d401181a0113010501182d01188501187001184d01189b0110010f0118460108011001190120010a0119016101182a011201181901189b01185001183201183201181d01187d0118680118410118ad011863011896011822010b0118780118e001185e0118790118490118300119020b0118430118ee011892011401183d01183c011901520118610118d40118240118650118e001181f011902cc01182c01120118c601184601189c011501189401181b01182d01110119012c01183e01187501183701181e01182301184701150118310118af0104010201186201182901182301183a0118710118eb01188b01110104011901e10118510118a6011822011902d0011001140118530118370118210118d701184f0118f00118bb0118d001185c01181e01181b01183101010118540118cd0119016701184401186e01189901183001185301120118bc01183301188501184c01182f011901500118c4010b011901530119014c01150118350118d10118b301186001189b0119012d01183d01181b0118af010601183b01189d0118a0011902890118b30118840101010101190224011501185201070118b4011101182b0118a70118f20118d701184c01182b01182101183f010e0118e5010e01189f0118cc01185c011820011901160118470119027301188a01182301187e0118f201140118330118670118ea0118860118ca011819011874011839010101187801185601185e0118c00118ef01189b01183d0118b70118b101187801186801183301181d01181901188201183d011865011901fe0118920119013f010d0118a201183e01184f010a0118d80118a70118800118c20119010901188f0118700118ca0118730118fa01184701186f01040118970118d701185301120119019b0118df0119010f01182b01181e01189201183a0118630118f80118a70118d20118700118ee0118690118ab01184b0118660118a101185d01188001181c0119018201181c01187c0119019e0118200118c601184201183901183101182f01186b01181b01160118d1011827011899011901cb01184501186c01100118bc011838011901a10119011d010801182c01140118500118f3011826011901e801187c011896010c0118a801182d01188301181901090118640119011101181f021901ae0119015d01030101011901b2010d01189d011901a70118d6011889011501060118ee011901d901070119014b011904c70118cc011902f10118730118bf01188b0118cb011892011830010d0118b501184f0118830118790118fd01182501120119014801184a01182d01185f0118a6011901c6011901d201186f01183d0118aa01189e01100118560118c101184f01080118bc01190127011875010301182701070118ae011901530118a901186401181c011902280104011902be011901d50118bd011829011901ca01183901181801120118800118fe0119011101150118e701183d0119028e01170119012301184e01190219011902cb01183201182b0119010e0118290118a401","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","4.348681337991257","36.75738449067154","349.1608244891508","2803.7052150465256","21875.127876685547","178351.26041698662","1755775.4865747062","14211693.852437943","121076750.32435302","2146992128"],"densities":["0.02986250105839754","0.0030855909145420324","0.0003200989080033394","0.00004074075840090721","0.0000052434473177055506","6.390750996753103e-7","6.339448725443971e-8","8.02831208930051e-9","9.357595766234467e-10","4.936040325372868e-11"],"corrections":0,"bucketCount":3674},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","10.383070213282933","252.10114881366763","4691.000038714417","94555.02165284735","2424261.404081021","49175344.20207077","2146992128"],"densities":["0.015224989221002561","0.0005910072746081951","0.000032183013490612985","0.0000015897034240305539","6.131980576378375e-8","3.055696987263908e-9","6.80980073953415e-11"],"corrections":0,"bucketCount":3674},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.1104654732166996","21875.127876685547","428919940.8020706","2094294014.5529628","2146992128"],"densities":["0.0450261635376779","0.000020573293183103555","1.0492003485551642e-9","2.9422819036469216e-11","1.8976011370976746e-11"],"corrections":0,"bucketCount":3674}]},"bimodal":{"count":3000,"exceeds":0,"min":"100","max":"5499","mean":"2659.669","stddev":"2562.613756585062","percentile":["100","100","112","124","148","5241","5395","5491","5499","5499"],"percentilesAt":[["0.1","100"],["1","100"],["12","112"],["25","124"],["50","148"],["75","5241"],["90","5395"],["99","5491"],["99.9","5499"],["100","5499"]],"percentiles":[["0","100"],["50","148"],["75","5241"],["87.5","5369"],["93.75","5437"],["96.875","5471"],["98.4375","5487"],["99.21875","5491"],["99.609375","5497"],["99.8046875","5497"],["99.90234375","5499"],["100","5499"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0.018"],[999,"0.505"],[1000,"0.505"],[5000,"0.505"],[1000000,"1"],[5499,"1"],[5500,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,54],[999,0],[1000,0],[5000,0],[1000000,0],[5499,5],[5500,0]],"linearStep":274,"linearBuckets":[["274","1515"],["548","0"],["822","0"],["1096","0"],["1370","0"],["1644","0"],["1918","0"],["2192","0"],["2466","0"],["2740","0"],["3014","0"],["3288","0"],["3562","0"],["3836","0"],["4110","0"],["4384","0"],["4658","0"],["4932","0"],["5206","640"],["5480","778"],["5754","67"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","883"],["256","632"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","1485"]],"logBuckets10":[["10","0"],["100","54"],["1000","1461"],["10000","1485"]],"skewness":"0.024700427716906424","kurtosis":"-1.9930061955998104","meanCI95":["2659.669","2567.9164121626095","2751.4215878373902"],"meanCI99":["2659.669","2539.057564663903","2780.2804353360966"],"percentileCI":[["50","0.95",148,146,5015],["99","0.95",5491,5487,5493],["90","0.99",5395,5383,5411]],"export":"ab00010101021b000000e8d4a51000030404190bb80518640619157b070008fb3ff0000000000000091a0006c0000a9902261864183602183302184102183202183002184402183f02183302183a02183c02184202183a02184b02184102183302183c02184602184602183f02183f02183d02183e02184202183e0218371912f5060207020402080201020902030206020702070202020c02080203020502080205020602080209020a0206020502050206020302030206020902050205020902050205020402060206020d02070207020b020302050208020402010207020502040205020202040205020a02060208020a020b02050202020602060205020602090207020a020c02060203020702070205020602050207020302030207020c02090205020802020205020a0206020c0205020502060209020302090205020602050204020302080206020602060203020502040207020202070204020602050207020702050206020802060208020302030204020302060203020a0209020502060205020802090207020c020802050203020702040206020302060208020a020602070208020602030206020602050205020602020207020a020202050206020602070206020402070207020702050203020102030209020102060207020402070207020e0205020702040204020302030204020902040204020202080206020802090209020202040208020402080207020602040202020802030205020202080208020202090205020202080204020a020502050206020402060204020702050208020102050203020102030209020a0202020a0208020602090205020402090208020302070203020e0204020602080205","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["100","109.98699073456697","119.98252823585753","129.5824353622775","138.70176685308016","1566.0020677370537","5093.2245492629045","5186.8869316629725","5289.015013603393","5395.421468835726","5499"],"densities":["0.010013026211577427","0.010004464490987972","0.010416767441925491","0.01096571608355891","0.000070062340726802","0.000028350919320728733","0.0010676644928041816","0.0009791626171765226","0.0009397926073343458","0.0009654510338769095"],"corrections":0,"bucketCount":275},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["100","114.14631642493161","127.98723858589582","141.52099292684923","5065.502689756932","5202.304042229508","5350.982030172245","5499"],"densities":["0.010098540041517095","0.010321360180757702","0.010555618142473173","0.00002901252515806672","0.0010442670359255492","0.0009608493149111228","0.0009651337808739179"],"corrections":0,"bucketCount":275},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["100","104.2446148394385","1566.0589121387704","5450.709491241179","5498.570930132635","5499"],"densities":["0.011779631813805347","0.0003078366389160132","0.00011584053464699966","0.001023788693673136","0.0023306227634707927"],"corrections":0,"bucketCount":275}]},"heavy":{"count":3000,"exceeds":0,"min":"1000","max":"1000031","mean":"7746.738333333334","stddev":"40685.67290036954","percentile":["1000","1009","1145","1342","1960","4081","10416","100003","500015","1000031"],"percentilesAt":[["0.1","1000"],["1","1009"],["12","1145"],["25","1342"],["50","1960"],["75","4081"],["90","10416"],["99","100003"],["99.9","500015"],["100","1000031"]],"percentiles":[["0","1000"],["50","1960"],["75","4081"],["87.5","8474"],["93.75","15873"],["96.875","34482"],["98.4375","62500"],["99.21875","142856"],["99.609375","333328"],["99.8046875","500000"],["99.90234375","1000000"],["100","1000000"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0.001"],[5000,"0.7953333333333333"],[1000000,"1"],[1000031,"1"],[1000032,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,3],[5000,1],[1000000,3],[1000031,3],[1000032,0]],"linearStep":50001,"linearBuckets":[["50000","2943"],["100000","28"],["150000","11"],["200000","5"],["250000","1"],["300000","0"],["350000","6"],["400000","0"],["450000","0"],["500000","3"],["549984","0"],["600000","0"],["649984","0"],["700000","0"],["749984","0"],["800000","0"],["850016","0"],["900000","0"],["950016","0"],["1000000","3"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","87"],["2048","1460"],["4096","703"],["8192","361"],["16384","208"],["32768","83"],["65536","55"],["131072","19"],["262144","12"],["524288","9"],["1048576","3"]],"logBuckets10":[["10","0"],["100","0"],["1000","3"],["10000","2684"],["100000","284"],["1000000","29"]],"skewness":"17.665418021856375","kurtosis":"381.8910410744203","meanCI95":["7746.738333333334","6290.016356827168","9203.460309839498"],"meanCI99":["7746.738333333334","5831.835085226297","9661.64158144037"],"percentileCI":[["50","0.95",1960,1893,2061],["99","0.95",100003,66667,142863],["90","0.99",10416,9433,12500]],"export":"ab00010101021b000000e8d4a51000030404190bb8051903e8061a000f4240070008fb3ff0000000000000091a0006c0000a9907701903e80301040106010401040104010101030206010501010102010601030101010401050102010401040103010401060102010301020106010501040102010402030104010202040105010101040105010301030103020101020102010201020101010101020102010402030101020301030103010201040202010201030107010301040102020101010102010601030101020201010102010301010102020701040102010101010204010101020103010202020103010401030103030101020106020201020105010102040106010201010203010501010105020301010101010202030104010102030104010401040201010103030108010202050108010202030105010402010103010302020105010102020102010302020101010202020103020301050103020301050204010501030202010302040104010302040103020201030203010301030203010202040104020401010204010402020102020401030205010301020205010802030201010702010304010302030103020701030203010102010203010102050102020501020201020501030201010202020202030101050203020401030202020301010502020201040204020202040104020102040105020102040104020302050204010402040402010502030201020401010202020202030101020402020207020401030205020302030201020501010203020502040204020301020205020402030203020202030201020202030105020502020207020702020203020202010402020602020201040202020202020402030204020404050407020702030304020402020203020802030202020202020209030402030203020402050203030402010208020102030303020502040205030202010202020503050402020303020204020503040205020403040404030202010204030202030206030402040306020103030203020203020203030402050305020603060202030602010303020103040302020203030207030202020301050303020301020205030306030103020205030203010503030403030202030103060307030205030302030303030305020403040304030503020302030303060302030103090303030203050305030403050303030303030303030203040601030504030303030403040305030404010304030303020405030503020301040703030302040503050703030204040304030304010305040403020405030304020306040103020401030604020401030404030405030204020401030304030402030404010403040704020303040304050403040304020401040504010401080304030402040204020c02040604010403040205020401040204060505040304010503040104030504040a040305040404050204010502040305010902040205010501040205050501040305020501050104050502050305010502050105040503050305050504050305020502050205040507050306020501050405020602050310060601050305030604050306010602050606010503060406050603050406040606060206010603060305020602070106070601060306010602060507010607060306010702060207040606070306050d06181b040704060407040706070507020704070107030e060703080307030703080107040703080607040803070208020803070308060801080607030802080308010804090708020803080208010904080208050906080209040901181a010903090309020902090309020907090109030a04090109030a0409020a030a020a0409030a0114010a040a030a020b020a040a010b050a020b050a010b030b040b040b030b050b030b010b040c050b020b020c010b030c050c030c030c030c030c020c060c040d040c030d040c03181a070d010d010d050d020d030e040d050e020d020e030e04181c030e040e050f050e060f010f030f05181e020f010f0210020f031002100610031001100318210210031103110311031102110112011105120212071201120412021826031206130313061401130214031402140214041402150315061504150315041602150116051604170516031702170517011818031818021701181903181806181904181901181902181904181a02181a02181a04185001181b01181c02181c01181c01181c03181d03181d05181d02181e02181e03181e05181f01183e05182007182005182101182103182102182104182201182302182303182307182401182402182407182504182504182603182701182704182704182802182802182902182902182a01182a01182b05182c04182c01182d06182d02182e03182f04182f03183005183101183103183206183202183402183402183505183504183703183701183807183904183a04183b02183b02183d03183e02183e04184006184003184202184204184403184501184602184704184902184904184b06184c01184e03184f0118a203185404185505185602185804185a05185c01185d03185f0318610b186404186502186704186904186b04186e0518700218720218750218f201187c01188005188302188502188906188c04189001189304189604189b05189e0418a20418a70218aa0718b00318b40218b90318be0318c40618c90418cf0318d50618dc0618e20318e90318f10418f80619010003190108031901110219011b011901240119012e0319013a02190144041901510419015d041902e40319018906190198031901a9031901bb011901cf051901e3021901f9051902100119022a011902450319026202190281041902a2021902c8011902ee0419031a011903480119037c061903b3021903f0031903180419023f0219026804190295041902c805190302041903410319038a051903dc0319043a041904a703190523021905b6031906620319072f06190708031904a70219055d0119064303190766031908e102190ad90519194006190ba0021910470319186a0119174f061928b10319400003","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1000","1115.6005988741867","1251.266686571931","1435.5325178934097","1656.824308093357","1966.1994871796107","2515.5206952358635","3390.064809390491","5211.279501655024","10584.308085188894","1000016"],"densities":["0.00086504742167326","0.0007371038827536175","0.0005426942113078756","0.00045189204673903806","0.00032323213612466315","0.00018204285313113119","0.00011434528959887214","0.000054908408341280274","0.000018611477390323028","1.010681190193874e-7"],"corrections":0,"bucketCount":952},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1000","1172.4579899444116","1406.2934331673869","1732.3966090498104","2345.082395290817","3551.4931894359443","7451.812749731599","1000016"],"densities":["0.0008283590856137778","0.0006109302374701191","0.00043807344859667964","0.00023316542682279298","0.00011841500718532006","0.0000366270354643233","1.4392735975383568e-7"],"corrections":0,"bucketCount":952},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1000","1046.319804018247","1966.1998672958782","20207.940503008114","777986.5591669949","1000016"],"densities":["0.0010794518901743044","0.0004891942090761287","0.000024668698507807176","6.466268484374796e-8","4.503907212702168e-9"],"corrections":0,"bucketCount":952}]},"huge":{"count":110,"exceeds":890,"min":"988577792","max":"997405491199","mean":"500808796644.07275","stddev":"288830461528.6621","percentile":["988610559","988610559","135564099583","260826988543","487378124799","731050409983","904828813311","991332139007","997405491199","997405491199"],"percentilesAt":[["0.1","988610559"],["1","988610559"],["12","135564099583"],["25","260826988543"],["50","487378124799"],["75","731050409983"],["90","904828813311"],["99","991332139007"],["99.9","997405491199"],["100","997405491199"]],"percentiles":[["0","988577792"],["50","487361347584"],["75","731016855552"],["87.5","894259167232"],["93.75","978581454848"],["96.875","990359060480"],["98.4375","991298584576"],["99.21875","997371936768"],["100","997371936768"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[997405491199,"1"],[997405491200,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[997405491199,1],[997405491200,0]],"linearStep":49870274559,"linearBuckets":[["49868177408","5"],["99736354816","7"],["149602435072","5"],["199472709632","2"],["249342984192","5"],["299204870144","8"],["349083533312","7"],["398945419264","6"],["448824082432","6"],["498685968384","4"],["548564631552","8"],["598409740288","1"],["648305180672","7"],["698167066624","7"],["748028952576","7"],["797890838528","3"],["847786278912","7"],["897648164864","2"],["947510050816","4"],["997371936768","9"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","0"],["16384","0"],["32768","0"],["65536","0"],["131072","0"],["262144","0"],["524288","0"],["1048576","0"],["2097152","0"],["4194304","0"],["8388608","0"],["16777216","0"],["33554432","0"],["67108864","0"],["134217728","0"],["268435456","0"],["536870912","0"],["1073741824","1"],["2147483648","0"],["4294967296","0"],["8589934592","0"],["17179869184","1"],["34359738368","1"],["68719476736","6"],["137438953472","4"],["274877906944","17"],["549755813888","33"],["1099478073344","47"]],"logBuckets10":[["10","0"],["100","0"],["1000","0"],["10000","0"],["100000","0"],["1000000","0"],["9999872","0"],["99999744","0"],["999981056","1"],["9999745024","0"],["99996401664","11"],["999989182464","98"]],"skewness":"0.06663327801976736","kurtosis":"-1.1296549695540976","meanCI95":["500808796644.07275","445977783368.12585","555639809920.0197"],"meanCI99":["500808796644.07275","428279944672.20685","573337648615.9387"],"percentileCI":[["50","0.95",487378124799,397250920447,607234555903],["99","0.95",991332139007,984252153855,997405491199],["90","0.99",904828813311,803796418559,990392614911]],"export":"ab00010101021b000000e8d4a51000030404186e051a3aecbecb061b000000e83842d42d070008fb3ff0000000000000091a0006c0000a98da1a000435d90119efbb011956600119138a011909f801190cbc0119089c011907ec0119093101190b2e01190382011903fa01192bab0119020a011901b8011901c40118ae011908da01190f660118e0011905b60119051b011903c9011872011908410118a6011901cc01190221011903f60119012d011902a20118e3011903ff01181b01190258011901bd0119050801186201181d0119024c0118b5011872011826011905970119037b0119014f0119028b0118c80118d70118c4011901a3011907a901187e01190301011901f40119052c01185e0110011850011902b40118a1011903d9011901180119055e0119019501140118310118f80110011902500118c1011902de01040118fb011855011501181f011901ab011901860119014d0118d6011858010e0118630118ec0118ca011901a7011901060119034c0118660118260119026701090119015c021903fc0119029b0118cd01186d01190245011818011905f501140118310118230118850118b701181a01020118b501","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["988594176","99741391149.59244","219567888138.1241","302157368675.788","387686392523.5908","489463896166.3159","601022868441.8236","695397441469.3376","784724566010.7021","918985802070.0681","997388713984"],"densities":["1.0126295463483539e-12","8.345399599687103e-13","1.2108079545844367e-12","1.1691937485215317e-12","9.825353975180528e-13","8.963868881208272e-13","1.059607442895089e-12","1.119480790559796e-12","7.448166197113158e-13","1.2754628311481168e-12"],"corrections":0,"bucketCount":109},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["988594176","153175060635.65424","290611690909.2155","415092534020.1993","568550858099.4757","706837265006.6827","855085687258.6643","997388713984"],"densities":["9.386980733599944e-13","1.0394400864805277e-12","1.147623516092153e-12","9.30918174131388e-13","1.0330526770646584e-12","9.636334787720365e-13","1.0038939166970548e-12"],"corrections":0,"bucketCount":109},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["988594176","53539307928.56337","489463896166.3159","978288039989.8633","997221208166.6312","997388713984"],"densities":["9.514618628288575e-13","1.0322886392326435e-12","9.205764602380975e-13","2.5880507447309206e-12","5.969941914306409e-12"],"corrections":0,"bucketCount":109}]}},"comparisons":[{"a":"uniform","b":"wide","welch":["-19.36559777570248","4999.000003337148","0","-75157261.84684463","-61339297.94735534"],"mannWhitney":["4151300","-11.111466871201321","1.1033201289337259e-28"],"cohensD":"-0.32403454015703786","cliffsD":"-0.16974","ksTest":"0.5367999999999999"},{"a":"bimodal","b":"heavy","welch":["-6.833679804310956","3022.7948475282024","9.969469694226518e-12","-6546.673359144903","-3627.4653075217648"],"mannWhitney":["3569123.5","-13.875721358925533","8.889425333370512e-44"],"cohensD":"-0.1764448538360686","cliffsD":"-0.20686144444444443","ksTest":"0.505"},{"a":"uniform","b":"constant","welch":["76.46930353227323","1999","0","4797.287652764121","5049.829347235879"],"mannWhitney":["199200","16.764775742620284","4.416774906879843e-63"],"cohensD":"1.7517353002131606","cliffsD":"0.992","ksTest":"0.996"},{"a":"three","b":"four","welch":["0.9508731633956237","2.0082239472623944","0.44167566755067034","-1109.4563086478202","1742.1229753144867"],"mannWhitney":["6","0","1"],"cohensD":"0.867135644459936","cliffsD":"0","ksTest":"0.33333333333333337"},{"a":"empty","b":"uniform","welch":["0","0","1","0","0"],"mannWhitney":["0","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"},{"a":"one","b":"two","welch":["0","0","1","0","0"],"mannWhitney":["1","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0.5"}]},{"options":{"lowest":1,"highest":1000000000,"figures":5},"histograms":{"empty":{"count":0,"exceeds":0,"min":"9223372036854775807","max":"0","mean":"NaN","stddev":"NaN","percentile":["0","0","0","0","0","0","0","0","0","0"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","1"],["75","1"],["90","1"],["99","1"],["99.9","1"],["100","1"]],"percentiles":[["100","0"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[1,"0"],[2,"0"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[1,0],[2,0]],"linearStep":1,"linearBuckets":[],"logBuckets":[],"logBuckets10":[],"skewness":"0","kurtosis":"0","meanCI95":["NaN","NaN","NaN"],"meanCI99":["NaN","NaN","NaN"],"percentileCI":[["50","0.95",0,0,0],["99","0.95",0,0,0],["90","0.99",0,0,0]],"export":"ab00010101021a3b9aca0003050400051b7fffffffffffffff0600070008fb3ff0000000000000091a001c00000a80"},"one":{"count":1,"exceeds":0,"min":"7","max":"7","mean":"7","stddev":"0","percentile":["7","7","7","7","7","7","7","7","7","7"],"percentilesAt":[["0.1","7"],["1","7"],["12","7"],["25","7"],["50","7"],["75","7"],["90","7"],["99","7"],["99.9","7"],["100","7"]],"percentiles":[["0","7"],["100","7"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[7,"1"],[8,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,1],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[7,1],[8,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","0"],["4","0"],["5","0"],["6","0"],["7","1"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","1"]],"logBuckets10":[["10","1"]],"skewness":"0","kurtosis":"0","meanCI95":["7","NaN","NaN"],"meanCI99":["7","NaN","NaN"],"percentileCI":[["50","0.95",7,7,7],["99","0.95",7,7,7],["90","0.99",7,7,7]],"export":"ab00010101021a3b9aca000305040105070607070008fb3ff0000000000000091a001c00000a820701","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["7","7","7","7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["7","7","7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["7","7","7","7","7","7"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]},"two":{"count":2,"exceeds":0,"min":"3","max":"9","mean":"6","stddev":"3","percentile":["3","3","3","3","3","9","9","9","9","9"],"percentilesAt":[["0.1","3"],["1","3"],["12","3"],["25","3"],["50","3"],["75","9"],["90","9"],["99","9"],["99.9","9"],["100","9"]],"percentiles":[["0","3"],["50","3"],["75","9"],["100","9"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.5"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[9,"1"],[10,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[9,1],[10,0]],"linearStep":1,"linearBuckets":[["1","0"],["2","0"],["3","1"],["4","0"],["5","0"],["6","0"],["7","0"],["8","0"],["9","1"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"]],"logBuckets10":[["10","2"]],"skewness":"0","kurtosis":"0","meanCI95":["6","-32.11861420852408","44.11861420852408"],"meanCI99":["6","-184.97022348861458","196.97022348861458"],"percentileCI":[["50","0.95",3,3,9],["99","0.95",9,9,9],["90","0.99",9,3,9]],"export":"ab00010101021a3b9aca000305040205030609070008fb3ff0000000000000091a001c00000a8403010601","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","3.20631895627745","3.614082426485135","4.244224543345755","5.066378477103939","6.000000000000661","6.933621522896061","7.755775456654244","8.385917573514865","8.79368104372255","9"],"densities":["0.4846864379515554","0.24524021229529785","0.15869436008848586","0.12163172356651711","0.10710978436929422","0.10710978436944585","0.12163172356651718","0.1586943600884858","0.24524021229529752","0.48468643795155636"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","3.354130868400228","4.141148961265419","5.326286696880615","6.673713303119386","7.858851038734582","8.645869131599772","9"],"densities":["0.4034021193986685","0.18151697419949023","0.12054054019551301","0.10602220721759137","0.12054054019551305","0.18151697419949037","0.40340211939866866"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","3.080949106595003","6.000000000000661","8.919050893404997","8.998766051082084","9"],"densities":["0.6176720423877602","0.15415969657006728","0.1541596965701371","0.6146886166679947","0.8104063186740486"],"corrections":0,"bucketCount":2}]},"three":{"count":3,"exceeds":0,"min":"1","max":"1000","mean":"335.3333333333333","stddev":"469.99314415803514","percentile":["1","1","1","1","5","5","1000","1000","1000","1000"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","5"],["75","5"],["90","1000"],["99","1000"],["99.9","1000"],["100","1000"]],"percentiles":[["0","1"],["50","5"],["75","1000"],["100","1000"]],"cdf":[[0,"0"],[1,"0.3333333333333333"],[2,"0.3333333333333333"],[7,"0.6666666666666666"],[42,"0.6666666666666666"],[100,"0.6666666666666666"],[999,"0.6666666666666666"],[1000,"1"],[5000,"1"],[1000000,"1"],[1000,"1"],[1001,"1"]],"countAt":[[0,0],[1,1],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,1],[5000,0],[1000000,0],[1000,1],[1001,0]],"linearStep":50,"linearBuckets":[["50","2"],["100","0"],["150","0"],["200","0"],["250","0"],["300","0"],["350","0"],["400","0"],["450","0"],["500","0"],["550","0"],["600","0"],["650","0"],["700","0"],["750","0"],["800","0"],["850","0"],["900","0"],["950","0"],["1000","1"]],"logBuckets":[["1","1"],["2","0"],["4","0"],["8","1"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","1"]],"logBuckets10":[["10","2"],["100","0"],["1000","1"]],"skewness":"0.707068367939628","kurtosis":"0","meanCI95":["335.3333333333333","-1094.5902216930783","1765.2568883597448"],"meanCI99":["335.3333333333333","-2963.0427997930706","3633.7094664597375"],"percentileCI":[["50","0.95",5,1,1000],["99","0.95",1000,5,1000],["90","0.99",1000,1,1000]],"export":"ab00010101021a3b9aca00030504030501061903e8070008fb3ff0000000000000091a001c00000a86010104011903e301","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","5.932607603243072","22.700394847776252","62.922989940120665","139.7725442831563","261.92592592592587","426.13620879717644","612.9672969674574","790.2991406883924","925.3771141512873","1000"],"densities":["0.0202732526167807","0.005963816128010755","0.00248616479793053","0.0013012437203425678","0.0008186429115195856","0.0006089752617892097","0.0005352428280504276","0.0005639145113574124","0.0007403131497784085","0.0013400714655117433"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","11.075754645242279","55.227569135094825","169.9105532246607","375.7563621894388","639.7111592472341","874.319812866049","1000"],"densities":["0.014178307023841562","0.003235589397803251","0.0012456699133811633","0.0006940007356748608","0.0005412182102750836","0.0006089167669375607","0.001136671945792733"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.5568893265228576","261.92592592592587","970.4876086277768","999.5554699188299","1000"],"densities":["0.03211532068992312","0.001734979648688859","0.0006350893803403017","0.0016857105347162903","0.002249566547595048"],"corrections":0,"bucketCount":3}]},"four":{"count":4,"exceeds":0,"min":"2","max":"64","mean":"19","stddev":"26.095976701399778","percentile":["2","2","2","2","2","8","64","64","64","64"],"percentilesAt":[["0.1","2"],["1","2"],["12","2"],["25","2"],["50","2"],["75","8"],["90","64"],["99","64"],["99.9","64"],["100","64"]],"percentiles":[["0","2"],["50","2"],["75","8"],["87.5","64"],["100","64"]],"cdf":[[0,"0"],[1,"0"],[2,"0.5"],[7,"0.5"],[42,"0.75"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[64,"1"],[65,"1"]],"countAt":[[0,0],[1,0],[2,2],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[64,1],[65,0]],"linearStep":3,"linearBuckets":[["3","2"],["6","0"],["9","1"],["12","0"],["15","0"],["18","0"],["21","0"],["24","0"],["27","0"],["30","0"],["33","0"],["36","0"],["39","0"],["42","0"],["45","0"],["48","0"],["51","0"],["54","0"],["57","0"],["60","0"],["63","0"],["66","1"]],"logBuckets":[["1","0"],["2","2"],["4","0"],["8","1"],["16","0"],["32","0"],["64","1"]],"logBuckets10":[["10","3"],["100","1"]],"skewness":"1.1249561006949362","kurtosis":"-0.6915329232082903","meanCI95":["19","-28.948388276616278","66.94838827661627"],"meanCI99":["19","-69.00217210471634","107.00217210471634"],"percentileCI":[["50","0.95",2,2,64],["99","0.95",64,8,64],["90","0.99",64,2,64]],"export":"ab00010101021a3b9aca00030504040502061840070008fb3ff0000000000000091a001c00000a8602020601183801","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["2","2.1214881848725766","2.59375","3.8766929625087334","6.718750000000001","12.088759862808779","20.781250000000007","32.65059402055783","45.90625000000002","57.34108084774391","64"],"densities":["0.8231253113616391","0.21174695221339987","0.07794578786609092","0.03518578222774577","0.018621939727256866","0.01150418331476101","0.008425065431316082","0.007543949552937039","0.008745210255535187","0.015017452189086762"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["2","2.260178895464949","3.6186449301918753","7.9516758986192855","17.940490621956265","34.523454456253454","52.88675172594847","64"],"densities":["0.5490727547361286","0.10516062912523202","0.03296933345228088","0.01430171114530577","0.00861469302379368","0.007779493015826749","0.012854670329889233"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["2","2.083206032793557","12.253877226493122","61.37877112059458","63.96152982082045","64"],"densities":["0.6009179661774692","0.04424486756378103","0.009160325129048932","0.01897196203257965","0.025994160186590367"],"corrections":0,"bucketCount":3}]},"constant":{"count":100,"exceeds":0,"min":"42","max":"42","mean":"42","stddev":"0","percentile":["42","42","42","42","42","42","42","42","42","42"],"percentilesAt":[["0.1","42"],["1","42"],["12","42"],["25","42"],["50","42"],["75","42"],["90","42"],["99","42"],["99.9","42"],["100","42"]],"percentiles":[["0","42"],["100","42"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[42,"1"],[43,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,100],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[42,100],[43,0]],"linearStep":2,"linearBuckets":[["2","0"],["4","0"],["6","0"],["8","0"],["10","0"],["12","0"],["14","0"],["16","0"],["18","0"],["20","0"],["22","0"],["24","0"],["26","0"],["28","0"],["30","0"],["32","0"],["34","0"],["36","0"],["38","0"],["40","0"],["42","100"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","100"]],"logBuckets10":[["10","0"],["100","100"]],"skewness":"0","kurtosis":"0","meanCI95":["42","42","42"],"meanCI99":["42","42","42"],"percentileCI":[["50","0.95",42,42,42],["99","0.95",42,42,42],["90","0.99",42,42,42]],"export":"ab00010101021a3b9aca00030504186405182a06182a070008fb3ff0000000000000091a001c00000a82182a1864","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["42","42","42","42","42","42","42","42","42","42","42"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["42","42","42","42","42","42","42","42"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["41.5","41.55","42","42.45","42.499","42.5"],"densities":["1.0000000000000568","0.9999999999999937","0.9999999999999936","1.0000000000000113","1.0000000000023315"],"corrections":0,"bucketCount":1}]},"uniform":{"count":2000,"exceeds":0,"min":"3","max":"9995","mean":"4965.5585","stddev":"2878.7129715513056","percentile":["9","93","1149","2456","5017","7342","8957","9877","9968","9995"],"percentilesAt":[["0.1","9"],["1","93"],["12","1149"],["25","2456"],["50","5017"],["75","7342"],["90","8957"],["99","9877"],["99.9","9968"],["100","9995"]],"percentiles":[["0","3"],["50","5017"],["75","7342"],["87.5","8718"],["93.75","9292"],["96.875","9678"],["98.4375","9815"],["99.21875","9907"],["99.609375","9938"],["99.8046875","9957"],["99.90234375","9980"],["99.951171875","9995"],["100","9995"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0.0005"],[42,"0.004"],[100,"0.0125"],[999,"0.106"],[1000,"0.106"],[5000,"0.4965"],[1000000,"1"],[9995,"1"],[9996,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[9995,1],[9996,0]],"linearStep":499,"linearBuckets":[["499","104"],["998","108"],["1497","93"],["1996","109"],["2495","96"],["2994","84"],["3493","110"],["3992","90"],["4491","99"],["4990","98"],["5489","98"],["5988","104"],["6487","105"],["6986","128"],["7485","94"],["7984","98"],["8483","91"],["8982","96"],["9481","97"],["9980","97"],["10479","1"]],"logBuckets":[["1","0"],["2","0"],["4","1"],["8","0"],["16","1"],["32","3"],["64","7"],["128","17"],["256","21"],["512","58"],["1024","106"],["2048","215"],["4096","386"],["8192","840"],["16384","345"]],"logBuckets10":[["10","2"],["100","23"],["1000","187"],["10000","1788"]],"skewness":"-0.016305416482334096","kurtosis":"-1.189348249612471","meanCI95":["4965.5585","4839.287652764121","5091.829347235879"],"meanCI99":["4965.5585","4799.552454720941","5131.5645452790595"],"percentileCI":[["50","0.95",5017,4782,5226],["99","0.95",9877,9818,9926],["90","0.99",8957,8801,9123]],"export":"ab00010101021a3b9aca000305041907d005030619270b070008fb3ff0000000000000091a001c00000a990e2c030106010a01070102010c02010106010b0103010301050101010101020106010301040107010101020203020801020104010b010501080101011201010106010501080101020502160104010201130108010601020105010401090201010e0104020101030103010401020102020102040202010601040216010301040103020e01040108010e0108020e020101020104010401020109010d01010101010301010102010901040109010201020102010e010501050103010502020101010601030102010101050101010601030102020401030103010301010107010c01120102010101010104010701030101010201050102010802020105010101010106010501030106010a0101010401020102010401050102010101030209010b010201010102010201030102010101040104011101020103010101040102011401040104010a0101010401030103011101030102010101030104010701010101010a0109010b010e0110020b01020104010201010101010101060112010301140101010f010f010d0102010501020107010801070101010801050201010301140203010d0109010201010104010d0101010101050103010101010102010501030103010301020102010901090101010e010201010114010b01020101010301010109030101070104010c010101020104010301050107010e0112010301010106010201050101010201181d01080101010101010203010301090105010901030103010e010101050102010901010101010d010c0105010901090201010101070101010301020103030b0101010a01020113010702010104010101080103010401020103010d01060101010501080108010201020101010f0105010101030109010302060203010501150102010101060113010801020101010101020107010101060102010301080112010301070101010201020106010201010105011001030101010f01040102010401020101020701010103010501010103010701010105010201080103010801050106010701070105010501020101010301010102010e01010103010301030101010101050101020201020102010d0101010a0106010101050208010901040101020201060106010601030106010f010601020108010f0108010301160105010501110102010c01060107010801010105010401020107020b010201181901010109010a0101010b010701060101010202020104010801030102010201010106010c010701070102010201181901090201010101060102020301010109010a020101010203010c01020102010201050102020601020201020201010102010802010101010b0101010501060101010801090109010101030105010d0108010a01160109010101181901030103010601150104010801060216010e01080102010d0108010b010301070106020201010118240108010901060102010a0207010501040202010c0303010b0102010801030102011601020102010201030206010b01020103020501070108030101070107020201020102010f01070101010101010101010301050102010d010b010201010208010c010101020106010501020101020501020101010a020101070103010201010103010501050205010201060104010701040104010301010102010a01060118260104010a010302030102010301010104010f020901010101010301030109010301050105010502130102010b010301020106010502040102020101070204010b0203010c0104010c010901040103010301100106010201010108010301010109010101020301010801020104010101030105010c0103010d01010105010a0101010801030103010a010401040104010e010e010401040103010201020206010f010101182d01090106010e01010105010302010102010501080101011101020102010f01040101010e01040201010101181c010601181e010302030104010301040106020b010401010304010101010105010a0101010601010102010601040109010201030101010a0113020201030101010a010201070102010901020104010601060101010a010a010501110103010101020103010602060102010301160108010301030104010101120102010201050109010401010111010101030104010201020101010301010109010201020101010501030102010601010101010101140102020301140101010c010101020102010a01110102010d0105010101090105010601010105010701040101020a010b010501010102020602050101010d02040104010b01020102010602010101010b0203010202010102010a010301040101010801040101010d01040105010801040102010a0105010a010701130108010d01010108010201020101010301020104010401030101010301010103010a010501150105010201020102010801030107010301150204010201040110010e010301050101010301070108020a010201040107010a010201040104010201090103010601050106030602090203010801170101010602030104010301050102010702020104010301020101010701040107010b010301090105010401030110020301010104020302020103010101070103010701020107011201020104010f0108010201090213010a0114010b01020106010601010102010602030118190204010401010102010301030103010301040103010b010d010b010401090104010a01020104010301020108010302030109010601060102010402040103011001030202010a0104010101040102010c010a01060102010501020204010d02060104010101181a010a01090103030201010103010401090107010501010209010101050104010701010103010b0101010301050103010401020102010401010103010201040102010202030108010701030104010601090203010301040102010201050105010a0107010701050111010a020201010105010a0211010501070102010701070108020a01040101010a0202030a010801080101010701040106010b0107010502100105010a0105010a020101060208010f01020106010902040107010a0104020701050102010b01080108010301100107010a01010101010101020112010e01050104010101010102010403020103010a01070102010101020104020a0103010301030104010101030103010101010104010101070109010c010101030101010a01040105010501070102010101030102010201010104010801020102010801010102010201080202010e0102010401010103010a0104010101010109010401060102010c01090203010301020102010b0104010401020203010501040203010101040101010301010102010601010104010501020201010501010107010101020101010601020103010e010601020102010b010d0104010601040102010801010104010301010102010a01030102010b02040110010301010108010c0105010601010104010f030101020201010801010108010a0102030701030101010401070105010302010103010301030106010601040101020101010204010401020102010801060115010a010a01030103010601030107010a0201010101010106010101080102020201070107010501010107010101090102010a010b010701010402010c02010209010202181801010104010b01010101010602040110010c010202030106010b01010203010301020104010101060205010601120101010401040101010d01080104010b0104010b0104010a010201060118210102010501010101010a010301070104010101030102010a0103010401070105010201030104010401060106010101010102010c0103010c010401090110010b0104020a010101050103010301090108010101010107011701010101010501090106010201040301010101050108010501040101011501010102010701050201020b010201010108010102050101010301090101010a010b010301010105010101020101010801060105021601020103010101080102010d0101010901070103011501070112010601070107010301070105010401050110010102110104010501030104010701010104010301040106010a01040104010b010b0105010a01010108010201040101010101010107010f020a0204010501010106020901020207010b010401020103010301060108010301150108010a01020104010a01080101010f02020105021602060106010a0203010601020102010c01020105010401040101010101010105010f0104021501070106010501020104010501030104010c0101010d011401020105010a01020108010a0109010201020104010b010101020104010201060101010a01080109010601010103010201060101010c0102010b010401090102010501080104010401020102010801090104010f0102010501010101010c0101020901030205020601060101010401010202010901050107010c01050107010c0201010301070103010201050104010c01010106010201060107010502050101010901050106020601020202010101050108010101080101010901050115010201010102010101120103010101020203010201080102010501040203010201010102010201010203010b01010102010a01040103010902020103010601080105010901010103010201020102010a01130103010e01050104010a010501040104010401160103010c010301070106020c01070101011101050102010a02120111020501040106010d01020107010d020701010101010b0103010101010105010501040103010f0101010901050116010a0108010201070102010a0103010201010107010101030108020101030105010201050106010401010104010301010102010c0105010301060102010201030201010601030101010301040204010e0111010e01020101020a01040101010f01070111010201030102010101010101011819010c01010104010201010106010401010104010401030108010b010c010f01","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["3","916.7549974278079","1933.7984004506598","3013.7353696379046","4026.9818637232156","5023.466593408129","6022.204810477519","6903.869397967374","7876.523366774738","8960.007546081224","9995"],"densities":["0.0001094385259522486","0.00009832422068004222","0.00009259799678425629","0.00009869266815502099","0.00010035276710323482","0.00010012633770381916","0.0001134218175697689","0.00010281148610601653","0.00009229484094913849","0.00009661906192781553"],"corrections":0,"bucketCount":1814},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["3","1381.9174917125915","2862.801260325292","4314.6514446898","5744.502357539123","7020.901868738615","8521.227700374795","9995"],"densities":["0.00010360093603549605","0.00009646749183494136","0.00009839661446864305","0.0000999105162456871","0.00011192196612712068","0.00009521741200799694","0.00009693298136589546"],"corrections":0,"bucketCount":1814},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["3","475.5251795489272","5023.4664011498235","9465.399685784294","9975.93305996863","9995"],"densities":["0.00010581446696180303","0.00009894586980646991","0.00010130723970047892","0.00009597805447740996","0.00005244680050153288"],"corrections":0,"bucketCount":1814}]},"wide":{"count":4900,"exceeds":100,"min":"1","max":"988925951","mean":"38632467.499387756","stddev":"130503824.32363454","percentile":["1","1","7","100","16620","3678479","80503295","776790015","974999551","988925951"],"percentilesAt":[["0.1","1"],["1","1"],["12","7"],["25","100"],["50","16620"],["75","3678479"],["90","80503295"],["99","776790015"],["99.9","974999551"],["100","988925951"]],"percentiles":[["0","1"],["50","16620"],["75","3678464"],["87.5","49387776"],["93.75","201484288"],["96.875","418402304"],["98.4375","633270272"],["99.21875","829915136"],["99.609375","907624448"],["99.8046875","949907456"],["99.90234375","976445440"],["99.951171875","979705856"],["99.9755859375","987672576"],["99.98779296875","988921856"],["100","988921856"]],"cdf":[[0,"0"],[1,"0.02959183673469388"],[2,"0.0646938775510204"],[7,"0.12714285714285714"],[42,"0.2089795918367347"],[100,"0.2504081632653061"],[999,"0.35714285714285715"],[1000,"0.35714285714285715"],[5000,"0.44081632653061226"],[1000000,"0.69"],[988925951,"1"],[988925952,"1"]],"countAt":[[0,0],[1,145],[2,172],[7,39],[42,3],[100,5],[999,0],[1000,0],[5000,0],[1000000,0],[988925951,1],[988925952,0]],"linearStep":49446297,"linearBuckets":[["49446144","4288"],["98892288","160"],["148338688","101"],["197784576","41"],["247230464","59"],["296677376","30"],["346122240","33"],["395569152","23"],["445016064","21"],["494460928","22"],["543907840","21"],["593354752","12"],["642801664","13"],["692244480","10"],["741691392","11"],["791138304","9"],["840585216","10"],["890032128","10"],["939479040","13"],["988921856","13"]],"logBuckets":[["1","145"],["2","172"],["4","192"],["8","164"],["16","172"],["32","139"],["64","149"],["128","149"],["256","162"],["512","173"],["1024","138"],["2048","171"],["4096","190"],["8192","172"],["16384","158"],["32768","158"],["65536","168"],["131072","176"],["262144","144"],["524288","141"],["1048576","164"],["2097152","147"],["4194304","166"],["8388608","173"],["16777216","167"],["33554432","168"],["67108864","166"],["134217728","144"],["268435456","143"],["536870912","125"],["1073737728","104"]],"logBuckets10":[["10","720"],["100","507"],["1000","523"],["10000","566"],["100000","557"],["1000000","508"],["10000000","524"],["99999744","548"],["999997440","447"]],"skewness":"4.6332782706942375","kurtosis":"23.17631761596742","meanCI95":["38632467.499387756","34977151.553299636","42287783.44547588"],"meanCI99":["38632467.499387756","33827883.00716409","43437051.99161142"],"percentileCI":[["50","0.95",16620,13793,24090],["99","0.95",776790015,707690495,841441279],["90","0.99",80503295,62270719,103877119]],"export":"ab00010101021a3b9aca000305041913240501061a3af1c583070008fb3ff0000000000000091a001c00000a991c020118910118ac01186c01185401182501182601182701183201181901160111010b0116011819011701181b010801070111011001070106010401090106010c010c0108010b01030109010401050103010401040102010501040102010801030108010401090103010c010302030105010301020105010601030107010401010106010601030106010a0102010501020103010401010104010201030103010202030102010201040102010501020103010302050102010301020203010101030101010401030104010101050103010301020101010501010103010201020102010101050304020501030301020302020102010301020101020103010101010201020101010201020102010101020202030103010202010101020102010102010105010101050202010201010101020401020101010101010101020201020201020102030102010101030101010101050103010101010203010202010101010102010304020101030301020101010202010103020102010101030102020201010202030101030101010301020202020201010103010202020101020101010103020302030102010201010202020101010201020102010601020102020101010101010101020102010201010108010101040101020201010201010401010103010103070101010203010101010101020303010102020101010101010101010201030101010402020105010102020101010201020101020203050101010101010101010101020101010201010102010101010102010301040101010201050101020101020102010102020403010201040205010203020103010102020101020301020101010202010203010201030101010101030103020101010101020101020103010101010101010401020101010203010201010101010101010303010101010104020104010102010101010102020102010101040103010401020105010301090103010101040101020101020105020401010102010101050108010501020103010d01020102010301010101011101010106010302020101010801020201010201060201010201040103010301010102010201010104010601050102010c0102010101020104010401010109010101020107010201010106020501090306010501080103010301050201010d010502020106010101060104010901040102010802070103020401010101010501010105010d010601010107010201040105010901010105010902010202010201020101010a0204010b0101011302010108010301030108010401010112011822010b02010112010b010d01120202010601050105010101020106020f010101010101010b01060105010102050101010101020101010c010b01020105010601010108010a0102010d01050102010101020109010e01080108010101020106020702080101010601030106010d01030104010a0101010301050104010301080106010501020101010201090203010c0105010301040107010301060103011301030104010201160102010101010101010401020117010101070102010301020102010601010106010401090110010c0111010a020801030108010401050112020201020101010601010114010b0106010501050118200101010f010c0104010801040113010201020110010c01010106010201030101010102010109010d01010103010301040105020101010108010401080115010201100103010201181d011818010101090108010b010a01070105010a010501040105010301070118260110010201181801060103010e0109010c0105010101184a010a010201030103010601090111010d0107010c010301100104010f01080111010701090108010e011819010101080101010701040107010a0106010601020103010101010105010c010101040118250105010701030118260105010801181d010501120102010a010b010601050101010301060104011840010a0101011001050101010601080105010801010108010301060102010e0106011401060101010201030113010c010301020202010602120111010e01050105011856011601010112011001040102010401010104010101181e010c0106020c010401060105010f01030104010d01050104010901080203010601040105010201181b010d0109010601080102010a01160103010b01140118240104010f010701040108010201182b011828011823010f0105010a020701020218280106010401090108011201182801181f01060111011859010d010701080104010b010501010117010c010d010201184a01182b010801181a0102010a01182c010201181f01183c01110107010f01182701184c010101010104010501181a011824020c0110010a011852011001181a010101183a0103010801182a011835011819011829010101181f010601183b0116010d01181b010101010113011701184a0103011826010c01090109010c01188601182601181a01182d0104010c01184d011201181a0102010b010401182d010a01030105011201182d011701183501030118220106010f0117010f0118410118240117010e0106020a011867010c010701183801181901030108010a0111010901120113010201140118180108010e011834011874010a010601182d01181b01182101181f0101010901181b01182b010501020103010b010d010e01184401185e010501182301184b01184201020108011847010901182d010f010901183201182701181e021830010b011834011827011828010f010701120106010b011823010e010a0103011501182f0105010501010113010b0118e80118e0011501130114011301182b010e01186d010b01182501188d0102010f011401182b01183e011001181801184001182301183701182001181c01182101183f0118c10118520118a10118340117011001184a01184801185c0118c101130108011101185d011855011201140118ae01182d011301040118350104010801181a01183601185d01187501186901182f01183d01186001187501185301181e01110110010701185e01184b0115010501183c0103011834011856011701186601186201188e01181901181d011832011833010801182401183201070118200118c701183901184a0118860118630110011830011895010c010c011835011818011864011101080118280110010401184501186901186201020106010901186a0111011830010f01181e010c011819010c010c011827011848010c010b01185f01187501182d010a011821011859010a0118550104011832011601100117010a01185b011836011701183c010a01185901182d01181c01182b01185e011857010c010e01080105011501182e011850011401182101183b01181f01187b011885010d010a01183f01184c01188301182201188f010a0118580118360118cc0119015d01182e01030118cd0118280118b901183f0118760118400118b301183501183301188b0118450118690118690114010b01181e010301090118360118cc01182001182001183c01184901183801182f01189901187301185d0118ac0118eb01188401188b01183a01182101185b0118fc01181801182c01188e01190247011821011825010901184d01184a0107011901b90119018201183b010d0118780118260118520119010001181e0118650118e90118480118a301185a01040107011901930118cd0118e3010901183c011701182d0118180106011501181f0119016b0118700119019f01070118420118ad0118a501184201150118570118d901188a010301184001188c0118650118b501181c0119017f01183801186501181a0118c7010e01186f01182e01190126010301186501186501150118ce011401020118af01020119010101182801182901183201184d010e0116010c0108010301187b0118430118240118340118b801184401182201186101186601184a011887011101070118380118cb0118970118b60118760118280118ca011901b801181a01190146011902a401188801183e011501190326011901100118b80118970119010d0118580118630119010e0118370118dc0118b40118bf01186e0119023e0118fd01188501181d01190189010701190230011901050118730119015e0118f501020118a6011902660118180118250119013f0118510118460118230119014f0119012d011896011875010d010c01182d01189701184e011701188a01186c0118e701080119013f02185d01188a0118320118870118ac01185e01187101184301185d01187701187701183f0118e20118b30118730119015f01188f010701187401185a0106011901020118c20118ad011901d40119012b0118500118180118630118250118db01185e011838011833011902840119013c0118220119013d0118a30118c701182901184e011851010b0118aa01184d01184c01190289011864011901f30118650118410118510119019c011301190249011850011901e60119017d0119012c01187d01184401189701040118990118650118be0118df0118320119011301181801188f0119019e01182101183801190121011899011822011901150118c3011901b201182301183a01185701181801188301190164011897011902e10118c20104011901d30119024d011901990118470119030c0119049b0119016c01187201182a0118370118e7011896011601190199011869011824010d01182501190135011901870118d9011901fb01185e011901200119018801185e01050119016601187201080119027d0118300119016b0118360118a50118d30118190119019a010d011905a6011901af0118f6011903430118a90119013a011901dc01190489011901260119018a01184201184a01070119013f011902850119017b011901980118e30118ad011901010118be011901340119014a011902fe01190412011902050118cf0118ab01182901184101184201182901190152011901660118ad011901c90119019c0118c301030118690118220119030901189e011904df01183c0118a901190150011884011902c401187e010f01190512011903290119028f01188c011904aa011852011902390118fc0119039801190145011901060118d90118880118190118830119048701182f0119010501040118220106011901f00118f00118670118240118f30119017e01189f0116011907780118d10118250119025f01190248011901920118c801184d0119019401184601188e011901d4011601185801184a0118bf010201190334011905c601189c0118c2011905b70118900119038201190206011902c4011904f901185a0118660118890118df0119019a010d011903fa01190101011901300118b70119033101183f011904500118f40119018b0118c20119023401188901181c0119022a0119011b0118360119037e01187c0118c30119016b0119031201188c011901d4011901be0118cd011905c90119015201182c0119017c01186a01190338011859011902ad0119019901184a01190297011904d2010501186701190352011835011901c40118fc0118a4011905bc01190102011902760118540119034b0119010301190137011904750118940118e3011901e701190177011908b4011906ec0118f201190202011901a70118a1011911130118a50119013101190255011901dc011901b2011902710119021a011910ef01190594010d0119034f0119058d011901cd01190f9201189701182601190c0f011903c001190185011903f60118e2011905730119011c01190311011902d10118a7011875011905500118f5011902a70118a401182d011904b30118a60119016a01190390011901e701190358011903d601185d0119031a01190119011902e601190461011901da01190323011904c401190224011906ca0118c501190ca50119062d011908eb01190a3c011902ed011908c301188d011903e401186e011901a501190e1701190cff0119019d011833011903b3011904460119024c011904d701181f0119027301190478011834011904da011902a30119013f011902cc0119039401190bcd011901f2010101183501183701190795011902bd01190d7f011840011906180119029e0118f40119010201190271010a011905b4011901500119018f0119044f0119026d0118a601190c84011901ee011904bd01187c01185c0118e401190396011901510119014e011908e101188c0119065f0119043f011907410118e00119019e0119018101190722011901fb0119033a011902ef011901e1011904070118ce011908cf011904a4011908fd01188201190523011902080119010a01182d01187d01190157011902bf011909c801188601187001190e5b0119043b011907e9011902ba0119098401190364011901a6011903f5011901ac011904b001190c5e011907ca011905b90118fc011907210119014201185d011902cb0118800119030d011902f5011906940119011a011901380118bf0119029301190183011905fa011904a20118d5011901500119022f011905dc0119021b011902cd011902870119035b0119038d0119039b01187c011901480109011905ee011902e0011904cb011904500118d40119084f0119010d011904120115011902c4011903a701190434011901700118fc011904c301190156011907330119074d011903ba0118560119012c011904ac0118190118d80119039f01185a011907040119028b011903d0011869011911230119036a011901af011907d90118730109011901cb0119013f011902710119049b01190b00011905cf0119016201182d0119034a0119039a0119045001190257011904e90119043801183201190c910119029d0119082a0119022201184c011906ff0119098901190850011838010a0119075e0118330118fd0118ec011902c401190716011901130119112c0119027f0118f2011905970118330119024c011901750119023401190237011901a701190759011890011901de0118b501190237011901f00119087e011909d701190181011904dd011851011906ad01190aed0119037c011903450119015b01187d0118db01190439011903490119043d0119116f01184a011903e00118d6011905ec0118e0011904af01190353011902b5011901e1011901800119057001190316011906af01190603011904a9011901bb0119035b01190f59011908a001190175011903ca0119049b011901fa0119024201190151011903e101190343011902e501150119018a01190574011901510119041a01183c0119067e0119018801190eaf0118e601190117011905fa011906e60118e001190356011901110119024701190137011902a7011906ad011901c10119041f011904bd011873011902c10118d2011904f1011903220119011e01181f0119087d0118b10119016f011901060118c40118df01186b0118ea011904100119022001150119099c01190121011902df011867011902730118ef0102011907ae011904c4011901e601190337011904610118a1011907cd0118670119063301187f011902ff01190544011903eb0118f801182c011901f301190991011906f5011901e80119049801188501188a011906d20101010a0119014b011901640119018001190256011903a401186e01183f011904240118610118910119050e011838011907110119019c0119011d01189b011903a1011902e20119029c01190a5401190127011903e5011902760119045a01181d011902ed011902f9011901c4011902ed011901100118ab011907050119032201190429011901d6011905b5011901ab01190694011902230119030d01190113011902d10119010601190215011905ab01182f011901590119026201190819011901a60119029f01190152011904d1011901a9011912f40119018d011907ff011904590119097d0118f10119010701190468011902c001181e0119019d0119017901190424011903dc02190221011901a6011903fd011904db01186f0119021a01188001190158011902020119104b0118a30118c70104011908860119066d011901080118630118600119015501188b011904cd0118a00118b201184f011911f401190338011901d50119031c0119026701190379011901e701190295011903a9011901ab01190915011902e0011902060118aa01188301183401190236011901d20119037d01190146011901d40119019c011902f6011908d30119029f01190493011906d6011901cb01185001190d70010a0104011903e20119036a01040119012e011903fa011907b4011902190118b30119013a0119082b01190382011905eb01190445011855011901c3011893011901630119048c0119038801182101186e0119046c011909d3011901fa011901e70119076701190423011902100118820118190119018501189d0119012d01190345011906ea011901540118ac01190d260118c4011905a30119080f011901bd0119045d0119021f01190ad101190f3301190263011902c50119026d011909dc011901c70118f801190133011908ed0118e0011903bd011919e90119029d0119041801181c0119011001187e0118e0011904ef011904f00118820119013d01186201189a0119018c01190fd1011902a8011903e30119022701187801190464011902300119046601190464011904e101190154011901d501186301184701190428011903e001190128011901cb011901880119017b011904fb0119036d0118620118ac011901eb0119031c01190bfe01187d01183d0119033c0118a1011901210119051b011901df0119048d011904110118ec01190199011901eb0119019a01190407011908d5011904ed011902de011901370118cd0119012601181a01190d53011840011905cd0119039401187d011904280119017b0118d40118b80119014b011903bf01187301190c47011903240119028f011903e3011901260119019a011901d1011902ad011903c00118c70118690118be01190a74011901f201190cbf01190147011825011901d4011901620119018d0119044a0118410119019301190107011902d6011903b30119028e011829011901cc0118620119025c011909db011901e801190a610119019d011902d6011906bf011901790119032c01190aa001190b85011902770118da01160119012d011901df011904660118840119018b0118c30119033501190117011885011909910118df01190691011903b10119064401190302011902e2011901f6011903e10118710119083c0118fe010b0119038f01190402011905f60119017d0118740119017701182e0118e40119035f0119037e0119016e0119062301184a011909d901190a8e011902fd0119025a0118eb0119031c0119031301187f0119016b01190305011904a3011889011902f3011909e4011906820118d7011902fc011905d3011904f20118550118f20119014e011903e1011907aa011905e3011901a101190102011821011909df011906d2011845011851011867011903660119019401190230011908ad011902b30119025f01190479011901100119047c01190cf6011901da0119022501190393011906070119035c011904930119077f0119010c0119034e011904b60118e9011903690119047301187501190293011908410118790119012d011903720119014101190906011901c30119014201190b63011832011901c5011904a40119022601182d01190500011904a7011901040118c501190a800119060f01190271011907180118a3011903d001190122011821011905a70118e8011901d8011901670119038f0119024c01190155011903ad0119040f01190660011903e6011902b00119027f0119070d01185e01190381011901f401185d011902120119033e0119027501181b0119043b011903820118380119073b011901fa01181b0118440119012d011903f00119015d011903350119032e0118bc0118a10119075b0119047a0118cd011902c001190134011907f601181f011902f1011905170119017f0118fe01187e01190371011902900119097101190191011902fa01190206011905d901190141011901150118460119026101190106011833011904fb011904570119031b01190329011903f10119025a01183101190808011901a50119028a01190103011901c00118550119019f0119020801190306011905710119021f011907cf0119039c011902d3011901c40119024f0119049c0118f20119013901190199010901184101183f011863011902560118e80118a601190106011903ca0119015001190311011902070119046f011903f301030119014c011904d90119049a01182a011908bd01182d011903410119026c010701190ad2011902f70118790119028b011902c001190233011901c2011905880119053201100119058b0119023e0119065b0119011e011905b1011902ee0119041201190b5b0118970119060401190eb201190a09011901520119012c0119019d011908b60119046b011901cd01189d011858011901230119055201190f1c0119024a01190329011906e20119024101190108011905270119024f0118e001190714011901880119012101190180011905e901190223011902fe011822011902170118440118d9011902790119029c01185c011865011902150119026301190123011903250118800118700119054101030118e601120118c40119051701183d011902080118ea0118e30119011d01187e011906ba0119026a011901800119066401190447011905940119094a0119015a0119077b0119051301186c011830011905f30119051c01188501188c0119021e01182b0118ed011903e501190a400119017e0118630118ac01182e01182b0119020d011905970119015a011909ad011901150118530119061f01130119043801190519011905c3011902bf0118a80119026501181b011901680119030c0119063a0118f7011901450118c10118a3011901750119045b011902420119046b01187d0118fa011903b50119079001185b0119014e011906840118b80119024a01190a7e0118e601190119011905c9011836011901210118e8011902d2011903920118b7011901340119077c011901b5011901da0119019e011901380119063101183101182b0118c901187201190361011902c90119030101190c980118ac011901110119041a0119059e011904c2010e0119062c0118530119023f0119058801188c0119039b01190221011908980119039f0119054a011903650119110501190ad30118f5011903440119014d011903bb0119017d011903a8010a0119046901186c01040118c30118910119034e0119036f01190101011819011902c001190bd3011903770119012e0118780118e1011903b80119030c01190dd601190a5301190432011902280119030a011901fe0119032f011904230119016b01190368011872011901690118350118a901190537011901ac011903ce01182c011901f401186f011839011902fd011904fd011902160119016f01182201183c0119012d0118720119012a011905bf011904fa011840011902bf0119026f0119022d0119046d0119017a0119017f01190624011904a001186e0119035c01190738011903ad01190121011902c2011901cb0118fd01185a0119081e0119017c0118ec0116011902aa0118ae0119032a011905da01190737011907ed0119012e0119017e0119031501189701183f011901f8011829011901050118fd011839011901b8010a0119046e011901dd0119021501188001190bb40118cc011897011901ab011901f101190258011902860119025b0119010a0119013301190806011901510119034a0119040a0118bf01187601190b8d011862011911cc01187101190412011909ca0119032e011903db011908e401100119016e0118e7011839011908430119043d011902da011858011903a3011908ae01190be8011901a30118e901190153011909680119018101190587011904680119012901190269011901c0011903b70118860119014c01182b0119042d0119034601187e0119050501190874011906240119058e011902a1011904e20119021601190546011906220118a90119025a0119022e0118d1011901e20119018501190e160119050b0119032f011908d80119016101190281011909ad0119053001182101190112011902ea011903e3011902d10119047c011902bf0119082b011907e101190451011904230119017a011904c4011902be011901fd0119010b01190322011895011902fd0119028c0119054001190127011908180119073a011901fe01190142011866011902860119047b011903ec011903b10119024d01190c8f011889011908c101190207011901eb0118e9011906a00119044a01184f0114011904660119036c0118ac01080119026b0119039b0119012c0119020c01190382011902fb011902210119011d01190250011905910118310118d7011903a00119047a011908a1011901810119014c011901700119043e011846011902a701181801190d340119016b0118bf011901e2011901ff011878010b011901200119026401190139011901f6011903ed0119087e011901c9011910900119011b0118f801190292011908e4011902ef011864011901e4011863011902f70118700118300119030401190523011902d80118a501190105010e011907e401190d970118750119016801183a01187201190451011901b0011905940118f501190466011901ff0119019c01190717011904be0119035c01185501181c011902bb0119025201190786011907100119015801181d0119026401070114011903eb011902490104011903660118910119011e0118190118410119035f0119020e0118e001185901190295011903750119037a0119034a0119042e011903800118f9011901070119011c01188001190394011906840119091601070119043e01190252011901450119140f0118bc011904eb01190ba00119019f01190c18011897011901250118920118260119044b01190361011905cf01181b0118880119034b011901850119061501190179011902bd0118fe0118990118ae011908b4011905c901190719011901cc0119024d0119025b0119015601190d0201190a390119026a011901b20119073b011909c10118de011901f50119012b011903020119025801190114011901850118600119037c0119047d011908f1011902e9011908ab01188f0119018e01190123011907800119032f0119015e011902be01190318011905810119022f011903d20119015d011901400118aa01190859011903810119021d01186b0118ad01190184011903b601190bc9011903200118f10118ca011902070118f60119010a011823011902ca0119059b01060119080d01190a3701186c011906110118e001190c1c01183f011902070118aa011896011904cc0118b601190470011901cf01181a011903890118f10119029d0119034b0119092d011905000119020b0119018f01190d790118480119060c0118e60118fc0118b701190836011901e301190136011903f50119039401190bb601181b0119062e011903c80119014c01190228011904380118380119046101170119020a0119034901188b011905ae01190183011901e1011901160118a50119131e0119011a0119051801182501190276011905c60106011907220119049e01190107011904bd01190aae0119046a0119016501190106011842011903f80119018b011901e00118b10119058e011901d3011903cd0118390119011301190135011901920119098501190277011904660118d30118e801190402011907500119058e0118ba0118e101190740011901c9011905ab011908be01191061011903bd011901be0119019201190b1401190112011903bb011902bd01190acc011901c401190a70011901ef011903a1011901530118cc011901b301190475011902b1011903990119029501190233011908470118b7011901500119047c0119059b0119010e01190dd701189401190570011909480119035b011907390118590119035f0119078701183b0119036c0118430119016501190801011906390119042b01184a0118780119020a011912b701190109011903f00119038d0119060901188d011902250119023601190152011904590119042001190477011908970119019101190348011904d801188e0119045e011904e401183b01190238011903f20119069c0118d30118940118290119016c01190428011903790119026d011904d901187a01187c01190230011843011880011908fd01185001190b0b0119014a01188f0118cd011904d90119027f011901910119018f0118e8011903e601190343011902060119056701190318011904af01190114011855011903c1011906fe011902ee011903c80119024a011901830119105a01190211011907720119049501189a011901e9011901e001190a8f0119030e0119069a0119012301190329011907010118f80119165d0119016001188e011906350119022c011904de0118aa011904a40118d40119016701188c01190960011901f1011903a6011901b70118f301190117011902380118a40119018d0119057701181d01110119030d011901490119011e011901c90119038f011907520119045901188601182601190f0701190284011905350119010e0119167c01188401189d0119029a011901b50119010e011906b40119027c0119077b011905d901190681011902df0118f40118d20119018e0105011902a00119066901190b37011902240119036a011904c80119018201190296011892011905df01190199011904270119025e0119017801190a870119061b01185b01190a9a01190a5f0118a4011901ad0119068701190595011902fe011904dd01190965011901ea0118d401190577011832011901d6011904ef011904fb011914480119059a0119041c010b010c0119111c0118a70119029101183b011905a1011885011901570119053901190793011906b40119025f0119015601190109011901f70118730119072a011871011904f90119065b011902e501190100011908af011902360119139b0119044d01190116011903f10119079101189e0119019c011903360119074f01190430011906530118c40119039f011901ca0108011903bf011902b4011902ec011906010119077c011904d4011901eb011905b301190588011903bf01190340011901980118ea0118c601190410011901ea0119032d01190fe901190496011909f301186e0119050d011901f201190273011856011906bc0119053a011904000119061201190848011904750119037e0119065201190395011907d6011902350119037a01181c011904b7011906bd0119029601188e01190cdb011906f8011908730119015d0118f001190492011901d001190313011907c10119053a011906900119037e0119076d011903490119055e011902560119032f01190505011902ea011904000118dd01190c100118e2011903de01190cf7011901000119062c01190210011901cc01190183011901770119035b0118d40118b00119068d0119013a011904c801190e56011902230119036501187f011905e3011901bd01190d06011908e80118420119016201189e0119027e011907990119013101","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1","4.186208696018375","32.85092149535828","304.5816219567075","2423.988862777046","17398.64863801883","131882.05610974005","1282546.1090565543","10589129.003994513","81230322.05303425","988923904"],"densities":["0.0313852636598991","0.003488609870261907","0.0003680114165613904","0.000047183003848422265","0.0000066779480469622464","8.734890252519885e-7","8.690633877360045e-8","1.0745082392635447e-8","1.4156046307227442e-9","1.1016933686531534e-10"],"corrections":0,"bucketCount":3585},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1","9.695833830730308","224.30984552299006","3900.1487838888947","74859.7313654541","1754796.3077857394","32101660.336042427","988923904"],"densities":["0.01642822823411118","0.0006656468593578558","0.000038863820002040143","0.0000020132184781799453","8.503722394184185e-8","4.7074762889544406e-9","1.4930374351467867e-10"],"corrections":0,"bucketCount":3585},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1","2.0813954317436707","17398.648638019364","255231606.34563354","973906960.553717","988923904"],"densities":["0.046236555595004385","0.000025867172168218265","1.7632247203661362e-9","6.818099398162011e-11","6.659144742584234e-11"],"corrections":0,"bucketCount":3585}]},"bimodal":{"count":3000,"exceeds":0,"min":"100","max":"5499","mean":"2659.669","stddev":"2562.613756585062","percentile":["100","100","112","124","148","5241","5395","5491","5499","5499"],"percentilesAt":[["0.1","100"],["1","100"],["12","112"],["25","124"],["50","148"],["75","5241"],["90","5395"],["99","5491"],["99.9","5499"],["100","5499"]],"percentiles":[["0","100"],["50","148"],["75","5241"],["87.5","5369"],["93.75","5437"],["96.875","5471"],["98.4375","5487"],["99.21875","5491"],["99.609375","5497"],["99.8046875","5497"],["99.90234375","5499"],["100","5499"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0.018"],[999,"0.505"],[1000,"0.505"],[5000,"0.505"],[1000000,"1"],[5499,"1"],[5500,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,54],[999,0],[1000,0],[5000,0],[1000000,0],[5499,5],[5500,0]],"linearStep":274,"linearBuckets":[["274","1515"],["548","0"],["822","0"],["1096","0"],["1370","0"],["1644","0"],["1918","0"],["2192","0"],["2466","0"],["2740","0"],["3014","0"],["3288","0"],["3562","0"],["3836","0"],["4110","0"],["4384","0"],["4658","0"],["4932","0"],["5206","640"],["5480","778"],["5754","67"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","883"],["256","632"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","1485"]],"logBuckets10":[["10","0"],["100","54"],["1000","1461"],["10000","1485"]],"skewness":"0.024700427716906424","kurtosis":"-1.9930061955998104","meanCI95":["2659.669","2567.9164121626095","2751.4215878373902"],"meanCI99":["2659.669","2539.057564663903","2780.2804353360966"],"percentileCI":[["50","0.95",148,146,5015],["99","0.95",5491,5487,5493],["90","0.99",5395,5383,5411]],"export":"ab00010101021a3b9aca00030504190bb80518640619157b070008fb3ff0000000000000091a001c00000a9902261864183602183302184102183202183002184402183f02183302183a02183c02184202183a02184b02184102183302183c02184602184602183f02183f02183d02183e02184202183e0218371912f5060207020402080201020902030206020702070202020c02080203020502080205020602080209020a0206020502050206020302030206020902050205020902050205020402060206020d02070207020b020302050208020402010207020502040205020202040205020a02060208020a020b02050202020602060205020602090207020a020c02060203020702070205020602050207020302030207020c02090205020802020205020a0206020c0205020502060209020302090205020602050204020302080206020602060203020502040207020202070204020602050207020702050206020802060208020302030204020302060203020a0209020502060205020802090207020c020802050203020702040206020302060208020a020602070208020602030206020602050205020602020207020a020202050206020602070206020402070207020702050203020102030209020102060207020402070207020e0205020702040204020302030204020902040204020202080206020802090209020202040208020402080207020602040202020802030205020202080208020202090205020202080204020a020502050206020402060204020702050208020102050203020102030209020a0202020a0208020602090205020402090208020302070203020e0204020602080205","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["100","109.98699073456697","119.98252823585753","129.5824353622775","138.70176685308016","1566.0020677370537","5093.2245492629045","5186.8869316629725","5289.015013603393","5395.421468835726","5499"],"densities":["0.010013026211577427","0.010004464490987972","0.010416767441925491","0.01096571608355891","0.000070062340726802","0.000028350919320728733","0.0010676644928041816","0.0009791626171765226","0.0009397926073343458","0.0009654510338769095"],"corrections":0,"bucketCount":275},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["100","114.14631642493161","127.98723858589582","141.52099292684923","5065.502689756932","5202.304042229508","5350.982030172245","5499"],"densities":["0.010098540041517095","0.010321360180757702","0.010555618142473173","0.00002901252515806672","0.0010442670359255492","0.0009608493149111228","0.0009651337808739179"],"corrections":0,"bucketCount":275},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["100","104.2446148394385","1566.0589121387704","5450.709491241179","5498.570930132635","5499"],"densities":["0.011779631813805347","0.0003078366389160132","0.00011584053464699966","0.001023788693673136","0.0023306227634707927"],"corrections":0,"bucketCount":275}]},"heavy":{"count":3000,"exceeds":0,"min":"1000","max":"1000003","mean":"7746.686","stddev":"40685.13319843917","percentile":["1000","1009","1145","1342","1960","4081","10416","100000","500001","1000003"],"percentilesAt":[["0.1","1000"],["1","1009"],["12","1145"],["25","1342"],["50","1960"],["75","4081"],["90","10416"],["99","100000"],["99.9","500001"],["100","1000003"]],"percentiles":[["0","1000"],["50","1960"],["75","4081"],["87.5","8474"],["93.75","15873"],["96.875","34482"],["98.4375","62500"],["99.21875","142857"],["99.609375","333332"],["99.8046875","500000"],["99.90234375","1000000"],["100","1000000"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0.001"],[5000,"0.7953333333333333"],[1000000,"1"],[1000003,"1"],[1000004,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,3],[5000,1],[1000000,3],[1000003,3],[1000004,0]],"linearStep":50000,"linearBuckets":[["50000","2943"],["100000","28"],["150000","11"],["200000","5"],["250000","1"],["300000","0"],["350000","6"],["400000","0"],["450000","0"],["500000","3"],["550000","0"],["600000","0"],["650000","0"],["700000","0"],["750000","0"],["800000","0"],["850000","0"],["900000","0"],["950000","0"],["1000000","3"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","87"],["2048","1460"],["4096","703"],["8192","361"],["16384","208"],["32768","83"],["65536","55"],["131072","19"],["262144","12"],["524288","9"],["1048576","3"]],"logBuckets10":[["10","0"],["100","0"],["1000","3"],["10000","2684"],["100000","284"],["1000000","29"]],"skewness":"17.665389389610407","kurtosis":"381.88996617490784","meanCI95":["7746.686","6289.983347142841","9203.388652857158"],"meanCI99":["7746.686","5831.808153389505","9661.563846610496"],"percentileCI":[["50","0.95",1960,1893,2061],["99","0.95",100000,66666,142857],["90","0.99",10416,9433,12500]],"export":"ab00010101021a3b9aca00030504190bb8051903e8061a000f4240070008fb3ff0000000000000091a001c00000a9907701903e80301040106010401040104010101030206010501010102010601030101010401050102010401040103010401060102010301020106010501040102010402030104010202040105010101040105010301030103020101020102010201020101010101020102010402030101020301030103010201040202010201030107010301040102020101010102010601030101020201010102010301010102020701040102010101010204010101020103010202020103010401030103030101020106020201020105010102040106010201010203010501010105020301010101010202030104010102030104010401040201010103030108010202050108010202030105010402010103010302020105010102020102010302020101010202020103020301050103020301050204010501030202010302040104010302040103020201030203010301030203010202040104020401010204010402020102020401030205010301020205010802030201010702010304010302030103020701030203010102010203010102050102020501020201020501030201010202020202030101050203020401030202020301010502020201040204020202040104020102040105020102040104020302050204010402040402010502030201020401010202020202030101020402020207020401030205020302030201020501010203020502040204020301020205020402030203020202030201020202030105020502020207020702020203020202010402020602020201040202020202020402030204020404050407020702030304020402020203020802030202020202020209030402030203020402050203030402010208020102030303020502040205030202010202020503050402020303020204020503040205020403040404030202010204030202030206030402040306020103030203020203020203030402050305020603060202030602010303020103040302020203030207030202020301050303020301020205030306030103020205030203010503030403030202030103060307030205030302030303030305020403040304030503020302030303060302030103090303030203050305030403050303030303030303030203040601030504030303030403040305030404010304030303020405030503020301040703030302040503050703030204040304030304010305040403020405030304020306040103020401030604020401030404030405030204020401030304030402030404010403040704020303040304050403040304020401040504010401080304030402040204020c02040604010403040205020401040204060505040304010503040104030504040a040305040404050204010502040305010902040205010501040205050501040305020501050104050502050305010502050105040503050305050504050305020502050205040507050306020501050405020602050310060601050305030604050306010602050606010503060406050603050406040606060206010603060305020602070106070601060306010602060507010607060306010702060207040606070306050d06181b040704060407040706070507020704070107030e060703080307030703080107040703080607040803070208020803070308060801080607030802080308010804090708020803080208010904080208050906080209040901181a010903090309020902090309020907090109030a04090109030a0409020a030a020a0409030a0114010a040a030a020b020a040a010b050a020b050a010b030b040b040b030b050b030b010b040c050b020b020c010b030c050c030c030c030c030c020c060c040d040c030d040c03181a070d010d010d050d020d030e040d050e020d020e030e04181c030e040e050f050e060f010f030f05181e020f010f0210020f031002100610031001100318210210031103110311031102110112011105120212071201120412021826031206130313061401130214031402140214041402150315061504150315041602150116051604170516031702170517011818031818021701181903181806181904181901181902181904181a02181a02181a04185001181b01181c02181c01181c01181c03181d03181d05181d02181e02181e03181e05181f01183e05182007182005182101182103182102182104182201182302182303182307182401182402182407182504182504182603182701182704182704182802182802182902182902182a01182a01182b05182c04182c01182d06182d02182e03182f04182f03183005183101183103183206183202183402183402183505183504183703183701183807183904183a04183b02183b02183d03183e02183e04184006184003184202184204184403184501184602184704184902184904184b06184c01184e03184f0118a203185404185505185602185804185a05185c01185d03185f0318610b186404186502186704186904186b04186e0518700218720218750218f201187c01188005188302188502188906188c04189001189304189604189b05189e0418a20418a70218aa0718b00318b40218b90318be0318c40618c90418cf0318d50618dc0618e20318e90318f10418f80619010003190108031901110219011b011901240119012e0319013a02190144041901510419015d041902e40319018906190198031901a9031901bb011901cf051901e3021901f9051902100119022a011902450319026202190281041902a2021902c8011902ee0419031a011903480119037c061903b3021903f0031904330419047d021904d00419052b04190590051906030419068203190714051907b8031908750419094d03190a4702190b6c03190cc403190e5d061910460319129a021915770119190a03191d980319238302192b6705197c0206195d01021982360319c3500119ba7a061a00014586031a0002000003","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["1000","1115.6005988741867","1251.266686571931","1435.5325178934097","1656.824308093357","1966.1994871796107","2515.5206952358635","3390.064809390491","5211.279501655024","10584.308085188894","1000002"],"densities":["0.00086504742167326","0.0007371038827536175","0.0005426942113078756","0.00045189204673903806","0.00032323213612466315","0.00018204285313113119","0.00011434528959887214","0.000054908408341280274","0.000018611477390323028","1.0106954910667797e-7"],"corrections":0,"bucketCount":952},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["1000","1172.4579899444116","1406.2934331673869","1732.3966090498104","2345.082395290817","3551.4931894359443","7451.812749731599","1000002"],"densities":["0.0008283590856137778","0.0006109302374701191","0.00043807344859667964","0.00023316542682279298","0.00011841500718532006","0.0000366270354643233","1.439293898607889e-7"],"corrections":0,"bucketCount":952},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["1000","1046.319804018247","1966.1998672958782","20207.94050303986","777979.8183126448","1000002"],"densities":["0.0010794518901743044","0.0004891942090761287","0.00002466869850776425","6.466326005873711e-8","4.50405447059416e-9"],"corrections":0,"bucketCount":952}]},"huge":{"count":1,"exceeds":999,"min":"988590080","max":"988594175","mean":"988592128","stddev":"0","percentile":["988594175","988594175","988594175","988594175","988594175","988594175","988594175","988594175","988594175","988594175"],"percentilesAt":[["0.1","988594175"],["1","988594175"],["12","988594175"],["25","988594175"],["50","988594175"],["75","988594175"],["90","988594175"],["99","988594175"],["99.9","988594175"],["100","988594175"]],"percentiles":[["0","988590080"],["100","988590080"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[988594175,"1"],[988594176,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[988594175,1],[988594176,0]],"linearStep":49429708,"linearBuckets":[["49429504","0"],["98859008","0"],["148288512","0"],["197718016","0"],["247147520","0"],["296577024","0"],["346007552","0"],["395436032","0"],["444866560","0"],["494295040","0"],["543723520","0"],["593154048","0"],["642584576","0"],["692015104","0"],["741441536","0"],["790872064","0"],["840302592","0"],["889733120","0"],["939163648","0"],["988590080","1"]],"logBuckets":[["1","0"],["2","0"],["4","0"],["8","0"],["16","0"],["32","0"],["64","0"],["128","0"],["256","0"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","0"],["16384","0"],["32768","0"],["65536","0"],["131072","0"],["262144","0"],["524288","0"],["1048576","0"],["2097152","0"],["4194304","0"],["8388608","0"],["16777216","0"],["33554432","0"],["67108864","0"],["134217728","0"],["268435456","0"],["536870912","0"],["1073737728","1"]],"logBuckets10":[["10","0"],["100","0"],["1000","0"],["10000","0"],["100000","0"],["1000000","0"],["10000000","0"],["99999744","0"],["999997440","1"]],"skewness":"0","kurtosis":"0","meanCI95":["988592128","NaN","NaN"],"meanCI99":["988592128","NaN","NaN"],"percentileCI":[["50","0.95",988594175,988594175,988594175],["99","0.95",988594175,988594175,988594175],["90","0.99",988594175,988594175,988594175]],"export":"ab00010101021a3b9aca0003050401051a3aecbecb061a3aecbecb070008fb3ff0000000000000091a001c00000a821a001baecb01","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["988592128","988592128","988592128","988592128","988592128","988592128","988592128","988592128","988592128","988592128","988592128"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["988592128","988592128","988592128","988592128","988592128","988592128","988592128","988592128"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["988592128","988592128","988592128","988592128","988592128","988592128"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]}},"comparisons":[{"a":"uniform","b":"wide","welch":["-20.71701134779574","4899.000011683751","0","-42282817.88915379","-34972185.99262172"],"mannWhitney":["4151300","-9.973240810495474","1.9960721403536482e-23"],"cohensD":"-0.3511857937606757","cliffsD":"-0.15279591836734693","ksTest":"0.5273469387755102"},{"a":"bimodal","b":"heavy","welch":["-6.833699794593757","3022.7954788057755","9.968137426596968e-12","-6546.60174032025","-3627.432259679749"],"mannWhitney":["3569123.5","-13.875721358925533","8.889425333370512e-44"],"cohensD":"-0.1764453699829511","cliffsD":"-0.20686144444444443","ksTest":"0.505"},{"a":"uniform","b":"constant","welch":["76.46930353227323","1999","0","4797.287652764121","5049.829347235879"],"mannWhitney":["199200","16.764775742620284","4.416774906879843e-63"],"cohensD":"1.7517353002131606","cliffsD":"0.992","ksTest":"0.996"},{"a":"three","b":"four","welch":["0.9508731633956237","2.0082239472623944","0.44167566755067034","-1109.4563086478202","1742.1229753144867"],"mannWhitney":["6","0","1"],"cohensD":"0.867135644459936","cliffsD":"0","ksTest":"0.33333333333333337"},{"a":"empty","b":"uniform","welch":["0","0","1","0","0"],"mannWhitney":["0","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"},{"a":"one","b":"two","welch":["0","0","1","0","0"],"mannWhitney":["1","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0.5"}]},{"options":{"lowest":1000,"highest":1000000000000,"figures":3},"histograms":{"empty":{"count":0,"exceeds":0,"min":"9223372036854775807","max":"0","mean":"NaN","stddev":"NaN","percentile":["511","511","511","511","511","511","511","511","511","511"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","1"],["75","1"],["90","1"],["99","1"],["99.9","1"],["100","1"]],"percentiles":[["100","0"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[1,"0"],[2,"0"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[1,0],[2,0]],"linearStep":1,"linearBuckets":[],"logBuckets":[],"logBuckets10":[],"skewness":"0","kurtosis":"0","meanCI95":["NaN","NaN","NaN"],"meanCI99":["NaN","NaN","NaN"],"percentileCI":[["50","0.95",511,511,511],["99","0.95",511,511,511],["90","0.99",511,511,511]],"export":"ab0001011903e8021b000000e8d4a5100003030400051b7fffffffffffffff0600070008fb3ff0000000000000091958000a80"},"one":{"count":1,"exceeds":0,"min":"0","max":"511","mean":"256","stddev":"0","percentile":["511","511","511","511","511","511","511","511","511","511"],"percentilesAt":[["0.1","511"],["1","511"],["12","511"],["25","511"],["50","511"],["75","511"],["90","511"],["99","511"],["99.9","511"],["100","511"]],"percentiles":[["0","0"],["100","0"]],"cdf":[[0,"1"],[1,"1"],[2,"1"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[511,"1"],[512,"1"]],"countAt":[[0,1],[1,1],[2,1],[7,1],[42,1],[100,1],[999,0],[1000,0],[5000,0],[1000000,0],[511,1],[512,0]],"linearStep":25,"linearBuckets":[["0","0"],["512","0"]],"logBuckets":[["0","0"],["512","1"]],"logBuckets10":[["0","0"],["512","1"]],"skewness":"0","kurtosis":"0","meanCI95":["256","NaN","NaN"],"meanCI99":["256","NaN","NaN"],"percentileCI":[["50","0.95",511,511,511],["99","0.95",511,511,511],["90","0.99",511,511,511]],"export":"ab0001011903e8021b000000e8d4a510000303040105070607070008fb3ff0000000000000091958000a820001","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["256","256","256","256","256","256","256","256","256","256","256"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["256","256","256","256","256","256","256","256"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["256","256","256","256","256","256"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]},"two":{"count":2,"exceeds":0,"min":"0","max":"511","mean":"256","stddev":"0","percentile":["511","511","511","511","511","511","511","511","511","511"],"percentilesAt":[["0.1","511"],["1","511"],["12","511"],["25","511"],["50","511"],["75","511"],["90","511"],["99","511"],["99.9","511"],["100","511"]],"percentiles":[["0","0"],["100","0"]],"cdf":[[0,"1"],[1,"1"],[2,"1"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[511,"1"],[512,"1"]],"countAt":[[0,2],[1,2],[2,2],[7,2],[42,2],[100,2],[999,0],[1000,0],[5000,0],[1000000,0],[511,2],[512,0]],"linearStep":25,"linearBuckets":[["0","0"],["512","0"]],"logBuckets":[["0","0"],["512","2"]],"logBuckets10":[["0","0"],["512","2"]],"skewness":"0","kurtosis":"0","meanCI95":["256","256","256"],"meanCI99":["256","256","256"],"percentileCI":[["50","0.95",511,0,511],["99","0.95",511,511,511],["90","0.99",511,0,511]],"export":"ab0001011903e8021b000000e8d4a510000303040205030609070008fb3ff0000000000000091958000a820002","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["0","51.20000000000001","102.4","153.6","204.80000000000004","256","307.2","358.3999999999999","409.6000000000001","460.8","512"],"densities":["0.0019531249999999998","0.0019531250000000004","0.001953125","0.001953124999999999","0.0019531250000000013","0.001953125","0.001953125000000002","0.0019531249999999957","0.001953125000000002","0.001953125"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["256","256","256","256","256","256","256","256"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["0","25.60000000000001","256","486.3999999999999","511.488","512"],"densities":["0.0019531249999999996","0.0019531250000000004","0.0019531250000000004","0.0019531249999999957","0.001953125"],"corrections":0,"bucketCount":1}]},"three":{"count":3,"exceeds":0,"min":"0","max":"1023","mean":"426.6666666666667","stddev":"241.35911464500822","percentile":["511","511","511","511","511","511","1023","1023","1023","1023"],"percentilesAt":[["0.1","511"],["1","511"],["12","511"],["25","511"],["50","511"],["75","511"],["90","1023"],["99","1023"],["99.9","1023"],["100","1023"]],"percentiles":[["0","0"],["50","0"],["75","512"],["100","512"]],"cdf":[[0,"0.6666666666666666"],[1,"0.6666666666666666"],[2,"0.6666666666666666"],[7,"0.6666666666666666"],[42,"0.6666666666666666"],[100,"0.6666666666666666"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[1023,"1"],[1024,"1"]],"countAt":[[0,2],[1,2],[2,2],[7,2],[42,2],[100,2],[999,1],[1000,1],[5000,0],[1000000,0],[1023,1],[1024,0]],"linearStep":51,"linearBuckets":[["0","0"],["512","0"]],"logBuckets":[["0","0"],["512","3"]],"logBuckets10":[["0","0"],["512","3"]],"skewness":"0.7071067811865472","kurtosis":"0","meanCI95":["426.6666666666667","-307.6527325439079","1160.9860658772413"],"meanCI99":["426.6666666666667","-1267.1732396233886","2120.506572956722"],"percentileCI":[["50","0.95",511,0,1023],["99","0.95",1023,511,1023],["90","0.99",1023,511,1023]],"export":"ab0001011903e8021b000000e8d4a51000030304030501061903e8070008fb3ff0000000000000091958000a8400020101","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["256","295.4670178055561","337.5356012399328","384.66113241954093","439.33343585278703","502.5185185185185","571.9268197424265","641.2865069502465","701.6984541420932","745.0652590504135","768"],"densities":["0.0025337612406560435","0.0023770707695920214","0.0021219919966286","0.0018290796933789013","0.0015826520403404518","0.0014407498560929273","0.001441759673747857","0.0016553017184239387","0.0023059111735670907","0.0043601974933927855"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["256","260.9248031313068","283.15916852707505","341.62683901311516","447.12539175582424","582.7143218177811","703.3481843215502","768"],"densities":["0.029007686002513387","0.0064250605004598955","0.0024433527395494883","0.0013541147166780977","0.0010536047654617887","0.0011842209135322953","0.00220963852844679"],"corrections":0,"bucketCount":2},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["256","275.53329520257705","502.5185185185185","759.0385924385938","767.8664540881032","768"],"densities":["0.002559731959275537","0.001982507907017558","0.0017542486758373838","0.005550608057243774","0.007488061489839078"],"corrections":0,"bucketCount":2}]},"four":{"count":4,"exceeds":0,"min":"0","max":"511","mean":"256","stddev":"0","percentile":["511","511","511","511","511","511","511","511","511","511"],"percentilesAt":[["0.1","511"],["1","511"],["12","511"],["25","511"],["50","511"],["75","511"],["90","511"],["99","511"],["99.9","511"],["100","511"]],"percentiles":[["0","0"],["100","0"]],"cdf":[[0,"1"],[1,"1"],[2,"1"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[511,"1"],[512,"1"]],"countAt":[[0,4],[1,4],[2,4],[7,4],[42,4],[100,4],[999,0],[1000,0],[5000,0],[1000000,0],[511,4],[512,0]],"linearStep":25,"linearBuckets":[["0","0"],["512","0"]],"logBuckets":[["0","0"],["512","4"]],"logBuckets10":[["0","0"],["512","4"]],"skewness":"0","kurtosis":"0","meanCI95":["256","256","256"],"meanCI99":["256","256","256"],"percentileCI":[["50","0.95",511,0,511],["99","0.95",511,511,511],["90","0.99",511,511,511]],"export":"ab0001011903e8021b000000e8d4a51000030304040502061840070008fb3ff0000000000000091958000a820004","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["0","51.2","102.4","153.6","204.8","256","307.2","358.4","409.6","460.8","512"],"densities":["0.001953125","0.001953125","0.001953125","0.001953125","0.001953125","0.001953125","0.001953125","0.001953125","0.001953125","0.001953125"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["256","256","256","256","256","256","256","256"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["0","25.6","256","486.4","511.488","512"],"densities":["0.001953125","0.001953125","0.001953125","0.001953125","0.001953125"],"corrections":0,"bucketCount":1}]},"constant":{"count":100,"exceeds":0,"min":"0","max":"511","mean":"256","stddev":"0","percentile":["511","511","511","511","511","511","511","511","511","511"],"percentilesAt":[["0.1","511"],["1","511"],["12","511"],["25","511"],["50","511"],["75","511"],["90","511"],["99","511"],["99.9","511"],["100","511"]],"percentiles":[["0","0"],["100","0"]],"cdf":[[0,"1"],[1,"1"],[2,"1"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[511,"1"],[512,"1"]],"countAt":[[0,100],[1,100],[2,100],[7,100],[42,100],[100,100],[999,0],[1000,0],[5000,0],[1000000,0],[511,100],[512,0]],"linearStep":25,"linearBuckets":[["0","0"],["512","0"]],"logBuckets":[["0","0"],["512","100"]],"logBuckets10":[["0","0"],["512","100"]],"skewness":"0","kurtosis":"0","meanCI95":["256","256","256"],"meanCI99":["256","256","256"],"percentileCI":[["50","0.95",511,511,511],["99","0.95",511,511,511],["90","0.99",511,511,511]],"export":"ab0001011903e8021b000000e8d4a51000030304186405182a06182a070008fb3ff0000000000000091958000a82001864","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["0","51.20000000000001","102.4","153.6","204.80000000000004","256","307.2","358.3999999999999","409.6000000000001","460.8","512"],"densities":["0.0019531249999999998","0.0019531250000000004","0.001953125","0.001953124999999999","0.0019531250000000013","0.001953125","0.001953125000000002","0.0019531249999999957","0.001953125000000002","0.001953125"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["256","256","256","256","256","256","256","256"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["0","25.60000000000001","256","486.3999999999999","511.488","512"],"densities":["0.0019531249999999996","0.0019531250000000004","0.0019531250000000004","0.0019531249999999957","0.001953125"],"corrections":0,"bucketCount":1}]},"uniform":{"count":2000,"exceeds":0,"min":"0","max":"10239","mean":"4963.584","stddev":"2879.1371962697435","percentile":["511","511","1535","2559","5119","7679","9215","10239","10239","10239"],"percentilesAt":[["0.1","511"],["1","511"],["12","1535"],["25","2559"],["50","5119"],["75","7679"],["90","9215"],["99","10239"],["99.9","10239"],["100","10239"]],"percentiles":[["0","0"],["50","4608"],["75","7168"],["87.5","8704"],["93.75","9216"],["96.875","9216"],["98.4375","9728"],["100","9728"]],"cdf":[[0,"0.054"],[1,"0.054"],[2,"0.054"],[7,"0.054"],[42,"0.054"],[100,"0.054"],[999,"0.1065"],[1000,"0.1065"],[5000,"0.5105"],[1000000,"1"],[10239,"1"],[10240,"1"]],"countAt":[[0,108],[1,108],[2,108],[7,108],[42,108],[100,108],[999,105],[1000,105],[5000,99],[1000000,0],[10239,49],[10240,0]],"linearStep":511,"linearBuckets":[["0","0"],["512","213"],["1024","104"],["1536","111"],["2048","104"],["2560","80"],["3072","114"],["3584","89"],["4096","107"],["4608","99"],["5120","97"],["5632","104"],["6144","121"],["6656","120"],["7168","94"],["7680","98"],["8192","90"],["8704","118"],["9216","88"],["9728","49"]],"logBuckets":[["0","0"],["512","213"],["1024","104"],["2048","215"],["4096","390"],["8192","823"],["16384","255"]],"logBuckets10":[["0","0"],["512","213"],["9728","1787"]],"skewness":"-0.005180010744750937","kurtosis":"-1.1946864716259202","meanCI95":["4963.584","4837.2945447211705","5089.873455278829"],"meanCI99":["4963.584","4797.5534910566175","5129.614508943382"],"percentileCI":[["50","0.95",5119,5119,5631],["99","0.95",10239,10239,10239],["90","0.99",9215,9215,9215]],"export":"ab0001011903e8021b000000e8d4a510000303041907d005030619270b070008fb3ff0000000000000091958000a982800186c01186901186801186f01186801185001187201185901186b01186301186101186801187901187801185e01186201185a011876011858011831","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["256","960.6679854372644","1918.9978430522817","2988.403848099506","4006.5901532176727","5011.6169242083115","6034.452547040134","6899.206096604371","7904.641497314695","8942.643757468326","9984"],"densities":["0.00014191080347995016","0.00010434820454083385","0.00009350985456228483","0.00009821385290425256","0.00009949983710526594","0.00009776742007004018","0.00011563988381474881","0.0000994593983157463","0.00009633890391067099","0.00009602861721641656"],"corrections":0,"bucketCount":20},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["256","1292.868978410256","2815.5619591547274","4338.566320774783","5821.143090975776","6933.913640008278","8459.28165582556","9984"],"densities":["0.00013777742977340657","0.00009381874393832003","0.00009379956253387373","0.00009635733253649702","0.00012837969425174854","0.00009365421418030776","0.00009369411957491272"],"corrections":0,"bucketCount":20},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["256","496.03263906645907","5011.6169242083115","9431.27393761352","9973.551020408162","9984"],"densities":["0.0002083050046629544","0.00009965487777089818","0.0001018178556922201","0.0000903597101088561","0.00009570312499998852"],"corrections":0,"bucketCount":20}]},"wide":{"count":5000,"exceeds":0,"min":"0","max":"2147483647","mean":"68253319.936","stddev":"249173948.75206476","percentile":["511","511","511","511","21503","5373951","121503743","1506803711","2110783487","2147483647"],"percentilesAt":[["0.1","511"],["1","511"],["12","511"],["25","511"],["50","21503"],["75","5373951"],["90","121503743"],["99","1506803711"],["99.9","2110783487"],["100","2147483647"]],"percentiles":[["0","0"],["50","20992"],["75","5369856"],["87.5","64913408"],["93.75","308543488"],["96.875","740294656"],["98.4375","1137704960"],["99.21875","1634729984"],["99.609375","1892679680"],["99.8046875","2002780160"],["99.90234375","2112880640"],["99.951171875","2132803584"],["99.9755859375","2135949312"],["99.98779296875","2146435072"],["100","2146435072"]],"cdf":[[0,"0.3234"],[1,"0.3234"],[2,"0.3234"],[7,"0.3234"],[42,"0.3234"],[100,"0.3234"],[999,"0.3508"],[1000,"0.3508"],[5000,"0.4326"],[1000000,"0.6762"],[2147483647,"1"],[2147483648,"1"]],"countAt":[[0,1617],[1,1617],[2,1617],[7,1617],[42,1617],[100,1617],[999,137],[1000,137],[5000,21],[1000000,0],[2147483647,1],[2147483648,0]],"linearStep":107374182,"linearBuckets":[["107347968","4470"],["214695936","137"],["321912832","92"],["429391872","51"],["536608768","46"],["643825664","28"],["751304704","22"],["858783744","24"],["966262784","23"],["1073217536","24"],["1180696576","9"],["1287651328","3"],["1395654656","6"],["1502609408","14"],["1609564160","8"],["1717567488","13"],["1824522240","6"],["1932525568","9"],["2039480320","8"],["2146435072","7"]],"logBuckets":[["0","0"],["512","1754"],["1024","91"],["2048","127"],["4096","170"],["8192","150"],["16384","161"],["32768","152"],["65536","169"],["131072","175"],["262144","145"],["524288","139"],["1048576","164"],["2097152","148"],["4194304","165"],["8388608","173"],["16777216","167"],["33554432","168"],["67108864","166"],["134217728","144"],["268435456","143"],["536870912","125"],["1073741824","121"],["2147483648","83"]],"logBuckets10":[["0","0"],["512","1754"],["9728","567"],["99840","553"],["999936","507"],["9994240","524"],["99942400","548"],["999817216","447"],["9999220736","100"]],"skewness":"5.1870936464425705","kurtosis":"29.75401032687803","meanCI95":["68253319.936","61344333.793806896","75162306.07819311"],"meanCI99":["68253319.936","59172104.12377828","77334535.74822173"],"percentileCI":[["50","0.95",21503,15871,29695],["99","0.95",1506803711,1404043263,1667235839],["90","0.99",121503743,97189887,154796031]],"export":"ab0001011903e8021b000000e8d4a510000303041913880501061a7ff88e77070008fb3ff0000000000000091958000a99105c0019065101188901185b01185101182e01183b01183601181f01181a01150113011701170116010d0118180105010c010a01070107010c0106010d010701090108010a0111010c010b010d010701050104010401060109010701040105010401010107010101060103010601020109010201040104010601020106010501070108010701080103010401020101010201020101010301030103010301010103010201010103010301010104010301060105010201060105010401020106010201010104010602030102010601020102010201030101010201020103010401030103010301040101010601020203010101010102020102010104010301050102010101020102010401010102010501020301010202020101010102010101010502020101010201030101020102010104010401010102010101040101010102020102010101030301010102020202010101010202010201030202010401030102010304030201010201020103010401020101030101020302020101010101030401010102020201020202020201010101010301010102020301010101010101020101030301020202020201010101020302020101010203020101010101010102030301010101040104020101010209020101010101010101010101010901020202010301010108030601020101010202030202010101010202010101010101020202010102010101010102010101010101010101020101010201020101010402060103010501050101010502020201010701070201010101030101010202010103020201010101010101020106010104040102010602030102020101010203020101020102020601010103040201010101010402030103010301010201010401010101010201010102020501020104010101020101010102010101010301090101020f010401080103010901040101010401020105010c010801050101010801010203020301030107010101010101010301010106010501010101010201060102010301020104010301040202020601030104010502090101010402030103010501010101010501010107010801030101010101050304020701030104021101040101010801010202010101020105010b01060101020401030104010301050104020d010201080103020701090109030701010101020301070101011201020101010602020102010201020102010702020101010201020109010a010101050207010b01050107010201010102010901060109011822010101080101010c010201090107010501040103010b0106010e010c01090103010701181f01110103010801090104010401030108010601060203010b01030108020d010301181e01020102010c010e010101070102010501020105010e010301090109010101050102010a01060102010101110101010301020101010201010102010801040214010201060205010202100109010401060109010101100101010c01010106010b01080101010101040113010e0104010901010101010d010102020103010301050107010102090201010b020e010301030101010701060105011501020108010501080101010501060104010601020101010e010701080104010b0103010d010501060102010601020104010b0101010201050110010301060102010a0103011826010301100109011301020102010901050204010301080108020401030108010a01010104010101030104011820010201010211010d01020101010101020101010a010101020218240107010301060105010701040105010701040112010601040101010101010104010401070102010401030106011201050109010e0103010101181b0308010602030108010f01040102010201110107010b010901010103010101030109010702010109011401040104010e0109010401010203010101030106010e0103010101181a0102010b0110010401080105011501181f010401060105011401030102010201120102010701183401060108020201010102010a010a0101010201010101010301182001050108010401010109010401090109010a010201040101020901070103010301030103010a0107010101010104010601181801010101010601010103010a01040109010801020103010401030108011201090106010301010102010101181a0101010b010801010108010301020101010301070101011818010701050108010201030104010501080101010101010115010401181a010201010103010301030109020301020106010801050204020501140104011401040105010e01030106011501170105010202020104010901010103010201060102010101130102010d0108010c01060106010401080211010202070108010c0103010101030202010701070103010c02140115010601050102010601060101010301060109010101060114010d01020106010b010a01010102010201080110010b010401020214010d0101020101070103010501110105010501090102010901181a010401040107010c01070109010f010201070109010201070109010101050110010101030106010301120104010201170204010901040101010a0109010201020115010c0105010e010101080102020b01020104010301070104010301070109010c010801060105010e020701040101010401070105020801070101010e010402010102010801030106010701010101010f0109010201050102011001010105010b010301020101010701050113010301060104010b010301020101010401020101010a01080107010601080105021001030105010201040101010301040106010b01040110010701050104010501090102010201030201020101050102010101020108010201060104010901080203010a01090212020601050216010601010105010501050103010b010b020b0104010d0102010c0106010801160102010c01181d0114010301020103011201090103010101010102010b01181e01050106010e01040103010a01040102010e010301030103010b0105010602040101010101050106020101040105010201070101020b02020201010b02040102010201020101010d01050103010d0109010b01120103010f010a0101020c010a01020101010402020108011401030101010101010204010b010301130103020d0208010a010c010501020104010101020107010c01020102010201010103010901040109010101020108010f0203010d01010105011501020102010c020201020106010701010103010f0103010401030102010d02010101010101070105010601181a010101020108010b010a020c01010105010b01010107010401110108010a01070118220116010201060103010701030107010101080101020201010107010601020101010501181801070102010101020107010601181c0115010801040106010401070108010301070101010201010101010b010301070101010401010206010a010401030201010201010102010c010a020601050104010901030103010c010901010107010e010801020105010401020101011001030102020501020106010c010e011001020103010601020204020201020101010302090104010401010117010201010104010301050105010501020102011001030107010801010101011701010118240101010801130107010701120203010202110108010601010107011101181801030102010301130103010b01090102010501030108010101020101010801070101010a0110010d010b0105010a0104010b010c01010105010401020104010301181c010a01060112010301050113010a01010102010601070106010901060110011001080109010301090106010401020106010101060105010b01020110010f0104010201010105010901080107010501181901010111010401040102010d01090101020801070102020501070102010401070106010401030104010b010101010108010901110103010301020109020602181a01030102010301040101020301050102010401080111010301182101030102010501110106010101040101010602010106010a010601010102021001181b01010103020101090103010b0102010901040103010f01090107020101050105010f010e0103020503080104020701010102010102070104010202060107010601070108010701020102010301010107010d01120209010401030118280101010a01170104011818010101020101010101080107010c020101070103010c010301050102010101020111010c010e010301050105010201181a011501050103010f01130102010401020106010501020103010101070109011201050112010101030102010f0107010201060106010b01040108010301020102011001070104010101020103010701181801060102010101040102010201010105010c021001140101010c01020118180101010401010101010a01010109010402070102010501070112010a0104010301181b0101010c010201020101011001040103010701080117020d01070103010401090209020401070101010b01030104010201010118260103010a0205010c020e01090102010a01150109010301020208010301040101010b01040108020201030103011301050109010101020108010f010b01010102010f0103010b0112011821010701040103011601020108010501160103011501040107010301010104010901050107010501050110010201020109010c010201181b0102010a01130107010e01010107010f0207010101020110010d0108010101010104011825010201080107010c0101010501040103010801090109011101030106010a01010109010a02050108010d010101020203010801070105010a010102050201011201010116010301010101010a0105010301030102010801060104010b0106010a010201010107010e01060108010401030118210104010f010901010104010401150106010d01030106010e010201182d01020101010d0104010a01010109010201030101011301040107010301020102010501010103010b020101060102010201040107010f0109010102181e0105010a010201182d01010102010501030102010e0105010f010b010d01060102010201030205010d0116010501070109010301050101010c01030109010401030115010d0215011501020103010d010b0106010a011301030102010b0204010a010a011828010c010803182201020105020b01010103010b010f010d0105010301020104020f0101010a010c0106010201110105011827010901020108010f010101030107010e0109010c010201070104020701060106010c010f01090104010b010c01070107010301020101010801040106011820010901140101010a010401050101010d010b0108010c011001090107010d0107011001040107020a010d0105010101181a010e011101030102010901030107010f010a010e0107010e0107010b01040107010a0106010801010118190101010801181a0102010c0104010401030103010701010102010d0102010a01181c010501070101010b010401181a0112020301010105010f010301181e010801090101010b010201090101010101060111010202181b011603181b0101010a01181a010d010901010101010f01181d0101011401184d010c0118300107010c0108010d010901030101010b0105010801080110010201010115010401030106010a01181d01181d01070104010a010a01010106010c0105020c0112010802020101010b0115010a0107010101182302182c01181d010c010301181c0104010101010108011001110102010e010401182901010112010501182201182d0103010201110103010a01","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["256","335.15893630179346","414.3178726035869","493.4821900296587","2797.336338920999","21879.051249346074","178341.50787008597","1755826.815109678","14211419.530023392","121082046.90549038","2146959360"],"densities":["0.0012632812499999999","0.0012632812499999999","0.0012631953795772418","0.000043405525496534574","0.000005240619119897139","6.391309593354836e-7","6.339203258570303e-8","8.028521989183623e-9","9.35710797773007e-10","4.936133069541653e-11"],"corrections":0,"bucketCount":2094},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["256","256","256.00000210253006","4709.972647496368","94564.15744868197","2424356.3799273274","49175029.991236694","2146959360"],"densities":["Infinity","67945.35093493776","0.00003207409524728027","0.0000015898774572740644","6.131754646564939e-8","3.0557237323439227e-9","6.809906090610665e-11"],"corrections":0,"bucketCount":2094},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["256","295.57946815089673","21879.051249346074","428906355.1630517","2094327495.8831866","2146959360"],"densities":["0.0012632812499999999","0.000020849287110151907","1.0492335933434278e-9","2.942198750930485e-11","1.8999897054388154e-11"],"corrections":0,"bucketCount":2094}]},"bimodal":{"count":3000,"exceeds":0,"min":"0","max":"5631","mean":"2728.1066666666666","stddev":"2501.7647934918973","percentile":["511","511","511","511","511","5631","5631","5631","5631","5631"],"percentilesAt":[["0.1","511"],["1","511"],["12","511"],["25","511"],["50","511"],["75","5631"],["90","5631"],["99","5631"],["99.9","5631"],["100","5631"]],"percentiles":[["0","0"],["50","0"],["75","5120"],["100","5120"]],"cdf":[[0,"0.505"],[1,"0.505"],[2,"0.505"],[7,"0.505"],[42,"0.505"],[100,"0.505"],[999,"0.505"],[1000,"0.505"],[5000,"0.6266666666666667"],[1000000,"1"],[5631,"1"],[5632,"1"]],"countAt":[[0,1515],[1,1515],[2,1515],[7,1515],[42,1515],[100,1515],[999,0],[1000,0],[5000,365],[1000000,0],[5631,1120],[5632,0]],"linearStep":281,"linearBuckets":[["0","0"],["512","0"],["1024","0"],["1536","0"],["2048","0"],["2560","0"],["3072","0"],["3584","0"],["4096","0"],["4608","0"],["5120","0"]],"logBuckets":[["0","0"],["512","1515"],["1024","0"],["2048","0"],["4096","0"],["8192","1485"]],"logBuckets10":[["0","0"],["512","1515"],["9728","1485"]],"skewness":"0.031109439329300596","kurtosis":"-1.9853805359380037","meanCI95":["2728.1066666666666","2638.5327332662446","2817.6806000670886"],"meanCI99":["2728.1066666666666","2610.3591357166756","2845.8541976166575"],"percentileCI":[["50","0.95",511,511,5119],["99","0.95",5631,5631,5631],["90","0.99",5631,5631,5631]],"export":"ab0001011903e8021b000000e8d4a51000030304190bb80518640619157b070008fb3ff0000000000000091958000a86001905eb0919016d01190460","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["256","306.6930693069307","357.38613861386136","408.0792079207921","458.7722772277228","1711.3689786161135","5007.769168854086","5170.285714285715","5238.857142857143","5307.428571428572","5376"],"densities":["0.0019726562500000005","0.0019726562500000005","0.001972656249999998","0.0019726562500000013","0.00007983415562978808","0.000030336122506042205","0.0006153219645077339","0.0014583333333333373","0.0014583333333333358","0.0014583333333333358"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["256","256","256","256.0000000000001","4864.000000144391","5376","5376","5376"],"densities":["Infinity","Infinity","1256584717458.2856","0.00003100198412601268","0.0002790178572215443","Infinity","Infinity"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["256","281.34653465346537","1711.3689786161135","5341.714285714285","5375.314285714286","5376"],"densities":["0.0019726562499999983","0.00031468037575202836","0.00012395515080070894","0.0014583333333333189","0.0014583333333332794"],"corrections":0,"bucketCount":3}]},"heavy":{"count":3000,"exceeds":0,"min":"512","max":"1000447","mean":"7763.114666666666","stddev":"40695.77598084169","percentile":["1023","1023","1535","1535","2047","4095","10751","100351","500223","1000447"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","1535"],["25","1535"],["50","2047"],["75","4095"],["90","10751"],["99","100351"],["99.9","500223"],["100","1000447"]],"percentiles":[["0","512"],["50","1536"],["75","3584"],["87.5","8192"],["93.75","15872"],["96.875","34304"],["98.4375","62464"],["99.21875","142848"],["99.609375","333312"],["99.8046875","499712"],["99.90234375","999936"],["100","999936"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0.028333333333333332"],[1000,"0.028333333333333332"],[5000,"0.7986666666666666"],[1000000,"1"],[1000447,"1"],[1000448,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,85],[1000,85],[5000,65],[1000000,3],[1000447,3],[1000448,0]],"linearStep":50022,"linearBuckets":[["49664","2943"],["99840","28"],["150016","11"],["199680","5"],["249856","1"],["300032","0"],["349696","6"],["399872","0"],["450048","0"],["499712","3"],["549888","0"],["600064","0"],["650240","0"],["699904","0"],["750080","0"],["800256","0"],["849920","0"],["900096","0"],["950272","0"],["999936","3"]],"logBuckets":[["0","0"],["512","85"],["1024","972"],["2048","768"],["4096","506"],["8192","306"],["16384","187"],["32768","78"],["65536","55"],["131072","19"],["262144","12"],["524288","9"],["1048576","3"]],"logBuckets10":[["0","0"],["512","85"],["9728","2608"],["99840","278"],["999936","29"]],"skewness":"17.661211680850652","kurtosis":"381.75034706734874","meanCI95":["7763.114666666666","6306.030956451678","9220.198376881654"],"meanCI99":["7763.114666666666","5847.735909117782","9678.49342421555"],"percentileCI":[["50","0.95",2047,2047,2559],["99","0.95",100351,67071,143359],["90","0.99",10751,9727,12799]],"export":"ab0001011903e8021b000000e8d4a51000030304190bb8051903e8061a000f4240070008fb3ff0000000000000091958000a98aa011855011903cc011901ea011901160118bd01188701186501185101184101182701183501181f01182401182001181801181a010c0114011818010f0106010f0109010d010d0108010d01090109010701090105010201050104010802030109010301010105010201050101010101030102020401020101020401010201020602020203020402020204030403050304030303050403050404030502060306030806080309020b010c030f0312021605183e06182e0218410318620118a306190145031903d103","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["768","1137.2510288065844","1295.275720164609","1453.3004115239069","1685.4204082355957","1999.2723994713822","2516.1454174746796","3398.4540622139143","5207.093815582276","10607.682735278024","1000192"],"densities":["0.0002708184736091298","0.0006328124999999999","0.0006328124999949018","0.0004308116552500552","0.00031862152477112477","0.0001934711167286392","0.00011333902325025373","0.00005529017031377465","0.000018516499123883263","1.0105253110357159e-7"],"corrections":0,"bucketCount":85},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["768","1280","1280.0000000000018","1792","2304.0097523789655","3534.8784170695803","7438.5880237326255","1000192"],"densities":["0.00027901785714285713","78536544841.14285","0.0002790178571428581","0.0002790125426193186","0.00011606205191116046","0.000036595227937371964","1.4389992633997418e-7"],"corrections":0,"bucketCount":85},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["768","1058.2386831273973","1999.2723994713822","20239.38290146041","778034.7219700341","1000192"],"densities":["0.00017227200544474983","0.0004781975312726279","0.00002467090316974389","6.466125809143567e-8","4.501315504347846e-9"],"corrections":0,"bucketCount":85}]},"huge":{"count":110,"exceeds":890,"min":"988282880","max":"997506154495","mean":"500809227543.2727","stddev":"288826416170.3161","percentile":["988807167","988807167","135627014143","260919263231","487478788095","731218182143","905164357631","991600574463","997506154495","997506154495"],"percentilesAt":[["0.1","988807167"],["1","988807167"],["12","135627014143"],["25","260919263231"],["50","487478788095"],["75","731218182143"],["90","905164357631"],["99","991600574463"],["99.9","997506154495"],["100","997506154495"]],"percentiles":[["0","988282880"],["50","487210352640"],["75","730681311232"],["87.5","893890068480"],["93.75","978178801664"],["96.875","989989961728"],["98.4375","991063703552"],["99.21875","996969283584"],["100","996969283584"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[997506154495,"1"],[997506154496,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[997506154495,1],[997506154496,0]],"linearStep":49875307724,"linearBuckets":[["49861885952","5"],["99723771904","7"],["149518548992","5"],["199447543808","2"],["249242320896","5"],["299037097984","8"],["348966092800","7"],["398895087616","6"],["448824082432","6"],["498484641792","4"],["548413636608","8"],["598074195968","1"],["648003190784","7"],["697932185600","7"],["747861180416","7"],["797790175232","3"],["847719170048","7"],["897648164864","2"],["947577159680","4"],["996969283584","9"]],"logBuckets":[["0","0"],["512","0"],["1024","0"],["2048","0"],["4096","0"],["8192","0"],["16384","0"],["32768","0"],["65536","0"],["131072","0"],["262144","0"],["524288","0"],["1048576","0"],["2097152","0"],["4194304","0"],["8388608","0"],["16777216","0"],["33554432","0"],["67108864","0"],["134217728","0"],["268435456","0"],["536870912","0"],["1073741824","1"],["2147483648","0"],["4294967296","0"],["8589934592","0"],["17179869184","1"],["34359738368","1"],["68719476736","6"],["137438953472","4"],["274877906944","17"],["549755813888","33"],["1098974756864","47"]],"logBuckets10":[["0","0"],["512","0"],["9728","0"],["99840","0"],["999936","0"],["9994240","0"],["99942400","0"],["999817216","1"],["9999220736","0"],["99992207360","11"],["999653638144","98"]],"skewness":"0.06654629598206836","kurtosis":"-1.129781323762667","meanCI95":["500809227543.2727","445978982230.28925","555639472856.2561"],"meanCI99":["500809227543.2727","428281391410.23175","573337063676.3137"],"percentileCI":[["50","0.95",487478788095,397284474879,607737872383],["99","0.95",991600574463,984621252607,997506154495],["90","0.99",905164357631,804232626175,990526832639]],"export":"ab0001011903e8021b000000e8d4a51000030304186e051a3aecbecb061b000000e83842d42d070008fb3ff0000000000000091958000a98d6192f5d01190efc01190566011901380118a00118cc01188901187f0118930118b3011838011840011902bb01182001181c01181c010b01188d0118f7010e01185b01185201183c0108011884010a01181d01182201183f011301182a010e011840010201182501181c01185101060102011824010c0107010201185a0118370115011829010c010e010c01181a01187b010801183001181f01185301060101010501182b010a01183e011101185601181901020103010f0101011825010c01182e021001050102010201181a0118190114010e010501010106010f010d01181a01110118340107010201182601010116021840011829010d0107011824010201185f0101010301020109010b0102020b01","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["988545024","99743345671.99953","219577502954.9895","302145900694.23364","387675303188.45776","489483240349.9035","601037803395.4934","695398229961.7782","784725727137.3978","918999065896.6589","997237719040"],"densities":["1.0126090007152042e-12","8.344866127263587e-13","1.2111171191162733e-12","1.1691885723947752e-12","9.82241687516183e-13","8.964223181003557e-13","1.0597662986374228e-12","1.1194761205880213e-12","7.447494858178074e-13","1.2781406118634217e-12"],"corrections":0,"bucketCount":107},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["988545024","153183781489.38614","290601474284.4893","415079603802.9569","568578526278.4238","706832238165.9202","855117136099.6432","997237719040"],"densities":["9.38643982393187e-13","1.0395833313120034e-12","1.1476485340016983e-12","9.306719588209168e-13","1.0332969792043819e-12","9.633964405531964e-13","1.0051826406952974e-12"],"corrections":0,"bucketCount":107},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["988545024","53537094824.56662","489483240349.9035","978219671980.1042","997077835573.638","997237719040"],"densities":["9.515010440775449e-13","1.0322375931497858e-12","9.20741673582643e-13","2.5983442002169152e-12","6.254555413099896e-12"],"corrections":0,"bucketCount":107}]}},"comparisons":[{"a":"uniform","b":"wide","welch":["-19.365607715442668","4999.000003338128","0","-75157342.49534652","-61339370.20865347"],"mannWhitney":["4090769.5","-11.99495309458641","3.776310177125828e-33"],"cohensD":"-0.32403470647358734","cliffsD":"-0.1818461","ksTest":"0.5358"},{"a":"bimodal","b":"heavy","welch":["-6.762697760653691","3021.667026066713","1.619571143862686e-11","-6494.837962402119","-3575.1780375978806"],"mannWhitney":["3568037.5","-14.094911092836716","4.0816157208647916e-45"],"cohensD":"-0.1746121053496396","cliffsD":"-0.20710277777777777","ksTest":"0.505"},{"a":"uniform","b":"constant","welch":["73.10416397615293","1998.9999999999998","0","4581.294544721123","4833.8734552788765"],"mannWhitney":["194600","16.011728087919977","1.0582918028683193e-57"],"cohensD":"1.6746477176368173","cliffsD":"0.946","ksTest":"0.946"},{"a":"three","b":"four","welch":["1.0000000000000002","2","0.42264973081037405","-563.6527325439079","904.9860658772413"],"mannWhitney":["8","1.1547005383792517","0.2482130789899235"],"cohensD":"0.912870929175277","cliffsD":"0.3333333333333333","ksTest":"0.33333333333333337"},{"a":"empty","b":"uniform","welch":["0","0","1","0","0"],"mannWhitney":["0","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"},{"a":"one","b":"two","welch":["0","0","1","0","0"],"mannWhitney":["1","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"}]},{"options":{"lowest":1024,"highest":1000000000000,"figures":2},"histograms":{"empty":{"count":0,"exceeds":0,"min":"9223372036854775807","max":"0","mean":"NaN","stddev":"NaN","percentile":["1023","1023","1023","1023","1023","1023","1023","1023","1023","1023"],"percentilesAt":[["0.1","1"],["1","1"],["12","1"],["25","1"],["50","1"],["75","1"],["90","1"],["99","1"],["99.9","1"],["100","1"]],"percentiles":[["100","0"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[1,"0"],[2,"0"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[1,0],[2,0]],"linearStep":1,"linearBuckets":[],"logBuckets":[],"logBuckets10":[],"skewness":"0","kurtosis":"0","meanCI95":["NaN","NaN","NaN"],"meanCI99":["NaN","NaN","NaN"],"percentileCI":[["50","0.95",1023,1023,1023],["99","0.95",1023,1023,1023],["90","0.99",1023,1023,1023]],"export":"ab000101190400021b000000e8d4a5100003020400051b7fffffffffffffff0600070008fb3ff000000000000009190c000a80"},"one":{"count":1,"exceeds":0,"min":"0","max":"1023","mean":"512","stddev":"0","percentile":["1023","1023","1023","1023","1023","1023","1023","1023","1023","1023"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","1023"],["25","1023"],["50","1023"],["75","1023"],["90","1023"],["99","1023"],["99.9","1023"],["100","1023"]],"percentiles":[["0","0"],["100","0"]],"cdf":[[0,"1"],[1,"1"],[2,"1"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[1023,"1"],[1024,"1"]],"countAt":[[0,1],[1,1],[2,1],[7,1],[42,1],[100,1],[999,1],[1000,1],[5000,0],[1000000,0],[1023,1],[1024,0]],"linearStep":51,"linearBuckets":[["0","0"],["1024","0"]],"logBuckets":[["0","0"],["1024","1"]],"logBuckets10":[["0","0"],["9216","1"]],"skewness":"0","kurtosis":"0","meanCI95":["512","NaN","NaN"],"meanCI99":["512","NaN","NaN"],"percentileCI":[["50","0.95",1023,1023,1023],["99","0.95",1023,1023,1023],["90","0.99",1023,1023,1023]],"export":"ab000101190400021b000000e8d4a510000302040105070607070008fb3ff000000000000009190c000a820001","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["512","512","512","512","512","512","512","512","512","512","512"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["512","512","512","512","512","512","512","512"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["512","512","512","512","512","512"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1}]},"two":{"count":2,"exceeds":0,"min":"0","max":"1023","mean":"512","stddev":"0","percentile":["1023","1023","1023","1023","1023","1023","1023","1023","1023","1023"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","1023"],["25","1023"],["50","1023"],["75","1023"],["90","1023"],["99","1023"],["99.9","1023"],["100","1023"]],"percentiles":[["0","0"],["100","0"]],"cdf":[[0,"1"],[1,"1"],[2,"1"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[1023,"1"],[1024,"1"]],"countAt":[[0,2],[1,2],[2,2],[7,2],[42,2],[100,2],[999,2],[1000,2],[5000,0],[1000000,0],[1023,2],[1024,0]],"linearStep":51,"linearBuckets":[["0","0"],["1024","0"]],"logBuckets":[["0","0"],["1024","2"]],"logBuckets10":[["0","0"],["9216","2"]],"skewness":"0","kurtosis":"0","meanCI95":["512","512","512"],"meanCI99":["512","512","512"],"percentileCI":[["50","0.95",1023,0,1023],["99","0.95",1023,1023,1023],["90","0.99",1023,0,1023]],"export":"ab000101190400021b000000e8d4a510000302040205030609070008fb3ff000000000000009190c000a820002","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["0","102.40000000000002","204.8","307.2","409.6000000000001","512","614.4","716.7999999999998","819.2000000000002","921.6","1024"],"densities":["0.0009765624999999999","0.0009765625000000002","0.0009765625","0.0009765624999999995","0.0009765625000000007","0.0009765625","0.000976562500000001","0.0009765624999999978","0.000976562500000001","0.0009765625"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["512","512","512","512","512","512","512","512"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["0","51.20000000000002","512","972.7999999999998","1022.976","1024"],"densities":["0.0009765624999999998","0.0009765625000000002","0.0009765625000000002","0.0009765624999999978","0.0009765625"],"corrections":0,"bucketCount":1}]},"three":{"count":3,"exceeds":0,"min":"0","max":"1023","mean":"512","stddev":"0","percentile":["1023","1023","1023","1023","1023","1023","1023","1023","1023","1023"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","1023"],["25","1023"],["50","1023"],["75","1023"],["90","1023"],["99","1023"],["99.9","1023"],["100","1023"]],"percentiles":[["0","0"],["100","0"]],"cdf":[[0,"1"],[1,"1"],[2,"1"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[1023,"1"],[1024,"1"]],"countAt":[[0,3],[1,3],[2,3],[7,3],[42,3],[100,3],[999,3],[1000,3],[5000,0],[1000000,0],[1023,3],[1024,0]],"linearStep":51,"linearBuckets":[["0","0"],["1024","0"]],"logBuckets":[["0","0"],["1024","3"]],"logBuckets10":[["0","0"],["9216","3"]],"skewness":"0","kurtosis":"0","meanCI95":["512","512","512"],"meanCI99":["512","512","512"],"percentileCI":[["50","0.95",1023,0,1023],["99","0.95",1023,1023,1023],["90","0.99",1023,1023,1023]],"export":"ab000101190400021b000000e8d4a51000030204030501061903e8070008fb3ff000000000000009190c000a820003","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["0","102.4","204.8","307.2","409.6","512","614.4","716.8","819.2","921.6","1024"],"densities":["0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["512","512","512","512","512","512","512","512"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["0","51.2","512","972.8","1022.976","1024"],"densities":["0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625"],"corrections":0,"bucketCount":1}]},"four":{"count":4,"exceeds":0,"min":"0","max":"1023","mean":"512","stddev":"0","percentile":["1023","1023","1023","1023","1023","1023","1023","1023","1023","1023"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","1023"],["25","1023"],["50","1023"],["75","1023"],["90","1023"],["99","1023"],["99.9","1023"],["100","1023"]],"percentiles":[["0","0"],["100","0"]],"cdf":[[0,"1"],[1,"1"],[2,"1"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[1023,"1"],[1024,"1"]],"countAt":[[0,4],[1,4],[2,4],[7,4],[42,4],[100,4],[999,4],[1000,4],[5000,0],[1000000,0],[1023,4],[1024,0]],"linearStep":51,"linearBuckets":[["0","0"],["1024","0"]],"logBuckets":[["0","0"],["1024","4"]],"logBuckets10":[["0","0"],["9216","4"]],"skewness":"0","kurtosis":"0","meanCI95":["512","512","512"],"meanCI99":["512","512","512"],"percentileCI":[["50","0.95",1023,0,1023],["99","0.95",1023,1023,1023],["90","0.99",1023,1023,1023]],"export":"ab000101190400021b000000e8d4a51000030204040502061840070008fb3ff000000000000009190c000a820004","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["0","102.4","204.8","307.2","409.6","512","614.4","716.8","819.2","921.6","1024"],"densities":["0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["512","512","512","512","512","512","512","512"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["0","51.2","512","972.8","1022.976","1024"],"densities":["0.0009765625","0.0009765625","0.0009765625","0.0009765625","0.0009765625"],"corrections":0,"bucketCount":1}]},"constant":{"count":100,"exceeds":0,"min":"0","max":"1023","mean":"512","stddev":"0","percentile":["1023","1023","1023","1023","1023","1023","1023","1023","1023","1023"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","1023"],["25","1023"],["50","1023"],["75","1023"],["90","1023"],["99","1023"],["99.9","1023"],["100","1023"]],"percentiles":[["0","0"],["100","0"]],"cdf":[[0,"1"],[1,"1"],[2,"1"],[7,"1"],[42,"1"],[100,"1"],[999,"1"],[1000,"1"],[5000,"1"],[1000000,"1"],[1023,"1"],[1024,"1"]],"countAt":[[0,100],[1,100],[2,100],[7,100],[42,100],[100,100],[999,100],[1000,100],[5000,0],[1000000,0],[1023,100],[1024,0]],"linearStep":51,"linearBuckets":[["0","0"],["1024","0"]],"logBuckets":[["0","0"],["1024","100"]],"logBuckets10":[["0","0"],["9216","100"]],"skewness":"0","kurtosis":"0","meanCI95":["512","512","512"],"meanCI99":["512","512","512"],"percentileCI":[["50","0.95",1023,1023,1023],["99","0.95",1023,1023,1023],["90","0.99",1023,1023,1023]],"export":"ab000101190400021b000000e8d4a51000030204186405182a06182a070008fb3ff000000000000009190c000a82001864","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["0","102.40000000000002","204.8","307.2","409.6000000000001","512","614.4","716.7999999999998","819.2000000000002","921.6","1024"],"densities":["0.0009765624999999999","0.0009765625000000002","0.0009765625","0.0009765624999999995","0.0009765625000000007","0.0009765625","0.000976562500000001","0.0009765624999999978","0.000976562500000001","0.0009765625"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["512","512","512","512","512","512","512","512"],"densities":["Infinity","Infinity","Infinity","Infinity","Infinity","Infinity","Infinity"],"corrections":0,"bucketCount":1},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["0","51.20000000000002","512","972.7999999999998","1022.976","1024"],"densities":["0.0009765624999999998","0.0009765625000000002","0.0009765625000000002","0.0009765624999999978","0.0009765625"],"corrections":0,"bucketCount":1}]},"uniform":{"count":2000,"exceeds":0,"min":"0","max":"10239","mean":"4970.496","stddev":"2866.959349900867","percentile":["1023","1023","2047","3071","5119","8191","9215","10239","10239","10239"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","2047"],["25","3071"],["50","5119"],["75","8191"],["90","9215"],["99","10239"],["99.9","10239"],["100","10239"]],"percentiles":[["0","0"],["50","4096"],["75","7168"],["87.5","8192"],["93.75","9216"],["100","9216"]],"cdf":[[0,"0.1065"],[1,"0.1065"],[2,"0.1065"],[7,"0.1065"],[42,"0.1065"],[100,"0.1065"],[999,"0.1065"],[1000,"0.1065"],[5000,"0.5105"],[1000000,"1"],[10239,"1"],[10240,"1"]],"countAt":[[0,213],[1,213],[2,213],[7,213],[42,213],[100,213],[999,213],[1000,213],[5000,206],[1000000,0],[10239,137],[10240,0]],"linearStep":511,"linearBuckets":[["0","0"],["1024","0"],["2048","0"],["3072","0"],["4096","0"],["5120","0"],["6144","0"],["7168","0"],["8192","0"],["9216","0"]],"logBuckets":[["0","0"],["1024","428"],["2048","184"],["4096","409"],["8192","842"],["16384","137"]],"logBuckets10":[["0","0"],["9216","2000"]],"skewness":"-0.0022802876766186628","kurtosis":"-1.19436336076271","meanCI95":["4970.496","4844.740709465522","5096.251290534478"],"meanCI99":["4970.496","4805.167747980351","5135.824252019649"],"percentileCI":[["50","0.95",5119,5119,6143],["99","0.95",10239,10239,10239],["90","0.99",9215,9215,9215]],"export":"ab000101190400021b000000e8d4a510000302041907d005030619270b070008fb3ff000000000000009190c000a940018d50118d70118b80118cb0118ce0118c90118f10118c00118d0011889","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["512","995.692599753462","1915.0321266571925","3003.362096062461","4020.0981485686416","5015.869629795527","6030.405038344462","6900.320319143732","7898.665322338549","8905.846153649274","9728"],"densities":["0.0002067428777098719","0.00010877374144544054","0.00009188389809264024","0.00009835394324171677","0.00010042464750726791","0.00009856728425381179","0.00011495372274426638","0.0001001657740360184","0.0000992870365392697","0.00012163173649781914"],"corrections":0,"bucketCount":10},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["512","1535.999742196584","2583.933139737475","4579.328200730678","5632.157869540227","6699.845768196243","8703.84368066838","9728"],"densities":["0.00013950896369436548","0.00013632273118919133","0.00007159341307882964","0.00013568875107658552","0.00013380047019074452","0.00007128607368702985","0.00013948763500319335"],"corrections":0,"bucketCount":10},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["512","752.3755868544602","5015.869629795527","9354.27640195136","9720.525547445255","9728"],"densities":["0.00020800781249999997","0.00010554723320067749","0.00010372471361794108","0.00013378870804987795","0.00013378906249999418"],"corrections":0,"bucketCount":10}]},"wide":{"count":5000,"exceeds":0,"min":"0","max":"2147483647","mean":"68241000.6528","stddev":"249106193.0045753","percentile":["1023","1023","1023","1023","21503","5373951","121634815","1509949439","2113929215","2147483647"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","1023"],["25","1023"],["50","21503"],["75","5373951"],["90","121634815"],["99","1509949439"],["99.9","2113929215"],["100","2147483647"]],"percentiles":[["0","0"],["50","20480"],["75","5341184"],["87.5","64749568"],["93.75","308281344"],["96.875","738197504"],["98.4375","1132462080"],["99.21875","1627389952"],["99.609375","1887436800"],["99.8046875","1996488704"],["99.90234375","2105540608"],["99.951171875","2130706432"],["99.9755859375","2130706432"],["99.98779296875","2139095040"],["100","2139095040"]],"cdf":[[0,"0.3508"],[1,"0.3508"],[2,"0.3508"],[7,"0.3508"],[42,"0.3508"],[100,"0.3508"],[999,"0.3508"],[1000,"0.3508"],[5000,"0.4326"],[1000000,"0.6764"],[2147483647,"1"],[2147483648,"1"]],"countAt":[[0,1754],[1,1754],[2,1754],[7,1754],[42,1754],[100,1754],[999,1754],[1000,1754],[5000,47],[1000000,1],[2147483647,1],[2147483648,0]],"linearStep":107374182,"linearBuckets":[["106954752","4470"],["213909504","137"],["320864256","92"],["427819008","51"],["534773760","46"],["641728512","28"],["750780416","23"],["855638016","23"],["964689920","23"],["1069547520","24"],["1174405120","9"],["1283457024","3"],["1392508928","6"],["1501560832","15"],["1602224128","7"],["1711276032","13"],["1820327936","6"],["1929379840","9"],["2038431744","8"],["2139095040","7"]],"logBuckets":[["0","0"],["1024","1926"],["2048","105"],["4096","132"],["8192","141"],["16384","154"],["32768","149"],["65536","171"],["131072","172"],["262144","146"],["524288","138"],["1048576","163"],["2097152","148"],["4194304","167"],["8388608","173"],["16777216","165"],["33554432","170"],["67108864","164"],["134217728","145"],["268435456","143"],["536870912","126"],["1073741824","119"],["2147483648","83"]],"logBuckets10":[["0","0"],["9216","2321"],["99328","553"],["999424","508"],["9961472","523"],["99614720","549"],["998244352","446"],["9999220736","100"]],"skewness":"5.1865449288698295","kurtosis":"29.748068655331238","meanCI95":["68241000.6528","61333893.21230424","75148108.09329574"],"meanCI99":["68241000.6528","59162254.21816992","77319747.08743007"],"percentileCI":[["50","0.95",21503,16383,29695],["99","0.95",1509949439,1409286143,1669332991],["90","0.99",121634815,97517567,155189247]],"export":"ab000101190400021b000000e8d4a510000302041913880501061a7ff88e77070008fb3ff000000000000009190c000a990a6e001906da0118ac01186901185501182f01182a01182d01182501110111011301130110011201181d011818010c0108010f010b0109010801070109010b0106010a0108010c010f010b01060103010301060106010401030106010501090107010b01060108010501060105010801040104010401070106010701070102010401030101010101070107010301060103010702030102010201010106010201030104010101010108010301050102010201030103010101010102010301010104010301060105010302030103010501060101010101020102010101020205010201040102010201020104010201040102010302050102010201020103010201030203010201020103010202010202010305030102010201010401010201010202040303010101010101020102010101030101010201020103010101020101010201020101010101010102020203010201020103020302010304010302010202020102010101020101010201010101030502010101030202010103010201020101010101020302020401020101020201010201010101030201010201010101010202010101020101010102010401010203040101010201010102010102010101010101030102010101010102030103010102030102010101010101010302020101010101020102010201010101010101020302010201020203010101010101020101020101010301020202010205010102020301030201020102030101010101010101010101020301010102030201020103030101010301010101010104010102010201020102010102040102030102020202010103010101010402020101050201020101010101010102010202020201020101020101040102020101010101020101010101030101010201020202040201010101020201020102010101010101010101020102010101030305010301010102020201020103020101010102010101010202010201010101010101030301010101010103020401020102010201030102020201020102010103020101010101030101010201020101010101010101010101010101010101020103020301010202010201010101040101010201010102010102010101040101010201020101020401020402010202010101010501010103040101010102010101020101010203020104010201020102010102010101010102030403010301010101020301010201010101010102010301010103010103020101020101040103010102030301010101010201010101020301040201010101020201020202010107010101010501010205010104020101010201010101010101010102010301010102010302010104010103030102010101020101010101020102010102010101010201030302020101020103010201020301010201010102010201030101020101010303010302040101010101040101020101010201010202020101030103020104010101030103030202010101010101010102010202030101010102030103010101020202020103020102010101030101010201010301010201010201010401010201020201020201020401020101020101020101010101010302010101010101010101010202010101010101010101030201010201020201010203030203010102030301010101010202010301010103010101020101010102010102010102020103010101030101010202010104010201010103020101020102020201010101010301010101020201010101010201030102010101010101010101030201010301030101010101010101020201010102010101070103010101020102010101020101010101020202010101020201010201010102010101010102020202020202010103020201030103020102020101010104020103010101010101010102020302020102020201030103010301020102010201050204010302020101010101010201020202010203010101010104010101010202010502020201010201020101020101010102010301010101010401010102010201010101020302030303020401010102010101010102010202050102010103010102010101010102020201010102010101010201010101010101040103020102010201020101010401010103010103020103010101010301030101020101010201010101010502020102010402020106020101020102010101020101010101010102020101020101010302020104010102010101020101020103010501020102010203030102010201030201010101010103030204020101030101010101020503010103010102010301010303020201010201020104010101020201020101010202020201010202010301020401010101010202010102010203010101020202020102010101010101010101010301020101010101020201020101030101010101010102030101010202010202010103010101030102010201010102010202030101010102020102010201020301010301040102010101010202040101030303010201030101010101040201030101040101010101020101010202010101010301020201020301030101010301040102010301010101010101010103010201010101020202030501010101010302030201030101010101030101010101020103010102010101020201010102030103020101020102020102010201030101010102010101020201020201010201010101010201010103020101010104010103010103010301020202020102020202030201030102010101020102010101010201010101020302020301010203010101010302020101020101010201030102020201030502010201010102020101010101010102010204010101030202010301020201010101020101010101010203020202010201040101010102030201010101020101010201010101030101010201010101010102030101020203010101010102010101020201010202010302010201010405020101010102030102010101010101030201010102010101020101010102050101010101010103010302020301010301010103010101020101010101010101010201010101010101010102040101010101020301010201010102020101020205010102010101010102010101030302010201030102010301020102010102010103040101010102060301030201010102010101020201040101010103010101010101020102010101010102010102010202030102010102020101010101010102010102010201010101010105010201010304020102010201010101020102020104020202010101010202010101050101020101020201020201010102020103010101010101010102010101010101010201010201030101010204010201020201010101010201010201010101010201010101010201010102020102010102010102030102010203020201010201010102010101010101010202010101010101010203020101040201010102010201030202010104010102010101020401020401010202040101010102020201030101020304010303030201010401010101020101010104020301090102010601010101010101020101030202010101010201010202010102010101010401040201010101010101020203020102010103010101010301010101020402060103010202040401010201030202020502030204010602010102020101","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["512","657.9521094640821","803.9042189281643","949.8563283922466","2778.5514356890744","21823.507859660596","178304.86545384338","1756220.1540733182","14213367.313994644","121079449.46306741","2143289344"],"densities":["0.0006851562499999999","0.0006851562499999999","0.0006851562499999986","0.00005468380136250256","0.000005250733988245407","6.390537603804474e-7","6.337475827836767e-8","8.027520163021953e-9","9.357505954087944e-10","4.9450850908282725e-11"],"corrections":0,"bucketCount":1335},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["512","512","512","4709.538879960935","94590.67534802966","2424512.0721517983","49191427.37431521","2143289344"],"densities":["Infinity","Infinity","0.000034033548453629176","0.0000015894007182240563","6.131414692921276e-8","3.0546625094713974e-9","6.821894130305765e-11"],"corrections":0,"bucketCount":1335},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["512","584.976054732041","21823.507859660596","428916434.4262269","2093710004.9870296","2143289344"],"densities":["0.0006851562500000004","0.000021187905272038354","1.0492087998877882e-9","2.943307859093539e-11","2.016969205132789e-11"],"corrections":0,"bucketCount":1335}]},"bimodal":{"count":3000,"exceeds":0,"min":"0","max":"6143","mean":"2921.8133333333335","stddev":"2453.719009684868","percentile":["1023","1023","1023","1023","1023","6143","6143","6143","6143","6143"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","1023"],["25","1023"],["50","1023"],["75","6143"],["90","6143"],["99","6143"],["99.9","6143"],["100","6143"]],"percentiles":[["0","0"],["50","0"],["75","5120"],["100","5120"]],"cdf":[[0,"0.505"],[1,"0.505"],[2,"0.505"],[7,"0.505"],[42,"0.505"],[100,"0.505"],[999,"0.505"],[1000,"0.505"],[5000,"0.6266666666666667"],[1000000,"1"],[6143,"1"],[6144,"1"]],"countAt":[[0,1515],[1,1515],[2,1515],[7,1515],[42,1515],[100,1515],[999,1515],[1000,1515],[5000,365],[1000000,0],[6143,1120],[6144,0]],"linearStep":307,"linearBuckets":[["0","0"],["1024","0"],["2048","0"],["3072","0"],["4096","0"],["5120","0"]],"logBuckets":[["0","0"],["1024","1515"],["2048","0"],["4096","365"],["8192","1120"]],"logBuckets10":[["0","0"],["9216","3000"]],"skewness":"0.06416999738609203","kurtosis":"-1.947412490220052","meanCI95":["2921.8133333333335","2833.9596455171877","3009.6670211494793"],"meanCI99":["2921.8133333333335","2806.3271150497258","3037.2995516169412"],"percentileCI":[["50","0.95",1023,1023,5119],["99","0.95",6143,6143,6143],["90","0.99",6143,6143,6143]],"export":"ab000101190400021b000000e8d4a51000030204190bb80518640619157b070008fb3ff000000000000009190c000a86001905eb0419016d01190460","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["512","613.3861386138614","714.7722772277227","816.1584158415842","917.5445544554456","1928.1032835278186","4895.538337708173","5220.571428571428","5357.714285714285","5494.857142857143","5632"],"densities":["0.0009863281250000002","0.0009863281250000002","0.000986328124999999","0.0009863281250000007","0.00009895515928281917","0.000033699136855287076","0.00030766098225386783","0.0007291666666666687","0.000729166666666663","0.0007291666666666679"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["512","512","512","512.0000000000001","4608.000000289558","5632","5632","5632"],"densities":["Infinity","Infinity","1256584717458.2856","0.00003487723214039157","0.00013950892861087772","Infinity","Infinity"],"corrections":0,"bucketCount":3},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["512","562.6930693069307","1928.1032835278186","5563.428571428572","5630.628571428571","5632"],"densities":["0.0009863281249999991","0.0003295712858401114","0.00012378534638914142","0.0007291666666666693","0.0007291666666666397"],"corrections":0,"bucketCount":3}]},"heavy":{"count":3000,"exceeds":0,"min":"0","max":"1003519","mean":"7810.3893333333335","stddev":"40716.010837053844","percentile":["1023","1023","2047","2047","2047","4095","11263","100351","501759","1003519"],"percentilesAt":[["0.1","1023"],["1","1023"],["12","2047"],["25","2047"],["50","2047"],["75","4095"],["90","11263"],["99","100351"],["99.9","501759"],["100","1003519"]],"percentiles":[["0","0"],["50","1024"],["75","3072"],["87.5","8192"],["93.75","15360"],["96.875","33792"],["98.4375","62464"],["99.21875","142336"],["99.609375","331776"],["99.8046875","499712"],["99.90234375","999424"],["100","999424"]],"cdf":[[0,"0.028333333333333332"],[1,"0.028333333333333332"],[2,"0.028333333333333332"],[7,"0.028333333333333332"],[42,"0.028333333333333332"],[100,"0.028333333333333332"],[999,"0.028333333333333332"],[1000,"0.028333333333333332"],[5000,"0.7986666666666666"],[1000000,"1"],[1003519,"1"],[1003520,"1"]],"countAt":[[0,85],[1,85],[2,85],[7,85],[42,85],[100,85],[999,85],[1000,85],[5000,146],[1000000,3],[1003519,3],[1003520,0]],"linearStep":50175,"linearBuckets":[["49152","2943"],["99328","28"],["149504","11"],["199680","5"],["249856","1"],["299008","0"],["350208","6"],["399360","0"],["450560","0"],["499712","3"],["548864","0"],["598016","0"],["651264","0"],["700416","0"],["749568","0"],["798720","0"],["851968","0"],["901120","0"],["950272","0"],["999424","3"]],"logBuckets":[["0","0"],["1024","1547"],["2048","467"],["4096","382"],["8192","253"],["16384","177"],["32768","80"],["65536","51"],["131072","19"],["262144","12"],["524288","9"],["1048576","3"]],"logBuckets10":[["0","0"],["9216","2693"],["99328","278"],["999424","29"]],"skewness":"17.68852757050971","kurtosis":"382.78586479860553","meanCI95":["7810.3893333333335","6352.581128286765","9268.197538379902"],"meanCI99":["7810.3893333333335","5894.058206335369","9726.720460331298"],"percentileCI":[["50","0.95",2047,2047,3071],["99","0.95",100351,67583,143359],["90","0.99",11263,10239,13311]],"export":"ab000101190400021b000000e8d4a51000030204190bb8051903e8061a000f4240070008fb3ff000000000000009190c000a987a001855011905b6011901d30118ec01189201185c01184301183801182601182c011501181801181a011501120110010701090108010c0104010701060104010201060101010501010106010201030104010201040204010502040103020502030204020303020303030304060403040206010603070309020b05181f061702182103183101182e06185203188003","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["512","1174.5882352941176","1384.7113543091655","1594.8344733242134","1804.9575923392613","2015.7972553101079","2602.7580299785864","3445.1578757028014","5192.91619558307","10664.387543364493","1001472"],"densities":["0.00015092329545454548","0.0004759114583333333","0.0004759114583333332","0.00047591145833333345","0.00047429406114079815","0.00017036913592135864","0.00011870847378186484","0.00005721614874466779","0.000018276619513058047","1.0092776714952486e-7"],"corrections":0,"bucketCount":61},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["512","1536","1536","1536","2559.999999542173","3584.004293225649","7498.531326761777","1001472"],"densities":["0.00013950892857142856","Infinity","Infinity","0.00013950892863380252","0.0001395083436059309","0.000036494100470701054","1.4372329580167717e-7"],"corrections":0,"bucketCount":61},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["512","1069.5266757862194","2015.7972553101079","20279.62485938105","778468.934288121","1001472"],"densities":["0.0000896818074749346","0.0004755510841586297","0.000024638865946133688","6.462765880584526e-8","4.484243285211179e-9"],"corrections":0,"bucketCount":61}]},"huge":{"count":110,"exceeds":890,"min":"985661440","max":"1000727379967","mean":"500930588299.63635","stddev":"288932717477.1055","percentile":["989855743","989855743","135828340735","260919263231","487478788095","734439407615","906238099455","992137445375","1000727379967","1000727379967"],"percentilesAt":[["0.1","989855743"],["1","989855743"],["12","135828340735"],["25","260919263231"],["50","487478788095"],["75","734439407615"],["90","906238099455"],["99","992137445375"],["99.9","1000727379967"],["100","1000727379967"]],"percentiles":[["0","985661440"],["50","485331304448"],["75","730144440320"],["87.5","893353197568"],["93.75","974957576192"],["96.875","987842478080"],["98.4375","987842478080"],["99.21875","996432412672"],["100","996432412672"]],"cdf":[[0,"0"],[1,"0"],[2,"0"],[7,"0"],[42,"0"],[100,"0"],[999,"0"],[1000,"0"],[5000,"0"],[1000000,"0"],[1000727379967,"1"],[1000727379968,"1"]],"countAt":[[0,0],[1,0],[2,0],[7,0],[42,0],[100,0],[999,0],[1000,0],[5000,0],[1000000,0],[1000727379967,1],[1000727379968,0]],"linearStep":50036368998,"linearBuckets":[["49928994816","5"],["99857989632","7"],["149250113536","5"],["199715979264","2"],["249108103168","5"],["298500227072","8"],["350039834624","7"],["399431958528","6"],["448824082432","6"],["498216206336","4"],["549755813888","8"],["597000454144","1"],["648540061696","7"],["700079669248","7"],["747324309504","8"],["798863917056","3"],["850403524608","6"],["897648164864","3"],["949187772416","3"],["996432412672","9"]],"logBuckets":[["0","0"],["1024","0"],["2048","0"],["4096","0"],["8192","0"],["16384","0"],["32768","0"],["65536","0"],["131072","0"],["262144","0"],["524288","0"],["1048576","0"],["2097152","0"],["4194304","0"],["8388608","0"],["16777216","0"],["33554432","0"],["67108864","0"],["134217728","0"],["268435456","0"],["536870912","0"],["1073741824","1"],["2147483648","0"],["4294967296","0"],["8589934592","0"],["17179869184","1"],["34359738368","1"],["68719476736","6"],["137438953472","5"],["274877906944","16"],["549755813888","33"],["1095216660480","47"]],"logBuckets10":[["0","0"],["9216","0"],["99328","0"],["999424","0"],["9961472","0"],["99614720","0"],["998244352","1"],["9999220736","0"],["99857989632","11"],["996432412672","98"]],"skewness":"0.06689310073048922","kurtosis":"-1.1298196430644234","meanCI95":["500930588299.63635","446080162953.2417","555781013646.031"],"meanCI99":["500930588299.63635","428376058611.8237","573485117987.449"],"percentileCI":[["50","0.95",487478788095,397284474879,609885356031],["99","0.95",992137445375,987842478079,1000727379967],["90","0.99",906238099455,807453851647,992137445375]],"export":"ab000101190400021b000000e8d4a51000030204186e051a3aecbecb061b000000e83842d42d070008fb3ff000000000000009190c000a98b21906eb011901e00118ac011827011401181a0111011001120117010701080118570104010401030101011201181f0102010b010a0108010101100102010301040108010301050102010802050103010a0101020501010101020c01060103010501020101010201030110010101060103010b01010306010101080102010b0103030202050101010602020101030401030102010201010302010201030102010701010205020302080105010101010105020c030101010101030201","qrde":[{"probabilities":["0","0.1","0.2","0.3","0.4","0.5","0.6","0.7","0.8","0.9","1"],"dequantize":"hdr","quantiles":["987758592","99801315378.66545","219575598182.52722","302280962580.8596","387633420854.72156","489754290943.9626","600949676570.7062","695408772853.0785","785030846423.1328","919379590852.3385","998579896320"],"densities":["1.0120068870296415e-12","8.349037678126327e-13","1.209111412874886e-12","1.1716124177600133e-12","9.792317663628632e-13","8.993178937809177e-13","1.0586592920713947e-12","1.1157965444956338e-12","7.443314816588734e-13","1.2626213928029767e-12"],"corrections":0,"bucketCount":89},{"probabilities":["0","0.14285714285714285","0.2857142857142857","0.42857142857142855","0.5714285714285714","0.7142857142857143","0.8571428571428571","1"],"dequantize":"none","quantiles":["987758592","153201837577.72714","290741586144.73883","415086167923.8387","568765086089.0807","706898984503.0677","855985228697.6581","998579896320"],"densities":["9.385277880276655e-13","1.0386607823958644e-12","1.1488811238348195e-12","9.29581913789482e-13","1.034193232055178e-12","9.582181349386119e-13","1.0018407086266098e-12"],"corrections":0,"bucketCount":89},{"probabilities":["0","0.05","0.5","0.95","0.999","1"],"dequantize":"all","quantiles":["987758592","53500140765.148","489754290943.9626","978458029388.858","998380363642.2141","998579896320"],"densities":["9.52156385424223e-13","1.0315088115850615e-12","9.208032691379553e-13","2.4595511437996074e-12","5.011710418045233e-12"],"corrections":0,"bucketCount":89}]}},"comparisons":[{"a":"uniform","b":"wide","welch":["-19.367376538489772","4999.000003311749","0","-75143137.59843849","-61328922.71516149"],"mannWhitney":["4013742.5","-13.061539960733374","5.461175970481877e-39"],"cohensD":"-0.32406430327521313","cliffsD":"-0.1972515","ksTest":"0.5358"},{"a":"bimodal","b":"heavy","welch":["-6.563238421667698","3020.7831130654486","6.173350719507198e-11","-6349.024771239486","-3428.1272287605143"],"mannWhitney":["3647322.5","-12.991129131574565","1.3738505801149958e-38"],"cohensD":"-0.16946208736205098","cliffsD":"-0.1894838888888889","ksTest":"0.4766666666666667"},{"a":"uniform","b":"constant","welch":["69.53016231009622","1998.9999999999998","0","4332.7407094654745","4584.251290534526"],"mannWhitney":["189350","15.185397927661304","4.4187353052102164e-52"],"cohensD":"1.592775586046003","cliffsD":"0.8935","ksTest":"0.8935"},{"a":"three","b":"four","welch":["0","0","1","0","0"],"mannWhitney":["6","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"},{"a":"empty","b":"uniform","welch":["0","0","1","0","0"],"mannWhitney":["0","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"},{"a":"one","b":"two","welch":["0","0","1","0","0"],"mannWhitney":["1","0","1"],"cohensD":"0","cliffsD":"0","ksTest":"0"}]}]} From 55696aebf95400000bbbe52a655941c8fa6c0ed5 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 18:03:03 +0000 Subject: [PATCH 08/24] Add a C++ interface to the histogram crate The benchmark harness records its samples into histograms and reports their statistics. The bridge exposes creating a histogram, recording into it, and a summary with the statistics the harness reports: count, min, max, mean, standard deviation, median, p75, p99, and confidence intervals on the median and the mean, plus skewness and kurtosis. --- src/rust/histogram/BUILD.bazel | 1 + src/rust/histogram/ffi-test.rs | 56 ++++++++++++++++++ src/rust/histogram/ffi.rs | 101 +++++++++++++++++++++++++++++++++ src/rust/histogram/lib.rs | 1 + 4 files changed, 159 insertions(+) create mode 100644 src/rust/histogram/ffi-test.rs create mode 100644 src/rust/histogram/ffi.rs diff --git a/src/rust/histogram/BUILD.bazel b/src/rust/histogram/BUILD.bazel index 062bb6286a0..d7bfc21c8d9 100644 --- a/src/rust/histogram/BUILD.bazel +++ b/src/rust/histogram/BUILD.bazel @@ -2,6 +2,7 @@ load("//:build/wd_rust_crate.bzl", "wd_rust_crate") wd_rust_crate( name = "histogram", + cxx_bridge_src = "ffi.rs", test_compile_data = ["testdata/node-golden.json"], test_deps = ["@crates_vendor//:serde_json"], visibility = ["//visibility:public"], diff --git a/src/rust/histogram/ffi-test.rs b/src/rust/histogram/ffi-test.rs new file mode 100644 index 00000000000..3025508801e --- /dev/null +++ b/src/rust/histogram/ffi-test.rs @@ -0,0 +1,56 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +use super::*; + +#[test] +fn rejects_invalid_options() { + assert!(new_histogram(0, 100, 3).is_err()); + assert!(new_histogram(1, 1, 3).is_err()); + assert!(new_histogram(1, 100, 6).is_err()); +} + +#[test] +fn summarizes_like_the_histogram() { + let mut h = new_histogram(1, 1_000_000, 3).unwrap(); + for v in 1..=100 { + assert!(h.record(v * 10)); + } + assert!(!h.record(2_000_000)); + assert_eq!(h.exceeds(), 1); + + let summary = summarize(&h, 0.95); + let median_ci = h.percentile_ci(50.0, 0.95); + let mean_ci = h.mean_ci(0.95); + assert_eq!( + summary, + Summary { + count: 100, + min: 10, + max: 1000, + mean: h.mean(), + stddev: h.stddev(), + median: 500, + p75: 750, + p99: 990, + median_lower: median_ci.lower, + median_upper: median_ci.upper, + mean_lower: mean_ci.lower, + mean_upper: mean_ci.upper, + skewness: h.skewness(), + kurtosis: h.kurtosis(), + } + ); + assert!(summary.median_lower < 500 && summary.median_upper > 500); + assert!(summary.mean_lower < summary.mean && summary.mean_upper > summary.mean); +} + +#[test] +fn summarizes_an_empty_histogram() { + let h = new_histogram(1, 1_000_000, 3).unwrap(); + let summary = summarize(&h, 0.95); + assert_eq!(summary.count, 0); + assert_eq!(summary.min, i64::MAX); + assert!(summary.mean.is_nan()); +} diff --git a/src/rust/histogram/ffi.rs b/src/rust/histogram/ffi.rs new file mode 100644 index 00000000000..ad42f7cc78f --- /dev/null +++ b/src/rust/histogram/ffi.rs @@ -0,0 +1,101 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! C++ interface, used by the benchmark harness. + +use crate::Histogram; +use crate::Options; + +#[cxx::bridge(namespace = "workerd::rust::histogram")] +#[expect(unsafe_code, reason = "the cxx bridge expands to unsafe FFI glue")] +mod bridge { + /// Summary statistics of a histogram, in its units. + #[derive(Debug, Clone, Copy, PartialEq)] + struct Summary { + count: u64, + min: i64, + max: i64, + mean: f64, + stddev: f64, + median: i64, + p75: i64, + p99: i64, + /// Confidence interval on the median (exact binomial). + median_lower: i64, + median_upper: i64, + /// Confidence interval on the mean (Student's t). + mean_lower: f64, + mean_upper: f64, + skewness: f64, + kurtosis: f64, + } + + extern "Rust" { + type Histogram; + + /// A histogram tracking values in `[lowest, highest]` to `figures` significant figures. + fn new_histogram(lowest: i64, highest: i64, figures: i32) -> Result>; + + /// Records `value`; returns false, counting it as exceeding, if it is out of range. + fn record(self: &mut Histogram, value: i64) -> bool; + + /// The number of values that were out of range. + fn exceeds(self: &Histogram) -> u64; + + /// Summary statistics, with confidence intervals at `confidence` (in (0, 1)). + fn summarize(histogram: &Histogram, confidence: f64) -> Summary; + } +} + +pub use bridge::Summary; + +/// Creates a histogram for C++. +/// +/// # Errors +/// +/// Fails if the options are invalid. +pub fn new_histogram( + lowest: i64, + highest: i64, + figures: i32, +) -> Result, cxx::KjError> { + let options = Options { + lowest, + highest, + figures, + ..Options::default() + }; + Histogram::new(&options) + .map(Box::new) + .map_err(|e| cxx::KjError::new(cxx::KjExceptionType::Failed, e.to_string())) +} + +/// Summarizes a histogram for C++. +#[must_use] +pub fn summarize(histogram: &Histogram, confidence: f64) -> Summary { + // percentile() only fails for percentiles outside (0, 100], which these aren't. + let percentile = |p| histogram.percentile(p).unwrap_or(0); + let median_ci = histogram.percentile_ci(50.0, confidence); + let mean_ci = histogram.mean_ci(confidence); + Summary { + count: histogram.count(), + min: histogram.min(), + max: histogram.max(), + mean: histogram.mean(), + stddev: histogram.stddev(), + median: percentile(50.0), + p75: percentile(75.0), + p99: percentile(99.0), + median_lower: median_ci.lower, + median_upper: median_ci.upper, + mean_lower: mean_ci.lower, + mean_upper: mean_ci.upper, + skewness: histogram.skewness(), + kurtosis: histogram.kurtosis(), + } +} + +#[cfg(test)] +#[path = "ffi-test.rs"] +mod tests; diff --git a/src/rust/histogram/lib.rs b/src/rust/histogram/lib.rs index 7f6c7f658ee..9126d803699 100644 --- a/src/rust/histogram/lib.rs +++ b/src/rust/histogram/lib.rs @@ -12,6 +12,7 @@ //! [`hdr`] is a port of the core of `HdrHistogram_c`, the C library Node.js builds on. mod cbor; +pub mod ffi; pub mod hdr; mod histogram; mod qrde; From a5e55d163c39e55968b73228e75663aab9e461b0 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 18:03:53 +0000 Subject: [PATCH 09/24] Add a process-wide bench mode Set by the upcoming `workerd bench` subcommand. Code that exists only for benchmarking checks it, so that it fails closed anywhere else. --- src/workerd/util/thread-scopes.c++ | 9 +++++++++ src/workerd/util/thread-scopes.h | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/src/workerd/util/thread-scopes.c++ b/src/workerd/util/thread-scopes.c++ index 6bfe3275c06..2c4ea275183 100644 --- a/src/workerd/util/thread-scopes.c++ +++ b/src/workerd/util/thread-scopes.c++ @@ -19,6 +19,7 @@ thread_local uint allowV8BackgroundThreadScopeCount = 0; bool multiTenantProcess = false; bool predictableMode = false; +bool benchMode = false; bool gcStressMode = false; // This variable is read in signal handlers, so use atomic stores and compiler barriers as @@ -56,6 +57,14 @@ void setPredictableModeForTest() { predictableMode = true; } +bool isBenchMode() { + return benchMode; +} + +void setBenchMode() { + benchMode = true; +} + bool isGcStressModeForTest() { // Also honor the WORKERD_GC_STRESS environment variable so that gc-stress mode can be enabled // in binaries that don't have a --gc-stress CLI flag (e.g., edgeworker's prod subcommand). diff --git a/src/workerd/util/thread-scopes.h b/src/workerd/util/thread-scopes.h index cdb0b4fc2b8..870b9891f4a 100644 --- a/src/workerd/util/thread-scopes.h +++ b/src/workerd/util/thread-scopes.h @@ -73,6 +73,11 @@ bool isPredictableModeForTest(); // etc. This should only be used in tests. void setPredictableModeForTest(); +// Tracks whether the process is running benchmarks (`workerd bench`). Only the bench subcommand +// sets this; code that exists only for benchmarking checks it so that it fails closed elsewhere. +bool isBenchMode(); +void setBenchMode(); + // When enabled, forces a full V8 garbage collection at key points where the KJ event loop // re-enters JavaScript (e.g., awaitIo continuations). This helps detect KJ async objects that // are reachable from the JS heap without proper IoOwn wrapping — such objects would violate From f437c16b9a3ef6e5ffbfd2282a538b9019c5184f Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 18:03:53 +0000 Subject: [PATCH 10/24] Add the bench event schema BenchParams carries the options for one bench() handler; BenchReport holds the results: per case, wall and CPU time per call with summary statistics and the raw samples. workerd bench --format=json will print the report with capnp's JSON codec, so field names are its JSON keys. --- src/workerd/io/BUILD.bazel | 2 + src/workerd/io/bench.capnp | 127 +++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/workerd/io/bench.capnp diff --git a/src/workerd/io/BUILD.bazel b/src/workerd/io/BUILD.bazel index 9a8dca6200b..069a9059f8c 100644 --- a/src/workerd/io/BUILD.bazel +++ b/src/workerd/io/BUILD.bazel @@ -495,6 +495,8 @@ wd_capnp_library( wd_capnp_library(src = "actor-storage.capnp") +wd_capnp_library(src = "bench.capnp") + wd_capnp_library(src = "outcome.capnp") wd_capnp_library(src = "script-version.capnp") diff --git a/src/workerd/io/bench.capnp b/src/workerd/io/bench.capnp new file mode 100644 index 00000000000..4dafcf9625a --- /dev/null +++ b/src/workerd/io/bench.capnp @@ -0,0 +1,127 @@ +# Copyright (c) 2026 Cloudflare, Inc. +# Licensed under the Apache 2.0 license found in the LICENSE file or at: +# https://opensource.org/licenses/Apache-2.0 + +@0xa7dcb95a0eecb745; + +# Parameters and results of the `bench` event, which runs a Worker's `bench()` handlers under +# `workerd bench`. Production never sends this event. +# +# The JSON encoding of `BenchReport` (`workerd bench --format=json`) is not yet stable: fields may +# be renamed or removed. + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("workerd::bench"); +$Cxx.allowCancellation; + +struct BenchParams { + caseFilter @0 :Text; + # Glob on case names; empty or "*" runs all cases. + + quick @1 :Bool; + # Short budgets, for checking that benchmarks run rather than measuring them. + + defaults @2 :CaseOptions; + # Defaults for options that a case doesn't set. +} + +struct CaseOptions { + minTimeNs @0 :UInt64; + # Sampling budget: calibration picks a batch size so that `samples` batches take about this long. + + warmupNs @1 :UInt64; + # Time spent calling the function before calibration and sampling. + + samples @2 :UInt32; + # The number of batches timed. + + batch @3 :UInt64; + # Calls per batch; 0 calibrates. +} + +struct BenchReport { + environment @0 :Environment; + groups @1 :List(Group); + + struct Environment { + workerdVersion @0 :Text; + buildMode @1 :Text; + # "release", "debug", or "sanitizer". + + v8Version @2 :Text; + cpuModel @3 :Text; + governor @4 :Text; + # The CPU frequency governor, if known (Linux). + + compatDate @5 :Text; + # The `--compat-date` override, if any. + + allAutogates @6 :Bool; + warnings @7 :List(Text); + # Conditions that make results unreliable, such as a non-release build. + } + + struct Group { + # The cases registered by one `bench()` handler. + + name @0 :Text; + # "service" or "service:entrypoint". + + error @1 :Text; + # Set if `bench()` itself failed. + + cases @2 :List(Case); + } + + struct Case { + name @0 :Text; + status @1 :Status; + enum Status { + ok @0; + skipped @1; + failed @2; + } + + error @2 :Text; + # Set if the case failed. + + iterationsPerSample @3 :UInt64; + wallNs @4 :Metric; + # Wall time per call. + + cpuNs @5 :Metric; + # Thread CPU time per call. On Windows, the thread CPU clock advances only on scheduler ticks + # (about 15ms), so unless batches are much longer than that, this is mostly noise. + + flags @6 :List(Flag); + enum Flag { + highVariance @0; + # The coefficient of variation of the wall time exceeds 10%. + } + } + + struct Metric { + # Statistics over the samples of one metric. Each sample is a batch's total divided by its + # number of calls. Statistics come from an HDR histogram with 3 significant figures, so they + # are accurate to 0.1%; `samples` are exact. + + samples @0 :List(Float64); + mean @1 :Float64; + meanLow @2 :Float64; + meanHigh @3 :Float64; + # 95% confidence interval on the mean (Student's t). + + median @4 :Float64; + medianLow @5 :Float64; + medianHigh @6 :Float64; + # 95% confidence interval on the median (exact binomial). + + p75 @7 :Float64; + p99 @8 :Float64; + min @9 :Float64; + max @10 :Float64; + stddev @11 :Float64; + skewness @12 :Float64; + kurtosis @13 :Float64; + } +} From 25e927e92e9b16cc1ec7ccd18511fc01a9867fa7 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 19:14:32 +0000 Subject: [PATCH 11/24] Add the bench handler and BenchController A Worker's `bench(b, env, ctx)` handler registers cases with `b.run(name, fn, options)`; the runtime then runs the timing loop, so user code never sees a high-resolution clock. For each case, the runner calls `fn` repeatedly to warm up, doubles the batch size until a batch takes at least minTime / samples (and 1 ms), then times `samples` batches, reading the monotonic and thread CPU clocks only between batches. Promises returned by `fn` are awaited before the next call. Batches are separated by a turn of the event loop. `setup` and `teardown` run untimed around each batch, and `b.blackBox(value)` returns its argument through a native call that V8 can't see into. The event is WorkerInterface::bench(params, groupBuilder), delivered like test(): only WorkerEntrypoint implements it, and the worker factory's wrappers forward it. Unlike test(), it awaits the handler directly instead of through finishScheduled(), since the report needs the handler's result. Failures of the handler or of a case are recorded in the report; statistics come from the histogram crate. A sample over an hour, more than the histograms hold, fails its case rather than being left out of the statistics. BenchController construction requires bench mode, which only `workerd bench` sets, so the event fails closed anywhere else. The handler isn't a member of ExportedHandler, which is unwrapped from the export for every event and reads each handler property. Reading a new property there would break exports that reject unknown properties, like miniflare's dev registry proxy, a WorkerEntrypoint that returns a Proxy from its constructor. Instead, as for `queue`, a separate BenchExportedHandler is unwrapped from the export's `self` only when a benchmark runs, through Worker::Api::getBenchTypeHandler(), whose default implementation throws. exported-handler-proxy-test checks that delivering an event reads no property outside the existing handler names. Compatibility: this adds `bench` to the ExportedHandler typings and the BenchController and BenchCaseOptions types to the generated typings, following the `test` precedent. Nothing in production dispatches the event or reads the property. Unlike `test`, `bench` isn't added to the WorkerEntrypoint or DurableObject classes: their methods are RPC methods, and an existing method named `bench` with another signature would stop type-checking. Such methods keep working over RPC. --- src/workerd/api/BUILD.bazel | 2 + src/workerd/api/bench.c++ | 485 ++++++++++++++++++ src/workerd/api/bench.h | 154 ++++++ src/workerd/api/global-scope.c++ | 27 + src/workerd/api/global-scope.h | 31 +- src/workerd/api/tests/BUILD.bazel | 5 + .../api/tests/exported-handler-proxy-test.js | 54 ++ .../tests/exported-handler-proxy-test.wd-test | 14 + src/workerd/io/BUILD.bazel | 2 + src/workerd/io/worker-entrypoint.c++ | 35 ++ src/workerd/io/worker-interface.h | 10 + src/workerd/io/worker.c++ | 5 + src/workerd/io/worker.h | 4 + src/workerd/server/factory/worker-factory.c++ | 8 + src/workerd/server/workerd-api.c++ | 5 + src/workerd/server/workerd-api.h | 2 + 16 files changed, 839 insertions(+), 4 deletions(-) create mode 100644 src/workerd/api/bench.c++ create mode 100644 src/workerd/api/bench.h create mode 100644 src/workerd/api/tests/exported-handler-proxy-test.js create mode 100644 src/workerd/api/tests/exported-handler-proxy-test.wd-test diff --git a/src/workerd/api/BUILD.bazel b/src/workerd/api/BUILD.bazel index b05a6b8f0d3..ed549f55e35 100644 --- a/src/workerd/api/BUILD.bazel +++ b/src/workerd/api/BUILD.bazel @@ -13,6 +13,7 @@ filegroup( "actor-call-retry.c++", "actor-state.c++", "basics.c++", + "bench.c++", "blob.c++", "cache.c++", "container.c++", @@ -71,6 +72,7 @@ filegroup( "actor-call-retry.h", "actor-state.h", "basics.h", + "bench.h", "blob.h", "cache.h", "container.h", diff --git a/src/workerd/api/bench.c++ b/src/workerd/api/bench.c++ new file mode 100644 index 00000000000..6ae168467ec --- /dev/null +++ b/src/workerd/api/bench.c++ @@ -0,0 +1,485 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +#include "bench.h" + +#include +#include +#include + +#include + +#include + +#if _WIN32 +#include +#else +#include +#endif + +namespace workerd::api { + +namespace { + +// Defaults for options that neither the case nor the command line sets. +constexpr uint64_t DEFAULT_MIN_TIME_NS = 1'000'000'000; +constexpr uint64_t DEFAULT_WARMUP_NS = 200'000'000; +constexpr uint32_t DEFAULT_SAMPLES = 50; + +// Under --quick, budgets are capped at these. +constexpr uint64_t QUICK_MIN_TIME_NS = 10'000'000; +constexpr uint64_t QUICK_WARMUP_NS = 2'000'000; +constexpr uint32_t QUICK_SAMPLES = 5; + +// A calibrated batch takes at least this long, so that clock resolution and the cost of reading +// the clocks don't matter. +constexpr uint64_t MIN_BATCH_NS = 1'000'000; + +constexpr uint64_t MAX_CALLS_PER_BATCH = uint64_t{1} << 40; +constexpr uint32_t MAX_SAMPLES = 1'000'000; + +// Samples are recorded into histograms in picoseconds, up to an hour per call. +constexpr int64_t HISTOGRAM_HIGHEST_PS = 3'600'000'000'000'000; +constexpr int32_t HISTOGRAM_FIGURES = 3; +constexpr double CONFIDENCE = 0.95; + +// A case is flagged as high-variance when the coefficient of variation of its wall time per call +// exceeds this. +constexpr double HIGH_VARIANCE_CV = 0.1; + +int64_t monotonicNs() { + return (kj::systemPreciseMonotonicClock().now() - kj::origin()) / kj::NANOSECONDS; +} + +int64_t threadCpuNs() { +#if _WIN32 + // GetThreadTimes() only advances on scheduler ticks (about 15ms), so with 1ms batches the CPU + // time samples on Windows are mostly noise. Windows has no precise thread CPU clock. + FILETIME creation, exit, kernel, user; + KJ_ASSERT(GetThreadTimes(GetCurrentThread(), &creation, &exit, &kernel, &user)); + auto ticks = [](const FILETIME& t) { + return (static_cast(t.dwHighDateTime) << 32) | t.dwLowDateTime; + }; + // FILETIME counts 100 ns ticks. + return (ticks(kernel) + ticks(user)) * 100; +#else + struct timespec ts; + KJ_SYSCALL(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts)); + return static_cast(ts.tv_sec) * 1'000'000'000 + ts.tv_nsec; +#endif +} + +uint64_t durationToNs(double ns, kj::StringPtr what) { + JSG_REQUIRE(std::isfinite(ns) && ns >= 0 && ns < 1e18, RangeError, what, + " must be a non-negative duration."); + return static_cast(ns); +} + +uint64_t parseDuration(const BenchDuration& duration, kj::StringPtr what) { + KJ_SWITCH_ONEOF(duration) { + KJ_CASE_ONEOF(ms, double) { + return durationToNs(ms * 1e6, what); + } + KJ_CASE_ONEOF(text, kj::String) { + size_t numberEnd = 0; + while (numberEnd < text.size() && + (('0' <= text[numberEnd] && text[numberEnd] <= '9') || text[numberEnd] == '.')) { + ++numberEnd; + } + auto number = + JSG_REQUIRE_NONNULL(kj::str(text.first(numberEnd)).tryParseAs(), TypeError, what, + " must be a number of milliseconds or a string like \"500ms\" or \"2s\"."); + auto unit = text.slice(numberEnd); + double scale; + if (unit == "ns"_kj) { + scale = 1; + } else if (unit == "us"_kj || unit == "µs"_kj) { + scale = 1e3; + } else if (unit == "ms"_kj || unit == ""_kj) { + scale = 1e6; + } else if (unit == "s"_kj) { + scale = 1e9; + } else { + JSG_FAIL_REQUIRE(TypeError, what, " has an unknown unit \"", unit, + "\"; use \"ns\", \"us\", \"ms\", or \"s\"."); + } + return durationToNs(number * scale, what); + } + } + KJ_UNREACHABLE; +} + +uint64_t parseCount(double value, uint64_t max, kj::StringPtr what) { + JSG_REQUIRE(value >= 1 && value <= static_cast(max) && value == std::floor(value), + RangeError, what, " must be an integer from 1 to ", max, "."); + return static_cast(value); +} + +// A promise for `value`, awaiting it if it is a promise. +jsg::Promise settle(jsg::Lock& js, jsg::Value value) { + if (value.getHandle(js)->IsPromise()) { + return js.toPromise(value.getHandle(js)); + } + return js.resolvedPromise(kj::mv(value)); +} + +// Whether any of `samples` is longer than the histograms hold. +bool exceedsHistogram(kj::ArrayPtr samples) { + for (auto ns: samples) { + if (ns * 1000 > HISTOGRAM_HIGHEST_PS) return true; + } + return false; +} + +::workerd::rust::histogram::Summary fillMetric( + kj::ArrayPtr samples, bench::BenchReport::Metric::Builder metric) { + auto list = metric.initSamples(samples.size()); + for (auto i: kj::indices(samples)) { + list.set(i, samples[i]); + } + + auto histogram = + ::workerd::rust::histogram::new_histogram(1, HISTOGRAM_HIGHEST_PS, HISTOGRAM_FIGURES); + for (auto sample: samples) { + // fillBenchReport() fails cases with samples out of range, so every sample is recorded. + KJ_ASSERT(histogram->record(static_cast(std::llround(kj::max(sample, 0.0) * 1000)))); + } + auto summary = ::workerd::rust::histogram::summarize(*histogram, CONFIDENCE); + + auto ns = [](auto ps) { return static_cast(ps) / 1000; }; + metric.setMean(ns(summary.mean)); + metric.setMeanLow(ns(summary.mean_lower)); + metric.setMeanHigh(ns(summary.mean_upper)); + metric.setMedian(ns(summary.median)); + metric.setMedianLow(ns(summary.median_lower)); + metric.setMedianHigh(ns(summary.median_upper)); + metric.setP75(ns(summary.p75)); + metric.setP99(ns(summary.p99)); + metric.setMin(ns(summary.min)); + metric.setMax(ns(summary.max)); + metric.setStddev(ns(summary.stddev)); + metric.setSkewness(summary.skewness); + metric.setKurtosis(summary.kurtosis); + return summary; +} + +} // namespace + +kj::String describeBenchException(jsg::Lock& js, const jsg::Value& exception) { + return js.tryCatch([&]() -> kj::String { + auto value = jsg::JsValue(exception.getHandle(js)); + KJ_IF_SOME(object, value.tryCast()) { + auto stack = object.get(js, "stack"); + if (stack.isString()) { + return stack.toString(js); + } + } + return value.toString(js); + }, [](jsg::Value&&) { return kj::str("(an exception that can't be converted to a string)"); }); +} + +void fillBenchReport(const BenchGroupResult& result, bench::BenchReport::Group::Builder report) { + KJ_IF_SOME(error, result.error) { + report.setError(error); + } + auto cases = report.initCases(result.cases.size()); + for (auto i: kj::indices(result.cases)) { + auto& from = result.cases[i]; + auto to = cases[i]; + to.setName(from.name); + to.setStatus(from.status); + KJ_IF_SOME(error, from.error) { + to.setError(error); + } + if (from.status != bench::BenchReport::Case::Status::OK) { + continue; + } + if (exceedsHistogram(from.wallNs) || exceedsHistogram(from.cpuNs)) { + // The histograms can't record such a sample; fail the case rather than leave it out. + to.setStatus(bench::BenchReport::Case::Status::FAILED); + to.setError( + "A call took longer than an hour, the longest that the statistics can represent."); + continue; + } + to.setIterationsPerSample(from.iterationsPerSample); + auto wall = fillMetric(from.wallNs, to.initWallNs()); + fillMetric(from.cpuNs, to.initCpuNs()); + if (wall.mean > 0 && wall.stddev / wall.mean > HIGH_VARIANCE_CV) { + to.initFlags(1).set(0, bench::BenchReport::Case::Flag::HIGH_VARIANCE); + } + } +} + +struct BenchController::BatchTiming { + int64_t wallNs = 0; + int64_t cpuNs = 0; + + static BatchTiming now() { + return {.wallNs = monotonicNs(), .cpuNs = threadCpuNs()}; + } + BatchTiming operator-(const BatchTiming& other) const { + return {.wallNs = wallNs - other.wallNs, .cpuNs = cpuNs - other.cpuNs}; + } +}; + +struct BenchController::CaseRun { + enum class Phase { WARMUP, CALIBRATE, SAMPLE }; + + size_t index; + Phase phase; + uint64_t calls = 1; + uint64_t warmedUpNs = 0; + // Calibration grows batches until one takes at least this long. + uint64_t targetBatchNs; + kj::Vector wallNs; + kj::Vector cpuNs; +}; + +BenchController::BenchController(bench::BenchParams::Reader params) + : caseFilter(kj::str(params.getCaseFilter())), + quick(params.getQuick()) { + // Only `workerd bench` delivers the bench event; fail closed anywhere else. + KJ_REQUIRE(isBenchMode(), "the bench event is only supported by `workerd bench`"); + + auto defaults = params.getDefaults(); + defaultMinTimeNs = defaults.getMinTimeNs() > 0 ? defaults.getMinTimeNs() : DEFAULT_MIN_TIME_NS; + defaultWarmupNs = defaults.getWarmupNs() > 0 ? defaults.getWarmupNs() : DEFAULT_WARMUP_NS; + defaultSamples = defaults.getSamples() > 0 ? defaults.getSamples() : DEFAULT_SAMPLES; + defaultBatch = defaults.getBatch(); +} + +void BenchController::run(jsg::Lock& js, + kj::String name, + v8::Local fn, + jsg::Optional maybeOptions) { + JSG_REQUIRE(!running, Error, + "BenchController.run() must be called by bench(), before it returns or its promise " + "resolves."); + for (auto& c: cases) { + JSG_REQUIRE(c.name != name, Error, "A case named \"", name, "\" is already registered."); + } + + auto options = kj::mv(maybeOptions).orDefault({}); + Case c{ + .name = kj::mv(name), + .fn = js.v8Ref(fn), + .setup = kj::mv(options.setup), + .teardown = kj::mv(options.teardown), + .skip = options.skip.orDefault(false), + .minTimeNs = defaultMinTimeNs, + .warmupNs = defaultWarmupNs, + .samples = defaultSamples, + .batch = defaultBatch, + }; + KJ_IF_SOME(minTime, options.minTime) { + c.minTimeNs = parseDuration(minTime, "minTime"); + } + KJ_IF_SOME(warmup, options.warmup) { + c.warmupNs = parseDuration(warmup, "warmup"); + } + KJ_IF_SOME(samples, options.samples) { + c.samples = static_cast(parseCount(samples, MAX_SAMPLES, "samples")); + } + KJ_IF_SOME(batch, options.batch) { + c.batch = parseCount(batch, MAX_CALLS_PER_BATCH, "batch"); + } + if (quick) { + c.minTimeNs = kj::min(c.minTimeNs, QUICK_MIN_TIME_NS); + c.warmupNs = kj::min(c.warmupNs, QUICK_WARMUP_NS); + c.samples = kj::min(c.samples, QUICK_SAMPLES); + } + cases.add(kj::mv(c)); +} + +BenchController::Options BenchController::getOptions() { + return { + .filter = + caseFilter == ""_kj || caseFilter == "*"_kj ? kj::none : kj::Maybe(kj::str(caseFilter)), + .quick = quick, + }; +} + +jsg::Promise BenchController::runCases(jsg::Lock& js) { + running = true; + if (cases.empty()) { + return js.resolvedPromise(BenchGroupResult{ + .error = kj::str("bench() did not register any cases."), + }); + } + return runCasesFrom(js, 0, {}); +} + +jsg::Promise BenchController::runCasesFrom( + jsg::Lock& js, size_t index, kj::Vector results) { + kj::GlobFilter filter(caseFilter == ""_kj ? "*"_kj : caseFilter.asPtr()); + while (index < cases.size() && !filter.matches(cases[index].name)) { + ++index; + } + if (index == cases.size()) { + return js.resolvedPromise(BenchGroupResult{.cases = results.releaseAsArray()}); + } + + return runCase(js, index).then(js, + IoContext::current().addFunctor([self = JSG_THIS, index, results = kj::mv(results)]( + jsg::Lock& js, BenchCaseResult result) mutable { + results.add(kj::mv(result)); + return self->runCasesFrom(js, index + 1, kj::mv(results)); + })); +} + +jsg::Promise BenchController::runCase(jsg::Lock& js, size_t index) { + auto& c = cases[index]; + if (c.skip) { + return js.resolvedPromise(BenchCaseResult{ + .name = kj::str(c.name), + .status = bench::BenchReport::Case::Status::SKIPPED, + }); + } + + using Phase = CaseRun::Phase; + auto run = kj::heap(CaseRun{ + .index = index, + .phase = c.warmupNs > 0 ? Phase::WARMUP + : c.batch > 0 ? Phase::SAMPLE + : Phase::CALIBRATE, + .calls = c.batch > 0 && c.warmupNs == 0 ? c.batch : 1, + .targetBatchNs = kj::max(c.minTimeNs / c.samples, MIN_BATCH_NS), + }); + + // Starting from a continuation turns exceptions thrown by the first batch into a rejection. + return js.resolvedPromise() + .then(js, + IoContext::current().addFunctor( + [self = JSG_THIS, run = kj::mv(run)](jsg::Lock& js) mutable { + return self->step(js, kj::mv(run)); + })).catch_(js, [name = kj::str(c.name)](jsg::Lock& js, jsg::Value exception) mutable { + return BenchCaseResult{ + .name = kj::mv(name), + .status = bench::BenchReport::Case::Status::FAILED, + .error = describeBenchException(js, exception), + }; + }); +} + +jsg::Promise BenchController::step(jsg::Lock& js, kj::Own run) { + // Yield to the event loop before each batch, so that pending I/O and timers make progress + // outside the timed region. + auto& context = IoContext::current(); + return context.awaitIo( + js, kj::evalLater([] {}), [self = JSG_THIS, run = kj::mv(run)](jsg::Lock& js) mutable { + auto index = run->index; + auto calls = run->calls; + return self->runBatch(js, index, calls) + .then(js, + IoContext::current().addFunctor( + [self = self.addRef(), run = kj::mv(run)]( + jsg::Lock& js, BatchTiming timing) mutable -> jsg::Promise { + using Phase = CaseRun::Phase; + auto& c = self->cases[run->index]; + auto wallNs = static_cast(kj::max(timing.wallNs, int64_t{0})); + auto grow = [&]() { + if (wallNs < run->targetBatchNs && run->calls < MAX_CALLS_PER_BATCH) { + run->calls *= 2; + return true; + } + return false; + }; + + switch (run->phase) { + case Phase::WARMUP: + run->warmedUpNs += wallNs; + if (run->warmedUpNs < c.warmupNs) { + grow(); + } else if (c.batch > 0) { + run->phase = Phase::SAMPLE; + run->calls = c.batch; + } else { + run->phase = Phase::CALIBRATE; + } + break; + case Phase::CALIBRATE: + if (!grow()) { + run->phase = Phase::SAMPLE; + } + break; + case Phase::SAMPLE: { + auto calls = static_cast(run->calls); + run->wallNs.add(static_cast(timing.wallNs) / calls); + run->cpuNs.add(static_cast(timing.cpuNs) / calls); + if (run->wallNs.size() == c.samples) { + return js.resolvedPromise(BenchCaseResult{ + .name = kj::str(c.name), + .iterationsPerSample = run->calls, + .wallNs = run->wallNs.releaseAsArray(), + .cpuNs = run->cpuNs.releaseAsArray(), + }); + } + break; + } + } + return self->step(js, kj::mv(run)); + })); + }); +} + +jsg::Promise BenchController::runBatch( + jsg::Lock& js, size_t index, uint64_t calls) { + // Times the batch given the setup result (undefined without `setup`), then runs `teardown`. + auto timeBatch = [self = JSG_THIS, index, calls]( + jsg::Lock& js, jsg::Value state) mutable -> jsg::Promise { + if (self->cases[index].teardown == kj::none) { + return self->callBatch(js, index, kj::mv(state), calls, BatchTiming::now()); + } + auto teardownState = state.addRef(js); + return self->callBatch(js, index, kj::mv(state), calls, BatchTiming::now()) + .then(js, + IoContext::current().addFunctor( + [self = self.addRef(), index, state = kj::mv(teardownState)]( + jsg::Lock& js, BatchTiming timing) mutable { + auto& teardown = KJ_ASSERT_NONNULL(self->cases[index].teardown); + return settle(js, teardown(js, kj::mv(state))).then(js, [timing](jsg::Lock&, jsg::Value) { + return timing; + }); + })); + }; + + KJ_IF_SOME(setup, cases[index].setup) { + return settle(js, setup(js)).then(js, IoContext::current().addFunctor(kj::mv(timeBatch))); + } + return timeBatch(js, js.v8Ref(js.v8Undefined())); +} + +jsg::Promise BenchController::callBatch( + jsg::Lock& js, size_t index, jsg::Value state, uint64_t remaining, BatchTiming start) { + auto& c = cases[index]; + auto isolate = js.v8Isolate; + auto context = js.v8Context(); + auto fn = c.fn.getHandle(js); + auto receiver = js.v8Undefined(); + v8::Local argument = state.getHandle(js); + int argumentCount = c.setup == kj::none ? 0 : 1; + + while (remaining > 0) { + v8::HandleScope scope(isolate); + --remaining; + auto result = jsg::check(fn->Call(context, receiver, argumentCount, &argument)); + if (result->IsPromise()) { + // Await the result before the next call; the batch's time includes the wait. + return js.toPromise(result).then(js, + IoContext::current().addFunctor([self = JSG_THIS, index, state = kj::mv(state), remaining, + start](jsg::Lock& js, jsg::Value) mutable { + return self->callBatch(js, index, kj::mv(state), remaining, start); + })); + } + } + return js.resolvedPromise(BatchTiming::now() - start); +} + +void BenchController::visitForGc(jsg::GcVisitor& visitor) { + for (auto& c: cases) { + visitor.visit(c.fn, c.setup, c.teardown); + } +} + +} // namespace workerd::api diff --git a/src/workerd/api/bench.h b/src/workerd/api/bench.h new file mode 100644 index 00000000000..e4b567c7975 --- /dev/null +++ b/src/workerd/api/bench.h @@ -0,0 +1,154 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +#pragma once + +// The `bench` handler: `BenchController`, which a Worker's `bench(b, env, ctx)` handler uses to +// register cases, and the runner that times them. Only `workerd bench` dispatches this event. +// +// The runtime runs the timing loop: user code supplies the function to time and never sees a +// high-resolution clock. For each case, the runner calls the function repeatedly to warm up, +// calibrates a batch size, then times `samples` batches, reading the monotonic and thread CPU +// clocks only between batches. Each sample is a batch's time divided by its number of calls. + +#include +#include + +namespace workerd::api { + +// Results of one case, before they are written to a `BenchReport::Case`. +struct BenchCaseResult { + kj::String name; + bench::BenchReport::Case::Status status = bench::BenchReport::Case::Status::OK; + kj::Maybe error; + uint64_t iterationsPerSample = 0; + // Wall and thread CPU time per call, one value per sampled batch. + kj::Array wallNs; + kj::Array cpuNs; +}; + +// Results of one `bench()` handler. +struct BenchGroupResult { + // Set if `bench()` itself failed. + kj::Maybe error; + kj::Array cases; +}; + +// The exception's stack if it has one, or else its string conversion. +kj::String describeBenchException(jsg::Lock& js, const jsg::Value& exception); + +// Writes `result` to `report`, computing summary statistics of the samples. Doesn't set the +// group's name, which the caller knows. +void fillBenchReport(const BenchGroupResult& result, bench::BenchReport::Group::Builder report); + +// A duration: a number of milliseconds, or a string with a unit, as in "500ms" or "2s". +using BenchDuration = kj::OneOf; + +// Options for one case, the third argument of `BenchController.run()`. +struct BenchCaseOptions { + // Sampling budget: the batch size is calibrated so that all samples take about this long. + jsg::Optional minTime; + // How long to call the function before calibrating and sampling, to let V8 optimize it. + jsg::Optional warmup; + // The number of batches to time. + jsg::Optional samples; + // Calls per batch, instead of calibrating. + jsg::Optional batch; + // Called before each batch, untimed. Its result (awaited if it is a promise) is passed to every + // call of the function in that batch. + jsg::Optional> setup; + // Called after each batch with the setup result (undefined without `setup`), untimed, and + // awaited if it returns a promise. + jsg::Optional> teardown; + // Reports the case as skipped instead of running it. + jsg::Optional skip; + + JSG_STRUCT(minTime, warmup, samples, batch, setup, teardown, skip); +}; + +// The first argument of a `bench()` handler. +// +// The bench types aren't in the generated TypeScript definitions: only `workerd bench` delivers +// the bench event, so they aren't part of the API that deployed Workers see. Nothing that the +// type generator visits refers to them. +class BenchController final: public jsg::Object { + public: + explicit BenchController(bench::BenchParams::Reader params); + + struct Options { + // The case filter given on the command line, if any. + jsg::Optional filter; + // Whether `--quick` was given: budgets are short, so results only show that cases run. + bool quick; + + JSG_STRUCT(filter, quick); + }; + + // Registers a case. Cases run, in registration order, after `bench()` returns or its promise + // resolves. `fn` may return a promise, which is awaited before the next call. + void run(jsg::Lock& js, + kj::String name, + v8::Local fn, + jsg::Optional options); + + // Returns `value`. Passing a result through it keeps V8 from optimizing away the code that + // computed it, since V8 can't see into the native call. + jsg::JsValue blackBox(jsg::JsValue value) { + return value; + } + + Options getOptions(); + + JSG_RESOURCE_TYPE(BenchController) { + JSG_METHOD(run); + JSG_METHOD(blackBox); + JSG_READONLY_PROTOTYPE_PROPERTY(options, getOptions); + } + + // Runs the registered cases, after the handler has registered them. Called from C++. + jsg::Promise runCases(jsg::Lock& js); + + private: + struct Case { + kj::String name; + jsg::V8Ref fn; + kj::Maybe> setup; + kj::Maybe> teardown; + bool skip; + uint64_t minTimeNs; + uint64_t warmupNs; + uint32_t samples; + // 0 calibrates. + uint64_t batch; + }; + + struct CaseRun; + struct BatchTiming; + + kj::String caseFilter; + bool quick; + uint64_t defaultMinTimeNs; + uint64_t defaultWarmupNs; + uint32_t defaultSamples; + uint64_t defaultBatch; + + kj::Vector cases; + // Set once the cases start running; registering a case after that is an error. + bool running = false; + + jsg::Promise runCasesFrom( + jsg::Lock& js, size_t index, kj::Vector results); + jsg::Promise runCase(jsg::Lock& js, size_t index); + jsg::Promise step(jsg::Lock& js, kj::Own run); + jsg::Promise runBatch(jsg::Lock& js, size_t index, uint64_t calls); + jsg::Promise callBatch( + jsg::Lock& js, size_t index, jsg::Value state, uint64_t remaining, BatchTiming start); + + void visitForGc(jsg::GcVisitor& visitor); +}; + +#define EW_BENCH_ISOLATE_TYPES \ + api::BenchController, api::BenchController::Options, api::BenchCaseOptions + +} // namespace workerd::api diff --git a/src/workerd/api/global-scope.c++ b/src/workerd/api/global-scope.c++ index 33312465004..1aa35989389 100644 --- a/src/workerd/api/global-scope.c++ +++ b/src/workerd/api/global-scope.c++ @@ -867,6 +867,33 @@ jsg::Promise ServiceWorkerGlobalScope::test( return testHandler(lock, js.alloc(), eh.env.addRef(lock), eh.getCtx()); } +jsg::Promise ServiceWorkerGlobalScope::bench(Worker::Lock& lock, + kj::Maybe exportedHandler, + bench::BenchParams::Reader params) { + ExportedHandler& eh = JSG_REQUIRE_NONNULL(exportedHandler, Error, + "Benchmarks are not currently supported with Service Workers syntax."); + jsg::Lock& js = lock; + auto& typeHandler = lock.getWorker().getIsolate().getApi().getBenchTypeHandler(lock); + auto handlers = JSG_REQUIRE_NONNULL(typeHandler.tryUnwrap(js, eh.self.getHandle(js)), Error, + "Entrypoint does not export a bench() function."); + auto benchHandler = JSG_REQUIRE_NONNULL( + kj::mv(handlers.bench), Error, "Entrypoint does not export a bench() function."); + auto controller = js.alloc(params); + return js + .tryCatch( + [&] { return benchHandler(lock, controller.addRef(), eh.env.addRef(lock), eh.getCtx()); }, + [&](jsg::Value exception) { return js.rejectedPromise(kj::mv(exception)); }) + .then(js, + IoContext::current().addFunctor( + [controller = controller.addRef()]( + jsg::Lock& js) mutable { return controller->runCases(js); }), + [](jsg::Lock& js, jsg::Value exception) { + return js.resolvedPromise(BenchGroupResult{ + .error = describeBenchException(js, exception), + }); + }); +} + // This promise is used to set the timeout for hibernatable websocket events. It's expected to be // dropped in most cases, as long as the hibernatable websocket event promise completes before it. kj::Promise ServiceWorkerGlobalScope::eventTimeoutPromise(uint32_t timeoutMs) { diff --git a/src/workerd/api/global-scope.h b/src/workerd/api/global-scope.h index be5c19b6bc1..ca6b08f4f1a 100644 --- a/src/workerd/api/global-scope.h +++ b/src/workerd/api/global-scope.h @@ -5,6 +5,7 @@ #pragma once #include "basics.h" +#include "bench.h" #include "filesystem.h" #include "http.h" #include "messagechannel.h" @@ -630,6 +631,20 @@ struct ExportedHandler { IsRetryableHandler isFetchRetryable(jsg::Lock& js); }; +// The `bench` handler of an exported object. It isn't a member of ExportedHandler, which is +// unwrapped from the export for every event, reading each of its handlers: reading another name +// there would break exports that reject unknown properties, such as a Proxy. Instead, +// ServiceWorkerGlobalScope::bench() unwraps this from ExportedHandler::self, only when a +// benchmark runs. +struct BenchExportedHandler { + using BenchHandler = jsg::Promise(jsg::Ref controller, + jsg::Value env, + jsg::Optional> ctx); + jsg::LenientOptional> bench; + + JSG_STRUCT(bench); +}; + // V8 private key set on functions marked with retryable() from "cloudflare:durable-objects". // Reading it runs no user code, so a claim can check it before the handler or method runs. inline constexpr auto RETRYABLE_METHOD_PRIVATE_KEY = "cloudflare:durable-objects:retryable"_kjc; @@ -757,6 +772,13 @@ class ServiceWorkerGlobalScope: public WorkerGlobalScope { // and report them. jsg::Promise test(Worker::Lock& lock, kj::Maybe exportedHandler); + // Received bench() (called from C++, not JS). See WorkerInterface::bench(). Runs the handler to + // register cases, then runs them. Failures, of the handler or of a case, are recorded in the + // result rather than rejecting. + jsg::Promise bench(Worker::Lock& lock, + kj::Maybe exportedHandler, + bench::BenchParams::Reader params); + kj::Promise eventTimeoutPromise(uint32_t timeoutMs); kj::Promise setHibernatableEventTimeout( kj::Promise event, kj::Maybe eventTimeoutMs); @@ -1252,9 +1274,10 @@ class ServiceWorkerGlobalScope: public WorkerGlobalScope { #define EW_GLOBAL_SCOPE_ISOLATE_TYPES \ api::WorkerGlobalScope, api::ServiceWorkerGlobalScope, api::TestController, \ - api::ExecutionContext, api::ExportedHandler, \ - api::ServiceWorkerGlobalScope::StructuredCloneOptions, api::Navigator, \ - api::AlarmInvocationInfo, api::Immediate, api::Cloudflare, api::CachePurgeError, \ - api::CachePurgeResult, api::CachePurgeOptions, api::CacheContext, api::AccessContext + EW_BENCH_ISOLATE_TYPES, api::ExecutionContext, api::ExportedHandler, \ + api::BenchExportedHandler, api::ServiceWorkerGlobalScope::StructuredCloneOptions, \ + api::Navigator, api::AlarmInvocationInfo, api::Immediate, api::Cloudflare, \ + api::CachePurgeError, api::CachePurgeResult, api::CachePurgeOptions, api::CacheContext, \ + api::AccessContext // The list of global-scope.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE } // namespace workerd::api diff --git a/src/workerd/api/tests/BUILD.bazel b/src/workerd/api/tests/BUILD.bazel index a844fadce18..7fa5100a94a 100644 --- a/src/workerd/api/tests/BUILD.bazel +++ b/src/workerd/api/tests/BUILD.bazel @@ -34,6 +34,11 @@ ts_project( deps = ["//src/node:node@tsproject"], ) +wd_test( + src = "exported-handler-proxy-test.wd-test", + data = ["exported-handler-proxy-test.js"], +) + wd_test( src = "stdio-writesync-reentry-uaf-test.wd-test", args = ["--experimental"], diff --git a/src/workerd/api/tests/exported-handler-proxy-test.js b/src/workerd/api/tests/exported-handler-proxy-test.js new file mode 100644 index 00000000000..662bc7ea34a --- /dev/null +++ b/src/workerd/api/tests/exported-handler-proxy-test.js @@ -0,0 +1,54 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { WorkerEntrypoint } from 'cloudflare:workers'; + +// The handler names the runtime may read from every entrypoint when it delivers an event. Some +// Workers return a Proxy from their entrypoint's constructor that rejects any other property, so +// reading a new name here breaks them. +const HANDLER_NAMES = new Set([ + 'alarm', + 'connect', + 'email', + 'fetch', + 'queue', + 'scheduled', + // ExportedHandler's `self` is a SelfRef. The struct wrapper ignores the value, but still reads + // the property. + 'self', + 'tail', + 'tailStream', + 'test', + 'trace', + 'webSocketClose', + 'webSocketError', + 'webSocketMessage', +]); + +export class Strict extends WorkerEntrypoint { + constructor(ctx, env) { + super(ctx, env); + return new Proxy(this, { + get(target, prop) { + if (Reflect.has(target, prop)) return Reflect.get(target, prop); + if (typeof prop === 'string' && HANDLER_NAMES.has(prop)) + return undefined; + throw new Error(`unexpected property read: ${String(prop)}`); + }, + }); + } + + fetch() { + return new Response('ok'); + } +} + +export const fetchThroughStrictProxy = { + async test(ctrl, env) { + const response = await env.STRICT.fetch('http://example.com/'); + if ((await response.text()) !== 'ok') { + throw new Error('fetch through a strict Proxy entrypoint failed'); + } + }, +}; diff --git a/src/workerd/api/tests/exported-handler-proxy-test.wd-test b/src/workerd/api/tests/exported-handler-proxy-test.wd-test new file mode 100644 index 00000000000..dc598687c86 --- /dev/null +++ b/src/workerd/api/tests/exported-handler-proxy-test.wd-test @@ -0,0 +1,14 @@ +using Workerd = import "/workerd/workerd.capnp"; + +const unitTests :Workerd.Config = ( + services = [( + name = "exported-handler-proxy-test", + worker = ( + modules = [(name = "worker", esModule = embed "exported-handler-proxy-test.js")], + bindings = [( + name = "STRICT", + service = (name = "exported-handler-proxy-test", entrypoint = "Strict"), + )], + ), + )], +); diff --git a/src/workerd/io/BUILD.bazel b/src/workerd/io/BUILD.bazel index 069a9059f8c..cd39ac1a7d4 100644 --- a/src/workerd/io/BUILD.bazel +++ b/src/workerd/io/BUILD.bazel @@ -72,6 +72,7 @@ wd_cc_library( implementation_deps = [ ":async-trace-perfetto", "//src/per_isolate", + "//src/rust/histogram", "//src/rust/jsg", "//src/workerd/api:compression", "//src/workerd/api:crypto-crc-impl", @@ -418,6 +419,7 @@ wd_cc_library( hdrs = ["worker-interface.h"], visibility = ["//visibility:public"], deps = [ + ":bench_capnp", ":frankenvalue_capnp", ":trace", ":worker-interface_capnp", diff --git a/src/workerd/io/worker-entrypoint.c++ b/src/workerd/io/worker-entrypoint.c++ index 2b33162758b..427cdbb552d 100644 --- a/src/workerd/io/worker-entrypoint.c++ +++ b/src/workerd/io/worker-entrypoint.c++ @@ -88,6 +88,8 @@ class WorkerEntrypoint final: public WorkerInterface { kj::Promise runAlarm(kj::Date scheduledTime, uint32_t retryCount) override; kj::Promise> abandonAlarm(kj::Date scheduledTime) override; kj::Promise test() override; + kj::Promise bench( + bench::BenchParams::Reader params, bench::BenchReport::Group::Builder report) override; kj::Promise customEvent(kj::Own event) override; private: @@ -1099,6 +1101,39 @@ kj::Promise> WorkerEntrypoint::abandonAlarm(kj::Date schedul return persistent.abandonAlarm(scheduledTime); } +kj::Promise WorkerEntrypoint::bench( + bench::BenchParams::Reader params, bench::BenchReport::Group::Builder report) { + TRACE_EVENT("workerd", "WorkerEntrypoint::bench()"); + auto incomingRequest = + kj::mv(KJ_REQUIRE_NONNULL(this->incomingRequest, "bench() can only be called once")); + this->incomingRequest = kj::none; + auto& context = incomingRequest->getContext(); + KJ_IF_SOME(t, incomingRequest->getWorkerTracer()) { + t.setEventInfo(*incomingRequest, tracing::CustomEventInfo()); + } + + incomingRequest->delivered(); + // Background work that the cases leave behind may continue; it isn't waited for. + KJ_DEFER(incomingRequest->drain(waitUntilTasks, kj::mv(incomingRequest))); + + // Unlike test(), this awaits the handler directly rather than through finishScheduled(), since + // the report needs the handler's result. + auto result = co_await context.run( + [params, entrypointName = entrypointName.clone(), versionInfo = kj::mv(versionInfo), + props = kj::mv(props)](Worker::Lock& lock, IoContext& context) mutable { + TRACE_EVENT("workerd", "WorkerEntrypoint::bench() run"); + jsg::AsyncContextFrame::StorageScope traceScope = context.makeAsyncTraceScope(lock); + jsg::AsyncContextFrame::StorageScope userTraceScope = context.makeUserAsyncTraceScope(lock); + + return context.awaitJs(lock, + lock.getGlobalScope().bench(lock, + lock.getExportedHandler( + asPtr(entrypointName), kj::mv(versionInfo), kj::mv(props), context.getActor()), + params)); + }); + api::fillBenchReport(result, report); +} + kj::Promise WorkerEntrypoint::test() { TRACE_EVENT("workerd", "WorkerEntrypoint::test()"); auto incomingRequest = diff --git a/src/workerd/io/worker-interface.h b/src/workerd/io/worker-interface.h index f4a5c15c208..0430bc07d64 100644 --- a/src/workerd/io/worker-interface.h +++ b/src/workerd/io/worker-interface.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -138,6 +139,15 @@ class WorkerInterface: public kj::HttpService { } // TODO(someday): Produce a structured test report? + // Run the bench handler with `params`, and write its results to `report`, which the caller owns + // and must keep alive until the promise settles. Failures in user code are recorded in the + // report; a rejected promise means the handler couldn't be run at all. Only `workerd bench` + // calls this. + virtual kj::Promise bench( + bench::BenchParams::Reader params, bench::BenchReport::Group::Builder report) { + return KJ_EXCEPTION(UNIMPLEMENTED, "this service doesn't support bench()"); + } + // These two constants are shared by multiple systems that invoke alarms (the production // implementation, and the preview implementation), whose code live in completely different // places. We end up defining them here mostly for lack of a better option. diff --git a/src/workerd/io/worker.c++ b/src/workerd/io/worker.c++ index 92f6715b694..cead6ef4ba8 100644 --- a/src/workerd/io/worker.c++ +++ b/src/workerd/io/worker.c++ @@ -544,6 +544,11 @@ jsg::Optional> Worker::Api::getCtxCacheProperty(jsg: return kj::none; } +const jsg::TypeHandler& Worker::Api::getBenchTypeHandler( + jsg::Lock& lock) const { + KJ_UNIMPLEMENTED("bench handlers are only supported under `workerd bench`"); +} + struct Worker::Impl { kj::Maybe> context; diff --git a/src/workerd/io/worker.h b/src/workerd/io/worker.h index 1f06b562dfc..80afe7c4475 100644 --- a/src/workerd/io/worker.h +++ b/src/workerd/io/worker.h @@ -50,6 +50,7 @@ class ServiceWorkerGlobalScope; struct ExportedHandler; struct CryptoAlgorithm; struct QueueExportedHandler; +struct BenchExportedHandler; class WebSocket; class WebSocketRequestResponsePair; class CacheContext; @@ -692,6 +693,9 @@ class Worker::Api { jsg::Lock& lock) const = 0; virtual const jsg::TypeHandler& getQueueTypeHandler( jsg::Lock& lock) const = 0; + // Only `workerd bench` delivers the bench event, so the default implementation throws. + virtual const jsg::TypeHandler& getBenchTypeHandler( + jsg::Lock& lock) const; // Look up crypto algorithms by case-insensitive name. This can be used to extend the set of // WebCrypto algorithms supported. diff --git a/src/workerd/server/factory/worker-factory.c++ b/src/workerd/server/factory/worker-factory.c++ index a447568d5a8..736d8c7cd9e 100644 --- a/src/workerd/server/factory/worker-factory.c++ +++ b/src/workerd/server/factory/worker-factory.c++ @@ -462,6 +462,10 @@ class RequestObserverWithTracer final: public RequestObserver, public WorkerInte kj::Promise test() override { co_return co_await observe(KJ_ASSERT_NONNULL(inner).test()); } + kj::Promise bench( + bench::BenchParams::Reader params, bench::BenchReport::Group::Builder report) override { + co_return co_await observe(KJ_ASSERT_NONNULL(inner).bench(params, report)); + } kj::Promise customEvent(kj::Own event) override { auto result = co_await observe(KJ_ASSERT_NONNULL(inner).customEvent(kj::mv(event))); if (outcome == EventOutcome::OK) outcome = result.outcome; @@ -1022,6 +1026,10 @@ class AccessHeaderExtractor final: public WorkerInterface { kj::Promise test() override { co_return co_await getInner().test(); } + kj::Promise bench( + bench::BenchParams::Reader params, bench::BenchReport::Group::Builder report) override { + co_await getInner().bench(params, report); + } kj::Promise customEvent(kj::Own event) override { co_return co_await getInner().customEvent(kj::mv(event)); } diff --git a/src/workerd/server/workerd-api.c++ b/src/workerd/server/workerd-api.c++ index 3ab5e2e519b..709c8161257 100644 --- a/src/workerd/server/workerd-api.c++ +++ b/src/workerd/server/workerd-api.c++ @@ -359,6 +359,11 @@ const jsg::TypeHandler& WorkerdApi::getQueueTypeHandl return kj::downcast(lock).getTypeHandler(); } +const jsg::TypeHandler& WorkerdApi::getBenchTypeHandler( + jsg::Lock& lock) const { + return kj::downcast(lock).getTypeHandler(); +} + jsg::JsObject WorkerdApi::wrapExecutionContext( jsg::Lock& lock, jsg::Ref ref) const { return jsg::JsObject( diff --git a/src/workerd/server/workerd-api.h b/src/workerd/server/workerd-api.h index 04614dce24b..e040a80e323 100644 --- a/src/workerd/server/workerd-api.h +++ b/src/workerd/server/workerd-api.h @@ -66,6 +66,8 @@ class WorkerdApi final: public Worker::Api { jsg::Lock& lock) const override; const jsg::TypeHandler& getQueueTypeHandler( jsg::Lock& lock) const override; + const jsg::TypeHandler& getBenchTypeHandler( + jsg::Lock& lock) const override; jsg::JsObject wrapExecutionContext( jsg::Lock& lock, jsg::Ref ref) const override; const jsg::IsolateObserver& getObserver() const override; From e5f1bf8def4e988c90830ce5cd183dc320504e30 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 19:15:23 +0000 Subject: [PATCH 12/24] Add run::bench() Runs the bench handlers of the entrypoints that match the service and entrypoint patterns, as run::test() runs test handlers, adding a group to a BenchReport for each. Progress is logged like test()'s, with [ BENCH ], [ DONE ], [ FAIL ], and [ SKIP ] lines. The Rust server owns the report (bench.rs). The worker factory's new worker_bench() runs one handler and returns the group it filled in as an encoded message, which the server copies into the report under the group's name. A handler that can't be started or run gets the error as its group's. The in-process server gains bench() for server-test. The startup and entrypoint selection that test() did inline move to start_local(), and running the handlers as a task of the KJ loop beside the listeners moves to Running::alongside(); both commands use them. Only plain-object exports have bench handlers. In bench mode, validation leaves `bench` out of the methods it reports for WorkerEntrypoint and Workflow classes, whose methods are RPC methods, so an unrelated RPC method named `bench` isn't run as a benchmark. Production validation doesn't change. --- src/workerd/io/worker.c++ | 9 + src/workerd/server/factory/worker-factory.c++ | 11 + src/workerd/server/factory/worker-factory.h | 2 + src/workerd/server/server-test.c++ | 202 ++++++++++++++++++ src/workerd/server/server/BUILD.bazel | 1 + src/workerd/server/server/bench.rs | 147 +++++++++++++ src/workerd/server/server/bridge.rs | 23 +- src/workerd/server/server/in_process.rs | 24 +++ src/workerd/server/server/lib.rs | 3 +- src/workerd/server/server/run.rs | 184 +++++++++++----- 10 files changed, 546 insertions(+), 60 deletions(-) create mode 100644 src/workerd/server/server/bench.rs diff --git a/src/workerd/io/worker.c++ b/src/workerd/io/worker.c++ index cead6ef4ba8..7386dafef5d 100644 --- a/src/workerd/io/worker.c++ +++ b/src/workerd/io/worker.c++ @@ -2946,6 +2946,11 @@ void Worker::Lock::validateHandlers(ValidationErrorReporter& errorReporter) { jsg::JsValue proto = ctor.get(js, "prototype"); collectMethodsFromPrototypeChain(proto, seenNames); }); + if (isBenchMode()) { + // `workerd bench` runs only plain objects' bench() handlers. A class's methods are RPC + // methods, so one named `bench` isn't a handler. + seenNames.eraseMatch("bench"_kj); + } errorReporter.addEntrypoint(entrypointName, KJ_MAP(n, seenNames) { return kj::mv(n); }); } @@ -2960,6 +2965,10 @@ void Worker::Lock::validateHandlers(ValidationErrorReporter& errorReporter) { jsg::JsValue proto = ctor.get(js, "prototype"); collectMethodsFromPrototypeChain(proto, seenNames); }); + if (isBenchMode()) { + // As for WorkerEntrypoint classes, a method named `bench` isn't a handler. + seenNames.eraseMatch("bench"_kj); + } errorReporter.addWorkflowClass(entrypointName, KJ_MAP(n, seenNames) { return kj::mv(n); }); } else { diff --git a/src/workerd/server/factory/worker-factory.c++ b/src/workerd/server/factory/worker-factory.c++ index 736d8c7cd9e..06b5b3b484c 100644 --- a/src/workerd/server/factory/worker-factory.c++ +++ b/src/workerd/server/factory/worker-factory.c++ @@ -1175,6 +1175,17 @@ kj::Own worker_start_request(const CompiledWorker& worker, kj::mv(channels), kj::mv(tails), kj::none); } +kj::Promise<::rust::Vec> worker_bench( + kj::Own worker, ::rust::Slice params) { + capnp::FlatArrayMessageReader paramsMessage(asWords(kj::from(params))); + capnp::MallocMessageBuilder groupMessage; + co_await worker->bench(paramsMessage.getRoot(), + groupMessage.initRoot()); + auto words = capnp::messageToFlatArray(groupMessage); + co_return kj::arrayPtr(reinterpret_cast(words.begin()), words.size()) + .as(); +} + // ======================================================================================= // Compiling a worker diff --git a/src/workerd/server/factory/worker-factory.h b/src/workerd/server/factory/worker-factory.h index 73fa5896fab..430a61ca943 100644 --- a/src/workerd/server/factory/worker-factory.h +++ b/src/workerd/server/factory/worker-factory.h @@ -203,6 +203,8 @@ kj::Own worker_start_request(const CompiledWorker& worker, ::rust::Box tails); kj::Own worker_interface_attach( kj::Own inner, ::rust::Box keep); +kj::Promise<::rust::Vec> worker_bench( + kj::Own worker, ::rust::Slice params); kj::Own worker_new_actor(const CompiledWorker& worker, ::rust::Str className, kj::Maybe> props, diff --git a/src/workerd/server/server-test.c++ b/src/workerd/server/server-test.c++ index 0cc45c22d6c..c4674fb982b 100644 --- a/src/workerd/server/server-test.c++ +++ b/src/workerd/server/server-test.c++ @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -427,6 +428,29 @@ class Server { .attach(reports(server)); } + // `workerd bench`, whose report's groups go to `report`. + kj::Promise bench(jsg::V8System& v8System, + config::Config::Reader config, + bench::BenchParams::Reader params, + bench::BenchReport::Builder report, + kj::StringPtr servicePattern, + kj::StringPtr entrypointPattern) { + auto& server = start(v8System, config); + capnp::MallocMessageBuilder paramsMessage; + paramsMessage.setRoot(params); + auto paramsWords = capnp::messageToFlatArray(paramsMessage); + auto outcome = co_await server + .bench(servicePattern.as(), + entrypointPattern.as(), + kj::arrayPtr(reinterpret_cast(paramsWords.begin()), + paramsWords.size()) + .as()) + .attach(reports(server)); + capnp::FlatArrayMessageReader reportMessage(asWords(kj::from(outcome.report))); + report.setGroups(reportMessage.getRoot().getGroups()); + co_return outcome.passed; + } + // The two ends of a connection to `addr`: the server's listener or the test take what the // other makes. kj::Own connect(kj::StringPtr addr) { @@ -5794,6 +5818,184 @@ KJ_TEST("Server: cache name is passed through to service") { } } +// ======================================================================================= +// Test the bench command + +KJ_TEST("Server: bench command") { + setBenchMode(); + + // With no warmup and a fixed batch, each case's function is called exactly batch * samples + // times, which the "verify" case checks. + kj::StringPtr config = R"(( + services = [ + ( name = "hello", + worker = ( + compatibilityDate = "2024-01-01", + modules = [ + ( name = "main.js", + esModule = + `import {WorkerEntrypoint, WorkflowEntrypoint} from "cloudflare:workers"; + `let calls = {sync: 0, async: 0, setup: 0, teardown: 0, state: 0, bareTeardown: 0, + ` asyncTeardown: 0, overlaps: 0}; + `export default { + ` fetch() { return new Response("ok"); }, + ` bench(b, env, ctx) { + ` if (b.options.quick || b.options.filter !== undefined) { + ` throw new Error("unexpected options"); + ` } + ` const fixed = {warmup: 0, batch: 3, samples: 4}; + ` b.run("sync", () => { calls.sync++; return b.blackBox(1); }, fixed); + ` b.run("async", async () => { calls.async++; }, {...fixed, warmup: "0ms"}); + ` b.run("state", (s) => { if (s.n === 7) calls.state++; }, { + ` ...fixed, + ` setup() { calls.setup++; return Promise.resolve({n: 7}); }, + ` teardown(s) { if (s.n === 7) calls.teardown++; }, + ` }); + ` b.run("bare teardown", () => {}, { + ` ...fixed, + ` teardown(s) { if (s === undefined) calls.bareTeardown++; }, + ` }); + ` // The teardown's subrequest takes several turns of the event loop, so a batch + ` // that started before it finished would see tearingDown set. + ` let tearingDown = false; + ` b.run("async teardown", () => { if (tearingDown) calls.overlaps++; }, { + ` ...fixed, + ` async teardown() { + ` tearingDown = true; + ` await (await env.SELF.fetch("http://x/")).text(); + ` tearingDown = false; + ` calls.asyncTeardown++; + ` }, + ` }); + ` b.run("skipped", () => { throw new Error("ran"); }, {skip: true}); + ` b.run("throws", () => { throw new Error("boom"); }, fixed); + ` b.run("late", () => { b.run("x", () => {}); }, fixed); + ` b.run("verify", () => { + ` const expected = {sync: 12, async: 12, setup: 4, teardown: 4, state: 12, + ` bareTeardown: 4, asyncTeardown: 4, overlaps: 0}; + ` if (JSON.stringify(calls) !== JSON.stringify(expected)) { + ` throw new Error("wrong call counts: " + JSON.stringify(calls)); + ` } + ` }, {warmup: 0, batch: 1, samples: 1}); + ` } + `} + `export let broken = { + ` bench(b) { throw new TypeError("not today"); } + `} + `export let empty = { + ` bench(b) {} + `} + `export let notBench = { + ` async fetch(req, env, ctx) { return new Response("ok"); } + `} + `// Classes' `bench` methods are RPC methods, not handlers. + `export class Rpc extends WorkerEntrypoint { + ` bench(x) { throw new Error("ran as a handler"); } + `} + `export class Flow extends WorkflowEntrypoint { + ` bench(x) { throw new Error("ran as a handler"); } + `} + ) + ], + bindings = [( name = "SELF", service = "hello" )] + ) + ), + ] + ))"_kj; + + auto makeParams = [](capnp::MallocMessageBuilder& message, kj::StringPtr caseFilter) { + auto params = message.initRoot(); + params.setCaseFilter(caseFilter); + return params.asReader(); + }; + + { + TestServer test(config); + capnp::MallocMessageBuilder paramsMessage; + capnp::MallocMessageBuilder reportMessage; + auto report = reportMessage.initRoot(); + KJ_EXPECT_LOG(DBG, "[ BENCH ] hello"); + KJ_EXPECT_LOG(DBG, "[ SKIP ] hello/skipped"); + KJ_EXPECT_LOG(DBG, "[ FAIL ] hello/throws: Error: boom"); + KJ_EXPECT_LOG(DBG, "[ FAIL ] hello/late: Error: BenchController.run() must be called by"); + KJ_EXPECT_LOG(DBG, "[ FAIL ] hello ("); + KJ_EXPECT(!test.server + .bench(v8System, *test.config, makeParams(paramsMessage, ""), report, "hello", + "default") + .wait(test.ws)); + + auto groups = report.getGroups(); + KJ_ASSERT(groups.size() == 1); + KJ_EXPECT(groups[0].getName() == "hello"); + KJ_EXPECT(!groups[0].hasError()); + auto cases = groups[0].getCases(); + KJ_ASSERT(cases.size() == 9); + + using Status = bench::BenchReport::Case::Status; + kj::StringPtr names[] = {"sync", "async", "state", "bare teardown", "async teardown", "skipped", + "throws", "late", "verify"}; + Status statuses[] = {Status::OK, Status::OK, Status::OK, Status::OK, Status::OK, + Status::SKIPPED, Status::FAILED, Status::FAILED, Status::OK}; + for (auto i: kj::indices(names)) { + KJ_EXPECT(cases[i].getName() == names[i]); + KJ_EXPECT(cases[i].getStatus() == statuses[i], names[i], cases[i].getError()); + } + + for (auto i: kj::zeroTo(3)) { + auto c = cases[i]; + KJ_EXPECT(c.getIterationsPerSample() == 3); + KJ_EXPECT(c.getWallNs().getSamples().size() == 4); + KJ_EXPECT(c.getCpuNs().getSamples().size() == 4); + auto wall = c.getWallNs(); + KJ_EXPECT(wall.getMin() > 0); + KJ_EXPECT(wall.getMin() <= wall.getMedian() && wall.getMedian() <= wall.getMax()); + KJ_EXPECT(wall.getMedianLow() <= wall.getMedian()); + KJ_EXPECT(wall.getMedian() <= wall.getMedianHigh()); + } + KJ_EXPECT(!cases[5].hasWallNs()); + KJ_EXPECT(kj::StringPtr(cases[6].getError()).startsWith("Error: boom")); + } + + { + // Handlers that fail or register nothing, and a case filter. + TestServer test(config); + capnp::MallocMessageBuilder paramsMessage; + capnp::MallocMessageBuilder reportMessage; + auto report = reportMessage.initRoot(); + KJ_EXPECT_LOG(DBG, "[ BENCH ] hello:broken"); + KJ_EXPECT_LOG(DBG, "[ FAIL ] hello:broken: TypeError: not today"); + KJ_EXPECT_LOG(DBG, "[ BENCH ] hello:empty"); + KJ_EXPECT_LOG(DBG, "[ FAIL ] hello:empty: bench() did not register any cases."); + KJ_EXPECT(!test.server + .bench(v8System, *test.config, makeParams(paramsMessage, "nothing"), report, + "hello", "*") + .wait(test.ws)); + + // The default entrypoint's options check fails because a filter is set. The Rpc and Flow + // classes' `bench` methods aren't handlers, so they have no groups. + kj::HashMap errors; + for (auto group: report.getGroups()) { + KJ_EXPECT(group.getCases().size() == 0, group.getName()); + errors.insert(group.getName(), group.getError()); + } + KJ_EXPECT(errors.size() == 3); + KJ_EXPECT(KJ_ASSERT_NONNULL(errors.find("hello"_kj)).startsWith("Error: unexpected options")); + } + + { + TestServer test(config); + capnp::MallocMessageBuilder paramsMessage; + capnp::MallocMessageBuilder reportMessage; + auto report = reportMessage.initRoot(); + KJ_EXPECT_LOG(ERROR, "No benchmarks found!"); + KJ_EXPECT(!test.server + .bench(v8System, *test.config, makeParams(paramsMessage, ""), report, "hello", + "notBench") + .wait(test.ws)); + KJ_EXPECT(report.getGroups().size() == 0); + } +} + // ======================================================================================= KJ_TEST("Server: JS RPC over HTTP connections") { diff --git a/src/workerd/server/server/BUILD.bazel b/src/workerd/server/server/BUILD.bazel index 73add32e183..485b787440e 100644 --- a/src/workerd/server/server/BUILD.bazel +++ b/src/workerd/server/server/BUILD.bazel @@ -32,6 +32,7 @@ wd_rust_crate( "//src/rust/cxx/kj-rs-tokio", "//src/rust/kj", "//src/rust/worker", + "//src/workerd/io:bench_capnp_rust", "//src/workerd/server:compiled-bindings_capnp_rust", "//src/workerd/server:workerd_capnp_rust", "@crates_vendor//:base64", diff --git a/src/workerd/server/server/bench.rs b/src/workerd/server/server/bench.rs new file mode 100644 index 00000000000..8a53379b99c --- /dev/null +++ b/src/workerd/server/server/bench.rs @@ -0,0 +1,147 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! `workerd bench`'s report: a `BenchReport` message (bench.capnp) with a group for each +//! `bench()` handler that `run::bench` runs. +//! +//! The worker factory runs a handler (`worker_bench`) and hands back the group the handler filled +//! in as an encoded message, which the report copies in under the group's name. + +use std::time::Duration; +use std::time::Instant; + +use bench_capnp::bench_report; +use capnp::message::ReaderOptions; +use capnp::message::TypedBuilder; +use kj_rs::KjMaybe; + +use crate::Result; +use crate::bridge::ffi; +use crate::channels::Channel; +use crate::config::capnp_error; +use crate::config::text; +use crate::log; + +/// A `BenchReport` being filled in, a group at a time. +pub struct Report { + message: TypedBuilder, + /// How many of the groups the report has room for are filled in. + filled: u32, + /// Whether a handler, or one of its cases, failed. + failed: bool, +} + +impl Report { + /// A report with room for `groups` groups. + #[must_use] + pub fn new(groups: u32) -> Self { + let mut message = TypedBuilder::::new_default(); + message.init_root().init_groups(groups); + Self { + message, + filled: 0, + failed: false, + } + } + + /// Whether at least one handler ran and nothing failed. + #[must_use] + pub fn passed(&self) -> bool { + self.filled > 0 && !self.failed + } + + /// The report, encoded (segment table, then segments) in words. + #[must_use] + pub fn to_words(&self) -> Vec { + capnp::serialize::write_message_to_words(self.message.borrow_inner()) + .chunks_exact(8) + .map(|chunk| u64::from_ne_bytes(chunk.try_into().unwrap_or_default())) + .collect() + } + + /// Runs the `bench()` handler behind `channel` with `params` (an encoded `BenchParams` + /// message) as the report's next group, named `name`. A handler that cannot be started or + /// run has the error as its group's. + /// + /// Progress is logged as `workerd test` logs it, at debug level: `[ BENCH ]` as the handler + /// starts; `[ SKIP ]` and `[ FAIL ]` for its cases; then `[ DONE ]` with the time it took, or + /// `[ FAIL ]`. + pub async fn run_group( + &mut self, + channel: &dyn Channel, + name: &str, + params: &[u64], + ) -> Result<()> { + log::debug(&format!("[ BENCH ] {name}")); + let start = Instant::now(); + let metadata = ffi::new_request_metadata(KjMaybe::None, KjMaybe::None); + let result = match channel.start_request(metadata) { + Ok(worker) => ffi::worker_bench(worker, params) + .await + .map_err(crate::Error::from), + Err(error) => Err(error), + }; + let duration = start.elapsed(); + + let index = self.filled; + self.filled += 1; + let root = self.message.get_root().map_err(capnp_error)?; + let mut groups = root.get_groups().map_err(capnp_error)?; + match result { + Ok(words) => { + let message = read_words(&words).map_err(capnp_error)?; + let group = message.get_root().map_err(capnp_error)?; + groups.set_with_caveats(index, group).map_err(capnp_error)?; + } + Err(error) => groups.reborrow().get(index).set_error(error.description()), + } + let mut group = groups.get(index); + group.set_name(name); + if log_outcome(group.into_reader(), name, duration)? { + self.failed = true; + } + Ok(()) + } +} + +/// Logs how the group `name` went: `[ SKIP ]` and `[ FAIL ]` for its cases, then `[ DONE ]` with +/// `duration`, or `[ FAIL ]`. Whether it failed. +fn log_outcome( + group: bench_report::group::Reader<'_>, + name: &str, + duration: Duration, +) -> Result { + let mut failed = group.has_error(); + for case in group.get_cases().map_err(capnp_error)? { + match case.get_status().map_err(|e| capnp_error(e.into()))? { + bench_report::case::Status::Ok => {} + bench_report::case::Status::Skipped => { + log::debug(&format!("[ SKIP ] {name}/{}", text(case.get_name())?)); + } + bench_report::case::Status::Failed => { + let case_name = text(case.get_name())?; + let error = text(case.get_error())?; + log::debug(&format!("[ FAIL ] {name}/{case_name}: {error}")); + failed = true; + } + } + } + if group.has_error() { + log::debug(&format!("[ FAIL ] {name}: {}", text(group.get_error())?)); + } else { + let verdict = if failed { "FAIL" } else { "DONE" }; + log::debug(&format!("[ {verdict} ] {name} ({duration:?})")); + } + Ok(failed) +} + +/// An encoded message (segment table, then segments) in words, read. The messages are the +/// factory's, as large as a handler's results, so their size is not limited. +fn read_words( + words: &[u64], +) -> capnp::Result> { + let bytes: Vec = words.iter().flat_map(|word| word.to_ne_bytes()).collect(); + let options = *ReaderOptions::new().traversal_limit_in_words(None); + capnp::serialize::read_message(bytes.as_slice(), options) +} diff --git a/src/workerd/server/server/bridge.rs b/src/workerd/server/server/bridge.rs index 91a99281e08..7bee90134a4 100644 --- a/src/workerd/server/server/bridge.rs +++ b/src/workerd/server/server/bridge.rs @@ -147,6 +147,13 @@ pub mod ffi { message: String, } + /// The in-process server's `workerd bench`: whether it passed, and its encoded + /// `BenchReport` (bench.capnp) in words. + struct BenchOutcome { + passed: bool, + report: Vec, + } + /// Where an actor keeps its data. struct ActorStorageSpec { /// Durable: keep state across requests. Ephemeral: no storage at all. @@ -334,6 +341,11 @@ pub mod ffi { inner: KjOwn, keep: Box, ) -> KjOwn; + /// Runs the `bench()` handler of the request `worker` with `params`, an encoded + /// `BenchParams` message (bench.capnp) in words. Returns the `BenchReport.Group` the + /// handler filled in, encoded, without its name. The failures of the handler's code are + /// in the group; an error means the handler could not be run at all. + async fn worker_bench(worker: KjOwn, params: &[u64]) -> Result>; /// Constructs an actor of `class_name` whose storage lives in `storage`. `hooks` is the /// actor's way back to the server; `hibernation_manager` is the evicted predecessor's, /// whose WebSockets the actor adopts. `container` gives the actor its container's Docker @@ -607,8 +619,9 @@ pub mod ffi { /// The server run inside a test process (in_process.rs), on a factory the test built. /// `run` is `workerd serve` until `drain`, with `--debug-port` if `debug_port` is not - /// empty; `test` is `workerd test`. `next_report` is the next config error or warning. - /// `connect` and `accept` are the two ends of a `loopback:` name. + /// empty; `test` is `workerd test`; `bench` is `workerd bench` with `params`, an encoded + /// `BenchParams` message. `next_report` is the next config error or warning. `connect` + /// and `accept` are the two ends of a `loopback:` name. type InProcessServer; fn new_in_process_server(factory: KjOwn) -> Box; async unsafe fn run<'a>(self: &'a InProcessServer, debug_port: &'a str) -> Result<()>; @@ -617,6 +630,12 @@ pub mod ffi { service_pattern: &'a str, entrypoint_pattern: &'a str, ) -> Result; + async unsafe fn bench<'a>( + self: &'a InProcessServer, + service_pattern: &'a str, + entrypoint_pattern: &'a str, + params: &'a [u64], + ) -> Result; fn drain(self: &InProcessServer); async unsafe fn next_report<'a>(self: &'a InProcessServer) -> ConfigReport; fn connect(self: &InProcessServer, name: &str) -> Result>; diff --git a/src/workerd/server/server/in_process.rs b/src/workerd/server/server/in_process.rs index 16c44610930..337446fb511 100644 --- a/src/workerd/server/server/in_process.rs +++ b/src/workerd/server/server/in_process.rs @@ -19,6 +19,7 @@ use tokio::sync::Notify; use tokio::sync::mpsc; use crate::Result; +use crate::bench; use crate::bridge::ffi; use crate::config::Factory; use crate::config::Reporter; @@ -98,6 +99,29 @@ impl InProcessServer { Ok(passed == Some(true)) } + /// `workerd bench`, with `params` (an encoded `BenchParams` message). + pub async fn bench( + &self, + service_pattern: &str, + entrypoint_pattern: &str, + params: &[u64], + ) -> Result { + let results = crate::run::bench( + Rc::clone(&self.factory), + RunOptions::default(), + self.reporter(), + service_pattern, + entrypoint_pattern, + params.to_vec(), + ) + .await? + .unwrap_or_else(|| bench::Report::new(0)); + Ok(ffi::BenchOutcome { + passed: results.passed(), + report: results.to_words(), + }) + } + pub fn drain(&self) { self.drain.notify_one(); } diff --git a/src/workerd/server/server/lib.rs b/src/workerd/server/server/lib.rs index a8dac9f9dc7..04f9aeb34ab 100644 --- a/src/workerd/server/server/lib.rs +++ b/src/workerd/server/server/lib.rs @@ -8,9 +8,10 @@ //! runs the event loop. Everything that touches the isolate is delegated to the C++ worker //! factory (worker-factory.h) through the bridge in `bridge`; the server owns the rest: //! config interpretation, bindings and channel numbering, services, listeners, actors, dynamic -//! workers, drain and the test runner. +//! workers, drain and the test and bench runners. pub mod actor; +pub mod bench; pub mod bindings; pub mod bridge; pub mod channels; diff --git a/src/workerd/server/server/run.rs b/src/workerd/server/server/run.rs index ad809a49f66..fd393393a84 100644 --- a/src/workerd/server/server/run.rs +++ b/src/workerd/server/server/run.rs @@ -2,8 +2,8 @@ // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 -//! `workerd serve` and `workerd test`: bind the sockets, build the service graph, listen, and -//! either wait for the drain signal or run the tests. +//! `workerd serve`, `workerd test` and `workerd bench`: bind the sockets, build the service +//! graph, listen, and either wait for the drain signal or run the tests or benchmarks. use std::collections::HashMap; use std::future::Future; @@ -24,7 +24,9 @@ use workerd_capnp::service; use workerd_capnp::socket; use crate::Result; +use crate::bench; use crate::bindings::Designator; +use crate::bindings::len_u32; use crate::bridge::ffi; use crate::channels::Channel; use crate::config::Factory; @@ -374,8 +376,77 @@ pub async fn run( Ok(ffi::factory_shutdown_containers(server.factory().raw()).await?) } -/// `workerd test`: runs the `test()` handler of every entrypoint matching the patterns (globs -/// as the `glob` crate reads them, matched against the whole name). +/// The entrypoints `workerd test` or `workerd bench` runs a handler of: the channel of each, +/// and its name ("service" or "service:entrypoint"). +type Targets = Vec<(Rc, String)>; + +/// Starts the server for `workerd test` or `workerd bench`, and selects the entrypoints that +/// match the patterns (globs as the `glob` crate reads them, matched against the whole name) and +/// export `handler`. `None` for a config `report` refuses, whose handlers do not run. +async fn start_local( + factory: Rc, + options: RunOptions, + report: Reporter, + (service_pattern, entrypoint_pattern): (&str, &str), + handler: &str, +) -> Result> { + let glob = |pattern: &str| { + glob::Pattern::new(pattern).map_err(|e| kj::failed!("{handler} filter \"{pattern}\": {e}")) + }; + let (services, entrypoints) = (glob(service_pattern)?, glob(entrypoint_pattern)?); + let Some(running) = start(factory, options, report, true).await? else { + return Ok(None); + }; + + let mut targets = Vec::new(); + for (name, service) in running.server.services() { + if !services.matches(name) { + continue; + } + let Some(worker) = service.as_worker() else { + continue; + }; + if worker.has_handler(None, handler) && entrypoints.matches("default") { + targets.push((service.channel(), name.to_owned())); + } + for entrypoint in worker.entrypoint_names() { + if entrypoints.matches(entrypoint) + && worker.has_handler(Some(entrypoint), handler) + && let Some(channel) = worker.entrypoint(Some(entrypoint), None, false) + { + targets.push((channel, format!("{name}:{entrypoint}"))); + } + } + } + Ok(Some((running, targets))) +} + +impl Running { + /// Runs `handlers` while the listeners serve; a listener failing meanwhile is fatal, as in + /// `run()`. + /// + /// `handlers` runs as a task of the KJ loop: each handler starts in the turn after the one + /// before it returned, ahead of the events that one left queued (its tail workers' + /// deliveries), where this future would resume only once the loop is idle. The task should + /// hold the channels, not the server, whose drop clears the tasks. + async fn alongside( + &mut self, + handlers: impl Future> + 'static, + ) -> Result { + let handlers = self.server.factory().spawn(handlers); + let serving = std::pin::pin!(serve_listeners(&mut self.listeners)); + match futures::future::select(handlers, serving).await { + Either::Left((result, _)) => result, + Either::Right((result, handlers)) => { + result?; + handlers.await + } + } + } +} + +/// `workerd test`: runs the `test()` handler of every entrypoint matching the patterns (see +/// `start_local`). /// /// The sockets listen meanwhile (tests can configure them). True if every test passed and there /// was at least one; `None` for a config `report` refuses, whose tests do not run. @@ -386,15 +457,9 @@ pub async fn test( service_pattern: &str, entrypoint_pattern: &str, ) -> Result> { - let glob = |pattern: &str| { - glob::Pattern::new(pattern).map_err(|e| kj::failed!("test filter \"{pattern}\": {e}")) - }; - let (services, entrypoints) = (glob(service_pattern)?, glob(entrypoint_pattern)?); - let Some(Running { - server, - mut listeners, - drain: _drain, - }) = start(factory, options, report, true).await? + let patterns = (service_pattern, entrypoint_pattern); + let Some((mut running, cases)) = + start_local(factory, options, report, patterns, "test").await? else { return Ok(None); }; @@ -413,54 +478,59 @@ pub async fn test( Ok::(result) }; - let mut cases = Vec::new(); - for (name, service) in server.services() { - if !services.matches(name) { - continue; - } - let Some(worker) = service.as_worker() else { - continue; - }; - if worker.has_handler(None, "test") && entrypoints.matches("default") { - cases.push((service.channel(), name.to_owned())); - } - for entrypoint in worker.entrypoint_names() { - if entrypoints.matches(entrypoint) - && worker.has_handler(Some(entrypoint), "test") - && let Some(channel) = worker.entrypoint(Some(entrypoint), None, false) - { - cases.push((channel, format!("{name}:{entrypoint}"))); + let (passed, failed) = running + .alongside(async move { + let (mut passed, mut failed) = (0u32, 0u32); + for (channel, name) in cases { + if run_one(channel, name).await? { + passed += 1; + } else { + failed += 1; + } } - } - } - // The tests run as a task of the KJ loop: a test starts in the turn after the one before it - // returned, ahead of the events that one left queued (its tail workers' deliveries), where - // this future would resume only once the loop is idle. The task holds the channels, not the - // server, whose drop clears the tasks. - let tests = server.factory().spawn(async move { - let (mut passed, mut failed) = (0u32, 0u32); - for (channel, name) in cases { - if run_one(channel, name).await? { - passed += 1; - } else { - failed += 1; - } - } - Ok::<_, crate::Error>((passed, failed)) - }); - - // A listener failing while the tests run is fatal, as in `run()`. - let serving = std::pin::pin!(serve_listeners(&mut listeners)); - let (passed, failed) = match futures::future::select(tests, serving).await { - Either::Left((result, _)) => result?, - Either::Right((result, tests)) => { - result?; - tests.await? - } - }; + Ok((passed, failed)) + }) + .await?; if passed + failed == 0 { tracing::error!("No tests found!"); } Ok(Some(passed > 0 && failed == 0)) } + +/// `workerd bench`: runs the `bench()` handler of every entrypoint matching the patterns (see +/// `start_local`), as `test()` runs `test()` handlers. +/// +/// The handlers get `params`, an encoded `BenchParams` message (bench.capnp). Returns the +/// report, with a group for each handler; `None` for a config `report` refuses, whose handlers +/// do not run. +pub async fn bench( + factory: Rc, + options: RunOptions, + report: Reporter, + service_pattern: &str, + entrypoint_pattern: &str, + params: Vec, +) -> Result> { + let patterns = (service_pattern, entrypoint_pattern); + let Some((mut running, targets)) = + start_local(factory, options, report, patterns, "bench").await? + else { + return Ok(None); + }; + if targets.is_empty() { + tracing::error!("No benchmarks found!"); + return Ok(Some(bench::Report::new(0))); + } + + let results = running + .alongside(async move { + let mut results = bench::Report::new(len_u32(&targets)); + for (channel, name) in targets { + results.run_group(&*channel, &name, ¶ms).await?; + } + Ok(results) + }) + .await?; + Ok(Some(results)) +} From 8ff607ab5e1fe742c71d6c153de98aaf9ece0a36 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 19:15:23 +0000 Subject: [PATCH 13/24] Add the workerd bench subcommand `workerd bench [filter]` runs the config's bench handlers and writes the results to stdout, or to --output, as a table or with --format=json as the BenchReport. It takes the same config files and filter syntax as `workerd test`, plus an optional / suffix, and the same --compat-date, --all-autogates, and --no-verbose flags. --quick caps budgets for checking that benchmarks run. The report's environment records the workerd and V8 versions, build mode, CPU model, and frequency governor, with warnings for non-release builds and governors other than "performance". The command runs through the server crate's entry::bench(), as `workerd test` runs through entry::test(), and the two share run_local(). The C++ bootstrap takes the new BenchOptions, which turn on bench mode before any worker compiles, and reports the build through build_info(). The Rust server formats the report (bench.rs); its JSON is what capnp's JsonCodec writes, through capnp reflection: fields in schema order, null pointers left out, and 64-bit integers and non-finite floats as strings. --- src/workerd/server/cli/args-test.rs | 58 +++ src/workerd/server/cli/args.rs | 110 ++++++ src/workerd/server/cli/bridge.rs | 4 +- src/workerd/server/cli/lib.rs | 33 +- src/workerd/server/factory/bootstrap.c++ | 64 +++- src/workerd/server/factory/bootstrap.h | 6 +- src/workerd/server/server/BUILD.bazel | 1 + src/workerd/server/server/bench-test.rs | 170 +++++++++ src/workerd/server/server/bench.rs | 431 ++++++++++++++++++++++- src/workerd/server/server/bridge.rs | 31 +- src/workerd/server/server/entry.rs | 117 ++++-- 11 files changed, 982 insertions(+), 43 deletions(-) create mode 100644 src/workerd/server/server/bench-test.rs diff --git a/src/workerd/server/cli/args-test.rs b/src/workerd/server/cli/args-test.rs index 6fb56027121..b331e9f5015 100644 --- a/src/workerd/server/cli/args-test.rs +++ b/src/workerd/server/cli/args-test.rs @@ -133,6 +133,64 @@ fn test_filters() { assert_eq!(filter("a:b:c:d"), Err("Too many colons.")); } +#[test] +fn bench_filters() { + let filter = |param| parse_bench_filter(param); + assert_eq!( + filter("svc:ep/encode*"), + Ok(BenchFilter { + test: TestFilter { + service_pattern: Some("svc".into()), + entrypoint_pattern: Some("ep".into()), + ..TestFilter::default() + }, + case_pattern: Some("encode*".into()), + }) + ); + assert_eq!( + filter("/a/b"), + Ok(BenchFilter { + case_pattern: Some("a/b".into()), + ..BenchFilter::default() + }) + ); + assert_eq!( + filter("svc"), + Ok(BenchFilter { + test: TestFilter { + service_pattern: Some("svc".into()), + ..TestFilter::default() + }, + case_pattern: None, + }) + ); + assert_eq!(filter("a:b:c:d/x"), Err("Too many colons.")); +} + +#[test] +fn bench_invocation() { + let Command::Bench { config, bench, .. } = parse(&[ + "bench", + "foo.wd-test", + "--quick", + "--format=json", + "--output=out.json", + "svc/case", + ]) + .command + else { + panic!("expected bench") + }; + assert_eq!(config.config_file, "foo.wd-test"); + assert!(bench.quick); + assert_eq!(bench.format, BenchFormat::Json); + assert_eq!(bench.output.as_deref(), Some("out.json")); + assert_eq!( + bench.filter.map(|f| f.case_pattern), + Some(Some("case".into())) + ); +} + #[test] fn bad_values_are_usage_errors() { for args in [ diff --git a/src/workerd/server/cli/args.rs b/src/workerd/server/cli/args.rs index be1e2c5a834..d4377abbe78 100644 --- a/src/workerd/server/cli/args.rs +++ b/src/workerd/server/cli/args.rs @@ -8,6 +8,8 @@ use clap::Args; use clap::Parser; use clap::Subcommand; +use clap::ValueEnum; +use workerd_server::bench; use workerd_server::bridge::ffi as server; use crate::bridge::ffi; @@ -122,6 +124,22 @@ pub enum Command { test: TestArgs, }, + /// run benchmarks + #[command( + long_about = "Runs benchmarks based on a config.", + after_long_help = BENCH_HELP, + )] + Bench { + #[command(flatten)] + config: ConfigFileArgs, + + #[command(flatten)] + serve_or_test: ServeOrTestArgs, + + #[command(flatten)] + bench: BenchArgs, + }, + /// outputs the package lock file used by Pyodide #[command(long_about = "Outputs the package lock file used by Pyodide.")] PyodideLock, @@ -170,6 +188,25 @@ under different entrypoint names: } "; +const BENCH_HELP: &str = "\ +Runs benchmarks for services defined in . , if given, has the format of the \ +`test` command's, optionally followed by /, a glob pattern matching case names: + [/] + :[/] + ::[/] + +Benchmarks are defined by exporting a function called `bench`, which registers cases. Each case \ +is timed after `bench` returns: + export default { + bench(b, env, ctx) { + const encoder = new TextEncoder(); + b.run('encode', () => b.blackBox(encoder.encode('hello'))); + b.run('fetch', async () => { await env.SERVICE.fetch('http://x/'); }, { minTime: '2s' }); + } + } +Results are written to stdout as a table, or as JSON with --format=json. +"; + #[derive(Args, Debug)] pub struct ConfigFileArgs { /// Add to the list of directories searched for non-relative imports in the config file @@ -334,6 +371,51 @@ pub struct TestArgs { pub filter: Option, } +#[derive(Args, Debug)] +pub struct BenchArgs { + /// Disable INFO-level logging, which otherwise shows uncaught exceptions. + #[arg(long)] + pub no_verbose: bool, + + /// Enable all autogates. + #[arg(long)] + pub all_autogates: bool, + + /// Set the compatibility date for all workers, as for `test`. + #[arg(long, value_name = "date")] + pub compat_date: Option, + + /// Use short time budgets, to check that benchmarks run rather than to measure them. + #[arg(long)] + pub quick: bool, + + /// The format of the results. + #[arg(long, value_enum, default_value_t = BenchFormat::Text)] + pub format: BenchFormat, + + /// Write the results to instead of stdout. + #[arg(long, value_name = "path")] + pub output: Option, + + #[arg(value_name = "filter", value_parser = parse_bench_filter)] + pub filter: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)] +pub enum BenchFormat { + /// A table for people. + Text, + /// The full report, including raw samples. + Json, +} + +/// `workerd bench`'s ``: `test`'s `` and `/`, each optional. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct BenchFilter { + pub test: TestFilter, + pub case_pattern: Option, +} + /// ``: ``, `:`, or /// `::`. #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -393,6 +475,19 @@ fn parse_test_filter(param: &str) -> Result { }) } +fn parse_bench_filter(param: &str) -> Result { + let (rest, case_pattern) = match param.split_once('/') { + Some((rest, case_pattern)) => (rest, Some(case_pattern.to_owned())), + None => (param, None), + }; + let test = if rest.is_empty() { + TestFilter::default() + } else { + parse_test_filter(rest)? + }; + Ok(BenchFilter { test, case_pattern }) +} + fn parse_control_fd(param: &str) -> Result { param .parse() @@ -458,6 +553,21 @@ impl From<&TestArgs> for server::TestOptions { } } +impl BenchArgs { + /// The server's options for the run, with `case_pattern` from the filter. + pub fn options(&self, case_pattern: Option) -> bench::Options { + bench::Options { + no_verbose: self.no_verbose, + all_autogates: self.all_autogates, + compat_date: self.compat_date.clone(), + quick: self.quick, + case_pattern, + json: self.format == BenchFormat::Json, + output: self.output.clone(), + } + } +} + #[cfg(test)] #[path = "args-test.rs"] mod tests; diff --git a/src/workerd/server/cli/bridge.rs b/src/workerd/server/cli/bridge.rs index e1cae70b3c4..af0f45f6d19 100644 --- a/src/workerd/server/cli/bridge.rs +++ b/src/workerd/server/cli/bridge.rs @@ -1,6 +1,6 @@ //! The FFI between the Rust entry point and the C++ side: the config compiler (config-compiler.h) -//! and what the driver (bootstrap.h) knows about the build. The `serve` and `test` commands go -//! through the server crate's bridge instead (`workerd_server::entry`). +//! and what the driver (bootstrap.h) knows about the build. The `serve`, `test` and `bench` +//! commands go through the server crate's bridge instead (`workerd_server::entry`). //! //! C++ calls back into [`Process`] to register the files a config depends on for `--watch`. diff --git a/src/workerd/server/cli/lib.rs b/src/workerd/server/cli/lib.rs index 2bc5e08294a..d6e8714f5f7 100644 --- a/src/workerd/server/cli/lib.rs +++ b/src/workerd/server/cli/lib.rs @@ -180,6 +180,11 @@ fn run_command(args: args::Main, executable: Option) -> workerd_serv watching(process), ) } + args::Command::Bench { + config, + serve_or_test, + bench, + } => run_bench(verbose, config, &serve_or_test, bench, executable), args::Command::PyodideLock => { println!("{}", ffi::pyodide_lock()?); Ok(0) @@ -223,6 +228,32 @@ fn run_compile( written.unwrap_or_else(|error| usage_error::(format!("writing to stdout: {error}"))); } +/// `workerd bench`: the benchmarks of the config. +fn run_bench( + verbose: bool, + config: args::ConfigFileArgs, + serve_or_test: &args::ServeOrTestArgs, + mut bench: args::BenchArgs, + executable: Option, +) -> workerd_server::Result { + type T = args::Main; + + check_serve_or_test::(serve_or_test); + let filter = bench.filter.take().unwrap_or_default(); + let watcher = watcher::(serve_or_test, executable.as_ref()); + let process = Process::new(executable, watcher, Vec::new()); + let config = load_config::(config, filter.test.const_name.clone(), &process); + entry::bench( + verbose, + config.into_words(), + serve_or_test.into(), + bench.options(filter.case_pattern), + run_options(serve_or_test), + patterns(filter.test), + watching(process), + ) +} + /// Runs a synthesized Python worker's tests with a baseline snapshot saved to `output_directory`. fn run_baseline_snapshot( verbose: bool, @@ -445,7 +476,7 @@ fn serve_options( (options, inherited) } -/// The options `serve` and `test` share: `--directory-path`, `--external-addr` and +/// The options `serve`, `test` and `bench` share: `--directory-path`, `--external-addr` and /// `--inspector-addr`. fn run_options(serve_or_test: &args::ServeOrTestArgs) -> RunOptions { RunOptions { diff --git a/src/workerd/server/factory/bootstrap.c++ b/src/workerd/server/factory/bootstrap.c++ index fe49929df02..683d8487024 100644 --- a/src/workerd/server/factory/bootstrap.c++ +++ b/src/workerd/server/factory/bootstrap.c++ @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -104,6 +105,19 @@ class EntropySourceImpl: public kj::EntropySource { // ======================================================================================= +// How the binary was built, for `workerd bench`'s report: a sanitizer's instrumentation and a +// debug build's checks make code slower than in a release build. +kj::StringPtr buildMode() { +#if __has_feature(address_sanitizer) || __has_feature(thread_sanitizer) || \ + __has_feature(memory_sanitizer) || __has_feature(undefined_behavior_sanitizer) + return "sanitizer"_kj; +#elifdef NDEBUG + return "release"_kj; +#else + return "debug"_kj; +#endif +} + bool structuredLogging(const ::rust::Vec& configWords) { capnp::FlatArrayMessageReader reader(asWords(kj::from(configWords)), CONFIG_READER_OPTIONS); auto config = reader.getRoot(); @@ -119,7 +133,8 @@ class Bootstrap { Bootstrap(kj_rs_tokio::TokioAsyncIoContext& loop, const ::rust::Vec& configWords, const ServeOrTestOptions& serveOrTest, - kj::Maybe test) + kj::Maybe test, + kj::Maybe bench) : timer(loop.getTimer()), monotonicClock(kj::systemPreciseMonotonicClock()), provider(loop.getTimer()) { @@ -139,8 +154,8 @@ class Bootstrap { } TRACE_EVENT("workerd", "Bootstrap()"); - // The process is set up for this config and these options: autogates, the test modes, and V8 - // with its flags (V8 reads the test modes as it starts). + // The process is set up for this config and these options: autogates, the test and bench + // modes, and V8 with its flags (V8 reads the test modes as it starts). capnp::FlatArrayMessageReader reader( asWords(kj::from(configWords)), CONFIG_READER_OPTIONS); auto config = reader.getRoot(); @@ -148,6 +163,9 @@ class Bootstrap { KJ_IF_SOME(t, test) { setUpTestProcess(t); } + KJ_IF_SOME(b, bench) { + setUpBenchProcess(b); + } platform = jsg::defaultPlatform(0); v8Platform = kj::heap(*platform); @@ -168,13 +186,17 @@ class Bootstrap { // A factory over `configWords`, which it owns for its run. kj::Own makeFactory(::rust::Vec configWords, const ServeOrTestOptions& serveOrTest, - kj::Maybe test) { + kj::Maybe test, + kj::Maybe bench) { auto options = kj::heap(); options->loggingOptions = Worker::LoggingOptions(Worker::ConsoleMode::STDOUT); applyServeOrTestOptions(*options, serveOrTest); KJ_IF_SOME(t, test) { applyTestOptions(*options, t); } + KJ_IF_SOME(b, bench) { + applyBenchOptions(*options, b); + } return kj::heap(*v8System, timer, monotonicClock, provider.getNetwork(), entropySource, *fs, kj::mv(options), kj::mv(configWords)); } @@ -256,6 +278,25 @@ class Bootstrap { options.testCompatibilityDateOverride = kj::str(compatDate); } } + + // The `bench` options that apply to the process. Bench mode is on before any worker compiles. + static void setUpBenchProcess(const BenchOptions& benchOptions) { + setBenchMode(); + if (!benchOptions.no_verbose) { + // As for tests, so that uncaught exceptions are displayed. + kj::_::Debug::setLogLevel(kj::LogSeverity::INFO); + } + if (benchOptions.all_autogates) { + util::Autogate::initAllAutogates(); + } + } + + // The `bench` options that apply to a factory. + static void applyBenchOptions(WorkerFactory::Options& options, const BenchOptions& benchOptions) { + KJ_IF_SOME(compatDate, benchOptions.compat_date) { + options.testCompatibilityDateOverride = kj::str(compatDate); + } + } }; } // namespace @@ -310,12 +351,21 @@ int32_t with_process_context( kj::Own bootstrap(kj_rs_tokio::TokioAsyncIoContext& loop, ::rust::Vec config, const ServeOrTestOptions& options, - kj::Maybe test) { - auto bootstrap = kj::heap(loop, config, options, test); - auto factory = bootstrap->makeFactory(kj::mv(config), options, test); + kj::Maybe test, + kj::Maybe bench) { + auto bootstrap = kj::heap(loop, config, options, test, bench); + auto factory = bootstrap->makeFactory(kj::mv(config), options, test, bench); return factory.attach(kj::mv(bootstrap)); } +BuildInfo build_info() { + return BuildInfo{ + .workerd_version = RELEASE_VERSION.as(), + .build_mode = buildMode().as(), + .v8_version = kj::StringPtr(v8::V8::GetVersion()).as(), + }; +} + void cli_exit(int32_t code) { _exit(code); } diff --git a/src/workerd/server/factory/bootstrap.h b/src/workerd/server/factory/bootstrap.h index b684cea4d0b..5b691c5e2a9 100644 --- a/src/workerd/server/factory/bootstrap.h +++ b/src/workerd/server/factory/bootstrap.h @@ -30,6 +30,8 @@ namespace workerd::server::cli { struct ServeOrTestOptions; struct TestOptions; +struct BenchOptions; +struct BuildInfo; ::rust::Slice release_version(); bool perfetto_supported(); @@ -42,7 +44,9 @@ int32_t with_process_context( kj::Own bootstrap(kj_rs_tokio::TokioAsyncIoContext& loop, ::rust::Vec config, const ServeOrTestOptions& options, - kj::Maybe test); + kj::Maybe test, + kj::Maybe bench); +BuildInfo build_info(); [[noreturn]] void cli_exit(int32_t code); void kj_log(uint8_t severity, ::rust::Str file, uint32_t line, ::rust::Str message); void json_log_to_stderr(uint8_t severity, ::rust::Str file, uint32_t line, ::rust::Str message); diff --git a/src/workerd/server/server/BUILD.bazel b/src/workerd/server/server/BUILD.bazel index 485b787440e..7de9a202fb2 100644 --- a/src/workerd/server/server/BUILD.bazel +++ b/src/workerd/server/server/BUILD.bazel @@ -47,6 +47,7 @@ wd_rust_crate( "@crates_vendor//:percent-encoding", "@crates_vendor//:rand", "@crates_vendor//:rustls", + "@crates_vendor//:serde", "@crates_vendor//:serde_json", "@crates_vendor//:socket2", "@crates_vendor//:tokio", diff --git a/src/workerd/server/server/bench-test.rs b/src/workerd/server/server/bench-test.rs new file mode 100644 index 00000000000..9bbe95b8b1e --- /dev/null +++ b/src/workerd/server/server/bench-test.rs @@ -0,0 +1,170 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +use bench_report::case::Flag; +use bench_report::case::Status; + +use super::*; + +#[test] +fn durations_have_three_significant_figures_and_a_fitting_unit() { + assert_eq!(format_duration(0.0), "0.00 ns"); + assert_eq!(format_duration(1.234), "1.23 ns"); + assert_eq!(format_duration(12.34), "12.3 ns"); + assert_eq!(format_duration(123.4), "123 ns"); + assert_eq!(format_duration(1234.0), "1.23 us"); + assert_eq!(format_duration(1.5e6), "1.50 ms"); + assert_eq!(format_duration(2.5e9), "2.50 s"); + assert_eq!(format_duration(7200e9), "7200 s"); +} + +#[test] +fn text_has_the_environment_then_a_table_for_each_group() { + let mut message = TypedBuilder::::new_default(); + let mut report = message.init_root(); + let mut environment = report.reborrow().init_environment(); + environment.set_workerd_version("2026-10-08"); + environment.set_build_mode("debug"); + environment.set_v8_version("14.0.0"); + environment.set_compat_date("2026-01-01"); + environment + .init_warnings(1) + .set(0, "This is a debug build."); + + let mut groups = report.init_groups(2); + let mut group = groups.reborrow().get(0); + group.set_name("main:encoding"); + let mut cases = group.init_cases(4); + + let mut case = cases.reborrow().get(0); + case.set_name("encode small"); + case.set_iterations_per_sample(1024); + let mut wall = case.reborrow().init_wall_ns(); + wall.reborrow().init_samples(3); + wall.set_median(12.34); + wall.set_median_low(12.2); + wall.set_median_high(12.5); + wall.set_mean(12.4); + case.init_cpu_ns().set_median(12.3); + + let mut case = cases.reborrow().get(1); + case.set_name("noisy"); + case.set_iterations_per_sample(1); + case.reborrow().init_wall_ns().set_median(2e6); + case.reborrow().init_cpu_ns(); + case.init_flags(1).set(0, Flag::HighVariance); + + let mut case = cases.reborrow().get(2); + case.set_name("broken"); + case.set_status(Status::Failed); + case.set_error("Error: boom\n at stack"); + + let mut case = cases.get(3); + case.set_name("later"); + case.set_status(Status::Skipped); + + let mut group = groups.get(1); + group.set_name("main:empty"); + group.set_error("bench() did not register any cases."); + + let text = format_text(message.get_root_as_reader().unwrap()).unwrap(); + assert_eq!( + text, + "Results compare runs on this machine and build; they don't predict production latency. +workerd 2026-10-08, debug build, V8 14.0.0, compat date 2026-01-01 +WARNING: This is a debug build. + +main:encoding + case median 95% CI mean cpu median samples + encode small 12.3 ns 12.2 ns - 12.5 ns 12.4 ns 12.3 ns 3 x 1024 + noisy 2.00 ms 0.00 ns - 0.00 ns 0.00 ns 0.00 ns 0 x 1 \ +(high variance: try more samples or a longer minTime) + broken FAILED: Error: boom + later skipped + +main:empty + FAILED: bench() did not register any cases. +" + ); +} + +#[test] +fn json_is_capnp_json_with_null_pointers_left_out() { + let mut message = TypedBuilder::::new_default(); + let mut report = message.init_root(); + let mut environment = report.reborrow().init_environment(); + environment.set_workerd_version("2026-10-08"); + environment.set_build_mode("release"); + environment.set_v8_version("14.0.0"); + environment.set_all_autogates(true); + environment.init_warnings(1).set(0, "a \"warning\""); + let mut group = report.init_groups(1).get(0); + group.set_name("main"); + let mut case = group.init_cases(1).get(0); + case.set_name("broken"); + case.set_status(Status::Failed); + case.set_error("Error: boom\n"); + case.set_iterations_per_sample(u64::MAX); + + let json = format_json(message.get_root_as_reader().unwrap()).unwrap(); + assert_eq!( + json, + r#"{ + "environment": { + "workerdVersion": "2026-10-08", + "buildMode": "release", + "v8Version": "14.0.0", + "allAutogates": true, + "warnings": [ + "a \"warning\"" + ] + }, + "groups": [ + { + "name": "main", + "cases": [ + { + "name": "broken", + "status": "failed", + "error": "Error: boom\n", + "iterationsPerSample": "18446744073709551615" + } + ] + } + ] +} +"# + ); +} + +#[test] +fn json_writes_floats_that_are_not_numbers_as_strings() { + let mut message = TypedBuilder::::new_default(); + let mut case = message + .init_root() + .init_groups(1) + .get(0) + .init_cases(1) + .get(0); + case.reborrow().init_flags(1).set(0, Flag::HighVariance); + let mut wall = case.init_wall_ns(); + wall.reborrow().init_samples(1).set(0, 2.5); + wall.set_median(1.5); + wall.set_mean(f64::NAN); + wall.set_max(f64::INFINITY); + wall.set_min(f64::NEG_INFINITY); + + let json = format_json(message.get_root_as_reader().unwrap()).unwrap(); + let value: serde_json::Value = serde_json::from_str(&json).unwrap(); + let case = &value["groups"][0]["cases"][0]; + assert_eq!(case["status"], "ok"); + assert_eq!(case["flags"], serde_json::json!(["highVariance"])); + let wall = &case["wallNs"]; + assert_eq!(wall["samples"], serde_json::json!([2.5])); + assert_eq!(wall["median"], 1.5); + assert_eq!(wall["mean"], "NaN"); + assert_eq!(wall["max"], "Infinity"); + assert_eq!(wall["min"], "-Infinity"); + assert_eq!(wall["stddev"], 0.0); +} diff --git a/src/workerd/server/server/bench.rs b/src/workerd/server/server/bench.rs index 8a53379b99c..6b56f6d6c18 100644 --- a/src/workerd/server/server/bench.rs +++ b/src/workerd/server/server/bench.rs @@ -2,27 +2,82 @@ // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 -//! `workerd bench`'s report: a `BenchReport` message (bench.capnp) with a group for each -//! `bench()` handler that `run::bench` runs. +//! `workerd bench`'s options and report: a `BenchReport` message (bench.capnp) with a group for +//! each `bench()` handler that `run::bench` runs, written as a table or as JSON. //! //! The worker factory runs a handler (`worker_bench`) and hands back the group the handler filled //! in as an encoded message, which the report copies in under the group's name. +use std::fmt::Write as _; +use std::io::Write as _; use std::time::Duration; use std::time::Instant; +use bench_capnp::bench_params; use bench_capnp::bench_report; +use capnp::dynamic_struct; +use capnp::dynamic_value; use capnp::message::ReaderOptions; use capnp::message::TypedBuilder; use kj_rs::KjMaybe; +use serde::Serialize; +use serde::Serializer; +use serde::ser::Error as _; +use serde::ser::SerializeMap; +use serde::ser::SerializeSeq; use crate::Result; +use crate::bindings::len_u32; use crate::bridge::ffi; use crate::channels::Channel; use crate::config::capnp_error; use crate::config::text; use crate::log; +/// `workerd bench`'s options, besides the service and entrypoint patterns. +#[expect( + clippy::struct_excessive_bools, + reason = "one bool per command-line flag" +)] +pub struct Options { + /// `--no-verbose`: no info logging, which otherwise shows uncaught exceptions. + pub no_verbose: bool, + /// `--all-autogates`. + pub all_autogates: bool, + /// `--compat-date`: the compatibility date of every worker. + pub compat_date: Option, + /// `--quick`: short budgets, to check that the benchmarks run rather than to measure them. + pub quick: bool, + /// The filter's `/`: a glob on case names. + pub case_pattern: Option, + /// `--format=json`: the report as JSON rather than as a table. + pub json: bool, + /// `--output`: where to write the report; stdout if none. + pub output: Option, +} + +impl Options { + /// The options the C++ bootstrap applies. + pub(crate) fn process(&self) -> ffi::BenchOptions { + ffi::BenchOptions { + no_verbose: self.no_verbose, + all_autogates: self.all_autogates, + compat_date: self.compat_date.clone().into(), + } + } + + /// The handlers' parameters, as an encoded `BenchParams` message. + pub(crate) fn params(&self) -> Vec { + let mut message = TypedBuilder::::new_default(); + let mut params = message.init_root(); + if let Some(pattern) = &self.case_pattern { + params.set_case_filter(pattern); + } + params.set_quick(self.quick); + to_words(message.borrow_inner()) + } +} + /// A `BenchReport` being filled in, a group at a time. pub struct Report { message: TypedBuilder, @@ -54,10 +109,7 @@ impl Report { /// The report, encoded (segment table, then segments) in words. #[must_use] pub fn to_words(&self) -> Vec { - capnp::serialize::write_message_to_words(self.message.borrow_inner()) - .chunks_exact(8) - .map(|chunk| u64::from_ne_bytes(chunk.try_into().unwrap_or_default())) - .collect() + to_words(self.message.borrow_inner()) } /// Runs the `bench()` handler behind `channel` with `params` (an encoded `BenchParams` @@ -103,6 +155,69 @@ impl Report { } Ok(()) } + + /// Records where the report was measured: the build and the machine, with `options`' + /// compatibility date and autogates, and warnings for what makes the results unreliable. + pub(crate) fn set_environment(&mut self, options: &Options) -> Result<()> { + let build = ffi::build_info()?; + let cpu_model = cpu_model(); + let governor = governor(); + let mut warnings = Vec::new(); + if build.build_mode != "release" { + warnings.push(format!( + "This is a {} build; its performance differs from a release build's, so use \ + results only to compare runs of this build.", + build.build_mode + )); + } + if let Some(governor) = governor.as_deref().filter(|g| *g != "performance") { + warnings.push(format!( + "The CPU frequency governor is \"{governor}\"; set it to \"performance\" for \ + more stable results." + )); + } + + let root = self.message.get_root().map_err(capnp_error)?; + let mut environment = root.init_environment(); + environment.set_workerd_version(&build.workerd_version); + environment.set_build_mode(&build.build_mode); + environment.set_v8_version(&build.v8_version); + if let Some(model) = &cpu_model { + environment.set_cpu_model(model); + } + if let Some(governor) = &governor { + environment.set_governor(governor); + } + if let Some(date) = &options.compat_date { + environment.set_compat_date(date); + } + environment.set_all_autogates(options.all_autogates); + let mut list = environment.init_warnings(len_u32(&warnings)); + for (index, warning) in (0..).zip(&warnings) { + list.set(index, warning); + } + Ok(()) + } + + /// Writes the report as `options` ask: as text or JSON, to stdout or to `--output`. + pub(crate) fn write(&self, options: &Options) -> Result<()> { + let report = self.message.get_root_as_reader().map_err(capnp_error)?; + let text = if options.json { + format_json(report)? + } else { + format_text(report)? + }; + let written = if let Some(path) = &options.output { + write_file(path, &text) + } else { + let mut stdout = std::io::stdout().lock(); + stdout + .write_all(text.as_bytes()) + .and_then(|()| stdout.flush()) + }; + let destination = options.output.as_deref().unwrap_or("stdout"); + written.map_err(|e| kj::failed!("writing the report to {destination}: {e}")) + } } /// Logs how the group `name` went: `[ SKIP ]` and `[ FAIL ]` for its cases, then `[ DONE ]` with @@ -136,6 +251,14 @@ fn log_outcome( Ok(failed) } +/// `message`, encoded (segment table, then segments) in words. +fn to_words(message: &capnp::message::Builder) -> Vec { + capnp::serialize::write_message_to_words(message) + .chunks_exact(8) + .map(|chunk| u64::from_ne_bytes(chunk.try_into().unwrap_or_default())) + .collect() +} + /// An encoded message (segment table, then segments) in words, read. The messages are the /// factory's, as large as a handler's results, so their size is not limited. fn read_words( @@ -145,3 +268,299 @@ fn read_words( let options = *ReaderOptions::new().traversal_limit_in_words(None); capnp::serialize::read_message(bytes.as_slice(), options) } + +/// Writes `text` to the file at `path`, creating its directory if need be. +fn write_file(path: &str, text: &str) -> std::io::Result<()> { + let path = std::path::Path::new(path); + if let Some(dir) = path.parent().filter(|dir| !dir.as_os_str().is_empty()) { + std::fs::create_dir_all(dir)?; + } + std::fs::write(path, text) +} + +/// The model name of the machine's CPU, as Linux reports it. +fn cpu_model() -> Option { + let cpuinfo = std::fs::read_to_string("/proc/cpuinfo").ok()?; + cpuinfo.lines().find_map(|line| { + let (key, value) = line.split_once(':')?; + (key.trim() == "model name").then(|| value.trim().to_owned()) + }) +} + +/// The CPU frequency governor, as Linux reports it for the first CPU. +fn governor() -> Option { + let path = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"; + Some(std::fs::read_to_string(path).ok()?.trim().to_owned()) +} + +/// `report` as text: a header describing the environment, then a table for each group. +fn format_text(report: bench_report::Reader<'_>) -> Result { + let mut out = String::from( + "Results compare runs on this machine and build; they don't predict production latency.\n", + ); + let environment = report.get_environment().map_err(capnp_error)?; + let mut parts = Vec::new(); + if environment.has_workerd_version() { + parts.push(format!( + "workerd {}", + text(environment.get_workerd_version())? + )); + } + parts.push(format!("{} build", text(environment.get_build_mode())?)); + parts.push(format!("V8 {}", text(environment.get_v8_version())?)); + if environment.has_cpu_model() { + parts.push(text(environment.get_cpu_model())?); + } + if environment.has_governor() { + parts.push(format!("governor {}", text(environment.get_governor())?)); + } + if environment.has_compat_date() { + parts.push(format!( + "compat date {}", + text(environment.get_compat_date())? + )); + } + if environment.get_all_autogates() { + parts.push("all autogates".to_owned()); + } + let _ = writeln!(out, "{}", parts.join(", ")); + for warning in environment.get_warnings().map_err(capnp_error)? { + let _ = writeln!(out, "WARNING: {}", text(warning)?); + } + for group in report.get_groups().map_err(capnp_error)? { + format_group(&mut out, group)?; + } + Ok(out) +} + +/// The columns of a group's table. +const COLUMNS: [&str; 6] = ["case", "median", "95% CI", "mean", "cpu median", "samples"]; + +/// A row of a group's table: a cell for each column, with any notes on the case as the message; +/// or the case's name, with a message saying why it has no statistics. +struct Row { + cells: Vec, + message: Option, +} + +/// Appends `group` to `out`: its name and error, then a table of its cases, the columns padded +/// to line up. +fn format_group(out: &mut String, group: bench_report::group::Reader<'_>) -> Result<()> { + let _ = writeln!(out, "\n{}", text(group.get_name())?); + if group.has_error() { + let _ = writeln!(out, " FAILED: {}", first_line(&text(group.get_error())?)); + } + let mut rows = Vec::new(); + for case in group.get_cases().map_err(capnp_error)? { + rows.push(case_row(case)?); + } + if rows.is_empty() { + return Ok(()); + } + + let width = |cell: &str| cell.chars().count(); + let mut widths = COLUMNS.map(width); + for row in &rows { + for (column, cell) in widths.iter_mut().zip(&row.cells) { + *column = (*column).max(width(cell)); + } + } + let header = Row { + cells: COLUMNS.map(str::to_owned).to_vec(), + message: None, + }; + for row in std::iter::once(&header).chain(&rows) { + out.push_str(" "); + for (index, (cell, column)) in row.cells.iter().zip(widths).enumerate() { + // The last cell of a row has no padding after it. + if index + 1 == row.cells.len() && row.message.is_none() { + out.push_str(cell); + } else { + let _ = write!(out, "{cell:) -> Result { + use bench_report::case::Status; + + let name = text(case.get_name())?; + let message = match case.get_status().map_err(|e| capnp_error(e.into()))? { + Status::Ok => return statistics_row(name, case), + Status::Skipped => "skipped".to_owned(), + Status::Failed => format!("FAILED: {}", first_line(&text(case.get_error())?)), + }; + Ok(Row { + cells: vec![name], + message: Some(message), + }) +} + +/// The row of `case`, which ran, named `name`: its statistics, with notes on its flags. +fn statistics_row(name: String, case: bench_report::case::Reader<'_>) -> Result { + use bench_report::case::Flag; + + let wall = case.get_wall_ns().map_err(capnp_error)?; + let median_interval = format!( + "{} - {}", + format_duration(wall.get_median_low()), + format_duration(wall.get_median_high()) + ); + let samples = wall.get_samples().map_err(capnp_error)?.len(); + let cells = vec![ + name, + format_duration(wall.get_median()), + median_interval, + format_duration(wall.get_mean()), + format_duration(case.get_cpu_ns().map_err(capnp_error)?.get_median()), + format!("{samples} x {}", case.get_iterations_per_sample()), + ]; + let mut notes = Vec::new(); + for flag in case.get_flags().map_err(capnp_error)? { + notes.push(match flag.map_err(|e| capnp_error(e.into()))? { + Flag::HighVariance => "high variance: try more samples or a longer minTime", + }); + } + Ok(Row { + cells, + message: (!notes.is_empty()).then(|| format!("({})", notes.join("; "))), + }) +} + +/// The first line of `text`. +fn first_line(text: &str) -> &str { + text.split_once('\n').map_or(text, |(first, _)| first) +} + +/// `ns` nanoseconds with three significant figures and a unit, as in "12.3 ns" or "1.50 ms". +fn format_duration(ns: f64) -> String { + const UNITS: [(f64, &str); 4] = [(1.0, "ns"), (1e3, "us"), (1e6, "ms"), (1e9, "s")]; + if !ns.is_finite() { + return ns.to_string(); + } + let (scale, unit) = UNITS + .iter() + .rev() + .find(|(scale, _)| ns.abs() >= *scale) + .unwrap_or(&UNITS[0]); + let value = ns / scale; + // Three significant figures: 1.23, 12.3, 123. + let decimals = match value.abs() { + magnitude if magnitude < 10.0 => 2, + magnitude if magnitude < 100.0 => 1, + _ => 0, + }; + format!("{value:.decimals$} {unit}") +} + +/// `report` as pretty-printed JSON, as capnp's C++ `JsonCodec` encodes it (see `Json`). +fn format_json(report: bench_report::Reader<'_>) -> Result { + let mut json = serde_json::to_string_pretty(&Json(report.into())) + .map_err(|e| kj::failed!("encoding the report as JSON: {e}"))?; + json.push('\n'); + Ok(json) +} + +/// A capnp value, serialized as capnp's C++ `JsonCodec` encodes it: struct fields in the +/// schema's order, without the pointers that are null; 64-bit integers as strings, which JSON numbers +/// cannot hold exactly; `NaN` and the infinities as strings, which JSON numbers cannot be; +/// enumerants by name. +struct Json<'a>(dynamic_value::Reader<'a>); + +impl Serialize for Json<'_> { + fn serialize(&self, serializer: S) -> std::result::Result { + match self.0 { + dynamic_value::Reader::Void => serializer.serialize_unit(), + dynamic_value::Reader::Bool(value) => serializer.serialize_bool(value), + dynamic_value::Reader::Int8(value) => serializer.serialize_i8(value), + dynamic_value::Reader::Int16(value) => serializer.serialize_i16(value), + dynamic_value::Reader::Int32(value) => serializer.serialize_i32(value), + dynamic_value::Reader::UInt8(value) => serializer.serialize_u8(value), + dynamic_value::Reader::UInt16(value) => serializer.serialize_u16(value), + dynamic_value::Reader::UInt32(value) => serializer.serialize_u32(value), + dynamic_value::Reader::Int64(value) => serializer.collect_str(&value), + dynamic_value::Reader::UInt64(value) => serializer.collect_str(&value), + dynamic_value::Reader::Float32(value) => serialize_float(serializer, value.into()), + dynamic_value::Reader::Float64(value) => serialize_float(serializer, value), + dynamic_value::Reader::Enum(value) => { + match value.get_enumerant().map_err(S::Error::custom)? { + Some(enumerant) => { + let name = enumerant.get_proto().get_name(); + serializer.serialize_str(name.and_then(to_str).map_err(S::Error::custom)?) + } + None => serializer.serialize_u16(value.get_value()), + } + } + dynamic_value::Reader::Text(value) => { + serializer.serialize_str(value.to_str().map_err(S::Error::custom)?) + } + dynamic_value::Reader::Data(value) => serializer.collect_seq(value), + dynamic_value::Reader::List(list) => { + let mut seq = serializer.serialize_seq(Some(list.len() as usize))?; + for element in list { + seq.serialize_element(&Json(element.map_err(S::Error::custom)?))?; + } + seq.end() + } + dynamic_value::Reader::Struct(value) => serialize_struct(serializer, value), + dynamic_value::Reader::AnyPointer(_) | dynamic_value::Reader::Capability(_) => Err( + S::Error::custom("AnyPointer and capability fields have no JSON encoding"), + ), + } + } +} + +/// `text` as a `&str`; an error if it is not UTF-8. +fn to_str(text: capnp::text::Reader<'_>) -> capnp::Result<&str> { + Ok(text.to_str()?) +} + +/// A float as a JSON number, except `NaN` and the infinities, which are strings. +fn serialize_float( + serializer: S, + value: f64, +) -> std::result::Result { + if value.is_nan() { + serializer.serialize_str("NaN") + } else if value.is_infinite() { + serializer.serialize_str(if value > 0.0 { "Infinity" } else { "-Infinity" }) + } else { + serializer.serialize_f64(value) + } +} + +/// A struct as a JSON object. A union's member is written when it is set, even to a null +/// pointer (as `null`), unless it is the union's first member, which is set by default. +fn serialize_struct( + serializer: S, + value: dynamic_struct::Reader<'_>, +) -> std::result::Result { + let active = value.which().map_err(S::Error::custom)?; + let mut map = serializer.serialize_map(None)?; + for field in value.get_schema().get_fields().map_err(S::Error::custom)? { + let proto = field.get_proto(); + let name = proto + .get_name() + .and_then(to_str) + .map_err(S::Error::custom)?; + if value.has(field).map_err(S::Error::custom)? { + map.serialize_entry(name, &Json(value.get(field).map_err(S::Error::custom)?))?; + } else if active.is_some_and(|active| active.get_index() == field.get_index()) + && proto.get_discriminant_value() != 0 + { + map.serialize_entry(name, &())?; + } + } + map.end() +} + +#[cfg(test)] +#[path = "bench-test.rs"] +mod tests; diff --git a/src/workerd/server/server/bridge.rs b/src/workerd/server/server/bridge.rs index 7bee90134a4..996e46ddc8c 100644 --- a/src/workerd/server/server/bridge.rs +++ b/src/workerd/server/server/bridge.rs @@ -177,8 +177,9 @@ pub mod ffi { // ===================================================================================== // The command line (bootstrap.h): the options the C++ driver applies, and the driver itself. - /// The options of `serve` and `test` the C++ driver applies: to the process (perfetto, the - /// async trace file), or to the worker factory (async tracing, experimental, Python). + /// The options of `serve`, `test` and `bench` the C++ driver applies: to the process + /// (perfetto, the async trace file), or to the worker factory (async tracing, experimental, + /// Python). #[namespace = "workerd::server::cli"] struct ServeOrTestOptions { perfetto_trace_path: KjMaybe, @@ -206,6 +207,25 @@ pub mod ffi { compat_date: KjMaybe, } + /// The options of `bench` the C++ driver applies: bench mode, which they turn on, logging, + /// autogates, the compatibility date every worker gets. + #[namespace = "workerd::server::cli"] + struct BenchOptions { + no_verbose: bool, + all_autogates: bool, + compat_date: KjMaybe, + } + + /// What the binary was built from, as `workerd bench`'s report records it. + #[namespace = "workerd::server::cli"] + struct BuildInfo { + /// The release date of the workerd source. + workerd_version: String, + /// "release", "debug", or "sanitizer". + build_mode: String, + v8_version: String, + } + #[namespace = "kj_rs_tokio"] unsafe extern "C++" { include!("kj-rs-tokio/tokio-event-port.h"); @@ -235,8 +255,8 @@ pub mod ffi { /// and returns the worker factory over the config, which owns it all: dropping the /// factory tears the rest down after it, in reverse, flushing the perfetto trace. /// `config` is an encoded message (segment table, then segments) in 8-byte words. - /// `test` is the `test` command's options, absent for `serve`. An error means the - /// process failed to start. + /// `test` and `bench` are the options of those commands, at most one of them given. An + /// error means the process failed to start. /// /// The factory dies before the Runtime (the timer and the loop must outlive everything /// on them). @@ -245,8 +265,11 @@ pub mod ffi { config: Vec, options: &ServeOrTestOptions, test: KjMaybe<&TestOptions>, + bench: KjMaybe<&BenchOptions>, ) -> Result>; + fn build_info() -> Result; + /// Ends the process with `code` at once, without running destructors (what /// `kj::ProcessContext::exit()` does when `KJ_CLEAN_SHUTDOWN` is not set). Does not return. fn cli_exit(code: i32); diff --git a/src/workerd/server/server/entry.rs b/src/workerd/server/server/entry.rs index 56d6615e85d..18972c7f019 100644 --- a/src/workerd/server/server/entry.rs +++ b/src/workerd/server/server/entry.rs @@ -2,13 +2,14 @@ // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 -//! The `serve` and `test` commands: the command line's Rust half, and the process's schedule. +//! The `serve`, `test` and `bench` commands: the command line's Rust half, and the process's +//! schedule. //! //! The command line (cli/lib.rs) parses the arguments into [`RunOptions`] and calls -//! [`serve`] / [`test`]. Each creates the event loop -- a `kj_rs_tokio::Runtime`, the KJ loop -//! with tokio running whenever it sleeps -- has the C++ bootstrap +//! [`serve`] / [`test`] / [`bench`]. Each creates the event loop -- a `kj_rs_tokio::Runtime`, the +//! KJ loop with tokio running whenever it sleeps -- has the C++ bootstrap //! (bootstrap.c++) set up the process (logging, perfetto, V8) and build the worker factory on that -//! loop, then blocks on `server_serve` / `server_test`, which run the server +//! loop, then blocks on `server_serve` / `server_test` / `server_bench`, which run the server //! (`run.rs`). Every config error is printed, and the server then does not serve; unless //! `--watch` is on, in which case it serves what it can, waits for the config to change and the //! process re-executes itself. @@ -30,6 +31,7 @@ use futures::future::LocalBoxFuture; use kj_rs::KjOwn; use crate::Result; +use crate::bench; use crate::bridge::ffi; use crate::channels::AbortReason; use crate::config::Factory; @@ -53,7 +55,7 @@ pub fn serve( watch: Option, ) -> Result { let command = move |factory, structured| server_serve(factory, run_options, watch, structured); - run(verbose, config, options, None, command) + run(verbose, config, options, None, None, command) } /// Runs `workerd test` (see the module doc); the exit status, if the process is to go on. @@ -69,7 +71,25 @@ pub fn test( ) -> Result { let command = move |factory, structured| server_test(factory, run_options, patterns, watch, structured); - run(verbose, config, options, Some(test), command) + run(verbose, config, options, Some(test), None, command) +} + +/// Runs `workerd bench` (see the module doc); the exit status, if the process is to go on. +/// `patterns` are the `` and `` globs. +pub fn bench( + verbose: bool, + config: Vec, + options: ffi::ServeOrTestOptions, + bench: bench::Options, + run_options: RunOptions, + patterns: (String, String), + watch: Option, +) -> Result { + let process = bench.process(); + let command = move |factory, structured| { + server_bench(factory, run_options, patterns, bench, watch, structured) + }; + run(verbose, config, options, None, Some(process), command) } /// A command's Rust half, for the C++ frame that sets up the process's logging around it @@ -89,17 +109,24 @@ pub fn run_pending_command( } /// The process's schedule (see the module doc): the logging, the loop, the bootstrap, the -/// command, the exit. +/// command, the exit. `test` and `bench` are the bootstrap's options for those commands. fn run> + 'static>( verbose: bool, config: Vec, options: ffi::ServeOrTestOptions, test: Option, + bench: Option, command: impl FnOnce(KjOwn, bool) -> Fut + 'static, ) -> Result { let command = move |config, structured| { let mut runtime = kj_rs_tokio::Runtime::new()?; - let factory = ffi::bootstrap(runtime.context(), config, &options, test.as_ref().into())?; + let factory = ffi::bootstrap( + runtime.context(), + config, + &options, + test.as_ref().into(), + bench.as_ref().into(), + )?; // The command owns the factory and drops it when it completes (`shutdown`); if it does not // complete, `block_on` drops it, and the factory with it, before returning. let result = runtime.block_on(command(factory, structured)); @@ -218,6 +245,26 @@ pub(crate) async fn shutdown(factory: Rc) -> Result<()> { Ok(()) } +/// Runs `command` -- `run::test` or `run::bench` -- on `factory` with loopback sockets enabled, +/// alongside the watcher, then tears the run down (`shutdown`). +async fn run_local>>( + factory: KjOwn, + watch: &mut Option, + structured: bool, + command: impl FnOnce(Rc, Reporter) -> Fut, +) -> Result { + crate::log::install(); + let report = reporter(watch.is_some(), structured); + let factory = Rc::new(Factory::new(factory)); + // Loopback sockets are for tests and benchmarks only. + factory.loopback().enable(); + let result = with_watch(command(Rc::clone(&factory), report), watch.as_mut()).await; + let shutdown = shutdown(factory).await; + let result = result?; + shutdown?; + Ok(result) +} + /// `workerd test`: runs the tests of `factory`'s config. Under `--watch`, waits for a change /// afterwards instead of returning. async fn server_test( @@ -227,25 +274,16 @@ async fn server_test( mut watch: Option, structured: bool, ) -> Result<()> { - crate::log::install(); - let report = reporter(watch.is_some(), structured); - let factory = Rc::new(Factory::new(factory)); - // Loopback sockets are for tests only. - factory.loopback().enable(); - let result = with_watch( + let passed = run_local(factory, &mut watch, structured, |factory, report| { crate::run::test( - Rc::clone(&factory), + factory, options, report, &service_pattern, &entrypoint_pattern, - ), - watch.as_mut(), - ) - .await; - let shutdown = shutdown(factory).await; - let passed = result?; - shutdown?; + ) + }) + .await?; if passed == Some(false) { error(structured, "Tests failed!"); } @@ -255,3 +293,38 @@ async fn server_test( } Ok(()) } + +/// `workerd bench`: runs the benchmarks of `factory`'s config and writes the report, as `bench` +/// asks. Under `--watch`, waits for a change afterwards instead of returning. +async fn server_bench( + factory: KjOwn, + options: RunOptions, + (service_pattern, entrypoint_pattern): (String, String), + bench: bench::Options, + mut watch: Option, + structured: bool, +) -> Result<()> { + let results = run_local(factory, &mut watch, structured, |factory, report| { + crate::run::bench( + factory, + options, + report, + &service_pattern, + &entrypoint_pattern, + bench.params(), + ) + }) + .await?; + if let Some(mut results) = results { + results.set_environment(&bench)?; + results.write(&bench)?; + if !results.passed() { + error(structured, "Benchmarks failed!"); + } + } + if let Some(watch) = watch { + // Under --watch the process stays for the next change rather than exiting. + watch.await?; + } + Ok(()) +} From 94b25839d733a1dcce974f37c90595acb18b6d71 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 19:21:41 +0000 Subject: [PATCH 14/24] Skip tail workers under workerd bench Benchmarked code often makes a subrequest on every call, as in `env.SELF.fetch()`, and tail workers would add their cost to each one. The worker factory gains a `tailWorkers` option, which `workerd bench` turns off. The server still looks up the tail workers a Worker's config names when it links the Worker's channels, so that config errors are reported, and then drops them; the factory drops a dynamic Worker's tail workers when it fetches the Worker's source. The bench server test now clears bench mode when it's done, so that it doesn't change later tests in the same binary. --- src/workerd/server/factory/bootstrap.c++ | 1 + src/workerd/server/factory/worker-factory.c++ | 8 + src/workerd/server/factory/worker-factory.h | 6 + src/workerd/server/server-test.c++ | 144 ++++++++++++++++++ src/workerd/server/server/bridge.rs | 3 + src/workerd/server/server/config.rs | 15 +- src/workerd/util/thread-scopes.c++ | 4 + src/workerd/util/thread-scopes.h | 3 + 8 files changed, 182 insertions(+), 2 deletions(-) diff --git a/src/workerd/server/factory/bootstrap.c++ b/src/workerd/server/factory/bootstrap.c++ index 683d8487024..b6e1978531f 100644 --- a/src/workerd/server/factory/bootstrap.c++ +++ b/src/workerd/server/factory/bootstrap.c++ @@ -296,6 +296,7 @@ class Bootstrap { KJ_IF_SOME(compatDate, benchOptions.compat_date) { options.testCompatibilityDateOverride = kj::str(compatDate); } + options.tailWorkers = false; } }; diff --git a/src/workerd/server/factory/worker-factory.c++ b/src/workerd/server/factory/worker-factory.c++ index 06b5b3b484c..0062eaffa36 100644 --- a/src/workerd/server/factory/worker-factory.c++ +++ b/src/workerd/server/factory/worker-factory.c++ @@ -254,6 +254,10 @@ bool factory_experimental(const WorkerFactory& factory) { return factory.getImpl().options->experimental; } +bool factory_tail_workers(const WorkerFactory& factory) { + return factory.getImpl().options->tailWorkers; +} + Worker::Actor::Id normalizeActorId(Worker::Actor::Id id) { KJ_IF_SOME(doId, id.tryGet>()) { KJ_IF_SOME(name, doId->getName()) { @@ -1535,6 +1539,10 @@ kj::Promise> factory_new_worker(const WorkerFactory& con auto fetched = co_await (*KJ_ASSERT_NONNULL(dynamicSource))(); co_await fetched.ensureAllResolved(); auto& dynamic = impl->dynamicSource.emplace(kj::mv(fetched)); + if (!options.tailWorkers) { + dynamic.tails = nullptr; + dynamic.streamingTails = nullptr; + } indexDynamicChannels(*impl, dynamic); featureFlags = dynamic.compatibilityFlags; source = kj::mv(dynamic.source); diff --git a/src/workerd/server/factory/worker-factory.h b/src/workerd/server/factory/worker-factory.h index 430a61ca943..e04a444412b 100644 --- a/src/workerd/server/factory/worker-factory.h +++ b/src/workerd/server/factory/worker-factory.h @@ -112,6 +112,11 @@ class WorkerFactory final: private ChannelTokenHandler::Resolver { bool experimental = false; // When set, every worker uses this compatibility date and none may specify its own. kj::Maybe testCompatibilityDateOverride; + // Whether requests get tail workers. `workerd bench` turns them off: benchmarked code often + // makes a subrequest on every call (e.g. `env.SELF.fetch()`), and tail workers would add + // their cost to each. The server still looks a config worker's tails up, so that their + // config errors are reported, but does not link them. + bool tailWorkers = true; Worker::LoggingOptions loggingOptions; api::pyodide::PythonConfig pythonConfig; // When set, async activity in every worker is traced to this file (--async-trace), which must @@ -163,6 +168,7 @@ class WorkerFactory final: private ChannelTokenHandler::Resolver { ::rust::Slice factory_config(const WorkerFactory& factory); const kj::HttpHeaderTable& factory_header_table(const WorkerFactory& factory); bool factory_experimental(const WorkerFactory& factory); +bool factory_tail_workers(const WorkerFactory& factory); void factory_set_server(const WorkerFactory& factory, ::rust::Box server); void factory_spawn(const WorkerFactory& factory, ::rust::Box task); void factory_clear_tasks(const WorkerFactory& factory); diff --git a/src/workerd/server/server-test.c++ b/src/workerd/server/server-test.c++ index c4674fb982b..8383405b668 100644 --- a/src/workerd/server/server-test.c++ +++ b/src/workerd/server/server-test.c++ @@ -404,6 +404,9 @@ class Server { void allowExperimental() { options->experimental = true; } + void disableTailWorkers() { + options->tailWorkers = false; + } void enableDebugPort(kj::String addr) { debugPort = kj::str("loopback:", addr); } @@ -4422,6 +4425,146 @@ KJ_TEST("Server: tail workers") { )"_blockquote); } +KJ_TEST("Server: tail workers turned off") { + TestServer test(R"(( + services = [ + ( name = "hello", + worker = ( + compatibilityDate = "2024-11-01", + modules = [ + ( name = "main.js", + esModule = + `export default { + ` async fetch(req, env, ctx) { + ` return new Response("OK"); + ` } + `} + ) + ], + tails = ["tail"], + streamingTails = ["tail"], + ) + ), + ( name = "tail", + worker = ( + compatibilityDate = "2024-11-01", + modules = [ + ( name = "main.js", + esModule = + `export default { + ` async tail(req, env, ctx) { + ` await fetch("http://tail"); + ` }, + ` tailStream(event, env, ctx) { + ` ctx.waitUntil(fetch("http://tail")); + ` return {}; + ` } + `} + ) + ], + ) + ), + ], + sockets = [ + ( name = "main", + address = "test-addr", + service = "hello" + ) + ] + ))"_kj); + + test.server.disableTailWorkers(); + test.start(); + auto conn = test.connect("test-addr"); + conn.sendHttpGet("/"); + conn.recvHttp200("OK"); + + // Give a tail worker, which runs after the response, time to run. + test.wait(1); + KJ_EXPECT(!test.server.accept("tail").poll(test.ws), "a tail worker ran"); +} + +KJ_TEST("Server: tail workers turned off for a dynamic worker") { + TestServer test(R"(( + services = [ + ( name = "hello", + worker = ( + compatibilityDate = "2025-01-01", + compatibilityFlags = ["enable_ctx_exports", "experimental"], + modules = [ + ( name = "main.js", + esModule = + `import {WorkerEntrypoint} from "cloudflare:workers"; + `export class Tail extends WorkerEntrypoint { + ` async tail(events) { + ` await fetch("http://tail"); + ` } + `} + `export default { + ` async fetch(req, env, ctx) { + ` let worker = env.LOADER.get("dynamic", () => ({ + ` compatibilityDate: "2025-01-01", + ` mainModule: "main.js", + ` modules: { + ` "main.js": "export default { fetch() { return new Response('OK'); } }", + ` }, + ` tails: [ctx.exports.Tail({})], + ` })); + ` return worker.getEntrypoint().fetch(req); + ` } + `} + ) + ], + bindings = [( name = "LOADER", workerLoader = () )], + ) + ), + ], + sockets = [ + ( name = "main", + address = "test-addr", + service = "hello" + ) + ] + ))"_kj); + + test.server.allowExperimental(); + test.server.disableTailWorkers(); + test.start(); + auto conn = test.connect("test-addr"); + conn.sendHttpGet("/"); + conn.recvHttp200("OK"); + + // Give a tail worker, which runs after the response, time to run. + test.wait(1); + KJ_EXPECT(!test.server.accept("tail").poll(test.ws), "a tail worker ran"); +} + +KJ_TEST("Server: tail workers turned off are still looked up") { + TestServer test(R"(( + services = [ + ( name = "hello", + worker = ( + compatibilityDate = "2024-11-01", + modules = [ + ( name = "main.js", + esModule = + `export default { + ` fetch(req, env, ctx) { return new Response("OK"); } + `} + ) + ], + tails = ["missing"], + ) + ), + ] + ))"_kj); + + test.server.disableTailWorkers(); + test.expectErrors(R"( + Worker "hello"'s tails refers to a service "missing", but no such service is defined. + )"_blockquote); +} + // ======================================================================================= // Test HttpOptions on receive @@ -5823,6 +5966,7 @@ KJ_TEST("Server: cache name is passed through to service") { KJ_TEST("Server: bench command") { setBenchMode(); + KJ_DEFER(unsetBenchModeForTest()); // With no warmup and a fixed batch, each case's function is called exactly batch * samples // times, which the "verify" case checks. diff --git a/src/workerd/server/server/bridge.rs b/src/workerd/server/server/bridge.rs index 996e46ddc8c..7efa09291e0 100644 --- a/src/workerd/server/server/bridge.rs +++ b/src/workerd/server/server/bridge.rs @@ -300,6 +300,9 @@ pub mod ffi { /// both read. Owned by the factory for the run; every reader into it stays valid. fn factory_config<'a>(factory: &'a WorkerFactory) -> &'a [u8]; fn factory_experimental(factory: &WorkerFactory) -> bool; + /// Whether requests get tail workers (`WorkerFactory::Options::tailWorkers`): not under + /// `workerd bench`. + fn factory_tail_workers(factory: &WorkerFactory) -> bool; /// Registers the server as the resolver of channel tokens and debug-port requests. /// Tokens cannot be decoded before this. fn factory_set_server(factory: &WorkerFactory, server: Box); diff --git a/src/workerd/server/server/config.rs b/src/workerd/server/server/config.rs index 4aea4a5a24f..5cef5422e88 100644 --- a/src/workerd/server/server/config.rs +++ b/src/workerd/server/server/config.rs @@ -100,6 +100,12 @@ impl Factory { ffi::factory_experimental(&self.inner) } + /// Whether requests get tail workers: not under `workerd bench`. + #[must_use] + pub fn tail_workers(&self) -> bool { + ffi::factory_tail_workers(&self.inner) + } + /// The reading of the factory's `kj::Timer`, which the server's own delays run on, as the /// time since the timer's origin. #[must_use] @@ -1141,8 +1147,13 @@ impl Server { let storage_path = self.storage_path(name, pending.storage.as_deref()); - let tails = pending.tails.iter().map(lookup).collect(); - let streaming_tails = pending.streaming_tails.iter().map(lookup).collect(); + let mut tails: Vec<_> = pending.tails.iter().map(lookup).collect(); + let mut streaming_tails: Vec<_> = pending.streaming_tails.iter().map(lookup).collect(); + // Without tail workers, the tails are only looked up, for their config errors. + if !self.factory.tail_workers() { + tails.clear(); + streaming_tails.clear(); + } let worker_loaders = pending .bindings diff --git a/src/workerd/util/thread-scopes.c++ b/src/workerd/util/thread-scopes.c++ index 2c4ea275183..3a3bba53007 100644 --- a/src/workerd/util/thread-scopes.c++ +++ b/src/workerd/util/thread-scopes.c++ @@ -65,6 +65,10 @@ void setBenchMode() { benchMode = true; } +void unsetBenchModeForTest() { + benchMode = false; +} + bool isGcStressModeForTest() { // Also honor the WORKERD_GC_STRESS environment variable so that gc-stress mode can be enabled // in binaries that don't have a --gc-stress CLI flag (e.g., edgeworker's prod subcommand). diff --git a/src/workerd/util/thread-scopes.h b/src/workerd/util/thread-scopes.h index 870b9891f4a..7663235c20b 100644 --- a/src/workerd/util/thread-scopes.h +++ b/src/workerd/util/thread-scopes.h @@ -78,6 +78,9 @@ void setPredictableModeForTest(); bool isBenchMode(); void setBenchMode(); +// Clears bench mode, so that a test that sets it doesn't affect later tests in the same process. +void unsetBenchModeForTest(); + // When enabled, forces a full V8 garbage collection at key points where the KJ event loop // re-enters JavaScript (e.g., awaitIo continuations). This helps detect KJ async objects that // are reachable from the JS heap without proper IoOwn wrapping — such objects would violate From 26531328f7d0bf1de493f2af07a77c7578b3e308 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 19:28:20 +0000 Subject: [PATCH 15/24] Add wd_bench() and a TextEncoder benchmark wd_bench(src = "x.wd-bench", data = [...]) takes a config in wd_test()'s format and, for each compat variant, defines a `bazel run` target that runs `workerd bench` (tagged workerd-benchmark, so CI's benchmark filter excludes it) and an @smoke test that runs each case briefly with --quick, so CI checks that benchmarks keep working. @benchmark.json writes the default variant's report, off by default like wd_cc_benchmark()'s @benchmark.csv. `just wd-bench [args]` runs one in the benchmark configuration. wd_test.bzl gains wd_cli_test(), a public name for its test rule, which wd_bench() uses for the smoke tests. As for wd_test(), configs can declare a writable disk service named TEST_TMPDIR, e.g. for Durable Object storage. The smoke tests get Bazel's test temporary directory; the run targets and @benchmark.json create a new temporary directory and remove it afterwards, so no target runs against persistent storage unless its config names a path. text-encoder-bench ports bench-text-encoder.c++. Each case is a single call, where the C++ version measured a fetch whose handler looped 1000 times. --- build/wd_bench.bzl | 156 ++++++++++++++++++ build/wd_test.bzl | 9 + justfile | 5 + src/workerd/api/tests/BUILD.bazel | 6 + src/workerd/api/tests/text-encoder-bench.js | 35 ++++ .../api/tests/text-encoder-bench.wd-bench | 10 ++ 6 files changed, 221 insertions(+) create mode 100644 build/wd_bench.bzl create mode 100644 src/workerd/api/tests/text-encoder-bench.js create mode 100644 src/workerd/api/tests/text-encoder-bench.wd-bench diff --git a/build/wd_bench.bzl b/build/wd_bench.bzl new file mode 100644 index 00000000000..5c2b3213662 --- /dev/null +++ b/build/wd_bench.bzl @@ -0,0 +1,156 @@ +"""wd_bench definition""" + +load("@workerd//:build/lint_test.bzl", "lint_test") +load("@workerd//:build/wd_test.bzl", "wd_cli_test") + +_WORKERD = "//src/workerd/server:workerd_cross" + +def wd_bench( + src, + data = [], + name = None, + args = [], + lint = True, + compat_date = "", + generate_all_compat_flags_variant = True, + generate_all_autogates_variant = True, + tags = [], + **kwargs): + """Defines benchmarks that run `workerd bench` with a particular config. + + Args: + src: A config file defining the benchmarks, in the same format as wd_test()'s. (`name` is + derived from it if not specified.) The extensions `.wd-bench`, `.wd-test`, and `.capnp` are + permitted. + data: Additional files that the config file may embed. TypeScript files are compiled, as for + wd_test(). + args: Additional arguments to pass to `workerd bench`. + lint: If True (default), lint the JavaScript and TypeScript in `data`. + compat_date: If specified, the compat date for the default variant instead of 2000-01-01. + generate_all_compat_flags_variant: If True (default), generate @all-compat-flags variants. + generate_all_autogates_variant: If True (default), generate @all-autogates variants. + tags: Tags for all targets. + + For each variant (`name@`, `name@all-compat-flags`, `name@all-autogates`), this generates: + - : a target for `bazel run`, which measures the benchmarks and prints the results. + Arguments after `--` are passed to `workerd bench`, e.g. a filter or `--format=json`. It is + tagged `workerd-benchmark`. + - @smoke: a test that runs the benchmarks once each with `--quick`, to check that they + work. This isn't tagged `workerd-benchmark`, so CI runs it. + It also generates `name@benchmark.json`, the default variant's report as a build output, which + is off by default. + + As for wd_test(), a writable disk service named TEST_TMPDIR, e.g. for Durable Object storage, + gets a new empty directory for every run. Any other disk service uses the path in the config. + """ + data = data + [src] + + ts_srcs = [s for s in data if s.endswith(".ts")] + if ts_srcs: + data = data + [s.removesuffix(".ts") + ".js" for s in ts_srcs] + + if name == None: + name = src.removesuffix(".capnp").removesuffix(".wd-test").removesuffix(".wd-bench") + + if lint: + lint_srcs = [s for s in data if (s.endswith(".ts") or s.endswith(".mts") or s.endswith(".js") or s.endswith(".mjs")) and not s.startswith("../") and not s.startswith("//")] + if lint_srcs: + lint_test( + name = name, + eslintrc_json = "@workerd//tools:base.eslint.config.mjs", + tsconfig_json = "@workerd//tools:base.tsconfig.json", + srcs = lint_srcs, + data = ["tsconfig.json"] if ts_srcs else [], + no_copy_to_bin = [ + "@workerd//tools:base.eslint.config.mjs", + "@workerd//tools:base.tsconfig.json", + ], + ) + + default_compat_args = ["--compat-date={}".format(compat_date or "2000-01-01")] + variants = [("@", default_compat_args)] + if generate_all_compat_flags_variant: + variants.append(("@all-compat-flags", ["--compat-date=2999-12-31"])) + if generate_all_autogates_variant: + variants.append(("@all-autogates", default_compat_args + ["--all-autogates"])) + + workerd_args = ["$(location {})".format(_WORKERD), "bench", "$(location {})".format(src)] + args + for suffix, variant_args in variants: + _wd_bench_run( + name = name + suffix, + data = data + [_WORKERD], + args = workerd_args + variant_args, + tags = ["workerd-benchmark"] + tags, + **kwargs + ) + wd_cli_test( + name = name + suffix.removesuffix("@") + "@smoke", + src = src, + data = data + [_WORKERD], + args = workerd_args + variant_args + ["--quick"], + tags = tags, + **kwargs + ) + + native.genrule( + name = name + "@benchmark.json", + # workerd is a source rather than a tool so that this uses the build that the other targets + # use, as wd_cc_benchmark()'s report does, rather than building it again for the exec + # configuration. + srcs = data + [_WORKERD], + outs = [name + ".benchmark.json"], + cmd = ("TEST_TMPDIR=$$(mktemp -d) && " + + "$(location {}) bench --format=json --output=\"$@\" $(location {}) {} " + + "-dTEST_TMPDIR=$$TEST_TMPDIR; status=$$?; rm -rf $$TEST_TMPDIR; exit $$status").format( + _WORKERD, + src, + " ".join(args + default_compat_args), + ), + tags = ["off-by-default", "benchmark_report", "workerd-benchmark"] + tags, + ) + +# As for wd_test(), configs can declare a writable disk service named TEST_TMPDIR, e.g. for +# Durable Object storage. The smoke tests get Bazel's test temporary directory from wd_cli_test(); +# the run targets and the report create a new temporary directory and remove it afterwards. +_SH_TEMPLATE = """#!/bin/bash +set -euo pipefail +TEST_TMPDIR=$(mktemp -d) +trap 'rm -rf "$TEST_TMPDIR"' EXIT +"$@" -dTEST_TMPDIR="$TEST_TMPDIR" +""" + +_WINDOWS_TEMPLATE = """@echo off +setlocal +set "WD_BENCH_TMPDIR=%TEMP%\\wd-bench-%RANDOM%%RANDOM%" +mkdir "%WD_BENCH_TMPDIR%" || exit /b 1 +%* -dTEST_TMPDIR=%WD_BENCH_TMPDIR% +set "STATUS=%ERRORLEVEL%" +rmdir /s /q "%WD_BENCH_TMPDIR%" +exit /b %STATUS% +""" + +def _wd_bench_run_impl(ctx): + is_windows = ctx.target_platform_has_constraint(ctx.attr._platforms_os_windows[platform_common.ConstraintValueInfo]) + if is_windows: + executable = ctx.actions.declare_file("%s_wd_bench.bat" % ctx.label.name) + content = _WINDOWS_TEMPLATE + else: + executable = ctx.actions.declare_file("%s_wd_bench.sh" % ctx.label.name) + content = _SH_TEMPLATE + ctx.actions.write(output = executable, content = content, is_executable = True) + + runfiles = ctx.runfiles(files = ctx.files.data) + for target in ctx.attr.data: + runfiles = runfiles.merge(target[DefaultInfo].default_runfiles) + return [DefaultInfo(executable = executable, runfiles = runfiles)] + +# Bazel runs only executables that the rule creates, so this writes a script that runs its +# arguments, which wd_bench() sets to the workerd command line. +_wd_bench_run = rule( + implementation = _wd_bench_run_impl, + executable = True, + attrs = { + "data": attr.label_list(allow_files = True), + "_platforms_os_windows": attr.label(default = "@platforms//os:windows"), + }, +) diff --git a/build/wd_test.bzl b/build/wd_test.bzl index bc452e64c9a..2977aa2a89f 100644 --- a/build/wd_test.bzl +++ b/build/wd_test.bzl @@ -396,3 +396,12 @@ _wd_test = rule( "_platforms_os_windows": attr.label(default = "@platforms//os:windows"), }, ) + +def wd_cli_test(**kwargs): + """Defines a test that runs workerd with the given `args`, as wd_test()'s variants do. + + The first argument is usually `$(location //src/workerd/server:workerd_cross)`, which must also + be in `data`. `-dTEST_TMPDIR=` is appended to the arguments. For macros outside this + file, such as wd_bench(). + """ + _wd_test(**kwargs) diff --git a/justfile b/justfile index 351a97bc7d7..82173054ff4 100644 --- a/justfile +++ b/justfile @@ -141,6 +141,11 @@ update-rust package="": bench path: bazel run //src/workerd/tests:bench-{{path}} --config=benchmark +# example: just wd-bench //src/workerd/api/tests:text-encoder-bench@ '*/encode ascii*' +# Runs a wd_bench() target in an optimized build; arguments after the target go to `workerd bench`. +wd-bench target *args: + bazel run {{target}} --config=benchmark -- {{args}} + # example: just clippy dns # example: just clippy //src/workerd/server:workerd-cli clippy package="...": diff --git a/src/workerd/api/tests/BUILD.bazel b/src/workerd/api/tests/BUILD.bazel index 7fa5100a94a..50e52f15276 100644 --- a/src/workerd/api/tests/BUILD.bazel +++ b/src/workerd/api/tests/BUILD.bazel @@ -2,6 +2,7 @@ load("@aspect_rules_js//js:defs.bzl", "js_binary") load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project") load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//:build/wasm_tools_parse.bzl", "wasm_tools_parse") +load("//:build/wd_bench.bzl", "wd_bench") load("//:build/wd_test.bzl", "wd_test") wasm_tools_parse( @@ -1518,3 +1519,8 @@ sh_test( "//conditions:default": [], }), ) + +wd_bench( + src = "text-encoder-bench.wd-bench", + data = ["text-encoder-bench.js"], +) diff --git a/src/workerd/api/tests/text-encoder-bench.js b/src/workerd/api/tests/text-encoder-bench.js new file mode 100644 index 00000000000..ab0ce6850f2 --- /dev/null +++ b/src/workerd/api/tests/text-encoder-bench.js @@ -0,0 +1,35 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +// TextEncoder.encode() and encodeInto() across character types and string lengths, which +// exercises the ASCII fast path and the one- and two-byte UTF-8 paths. + +const TYPES = { + ascii: 'a', + 'one-byte': '\xff', + 'two-byte': '\u011f', +}; +const LENGTHS = [32, 256, 1024, 8192]; + +export default { + bench(b) { + const encoder = new TextEncoder(); + for (const [type, char] of Object.entries(TYPES)) { + for (const len of LENGTHS) { + const input = char.repeat(len); + b.run(`encode ${type} ${len}`, () => b.blackBox(encoder.encode(input))); + } + } + for (const [type, char] of Object.entries(TYPES)) { + for (const len of LENGTHS) { + const input = char.repeat(len); + // Enough space for any UTF-8 encoding of the input. + const buffer = new Uint8Array(len * 3); + b.run(`encodeInto ${type} ${len}`, () => + b.blackBox(encoder.encodeInto(input, buffer)) + ); + } + } + }, +}; diff --git a/src/workerd/api/tests/text-encoder-bench.wd-bench b/src/workerd/api/tests/text-encoder-bench.wd-bench new file mode 100644 index 00000000000..d9580fc1bc6 --- /dev/null +++ b/src/workerd/api/tests/text-encoder-bench.wd-bench @@ -0,0 +1,10 @@ +using Workerd = import "/workerd/workerd.capnp"; + +const unitTests :Workerd.Config = ( + services = [( + name = "text-encoder-bench", + worker = ( + modules = [(name = "worker", esModule = embed "text-encoder-bench.js")], + ), + )], +); From 398ca9b798c53ec102b31aef357f883eda27bfe9 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 19:44:35 +0000 Subject: [PATCH 16/24] Measure and subtract the bench runner's overhead Before the first group runs, the runner times three internal cases the same way as user cases: an empty function, a function that returns a resolved promise, and `blackBox(undefined)`. Their medians are kept for the rest of the process. Each case's samples have the matching empty-case cost subtracted, chosen by whether its function returned a promise, never going below zero. A case whose median before subtraction is under twice the overhead is flagged atFloor, since it mostly measures the runner (or V8 optimized its work away). Its variance is meaningless then, so it isn't also flagged highVariance. Each group reports the overhead it subtracted, so raw values can be recovered, and the text report shows it under the group name. --- src/workerd/api/bench.c++ | 162 +++++++++++++++++++++--- src/workerd/api/bench.h | 36 +++++- src/workerd/io/bench.capnp | 27 ++++ src/workerd/server/server-test.c++ | 30 ++++- src/workerd/server/server/bench-test.rs | 15 ++- src/workerd/server/server/bench.rs | 15 ++- 6 files changed, 255 insertions(+), 30 deletions(-) diff --git a/src/workerd/api/bench.c++ b/src/workerd/api/bench.c++ index 6ae168467ec..8c5ae4ef419 100644 --- a/src/workerd/api/bench.c++ +++ b/src/workerd/api/bench.c++ @@ -10,6 +10,7 @@ #include +#include #include #if _WIN32 @@ -48,6 +49,19 @@ constexpr double CONFIDENCE = 0.95; // exceeds this. constexpr double HIGH_VARIANCE_CV = 0.1; +// A case is flagged as at the measurement floor when its median wall time before subtracting the +// overhead is less than this multiple of the overhead. +constexpr double AT_FLOOR_FACTOR = 2; + +// Budgets for the cases that measure overhead, which are capped further under --quick. +constexpr uint64_t OVERHEAD_MIN_TIME_NS = 200'000'000; +constexpr uint64_t OVERHEAD_WARMUP_NS = 50'000'000; +constexpr uint32_t OVERHEAD_SAMPLES = 20; + +// The overhead is measured once per thread. `workerd bench` runs every group on the same thread, +// so all groups in a run share one measurement. +thread_local kj::Maybe measuredOverhead; + int64_t monotonicNs() { return (kj::systemPreciseMonotonicClock().now() - kj::origin()) / kj::NANOSECONDS; } @@ -116,6 +130,14 @@ uint64_t parseCount(double value, uint64_t max, kj::StringPtr what) { return static_cast(value); } +double median(kj::ArrayPtr samples) { + KJ_ASSERT(samples.size() > 0); + auto sorted = kj::heapArray(samples); + std::sort(sorted.begin(), sorted.end()); + auto middle = sorted.size() / 2; + return sorted.size() % 2 == 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2; +} + // A promise for `value`, awaiting it if it is a promise. jsg::Promise settle(jsg::Lock& js, jsg::Value value) { if (value.getHandle(js)->IsPromise()) { @@ -183,6 +205,14 @@ void fillBenchReport(const BenchGroupResult& result, bench::BenchReport::Group:: KJ_IF_SOME(error, result.error) { report.setError(error); } + KJ_IF_SOME(o, result.overhead) { + auto overhead = report.initOverhead(); + overhead.setSyncNs(o.syncNs); + overhead.setSyncCpuNs(o.syncCpuNs); + overhead.setAsyncNs(o.asyncNs); + overhead.setAsyncCpuNs(o.asyncCpuNs); + overhead.setBlackBoxNs(o.blackBoxNs); + } auto cases = report.initCases(result.cases.size()); for (auto i: kj::indices(result.cases)) { auto& from = result.cases[i]; @@ -203,10 +233,32 @@ void fillBenchReport(const BenchGroupResult& result, bench::BenchReport::Group:: continue; } to.setIterationsPerSample(from.iterationsPerSample); - auto wall = fillMetric(from.wallNs, to.initWallNs()); - fillMetric(from.cpuNs, to.initCpuNs()); - if (wall.mean > 0 && wall.stddev / wall.mean > HIGH_VARIANCE_CV) { - to.initFlags(1).set(0, bench::BenchReport::Case::Flag::HIGH_VARIANCE); + + double wallOverhead = 0, cpuOverhead = 0; + KJ_IF_SOME(o, result.overhead) { + wallOverhead = from.async ? o.asyncNs : o.syncNs; + cpuOverhead = from.async ? o.asyncCpuNs : o.syncCpuNs; + } + auto subtract = [](kj::ArrayPtr samples, double overhead) { + return KJ_MAP(sample, samples) { return kj::max(sample - overhead, 0.0); }; + }; + auto wall = fillMetric(subtract(from.wallNs, wallOverhead), to.initWallNs()); + fillMetric(subtract(from.cpuNs, cpuOverhead), to.initCpuNs()); + + using Flag = bench::BenchReport::Case::Flag; + kj::Vector flags; + if (wallOverhead > 0 && median(from.wallNs) < AT_FLOOR_FACTOR * wallOverhead) { + flags.add(Flag::AT_FLOOR); + } else if (wall.mean > 0 && wall.stddev / wall.mean > HIGH_VARIANCE_CV) { + // At the floor, what's left after subtracting the overhead is mostly noise, so its variance + // means nothing. + flags.add(Flag::HIGH_VARIANCE); + } + if (!flags.empty()) { + auto list = to.initFlags(flags.size()); + for (auto i: kj::indices(flags)) { + list.set(i, flags[i]); + } } } } @@ -307,24 +359,92 @@ jsg::Promise BenchController::runCases(jsg::Lock& js) { .error = kj::str("bench() did not register any cases."), }); } - return runCasesFrom(js, 0, {}); + return measureOverhead(js).then( + js, IoContext::current().addFunctor([self = JSG_THIS](jsg::Lock& js) mutable { + return self->runCasesFrom(js, 0, self->cases.size(), {}); + })); +} + +jsg::Promise BenchController::measureOverhead(jsg::Lock& js) { + KJ_IF_SOME(o, measuredOverhead) { + overhead = o; + return js.resolvedPromise(); + } + + // The internal cases go after the user's, and are removed once they have run. + auto begin = cases.size(); + auto addCase = [&](kj::StringPtr name, v8::Local fn, + kj::Maybe> receiver = kj::none) { + cases.add(Case{ + .name = kj::str(name), + .fn = js.v8Ref(fn), + .receiver = receiver.map([&](v8::Local r) { return js.v8Ref(r); }), + .passArgument = receiver != kj::none, + .skip = false, + .minTimeNs = quick ? QUICK_MIN_TIME_NS : OVERHEAD_MIN_TIME_NS, + .warmupNs = quick ? QUICK_WARMUP_NS : OVERHEAD_WARMUP_NS, + .samples = quick ? QUICK_SAMPLES : OVERHEAD_SAMPLES, + .batch = 0, + .internal = true, + }); + }; + auto context = js.v8Context(); + addCase("(empty)"_kj, + jsg::check(v8::Function::New(context, [](const v8::FunctionCallbackInfo&) {}))); + addCase("(empty async)"_kj, + jsg::check(v8::Function::New(context, [](const v8::FunctionCallbackInfo& info) { + auto isolate = info.GetIsolate(); + auto context = isolate->GetCurrentContext(); + auto resolver = jsg::check(v8::Promise::Resolver::New(context)); + jsg::check(resolver->Resolve(context, v8::Undefined(isolate))); + info.GetReturnValue().Set(resolver->GetPromise()); + }))); + // `blackBox(undefined)`, called as a method of this controller. + auto controller = KJ_ASSERT_NONNULL(JSG_THIS.tryGetHandle(js)); + auto blackBox = jsg::JsObject(controller).get(js, "blackBox"_kj); + KJ_ASSERT(blackBox.isFunction()); + addCase("(blackBox)"_kj, v8::Local(blackBox).As(), + v8::Local(controller)); + + return runCasesFrom(js, begin, cases.size(), {}) + .then(js, + IoContext::current().addFunctor( + [self = JSG_THIS, begin](jsg::Lock& js, BenchGroupResult result) mutable { + self->cases.truncate(begin); + for (auto& c: result.cases) { + JSG_REQUIRE(c.status == bench::BenchReport::Case::Status::OK, Error, + "Measuring the runner's overhead failed: ", c.error.orDefault(kj::str())); + } + KJ_ASSERT(result.cases.size() == 3); + KJ_ASSERT(!result.cases[0].async && result.cases[1].async); + BenchOverhead o{ + .syncNs = median(result.cases[0].wallNs), + .syncCpuNs = median(result.cases[0].cpuNs), + .asyncNs = median(result.cases[1].wallNs), + .asyncCpuNs = median(result.cases[1].cpuNs), + .blackBoxNs = median(result.cases[2].wallNs), + }; + measuredOverhead = o; + self->overhead = o; + })); } jsg::Promise BenchController::runCasesFrom( - jsg::Lock& js, size_t index, kj::Vector results) { + jsg::Lock& js, size_t index, size_t end, kj::Vector results) { kj::GlobFilter filter(caseFilter == ""_kj ? "*"_kj : caseFilter.asPtr()); - while (index < cases.size() && !filter.matches(cases[index].name)) { + while (index < end && !cases[index].internal && !filter.matches(cases[index].name)) { ++index; } - if (index == cases.size()) { - return js.resolvedPromise(BenchGroupResult{.cases = results.releaseAsArray()}); + if (index == end) { + return js.resolvedPromise( + BenchGroupResult{.cases = results.releaseAsArray(), .overhead = overhead}); } return runCase(js, index).then(js, - IoContext::current().addFunctor([self = JSG_THIS, index, results = kj::mv(results)]( + IoContext::current().addFunctor([self = JSG_THIS, index, end, results = kj::mv(results)]( jsg::Lock& js, BenchCaseResult result) mutable { results.add(kj::mv(result)); - return self->runCasesFrom(js, index + 1, kj::mv(results)); + return self->runCasesFrom(js, index + 1, end, kj::mv(results)); })); } @@ -413,6 +533,7 @@ jsg::Promise BenchController::step(jsg::Lock& js, kj::Owncalls, .wallNs = run->wallNs.releaseAsArray(), .cpuNs = run->cpuNs.releaseAsArray(), + .async = c.returnsPromise.orDefault(false), }); } break; @@ -456,15 +577,26 @@ jsg::Promise BenchController::callBatch( auto isolate = js.v8Isolate; auto context = js.v8Context(); auto fn = c.fn.getHandle(js); - auto receiver = js.v8Undefined(); + v8::Local receiver = js.v8Undefined(); + KJ_IF_SOME(r, c.receiver) { + receiver = r.getHandle(js); + } v8::Local argument = state.getHandle(js); - int argumentCount = c.setup == kj::none ? 0 : 1; + int argumentCount = c.setup != kj::none || c.passArgument ? 1 : 0; while (remaining > 0) { v8::HandleScope scope(isolate); --remaining; auto result = jsg::check(fn->Call(context, receiver, argumentCount, &argument)); - if (result->IsPromise()) { + bool isPromise = result->IsPromise(); + KJ_IF_SOME(expected, c.returnsPromise) { + JSG_REQUIRE(isPromise == expected, Error, + "The function returned a promise from some calls but not from others. It must either " + "always or never return a promise."); + } else { + c.returnsPromise = isPromise; + } + if (isPromise) { // Await the result before the next call; the batch's time includes the wait. return js.toPromise(result).then(js, IoContext::current().addFunctor([self = JSG_THIS, index, state = kj::mv(state), remaining, @@ -478,7 +610,7 @@ jsg::Promise BenchController::callBatch( void BenchController::visitForGc(jsg::GcVisitor& visitor) { for (auto& c: cases) { - visitor.visit(c.fn, c.setup, c.teardown); + visitor.visit(c.fn, c.receiver, c.setup, c.teardown); } } diff --git a/src/workerd/api/bench.h b/src/workerd/api/bench.h index e4b567c7975..104f89d8c04 100644 --- a/src/workerd/api/bench.h +++ b/src/workerd/api/bench.h @@ -23,9 +23,21 @@ struct BenchCaseResult { bench::BenchReport::Case::Status status = bench::BenchReport::Case::Status::OK; kj::Maybe error; uint64_t iterationsPerSample = 0; - // Wall and thread CPU time per call, one value per sampled batch. + // Wall and thread CPU time per call, one value per sampled batch, including the runner's + // overhead. kj::Array wallNs; kj::Array cpuNs; + // Whether the function returned a promise, which selects the overhead to subtract. + bool async = false; +}; + +// The runner's own cost per call; see `BenchReport.Overhead`. +struct BenchOverhead { + double syncNs; + double syncCpuNs; + double asyncNs; + double asyncCpuNs; + double blackBoxNs; }; // Results of one `bench()` handler. @@ -33,13 +45,15 @@ struct BenchGroupResult { // Set if `bench()` itself failed. kj::Maybe error; kj::Array cases; + // Set once overhead has been measured. + kj::Maybe overhead; }; // The exception's stack if it has one, or else its string conversion. kj::String describeBenchException(jsg::Lock& js, const jsg::Value& exception); -// Writes `result` to `report`, computing summary statistics of the samples. Doesn't set the -// group's name, which the caller knows. +// Writes `result` to `report`, computing summary statistics of the samples after subtracting the +// overhead. Doesn't set the group's name, which the caller knows. void fillBenchReport(const BenchGroupResult& result, bench::BenchReport::Group::Builder report); // A duration: a number of milliseconds, or a string with a unit, as in "500ms" or "2s". @@ -113,6 +127,10 @@ class BenchController final: public jsg::Object { struct Case { kj::String name; jsg::V8Ref fn; + // `this` for calls of `fn`; undefined if none. + kj::Maybe> receiver; + // Whether `fn` gets an argument when there is no `setup`, in which case it is undefined. + bool passArgument = false; kj::Maybe> setup; kj::Maybe> teardown; bool skip; @@ -121,6 +139,11 @@ class BenchController final: public jsg::Object { uint32_t samples; // 0 calibrates. uint64_t batch; + // A case that measures overhead, which the case filter doesn't apply to. + bool internal = false; + // Whether `fn` returns a promise, once it has been called. Every call must agree, since the + // overhead subtracted from the samples depends on it. + kj::Maybe returnsPromise; }; struct CaseRun; @@ -136,9 +159,14 @@ class BenchController final: public jsg::Object { kj::Vector cases; // Set once the cases start running; registering a case after that is an error. bool running = false; + kj::Maybe overhead; + + // Measures the overhead, unless this process has already, by running internal cases. + jsg::Promise measureOverhead(jsg::Lock& js); + // Runs cases[index] through cases[end - 1]. jsg::Promise runCasesFrom( - jsg::Lock& js, size_t index, kj::Vector results); + jsg::Lock& js, size_t index, size_t end, kj::Vector results); jsg::Promise runCase(jsg::Lock& js, size_t index); jsg::Promise step(jsg::Lock& js, kj::Own run); jsg::Promise runBatch(jsg::Lock& js, size_t index, uint64_t calls); diff --git a/src/workerd/io/bench.capnp b/src/workerd/io/bench.capnp index 4dafcf9625a..5f19ec27f65 100644 --- a/src/workerd/io/bench.capnp +++ b/src/workerd/io/bench.capnp @@ -71,6 +71,28 @@ struct BenchReport { # Set if `bench()` itself failed. cases @2 :List(Case); + + overhead @3 :Overhead; + # The runner's own cost per call, which has been subtracted from the cases' samples. + } + + struct Overhead { + # The runner's cost per call, measured once per thread by timing empty cases the same way as + # user cases, in the same units as `Case.wallNs` and `Case.cpuNs`. `workerd bench` runs every + # group on one thread, so all groups in a report share one measurement. + + syncNs @0 :Float64; + syncCpuNs @1 :Float64; + # An empty function. Subtracted from cases whose function returns a value other than a promise. + + asyncNs @2 :Float64; + asyncCpuNs @3 :Float64; + # A function that returns a resolved promise. Subtracted from cases whose function returns a + # promise. + + blackBoxNs @4 :Float64; + # A call of `blackBox()`, including the call overhead. Not subtracted, since cases call it a + # varying number of times. } struct Case { @@ -97,6 +119,11 @@ struct BenchReport { enum Flag { highVariance @0; # The coefficient of variation of the wall time exceeds 10%. + + atFloor @1; + # The median wall time before subtracting the overhead is less than twice the overhead, so + # the result is mostly the runner's own cost. V8 may have optimized away the work; pass + # results through `blackBox()`. } } diff --git a/src/workerd/server/server-test.c++ b/src/workerd/server/server-test.c++ index 8383405b668..6ab8464cae9 100644 --- a/src/workerd/server/server-test.c++ +++ b/src/workerd/server/server-test.c++ @@ -6011,6 +6011,11 @@ KJ_TEST("Server: bench command") { ` calls.asyncTeardown++; ` }, ` }); + ` let mixedCalls = 0; + ` b.run("mixed", () => (mixedCalls++ === 0 ? Promise.resolve() : undefined), { + ` ...fixed, + ` batch: 1, + ` }); ` b.run("skipped", () => { throw new Error("ran"); }, {skip: true}); ` b.run("throws", () => { throw new Error("boom"); }, fixed); ` b.run("late", () => { b.run("x", () => {}); }, fixed); @@ -6059,6 +6064,7 @@ KJ_TEST("Server: bench command") { capnp::MallocMessageBuilder reportMessage; auto report = reportMessage.initRoot(); KJ_EXPECT_LOG(DBG, "[ BENCH ] hello"); + KJ_EXPECT_LOG(DBG, "[ FAIL ] hello/mixed: Error: The function returned a promise from some"); KJ_EXPECT_LOG(DBG, "[ SKIP ] hello/skipped"); KJ_EXPECT_LOG(DBG, "[ FAIL ] hello/throws: Error: boom"); KJ_EXPECT_LOG(DBG, "[ FAIL ] hello/late: Error: BenchController.run() must be called by"); @@ -6072,13 +6078,18 @@ KJ_TEST("Server: bench command") { KJ_ASSERT(groups.size() == 1); KJ_EXPECT(groups[0].getName() == "hello"); KJ_EXPECT(!groups[0].hasError()); + KJ_ASSERT(groups[0].hasOverhead()); + auto overhead = groups[0].getOverhead(); + KJ_EXPECT(overhead.getSyncNs() > 0); + KJ_EXPECT(overhead.getAsyncNs() > 0); + KJ_EXPECT(overhead.getBlackBoxNs() > 0); auto cases = groups[0].getCases(); - KJ_ASSERT(cases.size() == 9); + KJ_ASSERT(cases.size() == 10); using Status = bench::BenchReport::Case::Status; - kj::StringPtr names[] = {"sync", "async", "state", "bare teardown", "async teardown", "skipped", - "throws", "late", "verify"}; - Status statuses[] = {Status::OK, Status::OK, Status::OK, Status::OK, Status::OK, + kj::StringPtr names[] = {"sync", "async", "state", "bare teardown", "async teardown", "mixed", + "skipped", "throws", "late", "verify"}; + Status statuses[] = {Status::OK, Status::OK, Status::OK, Status::OK, Status::OK, Status::FAILED, Status::SKIPPED, Status::FAILED, Status::FAILED, Status::OK}; for (auto i: kj::indices(names)) { KJ_EXPECT(cases[i].getName() == names[i]); @@ -6096,8 +6107,15 @@ KJ_TEST("Server: bench command") { KJ_EXPECT(wall.getMedianLow() <= wall.getMedian()); KJ_EXPECT(wall.getMedian() <= wall.getMedianHigh()); } - KJ_EXPECT(!cases[5].hasWallNs()); - KJ_EXPECT(kj::StringPtr(cases[6].getError()).startsWith("Error: boom")); + KJ_EXPECT(!cases[6].hasWallNs()); + + // The async case does almost nothing beyond awaiting a resolved promise. + bool asyncAtFloor = false; + for (auto flag: cases[1].getFlags()) { + asyncAtFloor = asyncAtFloor || flag == bench::BenchReport::Case::Flag::AT_FLOOR; + } + KJ_EXPECT(asyncAtFloor); + KJ_EXPECT(kj::StringPtr(cases[7].getError()).startsWith("Error: boom")); } { diff --git a/src/workerd/server/server/bench-test.rs b/src/workerd/server/server/bench-test.rs index 9bbe95b8b1e..5b573d5fe87 100644 --- a/src/workerd/server/server/bench-test.rs +++ b/src/workerd/server/server/bench-test.rs @@ -35,7 +35,7 @@ fn text_has_the_environment_then_a_table_for_each_group() { let mut groups = report.init_groups(2); let mut group = groups.reborrow().get(0); group.set_name("main:encoding"); - let mut cases = group.init_cases(4); + let mut cases = group.reborrow().init_cases(4); let mut case = cases.reborrow().get(0); case.set_name("encode small"); @@ -53,7 +53,9 @@ fn text_has_the_environment_then_a_table_for_each_group() { case.set_iterations_per_sample(1); case.reborrow().init_wall_ns().set_median(2e6); case.reborrow().init_cpu_ns(); - case.init_flags(1).set(0, Flag::HighVariance); + let mut flags = case.init_flags(2); + flags.set(0, Flag::HighVariance); + flags.set(1, Flag::AtFloor); let mut case = cases.reborrow().get(2); case.set_name("broken"); @@ -64,6 +66,11 @@ fn text_has_the_environment_then_a_table_for_each_group() { case.set_name("later"); case.set_status(Status::Skipped); + let mut overhead = group.init_overhead(); + overhead.set_sync_ns(5.5); + overhead.set_async_ns(250.0); + overhead.set_black_box_ns(2.0); + let mut group = groups.get(1); group.set_name("main:empty"); group.set_error("bench() did not register any cases."); @@ -76,10 +83,12 @@ workerd 2026-10-08, debug build, V8 14.0.0, compat date 2026-01-01 WARNING: This is a debug build. main:encoding + overhead per call, subtracted: 5.50 ns, 250 ns async; blackBox() 2.00 ns case median 95% CI mean cpu median samples encode small 12.3 ns 12.2 ns - 12.5 ns 12.4 ns 12.3 ns 3 x 1024 noisy 2.00 ms 0.00 ns - 0.00 ns 0.00 ns 0.00 ns 0 x 1 \ -(high variance: try more samples or a longer minTime) +(high variance: try more samples or a longer minTime; at measurement floor: pass results through \ +b.blackBox()) broken FAILED: Error: boom later skipped diff --git a/src/workerd/server/server/bench.rs b/src/workerd/server/server/bench.rs index 6b56f6d6c18..07d705072eb 100644 --- a/src/workerd/server/server/bench.rs +++ b/src/workerd/server/server/bench.rs @@ -343,13 +343,23 @@ struct Row { message: Option, } -/// Appends `group` to `out`: its name and error, then a table of its cases, the columns padded -/// to line up. +/// Appends `group` to `out`: its name, error and the overhead subtracted from its cases, then a +/// table of its cases, the columns padded to line up. fn format_group(out: &mut String, group: bench_report::group::Reader<'_>) -> Result<()> { let _ = writeln!(out, "\n{}", text(group.get_name())?); if group.has_error() { let _ = writeln!(out, " FAILED: {}", first_line(&text(group.get_error())?)); } + if group.has_overhead() { + let overhead = group.get_overhead().map_err(capnp_error)?; + let _ = writeln!( + out, + " overhead per call, subtracted: {}, {} async; blackBox() {}", + format_duration(overhead.get_sync_ns()), + format_duration(overhead.get_async_ns()), + format_duration(overhead.get_black_box_ns()) + ); + } let mut rows = Vec::new(); for case in group.get_cases().map_err(capnp_error)? { rows.push(case_row(case)?); @@ -426,6 +436,7 @@ fn statistics_row(name: String, case: bench_report::case::Reader<'_>) -> Result< for flag in case.get_flags().map_err(capnp_error)? { notes.push(match flag.map_err(|e| capnp_error(e.into()))? { Flag::HighVariance => "high variance: try more samples or a longer minTime", + Flag::AtFloor => "at measurement floor: pass results through b.blackBox()", }); } Ok(Row { From 0a6e3c23a3512f69fbd969bb8ae65181e51e9afa Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 20:09:53 +0000 Subject: [PATCH 17/24] Document workerd bench and wd_bench() Adds a section on JavaScript benchmarks to docs/benchmarking.md: the bench handler and BenchController, how cases are timed, wd_bench() and running benchmarks, reading the report, and why classes have no bench handlers. --- docs/benchmarking.md | 120 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/docs/benchmarking.md b/docs/benchmarking.md index 761a0e9641f..5a7316db166 100644 --- a/docs/benchmarking.md +++ b/docs/benchmarking.md @@ -15,3 +15,123 @@ on a defined target to obtain benchmarking results. See example in [bench-json.c++](../src/workerd/tests/bench-json.c++) +# JavaScript benchmarks + +`workerd bench` measures JavaScript in a Worker. A Worker exports a `bench` handler that +registers cases; the runtime times them, so benchmark code never needs a precise clock (which +Workers don't have). + +```js +export default { + bench(b, env, ctx) { + const encoder = new TextEncoder(); + const input = 'a'.repeat(1024); + b.run('encode 1k', () => b.blackBox(encoder.encode(input))); + b.run('sha-256 1k', async () => { + b.blackBox(await crypto.subtle.digest('SHA-256', encoder.encode(input))); + }, { minTime: '2s' }); + }, +}; +``` + +`b.run(name, fn, options)` registers a case. Cases run in order after `bench()` returns (or its +promise resolves). If `fn` returns a promise, the runner awaits it before the next call. `fn` +must either always or never return a promise; a case that does both fails. + +For each case, the runner: + +1. Calls `fn` repeatedly for `warmup` (200ms by default), so V8 can optimize it. +2. Doubles the number of calls per batch until a batch takes at least `minTime / samples` + (and at least 1ms). `batch` sets the number instead. +3. Times `samples` batches (50 by default), reading the monotonic and thread CPU clocks only + between batches. Each sample is a batch's time divided by its number of calls. The statistics + hold samples of up to an hour; a case with a longer one fails. + +Options, all optional: + +| Option | Default | Meaning | +| ---------- | ------- | ---------------------------------------------------------------------------- | +| `minTime` | `1s` | Sampling budget. A number of milliseconds, or a string like `"500ms"`, `"2s"` | +| `warmup` | `200ms` | Warmup budget | +| `samples` | 50 | Number of batches to time | +| `batch` | (none) | Calls per batch, instead of calibrating | +| `setup` | (none) | Called before each batch, untimed; its result is passed to every call of `fn` | +| `teardown` | (none) | Called after each batch with the setup result (if any), untimed | +| `skip` | false | Report the case as skipped | + +`b.blackBox(value)` returns `value` through a native call that V8 can't see into. Pass results +through it so that V8 can't optimize away the work that computed them. `b.options` has the +command line's case `filter` and whether `--quick` was given. + +## Running + +Define benchmarks in a config like a `.wd-test` file (conventionally named `.wd-bench`) and add +them to a `BUILD.bazel` file: + +```python +load("//:build/wd_bench.bzl", "wd_bench") + +wd_bench( + src = "text-encoder-bench.wd-bench", + data = ["text-encoder-bench.js"], +) +``` + +For each compat variant (`name@`, `name@all-compat-flags`, `name@all-autogates`), this defines a +target for `bazel run` and an `@smoke` test (`name@smoke`, `name@all-compat-flags@smoke`, ...) +that runs each case briefly, so CI checks that benchmarks keep working. CI doesn't run the +benchmarks themselves. As for `wd_test()`, the config can declare a writable disk service named +`TEST_TMPDIR`, for example for Durable Object storage; every run gets a new empty directory for it. +To run one in an optimized build: + +``` +just wd-bench //src/workerd/api/tests:text-encoder-bench@ +just wd-bench //src/workerd/api/tests:text-encoder-bench@ '*/encode ascii*' --format=json +``` + +Arguments after the target go to `workerd bench`, which can also be run directly: + +``` +workerd bench config.wd-bench [filter] [--format=text|json] [--output=path] [--quick] + [--compat-date=date] [--all-autogates] +``` + +`--compat-date` requires the workers in the config to omit `compatibilityDate`. + +The filter has `workerd test`'s format, optionally followed by `/`: for example +`main:encoding/encode*`. `--quick` caps the budgets, to check that cases run rather than to +measure them. + +## Reading the results + +The text report has a table per handler with each case's median time per call, the 95% +confidence interval of the median, the mean, and the median thread CPU time (mostly noise on +Windows, whose thread CPU clock advances only on scheduler ticks). `--format=json` +writes the full report, including every sample, as defined by +[bench.capnp](../src/workerd/io/bench.capnp). The JSON format isn't stable yet. + +- The runner's own cost per call (an empty function, and an empty async function for cases that + return promises) is measured once per run and subtracted from the results. It is shown under + each group's name. +- **at measurement floor**: the case took less than twice the runner's overhead, so the result is + mostly the runner's cost. V8 may have optimized away the work; pass results through + `b.blackBox()`. +- **high variance**: the coefficient of variation exceeds 10%. Try more `samples` or a longer + `minTime`, and check that the machine is quiet. + +Results compare runs on the same machine and build. They don't predict production latency. The +report records the build mode, CPU, and frequency governor, and warns about a debug or sanitizer +build or a governor other than "performance". Use `--config=benchmark` (as `just wd-bench` does). + +Tail workers don't run under `workerd bench`, since they would add their cost to every +subrequest. + +## Classes + +Only plain-object exports, like the default export above, have `bench` handlers. The methods of +`WorkerEntrypoint` and `DurableObject` classes are RPC methods, so a method named `bench` on one +is an ordinary RPC method that `workerd bench` doesn't run. To measure a Durable Object or an RPC +entrypoint, call it through a binding from a plain object's `bench` handler. + +See [text-encoder-bench.js](../src/workerd/api/tests/text-encoder-bench.js). + From 9cfe9e597d8380372f70344cd2149d25e73ad993 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 20:30:49 +0000 Subject: [PATCH 18/24] Add workerd bench --trace Tail workers are skipped under `workerd bench`, since they add their cost to every request; --trace runs them, to measure that cost, by leaving the worker factory's `tailWorkers` option on. --- docs/benchmarking.md | 4 ++-- src/workerd/server/cli/args-test.rs | 2 ++ src/workerd/server/cli/args.rs | 9 +++++++++ src/workerd/server/factory/bootstrap.c++ | 2 +- src/workerd/server/factory/worker-factory.h | 8 ++++---- src/workerd/server/server/bench.rs | 3 +++ src/workerd/server/server/bridge.rs | 5 +++-- src/workerd/server/server/config.rs | 2 +- 8 files changed, 25 insertions(+), 10 deletions(-) diff --git a/docs/benchmarking.md b/docs/benchmarking.md index 5a7316db166..484020e8fbd 100644 --- a/docs/benchmarking.md +++ b/docs/benchmarking.md @@ -93,7 +93,7 @@ Arguments after the target go to `workerd bench`, which can also be run directly ``` workerd bench config.wd-bench [filter] [--format=text|json] [--output=path] [--quick] - [--compat-date=date] [--all-autogates] + [--compat-date=date] [--all-autogates] [--trace] ``` `--compat-date` requires the workers in the config to omit `compatibilityDate`. @@ -124,7 +124,7 @@ report records the build mode, CPU, and frequency governor, and warns about a de build or a governor other than "performance". Use `--config=benchmark` (as `just wd-bench` does). Tail workers don't run under `workerd bench`, since they would add their cost to every -subrequest. +subrequest. `--trace` runs them, to measure that cost. ## Classes diff --git a/src/workerd/server/cli/args-test.rs b/src/workerd/server/cli/args-test.rs index b331e9f5015..09f8bc93061 100644 --- a/src/workerd/server/cli/args-test.rs +++ b/src/workerd/server/cli/args-test.rs @@ -175,6 +175,7 @@ fn bench_invocation() { "--quick", "--format=json", "--output=out.json", + "--trace", "svc/case", ]) .command @@ -185,6 +186,7 @@ fn bench_invocation() { assert!(bench.quick); assert_eq!(bench.format, BenchFormat::Json); assert_eq!(bench.output.as_deref(), Some("out.json")); + assert!(bench.trace); assert_eq!( bench.filter.map(|f| f.case_pattern), Some(Some("case".into())) diff --git a/src/workerd/server/cli/args.rs b/src/workerd/server/cli/args.rs index d4377abbe78..1bf1ad85d5b 100644 --- a/src/workerd/server/cli/args.rs +++ b/src/workerd/server/cli/args.rs @@ -372,6 +372,10 @@ pub struct TestArgs { } #[derive(Args, Debug)] +#[expect( + clippy::struct_excessive_bools, + reason = "one bool per command-line flag" +)] pub struct BenchArgs { /// Disable INFO-level logging, which otherwise shows uncaught exceptions. #[arg(long)] @@ -397,6 +401,10 @@ pub struct BenchArgs { #[arg(long, value_name = "path")] pub output: Option, + /// Run tail workers, which are otherwise skipped since they add their cost to every request. + #[arg(long)] + pub trace: bool, + #[arg(value_name = "filter", value_parser = parse_bench_filter)] pub filter: Option, } @@ -564,6 +572,7 @@ impl BenchArgs { case_pattern, json: self.format == BenchFormat::Json, output: self.output.clone(), + trace: self.trace, } } } diff --git a/src/workerd/server/factory/bootstrap.c++ b/src/workerd/server/factory/bootstrap.c++ index b6e1978531f..2dc92d57606 100644 --- a/src/workerd/server/factory/bootstrap.c++ +++ b/src/workerd/server/factory/bootstrap.c++ @@ -296,7 +296,7 @@ class Bootstrap { KJ_IF_SOME(compatDate, benchOptions.compat_date) { options.testCompatibilityDateOverride = kj::str(compatDate); } - options.tailWorkers = false; + options.tailWorkers = benchOptions.trace; } }; diff --git a/src/workerd/server/factory/worker-factory.h b/src/workerd/server/factory/worker-factory.h index e04a444412b..ee57f3defd4 100644 --- a/src/workerd/server/factory/worker-factory.h +++ b/src/workerd/server/factory/worker-factory.h @@ -112,10 +112,10 @@ class WorkerFactory final: private ChannelTokenHandler::Resolver { bool experimental = false; // When set, every worker uses this compatibility date and none may specify its own. kj::Maybe testCompatibilityDateOverride; - // Whether requests get tail workers. `workerd bench` turns them off: benchmarked code often - // makes a subrequest on every call (e.g. `env.SELF.fetch()`), and tail workers would add - // their cost to each. The server still looks a config worker's tails up, so that their - // config errors are reported, but does not link them. + // Whether requests get tail workers. `workerd bench` turns them off unless given `--trace`: + // benchmarked code often makes a subrequest on every call (e.g. `env.SELF.fetch()`), and tail + // workers would add their cost to each. The server still looks a config worker's tails up, so + // that their config errors are reported, but does not link them. bool tailWorkers = true; Worker::LoggingOptions loggingOptions; api::pyodide::PythonConfig pythonConfig; diff --git a/src/workerd/server/server/bench.rs b/src/workerd/server/server/bench.rs index 07d705072eb..78890622fa6 100644 --- a/src/workerd/server/server/bench.rs +++ b/src/workerd/server/server/bench.rs @@ -54,6 +54,8 @@ pub struct Options { pub json: bool, /// `--output`: where to write the report; stdout if none. pub output: Option, + /// `--trace`: run tail workers, which add their cost to every request. + pub trace: bool, } impl Options { @@ -63,6 +65,7 @@ impl Options { no_verbose: self.no_verbose, all_autogates: self.all_autogates, compat_date: self.compat_date.clone().into(), + trace: self.trace, } } diff --git a/src/workerd/server/server/bridge.rs b/src/workerd/server/server/bridge.rs index 7efa09291e0..2dbb932aeed 100644 --- a/src/workerd/server/server/bridge.rs +++ b/src/workerd/server/server/bridge.rs @@ -208,12 +208,13 @@ pub mod ffi { } /// The options of `bench` the C++ driver applies: bench mode, which they turn on, logging, - /// autogates, the compatibility date every worker gets. + /// autogates, the compatibility date every worker gets, whether requests get tail workers. #[namespace = "workerd::server::cli"] struct BenchOptions { no_verbose: bool, all_autogates: bool, compat_date: KjMaybe, + trace: bool, } /// What the binary was built from, as `workerd bench`'s report records it. @@ -301,7 +302,7 @@ pub mod ffi { fn factory_config<'a>(factory: &'a WorkerFactory) -> &'a [u8]; fn factory_experimental(factory: &WorkerFactory) -> bool; /// Whether requests get tail workers (`WorkerFactory::Options::tailWorkers`): not under - /// `workerd bench`. + /// `workerd bench`, unless given `--trace`. fn factory_tail_workers(factory: &WorkerFactory) -> bool; /// Registers the server as the resolver of channel tokens and debug-port requests. /// Tokens cannot be decoded before this. diff --git a/src/workerd/server/server/config.rs b/src/workerd/server/server/config.rs index 5cef5422e88..31f38933477 100644 --- a/src/workerd/server/server/config.rs +++ b/src/workerd/server/server/config.rs @@ -100,7 +100,7 @@ impl Factory { ffi::factory_experimental(&self.inner) } - /// Whether requests get tail workers: not under `workerd bench`. + /// Whether requests get tail workers: not under `workerd bench`, unless given `--trace`. #[must_use] pub fn tail_workers(&self) -> bool { ffi::factory_tail_workers(&self.inner) From 55cf85cc72231dd4be3fdbdf591fb9c6085b5314 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 20:31:04 +0000 Subject: [PATCH 19/24] Unit-test fillBenchReport() instead of timing The bench server test asserted that an empty async case is flagged at the floor, which depends on timing and failed under load. bench-test checks the report's statistics with fixed samples instead: which overhead is subtracted, clamping at zero, and when cases are flagged at the floor or high-variance. It also checks that a case with a sample over an hour fails. --- src/workerd/api/BUILD.bazel | 1 + src/workerd/api/bench-test.c++ | 153 +++++++++++++++++++++++++++++ src/workerd/server/server-test.c++ | 6 -- 3 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 src/workerd/api/bench-test.c++ diff --git a/src/workerd/api/BUILD.bazel b/src/workerd/api/BUILD.bazel index ed549f55e35..74e8626ba8c 100644 --- a/src/workerd/api/BUILD.bazel +++ b/src/workerd/api/BUILD.bazel @@ -596,6 +596,7 @@ wd_cc_library( "streams/pendingabort-gc-uaf-test.c++", "actor-state-test.c++", "alarm-retry-test.c++", + "bench-test.c++", "basics-test.c++", "crypto/aes-test.c++", "crypto/impl-test.c++", diff --git a/src/workerd/api/bench-test.c++ b/src/workerd/api/bench-test.c++ new file mode 100644 index 00000000000..872cafc1a75 --- /dev/null +++ b/src/workerd/api/bench-test.c++ @@ -0,0 +1,153 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +#include "bench.h" + +#include +#include + +#include + +namespace workerd::api { +namespace { + +using Flag = bench::BenchReport::Case::Flag; + +constexpr BenchOverhead OVERHEAD{ + .syncNs = 10, + .syncCpuNs = 8, + .asyncNs = 1000, + .asyncCpuNs = 900, + .blackBoxNs = 2, +}; + +BenchCaseResult makeCase(kj::ArrayPtr samples, bool async = false) { + return { + .name = kj::str("case"), + .iterationsPerSample = 100, + .wallNs = kj::heapArray(samples), + .cpuNs = kj::heapArray(samples), + .async = async, + }; +} + +// Fills a report for one case and returns the case. +bench::BenchReport::Case::Reader fill(capnp::MallocMessageBuilder& message, + BenchCaseResult c, + kj::Maybe overhead = OVERHEAD) { + BenchGroupResult result{.cases = kj::arr(kj::mv(c)), .overhead = overhead}; + auto group = message.initRoot(); + fillBenchReport(result, group); + return group.asReader().getCases()[0]; +} + +// Statistics come from a histogram with three significant figures. +bool near(double actual, double expected) { + return std::abs(actual - expected) <= expected * 0.001 + 0.001; +} + +bool hasFlag(bench::BenchReport::Case::Reader c, Flag flag) { + for (auto f: c.getFlags()) { + if (f == flag) return true; + } + return false; +} + +KJ_TEST("fillBenchReport subtracts the sync overhead from cases that don't return promises") { + capnp::MallocMessageBuilder message; + double samples[] = {100, 101, 99, 100, 100}; + auto c = fill(message, makeCase(samples)); + + auto wall = c.getWallNs(); + KJ_EXPECT(near(wall.getMedian(), 90), wall.getMedian()); + KJ_EXPECT(near(c.getCpuNs().getMedian(), 92), c.getCpuNs().getMedian()); + KJ_EXPECT(wall.getSamples()[1] == 91); + KJ_EXPECT(c.getFlags().size() == 0); + KJ_EXPECT(c.getIterationsPerSample() == 100); + + auto overhead = message.getRoot().getOverhead(); + KJ_EXPECT(overhead.getSyncNs() == 10); + KJ_EXPECT(overhead.getAsyncNs() == 1000); + KJ_EXPECT(overhead.getBlackBoxNs() == 2); +} + +KJ_TEST("fillBenchReport subtracts the async overhead from cases that return promises") { + capnp::MallocMessageBuilder message; + double samples[] = {3000, 3000, 3000}; + auto c = fill(message, makeCase(samples, /*async=*/true)); + KJ_EXPECT(near(c.getWallNs().getMedian(), 2000), c.getWallNs().getMedian()); + KJ_EXPECT(near(c.getCpuNs().getMedian(), 2100), c.getCpuNs().getMedian()); + KJ_EXPECT(!hasFlag(c, Flag::AT_FLOOR)); +} + +KJ_TEST("fillBenchReport flags cases at the floor and doesn't go below zero") { + capnp::MallocMessageBuilder message; + // A median under twice the overhead, with samples below the overhead and noise that would + // otherwise count as high variance. + double samples[] = {5, 11, 19, 15, 12}; + auto c = fill(message, makeCase(samples)); + KJ_EXPECT(hasFlag(c, Flag::AT_FLOOR)); + KJ_EXPECT(!hasFlag(c, Flag::HIGH_VARIANCE)); + KJ_EXPECT(c.getWallNs().getSamples()[0] == 0); + KJ_EXPECT(c.getWallNs().getMin() == 0); +} + +KJ_TEST("fillBenchReport flags high variance") { + capnp::MallocMessageBuilder message; + double samples[] = {100, 300, 100, 300}; + auto c = fill(message, makeCase(samples), kj::none); + KJ_EXPECT(hasFlag(c, Flag::HIGH_VARIANCE)); + KJ_EXPECT(!hasFlag(c, Flag::AT_FLOOR)); + KJ_EXPECT(!message.getRoot().hasOverhead()); +} + +KJ_TEST("fillBenchReport fails cases with calls longer than an hour") { + // The histograms hold samples of up to an hour, 3.6e12 ns. + { + capnp::MallocMessageBuilder message; + double samples[] = {100, 3.6e12, 100}; + auto c = fill(message, makeCase(samples)); + KJ_EXPECT(c.getStatus() == bench::BenchReport::Case::Status::OK); + KJ_EXPECT(near(c.getWallNs().getMax(), 3.6e12), c.getWallNs().getMax()); + } + { + capnp::MallocMessageBuilder message; + double samples[] = {100, 3.6e12 + 1, 100}; + auto c = fill(message, makeCase(samples)); + KJ_EXPECT(c.getStatus() == bench::BenchReport::Case::Status::FAILED); + KJ_EXPECT(c.getError() == + "A call took longer than an hour, the longest that the statistics can represent."); + KJ_EXPECT(!c.hasWallNs()); + } +} + +KJ_TEST("fillBenchReport writes failures and skips without statistics") { + capnp::MallocMessageBuilder message; + BenchGroupResult result{ + .error = kj::str("handler failed"), + .cases = kj::arr( + BenchCaseResult{ + .name = kj::str("broken"), + .status = bench::BenchReport::Case::Status::FAILED, + .error = kj::str("Error: boom"), + }, + BenchCaseResult{ + .name = kj::str("later"), + .status = bench::BenchReport::Case::Status::SKIPPED, + }), + }; + auto group = message.initRoot(); + fillBenchReport(result, group); + + KJ_EXPECT(group.getError() == "handler failed"); + auto cases = group.getCases(); + KJ_ASSERT(cases.size() == 2); + KJ_EXPECT(cases[0].getError() == "Error: boom"); + KJ_EXPECT(!cases[0].hasWallNs()); + KJ_EXPECT(cases[1].getStatus() == bench::BenchReport::Case::Status::SKIPPED); + KJ_EXPECT(!cases[1].hasWallNs()); +} + +} // namespace +} // namespace workerd::api diff --git a/src/workerd/server/server-test.c++ b/src/workerd/server/server-test.c++ index 6ab8464cae9..a8ba0fb03d7 100644 --- a/src/workerd/server/server-test.c++ +++ b/src/workerd/server/server-test.c++ @@ -6109,12 +6109,6 @@ KJ_TEST("Server: bench command") { } KJ_EXPECT(!cases[6].hasWallNs()); - // The async case does almost nothing beyond awaiting a resolved promise. - bool asyncAtFloor = false; - for (auto flag: cases[1].getFlags()) { - asyncAtFloor = asyncAtFloor || flag == bench::BenchReport::Case::Flag::AT_FLOOR; - } - KJ_EXPECT(asyncAtFloor); KJ_EXPECT(kj::StringPtr(cases[7].getError()).startsWith("Error: boom")); } From b398592f990d8d9ffd4afe5fc93be9f7b741298c Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 20:31:04 +0000 Subject: [PATCH 20/24] Add just new-bench `just new-bench //path/to:name-bench` scaffolds a wd_bench(): a .wd-bench config, a module with an example bench() handler, and the BUILD rule, as `just new-test` does for wd_test(). --- docs/benchmarking.md | 5 ++-- justfile | 4 +++ tools/unix/new-bench.sh | 62 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100755 tools/unix/new-bench.sh diff --git a/docs/benchmarking.md b/docs/benchmarking.md index 484020e8fbd..1912e1d5973 100644 --- a/docs/benchmarking.md +++ b/docs/benchmarking.md @@ -65,8 +65,9 @@ command line's case `filter` and whether `--quick` was given. ## Running -Define benchmarks in a config like a `.wd-test` file (conventionally named `.wd-bench`) and add -them to a `BUILD.bazel` file: +`just new-bench //path/to:name-bench` scaffolds a benchmark. Otherwise, define benchmarks in a +config like a `.wd-test` file (conventionally named `.wd-bench`) and add them to a `BUILD.bazel` +file: ```python load("//:build/wd_bench.bzl", "wd_bench") diff --git a/justfile b/justfile index 82173054ff4..c462ca50347 100644 --- a/justfile +++ b/justfile @@ -120,6 +120,10 @@ new-wpt-test test_name: new-test test_name: ./tools/unix/new-test.sh {{test_name}} +# e.g. just new-bench //src/workerd/api/tests:url-bench +new-bench bench_name: + ./tools/unix/new-bench.sh {{bench_name}} + format *files: {{ if files == "" { "python3 tools/cross/format.py" } else { "python3 tools/cross/format.py files -- " + files } }} diff --git a/tools/unix/new-bench.sh b/tools/unix/new-bench.sh new file mode 100755 index 00000000000..5cb9874247b --- /dev/null +++ b/tools/unix/new-bench.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# Scaffolds a wd_bench(): a config, a JavaScript module with a bench() handler, and the BUILD rule. + +set -euo pipefail + +BAZEL_TARGET="$1" +REPO_ROOT="$(git rev-parse --show-toplevel)" + +# Sloppily convert the Bazel target to a FS path +BENCH_PATH="$REPO_ROOT/$(echo $BAZEL_TARGET | sed s_:_/_g | sed s_//__)" +BENCH_BASENAME=$(basename $BENCH_PATH) +BENCH_DIRNAME=$(dirname $BENCH_PATH) +BUILD_FILE="$BENCH_DIRNAME/BUILD.bazel" + +cat << EOF > $BENCH_PATH.wd-bench +using Workerd = import "/workerd/workerd.capnp"; + +const unitTests :Workerd.Config = ( + services = [ + ( name = "$BENCH_BASENAME", + worker = ( + modules = [ + (name = "worker", esModule = embed "$BENCH_BASENAME.js") + ], + compatibilityFlags = ["nodejs_compat"], + ) + ), + ], +); +EOF + +git add $BENCH_PATH.wd-bench + +cat << EOF > $BENCH_PATH.js +// Copyright (c) $(date +%Y) Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +export default { + bench(b) { + b.run('example', () => b.blackBox(JSON.stringify({ a: 1 }))); + }, +}; +EOF + +git add $BENCH_PATH.js + +cat << EOF >> $BUILD_FILE + +wd_bench( + src = "$BENCH_BASENAME.wd-bench", + data = ["$BENCH_BASENAME.js"], +) +EOF + +git add $BUILD_FILE + +if ! grep -q '"//:build/wd_bench.bzl"' $BUILD_FILE; then + echo "Add this to the loads at the top of $BUILD_FILE:" + echo ' load("//:build/wd_bench.bzl", "wd_bench")' +fi From 8fdf06872a1852ec9ae0c7357b17dacf9bf2e93d Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 20:31:04 +0000 Subject: [PATCH 21/24] Mention workerd bench in AGENTS.md Lists `just new-bench` and `just wd-bench`, wd_bench() under test types, and where the bench harness lives. The server's AGENTS.md mentions `bench` alongside `test`: the subcommand, its entry points in entry.rs and run.rs, bench.rs, its loopback sockets and the smoke tests that run it. --- AGENTS.md | 5 ++++- src/workerd/server/AGENTS.md | 23 ++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b6395ccc731..c1603a214ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,6 +41,8 @@ Subdirectory `AGENTS.md` files provide component-specific context (key classes, - `just build-asan` - Build with AddressSanitizer - `just test-asan` - Run tests with AddressSanitizer - `just new-test ` - Scaffold a new test (e.g., `just new-test //src/workerd/api/tests:my-test`) +- `just new-bench ` - Scaffold a new JavaScript benchmark (`wd_bench()`) +- `just wd-bench [args]` - Run a `wd_bench()` target in an optimized build (e.g., `just wd-bench //src/workerd/api/tests:text-encoder-bench@`) - `just new-wpt-test ` - Scaffold a new WPT test - `just lint` or `just eslint` - Run ESLint on TypeScript sources - `just coverage ` - Generate code coverage report (Linux only, defaults to `//...`) @@ -55,7 +57,7 @@ Subdirectory `AGENTS.md` files provide component-specific context (key classes, - **Rust unit tests**: `-test.rs` beside the module under test, never inline; run by the crate's test target (`_test` for a crate defined with a `wd_rust_*` macro; otherwise see the package's `BUILD.bazel`). See `src/rust/AGENTS.md` §UNIT TEST FILES. - **Node.js compatibility tests**: `just node-test ` - **Web Platform Tests**: `just wpt-test ` -- **Benchmarks**: `just bench ` (e.g., `just bench mimetype`) +- **Benchmarks**: `just bench ` (e.g., `just bench mimetype`) for C++ (`wd_cc_benchmark()`); `wd_bench()` for JavaScript via `workerd bench`, whose `@smoke` tests run in CI. See `docs/benchmarking.md`. ### Running a Single Test @@ -196,6 +198,7 @@ Be aware that workerd uses tcmalloc for memory allocation in the typical case. W | Durable Object storage | `src/workerd/io/actor-cache.{h,c++}` + `actor-sqlite.{h,c++}` | LRU cache over RPC / SQLite-backed | | Streams implementation | `src/workerd/api/streams/` | Has 842-line README; dual internal/standard impl | | Bazel build rules | `build/` | Custom `wd_*` macros; `wd_test.bzl` generates 3 test variants | +| JS benchmarks | `src/workerd/api/bench.{h,c++}`, `build/wd_bench.bzl` | `bench` handler; runner `BenchController`; report schema `io/bench.capnp`; driver `server/server/bench.rs` | | TypeScript types | `types/` | Extracted from C++ RTTI + hand-written `defines/*.d.ts`; see `types/AGENTS.md` for detailed typings guidance | | V8 patches | `patches/v8/` | 33 patches; see `docs/v8-updates.md` | diff --git a/src/workerd/server/AGENTS.md b/src/workerd/server/AGENTS.md index 4625d5c0802..18c6c34c26a 100644 --- a/src/workerd/server/AGENTS.md +++ b/src/workerd/server/AGENTS.md @@ -4,25 +4,25 @@ The `workerd` binary and its server. `:workerd` (`workerd.rs`, this package) is a Rust binary over three crates in this directory: -- `cli/` -- the `workerd-cli` crate (declared in this package's `BUILD.bazel`, sources globbed from `cli/`): parses the command line (clap), produces the encoded config (schema files are compiled by this package's `config-compiler.c++`), handles `compile`, `--watch` and re-exec, and hands `serve` and `test` (which `fuzzilli` and `make-pyodide-baseline-snapshot` are built on) to the server crate's `entry` module. -- `server/` -- the `workerd-server` crate: the single-tenant server. It interprets the config, builds the service graph, binds the sockets, runs the listeners, actors and dynamic workers, drains on SIGTERM, and runs `workerd test`. `Error` is `cxx::KjError`; `bridge.rs` is the one module that allows `unsafe`. -- `factory/` -- C++: `WorkerFactory` (`worker-factory*`), everything that needs the isolate, driven by the server through the cxx bridge in `server/bridge.rs`; and `bootstrap.{h,c++}`, the C++ half of a command. `with_process_context` sets up the process's logging (the JSON logger, when the config asks for structured logging) on the C++ stack around the Rust command; `bootstrap` sets up perfetto, autogates and V8 on the Rust-owned event loop and builds the `WorkerFactory`, which owns them, from the encoded config and the command-line options for `server/entry.rs`, which owns the loop (`kj_rs_tokio::Runtime`) and blocks on `server_serve` / `server_test`. +- `cli/` -- the `workerd-cli` crate (declared in this package's `BUILD.bazel`, sources globbed from `cli/`): parses the command line (clap), produces the encoded config (schema files are compiled by this package's `config-compiler.c++`), handles `compile`, `--watch` and re-exec, and hands `serve`, `test` (which `fuzzilli` and `make-pyodide-baseline-snapshot` are built on) and `bench` to the server crate's `entry` module. +- `server/` -- the `workerd-server` crate: the single-tenant server. It interprets the config, builds the service graph, binds the sockets, runs the listeners, actors and dynamic workers, drains on SIGTERM, and runs `workerd test` and `workerd bench`. `Error` is `cxx::KjError`; `bridge.rs` is the one module that allows `unsafe`. +- `factory/` -- C++: `WorkerFactory` (`worker-factory*`), everything that needs the isolate, driven by the server through the cxx bridge in `server/bridge.rs`; and `bootstrap.{h,c++}`, the C++ half of a command. `with_process_context` sets up the process's logging (the JSON logger, when the config asks for structured logging) on the C++ stack around the Rust command; `bootstrap` sets up perfetto, autogates and V8 on the Rust-owned event loop and builds the `WorkerFactory`, which owns them, from the encoded config and the command-line options for `server/entry.rs`, which owns the loop (`kj_rs_tokio::Runtime`) and blocks on `server_serve` / `server_test` / `server_bench`. This package keeps the config schema (`workerd.capnp`, `compiled-bindings.capnp`), the config compiler, the `WorkerdApi` and the C++ libraries the factory is built from (alarm scheduler, channel tokens, container client, facet tree index, fallback service, actor IDs, JSON logger, Pyodide, V8 platform), and their C++ unit tests. -**The Rust/C++ boundary.** The factory keeps everything that touches the isolate: compiling a worker (`WorkerdApi`, compatibility flags, extensions, modules), starting a request on it, constructing actors with their storage, alarms and containers, the capnp RPC servers, channel-token encoding and the inspector. It also keeps the single-tenant policy objects, which live in this directory because the production runtime supplies its own: the unlimited `LimitEnforcer`, the clock-corrected `TimerChannel`, the null `IsolateLimitEnforcer`, the default observers. The server owns everything else: config interpretation, bindings and channel numbering, services, listeners, actor lifecycle and eviction, dynamic workers, drain and the test runner. Every bridge function on the C++ side is a free function taking the object first; one that can throw is declared fallible (a C++ exception becomes a `Result` error), the rest do not throw. A `KjOwn` moves ownership in the direction of the call, a `Box` of a Rust type handed to C++ is a cheap `Rc` or `Weak` handle C++ may hold. +**The Rust/C++ boundary.** The factory keeps everything that touches the isolate: compiling a worker (`WorkerdApi`, compatibility flags, extensions, modules), starting a request on it, constructing actors with their storage, alarms and containers, the capnp RPC servers, channel-token encoding and the inspector. It also keeps the single-tenant policy objects, which live in this directory because the production runtime supplies its own: the unlimited `LimitEnforcer`, the clock-corrected `TimerChannel`, the null `IsolateLimitEnforcer`, the default observers. The server owns everything else: config interpretation, bindings and channel numbering, services, listeners, actor lifecycle and eviction, dynamic workers, drain, the test runner and `workerd bench`'s report. Every bridge function on the C++ side is a free function taking the object first; one that can throw is declared fallible (a C++ exception becomes a `Result` error), the rest do not throw. A `KjOwn` moves ownership in the direction of the call, a `Box` of a Rust type handed to C++ is a cheap `Rc` or `Weak` handle C++ may hold. Crossing the boundary: - **Bindings cross as one message** (`server/bindings.rs`, `server/channels.rs`; their module docs have the detail). `compile_bindings` interprets `Worker.bindings` into `Globals` (`compiled-bindings.capnp`), every capability a channel number, and `loopback_globals` does the same for `ctx.exports`; the factory compiles the message into `env` and `ctx.exports` under the isolate lock, and at request time the runtime asks the worker's `ChannelFactory` (Rust) for the channel behind a number. A dynamic worker's `env` arrives as KJ capabilities already, so the factory keeps them as the worker's own tables (`CompiledWorker::Impl`), together with its global outbound and tails, and asks the Rust `ChannelFactory` only for the channel numbers past them: the worker's `ctx.exports`, which the server numbers from the counts `WorkerInfo` reports. - **Workflows are wired after every service exists.** `workflowsEngine` gets two passes in `server/config.rs`: `collect_workflow_configs` (with the actor configs) checks it and synthesizes the `Durable` config of each valid Workflow's namespace, keyed `miniflare-workflows-` and carrying the `Workflow` (name, class, `bindingService`) the later pass reads; `init_workflows` (after the services) resolves the engine's class and the `bindingService`, builds the actor props (`workflowClass` stub, `workflowClassName`, `workflowName`) with the `frankenvalue_*` shims and creates the namespace on the app worker (`WorkerService::init_workflow_namespace`), its storage in the bindingService worker's directory. The bindings compiler gives each configured Workflow a subrequest channel to its bindingService (`workflow_binding_channels`), the inner fetcher of the Workflow's wrapped `ctx.exports` binding; a Workflow class takes no loopback channel. - **Background tasks run on the KJ loop** (`server/tasks.rs`, whose module doc says why): spawn with `Factory::spawn` / `spawn_detached`, which put the future in the factory's `kj::TaskSet`; never with `kj_rs_tokio::spawn`. -- **`loopback:` addresses** (`server/listen/loopback.rs`) are in-process connections, refused unless the registry is enabled, which only `workerd test` and the in-process server (`server/in_process.rs`) do. An HTTP or TCP listener serves a connection with its hang-up signal (`listen/mod.rs`, `hangup`) and a dialer hands one to kj-hyper's client: `LoopbackStream::hangup`, or a socket's `kj_rs_io::when_write_disconnected`. +- **`loopback:` addresses** (`server/listen/loopback.rs`) are in-process connections, refused unless the registry is enabled, which only `workerd test`, `workerd bench` and the in-process server (`server/in_process.rs`) do. An HTTP or TCP listener serves a connection with its hang-up signal (`listen/mod.rs`, `hangup`) and a dialer hands one to kj-hyper's client: `LoopbackStream::hangup`, or a socket's `kj_rs_io::when_write_disconnected`. - **`--control-fd`** (`Control`, `server/run.rs`): `listen-inspector` and `listen` events, one JSON object per line with `port` last; a config the `Reporter` refuses gets no `listen` event. The server writes to a duplicate of the descriptor (`cli/socket_fd.rs`). - **Header names the config chooses are table headers** (`buildHeaderTable`, `factory/worker-factory.c++`): the factory's `kj::HttpHeaderTable` holds every header the config's `HttpOptions` name, so kj writes them as the config spells them and the rewriter (`services/rewriter.rs`, whose module doc has the rest) edits `kj::HttpHeaders` by name. - **Logging** (`server/log.rs`): log from the server with `tracing::error!` / `warn!` / `info!`, which become KJ log lines through `kj_log`. Config errors and warnings go to the `Reporter` (`server/config.rs`), which `entry.rs` prints to stderr, as it does `Tests failed!` and the run's failure (`*** Uncaught exception ***`): as they are, or, under structured logging, as lines of the JSON logger's format (`json_log_to_stderr`, over `buildJsonLogMessage`). The logger's own lines (`KJ_LOG`, `tracing`, the workers' consoles) go to stdout; what supervises the process (miniflare) reads the reason of a failed start from stderr. An error is printed the way KJ prints an exception through `exception_text` (`kj::str`: `file:line: type: description`; a `KjError` carries no stack trace, and one made in Rust has the bridge's location). Every error is reported and the server then does not serve, unless `--watch` is on. -Bazel labels: `//src/workerd/server:workerd` (and `:workerd_cross`, which `wd_test` runs), `//src/workerd/server:workerd-cli`, `//src/workerd/server/server:workerd-server` (its bridge library `:bridge.rs@cxx`, whose header is ``), `//src/workerd/server/factory:worker-factory` / `:bootstrap` (header-only `:worker-factory-hdrs` / `:bootstrap-hdrs` for the bridges). The Rust packages hold only `.rs` files; the bridges take their C++ headers from `cxx_bridge_deps`. +Bazel labels: `//src/workerd/server:workerd` (and `:workerd_cross`, which `wd_test` and `wd_bench` run), `//src/workerd/server:workerd-cli`, `//src/workerd/server/server:workerd-server` (its bridge library `:bridge.rs@cxx`, whose header is ``), `//src/workerd/server/factory:worker-factory` / `:bootstrap` (header-only `:worker-factory-hdrs` / `:bootstrap-hdrs` for the bridges). The Rust packages hold only `.rs` files; the bridges take their C++ headers from `cxx_bridge_deps`. ## KEY FILES @@ -64,9 +64,10 @@ Bazel labels: `//src/workerd/server:workerd` (and `:workerd_cross`, which `wd_te | File | Role | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `lib.rs` | Crate root: `Error` / `Result` | -| `bridge.rs` | The cxx bridge (`workerd::server`) to `factory/worker-factory.h` and `factory/bootstrap.h`. `extern "C++"`: the factory (compile, start request, actors and storage, RPC, tokens, inspector, `factory_spawn`), the bootstrap, the process's logging and its exit; `extern "Rust"`: the channel objects, the channel factory, the pending command. Ownership rules in its `//!` | -| `entry.rs` | `serve` / `test`: the process's schedule (`with_process_context`, the `kj_rs_tokio::Runtime`, `bootstrap`, `block_on`, exit), `server_serve` / `server_test`, the `Watch` future the command line supplies, printing config errors and the exit status they make, SIGTERM drain, `--watch` reload, shutdown order (settle tasks, drop the factory and with it V8 and the perfetto session, then the Runtime) | -| `run.rs` | `run()` / `test()`: bind sockets, start the inspector (before any isolate exists), build the graph, start the listeners (not for a config the `Reporter` refuses), drain, control-fd events, the check that every `--directory-path` / `--external-addr` override named a service | +| `bridge.rs` | The cxx bridge (`workerd::server`) to `factory/worker-factory.h` and `factory/bootstrap.h`. `extern "C++"`: the factory (compile, start request, bench, actors and storage, RPC, tokens, inspector, `factory_spawn`), the bootstrap, the process's logging and its exit; `extern "Rust"`: the channel objects, the channel factory, the pending command. Ownership rules in its `//!` | +| `entry.rs` | `serve` / `test` / `bench`: the process's schedule (`with_process_context`, the `kj_rs_tokio::Runtime`, `bootstrap`, `block_on`, exit), `server_serve` / `server_test` / `server_bench`, the `Watch` future the command line supplies, printing config errors and the exit status they make, SIGTERM drain, `--watch` reload, shutdown order (settle tasks, drop the factory and with it V8 and the perfetto session, then the Runtime) | +| `run.rs` | `run()` / `test()` / `bench()`: bind sockets, start the inspector (before any isolate exists), build the graph, start the listeners (not for a config the `Reporter` refuses), drain, control-fd events, the check that every `--directory-path` / `--external-addr` override named a service | +| `bench.rs` | `workerd bench`'s options and report (`io/bench.capnp`): a group for each `bench()` handler `run::bench` runs (through the factory's `worker_bench`), progress logged as `workerd test` logs it, the environment, and the report as a table or as JSON | | `config.rs` | `Server`: the service graph from the config in three passes (actor configs, construction, link); `Factory`, the Rust owner of the C++ `WorkerFactory`; `Reporter`; resolves channel tokens and debug-port requests | | `channels.rs` | The handles C++ holds and the runtime reaches into the server through: `SubrequestChannel` (an `Rc`), `ActorClassChannel` (an `Rc`), `ChannelFactory` (a worker's `LinkedChannels`), `WorkerStub`, and the `Weak` back-pointers (`ServerHandle`, `ActorNamespaceHandle`, `ActorHooks`). Single-threaded | | `bindings.rs` | `compile_bindings` / `loopback_globals`: `Worker.bindings` and `ctx.exports` to the `Globals` message and the channel tables the link stage fills | @@ -95,8 +96,8 @@ Bazel labels: `//src/workerd/server:workerd` (and `:workerd_cross`, which `wd_te - **Rust unit tests**: a module's tests are in the `-test.rs` beside it (`config-test.rs` for `config.rs`, `listen/mod-test.rs` for `listen/mod.rs`), compiled only into the crates' test targets (`//src/workerd/server/server:workerd-server_test`, `//src/workerd/server:workerd-cli_test`); see `src/rust/AGENTS.md`, UNIT TEST FILES. The server crate's test binary links `//src/workerd/util:setup-async-io` because the C++ it reaches calls `kj::setupAsyncIo()`. - **C++ unit tests** (`*-test.c++`, `kj_test`): alarm scheduler, channel tokens, container client, facet tree index, fallback service, JSON logger, actor IDs. -- **`server-test.c++`** (`//src/workerd/server:server-test@`): the server end to end, in the test process. Each `TestServer` builds a `WorkerFactory` on the test's V8, timer, network and a temp directory, and hands it to `InProcessServer` (`server/in_process.rs`, through the server crate's bridge), which runs `run::run` / `run::test` on it and queues the config errors and warnings for the test. No connection but UDP's leaves the process: the config's socket and external addresses become `loopback:` names, the test connects to the server's sockets and accepts the server's outbound connections by name (the `network` services' too), and the factory's own C++ clients (Docker, the debug port) dial the same names through the test's `kj::Network`. The factory's timer is the test's mock `kj::TimerImpl`, which `test.wait(n)` advances; the server's own delays run on it too (`Factory::sleep` / `Factory::now`, `server/config.rs`). An unexpected error log, config error or warning fails the case, as does a factory still referenced once the server is closed. Add a case here for every new config error or warning. -- **`tests/`**: `server-harness.mjs`, a Node.js harness spawning the `workerd` binary for E2E tests; it reads the `listen` events off `--control-fd`. Subdirectories: `compile-tests`, `container-client`, `container-shutdown`, `extensions`, `inspector`, `module-fallback`, `python`, `socket-close`, `structured-logging`, `udp-connect`, `udp-connect-ts-streams`, `udp-datagram-drop`, `unsafe-eval`, `unsafe-module`, `weakref`, `wrapped-binding-serialization`. Every `.wd-test` in the repository also runs through `workerd test` (`wd_test` in `build/wd_test.bzl`, over `:workerd_cross`). +- **`server-test.c++`** (`//src/workerd/server:server-test@`): the server end to end, in the test process. Each `TestServer` builds a `WorkerFactory` on the test's V8, timer, network and a temp directory, and hands it to `InProcessServer` (`server/in_process.rs`, through the server crate's bridge), which runs `run::run` / `run::test` / `run::bench` on it and queues the config errors and warnings for the test. No connection but UDP's leaves the process: the config's socket and external addresses become `loopback:` names, the test connects to the server's sockets and accepts the server's outbound connections by name (the `network` services' too), and the factory's own C++ clients (Docker, the debug port) dial the same names through the test's `kj::Network`. The factory's timer is the test's mock `kj::TimerImpl`, which `test.wait(n)` advances; the server's own delays run on it too (`Factory::sleep` / `Factory::now`, `server/config.rs`). An unexpected error log, config error or warning fails the case, as does a factory still referenced once the server is closed. Add a case here for every new config error or warning. +- **`tests/`**: `server-harness.mjs`, a Node.js harness spawning the `workerd` binary for E2E tests; it reads the `listen` events off `--control-fd`. Subdirectories: `compile-tests`, `container-client`, `container-shutdown`, `extensions`, `inspector`, `module-fallback`, `python`, `socket-close`, `structured-logging`, `udp-connect`, `udp-connect-ts-streams`, `udp-datagram-drop`, `unsafe-eval`, `unsafe-module`, `weakref`, `wrapped-binding-serialization`. Every `.wd-test` in the repository also runs through `workerd test` (`wd_test` in `build/wd_test.bzl`, over `:workerd_cross`), and every `wd_bench()` config through `workerd bench --quick`, in its `@smoke` tests (`build/wd_bench.bzl`). - **kj-hyper contract tests** (`src/rust/cxx/kj-hyper/tests`): HTTP and WebSocket semantics against kj's interfaces. - `:rust-io-link-check` inspects the linked binary's symbols (unix): kj's own `setupAsyncIo()` must not be linked in. From 615b26dcfbfcc7b0b45f5b01d65e2d5b4c7daddb Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 8 Oct 2026 20:37:45 +0000 Subject: [PATCH 22/24] Make the histogram crate's tests large The Node.js parity tests take about 10 seconds in an unoptimized build, since Rust tests run serially, so under load they exceeded a medium test's 15-second timeout. Optimized, they take under a second. --- src/rust/histogram/BUILD.bazel | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rust/histogram/BUILD.bazel b/src/rust/histogram/BUILD.bazel index d7bfc21c8d9..93c6ebb7238 100644 --- a/src/rust/histogram/BUILD.bazel +++ b/src/rust/histogram/BUILD.bazel @@ -5,6 +5,9 @@ wd_rust_crate( cxx_bridge_src = "ffi.rs", test_compile_data = ["testdata/node-golden.json"], test_deps = ["@crates_vendor//:serde_json"], + # The Node.js parity tests check every query on every dataset and layout: about 10 seconds in + # an unoptimized build. + test_size = "large", visibility = ["//visibility:public"], deps = [ "@crates_vendor//:libm", From 4dd99270d2b79fc725b564009985f571bffcfe3f Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 9 Oct 2026 05:05:08 +0000 Subject: [PATCH 23/24] Leave just generate-types' snapshot writable The recipe copies the snapshot from bazel-bin, whose outputs are read-only, and cp keeps their modes, so types/generated-snapshot/ experimental was left read-only. Git then couldn't replace the files in it, and a checkout or rebase that changed them failed with "unable to unlink old ...: Permission denied". The recipe already made the directory writable before removing it; it now does so after the copy too. --- justfile | 1 + 1 file changed, 1 insertion(+) diff --git a/justfile b/justfile index c462ca50347..fd4001c6890 100644 --- a/justfile +++ b/justfile @@ -165,6 +165,7 @@ generate-types: rm -rf types/generated-snapshot mkdir -p types/generated-snapshot cp -r bazel-bin/types/definitions/. types/generated-snapshot/ + chmod -R u+w types/generated-snapshot update-reported-node-version: python3 tools/update_node_version.py src/workerd/api/node/node-version.h From 48763398ca9765f80479463cb3b1f2b74e77e790 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 10 Oct 2026 11:56:10 +0000 Subject: [PATCH 24/24] Default workerd bench to the newest compat date Benchmarks usually measure current behavior, but wd_bench()'s default variant copied wd_test()'s 2000-01-01, so `just wd-bench ...@` measured year-2000 semantics, and @all-compat-flags (2999-12-31) enables flags whose dates haven't arrived. `workerd bench --compat-date=latest` now overrides every worker's date with the newest one this build supports: today's date, or MAXIMUM_COMPATIBILITY_DATE if that's earlier, since the maximum is set a little into the future. Without --compat-date, workers that omit compatibilityDate get that date rather than a config error, through the worker factory's new `defaultCompatibilityDate` option; workers that set a date keep it. The dates are resolved once, so that the workers and the report agree: the report records the resolved override in Environment.compatDate and the default in the new Environment.defaultCompatDate. wd_bench()'s default and @all-autogates variants now pass --compat-date=latest unless compat_date is set. Since the date is only known at run time, it can't come from the macro itself. --- build/wd_bench.bzl | 10 ++- docs/benchmarking.md | 10 ++- src/workerd/api/tests/BUILD.bazel | 20 +++++- .../tests/bench-default-compat-date-test.js | 16 +++++ .../bench-default-compat-date-test.wd-bench | 12 ++++ src/workerd/io/bench.capnp | 6 +- src/workerd/io/compatibility-date-test.c++ | 10 +++ src/workerd/io/compatibility-date.c++ | 6 ++ src/workerd/io/compatibility-date.h | 4 ++ src/workerd/server/cli/args.rs | 4 +- src/workerd/server/factory/bootstrap.c++ | 7 +++ src/workerd/server/factory/bootstrap.h | 1 + src/workerd/server/factory/worker-factory.c++ | 4 ++ src/workerd/server/factory/worker-factory.h | 3 + src/workerd/server/server-test.c++ | 43 +++++++++++++ src/workerd/server/server/bench-test.rs | 63 +++++++++++++++++++ src/workerd/server/server/bench.rs | 57 ++++++++++++++--- src/workerd/server/server/bridge.rs | 9 ++- src/workerd/server/server/entry.rs | 19 ++++-- 19 files changed, 283 insertions(+), 21 deletions(-) create mode 100644 src/workerd/api/tests/bench-default-compat-date-test.js create mode 100644 src/workerd/api/tests/bench-default-compat-date-test.wd-bench diff --git a/build/wd_bench.bzl b/build/wd_bench.bzl index 5c2b3213662..c8eb6aca09e 100644 --- a/build/wd_bench.bzl +++ b/build/wd_bench.bzl @@ -26,12 +26,14 @@ def wd_bench( wd_test(). args: Additional arguments to pass to `workerd bench`. lint: If True (default), lint the JavaScript and TypeScript in `data`. - compat_date: If specified, the compat date for the default variant instead of 2000-01-01. + compat_date: If specified, the compat date for the default variant instead of `latest`, the + newest that the build supports. generate_all_compat_flags_variant: If True (default), generate @all-compat-flags variants. generate_all_autogates_variant: If True (default), generate @all-autogates variants. tags: Tags for all targets. - For each variant (`name@`, `name@all-compat-flags`, `name@all-autogates`), this generates: + The variants are `name@` (`compat_date`), `name@all-compat-flags` (2999-12-31), and + `name@all-autogates` (`compat_date` with all autogates). For each variant, this generates: - : a target for `bazel run`, which measures the benchmarks and prints the results. Arguments after `--` are passed to `workerd bench`, e.g. a filter or `--format=json`. It is tagged `workerd-benchmark`. @@ -67,7 +69,9 @@ def wd_bench( ], ) - default_compat_args = ["--compat-date={}".format(compat_date or "2000-01-01")] + # Benchmarks usually measure current behavior, so unlike wd_test(), the default variant uses + # the newest compat date that the build supports rather than the oldest. + default_compat_args = ["--compat-date={}".format(compat_date or "latest")] variants = [("@", default_compat_args)] if generate_all_compat_flags_variant: variants.append(("@all-compat-flags", ["--compat-date=2999-12-31"])) diff --git a/docs/benchmarking.md b/docs/benchmarking.md index 1912e1d5973..a8b8bc949eb 100644 --- a/docs/benchmarking.md +++ b/docs/benchmarking.md @@ -78,6 +78,11 @@ wd_bench( ) ``` +Unlike `wd_test()`, the default variant `name@` uses the newest compat date that the build +supports (`--compat-date=latest`) rather than the oldest, since benchmarks usually measure current +behavior; `compat_date` sets another. `name@all-compat-flags` uses 2999-12-31, and +`name@all-autogates` uses the default variant's date with all autogates enabled. + For each compat variant (`name@`, `name@all-compat-flags`, `name@all-autogates`), this defines a target for `bazel run` and an `@smoke` test (`name@smoke`, `name@all-compat-flags@smoke`, ...) that runs each case briefly, so CI checks that benchmarks keep working. CI doesn't run the @@ -94,10 +99,13 @@ Arguments after the target go to `workerd bench`, which can also be run directly ``` workerd bench config.wd-bench [filter] [--format=text|json] [--output=path] [--quick] - [--compat-date=date] [--all-autogates] [--trace] + [--compat-date=date|latest] [--all-autogates] [--trace] ``` `--compat-date` requires the workers in the config to omit `compatibilityDate`. +`--compat-date=latest` is the newest date that the build supports: today's date, or the build's +maximum compatibility date if that's earlier. Without `--compat-date`, workers that omit +`compatibilityDate` get that date, so that benchmarks measure current behavior. The filter has `workerd test`'s format, optionally followed by `/`: for example `main:encoding/encode*`. `--quick` caps the budgets, to check that cases run rather than to diff --git a/src/workerd/api/tests/BUILD.bazel b/src/workerd/api/tests/BUILD.bazel index 50e52f15276..390ae74b5f5 100644 --- a/src/workerd/api/tests/BUILD.bazel +++ b/src/workerd/api/tests/BUILD.bazel @@ -3,7 +3,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project") load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//:build/wasm_tools_parse.bzl", "wasm_tools_parse") load("//:build/wd_bench.bzl", "wd_bench") -load("//:build/wd_test.bzl", "wd_test") +load("//:build/wd_test.bzl", "wd_cli_test", "wd_test") wasm_tools_parse( name = "js-string-builtins.wasm", @@ -1524,3 +1524,21 @@ wd_bench( src = "text-encoder-bench.wd-bench", data = ["text-encoder-bench.js"], ) + +# wd_bench() always passes --compat-date, so this runs `workerd bench` without it, to check that +# workers that omit compatibilityDate get the newest date. +wd_cli_test( + name = "bench-default-compat-date-test", + src = "bench-default-compat-date-test.wd-bench", + args = [ + "$(location //src/workerd/server:workerd_cross)", + "bench", + "$(location bench-default-compat-date-test.wd-bench)", + "--quick", + ], + data = [ + "bench-default-compat-date-test.js", + "bench-default-compat-date-test.wd-bench", + "//src/workerd/server:workerd_cross", + ], +) diff --git a/src/workerd/api/tests/bench-default-compat-date-test.js b/src/workerd/api/tests/bench-default-compat-date-test.js new file mode 100644 index 00000000000..6f263ca631d --- /dev/null +++ b/src/workerd/api/tests/bench-default-compat-date-test.js @@ -0,0 +1,16 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +export default { + bench(b, _env, ctx) { + // `ctx.exports` exists from enable_ctx_exports' date, 2025-11-17, so it shows that the worker + // got a recent date rather than an old one. + if (ctx.exports === undefined) { + throw new Error( + 'Expected the newest compat date, which enables ctx.exports.' + ); + } + b.run('noop', () => b.blackBox(1)); + }, +}; diff --git a/src/workerd/api/tests/bench-default-compat-date-test.wd-bench b/src/workerd/api/tests/bench-default-compat-date-test.wd-bench new file mode 100644 index 00000000000..d581a49abd0 --- /dev/null +++ b/src/workerd/api/tests/bench-default-compat-date-test.wd-bench @@ -0,0 +1,12 @@ +using Workerd = import "/workerd/workerd.capnp"; + +# The worker omits compatibilityDate, and the test runs `workerd bench` without --compat-date, so +# the worker gets the newest date that the build supports. +const unitTests :Workerd.Config = ( + services = [( + name = "bench-default-compat-date-test", + worker = ( + modules = [(name = "worker", esModule = embed "bench-default-compat-date-test.js")], + ), + )], +); diff --git a/src/workerd/io/bench.capnp b/src/workerd/io/bench.capnp index 5f19ec27f65..10c2be61d8f 100644 --- a/src/workerd/io/bench.capnp +++ b/src/workerd/io/bench.capnp @@ -54,7 +54,11 @@ struct BenchReport { # The CPU frequency governor, if known (Linux). compatDate @5 :Text; - # The `--compat-date` override, if any. + # The `--compat-date` override, if any, with `latest` resolved to a date. + + defaultCompatDate @8 :Text; + # Without `--compat-date`, the compat date of workers that don't specify `compatibilityDate`: + # the newest that this build supports. allAutogates @6 :Bool; warnings @7 :List(Text); diff --git a/src/workerd/io/compatibility-date-test.c++ b/src/workerd/io/compatibility-date-test.c++ index b430e93a201..ede8f3eed6b 100644 --- a/src/workerd/io/compatibility-date-test.c++ +++ b/src/workerd/io/compatibility-date-test.c++ @@ -69,6 +69,16 @@ KJ_TEST("compatibility date parsing") { expectNoParse("202-05-07"); } +KJ_TEST("newest compatibility date") { + auto newest = newestCompatDateStr(); + KJ_EXPECT(normalizeCompatDate(newest) != kj::none, newest); + auto today = currentDateStr(); + kj::StringPtr maximum = MAXIMUM_COMPATIBILITY_DATE; + // Dates in this format compare like their strings. + kj::StringPtr expected = today.asPtr() < maximum ? today.asPtr() : maximum; + KJ_EXPECT(newest == expected, newest, today, maximum); +} + KJ_TEST("compatibility flag parsing") { auto expectCompileCompatibilityFlags = [](kj::StringPtr compatDate, kj::ArrayPtr featureFlags, diff --git a/src/workerd/io/compatibility-date.c++ b/src/workerd/io/compatibility-date.c++ index 259bdaad010..cbc6cef0ad8 100644 --- a/src/workerd/io/compatibility-date.c++ +++ b/src/workerd/io/compatibility-date.c++ @@ -99,6 +99,12 @@ kj::String currentDateStr() { return CompatDate::today().toString(); } +kj::String newestCompatDateStr() { + auto maximum = KJ_ASSERT_NONNULL(CompatDate::parse(MAXIMUM_COMPATIBILITY_DATE)); + auto today = CompatDate::today(); + return (today < maximum ? today : maximum).toString(); +} + static void compileCompatibilityFlags(kj::StringPtr compatDate, kj::HashSet flagSet, CompatibilityFlags::Builder output, diff --git a/src/workerd/io/compatibility-date.h b/src/workerd/io/compatibility-date.h index 9c32cce81cb..e3bb5edd561 100644 --- a/src/workerd/io/compatibility-date.h +++ b/src/workerd/io/compatibility-date.h @@ -70,6 +70,10 @@ kj::Maybe normalizeCompatDate(kj::StringPtr date); // Returns the current date as a string formatted by CompatDate. kj::String currentDateStr(); +// Returns the newest compatibility date that a Worker may set with CODE_VERSION validation: the +// earlier of today's date and MAXIMUM_COMPATIBILITY_DATE. +kj::String newestCompatDateStr(); + // These values come from src/workerd/io/compatibility-date.capnp static constexpr uint64_t COMPAT_ENABLE_FLAG_ANNOTATION_ID = 0xb6dabbc87cd1b03eull; static constexpr uint64_t COMPAT_DISABLE_FLAG_ANNOTATION_ID = 0xd145cf1adc42577cull; diff --git a/src/workerd/server/cli/args.rs b/src/workerd/server/cli/args.rs index 1bf1ad85d5b..86213e3e0a8 100644 --- a/src/workerd/server/cli/args.rs +++ b/src/workerd/server/cli/args.rs @@ -385,7 +385,9 @@ pub struct BenchArgs { #[arg(long)] pub all_autogates: bool, - /// Set the compatibility date for all workers, as for `test`. + /// Set the compatibility date for all workers, as for `test`. 'latest' is the newest date that + /// this build supports. Without this, workers that don't specify compatibilityDate get that + /// date. #[arg(long, value_name = "date")] pub compat_date: Option, diff --git a/src/workerd/server/factory/bootstrap.c++ b/src/workerd/server/factory/bootstrap.c++ index 2dc92d57606..1af861f8cb0 100644 --- a/src/workerd/server/factory/bootstrap.c++ +++ b/src/workerd/server/factory/bootstrap.c++ @@ -296,6 +296,9 @@ class Bootstrap { KJ_IF_SOME(compatDate, benchOptions.compat_date) { options.testCompatibilityDateOverride = kj::str(compatDate); } + KJ_IF_SOME(compatDate, benchOptions.default_compat_date) { + options.defaultCompatibilityDate = kj::str(compatDate); + } options.tailWorkers = benchOptions.trace; } }; @@ -367,6 +370,10 @@ BuildInfo build_info() { }; } +::rust::String newest_compat_date() { + return newestCompatDateStr().as(); +} + void cli_exit(int32_t code) { _exit(code); } diff --git a/src/workerd/server/factory/bootstrap.h b/src/workerd/server/factory/bootstrap.h index 5b691c5e2a9..ddcd8b4ebde 100644 --- a/src/workerd/server/factory/bootstrap.h +++ b/src/workerd/server/factory/bootstrap.h @@ -47,6 +47,7 @@ kj::Own bootstrap(kj_rs_tokio::TokioAsyncIoContext& loop, kj::Maybe test, kj::Maybe bench); BuildInfo build_info(); +::rust::String newest_compat_date(); [[noreturn]] void cli_exit(int32_t code); void kj_log(uint8_t severity, ::rust::Str file, uint32_t line, ::rust::Str message); void json_log_to_stderr(uint8_t severity, ::rust::Str file, uint32_t line, ::rust::Str message); diff --git a/src/workerd/server/factory/worker-factory.c++ b/src/workerd/server/factory/worker-factory.c++ index 0062eaffa36..c5980e395c0 100644 --- a/src/workerd/server/factory/worker-factory.c++ +++ b/src/workerd/server/factory/worker-factory.c++ @@ -1421,6 +1421,10 @@ CompatibilityFlags::Reader compileFlags(const WorkerFactory::Options& options, compileCompatibilityFlags(conf.getCompatibilityDate(), conf.getCompatibilityFlags(), flags, errorReporter, options.experimental, CompatibilityDateValidation::CODE_VERSION, nullptr, isPythonMainModule(conf)); + } else KJ_IF_SOME(defaultDate, options.defaultCompatibilityDate) { + compileCompatibilityFlags(defaultDate, conf.getCompatibilityFlags(), flags, errorReporter, + options.experimental, CompatibilityDateValidation::CODE_VERSION, nullptr, + isPythonMainModule(conf)); } else { errorReporter.addError(kj::str("Worker must specify compatibilityDate.")); } diff --git a/src/workerd/server/factory/worker-factory.h b/src/workerd/server/factory/worker-factory.h index ee57f3defd4..e8b9574e867 100644 --- a/src/workerd/server/factory/worker-factory.h +++ b/src/workerd/server/factory/worker-factory.h @@ -112,6 +112,9 @@ class WorkerFactory final: private ChannelTokenHandler::Resolver { bool experimental = false; // When set, every worker uses this compatibility date and none may specify its own. kj::Maybe testCompatibilityDateOverride; + // When set, the compatibility date of workers that don't specify one, which is otherwise an + // error. Workers that specify one use it. + kj::Maybe defaultCompatibilityDate; // Whether requests get tail workers. `workerd bench` turns them off unless given `--trace`: // benchmarked code often makes a subrequest on every call (e.g. `env.SELF.fetch()`), and tail // workers would add their cost to each. The server still looks a config worker's tails up, so diff --git a/src/workerd/server/server-test.c++ b/src/workerd/server/server-test.c++ index a8ba0fb03d7..eddf8b34bb6 100644 --- a/src/workerd/server/server-test.c++ +++ b/src/workerd/server/server-test.c++ @@ -407,6 +407,9 @@ class Server { void disableTailWorkers() { options->tailWorkers = false; } + void setDefaultCompatibilityDate(kj::String date) { + options->defaultCompatibilityDate = kj::mv(date); + } void enableDebugPort(kj::String addr) { debugPort = kj::str("loopback:", addr); } @@ -1339,6 +1342,46 @@ KJ_TEST("Server: compatibility dates are required") { )"_blockquote); } +KJ_TEST("Server: a default compatibility date applies only to workers that omit one") { + auto navigatorChecker = [](kj::StringPtr compatProperties) { + return singleWorker(kj::str("(", compatProperties, R"( + modules = [ + ( name = "main.js", + esModule = + `export default { + ` async fetch(request) { + ` return new Response(!!self.navigator); + ` } + `} + ) + ] + ))"_kj)); + }; + + // global_navigator is enabled on 2022-03-21. + { + TestServer test(navigatorChecker("")); + test.server.setDefaultCompatibilityDate(kj::str("2020-01-01")); + test.start(); + auto conn = test.connect("test-addr"); + conn.httpGet200("/", "false"); + } + { + TestServer test(navigatorChecker("")); + test.server.setDefaultCompatibilityDate(kj::str("2022-08-17")); + test.start(); + auto conn = test.connect("test-addr"); + conn.httpGet200("/", "true"); + } + { + TestServer test(navigatorChecker("compatibilityDate = \"2020-01-01\",")); + test.server.setDefaultCompatibilityDate(kj::str("2022-08-17")); + test.start(); + auto conn = test.connect("test-addr"); + conn.httpGet200("/", "false"); + } +} + KJ_TEST("Server: naming a flag the compatibility date already enables only warns") { TestServer test(singleWorker(R"(( compatibilityDate = "2022-08-17", diff --git a/src/workerd/server/server/bench-test.rs b/src/workerd/server/server/bench-test.rs index 5b573d5fe87..464ba1c1a1b 100644 --- a/src/workerd/server/server/bench-test.rs +++ b/src/workerd/server/server/bench-test.rs @@ -98,6 +98,69 @@ main:empty ); } +fn options_with_compat_date(compat_date: Option<&str>) -> Options { + Options { + no_verbose: false, + all_autogates: false, + compat_date: compat_date.map(str::to_owned), + quick: false, + case_pattern: None, + json: false, + output: None, + trace: false, + } +} + +fn is_date(date: &str) -> bool { + let bytes = date.as_bytes(); + bytes.len() == 10 + && bytes.iter().enumerate().all(|(index, byte)| match index { + 4 | 7 => *byte == b'-', + _ => byte.is_ascii_digit(), + }) +} + +#[test] +fn compat_date_overrides_every_worker_and_latest_is_a_date() { + let dates = options_with_compat_date(Some("2024-01-01")) + .compat_dates() + .unwrap(); + assert_eq!(dates.compat_date.as_deref(), Some("2024-01-01")); + assert_eq!(dates.default_compat_date, None); + + let dates = options_with_compat_date(Some("latest")) + .compat_dates() + .unwrap(); + let latest = dates.compat_date.unwrap(); + assert!(is_date(&latest), "{latest}"); + assert_eq!(dates.default_compat_date, None); +} + +#[test] +fn without_compat_date_workers_default_to_the_newest_date() { + let dates = options_with_compat_date(None).compat_dates().unwrap(); + assert_eq!(dates.compat_date, None); + let default = dates.default_compat_date.unwrap(); + assert!(is_date(&default), "{default}"); +} + +#[test] +fn text_shows_the_default_compat_date() { + let mut message = TypedBuilder::::new_default(); + let mut environment = message.init_root().init_environment(); + environment.set_build_mode("release"); + environment.set_v8_version("14.0.0"); + environment.set_default_compat_date("2026-10-08"); + + let text = format_text(message.get_root_as_reader().unwrap()).unwrap(); + assert_eq!( + text, + "Results compare runs on this machine and build; they don't predict production latency. +release build, V8 14.0.0, default compat date 2026-10-08 +" + ); +} + #[test] fn json_is_capnp_json_with_null_pointers_left_out() { let mut message = TypedBuilder::::new_default(); diff --git a/src/workerd/server/server/bench.rs b/src/workerd/server/server/bench.rs index 78890622fa6..954ec2146ac 100644 --- a/src/workerd/server/server/bench.rs +++ b/src/workerd/server/server/bench.rs @@ -44,7 +44,8 @@ pub struct Options { pub no_verbose: bool, /// `--all-autogates`. pub all_autogates: bool, - /// `--compat-date`: the compatibility date of every worker. + /// `--compat-date`: the compatibility date of every worker; `latest` is the newest that this + /// build supports. pub compat_date: Option, /// `--quick`: short budgets, to check that the benchmarks run rather than to measure them. pub quick: bool, @@ -58,13 +59,43 @@ pub struct Options { pub trace: bool, } +/// The compatibility dates of a run's workers, resolved once, so that the workers and the report +/// agree. +pub(crate) struct CompatDates { + /// `--compat-date`, with `latest` resolved: every worker's date. + compat_date: Option, + /// Without `--compat-date`, the date of workers that don't specify one. + default_compat_date: Option, +} + impl Options { - /// The options the C++ bootstrap applies. - pub(crate) fn process(&self) -> ffi::BenchOptions { + /// The workers' compatibility dates: `--compat-date`'s; or else, for workers that don't + /// specify one, a default, since benchmarks usually measure current behavior. `latest` and the + /// default are the newest date this build supports. + pub(crate) fn compat_dates(&self) -> Result { + Ok(match self.compat_date.as_deref() { + Some("latest") => CompatDates { + compat_date: Some(ffi::newest_compat_date()?), + default_compat_date: None, + }, + Some(date) => CompatDates { + compat_date: Some(date.to_owned()), + default_compat_date: None, + }, + None => CompatDates { + compat_date: None, + default_compat_date: Some(ffi::newest_compat_date()?), + }, + }) + } + + /// The options the C++ bootstrap applies, with the workers' compatibility dates. + pub(crate) fn process(&self, dates: &CompatDates) -> ffi::BenchOptions { ffi::BenchOptions { no_verbose: self.no_verbose, all_autogates: self.all_autogates, - compat_date: self.compat_date.clone().into(), + compat_date: dates.compat_date.clone().into(), + default_compat_date: dates.default_compat_date.clone().into(), trace: self.trace, } } @@ -159,9 +190,10 @@ impl Report { Ok(()) } - /// Records where the report was measured: the build and the machine, with `options`' - /// compatibility date and autogates, and warnings for what makes the results unreliable. - pub(crate) fn set_environment(&mut self, options: &Options) -> Result<()> { + /// Records where the report was measured: the build and the machine, with the workers' + /// compatibility dates and `options`' autogates, and warnings for what makes the results + /// unreliable. + pub(crate) fn set_environment(&mut self, options: &Options, dates: &CompatDates) -> Result<()> { let build = ffi::build_info()?; let cpu_model = cpu_model(); let governor = governor(); @@ -191,9 +223,12 @@ impl Report { if let Some(governor) = &governor { environment.set_governor(governor); } - if let Some(date) = &options.compat_date { + if let Some(date) = &dates.compat_date { environment.set_compat_date(date); } + if let Some(date) = &dates.default_compat_date { + environment.set_default_compat_date(date); + } environment.set_all_autogates(options.all_autogates); let mut list = environment.init_warnings(len_u32(&warnings)); for (index, warning) in (0..).zip(&warnings) { @@ -323,6 +358,12 @@ fn format_text(report: bench_report::Reader<'_>) -> Result { text(environment.get_compat_date())? )); } + if environment.has_default_compat_date() { + parts.push(format!( + "default compat date {}", + text(environment.get_default_compat_date())? + )); + } if environment.get_all_autogates() { parts.push("all autogates".to_owned()); } diff --git a/src/workerd/server/server/bridge.rs b/src/workerd/server/server/bridge.rs index 2dbb932aeed..bf4eeddfcb4 100644 --- a/src/workerd/server/server/bridge.rs +++ b/src/workerd/server/server/bridge.rs @@ -208,12 +208,15 @@ pub mod ffi { } /// The options of `bench` the C++ driver applies: bench mode, which they turn on, logging, - /// autogates, the compatibility date every worker gets, whether requests get tail workers. + /// autogates, the compatibility dates workers get, whether requests get tail workers. #[namespace = "workerd::server::cli"] struct BenchOptions { no_verbose: bool, all_autogates: bool, + /// Every worker's date; none may specify its own. compat_date: KjMaybe, + /// The date of workers that don't specify one. + default_compat_date: KjMaybe, trace: bool, } @@ -271,6 +274,10 @@ pub mod ffi { fn build_info() -> Result; + /// The newest compatibility date a worker may specify: today's, or the build's maximum + /// if that is earlier. `workerd bench`'s `--compat-date=latest`, and its default. + fn newest_compat_date() -> Result; + /// Ends the process with `code` at once, without running destructors (what /// `kj::ProcessContext::exit()` does when `KJ_CLEAN_SHUTDOWN` is not set). Does not return. fn cli_exit(code: i32); diff --git a/src/workerd/server/server/entry.rs b/src/workerd/server/server/entry.rs index 18972c7f019..edd0678c3fa 100644 --- a/src/workerd/server/server/entry.rs +++ b/src/workerd/server/server/entry.rs @@ -85,9 +85,17 @@ pub fn bench( patterns: (String, String), watch: Option, ) -> Result { - let process = bench.process(); + let dates = bench.compat_dates()?; + let process = bench.process(&dates); let command = move |factory, structured| { - server_bench(factory, run_options, patterns, bench, watch, structured) + server_bench( + factory, + run_options, + patterns, + (bench, dates), + watch, + structured, + ) }; run(verbose, config, options, None, Some(process), command) } @@ -295,12 +303,13 @@ async fn server_test( } /// `workerd bench`: runs the benchmarks of `factory`'s config and writes the report, as `bench` -/// asks. Under `--watch`, waits for a change afterwards instead of returning. +/// asks, with the workers' compatibility dates `dates`. Under `--watch`, waits for a change +/// afterwards instead of returning. async fn server_bench( factory: KjOwn, options: RunOptions, (service_pattern, entrypoint_pattern): (String, String), - bench: bench::Options, + (bench, dates): (bench::Options, bench::CompatDates), mut watch: Option, structured: bool, ) -> Result<()> { @@ -316,7 +325,7 @@ async fn server_bench( }) .await?; if let Some(mut results) = results { - results.set_environment(&bench)?; + results.set_environment(&bench, &dates)?; results.write(&bench)?; if !results.passed() { error(structured, "Benchmarks failed!");