From 9c34bebf2763492a3f5de1d81c22c7dcdda00dc0 Mon Sep 17 00:00:00 2001 From: fatalem0 Date: Fri, 30 May 2025 01:19:08 +0300 Subject: [PATCH 1/8] feat: add metrics collection for slo tests --- .github/scripts/check-metrics.sh | 72 +++ .github/workflows/slo-report.yml | 22 + .github/workflows/slo.yml | 92 ++- Cargo.lock | 802 ++++++++++++++++++------ ydb-slo-tests/Cargo.toml | 6 +- ydb-slo-tests/README.md | 32 +- ydb-slo-tests/examples/native/db.rs | 36 +- ydb-slo-tests/examples/native/native.rs | 58 +- ydb-slo-tests/src/args.rs | 12 +- ydb-slo-tests/src/lib.rs | 1 + ydb-slo-tests/src/metrics/mod.rs | 308 +++++++++ ydb-slo-tests/src/metrics/span.rs | 90 +++ ydb-slo-tests/src/workers.rs | 119 +++- 13 files changed, 1365 insertions(+), 285 deletions(-) create mode 100755 .github/scripts/check-metrics.sh create mode 100644 .github/workflows/slo-report.yml create mode 100644 ydb-slo-tests/src/metrics/mod.rs create mode 100644 ydb-slo-tests/src/metrics/span.rs diff --git a/.github/scripts/check-metrics.sh b/.github/scripts/check-metrics.sh new file mode 100755 index 00000000..beb79b9e --- /dev/null +++ b/.github/scripts/check-metrics.sh @@ -0,0 +1,72 @@ +#!/bin/sh +set -e + +metrics_file="${1:-./metrics.json}" + +if [ ! -f "$metrics_file" ]; then + echo "❌ Metrics file not found: $metrics_file" + exit 1 +fi + +echo "📊 Checking metrics in: $metrics_file" + +fail=0 + +check_metric() { + key="$1" + threshold="$2" + label="$3" + + if [ -z "$threshold" ]; then + echo "⚠️ No threshold for $label" + return + fi + + echo "🔍 Checking $label (threshold = $threshold)..." + + values=$(jq -r --arg key "$key" '.[$key][0]?.values[]? | select(.[1] != "NaN") | .[1]' "$metrics_file") + if [ -z "$values" ]; then + echo "❌ Metric '$key' is missing or has no valid values. Stopping checks." + exit 1 + fi + + sum=0 + count=0 + for value in $values; do + val=$(echo "$value" | tr -d '"') + sum=$(echo "$sum + $val" | bc -l) + count=$((count + 1)) + done + + if [ "$count" -eq 0 ]; then + echo "⚠️ No valid data points for $label." + exit 1 + fi + + average=$(echo "$sum / $count" | bc -l) + echo " ➤ Average $label = $average" + + if [ "$(echo "$average < $threshold" | bc -l)" -eq 1 ]; then + echo "❌ Average $label ($average) is below threshold $threshold" + fail=1 + return + fi + + echo "✅ $label passed (average = $average)" +} + +#check_metric "read_latency_ms_p95" "$READ_LATENCY_MS_THRESHOLD" "Read Latency P95" +#check_metric "write_latency_ms_p95" "$WRITE_LATENCY_MS_THRESHOLD" "Write Latency P95" +check_metric "read_throughput" "$READ_THROUGHPUT_THRESHOLD" "Read Throughput" +check_metric "write_throughput" "$WRITE_THROUGHPUT_THRESHOLD" "Write Throughput" +#check_metric "read_attempts" "$READ_ATTEMPTS_THRESHOLD" "Read Attempts" +#check_metric "write_attempts" "$WRITE_ATTEMPTS_THRESHOLD" "Write Attempts" +check_metric "read_availability" "$READ_AVAILABILITY_THRESHOLD" "Read Availability" +check_metric "write_availability" "$WRITE_AVAILABILITY_THRESHOLD" "Write Availability" + +if [ "$fail" -eq 1 ]; then + echo "❗ Some metrics did not meet thresholds." + exit 1 +else + echo "🎉 All metrics validated successfully." +fi diff --git a/.github/workflows/slo-report.yml b/.github/workflows/slo-report.yml new file mode 100644 index 00000000..9c505e87 --- /dev/null +++ b/.github/workflows/slo-report.yml @@ -0,0 +1,22 @@ +name: slo-report + +on: + workflow_run: + workflows: [ 'SLO' ] + types: + - completed + +jobs: + test-ydb-slo-action: + runs-on: ubuntu-latest + name: Publish YDB SLO Report + permissions: + contents: read + pull-requests: write + if: github.event.workflow_run.conclusion == 'success' + steps: + - name: Publish YDB SLO Report + uses: ydb-platform/ydb-slo-action/report@main + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + github_run_id: ${{ github.event.workflow_run.id }} \ No newline at end of file diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index 30f45027..74cb306e 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -1,4 +1,4 @@ -name: SLO tests +name: SLO on: push: @@ -31,8 +31,6 @@ env: jobs: ydb-slo-action-init: - # // https://github.com/ydb-platform/ydb-rs-sdk/issues/227 - if: ${{ false }} if: (!contains(github.event.pull_request.labels.*.name, 'no slo')) name: Run YDB SLO Tests @@ -40,21 +38,20 @@ jobs: strategy: matrix: - example: - - native + sdk: + - name: native + label: native rust_version: - "RUST_VERSION_OLD" - "RUST_VERSION_NEW" concurrency: - group: slo-${{ github.ref }}-${{ matrix.example }}-${{ matrix.rust_version }} + group: slo-${{ github.ref }}-${{ matrix.sdk.name }}-${{ matrix.rust_version }} cancel-in-progress: true steps: - - name: Checkout + - name: Checkout repository uses: actions/checkout@v4 - with: - submodules: true - name: Install rust uses: dtolnay/rust-toolchain@v1 @@ -73,33 +70,36 @@ jobs: - name: Rust cache uses: Swatinem/rust-cache@v2 + - name: Prepare envs + run: | + REF=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + REF_SAFE=${REF//\//__} + echo "METRICS_REF=$REF_SAFE" >> $GITHUB_ENV + echo "METRICS_LABEL=${{ matrix.sdk.label }}" >> $GITHUB_ENV + echo "METRICS_JOB_NAME=${{ matrix.sdk.name }}" >> $GITHUB_ENV + - name: Initialize YDB SLO uses: ydb-platform/ydb-slo-action/init@main with: github_pull_request_number: ${{ github.event.inputs.github_pull_request_number }} github_token: ${{ secrets.GITHUB_TOKEN }} - workload_name: ${{ matrix.example }}-${{ matrix.rust_version }} + workload_name: ${{ matrix.sdk.name }}-${{ matrix.rust_version }} ydb_database_node_count: 5 - name: Prepare SLO Database run: | - cargo run --example ${{ matrix.example }} grpc://localhost:2135 /Root/testdb tableName create + cargo run --example ${{ matrix.sdk.name }} grpc://localhost:2135 /Root/testdb tableName create - name: Run SLO Tests - env: - REF: '${{ github.head_ref || github.ref }}' run: | - cargo run --example ${{ matrix.example }} grpc://localhost:2135 /Root/testdb tableName run \ + cargo run --example ${{ matrix.sdk.name }} grpc://localhost:2135 /Root/testdb tableName run \ + --prom-pgw localhost:9091 \ + --report-period 250 \ --time ${{ inputs.slo_workload_duration_seconds || 600}} \ --read-rps ${{ inputs.slo_workload_read_max_rps || 1000}} \ --write-rps ${{ inputs.slo_workload_write_max_rps || 100}} \ - --read-timeout 10000 \ - --write-timeout 10000 || true - - - if: always() - name: Cleanup SLO Database - run: | - cargo run --example ${{ matrix.example }} grpc://localhost:2135 /Root/testdb tableName cleanup + --read-timeout 1000 \ + --write-timeout 1000 || true - if: always() name: Store ydb chaos testing logs @@ -109,6 +109,52 @@ jobs: - if: always() uses: actions/upload-artifact@v4 with: - name: ${{ matrix.example}}-${{ matrix.rust_version }}-chaos-ydb.log + name: ${{ matrix.sdk.name}}-${{ matrix.rust_version }}-chaos-ydb.log path: ./chaos-ydb.log - retention-days: 1 \ No newline at end of file + retention-days: 1 + + - if: always() + name: Cleanup SLO Database + run: | + cargo run --example ${{ matrix.sdk.name }} grpc://localhost:2135 /Root/testdb tableName cleanup || true + validate-slo-metrics: + name: Validate SLO metrics + needs: ydb-slo-action-init + runs-on: ubuntu-latest + + env: + # READ_LATENCY_MS_THRESHOLD: "1" # 95th percentile read operations latency in milliseconds + # WRITE_LATENCY_MS_THRESHOLD: "1" # 95th percentile write operations latency in milliseconds + READ_THROUGHPUT_THRESHOLD: "150" # Read operations throughput + WRITE_THROUGHPUT_THRESHOLD: "3" # Write operations throughput + # READ_ATTEMPTS_THRESHOLD: "1" # Read attempts throughput + # WRITE_ATTEMPTS_THRESHOLD: "1" # Write attempts throughput + READ_AVAILABILITY_THRESHOLD: "90" # Read operations availability + WRITE_AVAILABILITY_THRESHOLD: "90" # Write operations availability + + strategy: + matrix: + sdk: + - name: native + label: native + rust_version: + - "RUST_VERSION_OLD" + - "RUST_VERSION_NEW" + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Download metrics artifact + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.sdk.name }}-${{ matrix.rust_version }}-metrics.json + path: ./artifacts + + - name: Make script executable + run: chmod +x .github/scripts/check-metrics.sh + + - name: Validate SLO thresholds + run: | + .github/scripts/check-metrics.sh \ + ./artifacts/${{ matrix.sdk.name }}-${{ matrix.rust_version }}-metrics.json \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 15fb581e..5d06198f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,6 +146,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -160,12 +166,12 @@ checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" dependencies = [ "async-trait", "axum-core", - "bitflags", + "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 0.2.8", + "http-body 0.4.5", + "hyper 0.14.17", "itoa", "matchit", "memchr", @@ -190,8 +196,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 0.2.8", + "http-body 0.4.5", "mime", "tower-layer", "tower-service", @@ -236,6 +242,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + [[package]] name = "block-buffer" version = "0.10.2" @@ -295,7 +307,7 @@ dependencies = [ "num-integer", "num-traits", "serde", - "time 0.1.45", + "time", "winapi", ] @@ -318,7 +330,7 @@ checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" dependencies = [ "anstream", "anstyle", - "bitflags", + "bitflags 1.3.2", "clap_lex", "strsim", ] @@ -332,7 +344,7 @@ dependencies = [ "heck 0.4.0", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.101", ] [[package]] @@ -341,17 +353,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" -[[package]] -name = "clocksource" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129026dd5a8a9592d96916258f3a5379589e513ea5e86aeb0bd2530286e44e9e" -dependencies = [ - "libc", - "time 0.3.37", - "winapi", -] - [[package]] name = "colorchoice" version = "1.0.3" @@ -383,6 +384,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.3" @@ -429,22 +436,26 @@ dependencies = [ ] [[package]] -name = "decimal-rs" -version = "0.1.43" +name = "dashmap" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0ad9d041ab836f528b91b4f4039feda1091adbef4d85850eac6b3d2f9cd6f3" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "serde", - "stack-buf", + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] -name = "deranged" -version = "0.3.11" +name = "decimal-rs" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "da0ad9d041ab836f528b91b4f4039feda1091adbef4d85850eac6b3d2f9cd6f3" dependencies = [ - "powerfmt", + "serde", + "stack-buf", ] [[package]] @@ -526,6 +537,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "fastrand" version = "1.7.0" @@ -556,6 +573,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -572,11 +604,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da1b8f89c5b5a5b7e59405cfcf0bb9588e5ed19f0b57a4cd542bbba3f164a6d" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -584,45 +631,63 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.101", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -670,6 +735,24 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "governor" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "quanta", + "rand", + "smallvec", +] + [[package]] name = "h2" version = "0.3.11" @@ -681,14 +764,33 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", - "indexmap", + "http 0.2.8", + "indexmap 1.8.0", "slab", "tokio", "tokio-util 0.6.9", "tracing", ] +[[package]] +name = "h2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap 2.9.0", + "slab", + "tokio", + "tokio-util 0.7.11", + "tracing", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -705,6 +807,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + [[package]] name = "hashers" version = "1.0.1" @@ -721,10 +829,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" dependencies = [ "base64 0.13.0", - "bitflags", + "bitflags 1.3.2", "bytes", "headers-core", - "http", + "http 0.2.8", "httpdate", "mime", "sha-1", @@ -736,7 +844,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http", + "http 0.2.8", ] [[package]] @@ -780,6 +888,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -787,7 +906,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.8", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", "pin-project-lite", ] @@ -819,9 +961,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.11", + "http 0.2.8", + "http-body 0.4.5", "httparse", "httpdate", "itoa", @@ -833,14 +975,34 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.10", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ - "http", - "hyper", + "http 0.2.8", + "hyper 0.14.17", "rustls", "tokio", "tokio-rustls", @@ -852,12 +1014,61 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.17", "pin-project-lite", "tokio", "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.17", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.2.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.2.0", + "pin-project-lite", + "socket2 0.5.8", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -885,6 +1096,16 @@ dependencies = [ "hashbrown 0.11.2", ] +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.3", +] + [[package]] name = "instant" version = "0.1.12" @@ -928,10 +1149,11 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -963,10 +1185,11 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ + "autocfg", "scopeguard", ] @@ -979,6 +1202,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1093,6 +1325,35 @@ dependencies = [ "twoway", ] +[[package]] +name = "native-tls" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -1185,12 +1446,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-integer" version = "0.1.44" @@ -1258,12 +1513,50 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1276,15 +1569,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.12", "smallvec", - "windows-sys 0.32.0", + "windows-targets 0.52.6", ] [[package]] @@ -1348,7 +1641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.8.0", ] [[package]] @@ -1384,10 +1677,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "powerfmt" -version = "0.2.0" +name = "pkg-config" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "ppv-lite86" @@ -1445,13 +1738,30 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.68" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] +[[package]] +name = "prometheus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "libc", + "memchr", + "parking_lot", + "protobuf", + "reqwest 0.12.4", + "thiserror", +] + [[package]] name = "prost" version = "0.11.2" @@ -1507,6 +1817,28 @@ dependencies = [ "prost", ] +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -1515,9 +1847,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -1553,14 +1885,12 @@ dependencies = [ ] [[package]] -name = "ratelimit" -version = "0.10.0" +name = "raw-cpuid" +version = "10.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea961700fd7260e7fa3701c8287d901b2172c51f9c1421fa0f21d7f7e184b7" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" dependencies = [ - "clocksource", - "parking_lot", - "thiserror", + "bitflags 1.3.2", ] [[package]] @@ -1569,7 +1899,16 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags 2.9.1", ] [[package]] @@ -1618,16 +1957,18 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.11", + "http 0.2.8", + "http-body 0.4.5", + "hyper 0.14.17", "hyper-rustls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "lazy_static", "log", "mime", + "native-tls", "percent-encoding", "pin-project-lite", "rustls", @@ -1636,13 +1977,57 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "webpki-roots", - "winreg", + "winreg 0.7.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.10", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.2.0", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.52.0", ] [[package]] @@ -1708,6 +2093,30 @@ dependencies = [ "base64 0.13.0", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "ryu" version = "1.0.9" @@ -1776,7 +2185,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -1810,7 +2219,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.101", ] [[package]] @@ -1960,9 +2369,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -1975,6 +2384,27 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1984,7 +2414,7 @@ dependencies = [ "cfg-if", "fastrand", "libc", - "redox_syscall", + "redox_syscall 0.2.10", "remove_dir_all", "winapi", ] @@ -2012,7 +2442,7 @@ checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.101", ] [[package]] @@ -2035,37 +2465,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "time" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinyvec" version = "1.5.1" @@ -2118,7 +2517,17 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.101", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] @@ -2206,10 +2615,10 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.11", + "http 0.2.8", + "http-body 0.4.5", + "hyper 0.14.17", "hyper-timeout", "percent-encoding", "pin-project", @@ -2250,7 +2659,7 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 1.8.0", "pin-project", "pin-project-lite", "rand", @@ -2268,12 +2677,12 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" dependencies = [ - "bitflags", + "bitflags 1.3.2", "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 0.2.8", + "http-body 0.4.5", "http-range-header", "pin-project-lite", "tower", @@ -2295,9 +2704,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.30" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if", "log", @@ -2308,22 +2717,22 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.101", ] [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ - "lazy_static", + "once_cell", "valuable", ] @@ -2404,7 +2813,7 @@ dependencies = [ "base64 0.13.0", "byteorder", "bytes", - "http", + "http 0.2.8", "httparse", "log", "rand", @@ -2510,6 +2919,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -2547,8 +2962,8 @@ dependencies = [ "futures-channel", "futures-util", "headers", - "http", - "hyper", + "http 0.2.8", + "hyper 0.14.17", "log", "mime", "mime_guess", @@ -2582,26 +2997,27 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", - "lazy_static", "log", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -2619,9 +3035,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2629,22 +3045,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" @@ -2717,19 +3136,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" -dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -2800,12 +3206,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2818,12 +3218,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2842,12 +3236,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2860,12 +3248,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2890,12 +3272,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2917,6 +3293,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "ydb" version = "0.9.7" @@ -2928,7 +3314,7 @@ dependencies = [ "derivative", "derive_builder", "futures-util", - "http", + "http 0.2.8", "itertools", "jsonwebtoken", "lazy_static", @@ -2940,7 +3326,7 @@ dependencies = [ "prost", "prost-types", "rand", - "reqwest", + "reqwest 0.11.9", "secrecy", "serde", "serde_json", @@ -2996,9 +3382,11 @@ dependencies = [ "async-trait", "base64 0.22.1", "clap", + "governor", + "prometheus", "rand", "rand_core", - "ratelimit", + "reqwest 0.11.9", "tokio", "tokio-util 0.7.11", "ydb", @@ -3021,7 +3409,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.101", ] [[package]] diff --git a/ydb-slo-tests/Cargo.toml b/ydb-slo-tests/Cargo.toml index 93f4b813..51321d84 100644 --- a/ydb-slo-tests/Cargo.toml +++ b/ydb-slo-tests/Cargo.toml @@ -14,11 +14,13 @@ base64 = { version = "0.22.1" } rand = { version = "0.8.5" } clap = { version = "=4.2.7", features = ["derive"] } rand_core = { version = "0.6.4" } -ratelimit = { version = "0.10.0" } tokio = { version = "=1.38.1" } tokio-util = { version = "=0.7.11", features = ["rt"] } -ydb = { version = "0.9.7", path="../ydb"} +ydb = { version = "0.9.7", path = "../ydb" } async-trait = "0.1" +reqwest = { version = "=0.11.9" } +governor = { version = "=0.6.0" } +prometheus = { version = "=0.13.4", features = ["push"] } [[example]] name = "native" diff --git a/ydb-slo-tests/README.md b/ydb-slo-tests/README.md index 2736b311..e9b0148d 100644 --- a/ydb-slo-tests/README.md +++ b/ydb-slo-tests/README.md @@ -23,7 +23,7 @@ cleanup: run: -`cargo run --example native grpc://localhost:2136 /local tableName run -c 1000 --read-rps 1000 --read-timeout 10000 --write-rps 100 --write-timeout 10000 --time 600` +`cargo run --example native grpc://localhost:2136 /local tableName run -c 1000 --read-rps 1000 --read-timeout 10000 --write-rps 100 --write-timeout 10000 --time 600 --prom-pgw localhost:9091 --report-period 250` ## Arguments for commands: @@ -71,21 +71,25 @@ Arguments: Options: -c --initial-data-count amount of initially created rows - --read-rps read RPS + --read-rps read RPS --read-timeout read timeout milliseconds - --write-rps write RPS + --write-rps write RPS --write-timeout write timeout milliseconds --time run time in seconds + + --prom-pgw prometheus push gateway + --report-period prometheus push period in milliseconds ``` ## What's inside -When running `run` command, the program creates two jobs: `readJob`, `writeJob`. +When running `run` command, the program creates two jobs: `readJob`, `writeJob`, `metricsJob`. - `readJob` reads rows from the table one by one with random identifiers generated by `writeJob` - `writeJob` generates and inserts rows +- `metricsJob` periodically sends metrics to Prometheus Table have these fields: @@ -97,3 +101,23 @@ Table have these fields: - `payload_hash Uint64?` Primary key: `("hash", "id")` + +## Collected metrics + +- `sdk_errors_total` - Total number of errors encountered, categorized by error type +- `sdk_operations_total` - Total number of operations, categorized by type attempted by the SDK +- `sdk_operations_success_total` - Total number of successful operations, categorized by type +- `sdk_operations_failure_total` - Total number of failed operations, categorized by type +- `sdk_operation_latency_seconds` - Latency of operations performed by the SDK in seconds, categorized by type and + status +- `sdk_retry_attempts` - Current retry attempts, categorized by operation type +- `sdk_retry_attempts_total` - Total number of retry attempts, categorized by operation type +- `sdk_retries_success_total` - Total number of successful retries, categorized by operation type +- `sdk_retries_failure_total` - Total number of failed retries, categorized by operation type +- `sdk_pending_operations` - Current number of pending operations, categorized by type + +## Look at metrics in grafana + +You can get dashboard used in that +test [here](https://github.com/ydb-platform/slo-tests/blob/main/k8s/helms/grafana.yaml#L69) - you will need to import +json into grafana. diff --git a/ydb-slo-tests/examples/native/db.rs b/ydb-slo-tests/examples/native/db.rs index 84e41c3d..6bdf7fc0 100644 --- a/ydb-slo-tests/examples/native/db.rs +++ b/ydb-slo-tests/examples/native/db.rs @@ -1,12 +1,13 @@ use async_trait::async_trait; -use ydb::{ - ydb_params, ClientBuilder, Query, Row, TableClient, YdbResult, YdbResultWithCustomerErr, -}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use ydb::{ydb_params, ClientBuilder, Query, TableClient, YdbResult, YdbResultWithCustomerErr}; use ydb_slo_tests::args::CreateArgs; use ydb_slo_tests::cli::SloTestsCli; use ydb_slo_tests::row::{RowID, TestRow}; use ydb_slo_tests::workers::ReadWriter; +pub type Attempts = usize; + #[derive(Clone)] pub struct Database { db_table_client: TableClient, @@ -65,7 +66,7 @@ impl Database { #[async_trait] impl ReadWriter for Database { - async fn read(&self, row_id: RowID) -> YdbResult { + async fn read(&self, row_id: RowID) -> (YdbResultWithCustomerErr<()>, Attempts) { let query = Query::from(format!( r#" DECLARE $id AS Uint64; @@ -78,17 +79,22 @@ impl ReadWriter for Database { )) .with_params(ydb_params!("$id" => row_id)); - self.db_table_client + let attempts = AtomicUsize::new(0); + + let result = self + .db_table_client .retry_transaction(|t| async { let mut t = t; - let res = t.query(query.clone()).await?; - Ok(res) + attempts.fetch_add(1, Ordering::Relaxed); + t.query(query.clone()).await?; + Ok(()) }) - .await? - .into_only_row() + .await; + + (result, attempts.load(Ordering::Relaxed)) } - async fn write(&self, row: TestRow) -> YdbResultWithCustomerErr<()> { + async fn write(&self, row: TestRow) -> (YdbResultWithCustomerErr<()>, Attempts) { let query = Query::from(format!( r#" DECLARE $id AS Uint64; @@ -119,13 +125,19 @@ impl ReadWriter for Database { "$payload_timestamp" => row.payload_timestamp, )); - self.db_table_client + let attempts = AtomicUsize::new(0); + + let result = self + .db_table_client .retry_transaction(|t| async { let mut t = t; + attempts.fetch_add(1, Ordering::Relaxed); t.query(query.clone()).await?; t.commit().await?; Ok(()) }) - .await + .await; + + (result, attempts.load(Ordering::Relaxed)) } } diff --git a/ydb-slo-tests/examples/native/native.rs b/ydb-slo-tests/examples/native/native.rs index 9c6d8aac..f3c1effb 100644 --- a/ydb-slo-tests/examples/native/native.rs +++ b/ydb-slo-tests/examples/native/native.rs @@ -2,7 +2,8 @@ extern crate ydb_slo_tests; use crate::db::Database; use clap::Parser; -use ratelimit::Ratelimiter; +use governor::{Quota, RateLimiter}; +use std::num::NonZeroU32; use std::sync::Arc; use std::time::Duration; use tokio::sync::Mutex; @@ -21,10 +22,12 @@ mod db; async fn main() -> Result<(), Box> { let cli = SloTestsCli::parse(); let command = cli.command.clone(); + println!("Program is started"); let database = Database::new(cli) .await .unwrap_or_else(|err| panic!("Failed to initialize YDB client: {}", err)); + println!("Initialized database"); match command { Command::Create(create_args) => { @@ -70,19 +73,30 @@ async fn main() -> Result<(), Box> { println!("Cleaned up table"); } Command::Run(run_args) => { + let metrics_ref = std::env::var("METRICS_REF").unwrap_or("metrics_ref".to_string()); + let metrics_label = + std::env::var("METRICS_LABEL").unwrap_or("metrics_label".to_string()); + let metrics_job_name = + std::env::var("METRICS_JOB_NAME").unwrap_or("metrics-test-job".to_string()); + let generator = Arc::new(Mutex::new(Generator::new( run_args.initial_data_count as RowID, ))); - let workers = Workers::new(Arc::new(database), run_args.clone()); + let workers = Workers::new( + Arc::new(database), + run_args.clone(), + metrics_ref, + metrics_label, + metrics_job_name, + ); let tracker = TaskTracker::new(); let token = CancellationToken::new(); - let read_rate_limiter = Arc::new( - Ratelimiter::builder(run_args.read_rps, Duration::from_secs(1)) - .max_tokens(run_args.read_rps) - .build()?, - ); + let read_rate_limiter = Arc::new(RateLimiter::direct( + Quota::per_second(NonZeroU32::new(run_args.read_rps).unwrap()) + .allow_burst(NonZeroU32::new(1).unwrap()), + )); for _ in 0..run_args.read_rps { let cloned_token = token.clone(); @@ -100,11 +114,10 @@ async fn main() -> Result<(), Box> { } println!("Started {} read workers", run_args.read_rps); - let write_rate_limiter = Arc::new( - Ratelimiter::builder(run_args.write_rps, Duration::from_secs(1)) - .max_tokens(run_args.write_rps) - .build()?, - ); + let write_rate_limiter = Arc::new(RateLimiter::direct( + Quota::per_second(NonZeroU32::new(run_args.write_rps).unwrap()) + .allow_burst(NonZeroU32::new(1).unwrap()), + )); for _ in 0..run_args.write_rps { let cloned_token = token.clone(); @@ -124,6 +137,22 @@ async fn main() -> Result<(), Box> { } println!("Started {} write workers", run_args.write_rps); + let metrics_rate_limiter = Arc::new(RateLimiter::direct( + Quota::with_period(Duration::from_millis(run_args.report_period)) + .unwrap() + .allow_burst(NonZeroU32::new(1).unwrap()), + )); + + let metrics_worker = Arc::clone(&workers); + let metrics_token = token.clone(); + tracker.spawn(async move { + metrics_worker + .collect_metrics(&metrics_rate_limiter, metrics_token) + .await + }); + + println!("Started metrics worker"); + { let tracker = tracker.clone(); tokio::spawn(async move { @@ -135,6 +164,11 @@ async fn main() -> Result<(), Box> { tracker.wait().await; + workers + .close() + .await + .unwrap_or_else(|err| panic!("Failed to close workers: {}", err)); + println!("All workers are completed"); } } diff --git a/ydb-slo-tests/src/args.rs b/ydb-slo-tests/src/args.rs index d913b42d..3bbe8aa0 100644 --- a/ydb-slo-tests/src/args.rs +++ b/ydb-slo-tests/src/args.rs @@ -31,7 +31,7 @@ pub struct RunArgs { /// read RPS #[arg(long = "read-rps", default_value_t = 1000)] - pub read_rps: u64, + pub read_rps: u32, /// read timeout milliseconds #[arg(long = "read-timeout", default_value_t = 10000)] @@ -39,7 +39,7 @@ pub struct RunArgs { /// write RPS #[arg(long = "write-rps", default_value_t = 100)] - pub write_rps: u64, + pub write_rps: u32, /// write timeout milliseconds #[arg(long = "write-timeout", default_value_t = 10000)] @@ -48,4 +48,12 @@ pub struct RunArgs { /// run time in seconds #[arg(long, default_value_t = 600)] pub time: u64, + + /// prometheus push gateway + #[arg(long, default_value_t = String::from(""))] + pub prom_pgw: String, + + /// prometheus push period in milliseconds + #[arg(long, default_value_t = 250)] + pub report_period: u64, } diff --git a/ydb-slo-tests/src/lib.rs b/ydb-slo-tests/src/lib.rs index 3a334de1..7fd6cd89 100644 --- a/ydb-slo-tests/src/lib.rs +++ b/ydb-slo-tests/src/lib.rs @@ -1,5 +1,6 @@ pub mod args; pub mod cli; pub mod generator; +pub mod metrics; pub mod row; pub mod workers; diff --git a/ydb-slo-tests/src/metrics/mod.rs b/ydb-slo-tests/src/metrics/mod.rs new file mode 100644 index 00000000..0b28216d --- /dev/null +++ b/ydb-slo-tests/src/metrics/mod.rs @@ -0,0 +1,308 @@ +pub mod span; + +use crate::metrics::span::Span; +use prometheus::{ + labels, proto, BasicAuthentication, CounterVec, Encoder, Error, GaugeVec, HistogramVec, + ProtobufEncoder, Registry, TextEncoder, +}; +use reqwest::header::CONTENT_TYPE; +use reqwest::{Client, StatusCode}; +use std::collections::HashMap; +use std::fmt; +use std::hash::BuildHasher; +use std::time::Duration; + +#[derive(Debug)] +pub struct MetricsCollector { + pub registry: Registry, + pub job_name: String, + pub grouping: HashMap, + pub prom_pgw: String, + pub errors_total: CounterVec, + pub operations_total: CounterVec, + pub operations_success_total: CounterVec, + pub operations_failure_total: CounterVec, + pub operation_latency_seconds: HistogramVec, + pub retry_attempts: GaugeVec, + pub retry_attempts_total: CounterVec, + pub retries_success_total: CounterVec, + pub retries_failure_total: CounterVec, + pub pending_operations: GaugeVec, +} + +impl MetricsCollector { + pub fn new(prom_pgw: String, ref_id: String, label: String, job_name: String) -> Self { + let registry = Registry::new(); + + let errors_total = prometheus::register_counter_vec_with_registry!( + "sdk_errors_total", + "Total number of errors encountered, categorized by error type", + &["error_type"], + registry, + ) + .unwrap(); + + let operations_total = prometheus::register_counter_vec_with_registry!( + "sdk_operations_total", + "Total number of operations, categorized by type attempted by the SDK", + &["operation_type"], + registry, + ) + .unwrap(); + + let operations_success_total = prometheus::register_counter_vec_with_registry!( + "sdk_operations_success_total", + "Total number of successful operations, categorized by type", + &["operation_type"], + registry, + ) + .unwrap(); + + let operations_failure_total = prometheus::register_counter_vec_with_registry!( + "sdk_operations_failure_total", + "Total number of failed operations, categorized by type", + &["operation_type"], + registry, + ) + .unwrap(); + + let operation_latency_seconds = prometheus::register_histogram_vec_with_registry!( + "sdk_operation_latency_seconds", + "Latency of operations performed by the SDK in seconds, categorized by type and status", + &["operation_type", "operation_status"], + registry, + ) + .unwrap(); + + let retry_attempts = prometheus::register_gauge_vec_with_registry!( + "sdk_retry_attempts", + "Current retry attempts, categorized by operation type", + &["operation_type"], + registry, + ) + .unwrap(); + + let retry_attempts_total = prometheus::register_counter_vec_with_registry!( + "sdk_retry_attempts_total", + "Total number of retry attempts, categorized by operation type", + &["operation_type"], + registry, + ) + .unwrap(); + + let retries_success_total = prometheus::register_counter_vec_with_registry!( + "sdk_retries_success_total", + "Total number of successful retries, categorized by operation type", + &["operation_type"], + registry, + ) + .unwrap(); + + let retries_failure_total = prometheus::register_counter_vec_with_registry!( + "sdk_retries_failure_total", + "Total number of failed retries, categorized by operation type", + &["operation_type"], + registry, + ) + .unwrap(); + + let pending_operations = prometheus::register_gauge_vec_with_registry!( + "sdk_pending_operations", + "Current number of pending operations, categorized by type", + &["operation_type"], + registry, + ) + .unwrap(); + + let grouping = labels! { + "ref".to_owned() => ref_id.to_owned(), + "sdk".to_owned() => format!("{}-{}", "rust".to_owned(), label.to_owned()).to_owned(), + "sdk_version".to_owned() => env!("CARGO_PKG_VERSION").to_owned(), + }; + + Self { + registry, + job_name: job_name.to_owned(), + grouping, + prom_pgw, + errors_total, + operations_total, + operations_success_total, + operations_failure_total, + operation_latency_seconds, + retry_attempts, + retry_attempts_total, + retries_success_total, + retries_failure_total, + pending_operations, + } + } + + pub fn start(&self, operation_type: OperationType) -> Span { + Span::start(self, operation_type) + } + + pub async fn push_to_gateway(&self) -> Result<(), MetricsPushError> { + let mut buffer = String::new(); + let encoder = TextEncoder::new(); + let metric_families = self.registry.gather(); + encoder.encode_utf8(&metric_families, &mut buffer)?; + + self.push( + &self.job_name, + self.grouping.clone(), + &self.prom_pgw, + metric_families.clone(), + None, + ) + .await?; + + Ok(()) + } + + // non-blocking variation of push fn from https://github.com/tikv/rust-prometheus/blob/v0.14.0/src/push.rs + async fn push( + &self, + job: &str, + grouping: HashMap, + url: &str, + mfs: Vec, + basic_auth: Option, + ) -> prometheus::Result<()> { + // Suppress clippy warning needless_pass_by_value. + let grouping = grouping; + + let mut push_url = if url.contains("://") { + url.to_owned() + } else { + format!("http://{}", url) + }; + + if push_url.ends_with('/') { + push_url.pop(); + } + + let mut url_components = Vec::new(); + if job.contains('/') { + return Err(Error::Msg(format!("job contains '/': {}", job))); + } + + // TODO: escape job + url_components.push(job.to_owned()); + + for (ln, lv) in &grouping { + // TODO: check label name + if lv.contains('/') { + return Err(Error::Msg(format!( + "value of grouping label {} contains '/': {}", + ln, lv + ))); + } + url_components.push(ln.to_owned()); + url_components.push(lv.to_owned()); + } + + push_url = format!("{}/metrics/job/{}", push_url, url_components.join("/")); + + let encoder = ProtobufEncoder::new(); + let mut buf = Vec::new(); + + for mf in mfs { + // Check for pre-existing grouping labels: + for m in mf.get_metric() { + for lp in m.get_label() { + if lp.get_name() == "job" { + return Err(Error::Msg(format!( + "pushed metric {} already contains a \ + job label", + mf.get_name() + ))); + } + if grouping.contains_key(lp.get_name()) { + return Err(Error::Msg(format!( + "pushed metric {} already contains \ + grouping label {}", + mf.get_name(), + lp.get_name() + ))); + } + } + } + // Ignore error, `no metrics` and `no name`. + let _ = encoder.encode(&[mf], &mut buf); + } + + let client = Client::builder() + .timeout(Duration::from_secs(10)) + .build() + .unwrap(); + + let mut builder = client + .put(push_url.as_str()) + .header(CONTENT_TYPE, encoder.format_type()) + .body(buf); + + if let Some(BasicAuthentication { username, password }) = basic_auth { + builder = builder.basic_auth(username, Some(password)); + } + + let response = builder + .send() + .await + .map_err(|e| Error::Msg(format!("{}", e)))?; + + match response.status() { + StatusCode::ACCEPTED => Ok(()), + StatusCode::OK => Ok(()), + _ => Err(Error::Msg(format!( + "unexpected status code {} while pushing to {}", + response.status(), + push_url + ))), + } + } + + pub async fn reset(&self) -> Result<(), MetricsPushError> { + self.errors_total.reset(); + self.operations_total.reset(); + self.operations_success_total.reset(); + self.operations_failure_total.reset(); + self.operation_latency_seconds.reset(); + self.retry_attempts.reset(); + self.retry_attempts_total.reset(); + self.retries_success_total.reset(); + self.retries_failure_total.reset(); + self.pending_operations.reset(); + + self.push_to_gateway().await + } +} + +#[derive(Clone, Debug)] +pub enum OperationType { + Read, + Write, +} + +impl fmt::Display for OperationType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", format!("{:?}", self).to_lowercase()) + } +} + +#[derive(Debug)] +pub struct MetricsPushError { + value: prometheus::Error, +} + +impl fmt::Display for MetricsPushError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Failed to push metrics to Pushgateway: {}", self.value) + } +} + +impl From for MetricsPushError { + fn from(err: prometheus::Error) -> Self { + MetricsPushError { value: err } + } +} diff --git a/ydb-slo-tests/src/metrics/span.rs b/ydb-slo-tests/src/metrics/span.rs new file mode 100644 index 00000000..612eb80e --- /dev/null +++ b/ydb-slo-tests/src/metrics/span.rs @@ -0,0 +1,90 @@ +use crate::metrics::{MetricsCollector, OperationType}; +use crate::workers::Attempts; +use std::time::Instant; +use ydb::YdbOrCustomerError; + +pub struct Span<'a> { + name: OperationType, + start: Instant, + metrics: &'a MetricsCollector, +} + +impl<'a> Span<'a> { + pub fn start(metrics: &'a MetricsCollector, operation_type: OperationType) -> Self { + metrics + .pending_operations + .with_label_values(&[&operation_type.to_string()]) + .inc(); + + Self { + name: operation_type, + start: Instant::now(), + metrics, + } + } + + pub fn finish(self, attempts: Attempts, err: Option) { + let elapsed = self.start.elapsed().as_secs_f64(); + let operation_type = self.name.to_string(); + + self.metrics + .pending_operations + .with_label_values(&[operation_type.as_str()]) + .dec(); + + self.metrics + .retry_attempts + .with_label_values(&[operation_type.as_str()]) + .set(attempts as f64); + + self.metrics + .operations_total + .with_label_values(&[operation_type.as_str()]) + .inc(); + + self.metrics + .retry_attempts_total + .with_label_values(&[operation_type.as_str()]) + .inc_by(attempts as f64); + + match err { + Some(e) => { + self.metrics + .errors_total + .with_label_values(&[e.to_string().as_str()]) + .inc(); + + self.metrics + .retries_failure_total + .with_label_values(&[operation_type.as_str()]) + .inc_by(attempts as f64); + + self.metrics + .operations_failure_total + .with_label_values(&[operation_type.as_str()]) + .inc(); + + self.metrics + .operation_latency_seconds + .with_label_values(&[operation_type.as_str(), "failure"]) + .observe(elapsed); + } + None => { + self.metrics + .retries_success_total + .with_label_values(&[operation_type.as_str()]) + .inc_by(attempts as f64); + + self.metrics + .operations_success_total + .with_label_values(&[operation_type.as_str()]) + .inc(); + + self.metrics + .operation_latency_seconds + .with_label_values(&[operation_type.as_str(), "success"]) + .observe(elapsed); + } + } + } +} diff --git a/ydb-slo-tests/src/workers.rs b/ydb-slo-tests/src/workers.rs index 1e514eff..20553385 100644 --- a/ydb-slo-tests/src/workers.rs +++ b/ydb-slo-tests/src/workers.rs @@ -1,43 +1,71 @@ use crate::args::RunArgs; use crate::generator::Generator; +use crate::metrics; +use crate::metrics::{MetricsCollector, OperationType}; use crate::row::{RowID, TestRow}; use async_trait::async_trait; +use governor::clock::DefaultClock; +use governor::middleware::NoOpMiddleware; +use governor::state::{InMemoryState, NotKeyed}; +use governor::RateLimiter; use rand::Rng; -use ratelimit::Ratelimiter; +use std::fmt::{Display, Formatter}; use std::sync::Arc; use std::time::Duration; -use tokio::time::{sleep, timeout}; +use tokio::time::timeout; use tokio_util::sync::CancellationToken; -use ydb::{Row, YdbResult, YdbResultWithCustomerErr}; +use ydb::YdbResultWithCustomerErr; + +pub type Attempts = usize; #[async_trait] pub trait ReadWriter: Clone + Send + Sync { - async fn read(&self, row_id: RowID) -> YdbResult; - async fn write(&self, row: TestRow) -> YdbResultWithCustomerErr<()>; + async fn read(&self, row_id: RowID) -> (YdbResultWithCustomerErr<()>, Attempts); + async fn write(&self, row: TestRow) -> (YdbResultWithCustomerErr<()>, Attempts); } pub struct Workers { database: Arc, config: RunArgs, + metrics: MetricsCollector, } impl Workers { - pub fn new(database: Arc, config: RunArgs) -> Arc> { - Arc::new(Self { database, config }) + pub fn new( + database: Arc, + config: RunArgs, + metrics_ref: String, + metrics_label: String, + metrics_job_name: String, + ) -> Arc> { + let metrics = MetricsCollector::new( + config.prom_pgw.clone(), + metrics_ref, + metrics_label, + metrics_job_name, + ); + + Arc::new(Self { + database, + config, + metrics, + }) } - pub async fn start_read_load(&self, limiter: &Ratelimiter, cancel: CancellationToken) { + pub async fn start_read_load( + &self, + limiter: &RateLimiter, + cancel: CancellationToken, + ) { loop { if cancel.is_cancelled() { return; } - if let Err(interval) = limiter.try_wait() { - sleep(interval).await; - continue; - } + limiter.until_ready().await; let row_id = rand::thread_rng().gen_range(0..self.config.initial_data_count); + let span = self.metrics.start(OperationType::Read); let read_result = timeout( Duration::from_millis(self.config.read_timeout), @@ -46,11 +74,13 @@ impl Workers { .await; match read_result { - Ok(Ok(_)) => { + Ok((Ok(()), attempts)) => { + span.finish(attempts, None); continue; } - Ok(Err(err)) => { - println!("read failed: {}", err); + Ok((Err(e), attempts)) => { + span.finish(attempts, Some(e.clone())); + println!("Read failed: {}", e); return; } Err(_) => { @@ -62,7 +92,7 @@ impl Workers { pub async fn start_write_load( &self, - limiter: &Ratelimiter, + limiter: &RateLimiter, generator: &Generator, cancel: CancellationToken, ) { @@ -71,12 +101,10 @@ impl Workers { return; } - if let Err(interval) = limiter.try_wait() { - sleep(interval).await; - continue; - } + limiter.until_ready().await; let row = generator.to_owned().generate(); + let span = self.metrics.start(OperationType::Write); let write_result = timeout( Duration::from_millis(self.config.write_timeout), @@ -85,11 +113,13 @@ impl Workers { .await; match write_result { - Ok(Ok(_)) => { + Ok((Ok(()), attempts)) => { + span.finish(attempts, None); continue; } - Ok(Err(err)) => { - println!("write failed: {}", err); + Ok((Err(e), attempts)) => { + span.finish(attempts, Some(e.clone())); + println!("Write failed: {}", e); return; } Err(_) => { @@ -98,4 +128,47 @@ impl Workers { } } } + + pub async fn collect_metrics( + &self, + limiter: &RateLimiter, + cancel: CancellationToken, + ) { + loop { + if cancel.is_cancelled() { + return; + } + + limiter.until_ready().await; + + if let Err(err) = self.metrics.push_to_gateway().await { + println!("Failed to collect metrics: {}", err); + continue; + } + } + } + + pub async fn close(&self) -> Result<(), WorkersCloseError> { + self.metrics + .reset() + .await + .map_err(|err| WorkersCloseError { value: err }) + } +} + +#[derive(Debug)] +pub struct WorkersCloseError { + value: metrics::MetricsPushError, +} + +impl From for WorkersCloseError { + fn from(err: metrics::MetricsPushError) -> WorkersCloseError { + Self { value: err } + } +} + +impl Display for WorkersCloseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.value, f) + } } From 4d90ae07c1e1631b848b30a5143f48856f5737bd Mon Sep 17 00:00:00 2001 From: fatalem0 Date: Mon, 9 Jun 2025 00:28:22 +0300 Subject: [PATCH 2/8] fix: after comments --- .github/workflows/slo.yml | 28 +- Cargo.lock | 14 +- Cargo.toml | 3 +- ydb-slo-tests/README.md | 62 +++-- ydb-slo-tests/examples/native/db.rs | 143 ---------- ydb-slo-tests/examples/native/native.rs | 178 ------------ ydb-slo-tests/{ => lib}/Cargo.toml | 9 +- ydb-slo-tests/{ => lib}/src/args.rs | 44 ++- ydb-slo-tests/{ => lib}/src/cli.rs | 15 +- ydb-slo-tests/lib/src/db/mod.rs | 2 + ydb-slo-tests/lib/src/db/query.rs | 82 ++++++ ydb-slo-tests/{src => lib/src/db}/row.rs | 4 +- ydb-slo-tests/{ => lib}/src/generator.rs | 6 +- ydb-slo-tests/{ => lib}/src/lib.rs | 2 +- ydb-slo-tests/{ => lib}/src/metrics/mod.rs | 0 ydb-slo-tests/{ => lib}/src/metrics/span.rs | 0 ydb-slo-tests/{ => lib}/src/workers.rs | 68 ++--- ydb-slo-tests/native/Cargo.toml | 19 ++ ydb-slo-tests/native/src/db.rs | 108 ++++++++ ydb-slo-tests/native/src/main.rs | 292 ++++++++++++++++++++ 20 files changed, 626 insertions(+), 453 deletions(-) delete mode 100644 ydb-slo-tests/examples/native/db.rs delete mode 100644 ydb-slo-tests/examples/native/native.rs rename ydb-slo-tests/{ => lib}/Cargo.toml (77%) rename ydb-slo-tests/{ => lib}/src/args.rs (63%) rename ydb-slo-tests/{ => lib}/src/cli.rs (60%) create mode 100644 ydb-slo-tests/lib/src/db/mod.rs create mode 100644 ydb-slo-tests/lib/src/db/query.rs rename ydb-slo-tests/{src => lib/src/db}/row.rs (92%) rename ydb-slo-tests/{ => lib}/src/generator.rs (88%) rename ydb-slo-tests/{ => lib}/src/lib.rs (86%) rename ydb-slo-tests/{ => lib}/src/metrics/mod.rs (100%) rename ydb-slo-tests/{ => lib}/src/metrics/span.rs (100%) rename ydb-slo-tests/{ => lib}/src/workers.rs (69%) create mode 100644 ydb-slo-tests/native/Cargo.toml create mode 100644 ydb-slo-tests/native/src/db.rs create mode 100644 ydb-slo-tests/native/src/main.rs diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index 74cb306e..6bbafae7 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -38,15 +38,12 @@ jobs: strategy: matrix: - sdk: - - name: native - label: native rust_version: - "RUST_VERSION_OLD" - "RUST_VERSION_NEW" concurrency: - group: slo-${{ github.ref }}-${{ matrix.sdk.name }}-${{ matrix.rust_version }} + group: slo-${{ github.ref }}-native-${{ matrix.rust_version }} cancel-in-progress: true steps: @@ -75,31 +72,30 @@ jobs: REF=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} REF_SAFE=${REF//\//__} echo "METRICS_REF=$REF_SAFE" >> $GITHUB_ENV - echo "METRICS_LABEL=${{ matrix.sdk.label }}" >> $GITHUB_ENV - echo "METRICS_JOB_NAME=${{ matrix.sdk.name }}" >> $GITHUB_ENV + echo "METRICS_LABEL=native" >> $GITHUB_ENV + echo "METRICS_JOB_NAME=native" >> $GITHUB_ENV - name: Initialize YDB SLO uses: ydb-platform/ydb-slo-action/init@main with: github_pull_request_number: ${{ github.event.inputs.github_pull_request_number }} github_token: ${{ secrets.GITHUB_TOKEN }} - workload_name: ${{ matrix.sdk.name }}-${{ matrix.rust_version }} + workload_name: native-${{ matrix.rust_version }} ydb_database_node_count: 5 - name: Prepare SLO Database run: | - cargo run --example ${{ matrix.sdk.name }} grpc://localhost:2135 /Root/testdb tableName create + cargo run -- -t tableName grpc://localhost:2135 /Root/testdb create - name: Run SLO Tests run: | - cargo run --example ${{ matrix.sdk.name }} grpc://localhost:2135 /Root/testdb tableName run \ + cargo run -- -t tableName --write-timeout 1 grpc://localhost:2135 /Root/testdb run \ --prom-pgw localhost:9091 \ - --report-period 250 \ + --report-period 1 \ --time ${{ inputs.slo_workload_duration_seconds || 600}} \ --read-rps ${{ inputs.slo_workload_read_max_rps || 1000}} \ --write-rps ${{ inputs.slo_workload_write_max_rps || 100}} \ - --read-timeout 1000 \ - --write-timeout 1000 || true + --read-timeout 1 || true - if: always() name: Store ydb chaos testing logs @@ -109,14 +105,14 @@ jobs: - if: always() uses: actions/upload-artifact@v4 with: - name: ${{ matrix.sdk.name}}-${{ matrix.rust_version }}-chaos-ydb.log + name: native-${{ matrix.rust_version }}-chaos-ydb.log path: ./chaos-ydb.log retention-days: 1 - if: always() name: Cleanup SLO Database run: | - cargo run --example ${{ matrix.sdk.name }} grpc://localhost:2135 /Root/testdb tableName cleanup || true + cargo run -- -t tableName grpc://localhost:2135 /Root/testdb cleanup || true validate-slo-metrics: name: Validate SLO metrics needs: ydb-slo-action-init @@ -148,7 +144,7 @@ jobs: - name: Download metrics artifact uses: actions/download-artifact@v4 with: - name: ${{ matrix.sdk.name }}-${{ matrix.rust_version }}-metrics.json + name: native-${{ matrix.rust_version }}-metrics.json path: ./artifacts - name: Make script executable @@ -157,4 +153,4 @@ jobs: - name: Validate SLO thresholds run: | .github/scripts/check-metrics.sh \ - ./artifacts/${{ matrix.sdk.name }}-${{ matrix.rust_version }}-metrics.json \ No newline at end of file + ./artifacts/native-${{ matrix.rust_version }}-metrics.json \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5d06198f..abdc987d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3376,7 +3376,7 @@ dependencies = [ ] [[package]] -name = "ydb-slo-tests" +name = "ydb-slo-tests-lib" version = "0.1.0" dependencies = [ "async-trait", @@ -3388,8 +3388,20 @@ dependencies = [ "rand_core", "reqwest 0.11.9", "tokio", + "ydb", +] + +[[package]] +name = "ydb-slo-tests-native" +version = "0.1.0" +dependencies = [ + "async-trait", + "clap", + "governor", + "tokio", "tokio-util 0.7.11", "ydb", + "ydb-slo-tests-lib", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 448ecabd..6303bc29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,6 @@ members = [ "ydb-grpc", # "ydb-grpc-helpers", "ydb-example-urlshortener", - "ydb-slo-tests", + "ydb-slo-tests/lib", + "ydb-slo-tests/native", ] diff --git a/ydb-slo-tests/README.md b/ydb-slo-tests/README.md index e9b0148d..0ebe5b5c 100644 --- a/ydb-slo-tests/README.md +++ b/ydb-slo-tests/README.md @@ -15,72 +15,82 @@ It has 3 commands: create: -`cargo run --example native grpc://localhost:2136 /local tableName create --min-partitions-count 6 --max-partitions-count 1000 --partition-size 1 -c 1000 --write-timeout 10000` +`cargo run -- -t testingTable --write-timeout 10 --db-init-timeout 3 grpc://localhost:2136 /local create --min-partitions-count 6 --max-partitions-count 1000 --partition-size 1 -c 1000` cleanup: -`cargo run --example native grpc://localhost:2136 /local tableName cleanup` +`cargo run -- -t testingTable --write-timeout 10 --db-init-timeout 3 grpc://localhost:2136 /local cleanup` run: -`cargo run --example native grpc://localhost:2136 /local tableName run -c 1000 --read-rps 1000 --read-timeout 10000 --write-rps 100 --write-timeout 10000 --time 600 --prom-pgw localhost:9091 --report-period 250` +`cargo run -- -t testingTable --write-timeout 10 --db-init-timeout 3 grpc://localhost:2136 /local run -c 1000 --read-rps 1000 --read-timeout 10 --write-rps 100 --prom-pgw localhost:9091 --time 600 --report-period 1 --shutdown-time 30` -## Arguments for commands: +## Arguments and options for commands: ### create -`cargo run --example create [OPTIONS]` +`cargo run [COMMON_OPTIONS] create [OPTIONS]` ``` Arguments: ENDPOINT YDB endpoint to connect to DB YDB database to connect to - TABLE_NAME table name to create + +Common options: + -t --table-name table name to create [default: testingTable] + --write-timeout write timeout in seconds [default: 10] + --db-init-timeout YDB database initialization timeout in seconds [default: 3] Options: - --min-partitions-count minimum amount of partitions in table - --max-partitions-count maximum amount of partitions in table - --partition-size partition size in mb + -c --initial-data-count amount of initially created rows [default: 1000] - -c --initial-data-count amount of initially created rows + --min-partitions-count minimum amount of partitions in table [default: 6] - --write-timeout write timeout milliseconds + --max-partitions-count maximum amount of partitions in table [default: 1000] + --partition-size partition size in mb [default: 1] ``` ### cleanup -`cargo run --example cleanup` +`cargo run [COMMON_OPTIONS] cleanup` ``` Arguments: ENDPOINT YDB endpoint to connect to DB YDB database to connect to - TABLE_NAME table name to cleanup + +Common options: + -t --table-name table name to create [default: testingTable] + --write-timeout write timeout in seconds [default: 10] + --db-init-timeout YDB database initialization timeout in seconds [default: 3] ``` ### run -`cargo run --example run` +`cargo run [COMMON_OPTIONS] run [OPTIONS]` ``` Arguments: ENDPOINT YDB endpoint to connect to DB YDB database to connect to - TABLE_NAME table name to use + +Common options: + -t --table-name table name to create [default: testingTable] + --write-timeout write timeout in seconds [default: 10] + --db-init-timeout YDB database initialization timeout in seconds [default: 3] -Options: - -c --initial-data-count amount of initially created rows +Options: + -c --initial-data-count amount of initially created rows [default: 1000] - --read-rps read RPS - --read-timeout read timeout milliseconds - - --write-rps write RPS - --write-timeout write timeout milliseconds - - --time run time in seconds + --read-rps read RPS [default: 1000] + --write-rps write RPS [default: 100] + + --prom-pgw prometheus push gateway [default: ] - --prom-pgw prometheus push gateway - --report-period prometheus push period in milliseconds + --read-timeout read timeout in seconds [default: 10] + --time write timeout in seconds [default: 600] + --report-period prometheus push period in seconds [default: 1] + --shutdown-time time to wait before force kill workers in seconds [default: 30] ``` ## What's inside diff --git a/ydb-slo-tests/examples/native/db.rs b/ydb-slo-tests/examples/native/db.rs deleted file mode 100644 index 6bdf7fc0..00000000 --- a/ydb-slo-tests/examples/native/db.rs +++ /dev/null @@ -1,143 +0,0 @@ -use async_trait::async_trait; -use std::sync::atomic::{AtomicUsize, Ordering}; -use ydb::{ydb_params, ClientBuilder, Query, TableClient, YdbResult, YdbResultWithCustomerErr}; -use ydb_slo_tests::args::CreateArgs; -use ydb_slo_tests::cli::SloTestsCli; -use ydb_slo_tests::row::{RowID, TestRow}; -use ydb_slo_tests::workers::ReadWriter; - -pub type Attempts = usize; - -#[derive(Clone)] -pub struct Database { - db_table_client: TableClient, - cli_args: SloTestsCli, -} - -impl Database { - pub async fn new(cli: SloTestsCli) -> YdbResult { - let client = ClientBuilder::new_from_connection_string(&cli.endpoint)? - .with_database(&cli.db) - .client()?; - - client.wait().await?; - - let table_client = client.table_client(); - - Ok(Self { - db_table_client: table_client, - cli_args: cli, - }) - } - - pub async fn create_table(&self, create_args: &CreateArgs) -> YdbResult<()> { - self.db_table_client - .retry_execute_scheme_query(format!( - "CREATE TABLE IF NOT EXISTS {table} - ( - hash Uint64, - id Uint64, - payload_str Text?, - payload_double Double?, - payload_timestamp Timestamp?, - payload_hash Uint64?, - PRIMARY KEY (hash, id) - ) WITH ( - UNIFORM_PARTITIONS = {min_partitions_count}, - AUTO_PARTITIONING_BY_SIZE = ENABLED, - AUTO_PARTITIONING_PARTITION_SIZE_MB = {partition_size}, - AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = {min_partitions_count}, - AUTO_PARTITIONING_MAX_PARTITIONS_COUNT = {max_partitions_count} - )", - table = self.cli_args.table_name, - min_partitions_count = create_args.min_partitions_count, - max_partitions_count = create_args.max_partitions_count, - partition_size = create_args.partition_size, - )) - .await - } - - pub async fn drop_table(&self) -> YdbResult<()> { - self.db_table_client - .retry_execute_scheme_query(format!("DROP TABLE {}", self.cli_args.table_name)) - .await - } -} - -#[async_trait] -impl ReadWriter for Database { - async fn read(&self, row_id: RowID) -> (YdbResultWithCustomerErr<()>, Attempts) { - let query = Query::from(format!( - r#" - DECLARE $id AS Uint64; - - SELECT id, payload_str, payload_double, payload_timestamp, payload_hash - FROM {table} - WHERE id = $id AND hash = Digest::NumericHash($id); - "#, - table = self.cli_args.table_name - )) - .with_params(ydb_params!("$id" => row_id)); - - let attempts = AtomicUsize::new(0); - - let result = self - .db_table_client - .retry_transaction(|t| async { - let mut t = t; - attempts.fetch_add(1, Ordering::Relaxed); - t.query(query.clone()).await?; - Ok(()) - }) - .await; - - (result, attempts.load(Ordering::Relaxed)) - } - - async fn write(&self, row: TestRow) -> (YdbResultWithCustomerErr<()>, Attempts) { - let query = Query::from(format!( - r#" - DECLARE $id AS Uint64; - DECLARE $payload_str AS Utf8; - DECLARE $payload_double AS Double; - DECLARE $payload_timestamp AS Timestamp; - - UPSERT INTO {table} ( - id, - hash, - payload_str, - payload_double, - payload_timestamp - ) VALUES ( - $id, - Digest::NumericHash($id), - $payload_str, - $payload_double, - $payload_timestamp - ); - "#, - table = &self.cli_args.table_name, - )) - .with_params(ydb_params!( - "$id" => row.id, - "$payload_str" => row.payload_str, - "$payload_double" => row.payload_double, - "$payload_timestamp" => row.payload_timestamp, - )); - - let attempts = AtomicUsize::new(0); - - let result = self - .db_table_client - .retry_transaction(|t| async { - let mut t = t; - attempts.fetch_add(1, Ordering::Relaxed); - t.query(query.clone()).await?; - t.commit().await?; - Ok(()) - }) - .await; - - (result, attempts.load(Ordering::Relaxed)) - } -} diff --git a/ydb-slo-tests/examples/native/native.rs b/ydb-slo-tests/examples/native/native.rs deleted file mode 100644 index f3c1effb..00000000 --- a/ydb-slo-tests/examples/native/native.rs +++ /dev/null @@ -1,178 +0,0 @@ -extern crate ydb_slo_tests; - -use crate::db::Database; -use clap::Parser; -use governor::{Quota, RateLimiter}; -use std::num::NonZeroU32; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::Mutex; -use tokio::time; -use tokio::time::timeout; -use tokio_util::sync::CancellationToken; -use tokio_util::task::TaskTracker; -use ydb_slo_tests::cli::{Command, SloTestsCli}; -use ydb_slo_tests::generator::Generator; -use ydb_slo_tests::row::RowID; -use ydb_slo_tests::workers::{ReadWriter, Workers}; - -mod db; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let cli = SloTestsCli::parse(); - let command = cli.command.clone(); - println!("Program is started"); - - let database = Database::new(cli) - .await - .unwrap_or_else(|err| panic!("Failed to initialize YDB client: {}", err)); - println!("Initialized database"); - - match command { - Command::Create(create_args) => { - database - .create_table(&create_args) - .await - .unwrap_or_else(|err| panic!("Failed to create table: {}", err)); - - println!("Created table"); - - let tracker = TaskTracker::new(); - let database = Arc::new(database.clone()); - let generator = Arc::new(Mutex::new(Generator::new(0))); - - for _ in 0..create_args.initial_data_count { - let database = Arc::clone(&database); - let generator = Arc::clone(&generator); - - tracker.spawn(async move { - let database = &database; - let row = generator.lock().await.generate(); - - timeout( - Duration::from_millis(create_args.write_timeout), - database.write(row), - ) - .await - .unwrap() - }); - } - - tracker.close(); - tracker.wait().await; - - println!("Inserted {} rows", create_args.initial_data_count); - } - Command::Cleanup => { - database - .drop_table() - .await - .unwrap_or_else(|err| panic!("Failed to clean up table: {}", err)); - - println!("Cleaned up table"); - } - Command::Run(run_args) => { - let metrics_ref = std::env::var("METRICS_REF").unwrap_or("metrics_ref".to_string()); - let metrics_label = - std::env::var("METRICS_LABEL").unwrap_or("metrics_label".to_string()); - let metrics_job_name = - std::env::var("METRICS_JOB_NAME").unwrap_or("metrics-test-job".to_string()); - - let generator = Arc::new(Mutex::new(Generator::new( - run_args.initial_data_count as RowID, - ))); - - let workers = Workers::new( - Arc::new(database), - run_args.clone(), - metrics_ref, - metrics_label, - metrics_job_name, - ); - let tracker = TaskTracker::new(); - let token = CancellationToken::new(); - - let read_rate_limiter = Arc::new(RateLimiter::direct( - Quota::per_second(NonZeroU32::new(run_args.read_rps).unwrap()) - .allow_burst(NonZeroU32::new(1).unwrap()), - )); - - for _ in 0..run_args.read_rps { - let cloned_token = token.clone(); - let workers = Arc::clone(&workers); - let read_rate_limiter = Arc::clone(&read_rate_limiter); - - tracker.spawn(async move { - let workers = &workers; - let read_rate_limiter = &read_rate_limiter; - - workers - .start_read_load(read_rate_limiter, cloned_token.clone()) - .await - }); - } - println!("Started {} read workers", run_args.read_rps); - - let write_rate_limiter = Arc::new(RateLimiter::direct( - Quota::per_second(NonZeroU32::new(run_args.write_rps).unwrap()) - .allow_burst(NonZeroU32::new(1).unwrap()), - )); - - for _ in 0..run_args.write_rps { - let cloned_token = token.clone(); - let workers = Arc::clone(&workers); - let write_rate_limiter = Arc::clone(&write_rate_limiter); - let generator = Arc::clone(&generator); - - tracker.spawn(async move { - let workers = &workers; - let write_rate_limiter = &write_rate_limiter; - let generator = generator.lock().await; - - workers - .start_write_load(write_rate_limiter, &generator, cloned_token.clone()) - .await - }); - } - println!("Started {} write workers", run_args.write_rps); - - let metrics_rate_limiter = Arc::new(RateLimiter::direct( - Quota::with_period(Duration::from_millis(run_args.report_period)) - .unwrap() - .allow_burst(NonZeroU32::new(1).unwrap()), - )); - - let metrics_worker = Arc::clone(&workers); - let metrics_token = token.clone(); - tracker.spawn(async move { - metrics_worker - .collect_metrics(&metrics_rate_limiter, metrics_token) - .await - }); - - println!("Started metrics worker"); - - { - let tracker = tracker.clone(); - tokio::spawn(async move { - time::sleep(Duration::from_secs(run_args.time)).await; - tracker.close(); - token.cancel(); - }); - } - - tracker.wait().await; - - workers - .close() - .await - .unwrap_or_else(|err| panic!("Failed to close workers: {}", err)); - - println!("All workers are completed"); - } - } - - println!("Program is finished"); - Ok(()) -} diff --git a/ydb-slo-tests/Cargo.toml b/ydb-slo-tests/lib/Cargo.toml similarity index 77% rename from ydb-slo-tests/Cargo.toml rename to ydb-slo-tests/lib/Cargo.toml index 51321d84..8b50f2a4 100644 --- a/ydb-slo-tests/Cargo.toml +++ b/ydb-slo-tests/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] publish = false -name = "ydb-slo-tests" +name = "ydb-slo-tests-lib" version = "0.1.0" authors = ["fatalem0 "] edition = "2021" @@ -15,13 +15,8 @@ rand = { version = "0.8.5" } clap = { version = "=4.2.7", features = ["derive"] } rand_core = { version = "0.6.4" } tokio = { version = "=1.38.1" } -tokio-util = { version = "=0.7.11", features = ["rt"] } -ydb = { version = "0.9.7", path = "../ydb" } +ydb = { version = "0.9.7", path = "../../ydb" } async-trait = "0.1" reqwest = { version = "=0.11.9" } governor = { version = "=0.6.0" } prometheus = { version = "=0.13.4", features = ["push"] } - -[[example]] -name = "native" -path = "examples/native/native.rs" diff --git a/ydb-slo-tests/src/args.rs b/ydb-slo-tests/lib/src/args.rs similarity index 63% rename from ydb-slo-tests/src/args.rs rename to ydb-slo-tests/lib/src/args.rs index 3bbe8aa0..df9d6290 100644 --- a/ydb-slo-tests/src/args.rs +++ b/ydb-slo-tests/lib/src/args.rs @@ -2,6 +2,10 @@ use clap::Args; #[derive(Args, Clone)] pub struct CreateArgs { + /// amount of initially created rows + #[arg(long = "initial-data-count", short = 'c', default_value_t = 1000)] + pub initial_data_count: u64, + /// minimum amount of partitions in table #[arg(long = "min-partitions-count", default_value_t = 6)] pub min_partitions_count: u64, @@ -12,15 +16,7 @@ pub struct CreateArgs { /// partition size in mb #[arg(long = "partition-size", default_value_t = 1)] - pub partition_size: u64, - - /// amount of initially created rows - #[arg(long = "initial-data-count", short = 'c', default_value_t = 1000)] - pub initial_data_count: u64, - - /// write timeout milliseconds - #[arg(long = "write-timeout", default_value_t = 10000)] - pub write_timeout: u64, + pub partition_size_mb: u64, } #[derive(Args, Clone)] @@ -33,27 +29,27 @@ pub struct RunArgs { #[arg(long = "read-rps", default_value_t = 1000)] pub read_rps: u32, - /// read timeout milliseconds - #[arg(long = "read-timeout", default_value_t = 10000)] - pub read_timeout: u64, - /// write RPS #[arg(long = "write-rps", default_value_t = 100)] pub write_rps: u32, - /// write timeout milliseconds - #[arg(long = "write-timeout", default_value_t = 10000)] - pub write_timeout: u64, + /// prometheus push gateway + #[arg(long = "prom-pgw", default_value_t = String::from(""))] + pub prom_pgw: String, + + /// read timeout in seconds + #[arg(long = "read-timeout", default_value_t = 10)] + pub read_timeout_seconds: u64, /// run time in seconds - #[arg(long, default_value_t = 600)] - pub time: u64, + #[arg(long = "time", default_value_t = 600)] + pub time_seconds: u64, - /// prometheus push gateway - #[arg(long, default_value_t = String::from(""))] - pub prom_pgw: String, + /// prometheus push period in seconds + #[arg(long = "report-period", default_value_t = 1)] + pub report_period_seconds: u64, - /// prometheus push period in milliseconds - #[arg(long, default_value_t = 250)] - pub report_period: u64, + /// time to wait before force kill workers in seconds + #[arg(long = "shutdown-time", default_value_t = 30)] + pub shutdown_time_seconds: u64, } diff --git a/ydb-slo-tests/src/cli.rs b/ydb-slo-tests/lib/src/cli.rs similarity index 60% rename from ydb-slo-tests/src/cli.rs rename to ydb-slo-tests/lib/src/cli.rs index 0dccdf05..e3819863 100644 --- a/ydb-slo-tests/src/cli.rs +++ b/ydb-slo-tests/lib/src/cli.rs @@ -3,9 +3,6 @@ use clap::{Parser, Subcommand}; #[derive(Clone, Parser)] pub struct SloTestsCli { - #[command(subcommand)] - pub command: Command, - /// YDB endpoint to connect to pub endpoint: String, @@ -13,7 +10,19 @@ pub struct SloTestsCli { pub db: String, /// table name to create + #[arg(long = "table-name", short = 't', default_value_t = String::from("testingTable"))] pub table_name: String, + + /// write timeout in seconds + #[arg(long = "write-timeout", default_value_t = 10)] + pub write_timeout_seconds: u64, + + /// YDB database initialization timeout in seconds + #[arg(long = "db-init-timeout", default_value_t = 3)] + pub db_init_timeout_seconds: u64, + + #[command(subcommand)] + pub command: Command, } #[derive(Clone, Subcommand)] diff --git a/ydb-slo-tests/lib/src/db/mod.rs b/ydb-slo-tests/lib/src/db/mod.rs new file mode 100644 index 00000000..de4b252f --- /dev/null +++ b/ydb-slo-tests/lib/src/db/mod.rs @@ -0,0 +1,2 @@ +pub mod query; +pub mod row; diff --git a/ydb-slo-tests/lib/src/db/query.rs b/ydb-slo-tests/lib/src/db/query.rs new file mode 100644 index 00000000..68e4bd1c --- /dev/null +++ b/ydb-slo-tests/lib/src/db/query.rs @@ -0,0 +1,82 @@ +use crate::db::row::{Row, RowID}; +use ydb::{ydb_params, Query}; + +pub fn generate_create_table_query( + table_name: &str, + min_partitions_count: u64, + max_partitions_count: u64, + partition_size: u64, +) -> String { + format!( + "CREATE TABLE IF NOT EXISTS {table} + ( + hash Uint64, + id Uint64, + payload_str Text?, + payload_double Double?, + payload_timestamp Timestamp?, + payload_hash Uint64?, + PRIMARY KEY (hash, id) + ) WITH ( + UNIFORM_PARTITIONS = {min_partitions_count}, + AUTO_PARTITIONING_BY_SIZE = ENABLED, + AUTO_PARTITIONING_PARTITION_SIZE_MB = {partition_size}, + AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = {min_partitions_count}, + AUTO_PARTITIONING_MAX_PARTITIONS_COUNT = {max_partitions_count} + )", + table = table_name, + min_partitions_count = min_partitions_count, + max_partitions_count = max_partitions_count, + partition_size = partition_size, + ) +} + +pub fn generate_drop_table_query(table_name: &str) -> String { + format!("DROP TABLE {}", table_name) +} + +pub fn generate_read_query(table_name: &str, row_id: RowID) -> Query { + Query::from(format!( + r#" + DECLARE $id AS Uint64; + + SELECT id, payload_str, payload_double, payload_timestamp, payload_hash + FROM {table} + WHERE id = $id AND hash = Digest::NumericHash($id); + "#, + table = table_name + )) + .with_params(ydb_params!("$id" => row_id)) +} + +pub fn generate_write_query(table_name: &str, row: Row) -> Query { + Query::from(format!( + r#" + DECLARE $id AS Uint64; + DECLARE $payload_str AS Utf8; + DECLARE $payload_double AS Double; + DECLARE $payload_timestamp AS Timestamp; + + UPSERT INTO {table} ( + id, + hash, + payload_str, + payload_double, + payload_timestamp + ) VALUES ( + $id, + Digest::NumericHash($id), + $payload_str, + $payload_double, + $payload_timestamp + ); + "#, + table = table_name + )) + .with_params(ydb_params!( + "$id" => row.id, + "$payload_str" => row.payload_str, + "$payload_double" => row.payload_double, + "$payload_timestamp" => row.payload_timestamp, + )) +} diff --git a/ydb-slo-tests/src/row.rs b/ydb-slo-tests/lib/src/db/row.rs similarity index 92% rename from ydb-slo-tests/src/row.rs rename to ydb-slo-tests/lib/src/db/row.rs index 755c9b52..75de28c8 100644 --- a/ydb-slo-tests/src/row.rs +++ b/ydb-slo-tests/lib/src/db/row.rs @@ -3,14 +3,14 @@ use std::time::SystemTime; pub type RowID = u64; #[derive(Debug, Clone)] -pub struct TestRow { +pub struct Row { pub id: RowID, pub payload_str: String, pub payload_double: f64, pub payload_timestamp: SystemTime, } -impl TestRow { +impl Row { pub fn new( id: RowID, payload_str: String, diff --git a/ydb-slo-tests/src/generator.rs b/ydb-slo-tests/lib/src/generator.rs similarity index 88% rename from ydb-slo-tests/src/generator.rs rename to ydb-slo-tests/lib/src/generator.rs index 36653dc0..b155cdb6 100644 --- a/ydb-slo-tests/src/generator.rs +++ b/ydb-slo-tests/lib/src/generator.rs @@ -1,4 +1,4 @@ -use crate::row::{RowID, TestRow}; +use crate::db::row::{Row, RowID}; use base64::{engine::general_purpose::STANDARD, Engine as _}; use rand::prelude::StdRng; use rand::Rng; @@ -23,7 +23,7 @@ impl Generator { } } - pub fn generate(&mut self) -> TestRow { + pub fn generate(&mut self) -> Row { let id = { let mut id_guard = self.current_id.lock().unwrap(); *id_guard += 1; @@ -34,7 +34,7 @@ impl Generator { let payload_timestamp = SystemTime::now(); let payload_str = self.gen_payload_string(); - TestRow::new(id, payload_str, payload_double, payload_timestamp) + Row::new(id, payload_str, payload_double, payload_timestamp) } fn gen_payload_string(&mut self) -> String { diff --git a/ydb-slo-tests/src/lib.rs b/ydb-slo-tests/lib/src/lib.rs similarity index 86% rename from ydb-slo-tests/src/lib.rs rename to ydb-slo-tests/lib/src/lib.rs index 7fd6cd89..64b01d7f 100644 --- a/ydb-slo-tests/src/lib.rs +++ b/ydb-slo-tests/lib/src/lib.rs @@ -1,6 +1,6 @@ pub mod args; pub mod cli; +pub mod db; pub mod generator; pub mod metrics; -pub mod row; pub mod workers; diff --git a/ydb-slo-tests/src/metrics/mod.rs b/ydb-slo-tests/lib/src/metrics/mod.rs similarity index 100% rename from ydb-slo-tests/src/metrics/mod.rs rename to ydb-slo-tests/lib/src/metrics/mod.rs diff --git a/ydb-slo-tests/src/metrics/span.rs b/ydb-slo-tests/lib/src/metrics/span.rs similarity index 100% rename from ydb-slo-tests/src/metrics/span.rs rename to ydb-slo-tests/lib/src/metrics/span.rs diff --git a/ydb-slo-tests/src/workers.rs b/ydb-slo-tests/lib/src/workers.rs similarity index 69% rename from ydb-slo-tests/src/workers.rs rename to ydb-slo-tests/lib/src/workers.rs index 20553385..599d15f7 100644 --- a/ydb-slo-tests/src/workers.rs +++ b/ydb-slo-tests/lib/src/workers.rs @@ -1,8 +1,7 @@ -use crate::args::RunArgs; +use crate::db::row::{Row, RowID}; use crate::generator::Generator; use crate::metrics; use crate::metrics::{MetricsCollector, OperationType}; -use crate::row::{RowID, TestRow}; use async_trait::async_trait; use governor::clock::DefaultClock; use governor::middleware::NoOpMiddleware; @@ -13,37 +12,33 @@ use std::fmt::{Display, Formatter}; use std::sync::Arc; use std::time::Duration; use tokio::time::timeout; -use tokio_util::sync::CancellationToken; use ydb::YdbResultWithCustomerErr; pub type Attempts = usize; +type Limiter = RateLimiter; #[async_trait] pub trait ReadWriter: Clone + Send + Sync { async fn read(&self, row_id: RowID) -> (YdbResultWithCustomerErr<()>, Attempts); - async fn write(&self, row: TestRow) -> (YdbResultWithCustomerErr<()>, Attempts); + async fn write(&self, row: Row) -> (YdbResultWithCustomerErr<()>, Attempts); } pub struct Workers { database: Arc, - config: RunArgs, + config: WorkersConfig, metrics: MetricsCollector, } impl Workers { pub fn new( database: Arc, - config: RunArgs, + config: WorkersConfig, + prom_pgw: String, metrics_ref: String, metrics_label: String, metrics_job_name: String, ) -> Arc> { - let metrics = MetricsCollector::new( - config.prom_pgw.clone(), - metrics_ref, - metrics_label, - metrics_job_name, - ); + let metrics = MetricsCollector::new(prom_pgw, metrics_ref, metrics_label, metrics_job_name); Arc::new(Self { database, @@ -52,23 +47,15 @@ impl Workers { }) } - pub async fn start_read_load( - &self, - limiter: &RateLimiter, - cancel: CancellationToken, - ) { + pub async fn start_read_load(&self, limiter: &Limiter) { loop { - if cancel.is_cancelled() { - return; - } - limiter.until_ready().await; let row_id = rand::thread_rng().gen_range(0..self.config.initial_data_count); let span = self.metrics.start(OperationType::Read); let read_result = timeout( - Duration::from_millis(self.config.read_timeout), + Duration::from_millis(self.config.read_timeout_seconds), self.database.read(row_id), ) .await; @@ -83,31 +70,20 @@ impl Workers { println!("Read failed: {}", e); return; } - Err(_) => { - return; - } + Err(_) => return, } } } - pub async fn start_write_load( - &self, - limiter: &RateLimiter, - generator: &Generator, - cancel: CancellationToken, - ) { + pub async fn start_write_load(&self, limiter: &Limiter, generator: &Generator) { loop { - if cancel.is_cancelled() { - return; - } - limiter.until_ready().await; let row = generator.to_owned().generate(); let span = self.metrics.start(OperationType::Write); let write_result = timeout( - Duration::from_millis(self.config.write_timeout), + Duration::from_millis(self.config.write_timeout_seconds), self.database.clone().write(row), ) .await; @@ -122,23 +98,13 @@ impl Workers { println!("Write failed: {}", e); return; } - Err(_) => { - return; - } + Err(_) => return, } } } - pub async fn collect_metrics( - &self, - limiter: &RateLimiter, - cancel: CancellationToken, - ) { + pub async fn collect_metrics(&self, limiter: &Limiter) { loop { - if cancel.is_cancelled() { - return; - } - limiter.until_ready().await; if let Err(err) = self.metrics.push_to_gateway().await { @@ -156,6 +122,12 @@ impl Workers { } } +pub struct WorkersConfig { + pub initial_data_count: u64, + pub read_timeout_seconds: u64, + pub write_timeout_seconds: u64, +} + #[derive(Debug)] pub struct WorkersCloseError { value: metrics::MetricsPushError, diff --git a/ydb-slo-tests/native/Cargo.toml b/ydb-slo-tests/native/Cargo.toml new file mode 100644 index 00000000..2f3880c0 --- /dev/null +++ b/ydb-slo-tests/native/Cargo.toml @@ -0,0 +1,19 @@ +[package] +publish = false +name = "ydb-slo-tests-native" +version = "0.1.0" +authors = ["fatalem0 "] +edition = "2021" +license = "Apache-2.0" +description = "Crate contains CLI with SLO-tests for YDB" +repository = "https://github.com/ydb-platform/ydb-rs-sdk/tree/master/ydb-slo-tests" +rust-version = "1.68.0" + +[dependencies] +clap = { version = "=4.2.7", features = ["derive"] } +tokio = { version = "=1.38.1" } +tokio-util = { version = "=0.7.11", features = ["rt"] } +ydb = { version = "0.9.7", path = "../../ydb" } +async-trait = "0.1" +governor = { version = "=0.6.0" } +ydb-slo-tests-lib = { path = "../lib" } \ No newline at end of file diff --git a/ydb-slo-tests/native/src/db.rs b/ydb-slo-tests/native/src/db.rs new file mode 100644 index 00000000..00e25c24 --- /dev/null +++ b/ydb-slo-tests/native/src/db.rs @@ -0,0 +1,108 @@ +use async_trait::async_trait; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; +use tokio::time::timeout; +use ydb::{ClientBuilder, TableClient, YdbOrCustomerError, YdbResult, YdbResultWithCustomerErr}; +use ydb_slo_tests_lib::args::CreateArgs; +use ydb_slo_tests_lib::cli::SloTestsCli; +use ydb_slo_tests_lib::db::query::{ + generate_create_table_query, generate_drop_table_query, generate_read_query, + generate_write_query, +}; +use ydb_slo_tests_lib::db::row::{Row, RowID}; +use ydb_slo_tests_lib::workers::ReadWriter; + +pub type Attempts = usize; + +#[derive(Clone)] +pub struct Database { + db_table_client: TableClient, + cli_args: SloTestsCli, +} + +impl Database { + pub async fn new(cli: SloTestsCli) -> YdbResultWithCustomerErr { + let client = ClientBuilder::new_from_connection_string(&cli.endpoint)? + .with_database(&cli.db) + .client()?; + + match timeout( + Duration::from_secs(cli.db_init_timeout_seconds), + client.wait(), + ) + .await + { + Ok(res) => res?, + Err(elapsed) => { + return Err(YdbOrCustomerError::from_err(elapsed)); + } + } + + let table_client = client.table_client(); + + Ok(Self { + db_table_client: table_client, + cli_args: cli, + }) + } + + pub async fn create_table(&self, create_args: &CreateArgs) -> YdbResult<()> { + let query = generate_create_table_query( + &self.cli_args.table_name, + create_args.min_partitions_count, + create_args.max_partitions_count, + create_args.partition_size_mb, + ); + + self.db_table_client.retry_execute_scheme_query(query).await + } + + pub async fn drop_table(&self) -> YdbResult<()> { + let query = generate_drop_table_query(self.cli_args.table_name.as_str()); + + self.db_table_client.retry_execute_scheme_query(query).await + } +} + +#[async_trait] +impl ReadWriter for Database { + async fn read(&self, row_id: RowID) -> (YdbResultWithCustomerErr<()>, Attempts) { + let query = generate_read_query(self.cli_args.table_name.as_str(), row_id); + let attempts = AtomicUsize::new(0); + + let result = self + .db_table_client + .retry_transaction(|t| async { + let mut t = t; + attempts.fetch_add(1, Ordering::Relaxed); + t.query(query.clone()).await?; + Ok(()) + }) + .await; + + if attempts.load(Ordering::Relaxed) > 0 { + attempts.fetch_sub(1, Ordering::Relaxed); + (result, attempts.load(Ordering::Relaxed)) + } else { + (result, attempts.load(Ordering::Relaxed)) + } + } + + async fn write(&self, row: Row) -> (YdbResultWithCustomerErr<()>, Attempts) { + let query = generate_write_query(self.cli_args.table_name.as_str(), row); + let attempts = AtomicUsize::new(0); + + let result = self + .db_table_client + .retry_transaction(|t| async { + let mut t = t; + attempts.fetch_add(1, Ordering::Relaxed); + t.query(query.clone()).await?; + t.commit().await?; + Ok(()) + }) + .await; + + (result, attempts.load(Ordering::Relaxed)) + } +} diff --git a/ydb-slo-tests/native/src/main.rs b/ydb-slo-tests/native/src/main.rs new file mode 100644 index 00000000..7fb1d63f --- /dev/null +++ b/ydb-slo-tests/native/src/main.rs @@ -0,0 +1,292 @@ +use crate::db::Database; +use clap::Parser; +use governor::{Quota, RateLimiter}; +use std::num::NonZeroU32; +use std::sync::Arc; +use std::time::Duration; +use tokio::signal::unix::{signal, SignalKind}; +use tokio::sync::Mutex; +use tokio::task::JoinSet; +use tokio::time; +use tokio::time::timeout; +use tokio_util::sync::CancellationToken; +use tokio_util::task::TaskTracker; +use ydb::{YdbError, YdbResult}; +use ydb_slo_tests_lib::cli::{Command, SloTestsCli}; +use ydb_slo_tests_lib::db::row::RowID; +use ydb_slo_tests_lib::generator::Generator; +use ydb_slo_tests_lib::workers::{ReadWriter, Workers, WorkersConfig}; + +mod db; + +#[tokio::main] +async fn main() -> YdbResult<()> { + let cli = SloTestsCli::parse(); + + let token = CancellationToken::new(); + let shutdown_token = token.clone(); + let program_token = token.clone(); + + let timeout = choose_timeout(&cli.command); + + println!("program is started"); + tokio::spawn(wait_for_shutdown(shutdown_token, timeout)); + + let result = program(cli.clone(), program_token).await; + + println!("program is finished"); + + result +} + +async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { + let database = Database::new(cli.clone()) + .await + .map_err(|err| YdbError::Custom(format!("failed to initialize YDB client: {}", err)))?; + + println!("initialized database"); + + match cli.command { + Command::Create(create_args) => { + tokio::select! { + _ = token.cancelled() => { + return Err(YdbError::Custom(format!("{}", "failed to create table: cancelled or timeout"))) + }, + res = timeout( + Duration::from_secs(cli.write_timeout_seconds), + database.create_table(&create_args) + ) => { + match res { + Err(elapsed) => { + return Err(YdbError::Custom(format!("failed to create table: {}", elapsed))) + } + Ok(Err(err)) => { + return Err(YdbError::Custom(format!("failed to create table: {}", err))) + } + _ => { + println!("created table"); + } + } + } + } + + let mut join_set = JoinSet::new(); + let database = Arc::new(database); + let generator = Arc::new(Mutex::new(Generator::new(0))); + + for _ in 0..create_args.initial_data_count { + let database = Arc::clone(&database); + let generator = Arc::clone(&generator); + let token = token.clone(); + + join_set.spawn(async move { + let row = generator.lock().await.generate(); + + let _ = tokio::select! { + _ = token.cancelled() => { + return Err(YdbError::Custom(format!("{}", "failed to create row: cancelled or timeout"))) + }, + res = timeout( + Duration::from_secs(cli.write_timeout_seconds), + database.write(row) + ) => { + return match res { + Err(elapsed) => { + Err(YdbError::Custom(format!("failed to create row: {}", elapsed))) + } + Ok((Err(err), _)) => { + Err(YdbError::Custom(format!("failed to create row: {}", err))) + } + _ => { + Ok(()) + } + } + } + }; + }); + } + + while let Some(join_result) = join_set.join_next().await { + match join_result { + Ok(Ok(())) => {} + Ok(Err(err)) => { + return Err(err); + } + Err(join_err) => { + return Err(YdbError::Custom(format!( + "failed to create row: {}", + join_err + ))); + } + } + } + + println!("inserted {} rows", create_args.initial_data_count); + } + Command::Cleanup => { + tokio::select! { + _ = token.cancelled() => { + return Err(YdbError::Custom(format!("{}", "failed to clean up table: cancelled or timeout"))) + } + res = timeout( + Duration::from_secs(cli.write_timeout_seconds), + database.drop_table() + ) => { + match res { + Err(elapsed) => { + return Err(YdbError::Custom(format!("failed to clean up table: {}", elapsed))) + + } + Ok(Err(err)) => { + return Err(YdbError::Custom(format!("failed to clean up table: {}", err))) + } + _ => { + println!("cleaned up table"); + } + } + } + } + } + Command::Run(run_args) => { + let metrics_ref = std::env::var("METRICS_REF").unwrap_or("metrics_ref".to_string()); + let metrics_label = + std::env::var("METRICS_LABEL").unwrap_or("metrics_label".to_string()); + let metrics_job_name = + std::env::var("METRICS_JOB_NAME").unwrap_or("metrics_test_job".to_string()); + + let workers_token = token.clone(); + let shutdown_token = token.clone(); + + let generator = Arc::new(Mutex::new(Generator::new( + run_args.initial_data_count as RowID, + ))); + + let workers_config = WorkersConfig { + initial_data_count: run_args.initial_data_count, + read_timeout_seconds: run_args.read_timeout_seconds, + write_timeout_seconds: cli.write_timeout_seconds, + }; + + let workers = Workers::new( + Arc::new(database), + workers_config, + run_args.prom_pgw, + metrics_ref, + metrics_label, + metrics_job_name, + ); + + let tracker = TaskTracker::new(); + + let read_rate_limiter = Arc::new(RateLimiter::direct( + Quota::per_second(NonZeroU32::new(run_args.read_rps).unwrap()) + .allow_burst(NonZeroU32::new(1).unwrap()), + )); + + for _ in 0..run_args.read_rps { + let token = workers_token.clone(); + let workers = Arc::clone(&workers); + let read_rate_limiter = Arc::clone(&read_rate_limiter); + + tracker.spawn(async move { + tokio::select! { + _ = token.cancelled() => {} + _ = workers.start_read_load(&read_rate_limiter) => {} + } + }); + } + + println!("started {} read workers", run_args.read_rps); + + let write_rate_limiter = Arc::new(RateLimiter::direct( + Quota::per_second(NonZeroU32::new(run_args.write_rps).unwrap()) + .allow_burst(NonZeroU32::new(1).unwrap()), + )); + + for _ in 0..run_args.write_rps { + let token = workers_token.clone(); + let workers = Arc::clone(&workers); + let write_rate_limiter = Arc::clone(&write_rate_limiter); + let generator = Arc::clone(&generator); + + tracker.spawn(async move { + let generator = generator.lock().await; + + tokio::select! { + _ = token.cancelled() => {} + _ = workers.start_write_load(&write_rate_limiter, &generator) => {} + } + }); + } + + println!("started {} write workers", run_args.write_rps); + + let metrics_rate_limiter = Arc::new(RateLimiter::direct( + Quota::with_period(Duration::from_secs(run_args.report_period_seconds)) + .unwrap() + .allow_burst(NonZeroU32::new(1).unwrap()), + )); + + let metrics_worker = Arc::clone(&workers); + let workers_token = workers_token.clone(); + tracker.spawn(async move { + tokio::select! { + _ = workers_token.cancelled() => {} + _ = metrics_worker.collect_metrics(&metrics_rate_limiter) => {} + } + }); + + println!("started metrics worker"); + + { + let tracker = tracker.clone(); + tokio::spawn(async move { + time::sleep(Duration::from_secs(run_args.shutdown_time_seconds)).await; + tracker.close(); + shutdown_token.cancel(); + }); + } + + tracker.wait().await; + + workers + .close() + .await + .map_err(|err| YdbError::Custom(format!("failed to close workers: {}", err)))?; + + println!("all workers are completed"); + } + } + + Ok(()) +} + +async fn wait_for_shutdown(token: CancellationToken, timeout_secs: Duration) { + let mut sigint = signal(SignalKind::interrupt()).unwrap(); + let mut sigterm = signal(SignalKind::terminate()).unwrap(); + let mut sigquit = signal(SignalKind::quit()).unwrap(); + + tokio::select! { + _ = sigint.recv() => { + println!("received SIGINT signal"); + } + _ = sigterm.recv() => { + println!("received SIGTERM signal"); + } + _ = sigquit.recv() => { + println!("received SIGQUIT signal"); + } + _ = time::sleep(timeout_secs) => { + println!("timeout of {} seconds reached", timeout_secs.as_secs()); + } + } + + token.cancel(); +} + +fn choose_timeout(cmd: &Command) -> Duration { + match cmd { + Command::Create(_) | Command::Cleanup => Duration::from_secs(30), + Command::Run(run_args) => Duration::from_secs(run_args.time_seconds), + } +} From 432a8813ba726a96fed5333246663bfb55e373e6 Mon Sep 17 00:00:00 2001 From: fatalem0 Date: Mon, 9 Jun 2025 00:46:28 +0300 Subject: [PATCH 3/8] fix: after comments --- .github/workflows/slo.yml | 6 +++--- ydb-slo-tests/README.md | 12 ++++++------ ydb-slo-tests/native/src/main.rs | 11 ++++++++--- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index 6bbafae7..ea77498e 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -85,11 +85,11 @@ jobs: - name: Prepare SLO Database run: | - cargo run -- -t tableName grpc://localhost:2135 /Root/testdb create + cargo run --bin ydb-slo-tests-native -- -t tableName grpc://localhost:2135 /Root/testdb create - name: Run SLO Tests run: | - cargo run -- -t tableName --write-timeout 1 grpc://localhost:2135 /Root/testdb run \ + cargo run --bin ydb-slo-tests-native -- -t tableName --write-timeout 1 grpc://localhost:2135 /Root/testdb run \ --prom-pgw localhost:9091 \ --report-period 1 \ --time ${{ inputs.slo_workload_duration_seconds || 600}} \ @@ -112,7 +112,7 @@ jobs: - if: always() name: Cleanup SLO Database run: | - cargo run -- -t tableName grpc://localhost:2135 /Root/testdb cleanup || true + cargo run --bin ydb-slo-tests-native -- -t tableName grpc://localhost:2135 /Root/testdb cleanup || true validate-slo-metrics: name: Validate SLO metrics needs: ydb-slo-action-init diff --git a/ydb-slo-tests/README.md b/ydb-slo-tests/README.md index 0ebe5b5c..a55bfa3a 100644 --- a/ydb-slo-tests/README.md +++ b/ydb-slo-tests/README.md @@ -15,21 +15,21 @@ It has 3 commands: create: -`cargo run -- -t testingTable --write-timeout 10 --db-init-timeout 3 grpc://localhost:2136 /local create --min-partitions-count 6 --max-partitions-count 1000 --partition-size 1 -c 1000` +`cargo run --bin ydb-slo-tests-native -- -t testingTable --write-timeout 10 --db-init-timeout 3 grpc://localhost:2136 /local create --min-partitions-count 6 --max-partitions-count 1000 --partition-size 1 -c 1000` cleanup: -`cargo run -- -t testingTable --write-timeout 10 --db-init-timeout 3 grpc://localhost:2136 /local cleanup` +`cargo run --bin ydb-slo-tests-native -- -t testingTable --write-timeout 10 --db-init-timeout 3 grpc://localhost:2136 /local cleanup` run: -`cargo run -- -t testingTable --write-timeout 10 --db-init-timeout 3 grpc://localhost:2136 /local run -c 1000 --read-rps 1000 --read-timeout 10 --write-rps 100 --prom-pgw localhost:9091 --time 600 --report-period 1 --shutdown-time 30` +`cargo run --bin ydb-slo-tests-native -- -t testingTable --write-timeout 10 --db-init-timeout 3 grpc://localhost:2136 /local run -c 1000 --read-rps 1000 --read-timeout 10 --write-rps 100 --prom-pgw localhost:9091 --time 600 --report-period 1 --shutdown-time 30` ## Arguments and options for commands: ### create -`cargo run [COMMON_OPTIONS] create [OPTIONS]` +`cargo run --bin -- [COMMON_OPTIONS] create [OPTIONS]` ``` Arguments: @@ -52,7 +52,7 @@ Options: ### cleanup -`cargo run [COMMON_OPTIONS] cleanup` +`cargo run --bin -- [COMMON_OPTIONS] cleanup` ``` Arguments: @@ -67,7 +67,7 @@ Common options: ### run -`cargo run [COMMON_OPTIONS] run [OPTIONS]` +`cargo run --bin -- [COMMON_OPTIONS] run [OPTIONS]` ``` Arguments: diff --git a/ydb-slo-tests/native/src/main.rs b/ydb-slo-tests/native/src/main.rs index 7fb1d63f..9b6fffa6 100644 --- a/ydb-slo-tests/native/src/main.rs +++ b/ydb-slo-tests/native/src/main.rs @@ -40,9 +40,14 @@ async fn main() -> YdbResult<()> { } async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { - let database = Database::new(cli.clone()) - .await - .map_err(|err| YdbError::Custom(format!("failed to initialize YDB client: {}", err)))?; + let database = tokio::select! { + _ = token.cancelled() => { + return Err(YdbError::Custom(format!("{}", "failed to initialize YDB client: cancelled or timeout"))) + } + res = Database::new(cli.clone()) => { + res.map_err(|err| YdbError::Custom(format!("failed to initialize YDB client: {}", err)))? + } + }; println!("initialized database"); From a09f1beaedaf1476d076916450b82c7e38c9a98e Mon Sep 17 00:00:00 2001 From: fatalem0 Date: Mon, 9 Jun 2025 01:16:08 +0300 Subject: [PATCH 4/8] fix: after comments --- ydb-slo-tests/lib/src/workers.rs | 6 ++--- ydb-slo-tests/native/src/main.rs | 38 +++++++++++++++++++------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/ydb-slo-tests/lib/src/workers.rs b/ydb-slo-tests/lib/src/workers.rs index 599d15f7..bb60291a 100644 --- a/ydb-slo-tests/lib/src/workers.rs +++ b/ydb-slo-tests/lib/src/workers.rs @@ -67,7 +67,7 @@ impl Workers { } Ok((Err(e), attempts)) => { span.finish(attempts, Some(e.clone())); - println!("Read failed: {}", e); + println!("read failed: {}", e); return; } Err(_) => return, @@ -95,7 +95,7 @@ impl Workers { } Ok((Err(e), attempts)) => { span.finish(attempts, Some(e.clone())); - println!("Write failed: {}", e); + println!("write failed: {}", e); return; } Err(_) => return, @@ -108,7 +108,7 @@ impl Workers { limiter.until_ready().await; if let Err(err) = self.metrics.push_to_gateway().await { - println!("Failed to collect metrics: {}", err); + println!("failed to collect metrics: {}", err); continue; } } diff --git a/ydb-slo-tests/native/src/main.rs b/ydb-slo-tests/native/src/main.rs index 9b6fffa6..b4040d43 100644 --- a/ydb-slo-tests/native/src/main.rs +++ b/ydb-slo-tests/native/src/main.rs @@ -243,23 +243,31 @@ async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { println!("started metrics worker"); - { - let tracker = tracker.clone(); - tokio::spawn(async move { - time::sleep(Duration::from_secs(run_args.shutdown_time_seconds)).await; - tracker.close(); - shutdown_token.cancel(); - }); - } - + tracker.close(); tracker.wait().await; - workers - .close() - .await - .map_err(|err| YdbError::Custom(format!("failed to close workers: {}", err)))?; - - println!("all workers are completed"); + match timeout( + Duration::from_secs(run_args.shutdown_time_seconds), + workers.close(), + ) + .await + { + Err(elapsed) => { + return Err(YdbError::Custom(format!( + "failed to close workers: {}", + elapsed + ))) + } + Ok(Err(err)) => { + return Err(YdbError::Custom(format!( + "failed to close workers: {}", + err + ))) + } + _ => { + println!("all workers are completed") + } + } } } From 3b6f404ae09199cc371f800bd885d42e28d65b21 Mon Sep 17 00:00:00 2001 From: fatalem0 Date: Mon, 9 Jun 2025 01:46:47 +0300 Subject: [PATCH 5/8] fix: after comments --- ydb-slo-tests/native/src/main.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/ydb-slo-tests/native/src/main.rs b/ydb-slo-tests/native/src/main.rs index b4040d43..c1b4bc0e 100644 --- a/ydb-slo-tests/native/src/main.rs +++ b/ydb-slo-tests/native/src/main.rs @@ -42,7 +42,7 @@ async fn main() -> YdbResult<()> { async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { let database = tokio::select! { _ = token.cancelled() => { - return Err(YdbError::Custom(format!("{}", "failed to initialize YDB client: cancelled or timeout"))) + return Err(YdbError::Custom("failed to initialize YDB client: cancelled or timeout".to_string())) } res = Database::new(cli.clone()) => { res.map_err(|err| YdbError::Custom(format!("failed to initialize YDB client: {}", err)))? @@ -55,7 +55,7 @@ async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { Command::Create(create_args) => { tokio::select! { _ = token.cancelled() => { - return Err(YdbError::Custom(format!("{}", "failed to create table: cancelled or timeout"))) + return Err(YdbError::Custom("failed to create table: cancelled or timeout".to_string())) }, res = timeout( Duration::from_secs(cli.write_timeout_seconds), @@ -87,9 +87,9 @@ async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { join_set.spawn(async move { let row = generator.lock().await.generate(); - let _ = tokio::select! { + tokio::select! { _ = token.cancelled() => { - return Err(YdbError::Custom(format!("{}", "failed to create row: cancelled or timeout"))) + return Err(YdbError::Custom("failed to create row: cancelled or timeout".to_string())) }, res = timeout( Duration::from_secs(cli.write_timeout_seconds), @@ -107,7 +107,7 @@ async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { } } } - }; + } }); } @@ -131,7 +131,7 @@ async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { Command::Cleanup => { tokio::select! { _ = token.cancelled() => { - return Err(YdbError::Custom(format!("{}", "failed to clean up table: cancelled or timeout"))) + return Err(YdbError::Custom("failed to clean up table: cancelled or timeout".to_string())) } res = timeout( Duration::from_secs(cli.write_timeout_seconds), @@ -160,7 +160,6 @@ async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { std::env::var("METRICS_JOB_NAME").unwrap_or("metrics_test_job".to_string()); let workers_token = token.clone(); - let shutdown_token = token.clone(); let generator = Arc::new(Mutex::new(Generator::new( run_args.initial_data_count as RowID, @@ -233,10 +232,10 @@ async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { )); let metrics_worker = Arc::clone(&workers); - let workers_token = workers_token.clone(); + let metrics_token = workers_token.clone(); tracker.spawn(async move { tokio::select! { - _ = workers_token.cancelled() => {} + _ = metrics_token.cancelled() => {} _ = metrics_worker.collect_metrics(&metrics_rate_limiter) => {} } }); From 33feafa11bce81b5c57cc1bdfe7ad42c766a26d0 Mon Sep 17 00:00:00 2001 From: fatalem0 Date: Mon, 9 Jun 2025 01:53:37 +0300 Subject: [PATCH 6/8] fix: after comments --- .github/workflows/slo.yml | 3 --- ydb-slo-tests/native/src/main.rs | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index ea77498e..b72bfef0 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -130,9 +130,6 @@ jobs: strategy: matrix: - sdk: - - name: native - label: native rust_version: - "RUST_VERSION_OLD" - "RUST_VERSION_NEW" diff --git a/ydb-slo-tests/native/src/main.rs b/ydb-slo-tests/native/src/main.rs index c1b4bc0e..8d6c876f 100644 --- a/ydb-slo-tests/native/src/main.rs +++ b/ydb-slo-tests/native/src/main.rs @@ -89,13 +89,13 @@ async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { tokio::select! { _ = token.cancelled() => { - return Err(YdbError::Custom("failed to create row: cancelled or timeout".to_string())) + Err(YdbError::Custom("failed to create row: cancelled or timeout".to_string())) }, res = timeout( Duration::from_secs(cli.write_timeout_seconds), database.write(row) ) => { - return match res { + match res { Err(elapsed) => { Err(YdbError::Custom(format!("failed to create row: {}", elapsed))) } From 93d684215a8bf1805b1480e91f03fe631d6db5ca Mon Sep 17 00:00:00 2001 From: fatalem0 Date: Sat, 21 Jun 2025 22:44:23 +0300 Subject: [PATCH 7/8] fix: after comments --- ydb-slo-tests/lib/src/workers.rs | 40 ++++++++++++++-------------- ydb-slo-tests/native/src/db.rs | 45 +++++++++++++++++++++++--------- ydb-slo-tests/native/src/main.rs | 14 +++------- 3 files changed, 56 insertions(+), 43 deletions(-) diff --git a/ydb-slo-tests/lib/src/workers.rs b/ydb-slo-tests/lib/src/workers.rs index bb60291a..977eb5a4 100644 --- a/ydb-slo-tests/lib/src/workers.rs +++ b/ydb-slo-tests/lib/src/workers.rs @@ -11,7 +11,6 @@ use rand::Rng; use std::fmt::{Display, Formatter}; use std::sync::Arc; use std::time::Duration; -use tokio::time::timeout; use ydb::YdbResultWithCustomerErr; pub type Attempts = usize; @@ -19,8 +18,12 @@ type Limiter = RateLimiter (YdbResultWithCustomerErr<()>, Attempts); - async fn write(&self, row: Row) -> (YdbResultWithCustomerErr<()>, Attempts); + async fn read( + &self, + row_id: RowID, + timeout: Duration, + ) -> (YdbResultWithCustomerErr<()>, Attempts); + async fn write(&self, row: Row, timeout: Duration) -> (YdbResultWithCustomerErr<()>, Attempts); } pub struct Workers { @@ -54,23 +57,24 @@ impl Workers { let row_id = rand::thread_rng().gen_range(0..self.config.initial_data_count); let span = self.metrics.start(OperationType::Read); - let read_result = timeout( - Duration::from_millis(self.config.read_timeout_seconds), - self.database.read(row_id), - ) - .await; + let read_result = self + .database + .read( + row_id, + Duration::from_secs(self.config.read_timeout_seconds), + ) + .await; match read_result { - Ok((Ok(()), attempts)) => { + (Ok(()), attempts) => { span.finish(attempts, None); continue; } - Ok((Err(e), attempts)) => { + (Err(e), attempts) => { span.finish(attempts, Some(e.clone())); println!("read failed: {}", e); return; } - Err(_) => return, } } } @@ -82,23 +86,21 @@ impl Workers { let row = generator.to_owned().generate(); let span = self.metrics.start(OperationType::Write); - let write_result = timeout( - Duration::from_millis(self.config.write_timeout_seconds), - self.database.clone().write(row), - ) - .await; + let write_result = self + .database + .write(row, Duration::from_secs(self.config.write_timeout_seconds)) + .await; match write_result { - Ok((Ok(()), attempts)) => { + (Ok(()), attempts) => { span.finish(attempts, None); continue; } - Ok((Err(e), attempts)) => { + (Err(e), attempts) => { span.finish(attempts, Some(e.clone())); println!("write failed: {}", e); return; } - Err(_) => return, } } } diff --git a/ydb-slo-tests/native/src/db.rs b/ydb-slo-tests/native/src/db.rs index 00e25c24..2f0b4ca9 100644 --- a/ydb-slo-tests/native/src/db.rs +++ b/ydb-slo-tests/native/src/db.rs @@ -66,19 +66,28 @@ impl Database { #[async_trait] impl ReadWriter for Database { - async fn read(&self, row_id: RowID) -> (YdbResultWithCustomerErr<()>, Attempts) { + async fn read( + &self, + row_id: RowID, + timeout: Duration, + ) -> (YdbResultWithCustomerErr<()>, Attempts) { let query = generate_read_query(self.cli_args.table_name.as_str(), row_id); let attempts = AtomicUsize::new(0); - let result = self - .db_table_client - .retry_transaction(|t| async { + let result = match tokio::time::timeout( + timeout, + self.db_table_client.retry_transaction(|t| async { let mut t = t; attempts.fetch_add(1, Ordering::Relaxed); t.query(query.clone()).await?; Ok(()) - }) - .await; + }), + ) + .await + { + Ok(res) => res, + Err(elapsed) => Err(YdbOrCustomerError::from_err(elapsed)), + }; if attempts.load(Ordering::Relaxed) > 0 { attempts.fetch_sub(1, Ordering::Relaxed); @@ -88,21 +97,31 @@ impl ReadWriter for Database { } } - async fn write(&self, row: Row) -> (YdbResultWithCustomerErr<()>, Attempts) { + async fn write(&self, row: Row, timeout: Duration) -> (YdbResultWithCustomerErr<()>, Attempts) { let query = generate_write_query(self.cli_args.table_name.as_str(), row); let attempts = AtomicUsize::new(0); - let result = self - .db_table_client - .retry_transaction(|t| async { + let result = match tokio::time::timeout( + timeout, + self.db_table_client.retry_transaction(|t| async { let mut t = t; attempts.fetch_add(1, Ordering::Relaxed); t.query(query.clone()).await?; t.commit().await?; Ok(()) - }) - .await; + }), + ) + .await + { + Ok(res) => res, + Err(elapsed) => Err(YdbOrCustomerError::from_err(elapsed)), + }; - (result, attempts.load(Ordering::Relaxed)) + if attempts.load(Ordering::Relaxed) > 0 { + attempts.fetch_sub(1, Ordering::Relaxed); + (result, attempts.load(Ordering::Relaxed)) + } else { + (result, attempts.load(Ordering::Relaxed)) + } } } diff --git a/ydb-slo-tests/native/src/main.rs b/ydb-slo-tests/native/src/main.rs index 8d6c876f..1a0555ec 100644 --- a/ydb-slo-tests/native/src/main.rs +++ b/ydb-slo-tests/native/src/main.rs @@ -91,20 +91,12 @@ async fn program(cli: SloTestsCli, token: CancellationToken) -> YdbResult<()> { _ = token.cancelled() => { Err(YdbError::Custom("failed to create row: cancelled or timeout".to_string())) }, - res = timeout( - Duration::from_secs(cli.write_timeout_seconds), - database.write(row) - ) => { + res = database.write(row, Duration::from_secs(cli.write_timeout_seconds)) => { match res { - Err(elapsed) => { - Err(YdbError::Custom(format!("failed to create row: {}", elapsed))) - } - Ok((Err(err), _)) => { + (Err(err), _) => { Err(YdbError::Custom(format!("failed to create row: {}", err))) } - _ => { - Ok(()) - } + _ => { Ok(()) } } } } From f9fc7e46ef2536114d70f24b1e3d6bec00174063 Mon Sep 17 00:00:00 2001 From: fatalem0 Date: Sun, 22 Jun 2025 16:55:40 +0300 Subject: [PATCH 8/8] fix: after comments --- .github/scripts/check-metrics.sh | 45 +++++++++++++++++++++----------- .github/workflows/slo.yml | 10 +++---- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/.github/scripts/check-metrics.sh b/.github/scripts/check-metrics.sh index beb79b9e..cbdb04ea 100755 --- a/.github/scripts/check-metrics.sh +++ b/.github/scripts/check-metrics.sh @@ -16,13 +16,14 @@ check_metric() { key="$1" threshold="$2" label="$3" + mode="${4:-fail_if_below_threshold}" if [ -z "$threshold" ]; then echo "⚠️ No threshold for $label" return fi - echo "🔍 Checking $label (threshold = $threshold)..." + echo "🔍 Checking $label (mode = $mode, threshold = $threshold)..." values=$(jq -r --arg key "$key" '.[$key][0]?.values[]? | select(.[1] != "NaN") | .[1]' "$metrics_file") if [ -z "$values" ]; then @@ -44,25 +45,39 @@ check_metric() { fi average=$(echo "$sum / $count" | bc -l) - echo " ➤ Average $label = $average" - if [ "$(echo "$average < $threshold" | bc -l)" -eq 1 ]; then - echo "❌ Average $label ($average) is below threshold $threshold" - fail=1 - return - fi + case "$mode" in + fail_if_below_threshold) + if [ "$(echo "$average < $threshold" | bc -l)" -eq 1 ]; then + echo "❌ $label ($average) is below threshold $threshold" + fail=1 + return + fi + ;; + fail_if_above_threshold) + if [ "$(echo "$average > $threshold" | bc -l)" -eq 1 ]; then + echo "❌ $label ($average) is above threshold $threshold" + fail=1 + return + fi + ;; + *) + echo "❌ Unknown mode: $mode" + exit 1 + ;; + esac echo "✅ $label passed (average = $average)" } -#check_metric "read_latency_ms_p95" "$READ_LATENCY_MS_THRESHOLD" "Read Latency P95" -#check_metric "write_latency_ms_p95" "$WRITE_LATENCY_MS_THRESHOLD" "Write Latency P95" -check_metric "read_throughput" "$READ_THROUGHPUT_THRESHOLD" "Read Throughput" -check_metric "write_throughput" "$WRITE_THROUGHPUT_THRESHOLD" "Write Throughput" -#check_metric "read_attempts" "$READ_ATTEMPTS_THRESHOLD" "Read Attempts" -#check_metric "write_attempts" "$WRITE_ATTEMPTS_THRESHOLD" "Write Attempts" -check_metric "read_availability" "$READ_AVAILABILITY_THRESHOLD" "Read Availability" -check_metric "write_availability" "$WRITE_AVAILABILITY_THRESHOLD" "Write Availability" +check_metric "read_latency_ms" "$READ_LATENCY_MS_THRESHOLD" "Read Latency P95" fail_if_above_threshold +check_metric "write_latency_ms" "$WRITE_LATENCY_MS_THRESHOLD" "Write Latency P95" fail_if_above_threshold +check_metric "read_throughput" "$READ_THROUGHPUT_THRESHOLD" "Read Throughput" fail_if_below_threshold +check_metric "write_throughput" "$WRITE_THROUGHPUT_THRESHOLD" "Write Throughput" fail_if_below_threshold +check_metric "read_attempts" "$READ_ATTEMPTS_THRESHOLD" "Read Attempts" fail_if_above_threshold +check_metric "write_attempts" "$WRITE_ATTEMPTS_THRESHOLD" "Write Attempts" fail_if_above_threshold +check_metric "read_availability" "$READ_AVAILABILITY_THRESHOLD" "Read Availability" fail_if_below_threshold +check_metric "write_availability" "$WRITE_AVAILABILITY_THRESHOLD" "Write Availability" fail_if_below_threshold if [ "$fail" -eq 1 ]; then echo "❗ Some metrics did not meet thresholds." diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index b72bfef0..967b2b79 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -119,14 +119,14 @@ jobs: runs-on: ubuntu-latest env: - # READ_LATENCY_MS_THRESHOLD: "1" # 95th percentile read operations latency in milliseconds - # WRITE_LATENCY_MS_THRESHOLD: "1" # 95th percentile write operations latency in milliseconds + READ_LATENCY_MS_THRESHOLD: "600" # 95th percentile read operations latency in milliseconds + WRITE_LATENCY_MS_THRESHOLD: "800" # 95th percentile write operations latency in milliseconds READ_THROUGHPUT_THRESHOLD: "150" # Read operations throughput WRITE_THROUGHPUT_THRESHOLD: "3" # Write operations throughput - # READ_ATTEMPTS_THRESHOLD: "1" # Read attempts throughput - # WRITE_ATTEMPTS_THRESHOLD: "1" # Write attempts throughput + READ_ATTEMPTS_THRESHOLD: "100" # Read attempts throughput + WRITE_ATTEMPTS_THRESHOLD: "100" # Write attempts throughput READ_AVAILABILITY_THRESHOLD: "90" # Read operations availability - WRITE_AVAILABILITY_THRESHOLD: "90" # Write operations availability + WRITE_AVAILABILITY_THRESHOLD: "80" # Write operations availability strategy: matrix: