diff options
| -rw-r--r-- | docs/measurements.md | 1 | ||||
| -rw-r--r-- | src/api/stats.rs | 4 | ||||
| -rw-r--r-- | src/api/stats/prometheus.rs | 8 | ||||
| -rw-r--r-- | src/document/mod.rs | 10 | ||||
| -rw-r--r-- | src/swarm/stats.rs | 14 |
5 files changed, 34 insertions, 3 deletions
diff --git a/docs/measurements.md b/docs/measurements.md index 33f325c..15aa614 100644 --- a/docs/measurements.md +++ b/docs/measurements.md @@ -39,6 +39,7 @@ whole seconds in JSON unless named `…Ms`; Prometheus has seconds throughout. | JSON | Prometheus | Meaning | |---|---|---| | `document.heads` | `document_heads` | Heads of the local document now. 1 means every change has been merged into one line of history; more means concurrent writes not yet followed by a merging write. | +| `document.writers` | `document_writers` | Writers (Automerge actors) with changes in the document, counted with the saved size. Each start, rejoin or drop of a node makes one (a start keeps the one of before with `--keep-actor` after a clean stop), and every transfer of the whole history carries a signature for each; a remake starts again from the writers that write in the new epoch. | | `document.maxHeads` | `document_max_heads` | The most heads seen right after applying a sync message. A high value shows how concurrent the swarm's writing gets; it also bounds keepalive size (above 32 heads, keepalives carry a digest and 32 of them). | | `document.savedBytes` | `document_saved_bytes` | The document as Automerge saves it (compressed): roughly what a joining node downloads and what a document file rewrite writes. Measured in the background, only after a change, at least a minute apart and at most 1% of the time (a document that takes 2 s to save is measured every 200 s at most); an export (`GET /admin/document`) counts as a measurement. `savedBytesSecsAgo` and `savedBytesTookMs` say how fresh and how costly. | | `document.lockWait.read`, `.write`: `count`, `sumUs`, `maxUs`, `buckets[]` (`leUs`) | `document_lock_wait_seconds{mode}` (histogram), `document_lock_wait_max_seconds{mode}` | How long takers of the document's lock waited for it, since start, by mode. Readers (the API's reads, topology, stats, generating sync messages, serving changes and deltas, saves) wait only for a writer; writers (applying sync messages and fetched changes, writes, epoch switches) wait for everyone. Buckets from 10 µs to 10 s, the last unbounded; not cumulative in JSON. | diff --git a/src/api/stats.rs b/src/api/stats.rs index 90b4fc2..37a7447 100644 --- a/src/api/stats.rs +++ b/src/api/stats.rs @@ -101,6 +101,9 @@ struct DocumentStats { saved_bytes_secs_ago: Option<u64>, /// How long the last measurement took (copy and save), in milliseconds. saved_bytes_took_ms: Option<u64>, + /// Writers (Automerge actors: a node's run each) with changes in the document, + /// counted with `saved_bytes`; absent until first counted. + writers: Option<usize>, /// How long takers of the document's lock waited for it, since start: the node's /// work on the one document queues there. lock_wait: crate::document::lock::LockWaits, @@ -360,6 +363,7 @@ pub(crate) async fn collect(state: &AppState, list: &PeerListResponse) -> StatsR saved_bytes: saved.map(|s| s.bytes), saved_bytes_secs_ago: saved.map(|s| s.at.elapsed().as_secs()), saved_bytes_took_ms: saved.map(|s| s.took.as_millis() as u64), + writers: saved.and_then(|s| s.writers), lock_wait: state.doc.waits(), }, catch_up: CatchUpStats { diff --git a/src/api/stats/prometheus.rs b/src/api/stats/prometheus.rs index 0d79d2c..ca3f1af 100644 --- a/src/api/stats/prometheus.rs +++ b/src/api/stats/prometheus.rs @@ -460,6 +460,14 @@ pub(super) fn render(stats: &StatsResponse, list: &PeerListResponse) -> String { "Most heads after applying a sync message, since start.", document.max_heads as f64, ); + if let Some(writers) = document.writers { + out.single( + "document_writers", + "gauge", + "Writers (Automerge actors, a node's run each) with changes in the document; counted in the background.", + writers as f64, + ); + } if let Some(bytes) = document.saved_bytes { out.single( "document_saved_bytes", diff --git a/src/document/mod.rs b/src/document/mod.rs index c183537..a546d63 100644 --- a/src/document/mod.rs +++ b/src/document/mod.rs @@ -13,6 +13,16 @@ pub(crate) use automerge::{ ScalarValue, }; +/// How many writers (actors) `doc`'s changes have: from the changes' metadata, in +/// time proportional to the document. +pub(crate) fn writers(doc: &Document) -> usize { + doc.get_changes_meta(&[]) + .into_iter() + .map(|meta| meta.actor.into_owned()) + .collect::<std::collections::HashSet<_>>() + .len() +} + /// The whole document as bytes, as the engine saves it: a backup, the document /// file's full save, the size a joining node downloads. pub(crate) fn save(doc: &Document) -> Vec<u8> { diff --git a/src/swarm/stats.rs b/src/swarm/stats.rs index ed12888..3665b5a 100644 --- a/src/swarm/stats.rs +++ b/src/swarm/stats.rs @@ -99,12 +99,16 @@ impl Propagation { } /// The document's size as saved (compressed), when it was measured and how long that -/// took (copy and save). +/// took (copy and save), and how many writers (Automerge actors) its changes have. #[derive(Debug, Clone, Copy)] pub(crate) struct SavedSize { pub(crate) bytes: u64, pub(crate) at: Instant, pub(crate) took: Duration, + /// Writers with changes in the document, counted with the measurement: a node's + /// run each, so many a transfer of the whole history carries a signature for. None + /// where only the size was measured (an export). + pub(crate) writers: Option<usize>, } /// Passing changes on (`swarm::relay`), since start. @@ -434,10 +438,12 @@ impl NodeStats { /// measurement for free. pub(crate) fn saved_size_measured(&self, bytes: u64, took: Duration, version: (u64, Epoch)) { let mut inner = self.lock(); + let writers = inner.saved_size.and_then(|saved| saved.writers); inner.saved_size = Some(SavedSize { bytes, at: Instant::now(), took, + writers, }); inner.measured_version = Some(version); } @@ -494,11 +500,12 @@ pub(crate) fn measure_saved_size(state: &AppState) { // Both steps take time proportional to the document: off the async threads. let measured = match tokio::task::spawn_blocking(move || { let copy = doc.blocking_read().doc.clone(); - crate::document::save(©).len() as u64 + let writers = crate::document::writers(©); + (crate::document::save(©).len() as u64, writers) }) .await { - Ok(bytes) => { + Ok((bytes, writers)) => { let took = start.elapsed(); debug!( bytes, @@ -510,6 +517,7 @@ pub(crate) fn measure_saved_size(state: &AppState) { bytes, at: Instant::now(), took, + writers: Some(writers), }, version, )) |
