use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use exfiltrate_internal::args::{ArgKind, ArgSpec, ParsedArgs};
use exfiltrate_internal::command::{Command, CommandContext, Response};
use exfiltrate_internal::snapshot::{
FieldPrivacy, SnapshotField, SnapshotOutcome, SnapshotResponse, SnapshotRow, SnapshotSet,
SnapshotValue,
};
use wasm_lite_std::Mutex;
use wasm_lite_std::time::{Duration, Instant};
#[derive(Debug)]
pub struct SnapshotRequest<'a> {
selector: Option<&'a str>,
limit: usize,
deadline: Instant,
cancelled: &'a AtomicBool,
}
impl<'a> SnapshotRequest<'a> {
pub fn selector(&self) -> Option<&'a str> {
self.selector
}
pub fn limit(&self) -> usize {
self.limit
}
pub fn should_stop(&self) -> bool {
self.cancelled.load(Ordering::Relaxed) || Instant::now() >= self.deadline
}
}
#[derive(Clone, Debug, Default)]
pub struct Row(Vec<SnapshotField>);
impl Row {
pub fn new() -> Row {
Row(Vec::new())
}
pub fn support(mut self, name: impl Into<String>, value: impl Into<SnapshotValue>) -> Row {
self.0.push(SnapshotField {
name: name.into(),
privacy: FieldPrivacy::SupportSafe,
value: Some(value.into()),
});
self
}
pub fn local(mut self, name: impl Into<String>, value: impl Into<SnapshotValue>) -> Row {
self.0.push(SnapshotField {
name: name.into(),
privacy: FieldPrivacy::LocalOnly,
value: Some(value.into()),
});
self
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Debug)]
pub enum ProviderResult {
Rows(Vec<Row>),
Partial(Vec<Row>, String),
Unavailable(String),
NotCompiled,
Busy,
}
pub trait Provider: Send + Sync + 'static {
fn subsystem(&self) -> &'static str;
fn description(&self) -> &'static str;
fn snapshot(&self, request: &SnapshotRequest<'_>) -> ProviderResult;
}
static PROVIDERS: Mutex<Vec<Arc<dyn Provider>>> = Mutex::new(Vec::new());
pub fn add_provider<P: Provider>(provider: P) {
let provider: Arc<dyn Provider> = Arc::new(provider);
PROVIDERS.with_mut_sync(|providers| {
providers.retain(|existing| existing.subsystem() != provider.subsystem());
providers.push(provider);
});
}
pub fn registered() -> Vec<(&'static str, &'static str)> {
PROVIDERS.with_sync(|providers| {
providers
.iter()
.map(|provider| (provider.subsystem(), provider.description()))
.collect()
})
}
#[cfg(test)]
pub(crate) fn clear_providers() {
PROVIDERS.with_mut_sync(|providers| providers.clear());
}
fn run(
provider: &Arc<dyn Provider>,
selector: Option<&str>,
limit: usize,
timeout: Duration,
cancelled: &AtomicBool,
local_view: bool,
) -> SnapshotResponse {
let started = Instant::now();
let deadline = started + timeout;
let request = SnapshotRequest {
selector,
limit,
deadline,
cancelled,
};
#[cfg(not(target_arch = "wasm32"))]
let produced =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| provider.snapshot(&request)));
#[cfg(target_arch = "wasm32")]
let produced = Ok::<_, ()>(provider.snapshot(&request));
let elapsed_ms = started.elapsed().as_millis() as u64;
let overran = Instant::now() >= deadline;
let (mut rows, mut outcome, mut reason) = match produced {
Err(_) => (
Vec::new(),
SnapshotOutcome::Panicked,
Some("the provider panicked; this is a bug in that subsystem".to_string()),
),
Ok(ProviderResult::Rows(rows)) => (rows, SnapshotOutcome::Ready, None),
Ok(ProviderResult::Partial(rows, why)) => (rows, SnapshotOutcome::Partial, Some(why)),
Ok(ProviderResult::Unavailable(why)) => {
(Vec::new(), SnapshotOutcome::Unavailable, Some(why))
}
Ok(ProviderResult::NotCompiled) => (
Vec::new(),
SnapshotOutcome::NotCompiled,
Some("the subsystem is not compiled into this build".to_string()),
),
Ok(ProviderResult::Busy) => (
Vec::new(),
SnapshotOutcome::Busy,
Some("the subsystem's state was locked; retry".to_string()),
),
};
if overran && outcome == SnapshotOutcome::Ready {
outcome = SnapshotOutcome::TimedOut;
reason = Some(format!(
"the provider ran past its {}ms deadline; its result may be stale",
timeout.as_millis()
));
}
if rows.len() > limit {
rows.truncate(limit);
if outcome == SnapshotOutcome::Ready {
outcome = SnapshotOutcome::Partial;
reason = Some(format!("truncated to the requested limit of {limit}"));
}
}
let rows: Vec<SnapshotRow> = rows
.into_iter()
.map(|row| SnapshotRow {
fields: row
.0
.into_iter()
.map(|mut field| {
if field.privacy == FieldPrivacy::LocalOnly && !local_view {
field.value = None;
}
field
})
.collect(),
})
.collect();
SnapshotResponse {
subsystem: provider.subsystem().to_string(),
outcome,
reason,
returned: rows.len(),
rows,
selector: selector.map(str::to_string),
view: if local_view { "local" } else { "remote" }.to_string(),
elapsed_ms,
}
}
static ARGS: &[ArgSpec] = &[
ArgSpec::flag(
"subsystem",
"which subsystem to ask; comma-separated for several, omit for all",
ArgKind::String,
),
ArgSpec::flag(
"id",
"a selector whose meaning is the subsystem's, e.g. a task or context id",
ArgKind::String,
),
ArgSpec::flag(
"limit",
"the most rows to return per subsystem",
ArgKind::Integer,
),
ArgSpec::flag(
"timeout-ms",
"how long a single provider may take before its result is reported stale",
ArgKind::Integer,
),
ArgSpec::flag(
"view",
"remote is support-safe only; local additionally includes local-only fields",
ArgKind::Enum(&["remote", "local"]),
),
ArgSpec::flag(
"list",
"list the registered subsystems and return",
ArgKind::Bool,
),
];
const DEFAULT_LIMIT: usize = 256;
const DEFAULT_TIMEOUT_MS: u64 = 250;
#[derive(Debug)]
pub struct Snapshot;
impl Command for Snapshot {
fn name(&self) -> &'static str {
"snapshot"
}
fn short_description(&self) -> &'static str {
"Shows live state — what exists now, rather than what just happened."
}
fn full_description(&self) -> &'static str {
"Asks registered subsystems to describe their current state.\n\n\
This is the counterpart to reading logs, not a replacement for it. A log says what \
happened; this says what is there — live tasks, queue depths, routing tables. Rebuilding \
that from an event history is unreliable exactly when you need it: after records were \
dropped, or when the state you care about never emitted anything because it never changed.\n\n\
`--list` shows what is registered. With no `--subsystem`, every subsystem is asked.\n\n\
Each answer carries an outcome, and they are kept apart because they call for different \
things:\n\
ready everything the selector matched is here\n\
partial some of it; `reason` says what stopped it\n\
unavailable the subsystem is here but cannot answer now\n\
not-compiled it was compiled out of this build\n\
busy its state was locked; retry\n\
panicked the provider crashed — a bug in that subsystem\n\
timed-out it ran past its deadline; the result may be stale\n\n\
`--limit` and `--timeout-ms` are enforced by this command, not left to the provider: one \
that ignores them is truncated or reported rather than trusted.\n\n\
The default view is `remote`, which is support-safe only. `--view local` additionally \
shows local-only fields — values derived from the application's own data. A withheld \
field keeps its slot, so you can tell 'no such field' from 'not shown to you'."
}
fn args(&self) -> &'static [ArgSpec] {
ARGS
}
fn execute(&self, args: Vec<String>) -> Result<Response, Response> {
self.execute_with(args, &CommandContext::detached())
}
fn execute_with(
&self,
args: Vec<String>,
context: &CommandContext,
) -> Result<Response, Response> {
let parsed = ParsedArgs::parse(self.args(), args).map_err(Response::String)?;
if parsed.boolean("list") {
let listed: Vec<_> = registered()
.into_iter()
.map(|(name, description)| SnapshotRow {
fields: vec![
SnapshotField {
name: "subsystem".to_string(),
privacy: FieldPrivacy::SupportSafe,
value: Some(SnapshotValue::String(name.to_string())),
},
SnapshotField {
name: "description".to_string(),
privacy: FieldPrivacy::SupportSafe,
value: Some(SnapshotValue::String(description.to_string())),
},
],
})
.collect();
return Response::from_serialize(&SnapshotSet {
snapshots: vec![SnapshotResponse {
subsystem: "registry".to_string(),
outcome: SnapshotOutcome::Ready,
reason: None,
returned: listed.len(),
rows: listed,
selector: None,
view: "remote".to_string(),
elapsed_ms: 0,
}],
unknown: Vec::new(),
});
}
let wanted: Vec<String> = parsed
.get("subsystem")
.map(|value| {
value
.split(',')
.map(str::trim)
.filter(|name| !name.is_empty())
.map(str::to_string)
.collect()
})
.unwrap_or_default();
let selector = parsed.get("id");
let limit = parsed
.integer("limit")
.filter(|limit| *limit > 0)
.map_or(DEFAULT_LIMIT, |limit| limit as usize);
let timeout = Duration::from_millis(
parsed
.integer("timeout-ms")
.filter(|ms| *ms > 0)
.map_or(DEFAULT_TIMEOUT_MS, |ms| ms as u64),
);
let local_view = parsed.get("view") == Some("local");
let providers = PROVIDERS.with_sync(|providers| providers.clone());
let selected: Vec<_> = if wanted.is_empty() {
providers
} else {
providers
.into_iter()
.filter(|provider| wanted.iter().any(|name| name == provider.subsystem()))
.collect()
};
let unknown: Vec<String> = wanted
.iter()
.filter(|name| {
!selected
.iter()
.any(|provider| provider.subsystem() == name.as_str())
})
.cloned()
.collect();
let cancelled = context.cancel_flag();
let mut snapshots = Vec::with_capacity(selected.len());
for provider in &selected {
context.check_cancelled()?;
snapshots.push(run(
provider, selector, limit, timeout, &cancelled, local_view,
));
}
Response::from_serialize(&SnapshotSet { snapshots, unknown })
}
}
#[cfg(test)]
mod tests {
use super::*;
use exfiltrate_internal::snapshot::SnapshotSet;
use std::sync::atomic::AtomicU64;
fn session() -> std::sync::MutexGuard<'static, ()> {
static SERIALIZE: std::sync::Mutex<()> = std::sync::Mutex::new(());
let guard = SERIALIZE
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_providers();
guard
}
fn ask(args: &[&str]) -> SnapshotSet {
let response = Snapshot
.execute(args.iter().map(|arg| arg.to_string()).collect())
.expect("snapshot should not fail");
match response {
Response::Bytes(bytes) => rmp_serde::from_slice(&bytes).expect("decode"),
other => panic!("expected a structured response, got {other:?}"),
}
}
fn only(set: &SnapshotSet) -> &SnapshotResponse {
assert_eq!(set.snapshots.len(), 1, "{set:?}");
&set.snapshots[0]
}
struct Fixed {
name: &'static str,
result: fn(&SnapshotRequest<'_>) -> ProviderResult,
}
impl Provider for Fixed {
fn subsystem(&self) -> &'static str {
self.name
}
fn description(&self) -> &'static str {
"a test provider"
}
fn snapshot(&self, request: &SnapshotRequest<'_>) -> ProviderResult {
(self.result)(request)
}
}
#[wasm_lite::wasm_lite_test]
fn the_outcomes_are_distinct() {
let _session = session();
add_provider(Fixed {
name: "unavailable",
result: |_| ProviderResult::Unavailable("no window yet".to_string()),
});
add_provider(Fixed {
name: "notcompiled",
result: |_| ProviderResult::NotCompiled,
});
add_provider(Fixed {
name: "busy",
result: |_| ProviderResult::Busy,
});
for (subsystem, expected) in [
("unavailable", SnapshotOutcome::Unavailable),
("notcompiled", SnapshotOutcome::NotCompiled),
("busy", SnapshotOutcome::Busy),
] {
let set = ask(&["--subsystem", subsystem]);
let answer = only(&set);
assert_eq!(answer.outcome, expected, "{answer:?}");
assert!(
answer.reason.is_some(),
"a non-ready outcome should say why: {answer:?}"
);
}
}
#[wasm_lite::wasm_lite_test]
fn ignoring_the_limit_is_truncated_and_reported_partial() {
let _session = session();
add_provider(Fixed {
name: "greedy",
result: |_| {
ProviderResult::Rows((0..50).map(|n| Row::new().support("n", n as u64)).collect())
},
});
let set = ask(&["--subsystem", "greedy", "--limit", "5"]);
let answer = only(&set);
assert_eq!(answer.returned, 5);
assert_eq!(answer.outcome, SnapshotOutcome::Partial);
assert!(answer.reason.as_deref().unwrap().contains("limit"));
}
#[wasm_lite::wasm_lite_test]
fn a_self_reported_partial_keeps_its_reason() {
let _session = session();
add_provider(Fixed {
name: "partial",
result: |_| {
ProviderResult::Partial(
vec![Row::new().support("n", 1u64)],
"one shard was locked".to_string(),
)
},
});
let set = ask(&["--subsystem", "partial"]);
let answer = only(&set);
assert_eq!(answer.outcome, SnapshotOutcome::Partial);
assert_eq!(answer.reason.as_deref(), Some("one shard was locked"));
}
#[wasm_lite::wasm_lite_test]
fn ignoring_the_deadline_is_reported_timed_out() {
let _session = session();
add_provider(Fixed {
name: "slow",
result: |_request| {
let start = Instant::now();
while start.elapsed() < Duration::from_millis(30) {
std::hint::spin_loop();
}
ProviderResult::Rows(vec![Row::new().support("n", 1u64)])
},
});
let set = ask(&["--subsystem", "slow", "--timeout-ms", "5"]);
let answer = only(&set);
assert_eq!(answer.outcome, SnapshotOutcome::TimedOut, "{answer:?}");
assert!(answer.reason.as_deref().unwrap().contains("deadline"));
}
#[wasm_lite::wasm_lite_test]
fn a_cooperative_provider_observes_the_deadline() {
let _session = session();
add_provider(Fixed {
name: "cooperative",
result: |request| {
let mut rows = Vec::new();
loop {
if request.should_stop() {
return ProviderResult::Partial(rows, "deadline".to_string());
}
rows.push(Row::new().support("n", rows.len() as u64));
if rows.len() > 5_000_000 {
return ProviderResult::Rows(rows);
}
}
},
});
let set = ask(&[
"--subsystem",
"cooperative",
"--timeout-ms",
"5",
"--limit",
"100000",
]);
let answer = only(&set);
assert_eq!(answer.outcome, SnapshotOutcome::Partial, "{answer:?}");
assert_eq!(answer.reason.as_deref(), Some("deadline"));
}
#[wasm_lite::wasm_lite_test]
fn local_only_fields_are_withheld_from_a_remote_view() {
let _session = session();
add_provider(Fixed {
name: "mixed",
result: |_| {
ProviderResult::Rows(vec![
Row::new()
.support("depth", 3u64)
.local("label", "user typed this"),
])
},
});
let remote = ask(&["--subsystem", "mixed"]);
let row = &only(&remote).rows[0];
assert_eq!(only(&remote).view, "remote");
assert_eq!(row.get("depth").unwrap().value, Some(SnapshotValue::U64(3)));
let label = row.get("label").expect("the field keeps its slot");
assert_eq!(label.privacy, FieldPrivacy::LocalOnly);
assert_eq!(label.value, None, "withheld from a remote view");
let local = ask(&["--subsystem", "mixed", "--view", "local"]);
let row = &only(&local).rows[0];
assert_eq!(
row.get("label").unwrap().value,
Some(SnapshotValue::String("user typed this".to_string()))
);
}
#[wasm_lite::wasm_lite_test]
fn an_unknown_subsystem_is_reported() {
let _session = session();
add_provider(Fixed {
name: "known",
result: |_| ProviderResult::Rows(Vec::new()),
});
let set = ask(&["--subsystem", "known,nope"]);
assert_eq!(set.unknown, vec!["nope".to_string()]);
assert_eq!(set.snapshots.len(), 1);
}
#[wasm_lite::wasm_lite_test]
fn registering_twice_replaces_rather_than_duplicates() {
let _session = session();
add_provider(Fixed {
name: "dup",
result: |_| ProviderResult::Rows(vec![Row::new().support("v", 1u64)]),
});
add_provider(Fixed {
name: "dup",
result: |_| ProviderResult::Rows(vec![Row::new().support("v", 2u64)]),
});
assert_eq!(registered().len(), 1);
let set = ask(&["--subsystem", "dup"]);
assert_eq!(
only(&set).rows[0].get("v").unwrap().value,
Some(SnapshotValue::U64(2))
);
}
#[wasm_lite::wasm_lite_test]
fn the_selector_reaches_the_provider_and_is_echoed() {
let _session = session();
add_provider(Fixed {
name: "select",
result: |request| {
ProviderResult::Rows(vec![
Row::new().support("saw", request.selector().unwrap_or("<none>")),
])
},
});
let set = ask(&["--subsystem", "select", "--id", "task-7"]);
let answer = only(&set);
assert_eq!(answer.selector.as_deref(), Some("task-7"));
assert_eq!(
answer.rows[0].get("saw").unwrap().value,
Some(SnapshotValue::String("task-7".to_string()))
);
}
#[wasm_lite::wasm_lite_test(worker)]
fn concurrent_mutation_still_yields_a_coherent_answer() {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let _session = session();
COUNTER.store(0, Ordering::SeqCst);
add_provider(Fixed {
name: "mutating",
result: |request| {
let mut rows = Vec::new();
for _ in 0..64 {
if request.should_stop() {
return ProviderResult::Partial(rows, "stopped".to_string());
}
rows.push(Row::new().support("seen", COUNTER.load(Ordering::SeqCst)));
}
ProviderResult::Rows(rows)
},
});
#[cfg(not(target_arch = "wasm32"))]
let writer = std::thread::spawn(|| {
for _ in 0..10_000 {
COUNTER.fetch_add(1, Ordering::SeqCst);
}
});
#[cfg(target_arch = "wasm32")]
let writer = wasm_lite_std::spawn(|| {
for _ in 0..10_000 {
COUNTER.fetch_add(1, Ordering::SeqCst);
}
});
let set = ask(&["--subsystem", "mutating", "--limit", "64"]);
writer.join().expect("writer");
let answer = only(&set);
assert!(answer.returned <= 64);
for row in &answer.rows {
assert!(matches!(
row.get("seen").unwrap().value,
Some(SnapshotValue::U64(_))
));
}
}
#[wasm_lite::wasm_lite_test]
fn list_names_the_registered_subsystems() {
let _session = session();
add_provider(Fixed {
name: "listed",
result: |_| panic!("--list must not run providers"),
});
let set = ask(&["--list"]);
let answer = only(&set);
assert_eq!(answer.subsystem, "registry");
assert!(
answer
.rows
.iter()
.any(|row| row.get("subsystem").unwrap().value
== Some(SnapshotValue::String("listed".to_string()))),
"{answer:?}"
);
}
#[wasm_lite::wasm_lite_test]
fn a_cancelled_request_stops() {
let _session = session();
add_provider(Fixed {
name: "never",
result: |_| panic!("a cancelled request must not reach a provider"),
});
let context = CommandContext::new(Arc::new(AtomicBool::new(true)), Arc::new(|_| Ok(())));
assert!(Snapshot.execute_with(Vec::new(), &context).is_err());
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn a_panicking_provider_is_reported_not_swallowed() {
let _session = session();
add_provider(Fixed {
name: "boom",
result: |_| panic!("deliberate provider panic"),
});
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let set = ask(&["--subsystem", "boom"]);
std::panic::set_hook(previous);
let answer = only(&set);
assert_eq!(answer.outcome, SnapshotOutcome::Panicked);
assert!(answer.reason.as_deref().unwrap().contains("bug"));
assert!(answer.rows.is_empty());
}
}