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/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/build/wd_bench.bzl b/build/wd_bench.bzl new file mode 100644 index 00000000000..c8eb6aca09e --- /dev/null +++ b/build/wd_bench.bzl @@ -0,0 +1,160 @@ +"""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 `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. + + 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`. + - @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", + ], + ) + + # 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"])) + 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_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, 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/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 diff --git a/docs/benchmarking.md b/docs/benchmarking.md index 761a0e9641f..a8b8bc949eb 100644 --- a/docs/benchmarking.md +++ b/docs/benchmarking.md @@ -15,3 +15,132 @@ 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 + +`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") + +wd_bench( + src = "text-encoder-bench.wd-bench", + data = ["text-encoder-bench.js"], +) +``` + +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 +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|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 +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. `--trace` runs them, to measure that cost. + +## 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). + diff --git a/justfile b/justfile index 351a97bc7d7..fd4001c6890 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 } }} @@ -141,6 +145,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="...": @@ -156,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 diff --git a/src/rust/histogram/BUILD.bazel b/src/rust/histogram/BUILD.bazel new file mode 100644 index 00000000000..93c6ebb7238 --- /dev/null +++ b/src/rust/histogram/BUILD.bazel @@ -0,0 +1,16 @@ +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"], + # 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", + "@crates_vendor//:thiserror", + ], +) 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/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/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-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 new file mode 100644 index 00000000000..9126d803699 --- /dev/null +++ b/src/rust/histogram/lib.rs @@ -0,0 +1,58 @@ +// 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. + +mod cbor; +pub mod ffi; +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; +pub use stats::WelchTest; + +/// 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, + /// The input to `Histogram::import()` is not valid export data. + #[error("Invalid histogram export data")] + InvalidExportData, +} + +#[cfg(test)] +#[path = "lib-test.rs"] +mod tests; 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-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..4536f318a65 --- /dev/null +++ b/src/rust/histogram/stats.rs @@ -0,0 +1,705 @@ +// 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::PI; +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 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, + } + } + + /// 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 + } + } + + /// `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() + } + + /// 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). +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; 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"}]}]} diff --git a/src/workerd/api/BUILD.bazel b/src/workerd/api/BUILD.bazel index b05a6b8f0d3..74e8626ba8c 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", @@ -594,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/api/bench.c++ b/src/workerd/api/bench.c++ new file mode 100644 index 00000000000..8c5ae4ef419 --- /dev/null +++ b/src/workerd/api/bench.c++ @@ -0,0 +1,617 @@ +// 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 +#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; + +// 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; +} + +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); +} + +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()) { + 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); + } + 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]; + 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); + + 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]); + } + } + } +} + +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 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, size_t end, kj::Vector results) { + kj::GlobFilter filter(caseFilter == ""_kj ? "*"_kj : caseFilter.asPtr()); + while (index < end && !cases[index].internal && !filter.matches(cases[index].name)) { + ++index; + } + 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, end, results = kj::mv(results)]( + jsg::Lock& js, BenchCaseResult result) mutable { + results.add(kj::mv(result)); + return self->runCasesFrom(js, index + 1, end, 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(), + .async = c.returnsPromise.orDefault(false), + }); + } + 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); + 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 || c.passArgument ? 1 : 0; + + while (remaining > 0) { + v8::HandleScope scope(isolate); + --remaining; + auto result = jsg::check(fn->Call(context, receiver, argumentCount, &argument)); + 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, + 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.receiver, 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..104f89d8c04 --- /dev/null +++ b/src/workerd/api/bench.h @@ -0,0 +1,182 @@ +// 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, 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. +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 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". +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; + // `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; + uint64_t minTimeNs; + uint64_t warmupNs; + 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; + 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; + 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, 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); + 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..390ae74b5f5 100644 --- a/src/workerd/api/tests/BUILD.bazel +++ b/src/workerd/api/tests/BUILD.bazel @@ -2,7 +2,8 @@ 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_test.bzl", "wd_test") +load("//:build/wd_bench.bzl", "wd_bench") +load("//:build/wd_test.bzl", "wd_cli_test", "wd_test") wasm_tools_parse( name = "js-string-builtins.wasm", @@ -34,6 +35,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"], @@ -1513,3 +1519,26 @@ sh_test( "//conditions:default": [], }), ) + +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/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/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")], + ), + )], +); diff --git a/src/workerd/io/BUILD.bazel b/src/workerd/io/BUILD.bazel index 9a8dca6200b..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", @@ -495,6 +497,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..10c2be61d8f --- /dev/null +++ b/src/workerd/io/bench.capnp @@ -0,0 +1,158 @@ +# 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, 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); + # 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); + + 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 { + 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%. + + 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()`. + } + } + + 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; + } +} 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/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..7386dafef5d 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; @@ -2941,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); }); } @@ -2955,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/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/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. diff --git a/src/workerd/server/cli/args-test.rs b/src/workerd/server/cli/args-test.rs index 6fb56027121..09f8bc93061 100644 --- a/src/workerd/server/cli/args-test.rs +++ b/src/workerd/server/cli/args-test.rs @@ -133,6 +133,66 @@ 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", + "--trace", + "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!(bench.trace); + 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..86213e3e0a8 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,61 @@ pub struct TestArgs { pub filter: Option, } +#[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)] + pub no_verbose: bool, + + /// Enable all autogates. + #[arg(long)] + pub all_autogates: bool, + + /// 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, + + /// 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, + + /// 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, +} + +#[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 +485,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 +563,22 @@ 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(), + trace: self.trace, + } + } +} + #[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..1af861f8cb0 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,29 @@ 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); + } + KJ_IF_SOME(compatDate, benchOptions.default_compat_date) { + options.defaultCompatibilityDate = kj::str(compatDate); + } + options.tailWorkers = benchOptions.trace; + } }; } // namespace @@ -310,12 +355,25 @@ 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(), + }; +} + +::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 b684cea4d0b..ddcd8b4ebde 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,10 @@ 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(); +::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 a447568d5a8..c5980e395c0 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()) { @@ -462,6 +466,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 +1030,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)); } @@ -1167,6 +1179,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 @@ -1398,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.")); } @@ -1516,6 +1543,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 73fa5896fab..e8b9574e867 100644 --- a/src/workerd/server/factory/worker-factory.h +++ b/src/workerd/server/factory/worker-factory.h @@ -112,6 +112,14 @@ 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 + // 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 +171,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); @@ -203,6 +212,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..eddf8b34bb6 100644 --- a/src/workerd/server/server-test.c++ +++ b/src/workerd/server/server-test.c++ @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -403,6 +404,12 @@ class Server { void allowExperimental() { options->experimental = true; } + 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); } @@ -427,6 +434,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) { @@ -1312,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", @@ -4398,6 +4468,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 @@ -5794,6 +6004,197 @@ KJ_TEST("Server: cache name is passed through to service") { } } +// ======================================================================================= +// Test the bench command + +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. + 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++; + ` }, + ` }); + ` 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); + ` 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, "[ 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"); + 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()); + 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() == 10); + + using Status = bench::BenchReport::Case::Status; + 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]); + 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[6].hasWallNs()); + + KJ_EXPECT(kj::StringPtr(cases[7].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..7de9a202fb2 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", @@ -46,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..464ba1c1a1b --- /dev/null +++ b/src/workerd/server/server/bench-test.rs @@ -0,0 +1,242 @@ +// 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.reborrow().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(); + 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"); + 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 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."); + + 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 + 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; at measurement floor: pass results through \ +b.blackBox()) + broken FAILED: Error: boom + later skipped + +main:empty + FAILED: bench() did not register any cases. +" + ); +} + +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(); + 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 new file mode 100644 index 00000000000..954ec2146ac --- /dev/null +++ b/src/workerd/server/server/bench.rs @@ -0,0 +1,621 @@ +// 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 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; `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, + /// 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, + /// `--trace`: run tail workers, which add their cost to every request. + 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 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: dates.compat_date.clone().into(), + default_compat_date: dates.default_compat_date.clone().into(), + trace: self.trace, + } + } + + /// 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, + /// 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 { + to_words(self.message.borrow_inner()) + } + + /// 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(()) + } + + /// 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(); + 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) = &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) { + 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 +/// `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) +} + +/// `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( + 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) +} + +/// 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.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()); + } + 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, 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)?); + } + 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", + Flag::AtFloor => "at measurement floor: pass results through b.blackBox()", + }); + } + 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 91a99281e08..bf4eeddfcb4 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. @@ -170,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, @@ -199,6 +207,29 @@ pub mod ffi { compat_date: KjMaybe, } + /// The options of `bench` the C++ driver applies: bench mode, which they turn on, logging, + /// 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, + } + + /// 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"); @@ -228,8 +259,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). @@ -238,8 +269,15 @@ pub mod ffi { config: Vec, options: &ServeOrTestOptions, test: KjMaybe<&TestOptions>, + bench: KjMaybe<&BenchOptions>, ) -> Result>; + 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); @@ -270,6 +308,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`, 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. fn factory_set_server(factory: &WorkerFactory, server: Box); @@ -334,6 +375,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 +653,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 +664,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/config.rs b/src/workerd/server/server/config.rs index 4aea4a5a24f..31f38933477 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`, unless given `--trace`. + #[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/server/server/entry.rs b/src/workerd/server/server/entry.rs index 56d6615e85d..edd0678c3fa 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,33 @@ 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 dates = bench.compat_dates()?; + let process = bench.process(&dates); + let command = move |factory, structured| { + server_bench( + factory, + run_options, + patterns, + (bench, dates), + 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 +117,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 +253,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 +282,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 +301,39 @@ async fn server_test( } Ok(()) } + +/// `workerd bench`: runs the benchmarks of `factory`'s config and writes the report, as `bench` +/// 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, dates): (bench::Options, bench::CompatDates), + 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, &dates)?; + 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(()) +} 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)) +} 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; diff --git a/src/workerd/util/thread-scopes.c++ b/src/workerd/util/thread-scopes.c++ index 6bfe3275c06..3a3bba53007 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,18 @@ void setPredictableModeForTest() { predictableMode = true; } +bool isBenchMode() { + return benchMode; +} + +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 cdb0b4fc2b8..7663235c20b 100644 --- a/src/workerd/util/thread-scopes.h +++ b/src/workerd/util/thread-scopes.h @@ -73,6 +73,14 @@ 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(); + +// 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 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