Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions kernel_ai/api/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
("/syscalls-realtime", "syscalls_realtime", h.syscalls_realtime, None),
("/syscall/<name>", "syscall_detail", h.syscall_detail, None),
("/irq/<irq>", "irq_detail", h.irq_detail, None),
("/irq/<irq>/history", "irq_history", h.irq_history, None),
("/kernel-data", "kernel_data", h.kernel_data, None),
("/io-pulse", "io_pulse", h.io_pulse, None),
("/process-kernel-map", "process_kernel_map", h.process_kernel_map, None),
Expand All @@ -16,6 +17,8 @@
("/io-open-files", "io_open_files", h.io_open_files, None),
("/active-connections", "active_connections", h.active_connections, None),
("/traceroute", "traceroute_info", h.traceroute_info, None),
("/flow-history", "flow_history", h.flow_history, None),
("/ip-entry", "ip_entry", h.ip_entry, None),
("/network-stack-realtime", "network_stack_realtime", h.network_stack_realtime, None),
("/devices-realtime", "devices_realtime", h.devices_realtime, None),
("/filesystem-blocks", "filesystem_blocks", h.filesystem_blocks, None),
Expand Down
6 changes: 6 additions & 0 deletions kernel_ai/http/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
io_open_files,
io_pulse,
irq_detail,
irq_history,
kernel_data,
kernel_dna,
ml_anomalies,
Expand All @@ -26,6 +27,8 @@
filesystem_blocks,
hot_files,
isolation_context,
flow_history,
ip_entry,
network_stack_realtime,
path_walk,
traceroute_info,
Expand Down Expand Up @@ -63,6 +66,8 @@
"devices_realtime",
"ext4_anatomy",
"ext4_journal",
"flow_history",
"ip_entry",
"filesystem_blocks",
"hot_files",
"path_walk",
Expand All @@ -87,6 +92,7 @@
"io_open_files",
"io_pulse",
"irq_detail",
"irq_history",
"isolation_context",
"kernel_data",
"kernel_dna",
Expand Down
14 changes: 14 additions & 0 deletions kernel_ai/http/api_handlers/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ def _payload():
return api_json(_payload)


def irq_history(irq):
"""Lifetime of one interrupt line versus host uptime."""

def _payload():
from kernel_ai.services import irq_anatomy

anatomy = irq_anatomy.history(irq)
if not anatomy or not anatomy.get("found"):
return {"timestamp": datetime.now().isoformat(), "irq": str(irq), "found": False}
return {"timestamp": datetime.now().isoformat(), **anatomy}

return api_json(_payload)


def irq_detail(irq):
"""What one interrupt line is on this machine.

Expand Down
28 changes: 28 additions & 0 deletions kernel_ai/http/api_handlers/network_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,34 @@ def active_connections():
return api_json(lambda: {"connections": _network_service.get_active_connections()})


def ip_entry():
def _payload():
kind = request.args.get("kind", "").strip()
if kind not in {"neigh", "route"}:
raise ValueError("kind must be neigh or route")
return _network_service.get_ip_entry(
kind=kind,
ip=request.args.get("ip"),
destination=request.args.get("destination"),
gateway=request.args.get("gateway"),
iface=request.args.get("iface"),
)

return api_json(_payload, exception_statuses=[(ValueError, 400)])


def flow_history():
def _payload():
local = request.args.get("local", "").strip()
remote = request.args.get("remote", "").strip()
proto = request.args.get("proto", "TCP").strip()
if not local or not remote:
raise ValueError("Missing 'local' or 'remote' query parameter")
return _network_service.get_flow_history(local=local, remote=remote, proto=proto)

return api_json(_payload, exception_statuses=[(ValueError, 400)])


def traceroute_info():
def _payload():
state = get_state_container(current_app)
Expand Down
74 changes: 74 additions & 0 deletions kernel_ai/services/irq_anatomy.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,80 @@
return chain


def _uptime_s():
try:
with open("/proc/uptime", "r", encoding="utf-8", errors="ignore") as fh:
return float(fh.read().split()[0])
except (OSError, ValueError, IndexError):
return None


def history(irq):

Check failure on line 246 in kernel_ai/services/irq_anatomy.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=devops-stack_ring-0&issues=AaAueU3PkY5_UfzfDSob&open=AaAueU3PkY5_UfzfDSob&pullRequest=165
"""Biography of one interrupt line: lifetime count versus the host's age.

The anatomy card is the path into the kernel. This is how that line has
lived since boot — how many times it rang, what share of every interrupt
that is, which CPU took most of them, and the mean rate over uptime.
A wall-clock of the first fire is not something the kernel keeps.
"""
irq = str(irq).strip()
if not re.fullmatch(r"[A-Za-z0-9_-]{1,16}", irq):
return {"found": False, "irq": irq}

interrupts = read_interrupts()
if irq not in interrupts:
return {"found": False, "irq": irq}

counts, desc = interrupts[irq]
total = sum(counts)
host_total = sum(sum(row_counts) for row_counts, _ in interrupts.values())
uptime = _uptime_s()
online = _cpus_online()
per_cpu = counts[:online] if len(counts) >= online else counts
top_cpu = None
top_count = 0
if per_cpu:
top_cpu = max(range(len(per_cpu)), key=lambda i: per_cpu[i])
top_count = int(per_cpu[top_cpu])

payload = {
"found": True,
"irq": irq,
"label": desc,
"kind": "line" if irq.isdigit() else "aggregate",
"total": total,
"host_total": host_total,
"share": round(total / host_total, 5) if host_total else None,
"uptime_s": uptime,
"lifetime_per_sec": round(total / uptime, 4) if uptime and uptime > 0 else None,
"top_cpu": top_cpu,
"top_cpu_count": top_count,
"top_cpu_share": round(top_count / total, 4) if total else None,
"device": None,
"chip": None,
"softirq": None,
"source": "/proc/interrupts · /proc/uptime",
}

if irq.isdigit():
device = _read(f"{_SYS_IRQ}/{irq}/actions")
chip = _read(f"{_SYS_IRQ}/{irq}/chip_name")
payload["device"] = device or None
payload["chip"] = chip or None
vector = vector_for(device or desc, chip)
if vector:
softirqs = read_softirqs()
payload["softirq"] = {
"vector": vector,
"total": softirqs.get(vector),
"basis": "driver class",
}
else:
payload["device"] = desc.lower() if desc else None

return payload


def describe(irq):
"""Everything the card shows about one line, or None if there is no such line."""
irq = str(irq).strip()
Expand Down
Loading
Loading