Compare commits

...
Author SHA1 Message Date
Nils Andreas Svee 9240152e4b chore(release): irtt-client 0.5.3
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 34s
Rust CI / cargo clippy (push) Successful in 36s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 51s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo doc (push) Successful in 23s
Supply chain / cargo-deny advisories (push) Successful in 8s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 29s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m3s
Semver / cargo-semver-checks (push) Failing after 48s
Add socket routing configuration for interface binding, packet marks, and FreeBSD FIB selection.
2026-09-28 14:37:30 +07:00
Nils Andreas Svee 7bbc89e1c0 feat(socket): support advanced routing configuration for client socket
Rust CI / cargo fmt (push) Successful in 16s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 35s
Rust CI / cargo clippy (push) Successful in 36s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 51s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo doc (push) Successful in 21s
Supply chain / cargo-deny advisories (push) Successful in 9s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 30s
Supply chain / cargo-deny bans licenses sources (push) Successful in 8s
Supply chain / actionlint (push) Successful in 2s
Netem Interop / netem interop (push) Successful in 1m5s
Enable binding to a specific network interface, applying a firewall mark, or selecting a routing table (FIB) for outgoing packets on supported platforms.
2026-09-28 14:24:21 +07:00
Nils Andreas Svee b6043e4961 refactor(stats): use shared measurement statistics
Rust CI / cargo fmt (push) Successful in 16s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 32s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 48s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo doc (push) Successful in 21s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 28s
Supply chain / cargo-deny advisories (push) Successful in 7s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 2s
Netem Interop / netem interop (push) Successful in 1m2s
2026-09-27 00:03:35 +07:00
Nils Andreas Svee b36842ee98 chore(release): publish docs for irtt-proto, irtt-server, irtt-stats
Rust CI / cargo fmt (push) Successful in 13s
Rust CI / cargo clippy (push) Successful in 26s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 12s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 25s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 44s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo doc (push) Successful in 26s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 31s
Supply chain / cargo-deny advisories (push) Successful in 7s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m2s
Semver / cargo-semver-checks (push) Successful in 54s
Version the remaining public crates independently so their crate-specific READMEs, examples, and API doctests are represented in release history.
2026-09-25 00:32:11 +07:00
Nils Andreas Svee cf48988170 chore(release): irtt-client 0.5.2, irtt-rs 0.7.1
Rust CI / cargo fmt (push) Successful in 14s
Rust CI / cargo clippy (push) Successful in 29s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 25s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 43s
Rust CI / cargo doc (push) Successful in 23s
Supply chain / cargo-deny advisories (push) Successful in 7s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 29s
Supply chain / cargo-deny bans licenses sources (push) Successful in 6s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m8s
Semver / cargo-semver-checks (push) Successful in 58s
Publish irtt-server image / publish (push) Successful in 1m13s
Fix kernel transmit-timestamp correlation after failed probe sends and accept ordinary boolean values for the server DSCP environment option.
2026-09-25 00:29:52 +07:00
renovate Bot 41aee2a551 fix(deps): update rust crate clap to 4.6.7
Rust CI / cargo fmt (push) Canceled after 0s
Rust CI / cargo clippy (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo doc (push) Canceled after 0s
Fuzz Smoke / cargo fuzz smoke (push) Canceled after 0s
Netem Interop / netem interop (push) Canceled after 0s
Supply chain / cargo-deny advisories (push) Canceled after 0s
Supply chain / cargo-deny bans licenses sources (push) Canceled after 0s
Supply chain / actionlint (push) Canceled after 0s
2026-09-24 10:21:00 +00:00
renovate Bot 722539c8d7 chore(deps): update github actions to v2
Rust CI / cargo fmt (push) Canceled after 0s
Rust CI / cargo clippy (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo doc (push) Canceled after 0s
Fuzz Smoke / cargo fuzz smoke (push) Canceled after 0s
Netem Interop / netem interop (push) Canceled after 0s
Supply chain / cargo-deny advisories (push) Canceled after 0s
Supply chain / cargo-deny bans licenses sources (push) Canceled after 0s
Supply chain / actionlint (push) Canceled after 0s
2026-09-24 10:20:48 +00:00
renovate Bot 8369ec3502 chore(deps): update github actions
Rust CI / cargo fmt (push) Canceled after 0s
Rust CI / cargo clippy (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo doc (push) Canceled after 0s
Fuzz Smoke / cargo fuzz smoke (push) Canceled after 0s
Netem Interop / netem interop (push) Canceled after 0s
Supply chain / cargo-deny advisories (push) Canceled after 0s
Supply chain / cargo-deny bans licenses sources (push) Canceled after 0s
Supply chain / actionlint (push) Canceled after 0s
2026-09-24 10:19:59 +00:00
renovate Bot 1df69969db chore(deps): maintain cargo lockfile
Rust CI / cargo fmt (push) Canceled after 0s
Rust CI / cargo clippy (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo doc (push) Canceled after 0s
Fuzz Smoke / cargo fuzz smoke (push) Canceled after 0s
Netem Interop / netem interop (push) Canceled after 0s
Supply chain / cargo-deny advisories (push) Canceled after 0s
Supply chain / cargo-deny bans licenses sources (push) Canceled after 0s
Supply chain / actionlint (push) Canceled after 0s
2026-09-24 10:19:40 +00:00
Nils Andreas Svee 4908921fb8 Merge branch 'docs/doctests'
Rust CI / cargo fmt (push) Canceled after 0s
Rust CI / cargo clippy (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Canceled after 0s
Rust CI / cargo doc (push) Canceled after 0s
Fuzz Smoke / cargo fuzz smoke (push) Canceled after 0s
Netem Interop / netem interop (push) Canceled after 0s
Supply chain / cargo-deny advisories (push) Canceled after 0s
Supply chain / cargo-deny bans licenses sources (push) Canceled after 0s
Supply chain / actionlint (push) Canceled after 0s
2026-09-24 17:13:02 +07:00
25 changed files with 270 additions and 283 deletions

No files matched your search

+4 -4
View File
@@ -28,7 +28,7 @@ jobs:
persist-credentials: false
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
components: rustfmt
cache: false
@@ -48,7 +48,7 @@ jobs:
persist-credentials: false
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
components: clippy
cache: false
@@ -109,7 +109,7 @@ jobs:
sudo apt-get install --no-install-recommends musl-tools
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
target: ${{ matrix.target }}
cache: false
@@ -147,7 +147,7 @@ jobs:
persist-credentials: false
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
cache: false
+2 -2
View File
@@ -42,7 +42,7 @@ jobs:
# (native builds, no emulation), but buildx still needs a driver that
# can assemble a multi-platform manifest list.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
- name: Log in to Gitea Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
@@ -76,7 +76,7 @@ jobs:
# below), never a separate build, so the hash matches everywhere.
- name: Build and push to Gitea
id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
with:
context: .
platforms: linux/amd64,linux/arm64
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
persist-credentials: false
- name: Install stable Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
cache: false
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
persist-credentials: false
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
cache: false
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
persist-credentials: false
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
cache: false
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
# It remains here while portable actionlint runs with Gitea CI, but scans
# both workflow directories through its GitHub Actions rule set.
- name: Run zizmor
uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
with:
persona: regular
inputs: |
+1 -1
View File
@@ -56,7 +56,7 @@ jobs:
sudo apt-get install --no-install-recommends musl-tools
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
target: ${{ matrix.target }}
cache-key: ${{ matrix.task }}-${{ matrix.target }}
+2 -2
View File
@@ -31,7 +31,7 @@ permissions:
jobs:
scan-scheduled:
if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }}
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@6e4298ebc4db23e847df9b2e2de2939d6f066c67" # v2.5.1
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@a345acffa64b0eaede81a3d9aae6141214d9c8fc" # v2.6.0
with:
# Example of specifying custom arguments
scan-args: |-
@@ -40,7 +40,7 @@ jobs:
./
scan-pr:
if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@6e4298ebc4db23e847df9b2e2de2939d6f066c67" # v2.5.1
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@a345acffa64b0eaede81a3d9aae6141214d9c8fc" # v2.6.0
with:
# Example of specifying custom arguments
scan-args: |-
+2 -2
View File
@@ -29,7 +29,7 @@ jobs:
persist-credentials: false
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
components: clippy
cache-key: clippy-sarif
@@ -60,7 +60,7 @@ jobs:
exit 0
- name: Upload Clippy SARIF
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
if: always()
with:
sarif_file: rust-clippy-results.sarif
+36
View File
@@ -10,6 +10,12 @@ release. The format is loosely based on
## irtt-proto
### 0.5.1
#### Added
- Added a crate-specific README and runnable API doctests to make the published crate's protocol types and codecs easier to discover and use.
### 0.5.0
#### Added
@@ -28,6 +34,18 @@ release. The format is loosely based on
## irtt-client
### 0.5.3
#### Added
- `SocketConfig` can bind UDP sockets to a network interface and set a packet mark on Linux, Android, and Fuchsia, or select a routing FIB on FreeBSD.
### 0.5.2
#### Fixed
- Kernel transmit-timestamp correlation is now invalidated when a probe send fails, preventing a later timestamp from being matched to the wrong probe.
### 0.5.1
#### Added
@@ -56,6 +74,12 @@ release. The format is loosely based on
## irtt-server
### 0.5.3
#### Added
- Added a crate-specific README and usage examples for single- and multi-listener servers, plus runnable API doctests.
### 0.5.2
#### Changed
@@ -95,6 +119,12 @@ release. The format is loosely based on
## irtt-stats
### 0.5.1
#### Added
- Added a crate-specific README, a finite-versus-continuous statistics example, and runnable API doctests for the published crate.
### 0.5.0
#### Fixed
@@ -108,6 +138,12 @@ release. The format is loosely based on
## irtt-rs
### 0.7.1
#### Fixed
- `IRTT_SERVER_NO_DSCP` now accepts ordinary boolean environment values such as `1` and `0`; falsey values leave DSCP marking enabled, while truthy values disable it. The `--no-dscp` command-line flag remains a flag without a value.
### 0.7.0
#### Added
Generated
+52 -45
View File
@@ -213,9 +213,9 @@ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
[[package]]
name = "clap"
version = "4.6.6"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
dependencies = [
"clap_builder",
"clap_derive",
@@ -223,9 +223,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.6.6"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
dependencies = [
"anstream",
"anstyle",
@@ -235,14 +235,14 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.6.4"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 3.0.4",
"syn 3.0.5",
]
[[package]]
@@ -403,7 +403,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 3.0.4",
"syn 3.0.5",
]
[[package]]
@@ -414,7 +414,7 @@ checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [
"darling_core",
"quote",
"syn 3.0.4",
"syn 3.0.5",
]
[[package]]
@@ -710,12 +710,12 @@ dependencies = [
"indoc",
"proc-macro2",
"quote",
"syn 3.0.4",
"syn 3.0.5",
]
[[package]]
name = "irtt-client"
version = "0.5.1"
version = "0.5.3"
dependencies = [
"irtt-proto",
"irtt-server",
@@ -738,7 +738,7 @@ dependencies = [
[[package]]
name = "irtt-proto"
version = "0.5.0"
version = "0.5.1"
dependencies = [
"hmac",
"md-5",
@@ -749,7 +749,7 @@ dependencies = [
[[package]]
name = "irtt-rs"
version = "0.7.0"
version = "0.7.1"
dependencies = [
"clap",
"crossterm",
@@ -764,7 +764,7 @@ dependencies = [
[[package]]
name = "irtt-server"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"getrandom 0.4.3",
"irtt-proto",
@@ -776,9 +776,10 @@ dependencies = [
[[package]]
name = "irtt-stats"
version = "0.5.0"
version = "0.5.1"
dependencies = [
"irtt-client",
"measurement-stats",
"proptest",
]
@@ -805,9 +806,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.104"
version = "0.3.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
dependencies = [
"cfg-if",
"futures-util",
@@ -887,9 +888,9 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "lru"
version = "0.18.3"
version = "0.18.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d317b4b9eb398e6acce275758ec6125535505e7a146fb1a9b8bda2451b0ff4c"
checksum = "ff9840bcc50b71349309900da0ce7279aa336ae71d73250b07998932c7d97c25"
dependencies = [
"hashbrown 0.17.1",
]
@@ -914,6 +915,12 @@ dependencies = [
"digest 0.11.3",
]
[[package]]
name = "measurement-stats"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0edbfa45a060f44b15568637075ecebbe258c22f86ce6f2b5d58d7a64cbdb689"
[[package]]
name = "memchr"
version = "2.8.3"
@@ -943,9 +950,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mio"
version = "1.2.2"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
dependencies = [
"libc",
"log",
@@ -1118,9 +1125,9 @@ dependencies = [
[[package]]
name = "pest"
version = "2.9.0"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf"
checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad"
dependencies = [
"memchr",
"ucd-trie",
@@ -1128,9 +1135,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.9.0"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d"
checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f"
dependencies = [
"pest",
"pest_generator",
@@ -1138,9 +1145,9 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.9.0"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a"
checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5"
dependencies = [
"pest",
"pest_meta",
@@ -1151,9 +1158,9 @@ dependencies = [
[[package]]
name = "pest_meta"
version = "2.9.0"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496"
checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e"
dependencies = [
"pest",
]
@@ -1569,7 +1576,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.4",
"syn 3.0.5",
]
[[package]]
@@ -1628,9 +1635,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.2"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
[[package]]
name = "socket2"
@@ -1705,9 +1712,9 @@ dependencies = [
[[package]]
name = "syn"
version = "3.0.4"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
dependencies = [
"proc-macro2",
"quote",
@@ -1840,7 +1847,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.4",
"syn 3.0.5",
]
[[package]]
@@ -1886,7 +1893,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.4",
"syn 3.0.5",
]
[[package]]
@@ -1995,9 +2002,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.127"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
dependencies = [
"cfg-if",
"once_cell",
@@ -2008,9 +2015,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.127"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2018,22 +2025,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.127"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 2.0.119",
"syn 3.0.5",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.127"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
dependencies = [
"unicode-ident",
]
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "irtt-rs"
version = "0.7.0"
version = "0.7.1"
description = "IRTT-compatible client, TUI, and server applications"
edition.workspace = true
license.workspace = true
@@ -47,7 +47,7 @@ server = ["dep:irtt-server", "dep:tokio"]
tui =["dep:irtt-client", "irtt-client/ancillary", "irtt-client/tokio", "dep:irtt-proto", "dep:irtt-stats", "dep:ratatui", "dep:crossterm"]
[dependencies]
clap = { version = "4.6.6", features = ["derive", "env"] }
clap = { version = "4.6.7", features = ["derive", "env"] }
crossterm = { version = "0.29.0", optional = true }
ctrlc = { version = "3.5.2", features = ["termination"] }
irtt-client = { workspace = true, optional = true }
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "irtt-client"
version = "0.5.1"
version = "0.5.3"
description = "IRTT-compatible Rust client library"
edition.workspace = true
license.workspace = true
+9
View File
@@ -231,6 +231,15 @@ pub struct SocketConfig {
/// `None` binds to an unspecified address with an ephemeral port matching
/// the selected remote address family.
pub bind_addr: Option<SocketAddr>,
/// Network interface to bind this socket to (`SO_BINDTODEVICE`).
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
pub bind_to_device: Option<String>,
/// Firewall mark applied to outgoing packets (`SO_MARK`).
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
pub mark: Option<u32>,
/// FreeBSD FIB used for routing this socket (`SO_SETFIB`).
#[cfg(target_os = "freebsd")]
pub fib: Option<u32>,
/// Optional IPv4 TTL or IPv6 hop limit applied to sent packets.
///
/// `None` leaves the platform default unchanged. Values must fit in the
+15
View File
@@ -12,6 +12,14 @@ use crate::{
socket_options::apply_ttl_to_socket,
};
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "linux"
))]
use crate::socket_options::apply_routing_options;
pub(crate) fn validate_open_timeouts(timeouts: &[Duration]) -> Result<(), ClientError> {
if timeouts.is_empty() {
return Err(ClientError::NoOpenTimeouts);
@@ -115,6 +123,13 @@ fn create_connected_udp_socket(
remote: SocketAddr,
) -> Result<UdpSocket, ClientError> {
let socket = create_prebind_udp_socket(config, remote)?;
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "linux"
))]
apply_routing_options(&socket, config, remote)?;
let bind_addr = config.bind_addr.unwrap_or_else(|| {
if remote.is_ipv4() {
SocketAddr::from(([0, 0, 0, 0], 0))
+53
View File
@@ -5,11 +5,64 @@ use std::{
use socket2::SockRef;
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "linux"
))]
use socket2::Socket;
use crate::{
config::{MAX_DSCP_CODEPOINT, MAX_TTL},
error::ClientError,
};
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "linux"
))]
pub(crate) fn apply_routing_options(
socket: &Socket,
config: &crate::SocketConfig,
remote: SocketAddr,
) -> Result<(), ClientError> {
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
{
if let Some(device) = &config.bind_to_device {
socket
.bind_device(Some(device.as_bytes()))
.map_err(|source| ClientError::SocketOption {
operation: "bind socket to device",
remote,
source,
})?;
}
if let Some(mark) = config.mark {
socket
.set_mark(mark)
.map_err(|source| ClientError::SocketOption {
operation: "set firewall mark",
remote,
source,
})?;
}
}
#[cfg(target_os = "freebsd")]
if let Some(fib) = config.fib {
socket
.set_fib(fib)
.map_err(|source| ClientError::SocketOption {
operation: "set FIB",
remote,
source,
})?;
}
Ok(())
}
/// Converts a public DSCP codepoint (`0..=`[`MAX_DSCP_CODEPOINT`]) into the
/// raw IP TOS / Traffic Class byte it occupies the upper six bits of.
///
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "irtt-proto"
version = "0.5.0"
version = "0.5.1"
description = "IRTT protocol encoding and decoding primitives"
edition.workspace = true
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "irtt-server"
version = "0.5.2"
version = "0.5.3"
description = "IRTT-compatible Rust server library"
edition.workspace = true
license.workspace = true
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "irtt-stats"
version = "0.5.0"
version = "0.5.1"
description = "Statistics aggregation for irtt-client events"
edition.workspace = true
license.workspace = true
@@ -12,6 +12,7 @@ keywords = ["irtt", "networking", "latency", "statistics"]
categories = ["network-programming"]
[dependencies]
measurement-stats = "0.1.0"
irtt-client.workspace = true
[[example]]
+14
View File
@@ -10,6 +10,11 @@ This crate does not open sessions or drive sockets. Feed it the `ClientEvent`
stream produced by an `irtt-client` `Client`, `AsyncClient`, or managed
session via `StatsCollector::process`, and read back a `Snapshot`.
Generic timing accumulation and count-window storage come from
`measurement-stats`. `TimeStats` remains available from `irtt_stats` as a
re-export. Event interpretation, IPDV pairing, loss, and snapshot reconstruction
remain here.
A `StatsCollector` has one sequence/IPDV namespace and one packet count, both
scoped to a single target. A managed session covering multiple targets needs
one collector per target — as the `irtt-rs` CLI does — since a target's
@@ -26,6 +31,15 @@ bounded running statistics instead (no exact median) plus a bounded
unbounded sessions. Rolling-window snapshots are always running-only and
report no medians, whichever `StatsConfig` produced them.
An enabled time-based rolling window has no event-count cap, including in
continuous mode. It expires only on new events, anchored at their timestamps;
reading a snapshot does not advance time. Use count-based rolling storage for a
hard bound on retained events. The memory estimate excludes time-window storage.
Exact snapshots sort a temporary copy of each metric's retained samples, adding
O(n) scratch memory and O(n log n) computation for that metric. The retention
estimate is not peak snapshot or allocator accounting.
See `examples/` in the repository for a runnable comparison of both modes.
## Documentation
+40 -34
View File
@@ -1,9 +1,10 @@
use std::time::Duration;
use measurement_stats::TimeAccumulator;
use crate::ipdv::{IpdvSample, IpdvTracker};
use crate::loss::loss_stats;
use crate::normalization::{ReplySample, StatsEvent};
use crate::time_stats::TimeMetric;
use crate::{
EventCounts, EventStatsUpdate, IpdvPairUpdate, IpdvStats, LateReplyMode, OneWayDelayStats,
PacketCounts, RttStats, SampleMode, ServerProcessingStats, Snapshot,
@@ -15,17 +16,17 @@ pub(crate) const CONTINUOUS_SEQUENCE_LIMIT: usize = 4096;
pub(crate) struct CoreStats {
events: EventCounts,
packets: PacketCounts,
send_call: TimeMetric,
timer_error: TimeMetric,
rtt_primary: TimeMetric,
rtt_raw: TimeMetric,
rtt_adjusted: TimeMetric,
ipdv_round_trip: TimeMetric,
ipdv_send: TimeMetric,
ipdv_receive: TimeMetric,
send_delay: TimeMetric,
receive_delay: TimeMetric,
server_processing: TimeMetric,
send_call: TimeAccumulator,
timer_error: TimeAccumulator,
rtt_primary: TimeAccumulator,
rtt_raw: TimeAccumulator,
rtt_adjusted: TimeAccumulator,
ipdv_round_trip: TimeAccumulator,
ipdv_send: TimeAccumulator,
ipdv_receive: TimeAccumulator,
send_delay: TimeAccumulator,
receive_delay: TimeAccumulator,
server_processing: TimeAccumulator,
ipdv_tracker: IpdvTracker,
late_replies: LateReplyMode,
}
@@ -42,20 +43,25 @@ impl CoreStats {
Some(CONTINUOUS_SEQUENCE_LIMIT)
};
let metric = || match sample_mode {
SampleMode::Exact => TimeAccumulator::exact_unbounded(),
SampleMode::RunningOnly => TimeAccumulator::running(),
};
Self {
events: EventCounts::default(),
packets: PacketCounts::default(),
send_call: TimeMetric::new(retain_exact),
timer_error: TimeMetric::new(retain_exact),
rtt_primary: TimeMetric::new(retain_exact),
rtt_raw: TimeMetric::new(retain_exact),
rtt_adjusted: TimeMetric::new(retain_exact),
ipdv_round_trip: TimeMetric::new(retain_exact),
ipdv_send: TimeMetric::new(retain_exact),
ipdv_receive: TimeMetric::new(retain_exact),
send_delay: TimeMetric::new(retain_exact),
receive_delay: TimeMetric::new(retain_exact),
server_processing: TimeMetric::new(retain_exact),
send_call: metric(),
timer_error: metric(),
rtt_primary: metric(),
rtt_raw: metric(),
rtt_adjusted: metric(),
ipdv_round_trip: metric(),
ipdv_send: metric(),
ipdv_receive: metric(),
send_delay: metric(),
receive_delay: metric(),
server_processing: metric(),
ipdv_tracker: IpdvTracker::new(sequence_limit),
late_replies,
}
@@ -227,8 +233,8 @@ impl CoreStats {
events: self.events,
packets,
loss: loss_stats(packets),
send_call: self.send_call.stats(),
timer_error: self.timer_error.stats(),
send_call: self.send_call.snapshot(),
timer_error: self.timer_error.snapshot(),
rtt: self.rtt_stats(),
ipdv: self.ipdv_stats(),
one_way_delay: self.one_way_delay_stats(),
@@ -238,30 +244,30 @@ impl CoreStats {
fn rtt_stats(&self) -> RttStats {
RttStats {
primary: self.rtt_primary.stats(),
raw: self.rtt_raw.stats(),
adjusted: self.rtt_adjusted.stats(),
primary: self.rtt_primary.snapshot(),
raw: self.rtt_raw.snapshot(),
adjusted: self.rtt_adjusted.snapshot(),
}
}
fn ipdv_stats(&self) -> IpdvStats {
IpdvStats {
round_trip: self.ipdv_round_trip.stats(),
send: self.ipdv_send.stats(),
receive: self.ipdv_receive.stats(),
round_trip: self.ipdv_round_trip.snapshot(),
send: self.ipdv_send.snapshot(),
receive: self.ipdv_receive.snapshot(),
}
}
fn one_way_delay_stats(&self) -> OneWayDelayStats {
OneWayDelayStats {
send_delay: self.send_delay.stats(),
receive_delay: self.receive_delay.stats(),
send_delay: self.send_delay.snapshot(),
receive_delay: self.receive_delay.snapshot(),
}
}
fn server_processing_stats(&self) -> ServerProcessingStats {
ServerProcessingStats {
processing: self.server_processing.stats(),
processing: self.server_processing.snapshot(),
}
}
}
+11 -5
View File
@@ -15,8 +15,9 @@
//! [`StatsConfig::continuous`] ([`SampleMode::RunningOnly`]) keeps only
//! running statistics — no exact median — and bounds its adjacent-sequence
//! IPDV tracking, so memory stays bounded for a long-running or unbounded
//! session. Rolling snapshots always use running statistics regardless of
//! this setting.
//! session. An enabled time-based rolling window has no item cap and is not
//! hard-bounded; it expires only when new events arrive. Rolling snapshots
//! always use running statistics regardless of this setting.
//!
//! See `examples/` in the repository for a runnable comparison of both modes.
@@ -34,13 +35,13 @@ mod loss;
mod normalization;
mod retention;
mod rolling;
mod time_stats;
use core::CoreStats;
pub use loss::LossStats;
#[doc(inline)]
pub use measurement_stats::TimeStats;
use normalization::normalize_event;
use rolling::RollingEvents;
pub use time_stats::TimeStats;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Configuration for statistics collection.
@@ -54,6 +55,10 @@ pub struct StatsConfig {
/// and one unique reply event.
pub rolling_count: Option<usize>,
/// Time span of recent normalized events retained for time-based rolling snapshots.
///
/// This window has no item cap, even in continuous mode. Its memory depends
/// on event volume inside the interval. Expiry occurs on insertion, anchored
/// at the latest event timestamp, not when a snapshot is read.
pub rolling_time: Option<Duration>,
/// Whether matched late replies contribute measurements.
pub late_replies: LateReplyMode,
@@ -117,7 +122,8 @@ impl StatsConfig {
///
/// Continuous mode uses running statistics, retains no exact samples so no
/// timing metric reports a median, and bounds adjacent-sequence IPDV
/// tracking for long-running sessions.
/// tracking for long-running sessions. An explicitly enabled `rolling_time`
/// window is not hard-bounded; use `rolling_count` for bounded event storage.
///
/// # Example
///
+12 -12
View File
@@ -1,14 +1,15 @@
use std::{collections::VecDeque, time::Duration};
use measurement_stats::CountWindow;
use crate::{
core::CoreStats, normalization::StatsEvent, LateReplyMode, SampleMode, Snapshot, StatsConfig,
};
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct RollingEvents {
count_limit: Option<usize>,
time_limit: Option<Duration>,
count_events: Option<VecDeque<StatsEvent>>,
count_events: Option<CountWindow<StatsEvent>>,
time_events: Option<VecDeque<StatsEvent>>,
late_replies: LateReplyMode,
}
@@ -16,20 +17,16 @@ pub(crate) struct RollingEvents {
impl RollingEvents {
pub(crate) fn new(config: StatsConfig) -> Self {
Self {
count_limit: config.rolling_count,
time_limit: config.rolling_time,
count_events: config.rolling_count.map(|_| VecDeque::new()),
count_events: config.rolling_count.map(CountWindow::new),
time_events: config.rolling_time.map(|_| VecDeque::new()),
late_replies: config.late_replies,
}
}
pub(crate) fn push(&mut self, event: StatsEvent) {
if let (Some(limit), Some(window)) = (self.count_limit, self.count_events.as_mut()) {
window.push_back(event.clone());
while window.len() > limit {
window.pop_front();
}
if let Some(window) = self.count_events.as_mut() {
window.push(event.clone());
}
if let (Some(duration), Some(window)) = (self.time_limit, self.time_events.as_mut()) {
@@ -46,19 +43,22 @@ impl RollingEvents {
pub(crate) fn count_snapshot(&self) -> Option<Snapshot> {
self.count_events
.as_ref()
.map(|events| snapshot_window(events, self.late_replies))
.map(|events| snapshot_window(events.iter(), self.late_replies))
}
pub(crate) fn time_snapshot(&self) -> Option<Snapshot> {
self.time_events
.as_ref()
.map(|events| snapshot_window(events, self.late_replies))
.map(|events| snapshot_window(events.iter(), self.late_replies))
}
}
/// Recompute a window snapshot under the same normalized semantics as the
/// cumulative collector, including its late-reply measurement policy.
fn snapshot_window(events: &VecDeque<StatsEvent>, late_replies: LateReplyMode) -> Snapshot {
fn snapshot_window<'a>(
events: impl Iterator<Item = &'a StatsEvent>,
late_replies: LateReplyMode,
) -> Snapshot {
let mut core = CoreStats::new(SampleMode::RunningOnly, late_replies);
for event in events {
core.apply(event.clone());
-160
View File
@@ -1,160 +0,0 @@
#[derive(Debug, Clone, Copy, PartialEq)]
/// Summary statistics for timing values.
///
/// All fields use nanoseconds except `variance_ns2`, which is nanoseconds
/// squared. Values may represent signed timing quantities such as scheduling
/// error or one-way delay.
pub struct TimeStats {
/// Number of samples included in this summary.
pub count: u64,
/// Sum of all samples, in nanoseconds.
pub total_ns: i128,
/// Smallest sample, in nanoseconds.
pub min_ns: Option<i128>,
/// Largest sample, in nanoseconds.
pub max_ns: Option<i128>,
/// Arithmetic mean, in nanoseconds.
pub mean_ns: f64,
/// Median, in nanoseconds, when exact samples were retained.
///
/// This is `None` when the collector was not configured to retain exact
/// samples, and also when it was but this metric received none.
pub median_ns: Option<f64>,
/// Sample variance, in nanoseconds squared.
pub variance_ns2: f64,
}
impl TimeStats {
/// Returns the sample standard deviation, in nanoseconds.
pub fn stddev_ns(&self) -> f64 {
self.variance_ns2.sqrt()
}
/// Returns whether this summary contains no samples.
pub fn is_empty(&self) -> bool {
self.count == 0
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct TimeMetric {
running: RunningStats,
samples: Option<Vec<i128>>,
}
impl TimeMetric {
pub(crate) fn new(retain_samples: bool) -> Self {
Self {
running: RunningStats::default(),
samples: retain_samples.then(Vec::new),
}
}
pub(crate) fn push_ns(&mut self, value: i128) {
self.running.push(value);
if let Some(samples) = self.samples.as_mut() {
samples.push(value);
}
}
pub(crate) fn stats(&self) -> TimeStats {
self.running
.stats(self.samples.as_ref().and_then(|samples| median_ns(samples)))
}
}
#[derive(Debug, Clone, PartialEq, Default)]
struct RunningStats {
count: u64,
total_ns: i128,
min_ns: Option<i128>,
max_ns: Option<i128>,
mean_ns: f64,
m2_ns2: f64,
}
impl RunningStats {
fn push(&mut self, value: i128) {
self.count += 1;
self.total_ns = self.total_ns.saturating_add(value);
self.min_ns = Some(self.min_ns.map_or(value, |min| min.min(value)));
self.max_ns = Some(self.max_ns.map_or(value, |max| max.max(value)));
let x = value as f64;
let delta = x - self.mean_ns;
self.mean_ns += delta / self.count as f64;
let delta2 = x - self.mean_ns;
self.m2_ns2 += delta * delta2;
}
fn stats(&self, median_ns: Option<f64>) -> TimeStats {
TimeStats {
count: self.count,
total_ns: self.total_ns,
min_ns: self.min_ns,
max_ns: self.max_ns,
mean_ns: if self.count == 0 { 0.0 } else { self.mean_ns },
median_ns,
variance_ns2: sample_variance(self.count, self.m2_ns2),
}
}
}
fn sample_variance(count: u64, m2: f64) -> f64 {
if count < 2 {
0.0
} else {
m2 / (count - 1) as f64
}
}
fn median_ns(samples: &[i128]) -> Option<f64> {
if samples.is_empty() {
return None;
}
let mut sorted = samples.to_vec();
sorted.sort_unstable();
let mid = sorted.len() / 2;
Some(if sorted.len() % 2 == 1 {
sorted[mid] as f64
} else {
(sorted[mid - 1] as f64 + sorted[mid] as f64) / 2.0
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn running_duration_stats_use_sample_variance() {
let mut metric = TimeMetric::new(false);
metric.push_ns(1);
metric.push_ns(2);
metric.push_ns(3);
let stats = metric.stats();
assert_eq!(stats.count, 3);
assert_eq!(stats.total_ns, 6);
assert_eq!(stats.min_ns, Some(1));
assert_eq!(stats.max_ns, Some(3));
assert_eq!(stats.mean_ns, 2.0);
assert_eq!(stats.variance_ns2, 1.0);
assert_eq!(stats.stddev_ns(), 1.0);
}
#[test]
fn exact_median_handles_odd_and_even_samples() {
assert_eq!(median_ns(&[3, 1, 2]), Some(2.0));
assert_eq!(median_ns(&[4, 1, 2, 3]), Some(2.5));
assert_eq!(median_ns(&[-5, 1, 3]), Some(1.0));
assert_eq!(median_ns(&[-5, 1, 3, 7]), Some(2.0));
}
#[test]
fn single_sample_stddev_is_zero() {
let mut metric = TimeMetric::new(false);
metric.push_ns(42);
let stats = metric.stats();
assert_eq!(stats.variance_ns2, 0.0);
assert_eq!(stats.stddev_ns(), 0.0);
}
}
+6 -6
View File
@@ -31,9 +31,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.4.4"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -97,9 +97,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
[[package]]
name = "getrandom"
@@ -285,9 +285,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "3.0.4"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
dependencies = [
"proc-macro2",
"quote",