Metrics: the bytes pushes and fetches carry 85ec74fd82d8
The protocol's request and response bodies, counted by endpoint in its handler, before compression: the outer request layer sees compressed answers of no known size, and oxy's client accepts gzip. Refs #212
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key c897aff9d2ac
- When
- · 2026-10-18 06:27 UTC
- Signature
- ◈ sealed by agent:claude@andymccall
- Hash
85ec74fd82d8075b2e48d1383547cc3a18886327bb29b28f08c58d8a29929fad- Depends on
- 2c47eecfeab6 42fa95540468 8a48dd830178 93be9ce4653e a6d824c5ebf5 b9b74d997cec d23464c0ac60
- Depended on by
- 166f6e9bf4f1
- In bookmarks
- api-unknown-fields cancel-landed-runs diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
- In tags
- v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
- Edits
- 8 atoms, 0 tree operations, 4 files
Browse the tree at this change
modified crates/hub/src/metrics.rs +26 −0
| … | 36 unchanged lines |
||
| 37 | 37 | ||
| 38 | 38 | static HTTP: Mutex<Option<HashMap<(String, String), Hist>>> = Mutex::new(None); |
|
| 39 | 39 | ||
| 40 | + | /// Bytes the protocol received and sent, by endpoint, counted before the |
|
| 41 | + | /// answer is compressed: what pushes and fetches carry. |
|
| 42 | + | static PROTOCOL: Mutex<std::collections::BTreeMap<&'static str, (u64, u64)>> = Mutex::new(std::collections::BTreeMap::new()); |
|
| 43 | + | ||
| 44 | + | /// Count a protocol call's request and response bodies. |
|
| 45 | + | pub fn protocol_bytes(endpoint: &'static str, received: u64, sent: u64) { |
|
| 46 | + | let mut g = PROTOCOL.lock().unwrap_or_else(|p| p.into_inner()); |
|
| 47 | + | let e = g.entry(endpoint).or_default(); |
|
| 48 | + | e.0 += received; |
|
| 49 | + | e.1 += sent; |
|
| 50 | + | } |
|
| 51 | + | ||
| 40 | 52 | /// A request's route class: a few dozen at most, so a label never holds a |
|
| 41 | 53 | /// user's or repository's name. |
|
| 42 | 54 | pub fn route_class(path: &str) -> String { |
|
| … | 214 unchanged lines |
||
| 257 | 269 | .collect::<Vec<_>>(), |
|
| 258 | 270 | ); |
|
| 259 | 271 | family(&mut out, "oxyhub_tlog_entries", "Entries in the transparency log.", "gauge", &[(String::new(), app.tlog.size() as f64)]); |
|
| 272 | + | let protocol: Vec<(&'static str, (u64, u64))> = |
|
| 273 | + | PROTOCOL.lock().unwrap_or_else(|p| p.into_inner()).iter().map(|(k, v)| (*k, *v)).collect(); |
|
| 274 | + | family( |
|
| 275 | + | &mut out, |
|
| 276 | + | "oxyhub_protocol_bytes_total", |
|
| 277 | + | "Bytes the Oxygen protocol received and sent since the hub started, by endpoint, before compression: push carries pushes, fetch and objects carry fetches.", |
|
| 278 | + | "counter", |
|
| 279 | + | &protocol |
|
| 280 | + | .iter() |
|
| 281 | + | .flat_map(|(e, (rx, tx))| { |
|
| 282 | + | [(format!("endpoint=\"{e}\",direction=\"received\""), *rx as f64), (format!("endpoint=\"{e}\",direction=\"sent\""), *tx as f64)] |
|
| 283 | + | }) |
|
| 284 | + | .collect::<Vec<_>>(), |
|
| 285 | + | ); |
|
| 260 | 286 | family( |
|
| 261 | 287 | &mut out, |
|
| 262 | 288 | "oxyhub_mail_total", |
|
| … | 91 unchanged lines |
modified crates/hub/src/protocol.rs +5 −1
| … | 457 unchanged lines |
||
| 458 | 458 | Endpoint::Push | Endpoint::Checkpoint => Guard::Write(app.repos.write_guard(&repo).await), |
|
| 459 | 459 | _ => Guard::Read(app.repos.read_guard(&repo).await), |
|
| 460 | 460 | }; |
|
| 461 | - | app.server.handle(repo.to_string(), endpoint, &method, &headers, body).await |
|
| 461 | + | let (name, received) = (endpoint.name(), body.len() as u64); |
|
| 462 | + | let res = app.server.handle(repo.to_string(), endpoint, &method, &headers, body).await; |
|
| 463 | + | let sent = axum::body::HttpBody::size_hint(res.body()).exact().unwrap_or(0); |
|
| 464 | + | crate::metrics::protocol_bytes(name, received, sent); |
|
| 465 | + | res |
|
| 462 | 466 | } |
|
| 463 | 467 | ||
| 464 | 468 | enum Guard { |
|
| … | 3 unchanged lines |
modified crates/hub/tests/flow.rs +8 −0
| … | 5324 unchanged lines |
||
| 5325 | 5325 | let text = oxygenhub::metrics::render(&h.app).await; |
|
| 5326 | 5326 | assert!(!text.contains("oxyhub_flow_jobs{state=\"queued\"} 0"), "the run's jobs are queued:\n{text}"); |
|
| 5327 | 5327 | assert!(text.contains("oxyhub_pushes_last_day{result=\"accepted\"}"), "{text}"); |
|
| 5328 | + | // The push's bytes are counted (other tests share the counters, so |
|
| 5329 | + | // only that they are there and not nothing). |
|
| 5330 | + | let pushed = text |
|
| 5331 | + | .lines() |
|
| 5332 | + | .find_map(|l| l.strip_prefix("oxyhub_protocol_bytes_total{endpoint=\"push\",direction=\"received\"} ")) |
|
| 5333 | + | .and_then(|v| v.parse::<f64>().ok()) |
|
| 5334 | + | .unwrap_or(0.0); |
|
| 5335 | + | assert!(pushed > 0.0, "{text}"); |
|
| 5328 | 5336 | // Requests through the router are counted by route class. |
|
| 5329 | 5337 | call(&h.app, "GET", "/andy/demo", None, None).await; |
|
| 5330 | 5338 | let text = oxygenhub::metrics::render(&h.app).await; |
|
| … | 188 unchanged lines |
modified docs/spec.md +2 −2
| … | 644 unchanged lines |
||
| 645 | 645 | - OxygenFlow jobs queued, waiting for approval and running; runs finished in the last 24 hours by result; runners online and not, and seconds since each last called; |
|
| 646 | 646 | - the transparency log's entries; |
|
| 647 | 647 | - the database pool's connections (idle and in use); |
|
| 648 | - | - reference updates pushed in the last 24 hours, accepted and refused; |
|
| 648 | + | - reference updates pushed in the last 24 hours, accepted and refused, and the bytes the protocol received and sent by endpoint (`push` carries pushes, `fetch` and `objects` fetches), counted before compression; |
|
| 649 | 649 | - each owner's latest backup: seconds since it started, and whether it succeeded; |
|
| 650 | 650 | - mail sent and failed since the hub started; |
|
| 651 | 651 | - HTTP requests by route class (`page:<tab>`, `api:<area>`, `protocol:<endpoint>`, `static` and so on, never a user's or repository's name; at most 300 classes), method and status class, with latency histograms (5 ms to 30 s). |
|
| 652 | 652 | ||
| 653 | - | Push and fetch sizes, Diffusion, notifications and query latency are the next slices; the runner's own metrics too. |
|
| 653 | + | Diffusion, notifications and query latency are the next slices; the runner's own metrics too. |
|
| 654 | 654 | ||
| 655 | 655 | ### 13.6 Coverage reports (oxygenhub#113, done 2026-10-06) |
|
| 656 | 656 | ||
| … | 168 unchanged lines |