systemd applet: aggregate counts + lazy running list (step 2)
This commit is contained in:
parent
6fc7e8bc8a
commit
3fb9a36f3b
4 changed files with 210 additions and 123 deletions
|
|
@ -1,11 +1,10 @@
|
|||
// In-process systemd state for nova-shell.
|
||||
//
|
||||
// Lists of units and machines are exposed to QML as JSON-encoded QString props
|
||||
// (`failedUnitsJson`, `containersJson`) rather than QList<QVariantMap>. cxx-qt
|
||||
// 0.8.1 does not implement QVariantValue for QVariantMap/QVariantList, and
|
||||
// cxx-qt main on git regressed qt-build-utils to require QuickControls2.prl
|
||||
// files that nixpkgs strips. Switch to QList<QVariantMap> when a release ships
|
||||
// with both fixes.
|
||||
// The machine tree is exposed to QML as a JSON-encoded QString (`machinesJson`)
|
||||
// rather than QList<QVariantMap>. cxx-qt 0.8.1 does not implement QVariantValue
|
||||
// for QVariantMap/QVariantList, and cxx-qt main on git regressed qt-build-utils
|
||||
// to require QuickControls2.prl files that nixpkgs strips. Switch to
|
||||
// QList<QVariantMap> when a release ships with both fixes.
|
||||
|
||||
use core::pin::Pin;
|
||||
use cxx_qt_lib::QString;
|
||||
|
|
@ -26,13 +25,13 @@ pub mod qobject {
|
|||
#[qml_element]
|
||||
#[qml_singleton]
|
||||
#[qproperty(QString, hostname)]
|
||||
#[qproperty(QString, system_state, cxx_name = "systemState")]
|
||||
#[qproperty(QString, user_state, cxx_name = "userState")]
|
||||
// Local failed unit count (drives the bar module label).
|
||||
#[qproperty(i32, failed_count, cxx_name = "failedCount")]
|
||||
// JSON array: [{ name, description, subState, scope: "system"|"user", machine: "" | name }]
|
||||
#[qproperty(QString, failed_units_json, cxx_name = "failedUnitsJson")]
|
||||
// JSON array: [{ name, class, service, systemState, failedUnits: [...] }]
|
||||
#[qproperty(QString, containers_json, cxx_name = "containersJson")]
|
||||
// JSON array, local first then nspawn containers. Each entry:
|
||||
// { name, isLocal, marker, systemState, runningCount, totalCount,
|
||||
// failedUnits: [{name, description, subState, scope, machine}],
|
||||
// runningUnits: [...] }
|
||||
#[qproperty(QString, machines_json, cxx_name = "machinesJson")]
|
||||
type SystemdService = super::SystemdServiceRust;
|
||||
|
||||
#[qinvokable]
|
||||
|
|
@ -46,7 +45,7 @@ pub mod qobject {
|
|||
impl cxx_qt::Initialize for SystemdService {}
|
||||
}
|
||||
|
||||
// systemd1.Manager.ListUnitsFiltered returns a(ssssssouso): name, description,
|
||||
// systemd1.Manager.ListUnits returns a(ssssssouso): name, description,
|
||||
// load_state, active_state, sub_state, follower, unit_path, job_id, job_type, job_path.
|
||||
type UnitTuple = (
|
||||
String,
|
||||
|
|
@ -70,7 +69,7 @@ trait SystemdManager {
|
|||
#[zbus(property)]
|
||||
fn system_state(&self) -> zbus::Result<String>;
|
||||
|
||||
fn list_units_filtered(&self, states: Vec<&str>) -> zbus::Result<Vec<UnitTuple>>;
|
||||
fn list_units(&self) -> zbus::Result<Vec<UnitTuple>>;
|
||||
|
||||
fn restart_unit(&self, name: &str, mode: &str)
|
||||
-> zbus::Result<zbus::zvariant::OwnedObjectPath>;
|
||||
|
|
@ -88,44 +87,45 @@ trait Machined {
|
|||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct UnitJson<'a> {
|
||||
name: &'a str,
|
||||
description: &'a str,
|
||||
struct UnitJson {
|
||||
name: String,
|
||||
description: String,
|
||||
#[serde(rename = "subState")]
|
||||
sub_state: &'a str,
|
||||
scope: &'a str,
|
||||
machine: &'a str,
|
||||
sub_state: String,
|
||||
scope: String,
|
||||
machine: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ContainerJson<'a> {
|
||||
name: &'a str,
|
||||
class: &'a str,
|
||||
service: &'a str,
|
||||
struct MachineJson {
|
||||
name: String,
|
||||
#[serde(rename = "isLocal")]
|
||||
is_local: bool,
|
||||
marker: String,
|
||||
#[serde(rename = "systemState")]
|
||||
system_state: &'a str,
|
||||
system_state: String,
|
||||
#[serde(rename = "runningCount")]
|
||||
running_count: i32,
|
||||
#[serde(rename = "totalCount")]
|
||||
total_count: i32,
|
||||
#[serde(rename = "failedUnits")]
|
||||
failed_units: Vec<UnitJson<'a>>,
|
||||
failed_units: Vec<UnitJson>,
|
||||
#[serde(rename = "runningUnits")]
|
||||
running_units: Vec<UnitJson>,
|
||||
}
|
||||
|
||||
pub struct SystemdServiceRust {
|
||||
hostname: QString,
|
||||
system_state: QString,
|
||||
user_state: QString,
|
||||
failed_count: i32,
|
||||
failed_units_json: QString,
|
||||
containers_json: QString,
|
||||
machines_json: QString,
|
||||
}
|
||||
|
||||
impl Default for SystemdServiceRust {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hostname: QString::from(read_hostname()),
|
||||
system_state: QString::from("unknown"),
|
||||
user_state: QString::from("unknown"),
|
||||
failed_count: 0,
|
||||
failed_units_json: QString::from("[]"),
|
||||
containers_json: QString::from("[]"),
|
||||
machines_json: QString::from("[]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -146,22 +146,47 @@ fn rt() -> &'static Runtime {
|
|||
})
|
||||
}
|
||||
|
||||
async fn fetch_failed(bus: &Connection) -> (String, Vec<UnitTuple>) {
|
||||
async fn fetch_units(bus: &Connection) -> (String, Vec<UnitTuple>) {
|
||||
let mut state = String::from("unknown");
|
||||
let mut units = Vec::new();
|
||||
if let Ok(mgr) = SystemdManagerProxy::new(bus).await {
|
||||
if let Ok(s) = mgr.system_state().await {
|
||||
state = s;
|
||||
}
|
||||
if let Ok(u) = mgr.list_units_filtered(vec!["failed"]).await {
|
||||
if let Ok(u) = mgr.list_units().await {
|
||||
units = u;
|
||||
}
|
||||
}
|
||||
(state, units)
|
||||
}
|
||||
|
||||
// Partition a unit list by active_state. (failed, running)
|
||||
fn partition_units(
|
||||
units: Vec<UnitTuple>,
|
||||
scope: &str,
|
||||
machine: &str,
|
||||
) -> (Vec<UnitJson>, Vec<UnitJson>, i32) {
|
||||
let total = units.len() as i32;
|
||||
let mut failed = Vec::new();
|
||||
let mut running = Vec::new();
|
||||
for u in units {
|
||||
let entry = UnitJson {
|
||||
name: u.0,
|
||||
description: u.1,
|
||||
sub_state: u.4,
|
||||
scope: scope.to_string(),
|
||||
machine: machine.to_string(),
|
||||
};
|
||||
match u.3.as_str() {
|
||||
"failed" => failed.push(entry),
|
||||
"active" => running.push(entry),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
(failed, running, total)
|
||||
}
|
||||
|
||||
async fn poll_async() -> (
|
||||
String,
|
||||
String,
|
||||
Vec<UnitTuple>,
|
||||
Vec<UnitTuple>,
|
||||
|
|
@ -169,12 +194,11 @@ async fn poll_async() -> (
|
|||
) {
|
||||
let mut sys_state = String::from("unknown");
|
||||
let mut sys_units = Vec::new();
|
||||
let mut user_state = String::from("unknown");
|
||||
let mut user_units = Vec::new();
|
||||
let mut machines = Vec::new();
|
||||
|
||||
if let Ok(c) = Connection::system().await {
|
||||
let (s, u) = fetch_failed(&c).await;
|
||||
let (s, u) = fetch_units(&c).await;
|
||||
sys_state = s;
|
||||
sys_units = u;
|
||||
if let Ok(m) = MachinedProxy::new(&c).await {
|
||||
|
|
@ -188,25 +212,11 @@ async fn poll_async() -> (
|
|||
}
|
||||
}
|
||||
if let Ok(c) = Connection::session().await {
|
||||
let (s, u) = fetch_failed(&c).await;
|
||||
user_state = s;
|
||||
let (_, u) = fetch_units(&c).await;
|
||||
user_units = u;
|
||||
}
|
||||
|
||||
(sys_state, user_state, sys_units, user_units, machines)
|
||||
}
|
||||
|
||||
fn unit_jsons<'a>(units: &'a [UnitTuple], scope: &'a str, machine: &'a str) -> Vec<UnitJson<'a>> {
|
||||
units
|
||||
.iter()
|
||||
.map(|u| UnitJson {
|
||||
name: &u.0,
|
||||
description: &u.1,
|
||||
sub_state: &u.4,
|
||||
scope,
|
||||
machine,
|
||||
})
|
||||
.collect()
|
||||
(sys_state, sys_units, user_units, machines)
|
||||
}
|
||||
|
||||
impl cxx_qt::Initialize for qobject::SystemdService {
|
||||
|
|
@ -217,33 +227,54 @@ impl cxx_qt::Initialize for qobject::SystemdService {
|
|||
|
||||
impl qobject::SystemdService {
|
||||
fn poll(mut self: Pin<&mut Self>) {
|
||||
let (sys_state, user_state, sys_units, user_units, machines) = rt().block_on(poll_async());
|
||||
let (sys_state, sys_units, user_units, machines) = rt().block_on(poll_async());
|
||||
|
||||
let mut all_failed: Vec<UnitJson> = Vec::with_capacity(sys_units.len() + user_units.len());
|
||||
all_failed.extend(unit_jsons(&sys_units, "system", ""));
|
||||
all_failed.extend(unit_jsons(&user_units, "user", ""));
|
||||
let count = all_failed.len() as i32;
|
||||
let failed_json = serde_json::to_string(&all_failed).unwrap_or_else(|_| "[]".into());
|
||||
let (sys_failed, sys_running, sys_total) = partition_units(sys_units, "system", "");
|
||||
let (user_failed, user_running, user_total) = partition_units(user_units, "user", "");
|
||||
|
||||
let containers: Vec<ContainerJson> = machines
|
||||
.iter()
|
||||
.map(|(n, c, s)| ContainerJson {
|
||||
name: n,
|
||||
class: c,
|
||||
service: s,
|
||||
system_state: "unknown",
|
||||
let mut failed: Vec<UnitJson> = Vec::with_capacity(sys_failed.len() + user_failed.len());
|
||||
failed.extend(sys_failed);
|
||||
failed.extend(user_failed);
|
||||
let mut running: Vec<UnitJson> = Vec::with_capacity(sys_running.len() + user_running.len());
|
||||
running.extend(sys_running);
|
||||
running.extend(user_running);
|
||||
|
||||
let local_failed_count = failed.len() as i32;
|
||||
let local_running_count = running.len() as i32;
|
||||
let local_total_count = sys_total + user_total;
|
||||
|
||||
let local = MachineJson {
|
||||
name: read_hostname(),
|
||||
is_local: true,
|
||||
marker: "this machine".into(),
|
||||
system_state: sys_state,
|
||||
running_count: local_running_count,
|
||||
total_count: local_total_count,
|
||||
failed_units: failed,
|
||||
running_units: running,
|
||||
};
|
||||
|
||||
// Containers: enumerate only; unit fetching for containers comes in step 5.
|
||||
let mut all_machines = Vec::with_capacity(1 + machines.len());
|
||||
all_machines.push(local);
|
||||
for (name, _class, _service) in &machines {
|
||||
all_machines.push(MachineJson {
|
||||
name: name.clone(),
|
||||
is_local: false,
|
||||
marker: String::new(),
|
||||
system_state: "unknown".into(),
|
||||
running_count: 0,
|
||||
total_count: 0,
|
||||
failed_units: Vec::new(),
|
||||
})
|
||||
.collect();
|
||||
let containers_json = serde_json::to_string(&containers).unwrap_or_else(|_| "[]".into());
|
||||
running_units: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
self.as_mut().set_system_state(QString::from(sys_state));
|
||||
self.as_mut().set_user_state(QString::from(user_state));
|
||||
let machines_json = serde_json::to_string(&all_machines).unwrap_or_else(|_| "[]".into());
|
||||
|
||||
self.as_mut().set_failed_count(local_failed_count);
|
||||
self.as_mut()
|
||||
.set_failed_units_json(QString::from(failed_json));
|
||||
self.as_mut().set_failed_count(count);
|
||||
self.as_mut()
|
||||
.set_containers_json(QString::from(containers_json));
|
||||
.set_machines_json(QString::from(machines_json));
|
||||
}
|
||||
|
||||
fn restart_unit(self: Pin<&mut Self>, name: QString, scope: QString, machine: QString) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue