This commit is contained in:
Mateusz Gruszczyński committed 2026-10-15 16:15:49 +02:00
1 parent 5dd802ae83
commit a286355b0e
22 files changed
+447 -70

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes. Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes.
The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
compatibility. Current source version: 0.33.5. compatibility. Current source version: 0.33.6.
## Delivery ## Delivery
+23 -7
View File
@@ -3,15 +3,31 @@
> Operator-facing release history. Historical engineering notes are kept in > Operator-facing release history. Historical engineering notes are kept in
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md). > [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.33.6
- Normalize the height of all 14 large Overview/WWW plots to 125px, avoiding
width-dependent vertical stretching; fix quarter-grid alignment.
- Keep CPU at 0-100% and correct compact percentage sparklines to the same
absolute bounds. Never hide actual CPU readings in labels.
- Give transformation and compression activity, plus other unbounded rate,
count, latency and memory series, minimum zero-based ranges instead of
scaling near-zero readings to the plot ceiling. Extend only as necessary
using rounded ticks, and hold the ceiling until chart reset.
- Fix the full Cache/Transform rate plots at 180px across screen sizes.
Preserve actual /s values, updates and history resets.
- Add Chromium and helper-scale regressions for chart geometry and ranges.
No changes to status APIs, polling, database schema or account settings.
## 0.33.5 ## 0.33.5
- Autoscale the Overview **System and process CPU** time series using the - Fix the Overview **System and process CPU** axis at 0–100% so low
measured CPU history instead of the fixed 0–100% vertical range. Actual readings no longer expand to fill the chart. Clip plotted values above
CPU percentages in the summary and legend are unaffected. 100% without changing the actual percentages shown in the legend.
- Retain the same full, responsive plot height as other wide Overview charts. - Fix the CPU plot height at 125px across viewport widths and densities,
No other charts or dashboard sizing, data sources or settings are changed. independently of CPU readings. Other charts, their dimensions, data
- Add simulated Chromium checks for adaptive plot geometry, legend visibility sources, dashboard layout and settings are unchanged.
and responsive chart heights at desktop and mobile viewport sizes. - Check fixed percent mapping, clipping, legend visibility and plot geometry
with simulated Chromium at desktop and mobile viewport sizes.
- Overview heading cleanup: move the existing request-rate maximum (`max XX.X - Overview heading cleanup: move the existing request-rate maximum (`max XX.X
req/s`) from the chart footer to the upper-right Requests per second header; req/s`) from the chart footer to the upper-right Requests per second header;
remove its LIVE TRAFFIC eyebrow and LIVE badge. Remove the LIVE badge from remove its LIVE TRAFFIC eyebrow and LIVE badge. Remove the LIVE badge from
Generated
+1 -1
View File
@@ -1881,7 +1881,7 @@ dependencies = [
[[package]] [[package]]
name = "rust-proxy" name = "rust-proxy"
version = "0.33.5" version = "0.33.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "rust-proxy" name = "rust-proxy"
version = "0.33.5" version = "0.33.6"
edition = "2021" edition = "2021"
rust-version = "1.99" rust-version = "1.99"
description = "RustProxy - independent reverse proxy with local administration" description = "RustProxy - independent reverse proxy with local administration"
+8 -8
View File
@@ -1,13 +1,13 @@
# RustProxy 0.33.5 # RustProxy 0.33.6
## 0.33.5: adaptive CPU chart scaling on Overview ## 0.33.5: fixed CPU chart scale on Overview
- **System and process CPU** now uses the same adaptive vertical scale as other - **System and process CPU** uses a fixed 0–100% vertical scale, without
Overview time-series charts, instead of forcing a 0–100% axis. Low CPU autoscaling when measured CPU usage is low. Both lines and the legend retain
readings are visible while the legend continues to show their actual values. real CPU percentages; values above 100% are clipped at the top of the plot.
- Keep the CPU chart at the same responsive height as other wide Overview - Keep the CPU plot 125px high across viewport sizes and dashboard densities
charts (including HTTP protocol mix); do not shrink it or change any other so it does not stretch neighboring widgets. Other chart scales and heights,
charts, grid layout, sampling, APIs, databases or saved preferences. grid layout, sampling, APIs, databases and saved preferences are unchanged.
- Overview heading follow-up: **Requests per second** shows the existing - Overview heading follow-up: **Requests per second** shows the existing
dynamic `max XX.X req/s` value in the upper-right instead of the LIVE badge; dynamic `max XX.X req/s` value in the upper-right instead of the LIVE badge;
remove the redundant LIVE TRAFFIC eyebrow and its former peak footer label. remove the redundant LIVE TRAFFIC eyebrow and its former peak footer label.
+20 -7
View File
@@ -1,13 +1,26 @@
# RustProxy roadmap # RustProxy roadmap
## 0.33.5 - Adaptive Overview CPU chart scale ## 0.33.6 - Stable dashboard chart ranges and plot heights
- [x] Autoscale only the System and process CPU timeline using its measured - [x] Fix all 14 Overview/WWW full-width SVG heights to 125px and align their
history; retain actual percentage labels and both plotted series. grid lines with the actual drawing range.
- [x] Give the CPU plot the standard wide-chart height; leave other chart - [x] Apply zero-based, unit-specific scale floors to transform/compression,
scales, Overview cards and dashboard sizing behavior unchanged. rate, connection, latency, memory and WWW series; retain meaningful high
- [x] Add simulated real-browser checks for plot geometry and low/high CPU readings with stepped-up ceilings and no scale collapse on low samples.
samples across viewport widths and Dashboard densities. - [x] Keep CPU/other percentage sparklines on 0-100% without changing legends.
- [x] Fix full Cache/Transform live charts at 180px and stabilize their rate
axes, including scope-change resets.
- [x] Test Chromium layout/scaling across 320-1920px and run available UI,
schema and source tests. Rust runtime acceptance remains pending.
## 0.33.5 - Fixed Overview CPU chart scale
- [x] Set System and process CPU to an absolute 0–100% axis, independent of
history, while keeping real percentage labels and both plotted series.
- [x] Fix the CPU plot height at 125px regardless of viewport width or
measured values; leave other chart scales and sizes unchanged.
- [x] Check stable CPU chart geometry, percent mapping, clipping and labels
in a simulated browser across viewport widths and Dashboard densities.
- [x] Replace the Overview Requests per second LIVE badge with its live maximum - [x] Replace the Overview Requests per second LIVE badge with its live maximum
rate, remove the LIVE TRAFFIC eyebrow and the duplicate peak footer entry. rate, remove the LIVE TRAFFIC eyebrow and the duplicate peak footer entry.
- [x] Remove Connection Activity's LIVE badge, preserving its metrics. - [x] Remove Connection Activity's LIVE badge, preserving its metrics.
+8 -8
View File
@@ -2,12 +2,12 @@
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
ffae7ce20cc0fb1990394822515c933843ba39304aa44134403f0d25a6a66d9f AGENTS.md ffae7ce20cc0fb1990394822515c933843ba39304aa44134403f0d25a6a66d9f AGENTS.md
95f433de4f779002f6358209dbb178933eab2036f8e69dabda72c5946ef32716 CHANGELOG.md 8b72ed7b17505c129b60faf97ee6a838b855e4303dc390ea7196b0d97ee79134 CHANGELOG.md
a35a5d557bb57df37d130a03752ffc092f005cb4849733d3d81485fb9450a931 Cargo.lock a35a5d557bb57df37d130a03752ffc092f005cb4849733d3d81485fb9450a931 Cargo.lock
8b77b2beffb31dd14ff62e225ff5384084d2b4e66faa100aaf38470883fca879 Cargo.toml 8b77b2beffb31dd14ff62e225ff5384084d2b4e66faa100aaf38470883fca879 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile 25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
273fe8c9504f7565986b775850287fbc579a3de13aaffbfdd897a01ae64305fa README.md dfe12a6a0d360e0febec982f30098123a8974007c7c3fba39509be5c45d3ebbd README.md
de605453cec3511eae8b010af5791eb7d526fb09569f91c503b9dd53d0c2fe0e ROADMAP.md 65340bb1f93da256043cb6bd75b6c9de8539e31f0417f24c6f05093a27f8319f ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
@@ -64,7 +64,7 @@ ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md 007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
4dcf3243a13d9ae1827b5cefdbb9e27bd6310321c50d4d5934527d44437df6ef docs/SETTINGS.md 4dcf3243a13d9ae1827b5cefdbb9e27bd6310321c50d4d5934527d44437df6ef docs/SETTINGS.md
8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md 8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 docs/TEST_REPORT.md 72baf37ed0989de65123eba73f83d8c31cba7415382dd00e639d8ca412f0ef0d docs/TEST_REPORT.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md 751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md
@@ -132,7 +132,7 @@ e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/runtim
f0b48ec0cc4411755cc0b4128eebce710375afa99344cb76198cf31074f8eb63 scripts/test_stream_udp.py f0b48ec0cc4411755cc0b4128eebce710375afa99344cb76198cf31074f8eb63 scripts/test_stream_udp.py
cc7026ec9c5b69850a815816df093de0ffdf4aeadabb3af702783483122ede55 scripts/tests/dashboard_chart_headers_0335_browser_test.py cc7026ec9c5b69850a815816df093de0ffdf4aeadabb3af702783483122ede55 scripts/tests/dashboard_chart_headers_0335_browser_test.py
aa3ca7419d6d7d506c5aaf4d624d20cc7e8ee66bec3cb0cf9b5e7d164bcbab57 scripts/tests/dashboard_chart_width_0333_browser_test.py aa3ca7419d6d7d506c5aaf4d624d20cc7e8ee66bec3cb0cf9b5e7d164bcbab57 scripts/tests/dashboard_chart_width_0333_browser_test.py
3171c4e61c72e9ab12f41be7564fe68204aaa8bee7737b32ed6a2ca4e7a7ba94 scripts/tests/dashboard_cpu_autoscale_0335_browser_test.py fdcea6dd1e3bc160abe1139bf94b45c9a28f6aaf7afbe7e38fb452ee4892b8f1 scripts/tests/dashboard_cpu_fixed_scale_0335_browser_test.py
52eacb467df123dc4a35f14933e006b090c9238df1b14dcff856a16b37c8063f scripts/tests/dashboard_layout_0331_browser_test.py 52eacb467df123dc4a35f14933e006b090c9238df1b14dcff856a16b37c8063f scripts/tests/dashboard_layout_0331_browser_test.py
276e732fc2dfb0d39badb159e4fdcba0d7d73509c60ad5c9df49621c03046f1b scripts/tests/dashboard_metric_visuals_ui_test.mjs 276e732fc2dfb0d39badb159e4fdcba0d7d73509c60ad5c9df49621c03046f1b scripts/tests/dashboard_metric_visuals_ui_test.mjs
61a0fb2c4de86f25debec9662aedd923bce8c2ccd51e75593d485b2e0c9b8551 scripts/tests/dashboard_modal_0333_browser_test.py 61a0fb2c4de86f25debec9662aedd923bce8c2ccd51e75593d485b2e0c9b8551 scripts/tests/dashboard_modal_0333_browser_test.py
@@ -200,7 +200,7 @@ f3b566d79ba47848021a74653dbd7fec72f0fb819c6b2a07259e047bc8c427fd src/admin/web/
2f1669cbeb9f49aef43a548e2096825aa14c64c7097ffad520822bbbaefb2b4c src/admin/web/app/overview-extra-metrics.js 2f1669cbeb9f49aef43a548e2096825aa14c64c7097ffad520822bbbaefb2b4c src/admin/web/app/overview-extra-metrics.js
5e62cccf6a9507fe208e9f41bbd08453c3d8c6a6b83c56d93a2bb22550ea6e9a src/admin/web/app/overview-masonry.js 5e62cccf6a9507fe208e9f41bbd08453c3d8c6a6b83c56d93a2bb22550ea6e9a src/admin/web/app/overview-masonry.js
b52456b41451f156edf5f58492119c22726f2cf40cee938527748997e8ee7412 src/admin/web/app/overview-samples.js b52456b41451f156edf5f58492119c22726f2cf40cee938527748997e8ee7412 src/admin/web/app/overview-samples.js
9c039138fa58d8382601a4b2de366ba7763beb4d35f1cff7899cf2ce36c55afb src/admin/web/app/overview-wide-metrics.js 41d9d872ba2f8e1871564fa3f72e88bcb8f118c0ddd5255f256ac7230de945b6 src/admin/web/app/overview-wide-metrics.js
5bde5f0881320464eb133cd44de9eff5211e6816e79f7f7e9574b16784fab126 src/admin/web/app/preferences.js 5bde5f0881320464eb133cd44de9eff5211e6816e79f7f7e9574b16784fab126 src/admin/web/app/preferences.js
8e75b0915edfbadefc4388701431541f4d35fd5d717363297b63740b3afd34d2 src/admin/web/app/rate-sampling.js 8e75b0915edfbadefc4388701431541f4d35fd5d717363297b63740b3afd34d2 src/admin/web/app/rate-sampling.js
43f4dddd1c3ea7fa42eeb9d7b655a8db48b5ddcc6c68213e08dfc2fcfbe8b17d src/admin/web/app/refresh.js 43f4dddd1c3ea7fa42eeb9d7b655a8db48b5ddcc6c68213e08dfc2fcfbe8b17d src/admin/web/app/refresh.js
@@ -234,7 +234,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js 7cbdef9192a9dee1f8eac42b85f52b3190f7f6efae50004aa7ad116a910e4036 src/admin/web/file-logs.js
b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js b14e7b3578322f309627f6fe965cc16abcf85da3437c9070be6caecae1908743 src/admin/web/host404.js
840c6ef69b9db1d8c4b710ad0eadb4253ac729bd05a7f60e3bfaf41640dc57a3 src/admin/web/index.html 0ebfde7081385aa7158f3b0f8f2b9355836e087c3d91a7c98d5187afbd47abd0 src/admin/web/index.html
03ad9572bb14c34059636d612cf6b876d6361ce601817cff674c57b715dc65dd src/admin/web/integrations.js 03ad9572bb14c34059636d612cf6b876d6361ce601817cff674c57b715dc65dd src/admin/web/integrations.js
7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json 7a12f020fc587205694978ab41b660e9a80487d413fbf6e34fc525eabc58308f src/admin/web/login-keys.json
cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html cbd41678eadd994dc5252628ee8ef9551fa9f7d8694ca5abd206240d9a96ad30 src/admin/web/login.html
@@ -244,7 +244,7 @@ c2e5f554810b8e34fb47361fb90a01073a2e332ee26d82e030956fad3ec1f83e src/admin/web/
c28f4254ddc1f720d62646ee0f0b590b60a89c0bf447e8ec74ff3dde0c7635ba src/admin/web/settings.js c28f4254ddc1f720d62646ee0f0b590b60a89c0bf447e8ec74ff3dde0c7635ba src/admin/web/settings.js
8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html 8eadbada619b15087783dcfa0e889a81f88e78034d084c5ab616f7912b835940 src/admin/web/status_200.html
c13cffe18a616fe5faa5ee945ab0d94d8d392ae8f32474c62fe41010e063ddb1 src/admin/web/streams.js c13cffe18a616fe5faa5ee945ab0d94d8d392ae8f32474c62fe41010e063ddb1 src/admin/web/streams.js
7ffd017d38297a21160fd61926bb1d406b465e8fe610db9164234220dd8b111a src/admin/web/style.css cc0fdbed4a344487fef7593375177043ee4524b98f58842f37a97e2d2a414183 src/admin/web/style.css
d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js d290c048450655b3eeae7c6ec54a82d645d1965513694ab29fdf7eef796a1abd src/admin/web/traffic-panel.js
69f412bd897e45f4889b71e9982cfff56df9ef20ba2da109b454713a5ecd7c74 src/admin/web/transform-panel.js 69f412bd897e45f4889b71e9982cfff56df9ef20ba2da109b454713a5ecd7c74 src/admin/web/transform-panel.js
d816f2f70eef398c18091479320c36d07f9afe07278a250b840ebcdd7bc715f2 src/admin/web/vhosts/core.js d816f2f70eef398c18091479320c36d07f9afe07278a250b840ebcdd7bc715f2 src/admin/web/vhosts/core.js
+44
View File
@@ -0,0 +1,44 @@
# RustProxy 0.33.6 - verification report
## Scope (2026-10-15)
Additional 0.33.6 chart/UI changes:
- All 14 full-width Overview and Web Server plots retain a fixed 125px SVG
height, regardless of viewport width and dashboard density.
- Grid lines are mapped to the actual plotting range rather than approximate
horizontal positions.
- Transform and Compression activity (plus other unbounded unit series) use
sane zero-based minimum ranges and monotonic, rounded range expansion, so
low values do not look like maximum utilization and spikes remain visible.
- Full Cache and Transform live charts keep an 180px plot height and the same
stable per-second range policy. Compact percent sparklines stay 0-100%.
Previous 0.33.5 regression scope:
Correct the Overview **System and process CPU** widget:
- Use a fixed 0-100% vertical range, not the measured-history peak.
- Map 25%, 50% and 75% to the horizontal grid lines.
- Clip plotted CPU values above 100% at the top without modifying their measured values in the legend or summary.
- Fix only this plot's CSS height at 125px, independent of viewport width, dashboard density and CPU samples.
- Leave other chart scales, plot dimensions, status sampling and dashboard preferences unchanged.
## Checks completed
- **PASS:** `python scripts/tests/dashboard_stable_scales_0336_browser_test.py`
with real bundled HTML/CSS/JS in simulated Chromium at 320, 375, 768,
1440 and 1920px and all three density modes. Covered 14 large plots,
125/180px plot heights, quarter-grid alignment, 0-100% CPU mapping,
steady low/high traffic plotting, minimum count/rate/latency/memory scales,
sticky expanded scales, reset and non-clipped live operation-rate labels.
- **PASS:** `python scripts/tests/dashboard_cpu_fixed_scale_0335_browser_test.py` against the actual bundled HTML/CSS/JS in simulated Chromium, at 320, 375, 768, 1440 and 1920px, in normal/compact/ultra densities. Checked low and high CPU histories, missing samples, new status revisions, clipping above 100%, actual percentage labels, consistent grid positions, fixed plot height and no sample-dependent changes to the CPU/adjacent Connection Activity cards' heights.
- **PASS:** All 11 prior `scripts/tests/*browser_test.py` simulated-browser regressions (including the 0.33.5 CPU test) and the new 0.33.6 browser regression: chart headers, resized widths, dashboard grid/modal resizing, sidebar, progress bars, System RAM, Web observability and Web Server metrics. No live RustProxy server was used.
- **PASS:** `node scripts/tests/overview_samples_0330_ui_test.mjs`, `node scripts/tests/dashboard_resume_rate_charts_ui_test.mjs` and `node scripts/tests/dashboard_metric_visuals_ui_test.mjs`.
- **PASS:** `./dev.sh doctor` and `python scripts/preflight.py` (source layout).
- **PASS:** All eight `migrations/*.sql` applied successfully in two successive cycles to a disposable, in-memory SQLite database. This verifies SQL script execution/idempotency in this context, not an upgrade of production data.
- **PASS:** Parsed `scripts/deploy/upgrade.py --help` and `scripts/migration/migrate_npm.py --help`; did not run a live upgrade or migration against an external database or service.
## Not verified in this environment
- `./dev.sh check` and `./dev.sh test` both exit 127: Cargo/rustc are unavailable. Therefore no Rust compilation, unit/integration suite, actual proxy runtime, real SSE feed, production database upgrade or live load test has been verified.
- The real-runtime acceptance gate remains open until Rust builds and runs against representative test traffic.
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""0.33.5 fixed 0-100% CPU plot and height regression with production HTML/CSS/JS.
Uses Chromium with simulated authenticated DOM and status samples, not a live
RustProxy server, SSE stream or real CPU telemetry.
"""
from pathlib import Path
import re
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
HTML = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
CSS = (ROOT / 'src/admin/web/style.css').read_text(encoding='utf-8')
SCALE_JS = (ROOT / 'src/admin/web/app/chart-scales.js').read_text(encoding='utf-8')
WIDE_JS = (ROOT / 'src/admin/web/app/overview-wide-metrics.js').read_text(encoding='utf-8')
def inspect(page):
return page.evaluate('''() => {
const get = id => document.querySelector(`#dashboard-overview-grid [data-dashboard-widget="${id}"]`);
const cpu = get('wide_cpu'), cpuSvg = cpu.querySelector('svg');
const protocolSvg = get('wide_protocol_rates').querySelector('svg');
const connection = get('connection_chart');
const backendSvg = get('backend_traffic').querySelector('svg');
const legend = cpu.querySelector('.overview-wide-legend').getBoundingClientRect();
const bounds = cpu.getBoundingClientRect();
const matrix = cpuSvg.getScreenCTM();
return {
cpuHeight: cpuSvg.getBoundingClientRect().height,
cpuCardHeight: bounds.height,
connectionCardHeight: connection.getBoundingClientRect().height,
protocolHeight: protocolSvg.getBoundingClientRect().height,
backendHeight: backendSvg.getBoundingClientRect().height,
svgWidth: cpuSvg.getBoundingClientRect().width,
drawnWidth: Math.hypot(matrix.a, matrix.b) * cpuSvg.viewBox.baseVal.width,
ratio: cpuSvg.getAttribute('preserveAspectRatio'),
cpuSeries: cpuSvg.querySelectorAll('.overview-wide-series').length,
lineEffect: getComputedStyle(cpuSvg.querySelector('.overview-wide-series')).vectorEffect,
gridEffect: getComputedStyle(cpuSvg.querySelector('.chart-grid')).vectorEffect,
gridPath: cpuSvg.querySelector('.chart-grid').getAttribute('d'),
legendInCard: legend.top >= bounds.top && legend.bottom <= bounds.bottom + 1,
overflow: document.documentElement.scrollWidth - innerWidth,
};
}''')
def render_cpu(page, revision, system, process):
return page.evaluate('''({revision,system,process}) => {
window.rustProxyWideOverview.render({revision,live_system:{
cpu_percent:system, process_cpu_percent:process
}});
const read = name => {
const prefix = `overview-wide-wide_cpu-${name}`;
return {
path: document.getElementById(`${prefix}-line`).getAttribute('d'),
label: document.getElementById(`${prefix}-value`).textContent,
};
};
return {system:read('system'),process:read('process')};
}''', {'revision': revision, 'system': system, 'process': process})
def ys(path):
return [int(v) for v in re.findall(r'[ML]\d+\s+(\d+)', path)]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
for viewport in (320, 375, 768, 1440, 1920):
page = browser.new_page(viewport={'width': viewport, 'height': 950})
errors = []
page.on('pageerror', lambda error: errors.append(str(error)))
page.set_content(HTML.replace('/assets/app___RUSTPROXY_ASSET_ID__.js', '')
.replace('/assets/style___RUSTPROXY_ASSET_ID__.css', ''))
page.add_style_tag(content=CSS)
page.evaluate('''() => {
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
document.getElementById('workspace').hidden = false;
document.getElementById('panel-overview').hidden = false;
const grid = document.getElementById('dashboard-overview-grid');
grid.classList.add('is-aligned');
for (const card of grid.querySelectorAll(':scope > [data-dashboard-widget]')) {
card.hidden = !['wide_cpu', 'wide_protocol_rates', 'backend_traffic', 'connection_chart'].includes(card.dataset.dashboardWidget);
}
window.currentPanel = 'overview';
window.$ = id => document.getElementById(id);
window.fmt = (value, digits = 0) => Number(value).toFixed(digits);
window.fmtBytes = value => `${value} B`;
window.rustProxyOverviewSamples = {getLimit: () => 60, onChange: () => {}};
}''')
page.add_script_tag(content=SCALE_JS)
page.add_script_tag(content=WIDE_JS)
for density in ('compact', 'normal', 'ultra'):
page.evaluate('(density) => document.documentElement.dataset.dashboardDensity = density', density)
result = inspect(page)
assert result['protocolHeight'] >= 124, (viewport, density, result)
assert abs(result['cpuHeight'] - 125) <= 1, (
'CPU height must remain fixed at 125px', viewport, density, result)
assert result['backendHeight'] > 0, (viewport, density, result)
assert result['ratio'] == 'none' and result['cpuSeries'] == 2, result
assert result['lineEffect'] == result['gridEffect'] == 'non-scaling-stroke', result
assert result['gridPath'] == 'M0 47H600 M0 77H600 M0 107H600', result
assert result['svgWidth'] > 100 and abs(result['svgWidth'] - result['drawnWidth']) < 2, result
assert result['legendInCard'] and result['overflow'] <= 2, (viewport, density, result)
# Low measured CPU values stay near zero instead of filling the plot.
first = render_cpu(page, 1, 0.5, 0.2)
second = render_cpu(page, 1, 0.8, 0.6)
assert first['system']['label'] == '0.5%' and second['process']['label'] == '0.6%'
assert ys(second['system']['path']) == [136, 136], (viewport, second)
assert ys(second['process']['path']) == [137, 136], (viewport, second)
# The Y axis is always 0-100% regardless of recorded history.
third = render_cpu(page, 1, 3.4, 2.2)
assert ys(third['system']['path'])[-1] == 133, (viewport, third)
assert ys(third['process']['path'])[-1] == 134, (viewport, third)
high = render_cpu(page, 1, 250, 123)
assert high['system']['label'] == '250.0%' and high['process']['label'] == '123.0%'
assert ys(high['system']['path'])[-1] == 17, (viewport, high)
assert ys(high['process']['path'])[-1] == 17, (viewport, high)
mid = render_cpu(page, 1, 50, 25)
assert ys(mid['system']['path'])[-1] == 77, (viewport, mid)
assert ys(mid['process']['path'])[-1] == 107, (viewport, mid)
# Revision changes reset history; missing readings do not plot a zero.
idle = render_cpu(page, 2, 0, 0)
assert ys(idle['system']['path']) == [137] and ys(idle['process']['path']) == [137]
full = render_cpu(page, 2, 100, 100)
assert ys(full['system']['path'])[-1] == 17 and ys(full['process']['path'])[-1] == 17
missing = render_cpu(page, 3, None, None)
assert missing['system']['path'] == '' and missing['process']['label'] == '-'
# Samples may change paths, but not the plot/card or neighboring card sizes.
for density in ('compact', 'normal', 'ultra'):
page.evaluate('(density) => document.documentElement.dataset.dashboardDensity = density', density)
unchanged = inspect(page)
assert abs(unchanged['cpuHeight'] - 125) <= 1, (viewport, density, unchanged)
for value in (0.1, 99.9, 250, 0.0):
before = inspect(page)
render_cpu(page, 4, value, value)
after = inspect(page)
for key in ('cpuHeight', 'cpuCardHeight', 'connectionCardHeight'):
assert abs(before[key] - after[key]) < 1, (viewport, density, value, key, before, after)
assert not errors, (viewport, errors)
print(f'PASS CPU fixed 0-100% Y-scale and 125px plot height at {viewport}px (all densities)')
page.close()
browser.close()
@@ -39,7 +39,7 @@ const context = vm.createContext({
t: key => lang[key] || key, t: key => lang[key] || key,
console, console,
}); });
for (const script of ['app/rate-sampling.js', 'app/overview-extra-metrics.js', 'app/overview-wide-metrics.js']) { for (const script of ['app/rate-sampling.js', 'app/chart-scales.js', 'app/overview-extra-metrics.js', 'app/overview-wide-metrics.js']) {
vm.runInContext(code(script), context, { filename: script }); vm.runInContext(code(script), context, { filename: script });
} }
const extra = window.rustProxyExtraOverview, wide = window.rustProxyWideOverview; const extra = window.rustProxyExtraOverview, wide = window.rustProxyWideOverview;
@@ -42,7 +42,7 @@ const context = vm.createContext({
$: id => ids.get(id) || null, fmt: (value, n = 0) => Number(value).toFixed(n), $: id => ids.get(id) || null, fmt: (value, n = 0) => Number(value).toFixed(n),
fmtBytes: value => `${value} B`, t: value => value, fmtBytes: value => `${value} B`, t: value => value,
}); });
for (const file of ['app/rate-sampling.js', 'app/overview-wide-metrics.js', 'app/web-overview.js']) for (const file of ['app/rate-sampling.js', 'app/chart-scales.js', 'app/overview-wide-metrics.js', 'app/web-overview.js'])
vm.runInContext(code(file), context, { filename: file }); vm.runInContext(code(file), context, { filename: file });
const web = { const web = {
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""0.33.6 plot size/scale checks in simulated Chromium, not a running RustProxy."""
from pathlib import Path
import re
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
HTML = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
CSS = (ROOT / 'src/admin/web/style.css').read_text(encoding='utf-8')
SCALES = (ROOT / 'src/admin/web/app/chart-scales.js').read_text(encoding='utf-8')
RATES = (ROOT / 'src/admin/web/app/rate-sampling.js').read_text(encoding='utf-8')
WIDE = (ROOT / 'src/admin/web/app/overview-wide-metrics.js').read_text(encoding='utf-8')
WIDE_IDS = ('wide_protocol_rates', 'wide_cpu', 'wide_memory', 'wide_cache_activity',
'wide_connections', 'wide_backend_health', 'wide_latency',
'wide_transform_activity', 'wide_compression_activity',
'www_requests', 'www_latency', 'www_failures', 'www_traffic', 'www_status_trend')
with sync_playwright() as api:
browser = api.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
for width in (320, 375, 768, 1440, 1920):
page = browser.new_page(viewport={'width': width, 'height': 920})
errors = []
page.on('pageerror', lambda error: errors.append(str(error)))
page.set_content(HTML.replace('/assets/app___RUSTPROXY_ASSET_ID__.js', '')
.replace('/assets/style___RUSTPROXY_ASSET_ID__.css', ''))
page.add_style_tag(content=CSS)
page.evaluate('''() => {
document.documentElement.classList.remove('app-loading','app-startup-failed');
document.getElementById('workspace').hidden = false;
document.getElementById('panel-overview').hidden = false;
document.getElementById('dashboard-overview-grid').classList.add('is-aligned');
for (const card of document.querySelectorAll('#dashboard-overview-grid>[data-dashboard-widget]'))
card.hidden = !['wide_protocol_rates','wide_cpu','wide_memory','wide_cache_activity',
'wide_connections','wide_backend_health','wide_latency',
'wide_transform_activity','wide_compression_activity',
'www_requests','www_latency','www_failures','www_traffic','www_status_trend'].includes(card.dataset.dashboardWidget);
window.currentPanel='overview';
window.$ = id => document.getElementById(id);
window.fmt = (n, digits=0) => Number(n).toFixed(digits);
window.fmtBytes = n => `${n} B`;
window.rustProxyOverviewSamples = {getLimit:()=>60,onChange:()=>{}};
window.rustProxyRefresh = {current:()=>({overview_seconds:3})};
window.__clock = 1000;
Object.defineProperty(performance,'now',{configurable:true,value:()=>window.__clock});
}''')
for script in (RATES, SCALES, WIDE):
page.add_script_tag(content=script)
for density in ('normal','compact','ultra'):
measured = page.evaluate('''density => {
document.documentElement.dataset.dashboardDensity=density;
const results={};
for (const card of document.querySelectorAll('#dashboard-overview-grid > .overview-wide-chart')) {
const svg=card.querySelector('svg.overview-wide-svg');
if (!svg) continue;
const r=svg.getBoundingClientRect();
results[card.dataset.dashboardWidget]={height:r.height, width:r.width,
grid:svg.querySelector('.chart-grid').getAttribute('d'),
contentWidth:Math.hypot(svg.getScreenCTM().a,svg.getScreenCTM().b)*600};
}
document.getElementById('panel-transform').hidden=false;
document.getElementById('panel-cache').hidden=false;
for(const id of ['transform-live-chart','cache-live-chart'])document.getElementById(id).open=true;
results.transformDetails=document.getElementById('transform-live-activity-chart').getBoundingClientRect().height;
results.cacheDetails=document.getElementById('cache-live-activity-chart').getBoundingClientRect().height;
return results;
}''', density)
assert abs(measured['transformDetails'] - 180) < 1, (width,density,measured)
assert abs(measured['cacheDetails'] - 180) < 1, (width,density,measured)
for name in WIDE_IDS:
data=measured[name]
assert abs(data['height']-125)<1, (width,density,name,data)
assert data['width']>50 and abs(data['width']-data['contentWidth'])<2, (width,density,name,data)
assert data['grid']=='M0 47H600 M0 77H600 M0 107H600', (width,density,name,data)
result = page.evaluate('''() => {
const c=window.rustProxyChartScale;
const first=c.peak('probe:rate','rate',[[.2,.7]]);
const count=c.peak('probe:count','count',[[0,1]]);
const latency=c.peak('probe:ms','ms',[[.1,1.5]]);
const memory=c.peak('probe:bytes','bytes',[[1000]]);
const high=c.peak('probe:rate','rate',[[101]]);
const held=c.peak('probe:rate','rate',[[.3]]);
c.reset('probe:');
const reset=c.peak('probe:rate','rate',[[.3]]);
const percent=c.peak('probe:cpu','percent',[[.1,500]]);
const blank=c.peak('probe:blank','rate',[[null,NaN,Infinity]]);
const before={revision:10,live_system:{cpu_percent:0.7,process_cpu_percent:0.2},
metrics:{compression_gzip:50,compression_br:100,compression_zstd:10,compression_deflate:5},
transform_metrics:{applied:2,cache_hits:10,skipped:1,errors:0}};
window.rustProxyWideOverview.render(before);
window.__clock=4000;
before.metrics.compression_gzip+=3;before.metrics.compression_br+=6;
before.metrics.compression_zstd+=0;before.metrics.compression_deflate+=0;
before.transform_metrics.applied+=6;before.transform_metrics.cache_hits+=3;
window.rustProxyWideOverview.render(before);
const get = (id,key) => document.getElementById(`overview-wide-${id}-${key}-line`).getAttribute('d');
const compressionLow=get('wide_compression_activity','gzip');
const transformLow=get('wide_transform_activity','applied');
const cpu=get('wide_cpu','system');
const lowLabel=document.getElementById('overview-wide-wide_compression_activity-gzip-value').textContent;
window.__clock=7000;
before.metrics.compression_gzip+=603; // now 201/s => ceiling expands to 500/s
before.transform_metrics.applied+=603;
window.rustProxyWideOverview.render(before);
const highLabel=document.getElementById('overview-wide-wide_compression_activity-gzip-value').textContent;
const compressionHigh=get('wide_compression_activity','gzip');
window.__clock=10000;
before.metrics.compression_gzip+=3;
before.transform_metrics.applied+=3;
window.rustProxyWideOverview.render(before);
const afterSpike=get('wide_compression_activity','gzip');
return {first,count,latency,memory,high,held,reset,percent,blank,
lowLabel,highLabel,compressionLow,transformLow,cpu,compressionHigh,afterSpike};
}''')
assert result['first']==100 and result['count']==10 and result['latency']==100, (width,result)
assert result['memory']>=64*1024*1024, (width,result)
assert (result['high'],result['held'],result['reset'],result['percent'],result['blank']) == (200,200,100,100,100), (width,result)
assert result['lowLabel']=='1.0/s' and result['highLabel']=='201.0/s', (width,result)
def yvalues(line): return [int(n) for n in re.findall(r'[ML]\d+ (\d+)',line)]
assert yvalues(result['compressionLow'])[-1]>=135, (width,result)
assert yvalues(result['transformLow'])[-1]>=130, (width,result)
assert yvalues(result['cpu'])==[136,136], (width,result)
assert yvalues(result['compressionHigh'])[-1]<100, (width,result)
assert yvalues(result['afterSpike'])[-1]>=135, (width,result)
assert not errors, (width,errors)
print('PASS all 14 overview-wide plot sizes/grids + full Cache/Transform charts + stable scales:', width)
page.close()
browser.close()
@@ -93,6 +93,7 @@ assert.equal(again.window.rustProxyOverviewSamples.getLimit(), 300);
// Wide Overview charts must respect the same limit and redraw immediately when // Wide Overview charts must respect the same limit and redraw immediately when
// the limit shrinks, without recording an extra point or hitting the network. // the limit shrinks, without recording an extra point or hitting the network.
vm.runInContext(read('src/admin/web/app/rate-sampling.js'), context); vm.runInContext(read('src/admin/web/app/rate-sampling.js'), context);
vm.runInContext(read('src/admin/web/app/chart-scales.js'), context);
vm.runInContext(read('src/admin/web/app/overview-wide-metrics.js'), context); vm.runInContext(read('src/admin/web/app/overview-wide-metrics.js'), context);
select.value = '600'; select.fire('change'); select.value = '600'; select.fire('change');
const status = { const status = {
+1
View File
@@ -64,6 +64,7 @@ fn admin_app_javascript() -> &'static str {
include_str!("web/app/session-resume.js"), include_str!("web/app/session-resume.js"),
include_str!("web/app/refresh.js"), include_str!("web/app/refresh.js"),
include_str!("web/app/rate-sampling.js"), include_str!("web/app/rate-sampling.js"),
include_str!("web/app/chart-scales.js"),
"const ADMIN_DASHBOARD_WIDGET_REGISTRY = Object.freeze(", "const ADMIN_DASHBOARD_WIDGET_REGISTRY = Object.freeze(",
include_str!("web/dashboard-widgets.json"), include_str!("web/dashboard-widgets.json"),
");", ");",
+24
View File
@@ -0,0 +1,24 @@
// Stable plot ranges: percentages are absolute 0-100; unbounded metrics have
// unit-appropriate zero-based floors, expanding only for real higher readings.
window.rustProxyChartScale = (() => {
const floors = { rate: 100, count: 10, ms: 100, bytes: 64 * 1024 * 1024, "bytes/s": 64 * 1024 };
const ceilings = new Map();
function peak(key, unit, series) {
if (unit === "percent") return 100;
const floor = floors[unit] ?? 1;
let maximum = floor;
for (const values of series) for (const value of values) {
if (typeof value === "number" && Number.isFinite(value) && value >= 0)
maximum = Math.max(maximum, value);
}
const magnitude = 10 ** Math.floor(Math.log10(maximum));
const tick = [1, 2, 5, 10].find(n => n * magnitude >= maximum) ?? 10;
const result = Math.max(ceilings.get(key) ?? 0, tick * magnitude);
ceilings.set(key, result);
return result;
}
function reset(prefix) {
for (const key of ceilings.keys()) if (key.startsWith(prefix)) ceilings.delete(key);
}
return { peak, reset };
})();
+6 -5
View File
@@ -134,10 +134,10 @@ window.rustProxyExtraOverview = (() => {
if (unit === "rate") return `${fmt(value, 1)}/s`; if (unit === "rate") return `${fmt(value, 1)}/s`;
return fmt(value); return fmt(value);
} }
function points(history, unit) { function points(id, history, unit) {
if (!history.length) return "0,55 300,55"; if (!history.length) return "0,55 300,55";
const peak = unit === "percent" ? Math.max(100, ...history) : Math.max(1, ...history); const peak = window.rustProxyChartScale.peak(`extra:${id}`, unit, [history]);
return history.map((v, i) => `${i * 300 / Math.max(1, history.length - 1)},${55 - v / peak * 50}`).join(" "); return history.map((v, i) => `${i * 300 / Math.max(1, history.length - 1)},${55 - Math.min(v, peak) / peak * 50}`).join(" ");
} }
function render(status, record = true) { function render(status, record = true) {
if (!status || currentPanel !== "overview") return; if (!status || currentPanel !== "overview") return;
@@ -150,6 +150,7 @@ window.rustProxyExtraOverview = (() => {
const value = metric.value; const value = metric.value;
if (lastUnits.get(id) !== metric.unit) { if (lastUnits.get(id) !== metric.unit) {
histories.delete(id); histories.delete(id);
window.rustProxyChartScale.reset(`extra:${id}`);
lastUnits.set(id, metric.unit); lastUnits.set(id, metric.unit);
} }
if (!bounded.has(id) && id !== "extra_cache_hit_rate") { if (!bounded.has(id) && id !== "extra_cache_hit_rate") {
@@ -159,7 +160,7 @@ window.rustProxyExtraOverview = (() => {
if (history.length > limit()) history.shift(); if (history.length > limit()) history.shift();
histories.set(id, history); histories.set(id, history);
} }
$(`overview-extra-${id}-line`)?.setAttribute("points", points(history, metric.unit)); $(`overview-extra-${id}-line`)?.setAttribute("points", points(id, history, metric.unit));
} }
if (record) { if (record) {
lastValue.set(id, value); lastValue.set(id, value);
@@ -177,7 +178,7 @@ window.rustProxyExtraOverview = (() => {
if (detail && id !== "extra_cache_hit_rate") detail.textContent = metric.detail || t("overviewExtraRecentSamples"); if (detail && id !== "extra_cache_hit_rate") detail.textContent = metric.detail || t("overviewExtraRecentSamples");
} }
} }
function reset() { histories.clear(); lastCounter.clear(); lastValue.clear(); lastUnits.clear(); lastSegments.clear(); } function reset() { histories.clear(); lastCounter.clear(); lastValue.clear(); lastUnits.clear(); lastSegments.clear(); window.rustProxyChartScale.reset("extra:"); }
window.rustProxyOverviewSamples?.onChange?.(() => { window.rustProxyOverviewSamples?.onChange?.(() => {
for (const history of histories.values()) window.rustProxyOverviewSamples.trim(history); for (const history of histories.values()) window.rustProxyOverviewSamples.trim(history);
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false); if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false);
+5 -4
View File
@@ -6,7 +6,7 @@ window.rustProxyWideOverview = (() => {
[s.metrics?.http1_requests, s.metrics?.http2_requests, s.metrics?.http3_requests] [s.metrics?.http1_requests, s.metrics?.http2_requests, s.metrics?.http3_requests]
}, },
wide_cpu: { wide_cpu: {
unit: "percent", names: ["system", "process"], values: s => unit: "percent", names: ["system", "process"], max: 100, values: s =>
[(s.live_system || s.system)?.cpu_percent, (s.live_system || s.system)?.process_cpu_percent] [(s.live_system || s.system)?.cpu_percent, (s.live_system || s.system)?.process_cpu_percent]
}, },
wide_memory: { wide_memory: {
@@ -72,7 +72,8 @@ window.rustProxyWideOverview = (() => {
const value = values[i]; const value = values[i];
if (value === null) { drawing = false; continue; } if (value === null) { drawing = false; continue; }
const x = Math.round(600 * i / Math.max(1, count - 1)); const x = Math.round(600 * i / Math.max(1, count - 1));
const y = Math.round(137 - 120 * value / peak); // Fixed-range charts stop at the upper bound; the legend keeps the real reading.
const y = Math.round(137 - 120 * Math.min(value, peak) / peak);
result += `${drawing ? " L" : " M"}${x} ${y}`; result += `${drawing ? " L" : " M"}${x} ${y}`;
drawing = true; drawing = true;
} }
@@ -98,7 +99,7 @@ window.rustProxyWideOverview = (() => {
} }
last.set(id, values); last.set(id, values);
} }
const peak = Math.max(1, ...series.flat().filter(n => n !== null)); const peak = config.max ?? window.rustProxyChartScale.peak(`wide:${id}`, config.unit, series);
config.names.forEach((key, i) => { config.names.forEach((key, i) => {
const value = values[i]; const value = values[i];
const field = $(`overview-wide-${id}-${key}-value`); const field = $(`overview-wide-${id}-${key}-value`);
@@ -111,7 +112,7 @@ window.rustProxyWideOverview = (() => {
: format(values[0], config.unit); : format(values[0], config.unit);
} }
} }
function reset() { history.clear(); baselines.clear(); last.clear(); revision = null; } function reset() { history.clear(); baselines.clear(); last.clear(); revision = null; window.rustProxyChartScale.reset("wide:"); }
window.rustProxyOverviewSamples?.onChange?.(() => { window.rustProxyOverviewSamples?.onChange?.(() => {
for (const series of history.values()) for (const values of series) window.rustProxyOverviewSamples.trim(values); for (const series of history.values()) for (const values of series) window.rustProxyOverviewSamples.trim(values);
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false); if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false);
+4 -3
View File
@@ -87,7 +87,7 @@ window.rustProxyWebOverview = (() => {
return output; return output;
} }
function smallPath(values) { function smallPath(values) {
const peak = Math.max(1, ...values.filter(v => v != null)); const peak = window.rustProxyChartScale.peak("web:php-small", "rate", [values]);
return values.filter(value => value !== null).map((value, i, present) => `${i * 300 / Math.max(1, present.length - 1)},${55 - value / peak * 50}`).join(" "); return values.filter(value => value !== null).map((value, i, present) => `${i * 300 / Math.max(1, present.length - 1)},${55 - value / peak * 50}`).join(" ");
} }
function ring(id, amounts) { function ring(id, amounts) {
@@ -173,7 +173,8 @@ window.rustProxyWebOverview = (() => {
names.forEach((_, i) => { series[i].push(set[id]?.[i] ?? null); window.rustProxyOverviewSamples.trim(series[i]); }); names.forEach((_, i) => { series[i].push(set[id]?.[i] ?? null); window.rustProxyOverviewSamples.trim(series[i]); });
histories.set(id, series); histories.set(id, series);
} }
const peak = Math.max(1, ...series.flat().filter(v => v !== null)); const unit = id === "www_latency" ? "ms" : id === "www_traffic" ? "bytes/s" : "rate";
const peak = window.rustProxyChartScale.peak(`web:${id}`, unit, series);
names.forEach((name, i) => { names.forEach((name, i) => {
const value = set[id]?.[i]; const value = set[id]?.[i];
const label = id === "www_latency" ? (value == null ? "-" : `${fmt(value, 1)} ms`) const label = id === "www_latency" ? (value == null ? "-" : `${fmt(value, 1)} ms`)
@@ -216,7 +217,7 @@ window.rustProxyWebOverview = (() => {
} }
} }
} }
function reset() { histories.clear(); serviceBaselines.clear(); baseline = null; last = null; revision = null; } function reset() { histories.clear(); serviceBaselines.clear(); baseline = null; last = null; revision = null; window.rustProxyChartScale.reset("web:"); }
window.rustProxyOverviewSamples.onChange(() => { window.rustProxyOverviewSamples.onChange(() => {
for (const history of histories.values()) { for (const history of histories.values()) {
if (Array.isArray(history[0])) history.forEach(window.rustProxyOverviewSamples.trim); if (Array.isArray(history[0])) history.forEach(window.rustProxyOverviewSamples.trim);
+2 -1
View File
@@ -70,12 +70,13 @@
function resetLiveChart(selected = scope()) { function resetLiveChart(selected = scope()) {
liveChartScope = selected; liveChartPrevious = null; liveChartPreviousAt = null; liveChartGeneration = null; liveChartScope = selected; liveChartPrevious = null; liveChartPreviousAt = null; liveChartGeneration = null;
for (const values of Object.values(liveChartSamples)) values.length = 0; for (const values of Object.values(liveChartSamples)) values.length = 0;
window.rustProxyChartScale.reset("cache-live");
for (const id of ["cache-live-hit-line", "cache-live-miss-line", "cache-live-bypass-line"]) $(id)?.setAttribute("points", "0,170 900,170"); for (const id of ["cache-live-hit-line", "cache-live-miss-line", "cache-live-bypass-line"]) $(id)?.setAttribute("points", "0,170 900,170");
for (const id of ["cache-live-hit-rate", "cache-live-miss-rate", "cache-live-bypass-rate"]) if ($(id)) $(id).textContent = "0.0/s"; for (const id of ["cache-live-hit-rate", "cache-live-miss-rate", "cache-live-bypass-rate"]) if ($(id)) $(id).textContent = "0.0/s";
if ($("cache-live-chart-value")) $("cache-live-chart-value").textContent = "-"; if ($("cache-live-chart-value")) $("cache-live-chart-value").textContent = "-";
} }
function redrawLiveChart() { function redrawLiveChart() {
const peak = Math.max(1, ...liveChartSamples.hits, ...liveChartSamples.misses, ...liveChartSamples.bypasses); const peak = window.rustProxyChartScale.peak("cache-live", "rate", Object.values(liveChartSamples));
$("cache-live-hit-line")?.setAttribute("points", sparklinePoints(liveChartSamples.hits, 900, 180, 10, peak)); $("cache-live-hit-line")?.setAttribute("points", sparklinePoints(liveChartSamples.hits, 900, 180, 10, peak));
$("cache-live-miss-line")?.setAttribute("points", sparklinePoints(liveChartSamples.misses, 900, 180, 10, peak)); $("cache-live-miss-line")?.setAttribute("points", sparklinePoints(liveChartSamples.misses, 900, 180, 10, peak));
$("cache-live-bypass-line")?.setAttribute("points", sparklinePoints(liveChartSamples.bypasses, 900, 180, 10, peak)); $("cache-live-bypass-line")?.setAttribute("points", sparklinePoints(liveChartSamples.bypasses, 900, 180, 10, peak));
+16 -16
View File
@@ -651,7 +651,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="HTTP/1.1 · HTTP/2 · HTTP/3 traffic"> aria-label="HTTP/1.1 · HTTP/2 · HTTP/3 traffic">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" <path class="overview-wide-series overview-wide-series-0"
id="overview-wide-wide_protocol_rates-h1-line" d=""></path> id="overview-wide-wide_protocol_rates-h1-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -675,7 +675,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="System and process CPU"> aria-label="System and process CPU">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" <path class="overview-wide-series overview-wide-series-0"
id="overview-wide-wide_cpu-system-line" d=""></path> id="overview-wide-wide_cpu-system-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -697,7 +697,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Process RAM and cache memory"> aria-label="Process RAM and cache memory">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" <path class="overview-wide-series overview-wide-series-0"
id="overview-wide-wide_memory-process-line" d=""></path> id="overview-wide-wide_memory-process-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -720,7 +720,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Cache hits / misses per second"> aria-label="Cache hits / misses per second">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" <path class="overview-wide-series overview-wide-series-0"
id="overview-wide-wide_cache_activity-hits-line" d=""></path> id="overview-wide-wide_cache_activity-hits-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -741,7 +741,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Connections and WebSocket tunnels"> aria-label="Connections and WebSocket tunnels">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" <path class="overview-wide-series overview-wide-series-0"
id="overview-wide-wide_connections-open-line" d=""></path> id="overview-wide-wide_connections-open-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -764,7 +764,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Backend availability over time"> aria-label="Backend availability over time">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" <path class="overview-wide-series overview-wide-series-0"
id="overview-wide-wide_backend_health-up-line" d=""></path> id="overview-wide-wide_backend_health-up-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -786,7 +786,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Response and backend probe latency"> aria-label="Response and backend probe latency">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" <path class="overview-wide-series overview-wide-series-0"
id="overview-wide-wide_latency-response-line" d=""></path> id="overview-wide-wide_latency-response-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -809,7 +809,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
data-i18n-aria="overviewWideTransformActivity" aria-label="Transformation activity"> data-i18n-aria="overviewWideTransformActivity" aria-label="Transformation activity">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" <path class="overview-wide-series overview-wide-series-0"
id="overview-wide-wide_transform_activity-applied-line" d=""></path> id="overview-wide-wide_transform_activity-applied-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -842,7 +842,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
data-i18n-aria="overviewWideCompressionActivity" aria-label="Compression activity"> data-i18n-aria="overviewWideCompressionActivity" aria-label="Compression activity">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" <path class="overview-wide-series overview-wide-series-0"
id="overview-wide-wide_compression_activity-gzip-line" d=""></path> id="overview-wide-wide_compression_activity-gzip-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -954,7 +954,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Web Server request rate" data-i18n-aria="webMetricRequests"> aria-label="Web Server request rate" data-i18n-aria="webMetricRequests">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="www_requests-all-line" <path class="overview-wide-series overview-wide-series-0" id="www_requests-all-line"
d=""></path> d=""></path>
<path class="overview-wide-series overview-wide-series-1" id="www_requests-php-line" <path class="overview-wide-series overview-wide-series-1" id="www_requests-php-line"
@@ -975,7 +975,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Web Server header latency" data-i18n-aria="webMetricLatency"> aria-label="Web Server header latency" data-i18n-aria="webMetricLatency">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="www_latency-web-line" <path class="overview-wide-series overview-wide-series-0" id="www_latency-web-line"
d=""></path> d=""></path>
<path class="overview-wide-series overview-wide-series-1" id="www_latency-php-line" <path class="overview-wide-series overview-wide-series-1" id="www_latency-php-line"
@@ -996,7 +996,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="PHP FastCGI failures" data-i18n-aria="webMetricFailures"> aria-label="PHP FastCGI failures" data-i18n-aria="webMetricFailures">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="www_failures-timeout-line" <path class="overview-wide-series overview-wide-series-0" id="www_failures-timeout-line"
d=""></path> d=""></path>
<path class="overview-wide-series overview-wide-series-1" <path class="overview-wide-series overview-wide-series-1"
@@ -1027,7 +1027,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Web Server body byte rate" data-i18n-aria="webMetricTraffic"> aria-label="Web Server body byte rate" data-i18n-aria="webMetricTraffic">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="www_traffic-nonphp-line" <path class="overview-wide-series overview-wide-series-0" id="www_traffic-nonphp-line"
d=""></path> d=""></path>
<path class="overview-wide-series overview-wide-series-1" id="www_traffic-php-line" <path class="overview-wide-series overview-wide-series-1" id="www_traffic-php-line"
@@ -1048,7 +1048,7 @@
</div> </div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img" <svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Web Server HTTP statuses" data-i18n-aria="webMetricStatusTrend"> aria-label="Web Server HTTP statuses" data-i18n-aria="webMetricStatusTrend">
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path> <path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="www_status_trend-2xx-line" <path class="overview-wide-series overview-wide-series-0" id="www_status_trend-2xx-line"
d=""></path> d=""></path>
<path class="overview-wide-series overview-wide-series-1" id="www_status_trend-3xx-line" <path class="overview-wide-series overview-wide-series-1" id="www_status_trend-3xx-line"
@@ -2293,7 +2293,7 @@
<svg aria-label="Live cache activity" data-i18n-aria="cacheLiveChartTitle" <svg aria-label="Live cache activity" data-i18n-aria="cacheLiveChartTitle"
id="cache-live-activity-chart" role="img" viewBox="0 0 900 180" id="cache-live-activity-chart" role="img" viewBox="0 0 900 180"
preserveAspectRatio="none"> preserveAspectRatio="none">
<path class="chart-grid" d="M0 45H900 M0 90H900 M0 135H900"></path> <path class="chart-grid" d="M0 50H900 M0 90H900 M0 130H900"></path>
<polyline class="function-live-line cache-hit-line" id="cache-live-hit-line" <polyline class="function-live-line cache-hit-line" id="cache-live-hit-line"
points="0,170 900,170"></polyline> points="0,170 900,170"></polyline>
<polyline class="function-live-line cache-miss-line" id="cache-live-miss-line" <polyline class="function-live-line cache-miss-line" id="cache-live-miss-line"
@@ -2446,7 +2446,7 @@
<svg aria-label="Live transformation activity" data-i18n-aria="transformLiveChartTitle" <svg aria-label="Live transformation activity" data-i18n-aria="transformLiveChartTitle"
id="transform-live-activity-chart" role="img" viewBox="0 0 900 180" id="transform-live-activity-chart" role="img" viewBox="0 0 900 180"
preserveAspectRatio="none"> preserveAspectRatio="none">
<path class="chart-grid" d="M0 45H900 M0 90H900 M0 135H900"></path> <path class="chart-grid" d="M0 50H900 M0 90H900 M0 130H900"></path>
<polyline class="function-live-line transform-applied-line" <polyline class="function-live-line transform-applied-line"
id="transform-live-applied-line" points="0,170 900,170"></polyline> id="transform-live-applied-line" points="0,170 900,170"></polyline>
<polyline class="function-live-line transform-cache-line" id="transform-live-cache-line" <polyline class="function-live-line transform-cache-line" id="transform-live-cache-line"
+2 -5
View File
@@ -7177,7 +7177,7 @@ body[data-density=compact] .page-footer {
.function-live-chart svg { .function-live-chart svg {
display: block; display: block;
width: 100%; width: 100%;
height: calc(clamp(180px, 22vw, 270px)); height: 180px;
overflow: visible overflow: visible
} }
@@ -7270,9 +7270,6 @@ body[data-density=compact] .page-footer {
white-space: normal white-space: normal
} }
.function-live-chart svg {
height: 190px
}
.function-live-chart-footer { .function-live-chart-footer {
align-items: flex-start; align-items: flex-start;
@@ -10889,7 +10886,7 @@ html[data-dashboard-density=ultra] #panel-overview .dashboard-overview-grid>.car
#panel-overview .overview-wide-svg { #panel-overview .overview-wide-svg {
display: block; display: block;
width: 100%; width: 100%;
height: calc(clamp(125px, 13vw, 190px)); height: 125px;
margin-block: 13px 9px; margin-block: 13px 9px;
} }
+2 -1
View File
@@ -22,12 +22,13 @@
function resetLiveChart(selected = scope()) { function resetLiveChart(selected = scope()) {
liveChartScope = selected; liveChartPrevious = null; liveChartPreviousAt = null; liveChartGeneration = null; liveChartScope = selected; liveChartPrevious = null; liveChartPreviousAt = null; liveChartGeneration = null;
for (const values of Object.values(liveChartSamples)) values.length = 0; for (const values of Object.values(liveChartSamples)) values.length = 0;
window.rustProxyChartScale.reset("transform-live");
for (const id of ["transform-live-applied-line", "transform-live-cache-line", "transform-live-skipped-line", "transform-live-error-line"]) $(id)?.setAttribute("points", "0,170 900,170"); for (const id of ["transform-live-applied-line", "transform-live-cache-line", "transform-live-skipped-line", "transform-live-error-line"]) $(id)?.setAttribute("points", "0,170 900,170");
for (const id of ["transform-live-applied-rate", "transform-live-cache-rate", "transform-live-skipped-rate", "transform-live-error-rate"]) if ($(id)) $(id).textContent = "0.0/s"; for (const id of ["transform-live-applied-rate", "transform-live-cache-rate", "transform-live-skipped-rate", "transform-live-error-rate"]) if ($(id)) $(id).textContent = "0.0/s";
if ($("transform-live-chart-value")) $("transform-live-chart-value").textContent = "-"; if ($("transform-live-chart-value")) $("transform-live-chart-value").textContent = "-";
} }
function redrawLiveChart() { function redrawLiveChart() {
const peak = Math.max(1, ...liveChartSamples.applied, ...liveChartSamples.cache_hits, ...liveChartSamples.skipped, ...liveChartSamples.errors); const peak = window.rustProxyChartScale.peak("transform-live", "rate", Object.values(liveChartSamples));
$("transform-live-applied-line")?.setAttribute("points", sparklinePoints(liveChartSamples.applied, 900, 180, 10, peak)); $("transform-live-applied-line")?.setAttribute("points", sparklinePoints(liveChartSamples.applied, 900, 180, 10, peak));
$("transform-live-cache-line")?.setAttribute("points", sparklinePoints(liveChartSamples.cache_hits, 900, 180, 10, peak)); $("transform-live-cache-line")?.setAttribute("points", sparklinePoints(liveChartSamples.cache_hits, 900, 180, 10, peak));
$("transform-live-skipped-line")?.setAttribute("points", sparklinePoints(liveChartSamples.skipped, 900, 180, 10, peak)); $("transform-live-skipped-line")?.setAttribute("points", sparklinePoints(liveChartSamples.skipped, 900, 180, 10, peak));