summaryrefslogtreecommitdiffstats
path: root/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/api')
-rw-r--r--src/api/peers.rs16
-rw-r--r--src/api/stats.rs34
-rw-r--r--src/api/stats/prometheus.rs49
3 files changed, 92 insertions, 7 deletions
diff --git a/src/api/peers.rs b/src/api/peers.rs
index 919f187..a9694f7 100644
--- a/src/api/peers.rs
+++ b/src/api/peers.rs
@@ -16,7 +16,7 @@ use utoipa::ToSchema;
use super::store_status;
use crate::document::replica::Epoch;
use crate::document::store;
-use crate::state::{AppState, HelloState, Incidents, Liveness, PeerState, unix_millis};
+use crate::state::{AppState, HelloState, Incidents, Liveness, PeerState};
use crate::swarm::peer::forgotten_path;
use crate::transport::traffic::PeerTraffic;
@@ -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,13 +226,13 @@ pub(crate) async fn delete_peer(
.lock()
.await
.is_alive(&state.timing, Instant::now())
- && !query.force
+ && !force.allows("alive")
{
return Err(StatusCode::CONFLICT);
}
}
- let record = serde_json::json!({ "at": unix_millis(), "by": state.local_id() });
+ let record = serde_json::json!({ "at": state.clock.now_millis(), "by": state.local_id() });
let peer_clone = peer.clone();
state
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",