System Analysis¶
systemd-client wraps systemd-analyze to give you boot performance profiling, security auditing, unit verification, and critical chain analysis -- all from Python.
Let's walk through each analysis tool.
Boot Blame¶
Find out which units are slowing down your boot with analyze_blame():
from systemd_client import SystemdClient
client = SystemdClient()
entries = client.analyze_blame() # (1)!
# Top 10 slowest units
for entry in entries[:10]: # (2)!
seconds = entry.time_us / 1_000_000
print(f" {seconds:>8.3f}s {entry.unit}")
- Returns a
list[BlameEntry]sorted by initialization time, slowest first. - Each
BlameEntryhastime_us(microseconds) andunit(the unit name).
$ python blame.py
32.875s pmlogger.service
12.456s NetworkManager-wait-online.service
8.234s docker.service
5.678s postgresql.service
3.456s nginx.service
2.345s snapd.service
1.234s ssh.service
0.987s systemd-journal-flush.service
0.654s systemd-tmpfiles-setup.service
0.321s dbus.service
Tip
Focus your optimization efforts on the top entries. A service that takes 30+ seconds to start is often waiting on a network dependency or misconfigured timeout.
BlameEntry Fields¶
| Field | Type | Description |
|---|---|---|
time_us |
int |
Initialization time in microseconds |
unit |
str |
Unit name |
Example: Find Units Slower Than N Seconds¶
from systemd_client import SystemdClient
client = SystemdClient()
slow = [e for e in client.analyze_blame() if e.time_us > 5_000_000] # (1)!
print(f"{len(slow)} units took more than 5 seconds to start:")
for entry in slow:
print(f" {entry.time_us / 1e6:.1f}s {entry.unit}")
- Filter entries where
time_usexceeds 5 million microseconds (5 seconds).
Security Analysis¶
Audit the security hardening of any service unit with analyze_security():
from systemd_client import SystemdClient
client = SystemdClient()
analysis = client.analyze_security("my-app.service") # (1)!
# Exposure score: 0.0 (fully hardened) to 10.0 (no hardening)
print(f"Unit: {analysis.unit}")
print(f"Exposure: {analysis.exposure:.1f}/10.0") # (2)!
# Individual issues
for issue in analysis.issues[:10]:
print(f" [{issue.severity}] {issue.description}")
print(f" Current value: {issue.value}")
- Returns a
SecurityAnalysiswith an overall exposure score and a list of individual issues. - Lower is better. Score ranges:
0-2excellent,2-5medium,5-10needs attention.
$ python security.py
Unit: my-app.service
Exposure: 7.2/10.0
[MEDIUM] PrivateTmp is not set
Current value: no
[MEDIUM] ProtectSystem is not set
Current value: no
[LOW] NoNewPrivileges is not set
Current value: no
Interpreting Exposure Scores¶
| Score | Rating | Action |
|---|---|---|
| 0.0 - 2.0 | Excellent | Fully hardened, no changes needed |
| 2.0 - 5.0 | Medium | Review flagged issues |
| 5.0 - 7.5 | Exposed | Apply recommended hardening directives |
| 7.5 - 10.0 | Unsafe | Immediate attention needed |
Info
The score is calculated by systemd based on which security directives are set in the unit file. Directives like PrivateTmp=, ProtectSystem=, NoNewPrivileges=, and CapabilityBoundingSet= all contribute to a lower (better) score.
SecurityAnalysis Fields¶
| Field | Type | Description |
|---|---|---|
unit |
str |
Unit name |
exposure |
float |
Overall exposure score (0.0--10.0) |
issues |
list[SecurityIssue] |
Individual security findings |
SecurityIssue Fields¶
| Field | Type | Description |
|---|---|---|
id |
str |
Issue identifier |
description |
str |
Human-readable description |
severity |
str |
Severity level (e.g., "MEDIUM", "LOW") |
value |
str |
Current value of the setting |
Example: Audit All Active Services¶
from systemd_client import SystemdClient
client = SystemdClient()
services = client.list_units(unit_type="service", state="active")
print(f"{'Unit':<40} {'Score':>6}")
print("-" * 48)
for svc in services:
try:
analysis = client.analyze_security(svc.name)
score = analysis.exposure
indicator = "!!" if score >= 7 else ".." if score >= 4 else "ok"
print(f"{svc.name:<40} {score:>5.1f} {indicator}")
except Exception:
print(f"{svc.name:<40} N/A")
Warning
Security analysis requires the unit file to be on disk. It will not work for transient units or units that have been masked.
Unit Verification¶
Validate unit file syntax and configuration before deploying with analyze_verify():
from systemd_client import SystemdClient
client = SystemdClient()
messages = client.analyze_verify("my-app.service") # (1)!
if not messages:
print("my-app.service: OK") # (2)!
else:
print(f"Found {len(messages)} issue(s):")
for msg in messages:
print(f" {msg}")
- Returns a
list[str]of diagnostic messages. An empty list means the unit file is valid. - A clean verification means the unit file has no syntax errors and all referenced paths/units exist.
$ python verify.py
Found 2 issue(s):
my-app.service: Unknown key name 'ExecStrat' in section 'Service'
my-app.service: Unit configured to use Type=notify, but no WatchdogSec= setting
Pre-Deploy Check Pattern¶
Use verification as a gate before deploying new unit files:
from systemd_client import SystemdClient, ServiceBuilder
def safe_deploy(name: str, exec_cmd: str) -> bool:
client = SystemdClient()
# Build and install
unit = ServiceBuilder(name).exec_start(exec_cmd).wanted_by("default.target").build()
path = client.install(unit)
# Verify before enabling
messages = client.analyze_verify(f"{name}.service")
if messages:
print(f"Verification failed for {name}.service:")
for msg in messages:
print(f" {msg}")
client.uninstall(f"{name}.service") # (1)!
return False
# All good -- enable and start
client.daemon_reload()
client.enable(f"{name}.service")
client.start(f"{name}.service")
print(f"Deployed {name}.service from {path}")
return True
- If verification fails, clean up by removing the unit file immediately.
Tip
Always run analyze_verify() after installing a unit file and before enabling it. This catches typos and configuration errors before they cause runtime failures.
Critical Chain¶
Visualize the critical path of unit startup -- the chain of units that determined boot time:
from systemd_client import SystemdClient
client = SystemdClient()
# Full critical chain
chain = client.analyze_critical_chain() # (1)!
print(chain)
# Critical chain for a specific unit
chain = client.analyze_critical_chain("my-app.service") # (2)!
print(chain)
- Returns the raw text output from
systemd-analyze critical-chain. Shows the tree of dependencies that formed the longest startup path. - Pass a unit name to see the critical chain leading to that specific unit.
$ python critical_chain.py
The time when unit became active or started is printed after the "@" character.
The time the unit took to start is printed after the "+" character.
graphical.target @12.345s
multi-user.target @12.300s
my-app.service @8.100s +4.200s
network-online.target @7.900s
NetworkManager-wait-online.service @2.100s +5.800s
Info
The critical chain shows you why your system took as long as it did to boot. Each line shows when the unit became active (@) and how long it took (+). The chain always ends at the unit that completed last.
Putting It Together: Full System Audit¶
Here's a complete audit script that combines all four analysis tools:
from systemd_client import SystemdClient
def full_audit() -> None:
client = SystemdClient()
# 1. Boot performance
print("=== Boot Blame (Top 5) ===")
for entry in client.analyze_blame()[:5]:
print(f" {entry.time_us / 1e6:>8.3f}s {entry.unit}")
print()
# 2. Critical chain
print("=== Critical Chain ===")
chain = client.analyze_critical_chain()
for line in chain.splitlines()[:10]:
print(f" {line}")
print()
# 3. Security audit on active services
print("=== Security Audit ===")
active = client.list_units(unit_type="service", state="active")
for svc in active[:5]:
try:
analysis = client.analyze_security(svc.name)
print(f" {svc.name:<35} {analysis.exposure:.1f}/10.0")
except Exception:
pass
print()
# 4. Verify all enabled service files
print("=== Unit Verification ===")
files = client.list_unit_files(unit_type="service", state="enabled")
errors = 0
for f in files:
messages = client.analyze_verify(f.name)
if messages:
errors += 1
print(f" {f.name}: {len(messages)} issue(s)")
if errors == 0:
print(" All enabled services verified OK")
full_audit()
Check
This script gives you a complete picture of boot performance, startup dependencies, security posture, and configuration validity in one run.
CLI Commands¶
All analysis tools are available from the command line.
Blame¶
$ systemd-client analyze-blame
32.875s pmlogger.service
12.456s NetworkManager-wait-online.service
8.234s docker.service
Security¶
$ systemd-client analyze-security my-app.service
my-app.service: 7.2/10.0 exposure
[MEDIUM] PrivateTmp is not set: no
[MEDIUM] ProtectSystem is not set: no
Verify¶
$ systemd-client analyze-verify my-app.service
my-app.service: Unknown key name 'ExecStrat' in section 'Service'
A clean unit file produces:
JSON Output¶
All analysis commands support --json for scripting: