From 27ab7e979a9c18a03beff4d9aba2b4624df825e8 Mon Sep 17 00:00:00 2001 From: Mateusz Gienieczko Date: Fri, 25 Oct 2024 19:32:50 +0200 Subject: [PATCH 1/5] remove rsonpath-benchmarks submodule --- .gitmodules | 3 --- crates/rsonpath-benchmarks | 1 - 2 files changed, 4 deletions(-) delete mode 160000 crates/rsonpath-benchmarks diff --git a/.gitmodules b/.gitmodules index faf163c6..256b9e69 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "crates/rsonpath-benchmarks"] - path = crates/rsonpath-benchmarks - url = git@github.com:V0ldek/rsonpath-benchmarks.git [submodule "crates/rsonpath-test/jsonpath-compliance-test-suite"] path = crates/rsonpath-test/jsonpath-compliance-test-suite url = https://github.com/jsonpath-standard/jsonpath-compliance-test-suite.git diff --git a/crates/rsonpath-benchmarks b/crates/rsonpath-benchmarks deleted file mode 160000 index 96016a14..00000000 --- a/crates/rsonpath-benchmarks +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 96016a14793acbda0dc2ef5b329fb2e7d1c73bb8 From ba393baf539d7f4e0d3574f37bb98fd50d58c3c3 Mon Sep 17 00:00:00 2001 From: Mateusz Gienieczko Date: Fri, 25 Oct 2024 19:37:47 +0200 Subject: [PATCH 2/5] move rsonpath-benchmarks as a full subdirectory We used submodules to separate rsonpath-benchmarks from rsonpath but that is no longer useful. Since rsonpath-benchmarks needs to be updated every time rsonpath is, and we want to run benchmarks every time we update rsonpath, they are very much tied together. It remains separate from the main rsonpath workspace, though, as its dependencies are completely separate. --- .github/dependabot.yml | 2 +- .github/workflows/benchmarks.yml | 147 ++ .gitignore | 3 +- crates/rsonpath-benchmarks/.gitattributes | 1 + crates/rsonpath-benchmarks/.gitignore | 5 + crates/rsonpath-benchmarks/CODE_OF_CONDUCT.md | 128 ++ crates/rsonpath-benchmarks/Cargo.toml | 81 ++ crates/rsonpath-benchmarks/Justfile | 64 + crates/rsonpath-benchmarks/LICENSE | 21 + crates/rsonpath-benchmarks/README.md | 129 ++ crates/rsonpath-benchmarks/benches/main.rs | 274 ++++ .../rsonpath-benchmarks/benches/main_micro.rs | 83 ++ .../benches/rsonpath_query_compilation.rs | 62 + .../benches/rust_native.rs | 105 ++ crates/rsonpath-benchmarks/build.rs | 33 + crates/rsonpath-benchmarks/charts/__main__.py | 38 + crates/rsonpath-benchmarks/charts/charts.py | 32 + .../charts/dataset_stat.py | 40 + .../rsonpath-benchmarks/charts/draw_plot.py | 145 ++ .../charts/extract_info.py | 302 ++++ .../rsonpath-benchmarks/charts/query_stat.py | 27 + .../data/small/az_tenants.json | 1272 +++++++++++++++++ crates/rsonpath-benchmarks/src/dataset.rs | 543 +++++++ crates/rsonpath-benchmarks/src/framework.rs | 370 +++++ .../src/framework/benchmark_options.rs | 23 + .../src/framework/implementation.rs | 100 ++ .../src/implementations.rs | 4 + .../src/implementations/jsonpath_rust.rs | 71 + .../jsurferShim/.gitattributes | 9 + .../implementations/jsurferShim/.gitignore | 7 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + .../src/implementations/jsurferShim/gradlew | 240 ++++ .../implementations/jsurferShim/gradlew.bat | 91 ++ .../jsurferShim/lib/build.gradle.kts | 49 + .../jsurferShim/lib/src/main/kotlin/Shim.kt | 45 + .../jsurferShim/settings.gradle.kts | 12 + .../src/implementations/rsonpath.rs | 235 +++ .../src/implementations/rust_jsurfer.rs | 272 ++++ .../src/implementations/serde_json_path.rs | 67 + crates/rsonpath-benchmarks/src/lib.rs | 5 + crates/rsonpath-benchmarks/src/macros.rs | 27 + crates/rsonpath-benchmarks/src/main.rs | 53 + crates/rsonpath-benchmarks/src/prelude.rs | 6 + 44 files changed, 5225 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/benchmarks.yml create mode 100644 crates/rsonpath-benchmarks/.gitattributes create mode 100644 crates/rsonpath-benchmarks/.gitignore create mode 100644 crates/rsonpath-benchmarks/CODE_OF_CONDUCT.md create mode 100644 crates/rsonpath-benchmarks/Cargo.toml create mode 100644 crates/rsonpath-benchmarks/Justfile create mode 100644 crates/rsonpath-benchmarks/LICENSE create mode 100644 crates/rsonpath-benchmarks/README.md create mode 100644 crates/rsonpath-benchmarks/benches/main.rs create mode 100644 crates/rsonpath-benchmarks/benches/main_micro.rs create mode 100644 crates/rsonpath-benchmarks/benches/rsonpath_query_compilation.rs create mode 100644 crates/rsonpath-benchmarks/benches/rust_native.rs create mode 100644 crates/rsonpath-benchmarks/build.rs create mode 100644 crates/rsonpath-benchmarks/charts/__main__.py create mode 100644 crates/rsonpath-benchmarks/charts/charts.py create mode 100644 crates/rsonpath-benchmarks/charts/dataset_stat.py create mode 100644 crates/rsonpath-benchmarks/charts/draw_plot.py create mode 100644 crates/rsonpath-benchmarks/charts/extract_info.py create mode 100644 crates/rsonpath-benchmarks/charts/query_stat.py create mode 100644 crates/rsonpath-benchmarks/data/small/az_tenants.json create mode 100644 crates/rsonpath-benchmarks/src/dataset.rs create mode 100644 crates/rsonpath-benchmarks/src/framework.rs create mode 100644 crates/rsonpath-benchmarks/src/framework/benchmark_options.rs create mode 100644 crates/rsonpath-benchmarks/src/framework/implementation.rs create mode 100644 crates/rsonpath-benchmarks/src/implementations.rs create mode 100644 crates/rsonpath-benchmarks/src/implementations/jsonpath_rust.rs create mode 100644 crates/rsonpath-benchmarks/src/implementations/jsurferShim/.gitattributes create mode 100644 crates/rsonpath-benchmarks/src/implementations/jsurferShim/.gitignore create mode 100644 crates/rsonpath-benchmarks/src/implementations/jsurferShim/gradle/wrapper/gradle-wrapper.jar create mode 100644 crates/rsonpath-benchmarks/src/implementations/jsurferShim/gradle/wrapper/gradle-wrapper.properties create mode 100755 crates/rsonpath-benchmarks/src/implementations/jsurferShim/gradlew create mode 100644 crates/rsonpath-benchmarks/src/implementations/jsurferShim/gradlew.bat create mode 100644 crates/rsonpath-benchmarks/src/implementations/jsurferShim/lib/build.gradle.kts create mode 100644 crates/rsonpath-benchmarks/src/implementations/jsurferShim/lib/src/main/kotlin/Shim.kt create mode 100644 crates/rsonpath-benchmarks/src/implementations/jsurferShim/settings.gradle.kts create mode 100644 crates/rsonpath-benchmarks/src/implementations/rsonpath.rs create mode 100644 crates/rsonpath-benchmarks/src/implementations/rust_jsurfer.rs create mode 100644 crates/rsonpath-benchmarks/src/implementations/serde_json_path.rs create mode 100644 crates/rsonpath-benchmarks/src/lib.rs create mode 100644 crates/rsonpath-benchmarks/src/macros.rs create mode 100644 crates/rsonpath-benchmarks/src/main.rs create mode 100644 crates/rsonpath-benchmarks/src/prelude.rs diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b9fa9345..cd7a2580 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -43,7 +43,7 @@ updates: - minor - package-ecosystem: cargo - directory: /crates/rsonpath-test + directory: /crates/rsonpath-benchmarks schedule: interval: weekly day: monday diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 00000000..3b53222b --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,147 @@ +name: Benchmarks + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + CARGO_PATCH_CRATES_IO_RSONPATH_LIB_GIT: https://github.com/V0ldek/rsonpath.git + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Force remove rsonpath-lib patch from Cargo.toml + run: sed -i '/^\[patch.crates-io\]/d' ./Cargo.toml && sed -i '/^rsonpath-lib = { path = .*$/d' ./Cargo.toml + - name: Install lld + run: sudo apt install lld + - uses: actions/setup-java@v3.6.0 + name: Setup Java JDK + with: + distribution: temurin + java-version: 17 + - name: Cache restore + id: cache-restore + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cargo-${{ hashFiles('**/Cargo.toml') }} + - name: Build all features + uses: actions-rs/cargo@v1 + with: + command: build + args: --all-features + + clippy: + permissions: + checks: write + name: Clippy (stable) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Force remove rsonpath-lib patch from Cargo.toml + run: sed -i '/^\[patch.crates-io\]/d' ./Cargo.toml && sed -i '/^rsonpath-lib = { path = .*$/d' ./Cargo.toml + - name: Install lld + run: sudo apt install lld + - uses: actions/setup-java@v3.6.0 + name: Setup Java JDK + with: + distribution: temurin + java-version: 17 + - name: Cache restore + id: cache-restore + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cargo-${{ hashFiles('**/Cargo.toml') }} + - name: Build all features + uses: actions-rs/cargo@v1 + with: + command: build + args: --all-features + env: + RUSTFLAGS: "--deny warnings" + - name: Clippy all features + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features -- --deny warnings + + clippy-nightly: + permissions: + checks: write + name: Clippy (nightly) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Force remove rsonpath-lib patch from Cargo.toml + run: sed -i '/^\[patch.crates-io\]/d' ./Cargo.toml && sed -i '/^rsonpath-lib = { path = .*$/d' ./Cargo.toml + - name: Install lld + run: sudo apt install lld + - uses: actions/setup-java@v3.6.0 + name: Setup Java JDK + with: + distribution: temurin + java-version: 17 + - name: Cache restore + id: cache-restore + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: cargo-${{ hashFiles('**/Cargo.toml') }} + - name: Rustup nightly toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: clippy, rustfmt + override: true + default: true + - name: Build all features + uses: actions-rs/cargo@v1 + with: + command: build + args: --all-features + env: + RUSTFLAGS: "--deny warnings" + - name: Clippy all features + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features -- --deny warnings + + format: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Rustup stable toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + override: true + default: true + - name: Format + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/.gitignore b/.gitignore index 97758c2a..d8ce713e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ /target/* -/.vscode/*.log -/simd-benchmarks/plot.svg \ No newline at end of file +/.vscode/*.log \ No newline at end of file diff --git a/crates/rsonpath-benchmarks/.gitattributes b/crates/rsonpath-benchmarks/.gitattributes new file mode 100644 index 00000000..44b4224b --- /dev/null +++ b/crates/rsonpath-benchmarks/.gitattributes @@ -0,0 +1 @@ +* eol=lf \ No newline at end of file diff --git a/crates/rsonpath-benchmarks/.gitignore b/crates/rsonpath-benchmarks/.gitignore new file mode 100644 index 00000000..b71a936c --- /dev/null +++ b/crates/rsonpath-benchmarks/.gitignore @@ -0,0 +1,5 @@ +/target +/.vscode/*.log +/data/* +Cargo.lock +!/data/small \ No newline at end of file diff --git a/crates/rsonpath-benchmarks/CODE_OF_CONDUCT.md b/crates/rsonpath-benchmarks/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..9fac0357 --- /dev/null +++ b/crates/rsonpath-benchmarks/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +mat@gienieczko.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/crates/rsonpath-benchmarks/Cargo.toml b/crates/rsonpath-benchmarks/Cargo.toml new file mode 100644 index 00000000..957746b5 --- /dev/null +++ b/crates/rsonpath-benchmarks/Cargo.toml @@ -0,0 +1,81 @@ +[package] +name = "rsonpath-benchmarks" +authors = [ + "Mateusz Gienieczko ", + "Charles Paperman ", +] +version = "0.9.1" +edition = "2021" +description = "Benchmark suite for the `rsonpath` project." +readme = "./README.md" +license = "MIT" +keywords = ["rsonpath", "jsonpath", "query", "simd", "benchmark"] +exclude = ["/data"] +categories = ["text-processing", "development-tools"] +repository = "https://github.com/V0ldek/rsonpath-benchmarks" + +[[bin]] +path = "src/main.rs" +name = "pathimpl" + +[dependencies] +cfg-if = "1.0.0" +clap = { version = "4.5.19", features = ["derive", "wrap_help"] } +color-eyre = { version = "0.6.2", default-features = false } +criterion = "0.5.1" +eyre = "0.6.12" +flate2 = "1.0.34" +hex-literal = "0.4.1" +indicatif = "0.17.8" +jni = { version = "0.21.1", features = ["invocation", "default"] } +jsonpath-rust = "0.7.1" +libc = "0.2.159" +lazy_static = "1.5.0" +serde_json = "1.0.128" +sha2 = "0.10.8" +ouroboros = "0.18.4" +reqwest = { version = "0.12.8", features = ["blocking"] } +rsonpath-lib = { version = "0.9.1", default-features = false } +rsonpath-syntax = { version = "0.3.1", default-features = false } +serde_json_path = "0.6.7" +tar = "0.4.42" +thiserror = "1.0.64" + +[patch.crates-io] +rsonpath-lib = { path = "../rsonpath-lib" } +rsonpath-syntax = { path = "../rsonpath-syntax" } + +[dev-dependencies] +itertools = "0.13.0" +regex = "1.11.0" +memchr = "2.7.4" + +[features] +default = ["simd"] +simd = ["rsonpath-lib/simd"] + +[build-dependencies] +eyre = "0.6.12" + +[profile.dev] +lto = false + +[profile.release] +lto = "thin" +debug = 1 + +[[bench]] +name = "main" +harness = false + +[[bench]] +name = "main_micro" +harness = false + +[[bench]] +name = "rsonpath_query_compilation" +harness = false + +[[bench]] +name = "rust_native" +harness = false diff --git a/crates/rsonpath-benchmarks/Justfile b/crates/rsonpath-benchmarks/Justfile new file mode 100644 index 00000000..7c709cd1 --- /dev/null +++ b/crates/rsonpath-benchmarks/Justfile @@ -0,0 +1,64 @@ +[private] +default: build-bench + +# === BUILD === + +alias b := build-bench + +# Build the rsonpath-benchmarks harness. +build-bench: + cargo build --package rsonpath-benchmarks --profile release + +# === VERIFICATION/LINTING === + +alias v := verify-quick +alias verify := verify-full + +# Run all lints and checks required. +verify-full: build-bench verify-clippy verify-fmt + +# Run a quick formatting and compilation check. +verify-quick: verify-fmt verify-check + +# Run cargo check on non-benchmark packages. +verify-check: + cargo check --all-features + +# Run clippy lints on all packages. +verify-clippy: (build-bench) + cargo +nightly clippy --no-default-features --release -- --deny warnings + cargo +nightly clippy --all-features --release -- --deny warnings + +# Verify formatting rules are not violated. +verify-fmt: + cargo fmt -- --check + +# === BENCHES === + +# Run *all* benches (very long!). +bench-all: (build-bench) + cargo bench --package rsonpath-benchmarks + +# Run a given bench target. +bench target="main": (build-bench) + cargo bench --package rsonpath-benchmarks --bench {{target}} + +# === CLEAN === + +tmpdir := `mktemp -d -t criterion-reports-tmp-XXXXXXXX` + +# Clean all build artifacts without deleting benchmark results. +clean: + -cp -r ./target/criterion/* {{tmpdir}}/ + cargo clean + mkdir -p ./target/criterion + -cp -r {{tmpdir}}/* ./target/criterion + rm -rf {{tmpdir}} + +# Delete benchmark results. +clean-benches: + -rm -rf ./target/criterion/* + +# Clean all artifacts, including benchmark results. +clean-all: + cargo clean \ No newline at end of file diff --git a/crates/rsonpath-benchmarks/LICENSE b/crates/rsonpath-benchmarks/LICENSE new file mode 100644 index 00000000..5a63dc3b --- /dev/null +++ b/crates/rsonpath-benchmarks/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2022 Mateusz Gienieczko, Charles Paperman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/rsonpath-benchmarks/README.md b/crates/rsonpath-benchmarks/README.md new file mode 100644 index 00000000..bd5b70fc --- /dev/null +++ b/crates/rsonpath-benchmarks/README.md @@ -0,0 +1,129 @@ +# Benchmarks for `rsonpath` + +Benchmark suite for [`rsonpath`](https://github.com/v0ldek/rsonpath). + +| Bench name | Path | Size | Depth | Description | +|-----------------------|---------------------------------|-----------|--------|---| +| `ast` | `data/ast` | - | - | JSON representation of the AST of an arbitrary popular C file from Software Heritage. To generate the AST `clang` was used: `clang -Xclang -ast-dump=json -fsyntax-only parse_date.c > ast.json` | +| `crossref` | `data/crossref` | - | - | Concatenation of the first 100 files from [Crossref](https://www.crossref.org/) [source torrent link](https://academictorrents.com/details/e4287cb7619999709f6e9db5c359dda17e93d515) | +| `openfood` | `data/openfood` | - | - | Data extracted from [Open Food Facts API](https://wiki.openfoodfacts.org/Open_Food_Facts_Search_API_Version_2) with `curl "https://world.openfoodfacts.org/cgi/search.pl?action=process&tagtype_0=categories&tag_contains_0=contains&tag_0=cheeses&tagtype_1=labels&&json=1" > /tmp/openfood.json` | +| `twitter` | `data/twitter` | - | - | Taken from [`simdjson`](https://github.com/simdjson/simdjson) example benchmarks ([permalink](https://github.com/simdjson/simdjson/blob/960a7ebba149af00628e6a56f9605945f91a15b7/jsonexamples/twitter.json)) | +| `wikidata` | `data/wikidata` | - | - | Arbitrarily chosen datasets from [Wikidata](https://www.wikidata.org/wiki/Wikidata:Data_access) | + +## Prerequisites + +By default, the benches are performed against a released version of `rsonpath`. +Usually you might want to run it against the local version to test your changes. +To do that, pass a [patch config value] to `cargo`: + +```ini +--config 'patch.crates-io.rsonpath.path = "../rsonpath"' +``` + +Additionally: + +1. An appropriate C++ compiler is required for the [`cc` crate](https://lib.rs/crates/cc) to compile the + JSONSki code. +2. JDK of version at least 8 is required and your `JAVA_HOME` environment variable must be set + to its location. + +On x86_64 Ubuntu the latters can be done by installing `openjdk-17-jdk` and exporting `JAVA_HOME` as +`/usr/lib/jvm/java-1.17.0-openjdk-amd64`. + +### Download the dataset + +On a UNIX system with `wget` installed run the script `sh dl.sh`. +You can also manually download the dataset and put the JSON files in the correct folder. + +For more information, refers to: + +* AST: [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7229269.svg)](https://doi.org/10.5281/zenodo.7229269) +* Twitter: [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7229287.svg)](https://doi.org/10.5281/zenodo.7229287) +* Crossref: [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7229287.svg)](https://doi.org/10.5281/zenodo.7231920) + +For the benchmark to work, the directory layout should be as follows: + +```ini +── data +   ├── ast +   │   └── ast.json +   ├── crossref +   │   ├── crossref0.json +   │   ├── crossref16.json +   │   ├── crossref1.json +   │   ├── crossref2.json +   │   ├── crossref4.json +   │   └── crossref8.json +   └── twitter +   └── twitter.json +``` + +The sha256sum of the JSON files, for reference: + +* `c3ff840d153953ee08c1d9622b20f8c1dc367ae2abcb9c85d44100c6209571af ast/ast.json` +* `f76da4fbd5c18889012ab9bbc222cc439b4b28f458193d297666f56fc69ec500 crossref/crossref/crossref1.json` +* `95e0038e46ce2e94a0f9dde35ec7975280194220878f83436e320881ccd252b4 crossref/crossref/crossref2.json` +* `f14e65d4f8df3c9144748191c1e9d46a030067af86d0cc03cc67f22149143c5d twitter/twitter.json` + +TODO: checksums of other crossrefs + +## Usage + +To benchmark a dataset run + +```bash +cargo bench --bench +``` + +You can compare the SIMD and no-SIMD versions by disabling the default `simd` feature: + +```bash +cargo bench --bench --no-default-features +``` + +The folder `target/criterion` contains all the information needed to plot the experiment. + +As a reminder, to test against local changes instead of a crates.io version: + +```bash +cargo bench --bench --config 'patch.crates-io.rsonpath.path = "../rsonpath"' +``` + +## Plotting + +To plot the result once the is bench done: + +```bash +python3 charts/charts.py +``` + +You can also provide a path to a `criterion` folder with results: + +```bash +python3 charts/charts.py exps/chetemi +``` + +The plot will be saved in the `plot.png` file of the current directory. + +## Statistics + +Two statistics scripts are available: + +* One about the dataset: + +```python +python3 charts/dataset_stat.py +``` + +It will plot some informations about each JSON file in the `data` folder. Be aware that it will +load the file in memory, in Python. Expect it to be slow and memory consumming. + +* One about the queries: + +```python +python3 charts/queries_stat.py +``` + +This script will assume you've run the benchmark to extract the list +of queries from `target/criterion`. It will then compute some parameters and the number of query results with `rsonpath`. +The binary of `rsonpath` should be in the path (run `cargo install rsonpath`). diff --git a/crates/rsonpath-benchmarks/benches/main.rs b/crates/rsonpath-benchmarks/benches/main.rs new file mode 100644 index 00000000..dec069b6 --- /dev/null +++ b/crates/rsonpath-benchmarks/benches/main.rs @@ -0,0 +1,274 @@ +use rsonpath_benchmarks::prelude::*; + +pub fn canada_second_coord_component(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset: rsonpath_benchmarks::framework::ConfiguredBenchset = + Benchset::new("canada::second_coord_component", dataset::nativejson_canada())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$.features[*].geometry.coordinates[*][*][1]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn canada_coord_476_1446_1(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset: rsonpath_benchmarks::framework::ConfiguredBenchset = + Benchset::new("canada::coord_476_1446_1", dataset::nativejson_canada())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..coordinates[476][1446][1]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn canada_coord_slice_100_to_200(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset: rsonpath_benchmarks::framework::ConfiguredBenchset = + Benchset::new("canada::coord_slice_100_to_200", dataset::nativejson_canada())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..coordinates[100:201][*][*]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn canada_coord_slice_overlapping(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset: rsonpath_benchmarks::framework::ConfiguredBenchset = + Benchset::new("canada::coord_slice_overlapping", dataset::nativejson_canada())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..coordinates[5::7][3::10][*]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn citm_seat_category(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset: rsonpath_benchmarks::framework::ConfiguredBenchset = + Benchset::new("citm::seatCategoryId", dataset::nativejson_citm())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..seatCategoryId")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn ast_nested_inner(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("ast::nested_inner", dataset::ast())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..inner..inner..type.qualType")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn ast_deepest(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("ast::deepest", dataset::ast())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*..*")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn bestbuy_products_category_slice(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("bestbuy::products_category", dataset::pison_bestbuy_short())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$.products[*].categoryPath[1:3].id")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn bestbuy_products_video_only(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("bestbuy::products_video_only", dataset::pison_bestbuy_short())? + .do_not_measure_file_load_time() + .add_target_with_id( + BenchTarget::Rsonpath("$.products[*].videoChapters", ResultType::Count), + "rsonpath_direct_count", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$..videoChapters", ResultType::Count), + "rsonpath_descendant_count", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$.products[*].videoChapters", ResultType::Full), + "rsonpath_direct_nodes", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$..videoChapters", ResultType::Full), + "rsonpath_descendant_nodes", + )? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn bestbuy_all_nodes(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("bestbuy::all_nodes", dataset::pison_bestbuy_short())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..*")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn google_map_routes(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("google_map::routes", dataset::pison_google_map_short())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$[*].routes[*].legs[*].steps[*].distance.text")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn google_map_travel_modes(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("google_map::travel_modes", dataset::pison_google_map_short())? + .do_not_measure_file_load_time() + .add_target_with_id( + BenchTarget::Rsonpath("$[*].available_travel_modes", ResultType::Count), + "rsonpath_direct_count", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$..available_travel_modes", ResultType::Count), + "rsonpath_descendant_count", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$[*].available_travel_modes", ResultType::Full), + "rsonpath_direct_nodes", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$..available_travel_modes", ResultType::Full), + "rsonpath_descendant_nodes", + )? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn walmart_items_name(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("walmart::items_name", dataset::pison_walmart_short())? + .do_not_measure_file_load_time() + .add_target_with_id( + BenchTarget::Rsonpath("$.items[*].name", ResultType::Count), + "rsonpath_direct_count", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$..items_name", ResultType::Count), + "rsonpath_descendant_count", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$.items[*].name", ResultType::Full), + "rsonpath_direct_nodes", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$..items_name", ResultType::Full), + "rsonpath_descendant_nodes", + )? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn twitter_metadata(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("twitter::metadata", dataset::twitter())? + .do_not_measure_file_load_time() + .add_target_with_id( + BenchTarget::Rsonpath("$.search_metadata.count", ResultType::Count), + "rsonpath_direct_count", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$..count", ResultType::Count), + "rsonpath_descendant_count", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$.search_metadata.count", ResultType::Full), + "rsonpath_direct_nodes", + )? + .add_target_with_id( + BenchTarget::Rsonpath("$..count", ResultType::Full), + "rsonpath_descendant_nodes", + )? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn inner_array(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("inner_array", dataset::ast())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..inner[0]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn user_second_mention_index(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("user_mentions_indices", dataset::twitter())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..entities.user_mentions[1]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn all_first_index(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("all_first_index", dataset::twitter())? + .do_not_measure_file_load_time() + .add_rsonpath_with_all_result_types("$..[0]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +benchsets!( + main_benches, + canada_second_coord_component, + canada_coord_476_1446_1, + canada_coord_slice_100_to_200, + canada_second_coord_component, + citm_seat_category, + ast_nested_inner, + ast_deepest, + bestbuy_products_category_slice, + bestbuy_products_video_only, + bestbuy_all_nodes, + google_map_routes, + google_map_travel_modes, + inner_array, + user_second_mention_index, + walmart_items_name, + twitter_metadata, + all_first_index +); diff --git a/crates/rsonpath-benchmarks/benches/main_micro.rs b/crates/rsonpath-benchmarks/benches/main_micro.rs new file mode 100644 index 00000000..f5bc7915 --- /dev/null +++ b/crates/rsonpath-benchmarks/benches/main_micro.rs @@ -0,0 +1,83 @@ +use rsonpath_benchmarks::prelude::*; + +fn az_shallow_tenant_ids(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("az_tenant::shallow_ids", dataset::az_tenants())? + .do_not_measure_file_load_time() + .measure_compilation_time() + .add_rsonpath_with_all_result_types("$[*].tenantId")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +fn az_recursive_tenant_ids(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("az_tenants::recursive_ids", dataset::az_tenants())? + .do_not_measure_file_load_time() + .measure_compilation_time() + .add_rsonpath_with_all_result_types("$..tenantId")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +fn az_first_ten_tenant_ids(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("az_tenants::first_ten_tenant_ids", dataset::az_tenants())? + .do_not_measure_file_load_time() + .measure_compilation_time() + .add_rsonpath_with_all_result_types("$[:10].tenantId")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +fn az_tenant_17(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("az_tenants::tenant_17", dataset::az_tenants())? + .do_not_measure_file_load_time() + .measure_compilation_time() + .add_rsonpath_with_all_result_types("$[17]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +fn az_tenant_last(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("az_tenants::tenant_last", dataset::az_tenants())? + .do_not_measure_file_load_time() + .measure_compilation_time() + .add_rsonpath_with_all_result_types("$[83]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +fn az_every_other_tenant(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("az_tenants::every_other_tenant", dataset::az_tenants())? + .do_not_measure_file_load_time() + .measure_compilation_time() + .add_rsonpath_with_all_result_types("$[::2]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +benchsets!( + main_micro_benches, + az_shallow_tenant_ids, + az_recursive_tenant_ids, + az_first_ten_tenant_ids, + az_tenant_17, + az_tenant_last, + az_every_other_tenant +); diff --git a/crates/rsonpath-benchmarks/benches/rsonpath_query_compilation.rs b/crates/rsonpath-benchmarks/benches/rsonpath_query_compilation.rs new file mode 100644 index 00000000..f22482fc --- /dev/null +++ b/crates/rsonpath-benchmarks/benches/rsonpath_query_compilation.rs @@ -0,0 +1,62 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use rsonpath::engine::{Compiler, RsonpathEngine}; + +fn rsonpath_query_compilation(c: &mut Criterion, query_string: &str) { + let mut group = c.benchmark_group(format! {"rsonpath_{query_string}"}); + + group.bench_with_input(BenchmarkId::new("compile_query", query_string), query_string, |b, q| { + b.iter(|| { + let query = rsonpath_syntax::parse(q).unwrap(); + black_box(RsonpathEngine::compile_query(&query).unwrap()); + }) + }); + + group.finish(); +} + +pub fn descendant_only(c: &mut Criterion) { + rsonpath_query_compilation(c, "$..claims..references..hash"); +} + +pub fn small1(c: &mut Criterion) { + rsonpath_query_compilation(c, "$..en.value"); +} + +pub fn small2(c: &mut Criterion) { + rsonpath_query_compilation(c, "$[*].tenantId"); +} + +pub fn child_only(c: &mut Criterion) { + rsonpath_query_compilation(c, "$.user.entities.description.urls"); +} + +pub fn paper_query(c: &mut Criterion) { + rsonpath_query_compilation(c, "$..x..a.b.a.b.c..y.a"); +} + +pub fn many_components(c: &mut Criterion) { + rsonpath_query_compilation( + c, + "$..a.a.b.b.a.b.a.a.b.b.a.a.b.a.b.b.a..b.a.b.a.a.b.a.b.a.a.b.a.a.b..c.a.b.c.d.e.f.g.h.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z..d.d.d.d.d.d.d.d.d.d.d.d.d.d.d..e.a.a.a.a.b.b.b.b.c.c.c.c.d.d.d.d.e.e.e.e", + ) +} + +pub fn wildcard_explosion(c: &mut Criterion) { + rsonpath_query_compilation( + c, + "$['a'][*][*]..['b']..['c'][*][*]['a'][*]..['a'][*]['a'][*][*][*][*]..['a'][*][*]['a'][*]['a'][*]['b'][*][*][*][*][*][*]", + ) +} + +criterion_group!( + query_benches, + descendant_only, + small1, + small2, + child_only, + paper_query, + many_components, + wildcard_explosion +); + +criterion_main!(query_benches); diff --git a/crates/rsonpath-benchmarks/benches/rust_native.rs b/crates/rsonpath-benchmarks/benches/rust_native.rs new file mode 100644 index 00000000..eb40fd93 --- /dev/null +++ b/crates/rsonpath-benchmarks/benches/rust_native.rs @@ -0,0 +1,105 @@ +use rsonpath_benchmarks::prelude::*; + +pub fn ast_decl_inner(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("rust_native::ast::decl_inner", dataset::ast())? + .measure_compilation_time() + .add_rust_native_targets("$..decl.name")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +pub fn twitter_metadata(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("rust_native::twitter::metadata", dataset::twitter())? + .measure_compilation_time() + .add_target_with_id( + BenchTarget::RsonpathMmap("$.search_metadata.count", ResultType::Full), + "rsonpath_direct", + )? + .add_target_with_id( + BenchTarget::RsonpathMmap("$..count", ResultType::Full), + "rsonpath_descendant", + )? + .add_target_with_id( + BenchTarget::JsonpathRust("$.search_metadata.count"), + "jsonpath-rust_direct", + )? + .add_target_with_id(BenchTarget::JsonpathRust("$..count"), "jsonpath-rust_descendant")? + .add_target_with_id( + BenchTarget::SerdeJsonPath("$.search_metadata.count"), + "serde_json_path_direct", + )? + .add_target_with_id(BenchTarget::SerdeJsonPath("$..count"), "serde_json_path_descendant")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +fn az_tenant_last(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("rust_native::az_tenants::tenant_last", dataset::az_tenants())? + .measure_compilation_time() + .add_rust_native_targets("$[83]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +fn az_tenant_ids(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("rust_native::az_tenant::tenant_ids", dataset::az_tenants())? + .measure_compilation_time() + .add_target_with_id( + BenchTarget::RsonpathMmap("$[*].tenantId", ResultType::Full), + "rsonpath_direct", + )? + .add_target_with_id( + BenchTarget::RsonpathMmap("$..tenantId", ResultType::Full), + "rsonpath_descendant", + )? + .add_target_with_id(BenchTarget::JsonpathRust("$[*].tenantId"), "jsonpath-rust_direct")? + .add_target_with_id(BenchTarget::JsonpathRust("$..tenantId"), "jsonpath-rust_descendant")? + .add_target_with_id(BenchTarget::SerdeJsonPath("$[*].tenantId"), "serde_json_path_direct")? + .add_target_with_id(BenchTarget::SerdeJsonPath("$..tenantId"), "serde_json_path_descendant")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +fn az_every_other_tenant(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("rust_native::az_tenant:every_other_tenant", dataset::az_tenants())? + .measure_compilation_time() + .add_rust_native_targets("$[::2]")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +fn az_first_ten_tenant_ids(c: &mut Criterion) -> Result<(), BenchmarkError> { + let benchset = Benchset::new("rust_native::az_tenant::first_ten_tenant_ids", dataset::az_tenants())? + .measure_compilation_time() + .add_rust_native_targets("$[:10].tenantId")? + .finish(); + + benchset.run(c); + + Ok(()) +} + +benchsets!( + main_benches, + ast_decl_inner, + az_tenant_last, + az_tenant_ids, + az_every_other_tenant, + az_first_ten_tenant_ids, + twitter_metadata +); diff --git a/crates/rsonpath-benchmarks/build.rs b/crates/rsonpath-benchmarks/build.rs new file mode 100644 index 00000000..5ce5a3b8 --- /dev/null +++ b/crates/rsonpath-benchmarks/build.rs @@ -0,0 +1,33 @@ +use eyre::{eyre, Context, Result}; +use std::error::Error; +use std::process::Command; + +fn main() -> Result<(), Box> { + setup_jsurfer()?; + + Ok(()) +} + +fn setup_jsurfer() -> Result<()> { + let gradlew_status = Command::new("./gradlew") + .arg("shadowJar") + .current_dir("./src/implementations/jsurferShim") + .status()?; + + if !gradlew_status.success() { + return Err(eyre!("gradlew execution failed with status code: {}", gradlew_status)); + } + + let java_home = std::env::var("JAVA_HOME").wrap_err("JAVA_HOME env variable not set")?; + let jar_absolute_path = + std::path::Path::new("./src/implementations/jsurferShim/lib/jsurferShim.jar").canonicalize()?; + + println!("cargo:rerun-if-changed=src/implementations/jsurferShim"); + println!("cargo:rustc-env=LD_LIBRARY_PATH={java_home}/lib/server"); + println!( + "cargo:rustc-env=RSONPATH_BENCH_JSURFER_SHIM_JAR_PATH={}", + jar_absolute_path.display() + ); + + Ok(()) +} diff --git a/crates/rsonpath-benchmarks/charts/__main__.py b/crates/rsonpath-benchmarks/charts/__main__.py new file mode 100644 index 00000000..3bbaec42 --- /dev/null +++ b/crates/rsonpath-benchmarks/charts/__main__.py @@ -0,0 +1,38 @@ +from pathlib import Path +from shutil import rmtree +from .draw_plot import generate_graphs_exp +from argparse import ArgumentParser + +parser = ArgumentParser( + prog="A small cli to generate graph based on rsonpath-benchmark", + description=""" +First generate criterion data with rsonpath-benchmark. +The datafolder can be found in target/criterion.""") + +parser.add_argument("path", + help="Path toward the criterion folder", + type=Path) + +parser.add_argument("-o", "--output", + help="Path where to store the results. By default append _out to the input path", + type=Path, + default=None) + +parser.add_argument("-e", "--erase", + help="Flag to allow erasing the output folder if already exists. Default is False", + action="store_true") + +if __name__ == "__main__": + args = parser.parse_args() + path = args.path + output = args.output + if not output: + output = Path(path.parent, path.name+"_out") + if output.exists(): + if not args.erase: + raise ValueError("directory already exists, erase with -e flags if needed") + else: + rmtree(output) + output.mkdir() + + generate_graphs_exp(str(path), str(output)) \ No newline at end of file diff --git a/crates/rsonpath-benchmarks/charts/charts.py b/crates/rsonpath-benchmarks/charts/charts.py new file mode 100644 index 00000000..9218cacb --- /dev/null +++ b/crates/rsonpath-benchmarks/charts/charts.py @@ -0,0 +1,32 @@ +import os +import pathlib +import json +import sys +import matplotlib +import texttable +import numpy as np +import math +import pandas as pd +from extract_info import * +from draw_plot import print_plot as plot + +if __name__ == "__main__": + path = None + if len(sys.argv) > 1: + path = pathlib.Path(sys.argv[1]) + if not path.is_dir(): + raise ValueError("Expect a path to a directory in input") + + data = get_exp_data(path) + data = process_exp_data(data) + benches = sorted(map(format_bench, data)) + filtered_bench = ("included_from", "author_affiliation") + benches = list(filter(lambda e:"scala" not in e[1] and e[1] not in filtered_bench, benches)) + exps = [f"{e[0]}_{e[1]}" for e in benches] + exps_short = [f"{e[0].upper()}{i}" for i,e in enumerate(exps)] + print("\n".join(f"{e}: {f}" for e,f in zip(exps_short, exps))) + mapping = {e:data[benches[i][2]] for i,e in enumerate(exps_short)} + jsurfer = np.array([mapping[e].get("jsurfer", 0) for e in exps_short]) + rsonpath = np.array([mapping[e].get("rsonpath", 0) for e in exps_short]) + jsonski = np.array([mapping[e].get("jsonski", 0) for e in exps_short]) + plot(rsonpath, jsurfer, jsonski, exps_short) diff --git a/crates/rsonpath-benchmarks/charts/dataset_stat.py b/crates/rsonpath-benchmarks/charts/dataset_stat.py new file mode 100644 index 00000000..a424cc23 --- /dev/null +++ b/crates/rsonpath-benchmarks/charts/dataset_stat.py @@ -0,0 +1,40 @@ +import json +import os +import pathlib +from extract_info import * + +def depth(tree): + if type(tree) not in (dict, list): + return 1 + L = tree + if type(tree) == dict: + L = tree.values() + t = tuple(depth(e) for e in L) + if t: + return max(t) + 1 + else: + return 1 + +def density(tree): + if type(tree) not in (dict, list): + return 1 + L = tree + if type(tree) == dict: + L = tree.values() + return sum(density(e) for e in L) + 1 + +if __name__ == "__main__": + print("file", "size", "depth", "verbosity", sep="\t") + dataset = {} + for p in get_dataset(): + with open(p) as f: + x = f.read() + d = json.loads(x) + size = len(x) + if size < 1000000: + size = f"{size/1000:0.1f} KB" + else: + size = f"{size/1000000:0.1f} MB" + print(p.name[:-5], size, depth(d), f"{1/(density(d)/len(x)):0.1f}", sep="\t") + + diff --git a/crates/rsonpath-benchmarks/charts/draw_plot.py b/crates/rsonpath-benchmarks/charts/draw_plot.py new file mode 100644 index 00000000..2557a4ad --- /dev/null +++ b/crates/rsonpath-benchmarks/charts/draw_plot.py @@ -0,0 +1,145 @@ +import matplotlib.pyplot as plot +import numpy as np +import math +import chart.extract_info as ei + +plot.rcParams.update({ + "font.size": 18, + "axes.facecolor": "whitesmoke", + "font.family": "serif" +}) + +def print_plot(rsonpath, jsurfer, jsonski, exp_label, fileout="plot.png"): + width = 0.6 + ratio = 1.8 + pos = np.array(range(len(exp_label))) + fig, (ax0, ax1) = plot.subplots(1, 2, gridspec_kw={'width_ratios':[1, ratio]}) + ax0.grid(color = 'white', linestyle = '-', linewidth = 3, zorder=1) + bar = ax0.bar(exp_label, jsurfer, width=width, label="jsurfer", color="tab:gray", zorder=3) + ax0.legend() + ax0.set_ylabel("GB/s") + #ax0.bar_label(bar, [f"{e:0.2f}" for e in jsurfer]) + + width = width/ratio + + bar = ax1.bar(pos+width/2+0.03, rsonpath, label="rsonpath", width=width, color="tab:blue", zorder=4) + ax1.set_xticks(pos) + ax1.set_xticklabels(exp_label) + ax1.bar_label(bar, [f"{e:0.0f}" for e in rsonpath/jsurfer]) + pos2, jsonski2 = zip(*filter(lambda e:e[1] > 0, zip(pos, jsonski))) + jsonski2 = np.array(jsonski2) + pos2 = np.array(pos2) + + bar = ax1.bar(pos2-width/2-0.03, jsonski2, label="jsonski", width=width, color="tab:red", zorder=4) + ax1.bar_label(bar, [f"{e:0.0f}" for e in filter(bool, jsonski/jsurfer)], zorder=4) + ax1.set_ylabel("GB/s") + ax1.grid(color = 'white', linestyle = '-', linewidth = 3, zorder=1) + ax1.legend() + fig.tight_layout() + fig.set_size_inches(20, 5) + plot.subplots_adjust(wspace=0.2, left=0.06) + plot.savefig("plot.png") + +def plot_from_dataframe(df, + keys=None, + width=0.8, + colors=dict(rsonpath="tab:blue", + jsonski="tab:red", + rewritten_s="tab:green", + rewritten_s2="tab:olive", + jsurfer="tab:gray", + rewritten_j="tab:brown" + ), + labels = dict(rewritten_s="rsonpath (rewritten)", rewritten_s2="rsonpath (partial)", rewritten_j="jsurfer (rewritten)")): + + keys = list(df) if not keys else keys + plot.rcParams.update({ + "font.size": 28, + "axes.facecolor": "whitesmoke", + "font.family": "serif", + "figure.figsize":(20, 5) + }) + + lab_f = lambda e:labels.get(e, e) + + pos = np.array(range(len(df.index))) + fig, ax = plot.subplots() + fig.set_size_inches((12, 7)) + ax.grid(color = 'white', linestyle = '-', linewidth = 3, zorder=1) + ax.set_xticks(pos) + ax.set_xticklabels(df.index) + if len(keys) == 1: + ax.bar(pos, df[keys[0]], width=width, zorder=4, label=lab_f(keys[0]), color=colors[keys[0]]) + else: + w = width/len(keys) + for i, k in enumerate(keys): + npos = pos + (len(keys)-1)*w*((i/(len(keys)-1))-0.5) + ax.bar(npos, df[k], width=w, zorder=4, label=lab_f(k), color=colors[k]) + box = ax.get_position() + q = math.ceil(len(keys)/3) + if len(keys) < 4: + hfactor = 0.9 + hanchor = 1.2 + ncol = 3 + elif len(keys) == 4: + hfactor = 0.9 + hanchor = 1.35 + ncol = 2 + else: + hfactor = 0.8 + hanchor = 1.45 + ncol = 2 + ax.set_position([box.x0, box.y0, box.width, box.height*hfactor]) + ax.legend(loc='upper center', bbox_to_anchor=(0.5, hanchor), + ncol=ncol) + fig.tight_layout() + return fig + +def generate_graphs_csv(path, output): + import pandas as pd + df0 = pd.read_csv(path).set_index("id") + generate_graphs(df0, output) + +def generate_graphs_exp(path, outpath): + df0 = ei.exp_to_dataframe(path).set_index("id") + df0.to_csv(outpath+"/data.csv") + generate_graphs(df0, outpath) + +def generate_graphs(df0, outpath): + + df = df0[["jsurfer", "jsonski", "rsonpath"]].rename(dict(rsonpath="rsonpath"), axis=1).drop("N1", errors="ignore") + + df1 = df.filter(items=ei.jsonski_vs_rsonpath, axis=0) + fig = plot_from_dataframe(df1) + fig.savefig(outpath+"/main.png", bbox_inches='tight') + + query_orig = list(map(lambda e:e[:-1], ei.query_rewritten)) + df2 = df.filter(items=query_orig, axis=0) + df3 = df.filter(items=ei.query_rewritten, axis=0)[["rsonpath", "jsurfer"]] + df2[["rewritten_s", "rewritten_j"]] = df3.rename(lambda e:e[:-1]) + df2 = df2[["jsurfer", "rewritten_j", "jsonski", "rsonpath", "rewritten_s"]] + fig = plot_from_dataframe(df2) + fig.savefig(outpath+"/rewrite.png", bbox_inches='tight') + + + query = ["C2", "C3", "Ts"] + query_rewritten = [ + "A1", + "A2", + "C1", + "C2r", + "C3r", + "Tsr", + ] + query_partial = ["Tsp"] + df4 = df.filter(items=query_rewritten, axis=0)[["rsonpath"]].rename(lambda e:e[:-1] if e[-1] == "r" else e) + df4[["rewritten_s"]] = df4[["rsonpath"]] + df5 = df.filter(items=query, axis=0)[["jsonski", "rsonpath"]] + df4[["jsonski", "rsonpath"]] = df5 + df6 = df.filter(items=query_partial, axis=0)[["rsonpath"]].rename(lambda e:"Ts") + df4[["rewritten_s2"]] = df6 + df4 = df4[["jsonski", "rsonpath", "rewritten_s", "rewritten_s2"]] + #for i in ("Ts2", "Ts3"): + # jsonski = jsonski.drop(i) + fig = plot_from_dataframe(df4) + fig.savefig(outpath+"/other.png", bbox_inches='tight') \ No newline at end of file diff --git a/crates/rsonpath-benchmarks/charts/extract_info.py b/crates/rsonpath-benchmarks/charts/extract_info.py new file mode 100644 index 00000000..fcd872b3 --- /dev/null +++ b/crates/rsonpath-benchmarks/charts/extract_info.py @@ -0,0 +1,302 @@ +import os +import pathlib +import json +import numpy as np + +def collect_exps(path: pathlib.Path): + """ + path: a path toward a folder containing criterion results + """ + L = list(os.walk(path)) + L = list(filter(lambda e:"benchmark.json" in e[2] and "new" in e[0], L)) + exps = [] + for upath, _, docs in L: + p = pathlib.Path(upath, "benchmark.json") + with open(p) as f: + d = json.load(f) + exps.append(d) + p = pathlib.Path(upath, "estimates.json") + with open(p) as f: + t = json.load(f) + d["estimates"] = { + "mean": [ + t["mean"]["point_estimate"], + t["mean"]["standard_error"] + ], + "median": [ + t["median"]["point_estimate"], + t["median"]["standard_error"] + ] + } + return exps + +def get_exp_data(path): + exps = collect_exps(path) + groups = {} + for e in exps: + fname = e["function_id"] + if "_" in fname: + for prog in ("rsonpath", "jsonski", "jsurfer"): + if prog.lower() in fname: + fname = prog + groups[e["group_id"]] = L = groups.get(e["group_id"], {}) + L[fname] = e + return groups + +def get_dataset(path): + datapath = pathlib.Path(path, "data") + it = os.walk(datapath) + for directory,_,fs in it: + for filename in fs: + if filename.endswith(".json"): + p = pathlib.Path(directory, filename) + yield p + +def get_query_names(path=None): + d = get_exp_data(path=path) + exps = list(sorted(d)) + exps_short = [f"{exps[i][0].upper()}{i}" for i in range(len(exps))] + return exps_short, exps + +def format_bench(name): + a,b = name.split(".json_", maxsplit=1) + bench = a.split("/")[-1] + query = b + return bench.strip(), query.strip(), name.strip() + +def process_exp_data(data): + d2 = {} + for e,v in data.items(): + d2[e] = h = {} + for x in v: + t = v[x]["throughput"] + if not t: + continue + size = t.get("BytesDecimal", t.get("Bytes")) + stdev = v[x]["estimates"]["median"][1] + median = v[x]["estimates"]["median"][0] + h[x] = size/median #(size/(median+stdev), size/median, size/(median-stdev)) + return d2 + +header = ["id", "rsonpath_id", "dataset", "query", "count", "rsonpath", "jsonski", "jsurfer"] + +def exp_to_list(path:str): + data = get_exp_data(path) + processed = process_exp_data(data) + L = [] + for e, v in processed.items(): + if e[0] != ".": + continue + t = format_bench(e) + x, y, z = v["rsonpath"], v.get("jsonski"), v.get("jsurfer") + qid = id_map[t[1]] + query = id_queries[qid] + count = queries_results[qid] + L.append((qid, t[1], t[0], query, count, x, y, z)) + + L.sort(key=lambda e:e[:2]) + return L + + +id_map = { + "decl_name" : "A1", + "included_from" : "A3", + "nested_inner" : "A2", + "BB1_products_category" : "B1", + "BB1'_products_category" : "B1r", + "BB2_products_video" : "B2", + "BB2'_products_video" : "B2r", + "BB3_products_video_only" : "B3", + "BB3'_products_video_only" : "B3r", + "scalability_affiliation0" : "S0", + "scalability_affiliation1" : "S1", + "author_affiliation" : "C2", + "author_affiliation_descendant" : "C2r", + "DOI" : "C1", + "editor" : "C3", + "editor_descendant" : "C3r", + "orcid" : "C5", + "orcid_descendant" : "C5r", + "scalability_affiliation2" : "S2", + "title" : "C4", + "title_descendant" : "C4r", + "scalability_affiliation4" : "S4", + "GMD1_routes" : "G1", + "GMD2_travel_modes" : "G2", + "GMD2'_travel_modes" : "G2r", + "NSPL1_meta_columns" : "N1", + "NSPL2_data" : "N2", + "added_counties_tags" : "O2", + "added_countries_tags_descendant" : "O2r", + "specific_ingredients" : "O3", + "specific_ingredients_descendant" : "O3r", + "vitamins_tags" : "O1", + "vitamins_tags_descendant" : "O1r", + "all_hashtags" : "Ts4", + "hashtags_of_retweets" : "Ts5", + "metadata_1" : "Ts", + "metadata_2" : "Tsp", + "metadata_3" : "Tsr", + "TT1_entities_urls" : "T1", + "TT2_text" : "T2", + "WM1_items_price" : "W1", + "WM1'_items_price" : "W1r", + "WM2_items_name" : "W2", + "WM2'_items_name" : "W2r", + "WP1_claims_p150" : "Wi", + "WP1'_claims_p150" : "Wir" +} + +id_queries = { + "A1": "$..decl.name", + "A3": "$..loc.includedFrom.file", + "A2": "$..inner..inner..type.qualType", + "B1": "$.products[*].categoryPath[*].id", + "B1r": "$..categoryPath..id", + "B2": "$.products[*].videoChapters[*].chapter", + "B2r": "$..videoChapters..chapter", + "B3": "$.products[*].videoChapters", + "B3r": "$..videoChapters", + "S0": "$..affiliation..name", + "S1": "$..affiliation..name", + "C2": "$.items[*].author[*].affiliation[*].name", + "C2r": "$..author..affiliation..name", + "C1": "$..DOI", + "C3": "$.items[*].editor[*].affiliation[*].name", + "C3r": "$..editor..affiliation..name", + "C5": "$.items[*].author[*].ORCID", + "C5r": "$..author..ORCID", + "S2": "$..affiliation..name", + "C4": "$.items[*].title", + "C4r": "$..title", + "S4": "$..affiliation..name", + "G1": "$[*].routes[*].legs[*].steps[*].distance.text", + "G2": "$[*].available_travel_modes", + "G2r": "$..available_travel_modes", + "N1": "$.meta.view.columns[*].name", + "N2": "$.data[*][*][*]", + "O2": "$.products[*].added_countries_tags", + "O2r": "$..added_countries_tags", + "O3": "$.products[*].specific_ingredients[*].ingredient", + "O3r": "$..specific_ingredients..ingredient", + "O1": "$.products[*].vitamins_tags", + "O1r": "$..vitamins_tags", + "Ts4": "$..hashtags..text", + "Ts5": "$..retweeted_status..hashtags..text", + "Ts": "$.search_metadata.count", + "Tsp": "$..search_metadata.count", + "Tsr": "$..count", + "T1": "$[*].entities.urls[*].url", + "T2": "$[*].text", + "W1": "$.items[*].bestMarketplacePrice.price", + "W1r": "$..bestMarketplacePrice.price", + "W2": "$.items[*].name", + "W2r": "$..name", + "Wi": "$[*].claims.P150[*].mainsnak.property", + "Wir": "$..P150..mainsnak.property" +} + +queries_results = { + "A1": 35, + "A2": 78129, + "A3": 482, + "B1": 697440, + "B1r": 697440, + "B2": 8857, + "B2r": 8857, + "B3": 769, + "B3r": 769, + "C1": 1073589, + "C2": 64495, + "C2r": 64495, + "C3": 39, + "C3r": 39, + "C4": 93407, + "C4r": 93407, + "C5": 18401, + "C5r": 18401, + "G1": 1716752, + "G2": 90, + "G2r": 90, + "N1": 44, + "N2": 8774410, + "O1": 24, + "O1r": 24, + "O2": 24, + "O2r": 24, + "O3": 5, + "O3r": 5, + "S0": 38352, + "S1": 64535, + "S2": 116187, + "S4": 221443, + "T1": 88881, + "T2": 150135, + "Ts4": 10, + "Ts5": 2, + "Ts": 1, + "Tsp": 1, + "Tsr": 1, + "W1": 15892, + "W1r": 15892, + "W2": 272499, + "W2r": 272499, + "Wi": 15603, + "Wir": 15603 +} + +jsonski_vs_rsonpath = [ + "B1", + "B2", + "B3", + "G1", + "G2", + "N1", + "N2", + "T1", + "T2", + "W1", + "W2", + "Wi" +] + +query_rewritten = [ + "B1r", + "B2r", + "B3r", + "G2r", + "W1r", + "W2r", + "Wir" +] + + +def get_table(): + import texttable + T=texttable.Texttable(max_width=0) + T.header(header) + T.set_chars([' ', '|', '|', '-']) + T.set_deco(texttable.Texttable.VLINES|texttable.Texttable.HEADER|texttable.Texttable.BORDER) + return T + + +def print_table_csv(path: pathlib.Path): + import csv, sys + + L = exp_to_list(path) + writer = csv.writer(sys.stdout) + writer.writerow(header) + writer.writerows(L) + +def table_markdown(path: pathlib.Path): + L = exp_to_list(path) + T = get_table() + for e in L: + T.add_row(e) + return "\n".join(T.draw().split("\n")[0:-1]) + +def exp_to_dataframe(path: pathlib.Path): + L = exp_to_list(path) + import pandas + DF = pandas.DataFrame(L, columns=header) + return DF \ No newline at end of file diff --git a/crates/rsonpath-benchmarks/charts/query_stat.py b/crates/rsonpath-benchmarks/charts/query_stat.py new file mode 100644 index 00000000..435f7309 --- /dev/null +++ b/crates/rsonpath-benchmarks/charts/query_stat.py @@ -0,0 +1,27 @@ +import json +import os +import pathlib +from extract_info import * +import subprocess +import sys +path = None +if len(sys.argv) > 1: + path = pathlib.Path(sys.argv[1]) + if not path.is_dir(): + raise ValueError("Expect a path to a directory in input") + +exp_data = get_exp_data(path=path) +short_exps, exps = get_query_names(path=path) +datasets = {e.parent.name:e for e in get_dataset(path=path)} +queries = [] +for i in range(len(exps)): + queries.append((short_exps[i], exp_data[exps[i]]["rsonpath"]["value_str"], exps[i].split("_")[0])) +print("\n".join(map(str, queries))) +binary = pathlib.Path(rootpath.parent, "rsonpath", "target", "release", "rsonpath") +print(binary) +print("short name", "match", "query", sep="&\t", end="\\\\\n") +print("\\hline") +for t in queries: + p = subprocess.Popen([str(binary), "-r", "count", str(t[1]), str(datasets[t[2]])], stdout=subprocess.PIPE) + query = "\\texttt{"+t[1].replace("$", "\\$").replace("_","\_")+"}" + print(t[0], query, p.stdout.read().decode().strip(), sep="&\t", end="\\\\\n") diff --git a/crates/rsonpath-benchmarks/data/small/az_tenants.json b/crates/rsonpath-benchmarks/data/small/az_tenants.json new file mode 100644 index 00000000..1387306d --- /dev/null +++ b/crates/rsonpath-benchmarks/data/small/az_tenants.json @@ -0,0 +1,1272 @@ +[ + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "efd467ef-238c-42bb-b009-c450baec2d42", + "isDefault": false, + "managedByTenants": [], + "name": "Delta-November-01", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "a1ffc958-d2c7-493e-9f1e-125a0477f536", + "isDefault": false, + "managedByTenants": [], + "name": "ALPHA-kilo-05-JULIETT-Romeo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "b74d5345-100f-408a-a7ca-47abb52ba60d", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "SIERRA victor", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "52942f45-54fd-4fd9-b730-03d518fedf35", + "isDefault": false, + "managedByTenants": [], + "name": "Echo-November-TANGO", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "dad45786-32e5-4ef3-b90e-8e0838fbadb6", + "isDefault": false, + "managedByTenants": [], + "name": "echo.DELTA.Golf", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "8c4b5b03-3b24-4ed0-91f5-a703cd91b412", + "isDefault": false, + "managedByTenants": [], + "name": "VICTOR_delta\u0026quebec_victor_Foxtrot_100200", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "dd4c2dac-db51-4cd0-b734-684c6cc360c1", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "india bravo UNIFORM Papa Kilo (X-ray)", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "bac420ed-c6fc-4a05-8ac1-8c0c52da1d6e", + "isDefault": false, + "managedByTenants": [], + "name": "Romeo Sierra CHARLIE", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "c6ce990d-11c5-4299-adb2-7fe877104098", + "isDefault": true, + "managedByTenants": [], + "name": "Sierra_OSCAR_victor_SIERRA_ECHO", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "751c6376-3de4-448a-ad1c-5870c4a3a52a", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "FOXTROT kilo kilo QUEBEC sierra INDIA zulu QUEBEC WHISKEY LIMA", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "54b875cc-a81a-4914-8bfd-1a36bc7ddf4d", + "isDefault": false, + "managedByTenants": [], + "name": "Quebec-Mike-01", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "e1418c6a-b45d-4eb1-b994-c754e2792d55", + "isDefault": false, + "managedByTenants": [], + "name": "ROMEO_FOXTROT_november", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "588845a8-a4a7-4ab1-83a1-1388452e8c0c", + "isDefault": false, + "managedByTenants": [], + "name": "sierra YANKEE NOVEMBER november GOLF", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "407ff5d7-0113-4c5c-8534-f5cfb09298f5", + "isDefault": false, + "managedByTenants": [], + "name": "golf.Delta.romeo.YANKEE.kilo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "c3547baf-332f-4d8f-96bd-0659b39c7a59", + "isDefault": false, + "managedByTenants": [], + "name": "tango Uniform romeo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "f30787b9-82a8-4e74-bb0f-f12d64ecc496", + "isDefault": false, + "managedByTenants": [], + "name": "romeo TANGO", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "26596251-f2f3-4e31-8a1b-f0754e32ad73", + "isDefault": false, + "managedByTenants": [], + "name": "foxtrot-QUEBEC-Quebec-Delta-x-ray", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "482e1993-01d4-4b16-bff4-1866929176a1", + "isDefault": false, + "managedByTenants": [], + "name": "uniform-X-RAY-ALPHA-Quebec", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "4115b323-4aac-47f4-bb13-22af265ed58b", + "isDefault": false, + "managedByTenants": [], + "name": "DELTA-INDIA-WHISKEY-SIERRA-UNIFORM", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "9ec1d932-0f3f-486c-acc6-e7d78b358f9b", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "PAPA", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "e686ef8c-d35d-4e9b-92f8-caaaa7948c0a", + "isDefault": false, + "managedByTenants": [], + "name": "Oscar SIERRA Alpha Whiskey", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "8c2c7b23-848d-40fe-b817-690d79ad9dfd", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + }, + { + "tenantId": "a48abe87-79a1-43cf-be4c-cb1ba62e691b" + } + ], + "name": "Charlie_NOVEMBER_FOXTROT", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "cb9eb375-570a-4e75-b83a-77dd942bee9f", + "isDefault": false, + "managedByTenants": [], + "name": "sierra_romeo_x-ray_Uniform", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "6f53185c-ea09-4fc3-9075-318dec805303", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "0d2a6a5b-bd71-4c52-a2e8-2f502e12f0d6" + } + ], + "name": "Lima UNIFORM lima", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "1278a874-89fc-418c-b6b9-ac763b000415", + "isDefault": false, + "managedByTenants": [], + "name": "november ZULU Charlie Victor (Sierra)", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "29de2cfc-f00a-43bb-bdc8-3108795bd282", + "isDefault": false, + "managedByTenants": [], + "name": "ECHO-Golf QUEBEC-NOVEMBER Victor-hotel00", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "dcbdac96-1896-478d-89fc-c95ed43f4596", + "isDefault": false, + "managedByTenants": [], + "name": "LIMA CHARLIE", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "2145a411-d149-4010-84d4-40fe8a55db44", + "isDefault": false, + "managedByTenants": [], + "name": "golf Mike India", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "7afc2d66-d5b4-4e84-970b-a782e3e4cc46", + "isDefault": false, + "managedByTenants": [], + "name": "LIMA-juliett-yankee-01", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "3b2fda06-3ef6-454a-9dd5-994a548243e9", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "echo Foxtrot \u0026 Quebec Lima", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "0368444d-756e-4ca6-9ecd-e964248c227a", + "isDefault": false, + "managedByTenants": [], + "name": "HOTEL-INDIA", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "4dc2cd39-7a89-43d8-bebe-8bb501359891", + "isDefault": false, + "managedByTenants": [], + "name": "Foxtrot_india_INDIA_Charlie", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "5a7084cb-3357-4ee0-b28f-a3230de8b337", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "ALPHA OSCAR Kilo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "227531a4-d775-435b-a878-963ed8d0d18f", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "1cbaa500-c410-44f7-bbc6-5368a9a4f7db" + }, + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "LIMA-1ROMEO-INDIA", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "2998c1d7-09ae-4b08-b6d0-9c2ee5a84c35", + "isDefault": false, + "managedByTenants": [], + "name": "HOTEL-yankee-quebec-sierra-UNIFORM", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "432a7068-99ae-4975-ad38-d96b71172cdf", + "isDefault": false, + "managedByTenants": [], + "name": "TANGO HOTEL - tango", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "4628298e-882d-4f12-abf4-a9f9654960bb", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "Oscar_X-RAY_NOVEMBER", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "0cd6095b-b140-41ec-ad1d-32f2f7493386", + "isDefault": false, + "managedByTenants": [], + "name": "1KILO - UNIFORM Kilo MIKE - DELTA", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "f455dda6-5a9b-4d71-8d51-7afc3b459039", + "isDefault": false, + "managedByTenants": [], + "name": "TANGO YANKEE Tango", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "b65b516b-415b-4c68-a254-bfa7411275f8", + "isDefault": false, + "managedByTenants": [], + "name": "Yankee lima Zulu hotel - X-RAY", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "b68b2f37-1d37-4c2f-80f6-c23de402792e", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "Zulu Tango CHARLIE\u0026NOVEMBER Romeo 1", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "f1d79e73-f8e3-4b10-bfdb-4207ca0723ed", + "isDefault": false, + "managedByTenants": [], + "name": "romeo Bravo - bravo echo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "c0620f27-ac38-468c-a26b-264009fe7c41", + "isDefault": false, + "managedByTenants": [], + "name": "Lima-hotel00", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "1163fbbe-27e7-4b0f-8466-195fe5417043", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + }, + { + "tenantId": "6b845774-982c-4658-acee-d4fe4b75f68f" + }, + { + "tenantId": "025f27b5-e298-455c-8b0d-eafcff1311d6" + }, + { + "tenantId": "ea3046b1-e0ad-4b06-beae-13e5692de203" + } + ], + "name": "X-ray_WHISKEY_Uniform_Foxtrot_victor_DELTA", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "eef8b6d5-94da-4b36-9327-a662f2674efb", + "isDefault": false, + "managedByTenants": [], + "name": "Yankee-KILO-01", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "3cd95ff9-ac62-4b5c-8240-0cd046687ea0", + "isDefault": false, + "managedByTenants": [], + "name": "foxtrot Uniform Lima foxtrot uniform november", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "8cff5d56-95fb-4a74-ab9d-079edb45313e", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "1cbaa500-c410-44f7-bbc6-5368a9a4f7db" + } + ], + "name": "November-Yankee-x-ray", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "eec2de82-6ab2-4a84-ae5f-57e9a10bf661", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "WHISKEY YANKEE", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "e0fd569c-e34a-4249-8c24-e8d723c7f054", + "isDefault": false, + "managedByTenants": [], + "name": "hotel - X-ray ALPHA Mike\u0026victor", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "e75c95f3-27b4-410f-a40e-2b9153a807dd", + "isDefault": false, + "managedByTenants": [], + "name": "Golf Foxtrot", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "3905431d-c062-4c17-8fd9-c51f89f334c4", + "isDefault": false, + "managedByTenants": [], + "name": "foxtrot Echo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "13723929-6644-4060-a50a-cc38ebc5e8b1", + "isDefault": false, + "managedByTenants": [], + "name": "mike GOLF", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "ca38bc19-cf50-48e2-bbe6-8c35b40212d8", + "isDefault": false, + "managedByTenants": [], + "name": "Lima Foxtrot MIKE", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "8ae1955e-f748-4273-a507-10159ba940f9", + "isDefault": false, + "managedByTenants": [], + "name": "india-VICTOR", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "21d8f407-c4c4-452e-87a4-e609bfb86248", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + }, + { + "tenantId": "cc816ff6-6e86-40dd-b2f0-30dc13fd81d4" + } + ], + "name": "INDIA", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "ae71ef11-a03f-4b4f-a0e6-ef144727c711", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "INDIA yankee X-ray", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "9b4236fe-df75-4289-bf00-40628ed41fd9", + "isDefault": false, + "managedByTenants": [], + "name": "Whiskey UNIFORM charlie QUEBEC romeo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "85b3dbca-5974-4067-9669-67a141095a76", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "430b809f-f7ea-4d63-931d-8076342c32f1" + } + ], + "name": "Foxtrot oscar TANGO DELTA UNIFORM UNIFORM = 2 MIKE", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "a1920ebd-59b7-4f19-af9f-5e80599e88e4", + "isDefault": false, + "managedByTenants": [], + "name": "November", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "d2c9544f-4329-4642-b73d-020e7fef844f", + "isDefault": false, + "managedByTenants": [], + "name": "OSCAR1", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "e72e5254-f265-4e95-9bd2-9ee8e7329051", + "isDefault": false, + "managedByTenants": [], + "name": "MIKE Mike - charlie - ROMEO (JULIETT)", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "bac044cf-49e1-4843-8dda-1ce9662606c8", + "isDefault": false, + "managedByTenants": [], + "name": "uniform INDIA", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "a1c3dc6b-8630-4bb7-a29e-4ed4407c329b", + "isDefault": false, + "managedByTenants": [], + "name": "Oscar Juliett - Uniform2", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "79f57c16-00fe-48da-87d4-5192e86cd047", + "isDefault": false, + "managedByTenants": [], + "name": "LIMA", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "9372b318-ed3a-4504-95a6-941201300f78", + "isDefault": false, + "managedByTenants": [], + "name": "Bravo-Sierra echo november - QUEBEC - bravo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "09fa8e83-d677-474f-8f73-2a954a0b0ea4", + "isDefault": false, + "managedByTenants": [], + "name": "romeo - zulu UNIFORM sierra - Tango", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "948d4068-cee2-492b-8f82-e00a844e059b", + "isDefault": false, + "managedByTenants": [], + "name": "GOLF - tango sierra 2", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "40dd91f6-ead1-40dd-a291-2ae37717981b", + "isDefault": false, + "managedByTenants": [], + "name": "x-ray echo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "4f80d6c7-3b04-4854-9f1a-2d7c587d4f64", + "isDefault": false, + "managedByTenants": [], + "name": "PAPA_zulu_ZULU_KILO", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "2e131dbf-96b3-4377-9c8e-de5d3047f566", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "Tango_romeo_Oscar", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "664c7857-b611-43c5-9bdb-8d4595a8c515", + "isDefault": false, + "managedByTenants": [], + "name": "Quebec X-ray Echo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "b2a328a7-ffff-4c09-b643-a4758cf170bc", + "isDefault": false, + "managedByTenants": [], + "name": "Foxtrot-LIMA-02", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "ec6f35fa-c805-4254-b10b-03a5e0536b69", + "isDefault": false, + "managedByTenants": [], + "name": "quebec-Bravo", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "4bbecc02-f2c3-402a-8e01-1dfb1ffef499", + "isDefault": false, + "managedByTenants": [], + "name": "X-ray QUEBEC zulu alpha", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "50ff7bc0-cd15-49d5-abb2-e975184c2f65", + "isDefault": false, + "managedByTenants": [], + "name": "sierra oscar quebec Uniform", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "c6dcd830-359f-44d0-b4d4-c1ba95e86f48", + "isDefault": false, + "managedByTenants": [], + "name": "echo_QUEBEC_Victor_062020", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "0ee78edb-a0ad-456c-a0a2-901bf542c102", + "isDefault": false, + "managedByTenants": [ + { + "tenantId": "86331ceb-bf0d-4d55-a02c-3f028e454fe7" + } + ], + "name": "SIERRA kilo Kilo - Uniform yankee Foxtrot2", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "8168a4f2-74d6-4663-9951-8e3a454937b7", + "isDefault": false, + "managedByTenants": [], + "name": "Foxtrot - golf QUEBEC", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "84ca48fe-c942-42e5-b492-d56681d058fa", + "isDefault": false, + "managedByTenants": [], + "name": "ROMEO_foxtrot_golf_delta", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "2686cb8a-d887-452c-a8d1-0f10a285ecaf", + "isDefault": false, + "managedByTenants": [], + "name": "delta-ALPHA-X-RAY-WHISKEY", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "cd0fa82d-b6b6-4361-b002-050c32f71353", + "isDefault": false, + "managedByTenants": [], + "name": "golf quebec", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "f1251103-af5a-44b7-ac2a-4e5538e80cde", + "isDefault": false, + "managedByTenants": [], + "name": "zulu365 Quebec - MIKE India (Kilo)", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "52a442a2-31e9-42f9-8e3e-4b27dbf82673", + "isDefault": false, + "managedByTenants": [], + "name": "quebec-zulu-juliett", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + }, + { + "cloudName": "AzureCloud", + "homeTenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "id": "6d17b59e-06c4-4203-89d2-de793ebf5452", + "isDefault": false, + "managedByTenants": [], + "name": "PAPA-1Victor Golf - NOVEMBER - Tango", + "state": "Enabled", + "tenantId": "3aec9a1f-cbea-4c91-a0cb-121b3ac02c7b", + "user": { + "name": "user@domain.com", + "type": "user" + } + } + ] \ No newline at end of file diff --git a/crates/rsonpath-benchmarks/src/dataset.rs b/crates/rsonpath-benchmarks/src/dataset.rs new file mode 100644 index 00000000..e70f151b --- /dev/null +++ b/crates/rsonpath-benchmarks/src/dataset.rs @@ -0,0 +1,543 @@ +use hex_literal::hex; +use reqwest::blocking as reqwest; +use sha2::{Digest, Sha256}; +use std::fmt::Display; +use std::fs; +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; +use thiserror::Error; + +type Sha256Digest = [u8; 32]; + +pub(crate) struct JsonFile { + pub(crate) file_path: String, + pub(crate) size_in_bytes: usize, + checksum: Sha256Digest, +} + +pub struct Dataset { + name: &'static str, + path: &'static str, + source: DatasetSource, + checksum: Sha256Digest, +} + +#[derive(Debug, Clone)] +pub enum DatasetSource { + LocalJson, + UrlJson(&'static str), + UrlArchive(DatasetArchive), + UrlTarArchive(DatasetArchive, &'static str), +} + +#[derive(Debug, Clone)] +pub struct DatasetArchive { + url: &'static str, + checksum: Sha256Digest, +} + +impl DatasetSource { + fn url(&self) -> Option<&'static str> { + match self { + Self::LocalJson => None, + Self::UrlJson(url) => Some(url), + Self::UrlArchive(archive) | Self::UrlTarArchive(archive, _) => Some(archive.url), + } + } +} + +impl DatasetArchive { + fn validate_archive_checksum(&self, actual: Sha256Digest) -> Result<(), DatasetError> { + if self.checksum != actual { + Err(DatasetError::InvalidArchiveChecksum(self.url, self.checksum, actual)) + } else { + Ok(()) + } + } +} + +impl Dataset { + pub(crate) fn file_path(&self) -> Result { + match self.load_file()? { + Some(json_file) if self.checksum == json_file.checksum => return Ok(json_file), + Some(json_file) => { + eprintln!( + "File for dataset {} does not match expected checksum ({} expected, {} actual). Redownloading.", + self.name, + format_hex_string(&self.checksum), + format_hex_string(&json_file.checksum) + ); + } + None => { + eprintln!("File for dataset {} does not exist.", self.name); + } + } + let new_json_file = self.download_file()?; + + if new_json_file.checksum != self.checksum { + Err(DatasetError::InvalidJsonChecksum( + self.source.url().unwrap_or("None"), + self.checksum, + new_json_file.checksum, + )) + } else { + Ok(new_json_file) + } + } + + fn json_path(&self) -> &Path { + self.path.as_ref() + } + + fn directory_path(&self) -> Result<&Path, DatasetError> { + self.json_path().parent().ok_or(DatasetError::InvalidPath(self.path)) + } + + fn create_directories(&self) -> Result<(), DatasetError> { + fs::create_dir_all(self.directory_path()?).map_err(DatasetError::FileSystemError) + } + + fn archive_path(&self) -> PathBuf { + self.json_path().with_extension("gz") + } + + fn load_file(&self) -> Result, DatasetError> { + match fs::File::open(self.path) { + Ok(f) => { + let reader = io::BufReader::new(f); + let progress = get_progress_bar("Checking dataset integrity...", None); + let (md5, size_in_bytes) = read_digest_and_write::<_, fs::File>(progress.wrap_read(reader), None)?; + + Ok(Some(JsonFile { + file_path: self.path.to_string(), + checksum: md5, + size_in_bytes, + })) + } + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(DatasetError::FileSystemError(err)), + } + } + + fn download_file(&self) -> Result { + match self.source { + DatasetSource::LocalJson => self.read_local_file(), + DatasetSource::UrlJson(url) => self.download_json(url), + DatasetSource::UrlArchive(ref archive) => self.download_archive(archive), + DatasetSource::UrlTarArchive(ref archive, initial_path) => { + self.download_tar_archive(archive, initial_path.as_ref()) + } + } + } + + fn read_local_file(&self) -> Result { + self.create_directories()?; + let file = fs::File::open(self.json_path()).map_err(DatasetError::FileSystemError)?; + + let progress = get_progress_bar("Reading", file.metadata().map(|m| m.len()).ok()); + let (checksum, size_in_bytes) = read_digest_and_write(progress.wrap_read(file), None::<&mut fs::File>)?; + progress.finish_and_clear(); + + Ok(JsonFile { + file_path: self.path.to_string(), + checksum, + size_in_bytes, + }) + } + + fn download_json(&self, url: &'static str) -> Result { + self.create_directories()?; + let mut file = fs::File::create(self.json_path()).map_err(DatasetError::FileSystemError)?; + + let response = make_download_request(url)?; + let progress = get_progress_bar("Downloading", response.content_length()); + let (checksum, size_in_bytes) = read_digest_and_write(progress.wrap_read(response), Some(&mut file))?; + progress.finish_and_clear(); + + Ok(JsonFile { + file_path: self.path.to_string(), + checksum, + size_in_bytes, + }) + } + + fn download_archive(&self, archive: &DatasetArchive) -> Result { + use flate2::read::GzDecoder; + + self.create_directories()?; + let archive_path = self.archive_path(); + let mut archive_file = fs::File::create(&archive_path).map_err(DatasetError::FileSystemError)?; + + let response = make_download_request(archive.url)?; + let progress = get_progress_bar("Downloading", response.content_length()); + let (checksum, archive_size) = read_digest_and_write(progress.wrap_read(response), Some(&mut archive_file))?; + progress.finish_and_clear(); + archive_file.flush().map_err(DatasetError::InputOutputError)?; + + archive.validate_archive_checksum(checksum)?; + + let mut json_file = fs::File::create(self.json_path()).map_err(DatasetError::FileSystemError)?; + let archive_file = fs::File::open(&archive_path).map_err(DatasetError::FileSystemError)?; + let progress = get_progress_bar("Extracting", Some(archive_size as u64)); + let gz = GzDecoder::new(progress.wrap_read(archive_file)); + let (checksum, size_in_bytes) = read_digest_and_write(gz, Some(&mut json_file))?; + progress.finish_and_clear(); + + // Ignore errors, worst case scenario the file lingers. + fs::remove_file(archive_path).unwrap_or(()); + + Ok(JsonFile { + file_path: self.path.to_string(), + checksum, + size_in_bytes, + }) + } + + fn download_tar_archive(&self, archive: &DatasetArchive, initial_path: &Path) -> Result { + self.create_directories()?; + let archive_path = self.archive_path(); + let mut archive_file = fs::File::create(&archive_path).map_err(DatasetError::FileSystemError)?; + + let response = make_download_request(archive.url)?; + let progress = get_progress_bar("Downloading", response.content_length()); + let (checksum, archive_size) = read_digest_and_write(progress.wrap_read(response), Some(&mut archive_file))?; + progress.finish_and_clear(); + archive_file.flush().map_err(DatasetError::InputOutputError)?; + + archive.validate_archive_checksum(checksum)?; + + unpack_tar_gz(&archive_path, archive_size, initial_path)?; + + let json_file = fs::File::open(self.json_path()).map_err(DatasetError::FileSystemError)?; + let (checksum, size_in_bytes) = read_digest_and_write::(json_file, None)?; + + // Ignore errors, worst case scenario the file lingers. + fs::remove_file(archive_path).unwrap_or(()); + + Ok(JsonFile { + file_path: self.path.to_string(), + checksum, + size_in_bytes, + }) + } +} + +fn unpack_tar_gz(archive_path: &Path, archive_size: usize, target_path: &Path) -> Result<(), DatasetError> { + use flate2::read::GzDecoder; + use tar::Archive; + + let archive_file = fs::File::open(archive_path).map_err(DatasetError::FileSystemError)?; + let progress = get_progress_bar("Extracting", Some(archive_size as u64)).wrap_read(archive_file); + let gz = GzDecoder::new(progress); + let mut tar = Archive::new(gz); + tar.unpack(target_path).map_err(DatasetError::InputOutputError) +} + +fn make_download_request(url: &'static str) -> Result { + use std::time::Duration; + let msg = format!("Downloading {url}"); + let progress = get_progress_bar(msg, None); + progress.enable_steady_tick(Duration::from_millis(83)); + let response = reqwest::get(url).map_err(|err| DatasetError::DownloadError(url, err))?; + progress.finish(); + Ok(response) +} + +fn get_progress_bar(msg: S, content: Option) -> indicatif::ProgressBar +where + S: Into>, +{ + use indicatif::{ProgressBar, ProgressStyle}; + let style = ProgressStyle::with_template( + "{msg} {spinner} {wide_bar:.green/white} {bytes:>12}/{total_bytes:>12} ({bytes_per_sec:>12}) {eta:>10}", + ) + .unwrap() + .progress_chars("=>-"); + let progress = content.map_or_else(ProgressBar::new_spinner, |x| ProgressBar::new(x).with_style(style)); + progress.set_message(msg); + + progress +} + +fn read_digest_and_write(mut reader: R, mut writer: Option<&mut W>) -> Result<(Sha256Digest, usize), DatasetError> +where + R: Read, + W: Write, +{ + let mut total_size = 0; + let mut buf = [0; 4096]; + let mut hasher = Sha256::new(); + loop { + let size = reader.read(&mut buf).map_err(DatasetError::InputOutputError)?; + if size == 0 { + break; + } + total_size += size; + hasher.update(&buf[..size]); + + if let Some(w) = writer.as_mut() { + w.write_all(&buf[..size]).map_err(DatasetError::InputOutputError)?; + } + } + + Ok((hasher.finalize().into(), total_size)) +} + +macro_rules! dataset_path { + ($e:expr) => { + concat! {"./data", "/", $e} + }; +} + +pub const fn ast() -> Dataset { + Dataset { + name: "ast", + path: dataset_path!("ast/ast.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8395641/files/ast.json.gz", + checksum: hex!("fe8892bc52291755679267b8acf8e6665b294157cf21d8d0504c55478e2c1247"), + }), + checksum: hex!("c3ff840d153953ee08c1d9622b20f8c1dc367ae2abcb9c85d44100c6209571af"), + } +} + +pub fn crossref(size: u32) -> Dataset { + let source = DatasetSource::UrlTarArchive( + DatasetArchive { + url: "https://zenodo.org/record/8395641/files/crossref.tar.gz", + checksum: hex!("ffd5de82d757e0cbd22aa6aca9095d21e5e5c2835c5770f9e446f41b085fc890"), + }, + dataset_path!(""), + ); + + match size { + 0 => Dataset { + name: "crossref0", + path: dataset_path!("crossref/crossref0.json"), + source, + checksum: hex!("9ef2b42a76e2d3e3785dd60f1d0c82a6986a33960d540225fcf19a4531addd0f"), + }, + 1 => Dataset { + name: "crossref1", + path: dataset_path!("crossref/crossref1.json"), + source, + checksum: hex!("b88ae1fd6e72c963859128c23dc7198921a7f3d422d0fe0b4ab72ae1a940f035"), + }, + 2 => Dataset { + name: "crossref2", + path: dataset_path!("crossref/crossref2.json"), + source, + checksum: hex!("6c452a0ee33a0fc9c98e6830e6fb411e3f4736507977c0e96ec3027488b4c95f"), + }, + 4 => Dataset { + name: "crossref4", + path: dataset_path!("crossref/crossref4.json"), + source, + checksum: hex!("7c5768298eb2c90ccc59b0204477f22c27d91ebcd37ea477c307600b3e0e8c29"), + }, + _ => panic!("unsupported dataset crossref{size}"), + } +} + +pub const fn openfood() -> Dataset { + Dataset { + name: "openfood", + path: dataset_path!("openfood/openfood.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8395641/files/openfood.json.gz", + checksum: hex!("5e18cc0cde3c5b80cfdd6c30030e642778fb970e2e7845a573eb0663cfb6f507"), + }), + checksum: hex!("57ece15eecf3bbdc4d18a1215a7c3b9d0d58df0505dc4517b103dc75fac4843f"), + } +} + +pub const fn twitter() -> Dataset { + Dataset { + name: "twitter", + path: dataset_path!("twitter/twitter.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8395641/files/twitter.json.gz", + checksum: hex!("f391b4341c0c0c4d6483d5f6dd5c6b37c39d96abd998b4ebae0f752439921ca1"), + }), + checksum: hex!("f14e65d4f8df3c9144748191c1e9d46a030067af86d0cc03cc67f22149143c5d"), + } +} + +pub const fn pison_bestbuy_large() -> Dataset { + Dataset { + name: "pison_bestbuy", + path: dataset_path!("pison/bestbuy_large_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8395641/files/bestbuy_large_record.json.gz", + checksum: hex!("c8d5efe683256e1530922b7d198fd33c2c8764a594b04b6e8bd29346b09cfb3e"), + }), + checksum: hex!("8eee3043d6d0a11cecb43e169f70fae83c68efa7fe4a5508aa2192f717c45617"), + } +} + +pub const fn pison_bestbuy_short() -> Dataset { + Dataset { + name: "pison_bestbuy_short", + path: dataset_path!("pison/bestbuy_short_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8327309/files/bestbuy_short.json.gz", + checksum: hex!("6587d37e3d47e8a5bb3ac29d45121ea287b49d7eaeb8af568034c0fe0b83fa23"), + }), + checksum: hex!("ca0ec3d84e2212c20b50bce81e69d5cba6c3131a0fe6d47580c97a145be662b2"), + } +} + +pub const fn pison_google_map_large() -> Dataset { + Dataset { + name: "pison_google_map", + path: dataset_path!("pison/google_map_large_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8395641/files/google_map_large_record.json.gz", + checksum: hex!("bff82147ec42186a016615e888c1e009f306ab0599db20afdf102cb95e6f6e5b"), + }), + checksum: hex!("cdbc090edf4faeea80d917e3a2ff618fb0a42626eeac5a4521dae471e4f53574"), + } +} + +pub const fn pison_google_map_short() -> Dataset { + Dataset { + name: "pison_google_map_short", + path: dataset_path!("pison/google_map_short_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8327309/files/google_map_short.json.gz", + checksum: hex!("392d50e7eedfdf13c71e1f7a74a3bb15df85b5988ebc83fc182aec81cf3dece9"), + }), + checksum: hex!("8a23f138d97bbc35572ff04acacfe82877eab0c0f410741c1a9e52a0ad2a48c1"), + } +} + +pub const fn pison_nspl() -> Dataset { + Dataset { + name: "pison_nspl", + path: dataset_path!("pison/nspl_large_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8395641/files/nspl_large_record.json.gz", + checksum: hex!("3acfcfd9e846459296995bca9da4ed88c856eb8b3052f4f4eaa43c1d05e2e672"), + }), + checksum: hex!("174978fd3d7692dbf641c00c80b34e3ff81f0d3d4602c89ee231b989e6a30dd3"), + } +} + +pub const fn pison_twitter_large() -> Dataset { + Dataset { + name: "pison_twitter", + path: dataset_path!("pison/twitter_large_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8395641/files/twitter_large_record.json.gz", + checksum: hex!("4e8bfb5e68bd1b4a9c69c7f2515eb65608ce84e3c284ecb1fe6908eb57b4e650"), + }), + checksum: hex!("2357e2bdba1d621a20c2278a88bdec592e93c680de17d8403d9e3018c7539da6"), + } +} + +pub const fn pison_twitter_short() -> Dataset { + Dataset { + name: "pison_twitter_short", + path: dataset_path!("pison/twitter_short_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8327309/files/twitter_short.json.gz", + checksum: hex!("7d6cde2fe297783338cc507ad8046c3e8e0a905e809bde6af64b73f9bb75afe8"), + }), + checksum: hex!("177b1764cade21af7b4962f23836431dab9c0beb320bdbff11bb6c8006f360cb"), + } +} + +pub const fn pison_walmart_large() -> Dataset { + Dataset { + name: "pison_walmart", + path: dataset_path!("pison/walmart_large_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8395641/files/walmart_large_record.json.gz", + checksum: hex!("3ba4309dd620463045a3996596805f738ead2b257cf7152ea6b1f8ab339e71f4"), + }), + checksum: hex!("ebad2cf96871a1c2277c2a19dcc5818f9c2aed063bc8a56459f378024c5a6e14"), + } +} + +pub const fn pison_walmart_short() -> Dataset { + Dataset { + name: "pison_walmart_short", + path: dataset_path!("pison/walmart_short_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8327309/files/walmart_short.json.gz", + checksum: hex!("e0c4163bfb1da0cbcaf2cc0d26318b0d380bd5defb64113510ea7319d64a252b"), + }), + checksum: hex!("acf0afde1e83cd0a2dde829b63846acb6cf98fc7c9d210f3c230c285b903aff6"), + } +} + +pub const fn pison_wiki() -> Dataset { + Dataset { + name: "pison_wiki", + path: dataset_path!("pison/wiki_large_record.json"), + source: DatasetSource::UrlArchive(DatasetArchive { + url: "https://zenodo.org/record/8395641/files/wiki_large_record.json.gz", + checksum: hex!("60755f971307f29cebbb7daa8624acec41c257dfef5c1543ca0934f5b07edcf7"), + }), + checksum: hex!("1abea7979812edc38651a631b11faf64f1eb5a61e2ee875b4e4d4f7b15a8cea9"), + } +} + +pub const fn nativejson_canada() -> Dataset { + Dataset { + name: "nativejson_canada", + path: dataset_path!("nativejson/canada.json"), + source: DatasetSource::UrlJson("https://raw.githubusercontent.com/miloyip/nativejson-benchmark/478d5727c2a4048e835a29c65adecc7d795360d5/data/canada.json"), + checksum: hex!("f83b3b354030d5dd58740c68ac4fecef64cb730a0d12a90362a7f23077f50d78") + } +} + +pub const fn nativejson_citm() -> Dataset { + Dataset { + name: "nativejson_citm", + path: dataset_path!("nativejson/citm.json"), + source: DatasetSource::UrlJson("https://raw.githubusercontent.com/miloyip/nativejson-benchmark/478d5727c2a4048e835a29c65adecc7d795360d5/data/citm_catalog.json"), + checksum: hex!("a73e7a883f6ea8de113dff59702975e60119b4b58d451d518a929f31c92e2059") + } +} + +pub const fn az_tenants() -> Dataset { + Dataset { + name: "az_tenants", + path: dataset_path!("small/az_tenants.json"), + source: DatasetSource::LocalJson, + checksum: hex!("f4aa54189ddb9fff22a20bf24cb8bb2656880abdb0a01cf1a48cd3ddd30a87d0"), + } +} + +#[derive(Error, Debug)] +pub enum DatasetError { + #[error("Filesystem error: {0}")] + FileSystemError(#[source] std::io::Error), + #[error("I/O error reading dataset contents: {0}")] + InputOutputError(#[source] std::io::Error), + #[error("Invalid dataset path: {0} is not a valid path")] + InvalidPath(&'static str), + #[error("Error downloading a dataset from {0}: {1}")] + DownloadError(&'static str, #[source] ::reqwest::Error), + #[error( + "Checksum validation failed. \ + The URL source might be corrupted. \ + Expected JSON from {0} to have SHA2 checksum of {}, but it has {}.", format_hex_string(.1), format_hex_string(.2) + )] + InvalidJsonChecksum(&'static str, Sha256Digest, Sha256Digest), + #[error( + "Checksum validation failed. \ + The URL source might be corrupted. \ + Expected archive from {0} to have SHA2 checksum of {}, but it has {}.", format_hex_string(.1), format_hex_string(.2) + )] + InvalidArchiveChecksum(&'static str, Sha256Digest, Sha256Digest), +} + +fn format_hex_string(bytes: &[u8]) -> impl Display { + use std::fmt::Write; + bytes.iter().fold(String::new(), |mut f, b| { + let _ = write!(f, "{b:02x}"); + f + }) +} diff --git a/crates/rsonpath-benchmarks/src/framework.rs b/crates/rsonpath-benchmarks/src/framework.rs new file mode 100644 index 00000000..865760d0 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/framework.rs @@ -0,0 +1,370 @@ +use self::implementation::prepare; +use self::{benchmark_options::BenchmarkOptions, implementation::prepare_with_id}; +use crate::{ + dataset, + implementations::{ + jsonpath_rust::{JsonpathRust, JsonpathRustError}, + rsonpath::{Rsonpath, RsonpathCount, RsonpathError, RsonpathMmap, RsonpathMmapCount}, + rust_jsurfer::{JSurfer, JSurferError}, + serde_json_path::{SerdeJsonPath, SerdeJsonPathError}, + }, +}; +use criterion::{Criterion, Throughput}; +use implementation::{Implementation, PreparedQuery}; +use std::{path::PathBuf, time::Duration}; +use thiserror::Error; + +pub mod benchmark_options; +pub mod implementation; + +#[derive(Clone, Copy, Debug)] +pub enum BenchTarget<'q> { + RsonpathMmap(&'q str, ResultType), + Rsonpath(&'q str, ResultType), + JSurfer(&'q str), + JsonpathRust(&'q str), + SerdeJsonPath(&'q str), +} + +#[derive(Clone, Copy, Debug)] +pub enum ResultType { + Full, + Count, +} + +pub struct Benchset { + id: String, + options: BenchmarkOptions, + json_document: dataset::JsonFile, + implementations: Vec>, + measure_file_load: bool, + measure_compilation_time: bool, +} + +pub struct ConfiguredBenchset { + source: Benchset, +} + +impl ConfiguredBenchset { + pub fn run(&self, c: &mut Criterion) { + let bench = &self.source; + let mut group = c.benchmark_group(&bench.id); + + bench.options.apply_to(&mut group); + group.throughput(Throughput::BytesDecimal( + u64::try_from(bench.json_document.size_in_bytes).unwrap(), + )); + + for implementation in bench.implementations.iter() { + let id = implementation.id(); + group.bench_function(id, |b| b.iter(move || implementation.run())); + } + + group.finish(); + } +} + +impl Benchset { + pub fn new>(id: S, dataset: dataset::Dataset) -> Result { + let json_file = dataset.file_path().map_err(BenchmarkError::DatasetError)?; + + let warm_up_time = if json_file.size_in_bytes < 10_000_000 { + None + } else if json_file.size_in_bytes < 100_000_000 { + Some(Duration::from_secs(5)) + } else { + Some(Duration::from_secs(10)) + }; + + // We're aiming for over 1GB/s, but some queries run at 100MB/s. + // Let's say we want to run the query at least 10 times to get significant results. + const TARGET_NUMBER_OF_QUERIES: f64 = 10.0; + const TARGET_SPEED_IN_BYTES_PER_SEC: f64 = 100_000_000.0; + + let measurement_secs = + (json_file.size_in_bytes as f64) * TARGET_NUMBER_OF_QUERIES / TARGET_SPEED_IN_BYTES_PER_SEC; + let measurement_time = if measurement_secs > 5.0 { + Some(Duration::from_secs_f64(measurement_secs)) + } else { + None + }; + let sample_count = if json_file.size_in_bytes < 1_000_000 { + None + } else { + Some(10) + }; + + Ok(Self { + id: format!("{}_{}", json_file.file_path, id.into()), + options: BenchmarkOptions { + warm_up_time, + measurement_time, + sample_count, + }, + json_document: json_file, + implementations: vec![], + measure_file_load: true, + measure_compilation_time: false, + }) + } + + pub fn do_not_measure_file_load_time(self) -> Self { + Self { + measure_file_load: false, + ..self + } + } + + pub fn measure_compilation_time(self) -> Self { + Self { + measure_compilation_time: true, + ..self + } + } + + pub fn add_target(mut self, target: BenchTarget<'_>) -> Result { + let bench_fn = target.to_bench_fn( + &self.json_document.file_path, + !self.measure_file_load, + !self.measure_compilation_time, + )?; + self.implementations.push(bench_fn); + Ok(self) + } + + pub fn add_target_with_id(mut self, target: BenchTarget<'_>, id: &'static str) -> Result { + let bench_fn = target.to_bench_fn_with_id( + &self.json_document.file_path, + !self.measure_file_load, + !self.measure_compilation_time, + id, + )?; + self.implementations.push(bench_fn); + Ok(self) + } + + pub fn add_rsonpath_with_all_result_types(self, query: &str) -> Result { + self.add_target(BenchTarget::Rsonpath(query, ResultType::Full))? + .add_target(BenchTarget::Rsonpath(query, ResultType::Count))? + .add_target(BenchTarget::RsonpathMmap(query, ResultType::Full))? + .add_target(BenchTarget::RsonpathMmap(query, ResultType::Count)) + } + + pub fn add_all_targets_except_jsurfer(self, query: &str) -> Result { + self.add_target(BenchTarget::RsonpathMmap(query, ResultType::Full))? + .add_target(BenchTarget::JsonpathRust(query))? + .add_target(BenchTarget::SerdeJsonPath(query)) + } + + pub fn add_all_targets(self, query: &str) -> Result { + self.add_target(BenchTarget::RsonpathMmap(query, ResultType::Full))? + .add_target(BenchTarget::JSurfer(query))? + .add_target(BenchTarget::JsonpathRust(query))? + .add_target(BenchTarget::SerdeJsonPath(query)) + } + + pub fn add_rust_native_targets(self, query: &str) -> Result { + self.add_target(BenchTarget::RsonpathMmap(query, ResultType::Full))? + .add_target(BenchTarget::JsonpathRust(query))? + .add_target(BenchTarget::SerdeJsonPath(query)) + } + + pub fn finish(self) -> ConfiguredBenchset { + ConfiguredBenchset { source: self } + } +} + +trait Target { + fn to_bench_fn( + self, + file_path: &str, + load_ahead_of_time: bool, + compile_ahead_of_time: bool, + ) -> Result, BenchmarkError>; + + fn to_bench_fn_with_id( + self, + file_path: &str, + load_ahead_of_time: bool, + compile_ahead_of_time: bool, + id: &'static str, + ) -> Result, BenchmarkError>; +} + +impl<'a> Target for BenchTarget<'a> { + fn to_bench_fn( + self, + file_path: &str, + load_ahead_of_time: bool, + compile_ahead_of_time: bool, + ) -> Result, BenchmarkError> { + match self { + BenchTarget::Rsonpath(q, ResultType::Full) => { + let rsonpath = Rsonpath::new()?; + let prepared = prepare(rsonpath, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::Rsonpath(q, ResultType::Count) => { + let rsonpath = RsonpathCount::new()?; + let prepared = prepare(rsonpath, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::RsonpathMmap(q, ResultType::Full) => { + let rsonpath = RsonpathMmap::new()?; + let prepared = prepare(rsonpath, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::RsonpathMmap(q, ResultType::Count) => { + let rsonpath = RsonpathMmapCount::new()?; + let prepared = prepare(rsonpath, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::JSurfer(q) => { + let jsurfer = JSurfer::new()?; + let prepared = prepare(jsurfer, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::JsonpathRust(q) => { + let jsonpath_rust = JsonpathRust::new()?; + let prepared = prepare(jsonpath_rust, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::SerdeJsonPath(q) => { + let serde_json_path = SerdeJsonPath::new()?; + let prepared = prepare(serde_json_path, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + } + } + + fn to_bench_fn_with_id( + self, + file_path: &str, + load_ahead_of_time: bool, + compile_ahead_of_time: bool, + id: &'static str, + ) -> Result, BenchmarkError> { + match self { + BenchTarget::Rsonpath(q, ResultType::Full) => { + let rsonpath = Rsonpath::new()?; + let prepared = prepare_with_id(rsonpath, id, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::Rsonpath(q, ResultType::Count) => { + let rsonpath = RsonpathCount::new()?; + let prepared = prepare_with_id(rsonpath, id, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::RsonpathMmap(q, ResultType::Full) => { + let rsonpath = RsonpathMmap::new()?; + let prepared = prepare_with_id(rsonpath, id, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::RsonpathMmap(q, ResultType::Count) => { + let rsonpath = RsonpathMmapCount::new()?; + let prepared = prepare_with_id(rsonpath, id, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::JSurfer(q) => { + let jsurfer = JSurfer::new()?; + let prepared = prepare_with_id(jsurfer, id, file_path, q, load_ahead_of_time, compile_ahead_of_time)?; + Ok(Box::new(prepared)) + } + BenchTarget::JsonpathRust(q) => { + let jsonpath_rust = JsonpathRust::new()?; + let prepared = prepare_with_id( + jsonpath_rust, + id, + file_path, + q, + load_ahead_of_time, + compile_ahead_of_time, + )?; + Ok(Box::new(prepared)) + } + BenchTarget::SerdeJsonPath(q) => { + let serde_json_path = SerdeJsonPath::new()?; + let prepared = prepare_with_id( + serde_json_path, + id, + file_path, + q, + load_ahead_of_time, + compile_ahead_of_time, + )?; + Ok(Box::new(prepared)) + } + } + } +} + +trait BenchFn { + fn id(&self) -> &str; + + fn run(&self); +} + +impl BenchFn for PreparedQuery { + fn id(&self) -> &str { + self.id + } + + fn run(&self) { + let f_storage; + let q_storage; + + let f = match &self.file { + implementation::File::NeedToLoad(file_path) => { + f_storage = self.implementation.load_file(file_path).unwrap(); + &f_storage + } + implementation::File::AlreadyLoaded(f) => f, + }; + let q = match &self.query { + implementation::Query::NeedToCompile(query_string) => { + q_storage = self.implementation.compile_query(query_string).unwrap(); + &q_storage + } + implementation::Query::AlreadyCompiled(q) => q, + }; + + let result = self.implementation.run(q, f).unwrap(); + criterion::black_box(result); + } +} + +#[derive(Error, Debug)] +pub enum BenchmarkError { + #[error("invalid dataset file path, has to be valid UTF-8: '{0}'")] + InvalidFilePath(PathBuf), + #[error("error loading dataset: {0}")] + DatasetError( + #[source] + #[from] + dataset::DatasetError, + ), + #[error("error preparing Rsonpath bench: {0}")] + RsonpathError( + #[source] + #[from] + RsonpathError, + ), + #[error("error preparing JSurfer bench: {0}")] + JSurferError( + #[source] + #[from] + JSurferError, + ), + #[error("error preparing JsonpathRust bench: {0}")] + JsonpathRust( + #[source] + #[from] + JsonpathRustError, + ), + #[error("error preparing SerdeJsonPath bench: {0}")] + SerdeJsonPath( + #[source] + #[from] + SerdeJsonPathError, + ), +} diff --git a/crates/rsonpath-benchmarks/src/framework/benchmark_options.rs b/crates/rsonpath-benchmarks/src/framework/benchmark_options.rs new file mode 100644 index 00000000..132f218b --- /dev/null +++ b/crates/rsonpath-benchmarks/src/framework/benchmark_options.rs @@ -0,0 +1,23 @@ +use std::time::Duration; + +use criterion::{measurement::Measurement, BenchmarkGroup}; + +pub(crate) struct BenchmarkOptions { + pub(crate) warm_up_time: Option, + pub(crate) measurement_time: Option, + pub(crate) sample_count: Option, +} + +impl BenchmarkOptions { + pub(crate) fn apply_to(&self, group: &mut BenchmarkGroup<'_, M>) { + if let Some(duration) = self.warm_up_time { + group.warm_up_time(duration); + } + if let Some(duration) = self.measurement_time { + group.measurement_time(duration); + } + if let Some(sample_count) = self.sample_count { + group.sample_size(sample_count); + } + } +} diff --git a/crates/rsonpath-benchmarks/src/framework/implementation.rs b/crates/rsonpath-benchmarks/src/framework/implementation.rs new file mode 100644 index 00000000..988da813 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/framework/implementation.rs @@ -0,0 +1,100 @@ +use std::fmt::Display; + +pub trait Implementation: Sized { + type Query; + type File; + type Error: std::error::Error + Sync + Send + 'static; + type Result<'a>: Display; + + fn id() -> &'static str; + + fn new() -> Result; + + fn load_file(&self, file_path: &str) -> Result; + + fn compile_query(&self, query: &str) -> Result; + + fn run<'a>(&self, query: &'a Self::Query, file: &'a Self::File) -> Result, Self::Error>; +} + +pub struct PreparedQuery { + pub(crate) implementation: I, + pub(crate) id: &'static str, + pub(crate) query: Query, + pub(crate) file: File, +} + +pub(crate) enum File { + NeedToLoad(String), + AlreadyLoaded(F), +} + +pub(crate) enum Query { + NeedToCompile(String), + AlreadyCompiled(Q), +} + +impl File { + fn from_path(path: &str) -> File { + File::NeedToLoad(path.to_string()) + } + + fn from_file(file: F) -> File { + File::AlreadyLoaded(file) + } +} + +impl Query { + fn from_str(query: &str) -> Query { + Query::NeedToCompile(query.to_string()) + } + + fn from_query(query: Q) -> Query { + Query::AlreadyCompiled(query) + } +} + +pub(crate) fn prepare( + implementation: I, + file_path: &str, + query: &str, + load_ahead_of_time: bool, + compile_ahead_of_time: bool, +) -> Result, I::Error> { + prepare_with_id( + implementation, + I::id(), + file_path, + query, + load_ahead_of_time, + compile_ahead_of_time, + ) +} + +pub(crate) fn prepare_with_id( + implementation: I, + id: &'static str, + file_path: &str, + query: &str, + load_ahead_of_time: bool, + compile_ahead_of_time: bool, +) -> Result, I::Error> { + let query = if compile_ahead_of_time { + Query::from_query(implementation.compile_query(query)?) + } else { + Query::from_str(query) + }; + + let file = if load_ahead_of_time { + File::from_file(implementation.load_file(file_path)?) + } else { + File::from_path(file_path) + }; + + Ok(PreparedQuery { + implementation, + id, + query, + file, + }) +} diff --git a/crates/rsonpath-benchmarks/src/implementations.rs b/crates/rsonpath-benchmarks/src/implementations.rs new file mode 100644 index 00000000..499fb680 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations.rs @@ -0,0 +1,4 @@ +pub mod jsonpath_rust; +pub mod rsonpath; +pub mod rust_jsurfer; +pub mod serde_json_path; diff --git a/crates/rsonpath-benchmarks/src/implementations/jsonpath_rust.rs b/crates/rsonpath-benchmarks/src/implementations/jsonpath_rust.rs new file mode 100644 index 00000000..dd72b817 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/jsonpath_rust.rs @@ -0,0 +1,71 @@ +use crate::framework::implementation::Implementation; +use jsonpath_rust::{parser::JsonPath, JsonPathValue}; +use serde_json::Value; +use std::{ + fmt::Display, + fs, + io::{self, BufReader}, + str::FromStr, +}; +use thiserror::Error; + +pub struct JsonpathRust {} + +pub struct JsonpathRustResult<'a>(Vec>); + +impl Implementation for JsonpathRust { + type Query = JsonPath; + + type File = Value; + + type Error = JsonpathRustError; + + type Result<'a> = JsonpathRustResult<'a>; + + fn id() -> &'static str { + "jsonpath-rust" + } + + fn new() -> Result { + Ok(JsonpathRust {}) + } + + fn load_file(&self, file_path: &str) -> Result { + let file = fs::File::open(file_path)?; + let reader = BufReader::new(file); + let value: Value = serde_json::from_reader(reader)?; + + Ok(value) + } + + fn compile_query(&self, query: &str) -> Result { + JsonPath::from_str(query).map_err(JsonpathRustError::JsonPathInstError) + } + + fn run<'a>(&self, query: &'a Self::Query, file: &'a Self::File) -> Result, Self::Error> { + let results = query.find_slice(file); + + Ok(JsonpathRustResult(results)) + } +} + +impl<'a> Display for JsonpathRustResult<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for res in &self.0 { + let val = res.clone().to_data(); + writeln!(f, "{}", val)?; + } + + Ok(()) + } +} + +#[derive(Error, Debug)] +pub enum JsonpathRustError { + #[error(transparent)] + IoError(#[from] io::Error), + #[error("error parsing JSON with serde: '{0}'")] + SerdeError(#[from] serde_json::Error), + #[error("error parsing JSONPath query: '{0}'")] + JsonPathInstError(>::Error), +} diff --git a/crates/rsonpath-benchmarks/src/implementations/jsurferShim/.gitattributes b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/.gitattributes new file mode 100644 index 00000000..097f9f98 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/crates/rsonpath-benchmarks/src/implementations/jsurferShim/.gitignore b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/.gitignore new file mode 100644 index 00000000..df223264 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/.gitignore @@ -0,0 +1,7 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +*/jsurferShim.jar \ No newline at end of file diff --git a/crates/rsonpath-benchmarks/src/implementations/jsurferShim/gradle/wrapper/gradle-wrapper.jar b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/crates/rsonpath-benchmarks/src/implementations/jsurferShim/gradlew.bat b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/gradlew.bat new file mode 100644 index 00000000..f127cfd4 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/crates/rsonpath-benchmarks/src/implementations/jsurferShim/lib/build.gradle.kts b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/lib/build.gradle.kts new file mode 100644 index 00000000..fa3f233a --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/lib/build.gradle.kts @@ -0,0 +1,49 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Kotlin library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/7.5/userguide/building_java_projects.html + * This project uses @Incubating APIs which are subject to change. + */ + +plugins { + // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. + id("org.jetbrains.kotlin.jvm") version "1.6.21" + + id("com.github.johnrengelman.shadow") version "7.1.2" + + // Apply the java-library plugin for API and implementation separation. + `java-library` +} + +tasks { + shadowJar { + archiveBaseName.set("jsurferShim") + archiveClassifier.set("") + archiveVersion.set("") + destinationDirectory.set(File(projectDir, "")) + } +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + implementation(group = "com.github.jsurfer", name = "jsurfer-fastjson", version = "1.6.3") + + // Align versions of all Kotlin components + implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + + // Use the Kotlin JDK 8 standard library. + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + // This dependency is used internally, and not exposed to consumers on their own compile + // classpath. + implementation("com.google.guava:guava:31.0.1-jre") + + // This dependency is exported to consumers, that is to say found on their compile classpath. + api("org.apache.commons:commons-math3:3.6.1") +} diff --git a/crates/rsonpath-benchmarks/src/implementations/jsurferShim/lib/src/main/kotlin/Shim.kt b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/lib/src/main/kotlin/Shim.kt new file mode 100644 index 00000000..2c205cc8 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/lib/src/main/kotlin/Shim.kt @@ -0,0 +1,45 @@ +package com.v0ldek.rsonpath.jsurferShim + +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Paths +import org.jsfr.json.* +import org.jsfr.json.compiler.JsonPathCompiler + +class JsonFile(val contents: String) + +fun interface CompiledQuery { + fun run(file: JsonFile): Long +} + +object Shim { + private fun readFile(path: String, encoding: Charset): String = + Files.readString(Paths.get(path), encoding) + + @JvmStatic + fun loadFile(filePath: String): JsonFile { + val json = readFile(filePath, StandardCharsets.UTF_8) + return JsonFile(json) + } + + @JvmStatic + fun compileQuery(query: String): CompiledQuery { + var result = 0L + val surfer = JsonSurferFastJson.INSTANCE + val compiledPath = JsonPathCompiler.compile(query) + val config = + surfer.configBuilder() + .bind(compiledPath, JsonPathListener { _, _ -> result += 1L }) + .build() + return CompiledQuery { file -> + surfer.surf(file.contents, config) + result + } + } + + @JvmStatic + fun overheadShim(): CompiledQuery { + return CompiledQuery { _ -> 0 } + } +} diff --git a/crates/rsonpath-benchmarks/src/implementations/jsurferShim/settings.gradle.kts b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/settings.gradle.kts new file mode 100644 index 00000000..913cb02e --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/jsurferShim/settings.gradle.kts @@ -0,0 +1,12 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/7.5/userguide/multi_project_builds.html + * This project uses @Incubating APIs which are subject to change. + */ + +rootProject.name = "jsurferShim" +include("lib") diff --git a/crates/rsonpath-benchmarks/src/implementations/rsonpath.rs b/crates/rsonpath-benchmarks/src/implementations/rsonpath.rs new file mode 100644 index 00000000..7cce473d --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/rsonpath.rs @@ -0,0 +1,235 @@ +use crate::framework::implementation::Implementation; +use ouroboros::self_referencing; +use rsonpath::{ + engine::main::MainEngine, + input::OwnedBytes, + result::{Match, Sink}, +}; +use rsonpath::{ + engine::{Compiler, Engine}, + input::MmapInput, +}; +use rsonpath_syntax::JsonPathQuery; +use std::{convert::Infallible, fmt::Display, fs, io}; +use thiserror::Error; + +pub struct Rsonpath {} +pub struct RsonpathCount {} +pub struct RsonpathMmap {} +pub struct RsonpathMmapCount {} + +#[self_referencing()] +pub struct RsonpathQuery { + query: JsonPathQuery, + #[borrows(query)] + #[not_covariant] + engine: MainEngine<'this>, +} + +impl Implementation for Rsonpath { + type Query = RsonpathQuery; + + type File = OwnedBytes>; + + type Error = RsonpathError; + + type Result<'a> = &'static str; + + fn id() -> &'static str { + "rsonpath" + } + + fn new() -> Result { + Ok(Rsonpath {}) + } + + fn load_file(&self, file_path: &str) -> Result { + let file = fs::read_to_string(file_path)?; + let input = OwnedBytes::new(file.into_bytes()); + + Ok(input) + } + + fn compile_query(&self, query: &str) -> Result { + let query = rsonpath_syntax::parse(query).unwrap(); + + let rsonpath = RsonpathQuery::try_new(query, |query| { + MainEngine::compile_query(query).map_err(RsonpathError::CompilerError) + })?; + + Ok(rsonpath) + } + + fn run(&self, query: &Self::Query, file: &Self::File) -> Result, Self::Error> { + query + .with_engine(|engine| engine.matches(file, &mut VoidSink)) + .map_err(RsonpathError::EngineError)?; + + Ok("[not collected]") + } +} + +impl Implementation for RsonpathCount { + type Query = RsonpathQuery; + + type File = OwnedBytes>; + + type Error = RsonpathError; + + type Result<'a> = &'static str; + + fn id() -> &'static str { + "rsonpath_count" + } + + fn new() -> Result { + Ok(RsonpathCount {}) + } + + fn load_file(&self, file_path: &str) -> Result { + let file = fs::read_to_string(file_path)?; + let input = OwnedBytes::new(file.into_bytes()); + + Ok(input) + } + + fn compile_query(&self, query: &str) -> Result { + let query = rsonpath_syntax::parse(query).unwrap(); + + let rsonpath = RsonpathQuery::try_new(query, |query| { + MainEngine::compile_query(query).map_err(RsonpathError::CompilerError) + })?; + + Ok(rsonpath) + } + + fn run(&self, query: &Self::Query, file: &Self::File) -> Result, Self::Error> { + query + .with_engine(|engine| engine.count(file)) + .map_err(RsonpathError::EngineError)?; + + Ok("[not collected]") + } +} + +impl Implementation for RsonpathMmap { + type Query = RsonpathQuery; + + type File = MmapInput; + + type Error = RsonpathError; + + type Result<'a> = &'static str; + + fn id() -> &'static str { + "rsonpath_mmap" + } + + fn new() -> Result { + Ok(RsonpathMmap {}) + } + + fn load_file(&self, file_path: &str) -> Result { + let file = fs::File::open(file_path)?; + let input = unsafe { MmapInput::map_file(&file)? }; + + Ok(input) + } + + fn compile_query(&self, query: &str) -> Result { + let query = rsonpath_syntax::parse(query).unwrap(); + + let rsonpath = RsonpathQuery::try_new(query, |query| { + MainEngine::compile_query(query).map_err(RsonpathError::CompilerError) + })?; + + Ok(rsonpath) + } + + fn run(&self, query: &Self::Query, file: &Self::File) -> Result, Self::Error> { + query + .with_engine(|engine| engine.matches(file, &mut VoidSink)) + .map_err(RsonpathError::EngineError)?; + + Ok("[not collected]") + } +} + +impl Implementation for RsonpathMmapCount { + type Query = RsonpathQuery; + + type File = MmapInput; + + type Error = RsonpathError; + + type Result<'a> = &'static str; + + fn id() -> &'static str { + "rsonpath_mmap_count" + } + + fn new() -> Result { + Ok(RsonpathMmapCount {}) + } + + fn load_file(&self, file_path: &str) -> Result { + let file = fs::File::open(file_path)?; + let input = unsafe { MmapInput::map_file(&file)? }; + + Ok(input) + } + + fn compile_query(&self, query: &str) -> Result { + let query = rsonpath_syntax::parse(query).unwrap(); + + let rsonpath = RsonpathQuery::try_new(query, |query| { + MainEngine::compile_query(query).map_err(RsonpathError::CompilerError) + })?; + + Ok(rsonpath) + } + + fn run(&self, query: &Self::Query, file: &Self::File) -> Result, Self::Error> { + query + .with_engine(|engine| engine.count(file)) + .map_err(RsonpathError::EngineError)?; + + Ok("[not collected]") + } +} + +#[derive(Error, Debug)] +pub enum RsonpathError { + #[error(transparent)] + CompilerError(#[from] rsonpath::automaton::error::CompilerError), + #[error(transparent)] + EngineError(#[from] rsonpath::engine::error::EngineError), + #[error(transparent)] + InputError(#[from] rsonpath::input::error::InputError), + #[error(transparent)] + IoError(#[from] io::Error), + #[error("something happened")] + Unknown(), +} + +pub struct MatchDisplay(Vec); + +impl Display for MatchDisplay { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for m in &self.0 { + writeln!(f, "{m}")? + } + + Ok(()) + } +} + +struct VoidSink; + +impl Sink for VoidSink { + type Error = Infallible; + + fn add_match(&mut self, _data: D) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/crates/rsonpath-benchmarks/src/implementations/rust_jsurfer.rs b/crates/rsonpath-benchmarks/src/implementations/rust_jsurfer.rs new file mode 100644 index 00000000..9b041869 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/rust_jsurfer.rs @@ -0,0 +1,272 @@ +use crate::framework::implementation::Implementation; +use jni::objects::{JClass, JObject}; +use jni::signature::{JavaType, Primitive, ReturnType, TypeSignature}; +use jni::{AttachGuard, InitArgsBuilder, JNIEnv, JNIVersion, JavaVM}; +use lazy_static::lazy_static; +use std::num::TryFromIntError; +use std::ops::{Deref, DerefMut}; +use std::sync::{Mutex, MutexGuard}; +use thiserror::Error; + +macro_rules! package { + () => { + "com/v0ldek/rsonpath/jsurferShim" + }; +} + +const SHIM_CLASS: &str = concat!(package!(), "/Shim"); +const QUERY_CLASS: &str = concat!(package!(), "/CompiledQuery"); +const FILE_CLASS: &str = concat!(package!(), "/JsonFile"); +const COMPILE_METHOD: &str = "compileQuery"; +const LOAD_METHOD: &str = "loadFile"; +const RUN_METHOD: &str = "run"; +const OVERHEAD_METHOD: &str = "overheadShim"; + +fn string_type() -> JavaType { + JavaType::Object("java/lang/String".to_owned()) +} +fn json_file_type() -> JavaType { + JavaType::Object(FILE_CLASS.to_owned()) +} +fn compiled_query_type() -> JavaType { + JavaType::Object(QUERY_CLASS.to_owned()) +} + +fn load_file_sig() -> String { + format!("({}){}", string_type(), json_file_type()) +} + +fn compile_query_sig() -> String { + format!("({}){}", string_type(), compiled_query_type()) +} + +fn overhead_sig() -> String { + format!("(){}", compiled_query_type()) +} + +fn run_sig() -> String { + let sig = TypeSignature { + args: vec![json_file_type()], + ret: ReturnType::Primitive(Primitive::Long), + }; + + sig.to_string() +} + +lazy_static! { + static ref JVM: Jvm = Jvm::new().unwrap(); +} + +pub struct Jvm(JavaVM); + +pub struct JSurferContext<'j> { + jvm: Mutex>, + shim: JClass<'j>, +} + +pub struct CompiledQuery<'j> { + query_object: JObject<'j>, +} + +pub struct LoadedFile<'j> { + file_object: JObject<'j>, +} + +pub struct Overhead<'a, 'j> { + ctx: &'a JSurferContext<'j>, + shim: JObject<'j>, +} + +impl Jvm { + fn new() -> Result { + let jar_path = std::env::var("RSONPATH_BENCH_JSURFER_SHIM_JAR_PATH").map_err(JSurferError::NoJarPathEnvVar)?; + + let jvm_args = InitArgsBuilder::new() + .version(JNIVersion::V8) + .option("-Xcheck:jni") + .option(format!("-Djava.class.path={jar_path}")) + .build()?; + + let jvm = JavaVM::new(jvm_args)?; + + Ok(Jvm(jvm)) + } + + pub fn attach() -> Result, JSurferError> { + let mut guard = JVM.0.attach_current_thread()?; + let shim = guard.find_class(SHIM_CLASS)?; + let jvm = Mutex::new(guard); + + Ok(JSurferContext { jvm, shim }) + } +} + +struct EnvWrap<'a, 'j>(MutexGuard<'a, AttachGuard<'j>>); + +impl<'a, 'j> Deref for EnvWrap<'a, 'j> { + type Target = JNIEnv<'j>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, 'j> DerefMut for EnvWrap<'a, 'j> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'j> JSurferContext<'j> { + pub fn env(&'_ self) -> impl DerefMut> + '_ { + EnvWrap(self.jvm.lock().unwrap()) + } + + pub fn create_overhead(&'_ self) -> Result, JSurferError> { + let overhead_result = self + .env() + .call_static_method(&self.shim, OVERHEAD_METHOD, overhead_sig(), &[])?; + + let actual_type = overhead_result.type_name(); + let overhead_object = overhead_result + .l() + .map_err(|e| type_error(e, OVERHEAD_METHOD, "Object", actual_type))?; + + Ok(Overhead { + ctx: self, + shim: overhead_object, + }) + } +} + +impl<'a, 'j> Overhead<'a, 'j> { + pub fn run(&self, loaded_file: &LoadedFile) -> Result { + let result = + self.ctx + .env() + .call_method(&self.shim, RUN_METHOD, run_sig(), &[(&loaded_file.file_object).into()])?; + + let actual_type = result.type_name(); + result.j().map_err(|e| type_error(e, RUN_METHOD, "Long", actual_type)) + } +} + +pub struct JSurfer { + context: JSurferContext<'static>, +} + +impl JSurfer { + fn env(&self) -> impl DerefMut> + '_ { + self.context.env() + } + + fn shim(&self) -> &JClass<'static> { + &self.context.shim + } +} + +impl Implementation for JSurfer { + type Query = CompiledQuery<'static>; + + type File = LoadedFile<'static>; + + type Error = JSurferError; + + type Result<'a> = u64; // FIXME + + fn id() -> &'static str { + "jsurfer" + } + + fn new() -> Result { + Ok(JSurfer { + context: Jvm::attach()?, + }) + } + + fn load_file(&self, path: &str) -> Result { + let file_string = self.env().new_string(path)?; + + let loaded_file = + self.env() + .call_static_method(self.shim(), LOAD_METHOD, load_file_sig(), &[(&file_string).into()])?; + + let actual_type = loaded_file.type_name(); + loaded_file + .l() + .map_err(|e| type_error(e, LOAD_METHOD, "Object", actual_type)) + .map(|f| LoadedFile { file_object: f }) + } + + fn compile_query(&self, query: &str) -> Result { + let query_string = self.env().new_string(query)?; + let compile_query_result = self.env().call_static_method( + self.shim(), + COMPILE_METHOD, + compile_query_sig(), + &[(&query_string).into()], + )?; + + let actual_type = compile_query_result.type_name(); + let compiled_query_object = compile_query_result + .l() + .map_err(|e| type_error(e, OVERHEAD_METHOD, "Object", actual_type))?; + + Ok(CompiledQuery { + query_object: compiled_query_object, + }) + } + + fn run(&self, query: &Self::Query, file: &Self::File) -> Result { + let result = self.env().call_method( + &query.query_object, + RUN_METHOD, + run_sig(), + &[(&file.file_object).into()], + )?; + + let actual_type = result.type_name(); + result + .j() + .map_err(|e| type_error(e, RUN_METHOD, "Long (non-negative)", actual_type)) + .and_then(|l| { + l.try_into() + .map_err(|err| JSurferError::ResultOutOfRange { value: l, source: err }) + }) + } +} + +#[derive(Error, Debug)] +pub enum JSurferError { + #[error("could not find JSurfer shim jar path (this should be set by the build script): {0}")] + NoJarPathEnvVar(std::env::VarError), + #[error("error while setting up the JVM: {0}")] + JvmError(#[from] jni::JvmError), + #[error("error while starting the JVM: {0}")] + StartJvmError(#[from] jni::errors::StartJvmError), + #[error("runtime error in JSurfer code: {0}")] + JavaRuntimeError(#[from] jni::errors::Error), + #[error("JVM method {method} returned {actual} when {expected} was expected")] + JavaTypeError { + method: String, + expected: String, + actual: String, + #[source] + source: jni::errors::Error, + }, + #[error("received result outside of u64 range: {value}")] + ResultOutOfRange { + value: i64, + #[source] + source: TryFromIntError, + }, +} + +fn type_error(source: jni::errors::Error, method: &str, expected: &str, actual: &str) -> JSurferError { + JSurferError::JavaTypeError { + method: method.to_owned(), + expected: expected.to_owned(), + actual: actual.to_owned(), + source, + } +} diff --git a/crates/rsonpath-benchmarks/src/implementations/serde_json_path.rs b/crates/rsonpath-benchmarks/src/implementations/serde_json_path.rs new file mode 100644 index 00000000..f10df39d --- /dev/null +++ b/crates/rsonpath-benchmarks/src/implementations/serde_json_path.rs @@ -0,0 +1,67 @@ +use crate::framework::implementation::Implementation; +use serde_json::Value; +use serde_json_path::{JsonPath, NodeList, ParseError}; +use std::{ + fmt::Display, + fs, + io::{self, BufReader}, +}; +use thiserror::Error; + +pub struct SerdeJsonPath {} + +pub struct SerdeJsonPathResult<'a>(NodeList<'a>); + +impl Implementation for SerdeJsonPath { + type Query = JsonPath; + + type File = Value; + + type Error = SerdeJsonPathError; + + type Result<'a> = SerdeJsonPathResult<'a>; + + fn id() -> &'static str { + "serde_json_path" + } + + fn new() -> Result { + Ok(SerdeJsonPath {}) + } + + fn load_file(&self, file_path: &str) -> Result { + let file = fs::File::open(file_path)?; + let reader = BufReader::new(file); + let value: Value = serde_json::from_reader(reader)?; + + Ok(value) + } + + fn compile_query(&self, query: &str) -> Result { + JsonPath::parse(query).map_err(SerdeJsonPathError::JsonPathParseError) + } + + fn run<'a>(&self, query: &Self::Query, file: &'a Self::File) -> Result, Self::Error> { + Ok(SerdeJsonPathResult(query.query(file))) + } +} + +impl<'a> Display for SerdeJsonPathResult<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for res in self.0.iter() { + writeln!(f, "{res}")?; + } + + Ok(()) + } +} + +#[derive(Error, Debug)] +pub enum SerdeJsonPathError { + #[error(transparent)] + IoError(#[from] io::Error), + #[error("error parsing JSON with serde: '{0}'")] + SerdeError(#[from] serde_json::Error), + #[error(transparent)] + JsonPathParseError(#[from] ParseError), +} diff --git a/crates/rsonpath-benchmarks/src/lib.rs b/crates/rsonpath-benchmarks/src/lib.rs new file mode 100644 index 00000000..eef5dc74 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/lib.rs @@ -0,0 +1,5 @@ +pub mod dataset; +pub mod framework; +pub mod implementations; +pub mod macros; +pub mod prelude; diff --git a/crates/rsonpath-benchmarks/src/macros.rs b/crates/rsonpath-benchmarks/src/macros.rs new file mode 100644 index 00000000..2f8cda7c --- /dev/null +++ b/crates/rsonpath-benchmarks/src/macros.rs @@ -0,0 +1,27 @@ +#[macro_export] +macro_rules! benchsets { + (name = $name:ident; config = $config:expr; targets = $( $target:path ),+ $(,)*) => { + pub fn $name() { + let mut criterion: ::criterion::Criterion<_> = $config + .configure_from_args(); + $( + match $target(&mut criterion) + { + Ok(_) => (), + Err(err) => { + ::std::panic!("error running benchset: {}", err); + } + } + )+ + } + + ::criterion::criterion_main! { $name } + }; + ($name:ident, $( $target:path ),+ $(,)*) => { + $crate::benchsets!{ + name = $name; + config = ::criterion::Criterion::default(); + targets = $( $target ),+ + } + } +} diff --git a/crates/rsonpath-benchmarks/src/main.rs b/crates/rsonpath-benchmarks/src/main.rs new file mode 100644 index 00000000..a507c0a6 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/main.rs @@ -0,0 +1,53 @@ +use clap::{Parser, ValueEnum}; +use color_eyre::eyre::Result; +use rsonpath_benchmarks::framework::implementation::Implementation; +use rsonpath_benchmarks::implementations::{ + jsonpath_rust::JsonpathRust, rsonpath::RsonpathMmap, rust_jsurfer::JSurfer, serde_json_path::SerdeJsonPath, +}; + +fn main() -> Result<()> { + color_eyre::install()?; + let args = Args::parse(); + + match args.engine { + ImplArg::Rsonpath => run(RsonpathMmap::new()?, &args.query, &args.file_path), + ImplArg::JSurfer => run(JSurfer::new()?, &args.query, &args.file_path), + ImplArg::JsonpathRust => run(JsonpathRust::new()?, &args.query, &args.file_path), + ImplArg::SerdeJsonPath => run(SerdeJsonPath::new()?, &args.query, &args.file_path), + } +} + +fn run(imp: I, query_str: &str, path_str: &str) -> Result<()> { + let query = imp.compile_query(query_str)?; + let file = imp.load_file(path_str)?; + + let result = imp.run(&query, &file)?; + + println!("{}", result); + + Ok(()) +} + +#[derive(Parser, Debug)] +#[clap(author, version, about)] +struct Args { + /// JSONPath query to run against the input JSON. + query: String, + /// Input JSON file to query. + file_path: String, + /// JSONPath implementation to use for evaluating the query. + #[clap(short, long, value_enum)] + engine: ImplArg, +} + +#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)] +enum ImplArg { + /// Use rsonpath. + Rsonpath, + /// Use JSurfer via JNI. + JSurfer, + /// Use the jsonpath-rust crate. + JsonpathRust, + /// Use the serde_json_path crate. + SerdeJsonPath, +} diff --git a/crates/rsonpath-benchmarks/src/prelude.rs b/crates/rsonpath-benchmarks/src/prelude.rs new file mode 100644 index 00000000..d92eb774 --- /dev/null +++ b/crates/rsonpath-benchmarks/src/prelude.rs @@ -0,0 +1,6 @@ +pub use crate::benchsets; +pub use crate::dataset; +pub use crate::framework::BenchmarkError; +pub use crate::framework::Benchset; +pub use crate::framework::{BenchTarget, ResultType}; +pub use criterion::Criterion; From c3e3961e8bf827a95c599cd975762ae5503d779d Mon Sep 17 00:00:00 2001 From: Mateusz Gienieczko Date: Fri, 25 Oct 2024 19:50:19 +0200 Subject: [PATCH 3/5] add bench verification to Justfile --- Justfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Justfile b/Justfile index 8d9f6d8c..acec966c 100644 --- a/Justfile +++ b/Justfile @@ -194,6 +194,7 @@ verify-bench: verify-clippy: (build-all "release") cargo +nightly clippy --workspace --no-default-features --release -- --deny warnings cargo +nightly clippy --workspace --all-features --release -- --deny warnings + cargo +nightly clippy --manifest-path ./crates/rsonpath-benchmarks/Cargo.toml --release -- --deny warnings # Verify that documentation successfully builds for rsonpath-lib. verify-doc $RUSTDOCFLAGS="--cfg docsrs -D warnings": From 8d9bebdbb0855dd83b9448a4e5980e78946fb98c Mon Sep 17 00:00:00 2001 From: Mateusz Gienieczko Date: Fri, 25 Oct 2024 19:50:51 +0200 Subject: [PATCH 4/5] fix benchmark clippy lints --- crates/rsonpath-benchmarks/src/framework.rs | 2 +- .../src/implementations/jsonpath_rust.rs | 2 +- .../src/implementations/rust_jsurfer.rs | 10 +++++----- .../src/implementations/serde_json_path.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/rsonpath-benchmarks/src/framework.rs b/crates/rsonpath-benchmarks/src/framework.rs index 865760d0..a0337cc7 100644 --- a/crates/rsonpath-benchmarks/src/framework.rs +++ b/crates/rsonpath-benchmarks/src/framework.rs @@ -191,7 +191,7 @@ trait Target { ) -> Result, BenchmarkError>; } -impl<'a> Target for BenchTarget<'a> { +impl Target for BenchTarget<'_> { fn to_bench_fn( self, file_path: &str, diff --git a/crates/rsonpath-benchmarks/src/implementations/jsonpath_rust.rs b/crates/rsonpath-benchmarks/src/implementations/jsonpath_rust.rs index dd72b817..32781319 100644 --- a/crates/rsonpath-benchmarks/src/implementations/jsonpath_rust.rs +++ b/crates/rsonpath-benchmarks/src/implementations/jsonpath_rust.rs @@ -49,7 +49,7 @@ impl Implementation for JsonpathRust { } } -impl<'a> Display for JsonpathRustResult<'a> { +impl Display for JsonpathRustResult<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for res in &self.0 { let val = res.clone().to_data(); diff --git a/crates/rsonpath-benchmarks/src/implementations/rust_jsurfer.rs b/crates/rsonpath-benchmarks/src/implementations/rust_jsurfer.rs index 9b041869..28e2d8e1 100644 --- a/crates/rsonpath-benchmarks/src/implementations/rust_jsurfer.rs +++ b/crates/rsonpath-benchmarks/src/implementations/rust_jsurfer.rs @@ -103,7 +103,7 @@ impl Jvm { struct EnvWrap<'a, 'j>(MutexGuard<'a, AttachGuard<'j>>); -impl<'a, 'j> Deref for EnvWrap<'a, 'j> { +impl<'j> Deref for EnvWrap<'_, 'j> { type Target = JNIEnv<'j>; fn deref(&self) -> &Self::Target { @@ -111,7 +111,7 @@ impl<'a, 'j> Deref for EnvWrap<'a, 'j> { } } -impl<'a, 'j> DerefMut for EnvWrap<'a, 'j> { +impl DerefMut for EnvWrap<'_, '_> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -139,7 +139,7 @@ impl<'j> JSurferContext<'j> { } } -impl<'a, 'j> Overhead<'a, 'j> { +impl Overhead<'_, '_> { pub fn run(&self, loaded_file: &LoadedFile) -> Result { let result = self.ctx @@ -252,7 +252,7 @@ pub enum JSurferError { expected: String, actual: String, #[source] - source: jni::errors::Error, + source: Box, }, #[error("received result outside of u64 range: {value}")] ResultOutOfRange { @@ -267,6 +267,6 @@ fn type_error(source: jni::errors::Error, method: &str, expected: &str, actual: method: method.to_owned(), expected: expected.to_owned(), actual: actual.to_owned(), - source, + source: Box::new(source), } } diff --git a/crates/rsonpath-benchmarks/src/implementations/serde_json_path.rs b/crates/rsonpath-benchmarks/src/implementations/serde_json_path.rs index f10df39d..346f6984 100644 --- a/crates/rsonpath-benchmarks/src/implementations/serde_json_path.rs +++ b/crates/rsonpath-benchmarks/src/implementations/serde_json_path.rs @@ -46,7 +46,7 @@ impl Implementation for SerdeJsonPath { } } -impl<'a> Display for SerdeJsonPathResult<'a> { +impl Display for SerdeJsonPathResult<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for res in self.0.iter() { writeln!(f, "{res}")?; From d1a279e14e7db7d5c983dbec145dedd521e6bc9d Mon Sep 17 00:00:00 2001 From: Mateusz Gienieczko Date: Fri, 25 Oct 2024 21:10:54 +0200 Subject: [PATCH 5/5] fix the cargo fmt command --- .github/workflows/benchmarks.yml | 22 ++-------------------- Justfile | 4 ++-- crates/rsonpath-test-codegen/src/lib.rs | 3 +-- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 3b53222b..5b7b0c75 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -87,7 +87,7 @@ jobs: name: Clippy (nightly) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - name: Force remove rsonpath-lib patch from Cargo.toml run: sed -i '/^\[patch.crates-io\]/d' ./Cargo.toml && sed -i '/^rsonpath-lib = { path = .*$/d' ./Cargo.toml - name: Install lld @@ -126,22 +126,4 @@ jobs: uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features -- --deny warnings - - format: - name: Format - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Rustup stable toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: rustfmt - override: true - default: true - - name: Format - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + args: --all-features -- --deny warnings diff --git a/Justfile b/Justfile index acec966c..6c76407f 100644 --- a/Justfile +++ b/Justfile @@ -204,7 +204,7 @@ verify-doc $RUSTDOCFLAGS="--cfg docsrs -D warnings": # Verify formatting rules are not violated. verify-fmt: - cargo fmt -- --check + cargo fmt --all --check # === CLEAN === @@ -305,4 +305,4 @@ release-bug-template ver: let idx = (cat $path | str index-of '# '); if ($idx == -1) { sed -z -i 's/# ]*>/# \n - v{{ver}}/' $path; - } \ No newline at end of file + } diff --git a/crates/rsonpath-test-codegen/src/lib.rs b/crates/rsonpath-test-codegen/src/lib.rs index caacfbd8..88e1a3f3 100644 --- a/crates/rsonpath-test-codegen/src/lib.rs +++ b/crates/rsonpath-test-codegen/src/lib.rs @@ -111,8 +111,7 @@ impl DocumentName { if self.is_compressed { Path::join(&PathBuf::from("compressed"), s) - } - else { + } else { s } }