Compare commits

...
Author SHA1 Message Date
renovate Bot f350c82dae chore(deps): maintain cargo lockfile
Rust CI / cargo fmt (push) Successful in 18s
Rust CI / cargo clippy (push) Successful in 32s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 30s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 48s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 24s
Rust CI / cargo doc (push) Successful in 23s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 28s
Supply chain / cargo-deny advisories (push) Successful in 6s
Supply chain / cargo-deny bans licenses sources (push) Successful in 8s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m4s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 5m37s
2026-10-01 23:25:15 +07:00
renovate Bot 22d3f6cb27 fix(deps): update rust crate thiserror to 2.0.21 2026-10-01 23:25:15 +07:00
renovate Bot 85238682b8 chore(deps): update github actions to 2892aa5 2026-10-01 23:25:05 +07:00
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
16 changed files with 294 additions and 319 deletions

No files matched your search

+1 -1
View File
@@ -60,7 +60,7 @@ jobs:
exit 0 exit 0
- name: Upload Clippy SARIF - name: Upload Clippy SARIF
uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4 uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
if: always() if: always()
with: with:
sarif_file: rust-clippy-results.sarif sarif_file: rust-clippy-results.sarif
+24
View File
@@ -10,6 +10,12 @@ release. The format is loosely based on
## irtt-proto ## 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 ### 0.5.0
#### Added #### Added
@@ -28,6 +34,12 @@ release. The format is loosely based on
## irtt-client ## 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 ### 0.5.2
#### Fixed #### Fixed
@@ -62,6 +74,12 @@ release. The format is loosely based on
## irtt-server ## 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 ### 0.5.2
#### Changed #### Changed
@@ -101,6 +119,12 @@ release. The format is loosely based on
## irtt-stats ## 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 ### 0.5.0
#### Fixed #### Fixed
Generated
+89 -82
View File
@@ -141,9 +141,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.13.1" version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
@@ -201,9 +201,9 @@ dependencies = [
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]] [[package]]
name = "cfg_aliases" name = "cfg_aliases"
@@ -242,14 +242,14 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "1.1.0" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
[[package]] [[package]]
name = "cmov" name = "cmov"
@@ -313,7 +313,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"crossterm_winapi", "crossterm_winapi",
"derive_more", "derive_more",
"document-features", "document-features",
@@ -403,7 +403,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -414,7 +414,7 @@ checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -479,7 +479,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"block2", "block2",
"libc", "libc",
"objc2", "objc2",
@@ -554,9 +554,9 @@ dependencies = [
[[package]] [[package]]
name = "finl_unicode" name = "finl_unicode"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" checksum = "80bb028c8b4148c9ee0cca68fcd9add6044e81d3619f48577ddf13a263d047a2"
[[package]] [[package]]
name = "fixedbitset" name = "fixedbitset"
@@ -678,9 +678,9 @@ dependencies = [
[[package]] [[package]]
name = "hybrid-array" name = "hybrid-array"
version = "0.4.14" version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
dependencies = [ dependencies = [
"typenum", "typenum",
] ]
@@ -702,27 +702,27 @@ dependencies = [
[[package]] [[package]]
name = "instability" name = "instability"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8" checksum = "4c3b5acc1e2fd9375041a388da33d1eb8aed5f7a8c0dd3543e3ea2805adfbe20"
dependencies = [ dependencies = [
"darling", "darling",
"indoc", "indoc",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
name = "irtt-client" name = "irtt-client"
version = "0.5.2" version = "0.5.3"
dependencies = [ dependencies = [
"irtt-proto", "irtt-proto",
"irtt-server", "irtt-server",
"nix 0.31.3", "nix 0.31.3",
"proptest", "proptest",
"socket2", "socket2",
"thiserror 2.0.20", "thiserror 2.0.21",
"tokio", "tokio",
] ]
@@ -738,13 +738,13 @@ dependencies = [
[[package]] [[package]]
name = "irtt-proto" name = "irtt-proto"
version = "0.5.0" version = "0.5.1"
dependencies = [ dependencies = [
"hmac", "hmac",
"md-5", "md-5",
"proptest", "proptest",
"subtle", "subtle",
"thiserror 2.0.20", "thiserror 2.0.21",
] ]
[[package]] [[package]]
@@ -764,21 +764,22 @@ dependencies = [
[[package]] [[package]]
name = "irtt-server" name = "irtt-server"
version = "0.5.2" version = "0.5.3"
dependencies = [ dependencies = [
"getrandom 0.4.3", "getrandom 0.4.3",
"irtt-proto", "irtt-proto",
"nix 0.31.3", "nix 0.31.3",
"socket2", "socket2",
"thiserror 2.0.20", "thiserror 2.0.21",
"tokio", "tokio",
] ]
[[package]] [[package]]
name = "irtt-stats" name = "irtt-stats"
version = "0.5.0" version = "0.5.1"
dependencies = [ dependencies = [
"irtt-client", "irtt-client",
"measurement-stats",
"proptest", "proptest",
] ]
@@ -805,9 +806,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.105" version = "0.3.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@@ -822,7 +823,7 @@ checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899"
dependencies = [ dependencies = [
"hashbrown 0.16.1", "hashbrown 0.16.1",
"portable-atomic", "portable-atomic",
"thiserror 2.0.20", "thiserror 2.0.21",
] ]
[[package]] [[package]]
@@ -855,7 +856,7 @@ version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e" checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
] ]
[[package]] [[package]]
@@ -887,9 +888,9 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]] [[package]]
name = "lru" name = "lru"
version = "0.18.4" version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff9840bcc50b71349309900da0ce7279aa336ae71d73250b07998932c7d97c25" checksum = "ef9ac18847474e638e3702b76c65d4eb93428471a74778ef0f1be711717f89b5"
dependencies = [ dependencies = [
"hashbrown 0.17.1", "hashbrown 0.17.1",
] ]
@@ -914,6 +915,12 @@ dependencies = [
"digest 0.11.3", "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]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.3" version = "2.8.3"
@@ -959,7 +966,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@@ -972,7 +979,7 @@ version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@@ -1118,9 +1125,9 @@ dependencies = [
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.9.1" version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" checksum = "45d3aca230fad2e6f6317ca0a72724338c4960cb97168a85cdee66df4a9a21a8"
dependencies = [ dependencies = [
"memchr", "memchr",
"ucd-trie", "ucd-trie",
@@ -1128,9 +1135,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_derive" name = "pest_derive"
version = "2.9.1" version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f" checksum = "284b60557f2c4a2e72ad3f2d34d42685a2fa4a6a61d0d2a10c0ae2a5e916c2cf"
dependencies = [ dependencies = [
"pest", "pest",
"pest_generator", "pest_generator",
@@ -1138,9 +1145,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_generator" name = "pest_generator"
version = "2.9.1" version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5" checksum = "1d9d1f08a115309ee99268cf85e5228e0e56aa9caf8841ec12866b6be07c3109"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
@@ -1151,9 +1158,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_meta" name = "pest_meta"
version = "2.9.1" version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e" checksum = "ed93ba1a9ffcca32130a5188701c81c0c49cf00d4b7c5007d5148951d743adcb"
dependencies = [ dependencies = [
"pest", "pest",
] ]
@@ -1254,7 +1261,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
dependencies = [ dependencies = [
"bit-set 0.8.0", "bit-set 0.8.0",
"bit-vec 0.8.0", "bit-vec 0.8.0",
"bitflags 2.13.1", "bitflags 2.13.2",
"num-traits", "num-traits",
"rand 0.9.5", "rand 0.9.5",
"rand_chacha", "rand_chacha",
@@ -1367,7 +1374,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"compact_str", "compact_str",
"critical-section", "critical-section",
"hashbrown 0.17.1", "hashbrown 0.17.1",
@@ -1377,7 +1384,7 @@ dependencies = [
"palette", "palette",
"serde", "serde",
"strum", "strum",
"thiserror 2.0.20", "thiserror 2.0.21",
"unicode-segmentation", "unicode-segmentation",
"unicode-truncate", "unicode-truncate",
"unicode-width", "unicode-width",
@@ -1432,7 +1439,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"hashbrown 0.17.1", "hashbrown 0.17.1",
"indoc", "indoc",
"instability", "instability",
@@ -1452,7 +1459,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
] ]
[[package]] [[package]]
@@ -1495,11 +1502,11 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.1.4" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@@ -1569,7 +1576,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -1616,9 +1623,9 @@ dependencies = [
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256"
[[package]] [[package]]
name = "slab" name = "slab"
@@ -1628,9 +1635,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.16.0" version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
[[package]] [[package]]
name = "socket2" name = "socket2"
@@ -1705,9 +1712,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.5" version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1733,7 +1740,7 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"parking_lot", "parking_lot",
"rustix", "rustix",
"signal-hook", "signal-hook",
@@ -1769,7 +1776,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
"bitflags 2.13.1", "bitflags 2.13.2",
"fancy-regex", "fancy-regex",
"filedescriptor", "filedescriptor",
"finl_unicode", "finl_unicode",
@@ -1814,11 +1821,11 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.20" version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
dependencies = [ dependencies = [
"thiserror-impl 2.0.20", "thiserror-impl 2.0.21",
] ]
[[package]] [[package]]
@@ -1834,13 +1841,13 @@ dependencies = [
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.20" version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -1886,7 +1893,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -1909,9 +1916,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
@@ -1944,9 +1951,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.26.0" version = "1.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
dependencies = [ dependencies = [
"atomic", "atomic",
"getrandom 0.4.3", "getrandom 0.4.3",
@@ -1995,9 +2002,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.128" version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -2008,9 +2015,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.128" version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -2018,22 +2025,22 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.128" version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.128" version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -2155,18 +2162,18 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.56" version = "0.8.59"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.56" version = "0.8.59"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "irtt-client" name = "irtt-client"
version = "0.5.2" version = "0.5.3"
description = "IRTT-compatible Rust client library" description = "IRTT-compatible Rust client library"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
@@ -14,7 +14,7 @@ categories = ["network-programming"]
[dependencies] [dependencies]
irtt-proto.workspace = true irtt-proto.workspace = true
socket2 = { version = "0.6.5", features = ["all"] } socket2 = { version = "0.6.5", features = ["all"] }
thiserror = "2.0.20" thiserror = "2.0.21"
tokio = { version = "1.53.1", default-features = false, features = ["rt", "net", "time", "sync"], optional = true} tokio = { version = "1.53.1", default-features = false, features = ["rt", "net", "time", "sync"], optional = true}
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
+9
View File
@@ -231,6 +231,15 @@ pub struct SocketConfig {
/// `None` binds to an unspecified address with an ephemeral port matching /// `None` binds to an unspecified address with an ephemeral port matching
/// the selected remote address family. /// the selected remote address family.
pub bind_addr: Option<SocketAddr>, 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. /// Optional IPv4 TTL or IPv6 hop limit applied to sent packets.
/// ///
/// `None` leaves the platform default unchanged. Values must fit in the /// `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, 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> { pub(crate) fn validate_open_timeouts(timeouts: &[Duration]) -> Result<(), ClientError> {
if timeouts.is_empty() { if timeouts.is_empty() {
return Err(ClientError::NoOpenTimeouts); return Err(ClientError::NoOpenTimeouts);
@@ -115,6 +123,13 @@ fn create_connected_udp_socket(
remote: SocketAddr, remote: SocketAddr,
) -> Result<UdpSocket, ClientError> { ) -> Result<UdpSocket, ClientError> {
let socket = create_prebind_udp_socket(config, remote)?; 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(|| { let bind_addr = config.bind_addr.unwrap_or_else(|| {
if remote.is_ipv4() { if remote.is_ipv4() {
SocketAddr::from(([0, 0, 0, 0], 0)) SocketAddr::from(([0, 0, 0, 0], 0))
+53
View File
@@ -5,11 +5,64 @@ use std::{
use socket2::SockRef; use socket2::SockRef;
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "linux"
))]
use socket2::Socket;
use crate::{ use crate::{
config::{MAX_DSCP_CODEPOINT, MAX_TTL}, config::{MAX_DSCP_CODEPOINT, MAX_TTL},
error::ClientError, 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 /// Converts a public DSCP codepoint (`0..=`[`MAX_DSCP_CODEPOINT`]) into the
/// raw IP TOS / Traffic Class byte it occupies the upper six bits of. /// raw IP TOS / Traffic Class byte it occupies the upper six bits of.
/// ///
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "irtt-proto" name = "irtt-proto"
version = "0.5.0" version = "0.5.1"
description = "IRTT protocol encoding and decoding primitives" description = "IRTT protocol encoding and decoding primitives"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
@@ -15,7 +15,7 @@ categories = ["network-programming"]
hmac = "0.13" hmac = "0.13"
md-5 = "0.11" md-5 = "0.11"
subtle = "2.6.1" subtle = "2.6.1"
thiserror = "2.0.20" thiserror = "2.0.21"
[dev-dependencies] [dev-dependencies]
proptest = "1.11.0" proptest = "1.11.0"
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "irtt-server" name = "irtt-server"
version = "0.5.2" version = "0.5.3"
description = "IRTT-compatible Rust server library" description = "IRTT-compatible Rust server library"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
@@ -15,7 +15,7 @@ categories = ["network-programming"]
irtt-proto.workspace = true irtt-proto.workspace = true
getrandom = "0.4.3" getrandom = "0.4.3"
socket2 = { version = "0.6.5", features = ["all"] } socket2 = { version = "0.6.5", features = ["all"] }
thiserror = "2.0.20" thiserror = "2.0.21"
tokio = { version = "1.53.1", default-features = false, features = ["rt", "net", "time", "sync", "macros"] } tokio = { version = "1.53.1", default-features = false, features = ["rt", "net", "time", "sync", "macros"] }
# Wildcard listeners recover each request's local destination address and send # Wildcard listeners recover each request's local destination address and send
+2 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "irtt-stats" name = "irtt-stats"
version = "0.5.0" version = "0.5.1"
description = "Statistics aggregation for irtt-client events" description = "Statistics aggregation for irtt-client events"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
@@ -12,6 +12,7 @@ keywords = ["irtt", "networking", "latency", "statistics"]
categories = ["network-programming"] categories = ["network-programming"]
[dependencies] [dependencies]
measurement-stats = "0.1.0"
irtt-client.workspace = true irtt-client.workspace = true
[[example]] [[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 stream produced by an `irtt-client` `Client`, `AsyncClient`, or managed
session via `StatsCollector::process`, and read back a `Snapshot`. 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 A `StatsCollector` has one sequence/IPDV namespace and one packet count, both
scoped to a single target. A managed session covering multiple targets needs 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 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 unbounded sessions. Rolling-window snapshots are always running-only and
report no medians, whichever `StatsConfig` produced them. 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. See `examples/` in the repository for a runnable comparison of both modes.
## Documentation ## Documentation
+40 -34
View File
@@ -1,9 +1,10 @@
use std::time::Duration; use std::time::Duration;
use measurement_stats::TimeAccumulator;
use crate::ipdv::{IpdvSample, IpdvTracker}; use crate::ipdv::{IpdvSample, IpdvTracker};
use crate::loss::loss_stats; use crate::loss::loss_stats;
use crate::normalization::{ReplySample, StatsEvent}; use crate::normalization::{ReplySample, StatsEvent};
use crate::time_stats::TimeMetric;
use crate::{ use crate::{
EventCounts, EventStatsUpdate, IpdvPairUpdate, IpdvStats, LateReplyMode, OneWayDelayStats, EventCounts, EventStatsUpdate, IpdvPairUpdate, IpdvStats, LateReplyMode, OneWayDelayStats,
PacketCounts, RttStats, SampleMode, ServerProcessingStats, Snapshot, PacketCounts, RttStats, SampleMode, ServerProcessingStats, Snapshot,
@@ -15,17 +16,17 @@ pub(crate) const CONTINUOUS_SEQUENCE_LIMIT: usize = 4096;
pub(crate) struct CoreStats { pub(crate) struct CoreStats {
events: EventCounts, events: EventCounts,
packets: PacketCounts, packets: PacketCounts,
send_call: TimeMetric, send_call: TimeAccumulator,
timer_error: TimeMetric, timer_error: TimeAccumulator,
rtt_primary: TimeMetric, rtt_primary: TimeAccumulator,
rtt_raw: TimeMetric, rtt_raw: TimeAccumulator,
rtt_adjusted: TimeMetric, rtt_adjusted: TimeAccumulator,
ipdv_round_trip: TimeMetric, ipdv_round_trip: TimeAccumulator,
ipdv_send: TimeMetric, ipdv_send: TimeAccumulator,
ipdv_receive: TimeMetric, ipdv_receive: TimeAccumulator,
send_delay: TimeMetric, send_delay: TimeAccumulator,
receive_delay: TimeMetric, receive_delay: TimeAccumulator,
server_processing: TimeMetric, server_processing: TimeAccumulator,
ipdv_tracker: IpdvTracker, ipdv_tracker: IpdvTracker,
late_replies: LateReplyMode, late_replies: LateReplyMode,
} }
@@ -42,20 +43,25 @@ impl CoreStats {
Some(CONTINUOUS_SEQUENCE_LIMIT) Some(CONTINUOUS_SEQUENCE_LIMIT)
}; };
let metric = || match sample_mode {
SampleMode::Exact => TimeAccumulator::exact_unbounded(),
SampleMode::RunningOnly => TimeAccumulator::running(),
};
Self { Self {
events: EventCounts::default(), events: EventCounts::default(),
packets: PacketCounts::default(), packets: PacketCounts::default(),
send_call: TimeMetric::new(retain_exact), send_call: metric(),
timer_error: TimeMetric::new(retain_exact), timer_error: metric(),
rtt_primary: TimeMetric::new(retain_exact), rtt_primary: metric(),
rtt_raw: TimeMetric::new(retain_exact), rtt_raw: metric(),
rtt_adjusted: TimeMetric::new(retain_exact), rtt_adjusted: metric(),
ipdv_round_trip: TimeMetric::new(retain_exact), ipdv_round_trip: metric(),
ipdv_send: TimeMetric::new(retain_exact), ipdv_send: metric(),
ipdv_receive: TimeMetric::new(retain_exact), ipdv_receive: metric(),
send_delay: TimeMetric::new(retain_exact), send_delay: metric(),
receive_delay: TimeMetric::new(retain_exact), receive_delay: metric(),
server_processing: TimeMetric::new(retain_exact), server_processing: metric(),
ipdv_tracker: IpdvTracker::new(sequence_limit), ipdv_tracker: IpdvTracker::new(sequence_limit),
late_replies, late_replies,
} }
@@ -227,8 +233,8 @@ impl CoreStats {
events: self.events, events: self.events,
packets, packets,
loss: loss_stats(packets), loss: loss_stats(packets),
send_call: self.send_call.stats(), send_call: self.send_call.snapshot(),
timer_error: self.timer_error.stats(), timer_error: self.timer_error.snapshot(),
rtt: self.rtt_stats(), rtt: self.rtt_stats(),
ipdv: self.ipdv_stats(), ipdv: self.ipdv_stats(),
one_way_delay: self.one_way_delay_stats(), one_way_delay: self.one_way_delay_stats(),
@@ -238,30 +244,30 @@ impl CoreStats {
fn rtt_stats(&self) -> RttStats { fn rtt_stats(&self) -> RttStats {
RttStats { RttStats {
primary: self.rtt_primary.stats(), primary: self.rtt_primary.snapshot(),
raw: self.rtt_raw.stats(), raw: self.rtt_raw.snapshot(),
adjusted: self.rtt_adjusted.stats(), adjusted: self.rtt_adjusted.snapshot(),
} }
} }
fn ipdv_stats(&self) -> IpdvStats { fn ipdv_stats(&self) -> IpdvStats {
IpdvStats { IpdvStats {
round_trip: self.ipdv_round_trip.stats(), round_trip: self.ipdv_round_trip.snapshot(),
send: self.ipdv_send.stats(), send: self.ipdv_send.snapshot(),
receive: self.ipdv_receive.stats(), receive: self.ipdv_receive.snapshot(),
} }
} }
fn one_way_delay_stats(&self) -> OneWayDelayStats { fn one_way_delay_stats(&self) -> OneWayDelayStats {
OneWayDelayStats { OneWayDelayStats {
send_delay: self.send_delay.stats(), send_delay: self.send_delay.snapshot(),
receive_delay: self.receive_delay.stats(), receive_delay: self.receive_delay.snapshot(),
} }
} }
fn server_processing_stats(&self) -> ServerProcessingStats { fn server_processing_stats(&self) -> ServerProcessingStats {
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 //! [`StatsConfig::continuous`] ([`SampleMode::RunningOnly`]) keeps only
//! running statistics — no exact median — and bounds its adjacent-sequence //! running statistics — no exact median — and bounds its adjacent-sequence
//! IPDV tracking, so memory stays bounded for a long-running or unbounded //! IPDV tracking, so memory stays bounded for a long-running or unbounded
//! session. Rolling snapshots always use running statistics regardless of //! session. An enabled time-based rolling window has no item cap and is not
//! this setting. //! 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. //! See `examples/` in the repository for a runnable comparison of both modes.
@@ -34,13 +35,13 @@ mod loss;
mod normalization; mod normalization;
mod retention; mod retention;
mod rolling; mod rolling;
mod time_stats;
use core::CoreStats; use core::CoreStats;
pub use loss::LossStats; pub use loss::LossStats;
#[doc(inline)]
pub use measurement_stats::TimeStats;
use normalization::normalize_event; use normalization::normalize_event;
use rolling::RollingEvents; use rolling::RollingEvents;
pub use time_stats::TimeStats;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Configuration for statistics collection. /// Configuration for statistics collection.
@@ -54,6 +55,10 @@ pub struct StatsConfig {
/// and one unique reply event. /// and one unique reply event.
pub rolling_count: Option<usize>, pub rolling_count: Option<usize>,
/// Time span of recent normalized events retained for time-based rolling snapshots. /// 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>, pub rolling_time: Option<Duration>,
/// Whether matched late replies contribute measurements. /// Whether matched late replies contribute measurements.
pub late_replies: LateReplyMode, pub late_replies: LateReplyMode,
@@ -117,7 +122,8 @@ impl StatsConfig {
/// ///
/// Continuous mode uses running statistics, retains no exact samples so no /// Continuous mode uses running statistics, retains no exact samples so no
/// timing metric reports a median, and bounds adjacent-sequence IPDV /// 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 /// # Example
/// ///
+12 -12
View File
@@ -1,14 +1,15 @@
use std::{collections::VecDeque, time::Duration}; use std::{collections::VecDeque, time::Duration};
use measurement_stats::CountWindow;
use crate::{ use crate::{
core::CoreStats, normalization::StatsEvent, LateReplyMode, SampleMode, Snapshot, StatsConfig, core::CoreStats, normalization::StatsEvent, LateReplyMode, SampleMode, Snapshot, StatsConfig,
}; };
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub(crate) struct RollingEvents { pub(crate) struct RollingEvents {
count_limit: Option<usize>,
time_limit: Option<Duration>, time_limit: Option<Duration>,
count_events: Option<VecDeque<StatsEvent>>, count_events: Option<CountWindow<StatsEvent>>,
time_events: Option<VecDeque<StatsEvent>>, time_events: Option<VecDeque<StatsEvent>>,
late_replies: LateReplyMode, late_replies: LateReplyMode,
} }
@@ -16,20 +17,16 @@ pub(crate) struct RollingEvents {
impl RollingEvents { impl RollingEvents {
pub(crate) fn new(config: StatsConfig) -> Self { pub(crate) fn new(config: StatsConfig) -> Self {
Self { Self {
count_limit: config.rolling_count,
time_limit: config.rolling_time, 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()), time_events: config.rolling_time.map(|_| VecDeque::new()),
late_replies: config.late_replies, late_replies: config.late_replies,
} }
} }
pub(crate) fn push(&mut self, event: StatsEvent) { pub(crate) fn push(&mut self, event: StatsEvent) {
if let (Some(limit), Some(window)) = (self.count_limit, self.count_events.as_mut()) { if let Some(window) = self.count_events.as_mut() {
window.push_back(event.clone()); window.push(event.clone());
while window.len() > limit {
window.pop_front();
}
} }
if let (Some(duration), Some(window)) = (self.time_limit, self.time_events.as_mut()) { 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> { pub(crate) fn count_snapshot(&self) -> Option<Snapshot> {
self.count_events self.count_events
.as_ref() .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> { pub(crate) fn time_snapshot(&self) -> Option<Snapshot> {
self.time_events self.time_events
.as_ref() .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 /// Recompute a window snapshot under the same normalized semantics as the
/// cumulative collector, including its late-reply measurement policy. /// 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); let mut core = CoreStats::new(SampleMode::RunningOnly, late_replies);
for event in events { for event in events {
core.apply(event.clone()); 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);
}
}
+18 -18
View File
@@ -31,9 +31,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.4.5" version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -43,9 +43,9 @@ dependencies = [
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]] [[package]]
name = "cfg_aliases" name = "cfg_aliases"
@@ -97,9 +97,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.12" version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
@@ -123,16 +123,16 @@ dependencies = [
[[package]] [[package]]
name = "hybrid-array" name = "hybrid-array"
version = "0.4.14" version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
dependencies = [ dependencies = [
"typenum", "typenum",
] ]
[[package]] [[package]]
name = "irtt-proto" name = "irtt-proto"
version = "0.5.0" version = "0.5.1"
dependencies = [ dependencies = [
"hmac", "hmac",
"md-5", "md-5",
@@ -152,7 +152,7 @@ dependencies = [
[[package]] [[package]]
name = "irtt-server" name = "irtt-server"
version = "0.5.2" version = "0.5.3"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"irtt-proto", "irtt-proto",
@@ -285,9 +285,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.5" version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -296,18 +296,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.20" version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.20" version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -347,9 +347,9 @@ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]] [[package]]
name = "wasi" name = "wasi"