From 0234dbc94b6e0028f89f88ca30a53f5fb00c96bb Mon Sep 17 00:00:00 2001 From: Thanos Makatos Date: Thu, 16 Jul 2026 13:13:38 +0000 Subject: [PATCH] add the GetStats D-Bus method Expose the read-only `GetStats() -> s` D-Bus method in debug and release builds. It returns a JSON object containing the current tick and tracked VMs with their thread count, vCPU bound, manual sticky state, automatic- scaling eligibility, per-thread CPU utilisation, and cumulative I/O counters. Signed-off-by: Thanos Makatos --- README.md | 30 ++++++++++++++++++++++++++++++ src/controller.rs | 30 ++++++++++++++++++++++++++++++ src/dbus.rs | 17 +++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/README.md b/README.md index 956f488..63445a0 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,36 @@ instance's thread count. Setting `sticky=true` suppresses automatic scaling for that instance until a later call clears it. This debug-only override is kept in memory and is lost when the daemon restarts. +`GetStats()` returns a JSON fleet snapshot. For example: + +```json +{ + "tick": 42, + "vms": [{ + "vm": "vm-1", + "thread_count": 3, + "manual_scaling_sticky": false, + "scaling_allowed": true, + "vcpu_count": 8, + "per_thread_util": 0.72, + "read_io_count": 1000, + "write_io_count": 250, + "other_io_count": 0 + }] +} +``` + +```sh +busctl --system call \ + com.nutanix.io_thread_controller1 \ + /com/nutanix/io_thread_controller1 \ + com.nutanix.io_thread_controller1 \ + GetStats +``` + +`GetStats` is available in release builds and world-readable under the shipped +D-Bus policy. + ```sh busctl --system call \ com.nutanix.io_thread_controller1 \ diff --git a/src/controller.rs b/src/controller.rs index 295a1f6..b70746b 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -282,6 +282,36 @@ impl Controller { }; let _ = reply.send(result); } + DbusRequest::GetStats { reply } => { + let mut vms = Vec::with_capacity(self.instances.len()); + for (id, instance) in &self.instances { + let status = instance.status.read().await; + let (read_io_count, write_io_count, other_io_count) = match status.perf { + Some(perf) => { + (perf.read_io_count, perf.write_io_count, perf.other_io_count) + } + None => (0, 0, 0), + }; + vms.push(serde_json::json!({ + "vm": id, + "thread_count": status.thread_count, + "manual_scaling_sticky": status.manual_scaling_sticky, + "scaling_allowed": status.scaling_allowed, + "vcpu_count": status.vcpu_count, + "per_thread_util": status.per_thread_util, + // FIXME omit if status.perf.is_none()? + "read_io_count": read_io_count, + "write_io_count": write_io_count, + "other_io_count": other_io_count, + })); + } + let snapshot = serde_json::json!({ + "tick": self.tick_index, + "vms": vms, + }) + .to_string(); + let _ = reply.send(snapshot); + } DbusRequest::AddIoThread { vm, id, diff --git a/src/dbus.rs b/src/dbus.rs index c1c8c3f..34eee3f 100644 --- a/src/dbus.rs +++ b/src/dbus.rs @@ -11,6 +11,7 @@ //! //! Verbs currently exposed: //! * debug-only `SetThreadCount(vm, threads, sticky)`; +//! * read-only `GetStats()`; //! * named-IOThread and virtqueue-mapping operations for clients that support //! them. @@ -50,6 +51,11 @@ pub enum DbusRequest { /// Structured snapshot of every tracked instance. The reply is a JSON /// string so the wire signature stays a bare `s` and the payload shape can /// evolve without D-Bus IDL churn. Field contract is documented on + /// JSON snapshot of all tracked VMs. + GetStats { + /// Reply channel carrying the serialized snapshot. + reply: oneshot::Sender, + }, /// [`crate::controller::SnapshotPayload`]. GetSnapshot { /// Reply channel; JSON-encoded @@ -152,6 +158,17 @@ impl Service { Err(e) => Err(zbus::fdo::Error::Failed(e)), } } + + /// Return the controller's machine-readable fleet snapshot. + async fn get_stats(&self) -> zbus::fdo::Result { + let (tx, rx) = oneshot::channel(); + self.tx + .send(DbusRequest::GetStats { reply: tx }) + .await + .map_err(|_| zbus::fdo::Error::Failed("controller channel closed".into()))?; + await_with_timeout(rx).await + } + /// D-Bus wire signature: `GetSnapshot() -> s`. /// /// The `s` return is a JSON payload matching