A live restart of the system bus during an upgrade hung every login on a workstation until a reboot. The module owns the bus's packages, declares the bus running with no restart or reload trigger, publishes only curated events (health, services, denials; never traffic) and serves tools to look at both buses.
92 lines
2.8 KiB
Go
92 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// CountConnections reads the bus driver's Debug.Stats answer (`busctl call … GetStats --json=short`):
|
|
// dbus-broker lists one accounting entry per peer, the reference daemon says ActiveConnections.
|
|
func CountConnections(out string) (int, bool) {
|
|
var reply struct {
|
|
Data []map[string]struct {
|
|
Data json.RawMessage `json:"data"`
|
|
} `json:"data"`
|
|
}
|
|
if json.Unmarshal([]byte(strings.TrimSpace(out)), &reply) != nil || len(reply.Data) != 1 {
|
|
return 0, false
|
|
}
|
|
if v, ok := reply.Data[0]["org.bus1.DBus.Debug.Stats.PeerAccounting"]; ok {
|
|
var peers []json.RawMessage
|
|
if json.Unmarshal(v.Data, &peers) == nil {
|
|
return len(peers), true
|
|
}
|
|
}
|
|
if v, ok := reply.Data[0]["ActiveConnections"]; ok {
|
|
var n int
|
|
if json.Unmarshal(v.Data, &n) == nil {
|
|
return n, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// CountUniqueNames counts the connections among ListNames' answer (`busctl call … ListNames`).
|
|
func CountUniqueNames(out string) (int, bool) {
|
|
var reply struct {
|
|
Data [][]string `json:"data"`
|
|
}
|
|
if json.Unmarshal([]byte(strings.TrimSpace(out)), &reply) != nil || len(reply.Data) != 1 {
|
|
return 0, false
|
|
}
|
|
n := 0
|
|
for _, name := range reply.Data[0] {
|
|
if strings.HasPrefix(name, ":") {
|
|
n++
|
|
}
|
|
}
|
|
return n, true
|
|
}
|
|
|
|
// Health is the system bus's health now: a ping through the watcher's connection, how many
|
|
// connections the bus has, and how long it has run.
|
|
func (m *Machine) Health(ctx context.Context, w *Watcher) map[string]any {
|
|
h := map[string]any{"bus": "system"}
|
|
if w != nil {
|
|
took, err := w.PingNow()
|
|
if err != nil {
|
|
h["ping"] = "no answer: " + err.Error()
|
|
} else {
|
|
h["ping_ms"] = float64(took.Microseconds()) / 1000
|
|
}
|
|
h["watcher"] = w.Snapshot()
|
|
}
|
|
// Debug.Stats is root's on the system bus; asked as the account it is refused and logged as a
|
|
// denial, which the watcher would then publish. So it is asked through sudo -n or not at all.
|
|
call := []string{"busctl", "--system", "--json=short", "--no-pager", "call", busName, busPath}
|
|
if out, err := m.privileged(ctx, call[0], append(call[1:], busName+".Debug.Stats", "GetStats")...); err == nil {
|
|
if n, ok := CountConnections(out); ok {
|
|
h["connections"], h["connections_from"] = n, "the bus's Debug.Stats"
|
|
}
|
|
}
|
|
if _, ok := h["connections"]; !ok {
|
|
if out, err := m.Run(ctx, call[0], append(call[1:], busName, "ListNames")...); err == nil {
|
|
if n, ok := CountUniqueNames(out); ok {
|
|
h["connections"], h["connections_from"] = n, "the unique names on the bus (Debug.Stats needs sudo -n)"
|
|
}
|
|
}
|
|
}
|
|
if u, err := m.SystemBusUnit(ctx); err != nil {
|
|
h["unit"] = err.Error()
|
|
} else {
|
|
h["unit"], h["active"], h["pid"] = u.Unit, u.Active, u.PID
|
|
if !u.Since.IsZero() {
|
|
h["started"] = u.Started
|
|
h["uptime"] = m.Now().Sub(u.Since).Round(time.Second).String()
|
|
}
|
|
}
|
|
return h
|
|
}
|