diff options
Diffstat (limited to 'src/api')
| -rw-r--r-- | src/api/mod.rs | 7 | ||||
| -rw-r--r-- | src/api/peers.rs | 12 | ||||
| -rw-r--r-- | src/api/stats.rs | 34 | ||||
| -rw-r--r-- | src/api/stats/prometheus.rs | 49 |
4 files changed, 95 insertions, 7 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs index 1fba48b..affb9ef 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -120,8 +120,11 @@ fn parse_write_path( if path.first().map(String::as_str) != Some(crate::remake::snapshot::META_ROOT) { return Ok(path); } - let epochs = path.len() < 2 || path[1] == crate::remake::snapshot::EPOCHS; - if epochs || !state.protocol_writers.contains(&user.0) { + // `_continuum` itself, epoch metadata and the swarm ID: nobody. + let fixed = path.len() < 2 + || path[1] == crate::remake::snapshot::EPOCHS + || path[1] == crate::remake::snapshot::SWARM; + if fixed || !state.protocol_writers.contains(&user.0) { warn!(user = %user.0, method, path = %key, "Refused a write to protocol data"); return Err(StatusCode::FORBIDDEN); } diff --git a/src/api/peers.rs b/src/api/peers.rs index 919f187..dc4bd3b 100644 --- a/src/api/peers.rs +++ b/src/api/peers.rs @@ -100,8 +100,8 @@ pub(crate) struct PeerListResponse { #[derive(Debug, Deserialize)] pub(crate) struct DeletePeerQuery { - #[serde(default)] - force: bool, + /// The checks to override (`force`): `alive`. + force: Option<String>, } #[utoipa::path( @@ -197,12 +197,12 @@ pub(crate) async fn list_peers( tag = "Peers", params( ("peer" = String, Path, description = "Peer public key"), - ("force" = Option<bool>, Query, description = "Also forget a peer that is currently alive; it returns on its next message") + ("force" = Option<String>, Query, description = "`true` or `alive`: also forget a peer that is currently alive; it returns on its next message") ), responses( (status = 204, description = "Swarm-wide forget recorded"), (status = 400, description = "Not a public key, or the local node's own key"), - (status = 409, description = "Peer is alive; pass force=true"), + (status = 409, description = "Peer is alive; pass force=alive"), (status = 503, description = "Node is not ready") ) )] @@ -217,6 +217,8 @@ pub(crate) async fn delete_peer( if peer.is_empty() || peer == state.local_id() { return Err(StatusCode::BAD_REQUEST); } + let force = crate::force::Force::parse(query.force.as_deref(), &["alive"]) + .map_err(|_| StatusCode::BAD_REQUEST)?; // Unknown locally is fine: other nodes may still know the peer. if let Some(peer_state_mutex) = state.get_peer_state(&peer).await { @@ -224,7 +226,7 @@ pub(crate) async fn delete_peer( .lock() .await .is_alive(&state.timing, Instant::now()) - && !query.force + && !force.allows("alive") { return Err(StatusCode::CONFLICT); } diff --git a/src/api/stats.rs b/src/api/stats.rs index 838cc0f..fa11b45 100644 --- a/src/api/stats.rs +++ b/src/api/stats.rs @@ -9,6 +9,8 @@ use axum::{ http::{HeaderMap, header}, response::{IntoResponse, Response}, }; +use std::collections::BTreeMap; + use serde::Serialize; use super::peers::EpochInfo; @@ -35,6 +37,21 @@ pub(crate) struct StatsResponse { /// The peers listed in /peers together (a forgotten peer's are gone). incidents: Incidents, propagation: PropagationStats, + /// This node's own files (`files`), by kind. + disk: BTreeMap<&'static str, DiskStats>, +} + +/// Reads and writes of one kind of file, since the node started. +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct DiskStats { + reads: u64, + read_bytes: u64, + writes: u64, + written_bytes: u64, + appends: u64, + appended_bytes: u64, + errors: u64, } /// How long changes written elsewhere took from their commit there until applied here, @@ -195,5 +212,22 @@ async fn collect(state: &AppState) -> StatsResponse { .map(|(le_ms, count)| PropagationBucket { le_ms, count }) .collect(), }, + disk: state + .files + .counts() + .into_iter() + .map(|(kind, counts)| { + let stats = DiskStats { + reads: counts.reads, + read_bytes: counts.read_bytes, + writes: counts.writes, + written_bytes: counts.written_bytes, + appends: counts.appends, + appended_bytes: counts.appended_bytes, + errors: counts.errors, + }; + (kind.as_str(), stats) + }) + .collect(), } } diff --git a/src/api/stats/prometheus.rs b/src/api/stats/prometheus.rs index f68ca85..74c18e9 100644 --- a/src/api/stats/prometheus.rs +++ b/src/api/stats/prometheus.rs @@ -54,6 +54,45 @@ impl Out { } } +const DISK_COUNTERS: usize = 7; + +/// Name, help and value of each counter of one kind of file. +fn disk_counters(c: &super::DiskStats) -> [(&'static str, &'static str, u64); DISK_COUNTERS] { + [ + ( + "disk_reads_total", + "Reads of this node's own files.", + c.reads, + ), + ( + "disk_read_bytes_total", + "Bytes read from them.", + c.read_bytes, + ), + ("disk_writes_total", "Whole files written.", c.writes), + ( + "disk_written_bytes_total", + "Bytes of them.", + c.written_bytes, + ), + ( + "disk_appends_total", + "Appends to this node's own files.", + c.appends, + ), + ( + "disk_appended_bytes_total", + "Bytes appended.", + c.appended_bytes, + ), + ( + "disk_errors_total", + "Failed reads, writes and appends.", + c.errors, + ), + ] +} + fn escape(value: &str) -> String { value .replace('\\', "\\\\") @@ -297,6 +336,16 @@ pub(super) async fn render(state: &AppState, stats: &StatsResponse) -> String { stats.stale_hello_replies as f64, ); + for counter in 0..DISK_COUNTERS { + for (i, (kind, counts)) in stats.disk.iter().enumerate() { + let (name, help, value) = disk_counters(counts)[counter]; + if i == 0 { + out.family(name, "counter", help); + } + out.sample(name, &[("file", *kind)], value as f64); + } + } + let propagation = &stats.propagation; out.family( "propagation_delay_seconds", |
