v0.34.3_hf1
This commit is contained in:
1 parent
8f41b39c26
commit
29d9a7dd6c
10 files changed
+121
-67
No files matched your search
+1
-1
@@ -13,7 +13,7 @@ Release history focused on **features, settings, default behavior, compatibility
|
||||
- QUIC now opens the UDP listener socket with per-listener receive/send buffer targets (4 MiB each by default; `udp_receive_buffer_bytes`, `udp_send_buffer_bytes`; `0` uses OS defaults). Reports the effective buffer sizes and warns if kernel limits clamp them. Linux `SO_RCVBUF` / `SO_SNDBUF` accounting is accommodated, so observed `ss` values correspond to the configured effective target when permitted. UDP buffer modifications require restart, not silent hot application. Existing configs without these fields load the new defaults.
|
||||
- HTTP/3 `Alt-Svc` follows newly saved listener lifetime immediately on H1/H2 TLS responses for an already-bound H3 socket; disabling H3 publishes `Alt-Svc: clear`. New QUIC handshakes use saved stream/idle limits and reduced connection caps. Listener binding/TLS/H1-H2 changes, a new/disabled UDP listener or expanding startup capacity still need restart; UI restart indicator reflects this.
|
||||
- DEBUG-level bulk QUIC snapshots expose packet loss, congestion window, RTT, MTU and datagram syscall counters to investigate slow H3 and CPU usage, without assuming an unmeasured throughput improvement.
|
||||
- Overview adds a **recent HTTP cache outcomes** 100% stacked bar with HIT, MISS, BYPASS and OTHER (requests with no cache decision); retains the original lifetime HIT/MISS donut. Samples exclude pauses/resets and do not normalize invalid counter differences into misleading percentages. The widget is included in the dashboard registry and factory preset but **hidden by default**; administrators can enable it in the layout editor. Saved layout visibility is preserved. Both UI languages are supported.
|
||||
- Overview includes a **cumulative HTTP cache outcomes** 100% stacked bar with HIT, MISS, BYPASS and OTHER (requests without a cache decision), using totals since service start instead of sampled deltas. Hotfix: each category now shows counts and percentages, including before a second browser poll. Invalid/inconsistent totals are not normalized into misleading percentages. The widget remains **hidden by default** and opt-in via the layout editor. Saved layout visibility, the lifetime HIT/MISS donut and the separate recent cache trend chart are preserved. Both UI languages are supported.
|
||||
- Delivered as source for compilation and protocol acceptance on supported targets; no compiled Rust binary or measured post-patch benchmark is included in this candidate.
|
||||
|
||||
## 0.34.2 — configuration storage visibility and public HTTP error responses
|
||||
|
||||
@@ -20,7 +20,7 @@ Features and default settings vary by component; configure only the capabilities
|
||||
|
||||
- HTTP/3 now explicitly requests **4 MiB effective UDP receive and send buffers** per QUIC listener (configurable as `udp_receive_buffer_bytes` and `udp_send_buffer_bytes` in Settings > HTTP/3). On Linux the kernel can cap them using `net.core.rmem_max` / `net.core.wmem_max`; RustProxy logs the effective values and a warning if the target is not met. Setting either value to `0` leaves the OS default. Changing UDP buffers requires service restart.
|
||||
- HTTP/3 Alt-Svc response headers follow saved policy immediately; turning off an already-bound H3 service emits `Alt-Svc: clear`. New QUIC handshakes pick up modified stream/idle limits without socket rebinding. Increasing the connection cap beyond the startup bound or changing the UDP endpoint still requires restart. QUIC bulk-transfer diagnostic snapshots are available at debug log level.
|
||||
- The Overview adds an **optional, hidden-by-default 100% horizontal cache-outcome bar** showing recent HIT, MISS, BYPASS and OTHER HTTP requests. Enable it in the dashboard layout editor when needed. This complements the existing lifetime HIT/MISS donut. OTHER denotes HTTP requests that did not produce a cache decision during the sample. The chart does not claim a cache hit-rate for all requests.
|
||||
- The Overview has an **optional, hidden-by-default cumulative cache-outcome bar** showing HIT, MISS, BYPASS and OTHER request counts and percentages **since service start**. Enable it in the dashboard layout editor when needed. This complements the existing cumulative HIT/MISS donut and separate recent cache trend chart. OTHER denotes HTTP requests without a cache decision. Percentages use all HTTP requests, unlike the HIT/MISS donut.
|
||||
|
||||
## 0.34.2
|
||||
|
||||
|
||||
+18
-18
@@ -2,11 +2,11 @@
|
||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
||||
00e914cd6a8f8962d857b92bdfa172aa7ce0bdbcafcebe8c9c67f452ac8479f2 AGENTS.md
|
||||
58b2500a3ddf9893f48b4ef881a308229c74665fb160d6189383fd5fe8b848b6 CHANGELOG.md
|
||||
a8e1e36e38e2a6ad9fe00cc605f9e8d31a9c446ab2bd620af969f6dab8193864 CHANGELOG.md
|
||||
789f77a2ecb443f171197d002c0911779bf1a8d8f0c151a18a6a4ee54d8c97eb Cargo.lock
|
||||
b6cf2ee2017e6a0ad99f66a809713f031b7b5480a4c97d74e2ed2c07c5c4f210 Cargo.toml
|
||||
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
|
||||
1ada0bf3146cd5f76b78a9ef21d03ade4bd5d08dbe2b9c89111aceda34a8ae1a README.md
|
||||
b09ec16a9045006967d8c00a7f3ea98bb69dfccf804e001e29a3e148fd2e8cd8 README.md
|
||||
ae6d14ada6fde7d0b8514840b821678418cf5b1816c18434e3b7fb2ae27a0b21 ROADMAP.md
|
||||
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
|
||||
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
||||
@@ -23,7 +23,7 @@ bf687a851374d8f1fa59d050f3f76532f7c12f16a6daa7d3c36102c2d9a8ec9c docker-agent/d
|
||||
3657c195ee6ff9c3c9f1e0d99a09b1ec2091edc61173098ef03db9b8dd7483fa docker-agent/install.sh
|
||||
b7d0466f70b541a705d234fc8a88b99b6197fdd5e682d3a288a78c76764358cf docker-agent/main.go
|
||||
3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go
|
||||
3cc158839e9b539b7762840f8666ea14f2ee8525438e58f6ea74e938b145393b docker-agent/model.go
|
||||
5778abe7a3bb0386ac7fa8929006dc719602135ab1a1bc9a47fa634092717185 docker-agent/model.go
|
||||
e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go
|
||||
634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service
|
||||
5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go
|
||||
@@ -36,7 +36,7 @@ b0eaa2cfe4f6e86582f67891e457591ae650d15145e025d85cab43e37c883ac8 docs/ACCESS.md
|
||||
e6d9feabc7a4fcb9a56a2028accbab19ca44479ee67deeefd0ab37f29c45cf4a docs/CONFIGURATION.md
|
||||
80bcc666736b10f6202386d0342e8d6feeba858d77aea310bf3ec6de01c2bfb3 docs/CONTAINER.md
|
||||
dc670d912a6a946160c2f8528c9bb710ec38ebe406dcb7dcaf18fb2653f667b6 docs/CUSTOM_LOCATIONS.md
|
||||
1f53baf1a2dbea19e155fc44394703eb3e0d4a698af7550bd0041fd95e127cd4 docs/DASHBOARD.md
|
||||
47d5e758870a1a81ddc8a66a64f1c5eee7bb154fee5335918b385939ffc88584 docs/DASHBOARD.md
|
||||
5a8d311c7ab36af9fb84964585bd974b92d8ea4979829b5735fb834ef10156ce docs/DOCKER_DISCOVERY.md
|
||||
dbae1a739874ac7b61e276c1ad6fd7c3465acc95afa4db3751224c8426ad6569 docs/INSTALL.md
|
||||
37001cbf8ed248ca05ec40f12dd9876d33f6e0c97dcb21eb2e6f53c3d97a60bb docs/INTEGRATIONS.md
|
||||
@@ -76,8 +76,8 @@ cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/r
|
||||
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
|
||||
3f267cafa16006e5456ade78ee2d5fae02cc7ce747839523068ffd8d7c98798a integrations/routeros-oci-build/docker-entrypoint.sh
|
||||
13189fa546c3488dddd8efc80c47f6da5241e5fc28b79cbfe795414ea05c7a4f langs/README.md
|
||||
bf33b38fd6690f20a76ddeab0cdfa52772158437ae8678be2d8e666984da64c3 langs/en_US.json
|
||||
9c18537f67ecdfa110f13e8a0271566ff2e3ed46dcc9232d2c4cf2b2d7d56328 langs/pl_pl.json
|
||||
3b176f3983616312589c4cb0e47365658bca7af13a4f8f3be264b4d3b6c82c16 langs/en_US.json
|
||||
42a5f15bce232663dbc8efe6073675f09f1e93ee65e279f4382202f36853d044 langs/pl_pl.json
|
||||
a99ad040be10fa028351e290b9363236b3513a1cadbdc9fb9e0260d517b9b0ae make_zip.py
|
||||
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
|
||||
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
|
||||
@@ -175,15 +175,15 @@ d997f62e8bd7ac9d4f16bb302800c98ad943afab663bb0b212ea26c35b58de11 src/admin/web/
|
||||
5bde5f0881320464eb133cd44de9eff5211e6816e79f7f7e9574b16784fab126 src/admin/web/app/preferences.js
|
||||
8e75b0915edfbadefc4388701431541f4d35fd5d717363297b63740b3afd34d2 src/admin/web/app/rate-sampling.js
|
||||
43f4dddd1c3ea7fa42eeb9d7b655a8db48b5ddcc6c68213e08dfc2fcfbe8b17d src/admin/web/app/refresh.js
|
||||
86cd1d5b2a6d12e329e9fbd92c825e53ab4aa6286ff7cf484610e81c4f44ba70 src/admin/web/app/runtime.js
|
||||
25378ae93a9c26cf65f1c7ff67136d073b5a5d7de74f289fe21cf79e2d6e2ffd src/admin/web/app/runtime.js
|
||||
31201586925e6f4792071d7d03fb05dcc4a69a03c02a4e85eb0746f45af68074 src/admin/web/app/session-resume.js
|
||||
86ac42bd4a6a84bb137d5a3c3dc3d1f4e404a543447845e6ea6e6e8210e268ce src/admin/web/app/settings.js
|
||||
8386710827e5ccc9432fb46413368f94afc1016425404a6092b460903636837e src/admin/web/app/stream-dashboard.js
|
||||
7f89bfae66e2a08a05a112733864e908f036bb1ee3d8b5b9ff23783b870acd96 src/admin/web/app/web-overview.js
|
||||
849b2ca9d183bdf6cb1e04651cda52da55b8a631e11ab1d827a052c8436bbd76 src/admin/web/cache-panel.js
|
||||
02a9f5a61379e68e1723e64ef4c589500e9a993adcb02fe352f1cf9456c90c52 src/admin/web/certificate-api.js
|
||||
ec73368c1297d807fad53349c73fc3b94bc2f25a7043058057a8b38644f67167 src/admin/web/dashboard-defaults.json
|
||||
7da696305e693c29ecf0ff463a7966128018f17ee8d8bdada295bd8bf87e9b05 src/admin/web/dashboard-widgets.json
|
||||
560a8e047182fbcee08aa4f8d999400a8a5ddca0b6a4640efd46f6b5c34fde91 src/admin/web/dashboard-defaults.json
|
||||
14213011cfb6ac334c8711b76bbdde97588a7b7ba4eb2592a1e387f877d6cbe0 src/admin/web/dashboard-widgets.json
|
||||
50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html
|
||||
d01a2e48f344fe074c1e63ee0a9bbf7151dfe26140690418535197e13a2a16d7 src/admin/web/docker.js
|
||||
d4aef376cb44b2a13174dca1cb9383f64c04b0b93279f4f0b4805e76002548a1 src/admin/web/docs.css
|
||||
@@ -205,20 +205,20 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
|
||||
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
|
||||
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
|
||||
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
|
||||
72fe97fd22e4a1e14b7e8f20f3edd032eeddea53b539894168c6431302802a0c src/admin/web/index.html
|
||||
12c80c1dcc8c9423e991ec51c34ddbbd9136f9843bbd600e9e2cfdca2686eae1 src/admin/web/integrations.js
|
||||
a6d073e94d7fff31b5d590759a5b390558da95e7e37353bf308dbbf302228cc9 src/admin/web/index.html
|
||||
7915a06cebb3dfa2c51833c326586920691ba4431b1f984429b039333f9c2a93 src/admin/web/integrations.js
|
||||
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
|
||||
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
|
||||
c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/login.js
|
||||
097fdb41aa1cd526c4d9bc65e502b05a62e664ef9fff8e69f706b93b0b124dc2 src/admin/web/management.js
|
||||
4481a592fcce9a2aa1db5e4a25619e19fd459c39039b86c0549dfc427eec1b45 src/admin/web/partials/overview-charts.html
|
||||
c8b6ea885bfc180e7e57e93b51634ea3b6aa1e3669ca41f162c66b1d0162982f src/admin/web/partials/overview-metrics.html
|
||||
9fe5d80abc751c55c3cc8f2e2e1110e2b3bcf0098ab49247f056ec0fb52a01fe src/admin/web/partials/overview-metrics.html
|
||||
3928afce2ed6d5f2d3e29475dd29e4c3bd3c6df180ad5119beaff0c7a58e3b3b src/admin/web/prometheus-token.js
|
||||
90d9a0836662135b79fb6f183c4f3eff60f8a64392d6c5795ccf1d8ef6862263 src/admin/web/redirects.js
|
||||
17784875e475d6df42964d7ecfe50ca82b74bad0e942342bc17537e727224796 src/admin/web/settings.js
|
||||
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
|
||||
c13cffe18a616fe5faa5ee945ab0d94d8d392ae8f32474c62fe41010e063ddb1 src/admin/web/streams.js
|
||||
027752715d9c3cbe7aebbe4c0cad2c9ec931f235778b894a4469c05616231839 src/admin/web/style.css
|
||||
96fe0459ef3204beb0ac35cefddd852fb9808558f02b2802842b09e60ded4aef src/admin/web/style.css
|
||||
d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js
|
||||
dfb4ded2fefb90fc1e2d33370cdef10b040a85cb43235ea9dbc1d73bef823a79 src/admin/web/transform-panel.js
|
||||
d816f2f70eef398c18091479320c36d07f9afe07278a250b840ebcdd7bc715f2 src/admin/web/vhosts/core.js
|
||||
@@ -324,7 +324,7 @@ c1b49bef28d87712e4c46138e70b52c4bcfb1a00196579031fac043d0b4aaa7b src/geoip/upda
|
||||
9a912dad0b8d1e10410fd95d0da3684656092ef85cf8b23eda4ffe7fb92921d8 src/geoip/update_tests.rs
|
||||
f6ebafafa82391f8cb3b5a09331f3762b6ca5a9fb64bafb5772cde3ab814dfcf src/headers.rs
|
||||
e4f07bfb7eb75b6897ef812870caff158679a3046d64582428abe8c6df490dbe src/headers/tests.rs
|
||||
652dbc91c7f36d6b4def44ecca22db8dabec518413121a52510ff92f0993b927 src/http3.rs
|
||||
502b170c1a708abc0e0b6b5579c66f4c0da816dd53f5ff82b2b97a1506644c01 src/http3.rs
|
||||
eb57117938a447d9a6c2ec1806ebd9fecc640a68c88950a8b35fa9bad14d0dc6 src/http3/body.rs
|
||||
ed25da264a2d25b40c4fcddc97c626a3a43d4f3a9be6c6d31af58b9e4925c198 src/http3/diagnostics.rs
|
||||
37f54961d8f52b79a75fb2be31e81f4fdb1e66b06350345c4483d6197b8a0bbc src/http3/identity.rs
|
||||
@@ -354,7 +354,7 @@ c915d30bbd7032d867e7916b4cd45f3bb7e7604d3f4558f4383c59394b4300c6 src/not_found/
|
||||
3567c2b65b09553e8aa46fb223dcab885b358fee9fc69cd0faf148059433b9e4 src/pages.rs
|
||||
da40e8ece87245efc49a0c4e750526b1d19fd97565856bd5caf641fcc1dfe18d src/pages/tests.rs
|
||||
63a84af77b6c3dd390aa9dfde71c527eec6e1a3c0a38c1f02c08055e525f250c src/prometheus.rs
|
||||
c53a6a29dbe1ef64f471054c3a4b49fd55d39ca938baac961fcaa5e06f5fbc74 src/prometheus/core.rs
|
||||
192a780d5874afc158d62a7dbe1687d734635afe9342fff526b8a2f6db4e5633 src/prometheus/core.rs
|
||||
74822d8293fe1ce64b18d8363a67e9f0ed2ee9d181e2668e5ca6bd258f98285c src/prometheus/docker_misc.rs
|
||||
97926653a7c00e884784c2739a998d666abb3a891f1642bcf1ecc6c49d0efa62 src/prometheus/json.rs
|
||||
c34c3204bfb07a0de95250dfc1208979a5e701ccf5b4093cfbfd9b996ff3ba15 src/prometheus/model.rs
|
||||
@@ -364,7 +364,7 @@ e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus
|
||||
fb2d8d45cd2e947f783eb981b37fa418c39fcb327a3af17a648fadea7f37f888 src/prometheus/web_php.rs
|
||||
2c97367686b54989573ce7fb81b2c6dde72adb254df9522bc05ded74f1fbffb6 src/proxy/body.rs
|
||||
66b7f2cb47f2e70dc26b7b19eb8650872e34d3e44cf250d2effdb758295f8b8a src/proxy/body/tests.rs
|
||||
37ce11759496277686eab55f1ac428e1c62baf43a33f28f41cbb1dd6385d4403 src/proxy/cache_helpers.rs
|
||||
2c65303210bc6783e4c75d0ecedc9be5a1bbeb063ceea8e1ac9983edbe9f5d31 src/proxy/cache_helpers.rs
|
||||
aa49c9928bb91e3d5bae0d340d88396a0417b9edec6192d41e1403533319a897 src/proxy/cache_http_invalidation.rs
|
||||
676c1b325fd8e55bcdd86451c7d1d8b8d347ca00e8da6b3e5b148a58c376a9b4 src/proxy/cache_plan.rs
|
||||
3404b03096862de1050088f62cdd811ea1ab61d191f902501173107f81f23b9b src/proxy/cache_plan/tests.rs
|
||||
@@ -390,7 +390,7 @@ b9a2307c6aa7f16613d935508a61574dfee964020b1b5be86293df2d684615d8 src/proxy/requ
|
||||
0f2eacadf1447db8aa1c1a78005aa262e0f747096bfe7e0a0e41a2cd31a0d17f src/proxy/tests/h3_navigation_tests.rs
|
||||
d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs
|
||||
fecc1c7ffff740caa7168370e59234a17b9836a6dce6d08b770ee9648c22709b src/proxy/types.rs
|
||||
e9cf8c1585441ce39dfc75f4e1cab7d0c3a8e4ea7b79b658911bc3ef4da2fa08 src/proxy/upstream_helpers.rs
|
||||
1455d33b16daf9f88bba76255795fcab6e050fb9c2bbea0bd601ac90e8ec30d4 src/proxy/upstream_helpers.rs
|
||||
2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs
|
||||
d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs
|
||||
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
|
||||
@@ -501,7 +501,7 @@ b35e00e4084b4de2125e9b6fff9699358b881277427bed8706fb2e8286e3cf28 src/web_server
|
||||
400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs
|
||||
c645740e5175e32a9ee743700e99a2f16b6fd894de5f734b84021b774dd747eb src/web_server/serve.rs
|
||||
dc3a18c14c3cc28519103e860b0f42a59d163db15df3f68b76778cdfa6786e46 src/web_server/tests.rs
|
||||
b1703af0b7749aa6ec59f4afa90198576925a721e4992e6214a2afcf3e57d86f tests/cache_outcomes.cjs
|
||||
c4d02f937694eeb7d45b955afee6438a3090312cc5318f2168f5d3430d2418ff tests/cache_outcomes.cjs
|
||||
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
|
||||
1725c10331745dc04a07675784d385260da7b14567b8dba3cbae5e891eb5dadf tests/http2.rs
|
||||
347a4ff9e028a1e999ad129590c355b5734cf928dd81e5099aea0c8aca8d5908 tests/http3.rs
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ Unfinished editors may be minimized into a draft dock; this is a UI convenience
|
||||
## Interpreting charts
|
||||
|
||||
- The **Cache hit ratio** donut shows cumulative `HIT / (HIT + MISS)`: BYPASS and requests outside cache are deliberately excluded from its denominator.
|
||||
- The **Cache outcomes (recent)** compact horizontal 100% bar is **hidden by default**; enable it in **Settings → Appearance → Dashboard layout** or the Overview layout editor. It compares HIT, MISS, BYPASS and OTHER against all HTTP request counter increments in one contiguous sampling interval. BYPASS includes requests for which the cache path was considered but disabled/skipped (including globally disabled cache). OTHER means requests that generated no cache decision, e.g. early rejected or redirect paths. These are request counts, not response bytes. It shows no value until a second valid sample, after background-tab pauses, after counter resets, or when independently observed cache outcomes briefly exceed the requests delta. Revisit the next interval rather than trusting a forced 100% value.
|
||||
- The **Cache outcomes** compact horizontal 100% bar is **hidden by default**; enable it in **Settings → Appearance → Dashboard layout** or the Overview layout editor. It summarizes **cumulative request counters since the current service process started**, not recent polling intervals. The legend shows the count and percentage for each category: HIT, MISS, BYPASS and OTHER. The percentages use all HTTP requests as the denominator. BYPASS includes requests where the cache path was considered but disabled/skipped (including globally disabled cache); OTHER covers requests without a cache decision, e.g. early rejected or redirect paths. These are request counts, not bytes; service restarts reset them. Values appear immediately with the first HTTP request; refreshing or pausing the browser does not reset totals. If independently counted cache outcomes exceed the request counter (e.g. while snapshots are inconsistent or a request incurs multiple cache events), the summary shows unavailable rather than fabricating OTHER or a normalized 100% breakdown. The separate recent cache trend chart remains unchanged.
|
||||
|
||||
|
||||
- Live views are process/sampling-based and can show no-data when an integration has not reported yet.
|
||||
|
||||
+6
-5
@@ -2771,12 +2771,13 @@
|
||||
"h3_udp_receive_buffer_mib": "UDP receive buffer (MiB; 0 = system default)",
|
||||
"h3_udp_send_buffer_mib": "UDP send buffer (MiB; 0 = system default)",
|
||||
"http3ApplyPolicy": "LIVE / RESTART",
|
||||
"cacheMixTitle": "Cache outcomes (recent)",
|
||||
"cacheMixHelp": "HIT/MISS/BYPASS and other HTTP requests in the last sample; non-cache routes count as OTHER.",
|
||||
"cacheMixTitle": "Cache outcomes",
|
||||
"cacheMixHelp": "Cumulative HIT/MISS/BYPASS and OTHER HTTP request mix since service start; OTHER means no cache decision. Each category shows count and share.",
|
||||
"cacheMixOther": "OTHER",
|
||||
"cacheMixSample": "Last interval · {count} HTTP requests",
|
||||
"cacheMixWaiting": "Waiting for comparable traffic samples",
|
||||
"cacheMixAria": "Recent HTTP requests: {hit} HIT, {miss} MISS, {bypass} BYPASS, {other} OTHER"
|
||||
"cacheMixSample": "Since service start · {count} HTTP requests",
|
||||
"cacheMixWaiting": "No HTTP requests since service start",
|
||||
"cacheMixAria": "Since service start: {hit} HIT, {miss} MISS, {bypass} BYPASS, {other} OTHER",
|
||||
"cacheMixInvalid": "Totals unavailable: request and cache counters disagree"
|
||||
},
|
||||
"overviewLayoutNarrow": "Decrease card width",
|
||||
"overviewLayoutWiden": "Increase card width",
|
||||
|
||||
+6
-5
@@ -2771,12 +2771,13 @@
|
||||
"h3_udp_receive_buffer_mib": "Bufor odbiorczy UDP (MiB; 0 = domyślny systemowy)",
|
||||
"h3_udp_send_buffer_mib": "Bufor nadawczy UDP (MiB; 0 = domyślny systemowy)",
|
||||
"http3ApplyPolicy": "NA ŻYWO / RESTART",
|
||||
"cacheMixTitle": "Wyniki cache (ostatni pomiar)",
|
||||
"cacheMixHelp": "HIT/MISS/BYPASS i pozostały ruch HTTP w ostatnim pomiarze; trasy bez decyzji cache to INNE.",
|
||||
"cacheMixTitle": "Wyniki cache (łącznie)",
|
||||
"cacheMixHelp": "Udział HIT/MISS/BYPASS i INNE we wszystkich żądaniach HTTP od startu usługi. INNE oznacza brak decyzji cache. Każda kategoria pokazuje liczbę i procent.",
|
||||
"cacheMixOther": "INNE",
|
||||
"cacheMixSample": "Ostatni przedział · {count} żądań HTTP",
|
||||
"cacheMixWaiting": "Oczekiwanie na porównywalne próbki ruchu",
|
||||
"cacheMixAria": "Ostatnie żądania HTTP: {hit} HIT, {miss} MISS, {bypass} BYPASS, {other} INNE"
|
||||
"cacheMixSample": "Od startu usługi · {count} żądań HTTP",
|
||||
"cacheMixWaiting": "Brak żądań HTTP od startu usługi",
|
||||
"cacheMixAria": "Od startu usługi: {hit} HIT, {miss} MISS, {bypass} BYPASS, {other} INNE",
|
||||
"cacheMixInvalid": "Suma niedostępna: niespójne liczniki żądań i cache"
|
||||
},
|
||||
"overviewLayoutNarrow": "Zmniejsz szerokość karty",
|
||||
"overviewLayoutWiden": "Zwiększ szerokość karty",
|
||||
|
||||
@@ -174,7 +174,6 @@ $("reload-certificates").addEventListener("click", async () => {
|
||||
$("reset-certificate").addEventListener("click", () => { $("certificate-form").reset(); $("certificate-file-source").value = "managed"; syncCertificateFileSource(); });
|
||||
syncCertificateFileSource();
|
||||
|
||||
let cacheMixLastCounters = null, cacheMixLastShares = null, cacheMixLastTotal = 0;
|
||||
let systemSamples = [], connectionOpenSamples = [], connectionExchangeSamples = [], cacheMemorySamples = [], cacheTransientSamples = [], cacheFillSamples = [], cacheColdQueueSamples = [], cacheColdIoSamples = [], cacheColdStorageSamples = [], cacheHitSamples = [], cacheMissSamples = [], cacheBypassSamples = [], lastCacheCounters = null, lastCacheOutcomeRates = null,
|
||||
processCpuSamples = [], processRssSamples = [], networkRxSamples = [], networkTxSamples = [], responseLatencySamples = [], networkInterfaceSamples = new Map(), settingsDirty = new Set();
|
||||
const hiddenNetworkInterfaces = (() => {
|
||||
@@ -332,32 +331,36 @@ function renderNetworkInterfaceCharts(interfaces, appendSample) {
|
||||
}
|
||||
}
|
||||
|
||||
function cacheOutcomeMix(deltas) {
|
||||
if (!Array.isArray(deltas) || deltas.length !== 4 ||
|
||||
deltas.some(value => !Number.isFinite(value) || value < 0)) return null;
|
||||
const [requests, hits, misses, bypasses] = deltas;
|
||||
// Cumulative process counters, not rate-sampler deltas. Never invent OTHER
|
||||
// or force a 100% mix if independently read totals are inconsistent.
|
||||
function cacheOutcomeMix(totals) {
|
||||
if (!Array.isArray(totals) || totals.length !== 4 ||
|
||||
totals.some(value => !Number.isSafeInteger(value) || value < 0)) return null;
|
||||
const [requests, hits, misses, bypasses] = totals;
|
||||
const cacheTotal = hits + misses + bypasses;
|
||||
if (requests <= 0 || cacheTotal > requests) return null;
|
||||
if (requests === 0 || !Number.isSafeInteger(cacheTotal) || cacheTotal > requests) return null;
|
||||
const counts = [hits, misses, bypasses, requests - cacheTotal];
|
||||
return { counts, percentages: counts.map(value => value / requests * 100), requests };
|
||||
}
|
||||
function renderCacheMix(mix) {
|
||||
function renderCacheMix(mix, totalRequests) {
|
||||
const names = ["hit", "miss", "bypass", "other"];
|
||||
const unavailable = t(totalRequests === 0 ? "cacheMixWaiting" : "cacheMixInvalid");
|
||||
names.forEach((name, i) => {
|
||||
const pct = mix ? mix.percentages[i] : 0;
|
||||
$(`cache-mix-${name}`).style.width = `${pct}%`;
|
||||
$(`cache-mix-${name}-count`).textContent = mix ? fmt(mix.counts[i]) : "-";
|
||||
$(`cache-mix-${name}-percent`).textContent = mix ? `${fmt(pct, 1)}%` : "-";
|
||||
});
|
||||
$("cache-mix-headline").textContent = mix ? `${fmt(mix.percentages[0], 1)}% HIT` : "-";
|
||||
$("cache-mix-caption").textContent = mix
|
||||
? t("cacheMixSample").replace("{count}", fmt(mix.requests))
|
||||
: t("cacheMixWaiting");
|
||||
: unavailable;
|
||||
$("cache-mix-bar").setAttribute("aria-label", mix
|
||||
? t("cacheMixAria").replace("{hit}", fmt(mix.counts[0]))
|
||||
.replace("{miss}", fmt(mix.counts[1]))
|
||||
.replace("{bypass}", fmt(mix.counts[2]))
|
||||
.replace("{other}", fmt(mix.counts[3]))
|
||||
: t("cacheMixWaiting"));
|
||||
: unavailable);
|
||||
}
|
||||
function renderDiagnostics(status, appendSample = true, systemSample = appendSample) {
|
||||
const d = status.system || {}, live = status.live_system || d, c = status.cache || {}, m = status.metrics || {};
|
||||
@@ -366,14 +369,8 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
|
||||
const cacheEvents = cacheHits !== null && cacheMisses !== null ? cacheHits + cacheMisses : 0;
|
||||
$("metric-cache-ratio").textContent = cacheEvents > 0 ? `${fmt(cacheHits / cacheEvents * 100, 1)}%` : "-";
|
||||
window.rustProxyExtraOverview?.donut?.("cache-ratio", cacheHits, cacheMisses);
|
||||
if (appendSample) {
|
||||
const counters = [m.requests, c.hits, c.misses, c.bypasses].map(value => value == null ? NaN : Number(value));
|
||||
const delta = rustProxyRateSampling.difference(cacheMixLastCounters, counters,
|
||||
performance.now(), rustProxyRateSampling.cadence("overview_seconds"));
|
||||
cacheMixLastCounters = counters.every(Number.isFinite) ? delta.next : null;
|
||||
cacheMixLastShares = delta.deltas ? cacheOutcomeMix(delta.deltas) : null;
|
||||
}
|
||||
renderCacheMix(cacheMixLastShares);
|
||||
renderCacheMix(cacheOutcomeMix(counters), counters[0]);
|
||||
if (systemSample) {
|
||||
if (d.process_rss_bytes != null) $("metric-rss").textContent = fmtBytes(d.process_rss_bytes);
|
||||
$("process-summary").hidden = d.process_threads == null;
|
||||
|
||||
@@ -32,23 +32,27 @@
|
||||
</div>
|
||||
</article>
|
||||
<article class="stat stat-cache-outcomes" data-dashboard-widget="cache_outcomes">
|
||||
<span data-i18n="cacheMixTitle">Cache outcomes (recent)</span>
|
||||
<span data-i18n="cacheMixTitle">Cache outcomes</span>
|
||||
<strong id="cache-mix-headline">-</strong>
|
||||
<div class="cache-mix-bar" id="cache-mix-bar" role="img" aria-label="Recent request cache outcomes">
|
||||
<div class="cache-mix-bar" id="cache-mix-bar" role="img"
|
||||
aria-label="Cumulative HTTP cache outcomes since service start">
|
||||
<span class="cache-mix-hit" id="cache-mix-hit" style="width:0%"></span>
|
||||
<span class="cache-mix-miss" id="cache-mix-miss" style="width:0%"></span>
|
||||
<span class="cache-mix-bypass" id="cache-mix-bypass" style="width:0%"></span>
|
||||
<span class="cache-mix-other" id="cache-mix-other" style="width:0%"></span>
|
||||
</div>
|
||||
<small class="cache-mix-legend">
|
||||
<span class="cache-mix-hit-key"><i></i>HIT <b id="cache-mix-hit-percent">-</b></span>
|
||||
<span class="cache-mix-miss-key"><i></i>MISS <b id="cache-mix-miss-percent">-</b></span>
|
||||
<span class="cache-mix-bypass-key"><i></i>BYPASS <b id="cache-mix-bypass-percent">-</b></span>
|
||||
<span class="cache-mix-other-key"><i></i><span data-i18n="cacheMixOther">OTHER</span> <b
|
||||
id="cache-mix-other-percent">-</b></span>
|
||||
<span class="cache-mix-hit-key"><i></i>HIT <b><span id="cache-mix-hit-count">-</span><span
|
||||
class="cache-mix-percent" id="cache-mix-hit-percent">-</span></b></span>
|
||||
<span class="cache-mix-miss-key"><i></i>MISS <b><span id="cache-mix-miss-count">-</span><span
|
||||
class="cache-mix-percent" id="cache-mix-miss-percent">-</span></b></span>
|
||||
<span class="cache-mix-bypass-key"><i></i>BYPASS <b><span id="cache-mix-bypass-count">-</span><span
|
||||
class="cache-mix-percent" id="cache-mix-bypass-percent">-</span></b></span>
|
||||
<span class="cache-mix-other-key"><i></i><span data-i18n="cacheMixOther">OTHER</span> <b><span
|
||||
id="cache-mix-other-count">-</span><span class="cache-mix-percent"
|
||||
id="cache-mix-other-percent">-</span></b></span>
|
||||
</small>
|
||||
<small class="cache-mix-caption" id="cache-mix-caption" data-i18n="cacheMixWaiting">Waiting for request
|
||||
samples</small>
|
||||
<small class="cache-mix-caption" id="cache-mix-caption" data-i18n="cacheMixWaiting">No HTTP requests yet</small>
|
||||
</article>
|
||||
<article class="stat" data-dashboard-widget="memory"><span data-i18n="processMemory">Process
|
||||
memory</span><strong id="metric-rss">-</strong><small id="process-summary">-</small>
|
||||
|
||||
@@ -12267,9 +12267,19 @@ html[data-dashboard-density] #panel-overview .dashboard-overview-grid>.card.pool
|
||||
}
|
||||
|
||||
#panel-overview .cache-mix-legend b {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
margin-left: auto;
|
||||
color: var(--text);
|
||||
font: 500 10px var(--mono);
|
||||
overflow-wrap: anywhere;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#panel-overview .cache-mix-legend .cache-mix-percent {
|
||||
color: var(--muted);
|
||||
font: 400 10px var(--mono);
|
||||
}
|
||||
|
||||
#panel-overview .stat-cache-outcomes .cache-mix-caption {
|
||||
|
||||
+52
-11
@@ -6,19 +6,60 @@ const path = require("node:path");
|
||||
const vm = require("node:vm");
|
||||
const root = path.join(__dirname, "..");
|
||||
const src = fs.readFileSync(path.join(root, "src/admin/web/app/runtime.js"), "utf8");
|
||||
const begin = src.indexOf("function cacheOutcomeMix(deltas) {");
|
||||
const end = src.indexOf("\nfunction renderCacheMix(mix) {", begin);
|
||||
assert.ok(begin >= 0 && end > begin, "production cacheOutcomeMix function exists");
|
||||
const mix = vm.runInNewContext(`${src.slice(begin, end)}; cacheOutcomeMix;`);
|
||||
const begin = src.indexOf("function cacheOutcomeMix(totals) {");
|
||||
const renderBegin = src.indexOf("\nfunction renderCacheMix(mix, totalRequests) {", begin);
|
||||
const end = src.indexOf("\nfunction renderDiagnostics(", renderBegin);
|
||||
assert.ok(begin >= 0 && renderBegin > begin && end > renderBegin, "production cumulative widget functions exist");
|
||||
const mix = vm.runInNewContext(`${src.slice(begin, renderBegin)}; cacheOutcomeMix;`);
|
||||
const result = mix([10, 4, 2, 3]);
|
||||
assert.deepEqual(Array.from(result.counts), [4, 2, 3, 1]);
|
||||
assert.deepEqual(Array.from(result.percentages), [40, 20, 30, 10]);
|
||||
assert.equal(result.requests, 10);
|
||||
assert.deepEqual(Array.from(mix([12, 0, 0, 0]).counts), [0, 0, 0, 12]);
|
||||
assert.deepEqual(Array.from(mix([9, 0, 0, 9]).counts), [0, 0, 9, 0]);
|
||||
for (const invalid of [[0, 0, 0, 0], [1, 2, 0, 0], [10, -1, 0, 0], [NaN, 0, 0, 0], [], null]) {
|
||||
assert.equal(mix(invalid), null, `invalid sample ${invalid} is not charted`);
|
||||
for (const invalid of [[0, 0, 0, 0], [1, 2, 0, 0], [10, -1, 0, 0], [NaN, 0, 0, 0],
|
||||
[10.1, 2, 0, 0], [Number.MAX_SAFE_INTEGER + 1, 0, 0, 0], [], null]) {
|
||||
assert.equal(mix(invalid), null, `invalid lifetime counters ${invalid} are not charted`);
|
||||
}
|
||||
// Ensure the panel uses all-time counters straight from the status payload, on every render.
|
||||
const diagnostic = src.slice(end, src.indexOf("\nfunction ", end + 1));
|
||||
assert.match(diagnostic, /const counters = \[m\.requests, c\.hits, c\.misses, c\.bypasses\]/);
|
||||
assert.match(diagnostic, /renderCacheMix\(cacheOutcomeMix\(counters\), counters\[0\]\);/);
|
||||
assert.doesNotMatch(src, /cacheMixLastCounters|cacheMixLastShares|cacheMixLastTotal/);
|
||||
const translations = ["en_US", "pl_pl"].map(language =>
|
||||
JSON.parse(fs.readFileSync(path.join(root, `langs/${language}.json`), "utf8")).translations);
|
||||
const nodes = new Map();
|
||||
const dom = id => {
|
||||
if (!nodes.has(id)) {
|
||||
const node = { style: {}, textContent: "", attributes: {}, setAttribute(name, value) { this.attributes[name] = value; } };
|
||||
nodes.set(id, node);
|
||||
}
|
||||
return nodes.get(id);
|
||||
};
|
||||
const english = translations[0];
|
||||
const render = vm.runInNewContext(`${src.slice(renderBegin + 1, end)}; renderCacheMix;`, {
|
||||
$: dom,
|
||||
t: key => english[key],
|
||||
fmt: (value, digits = 0) => Number(value).toFixed(digits),
|
||||
});
|
||||
render(result, 10);
|
||||
assert.equal(dom("cache-mix-headline").textContent, "40.0% HIT");
|
||||
assert.equal(dom("cache-mix-caption").textContent, "Since service start · 10 HTTP requests");
|
||||
for (const [name, count, percent] of [["hit", 4, 40], ["miss", 2, 20],
|
||||
["bypass", 3, 30], ["other", 1, 10]]) {
|
||||
assert.equal(dom(`cache-mix-${name}-count`).textContent, String(count));
|
||||
assert.equal(dom(`cache-mix-${name}-percent`).textContent, `${percent.toFixed(1)}%`);
|
||||
assert.equal(dom(`cache-mix-${name}`).style.width, `${percent}%`);
|
||||
}
|
||||
assert.match(dom("cache-mix-bar").attributes["aria-label"], /4 HIT, 2 MISS, 3 BYPASS, 1 OTHER/);
|
||||
render(null, 0);
|
||||
assert.equal(dom("cache-mix-caption").textContent, english.cacheMixWaiting);
|
||||
assert.equal(dom("cache-mix-hit-count").textContent, "-");
|
||||
assert.equal(dom("cache-mix-hit").style.width, "0%");
|
||||
render(null, 10);
|
||||
assert.equal(dom("cache-mix-caption").textContent, english.cacheMixInvalid);
|
||||
assert.equal(dom("cache-mix-bar").attributes["aria-label"], english.cacheMixInvalid);
|
||||
|
||||
const widgets = JSON.parse(fs.readFileSync(path.join(root, "src/admin/web/dashboard-widgets.json"), "utf8"));
|
||||
const defaults = JSON.parse(fs.readFileSync(path.join(root, "src/admin/web/dashboard-defaults.json"), "utf8"));
|
||||
assert.equal(widgets.filter(widget => widget.id === "cache_outcomes").length, 1);
|
||||
@@ -29,14 +70,14 @@ const html = fs.readFileSync(path.join(root, "src/admin/web/partials/overview-me
|
||||
assert.match(html, /data-dashboard-widget="cache_outcomes"/);
|
||||
for (const key of ["hit", "miss", "bypass", "other"]) {
|
||||
assert.match(html, new RegExp(`id="cache-mix-${key}"`));
|
||||
assert.match(html, new RegExp(`id="cache-mix-${key}-count"`));
|
||||
assert.match(html, new RegExp(`id="cache-mix-${key}-percent"`));
|
||||
}
|
||||
const strings = ["en_US", "pl_pl"].map(language =>
|
||||
JSON.parse(fs.readFileSync(path.join(root, `langs/${language}.json`), "utf8")).translations);
|
||||
for (const translation of strings) {
|
||||
for (const key of ["cacheMixTitle", "cacheMixHelp", "cacheMixOther", "cacheMixSample", "cacheMixWaiting", "cacheMixAria",
|
||||
for (const translation of translations) {
|
||||
for (const key of ["cacheMixTitle", "cacheMixHelp", "cacheMixOther", "cacheMixSample", "cacheMixWaiting", "cacheMixInvalid", "cacheMixAria",
|
||||
"h3_udp_receive_buffer_mib", "h3_udp_send_buffer_mib", "http3ApplyPolicy"]) {
|
||||
assert.ok(translation[key], `translation missing: ${key}`);
|
||||
}
|
||||
assert.doesNotMatch(translation.cacheMixTitle, /recent|ostatni pomiar/i);
|
||||
}
|
||||
console.log("Cache outcomes, manifest and locale regression checks passed.");
|
||||
console.log("Cumulative cache outcomes, manifest and locale regression checks passed.");
|
||||
Reference in new issue
Block a user