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
5,196 changes: 17 additions & 5,179 deletions app.py

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions gunicorn.conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Optional: use with gunicorn -c gunicorn.conf.py ...
# For Prometheus with multiple workers, set before starting gunicorn:
# export PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus_kernel_ai
# rm -rf "$PROMETHEUS_MULTIPROC_DIR" && mkdir -p "$PROMETHEUS_MULTIPROC_DIR"


def child_exit(server, worker):
"""Required for prometheus_client multiprocess mode (gunicorn -w N > 1)."""
try:
from prometheus_client import multiprocess

multiprocess.mark_process_dead(worker.pid)
except ImportError:
pass
5 changes: 5 additions & 0 deletions kernel_ai/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Linux Kernel Visualization Backend package."""

from kernel_ai.webapp import app, create_app

__all__ = ["app", "create_app"]
Binary file added kernel_ai/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added kernel_ai/__pycache__/config.cpython-310.pyc
Binary file not shown.
Binary file added kernel_ai/__pycache__/hooks.cpython-310.pyc
Binary file not shown.
Binary file not shown.
Binary file added kernel_ai/__pycache__/state.cpython-310.pyc
Binary file not shown.
Binary file added kernel_ai/__pycache__/webapp.cpython-310.pyc
Binary file not shown.
5 changes: 5 additions & 0 deletions kernel_ai/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""JSON API blueprints."""

from kernel_ai.api.rest import bp as api_bp

__all__ = ["api_bp"]
Binary file added kernel_ai/api/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added kernel_ai/api/__pycache__/rest.cpython-310.pyc
Binary file not shown.
132 changes: 132 additions & 0 deletions kernel_ai/api/rest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""
REST API under ``/api``. Implementations are in ``kernel_ai.webapp`` (lazy import).
"""
from flask import Blueprint

bp = Blueprint("api", __name__, url_prefix="/api")


def _core():
from kernel_ai import webapp as core

return core


@bp.route("/syscalls-realtime")
def syscalls_realtime():
return _core().syscalls_realtime()


@bp.route("/kernel-data")
def kernel_data():
return _core().kernel_data()


@bp.route("/process-kernel-map")
def process_kernel_map():
return _core().process_kernel_map()


@bp.route("/processes")
def get_processes():
return _core().get_processes()


@bp.route("/nginx-files")
def nginx_files():
return _core().nginx_files()


@bp.route("/active-connections")
def active_connections():
return _core().active_connections()


@bp.route("/traceroute")
def traceroute_info():
return _core().traceroute_info()


@bp.route("/network-stack-realtime")
def network_stack_realtime():
return _core().network_stack_realtime()


@bp.route("/devices-realtime")
def devices_realtime():
return _core().devices_realtime()


@bp.route("/filesystem-blocks")
def filesystem_blocks():
return _core().filesystem_blocks()


@bp.route("/isolation-context")
def isolation_context():
return _core().isolation_context()


@bp.route("/process/<int:pid>/threads")
def get_process_threads(pid):
return _core().get_process_threads(pid)


@bp.route("/process/<int:pid>/cpu")
def get_process_cpu(pid):
return _core().get_process_cpu(pid)


@bp.route("/process/<int:pid>/fds")
def get_process_fds(pid):
return _core().get_process_fds(pid)


@bp.route("/processes-detailed")
def get_processes_detailed():
return _core().get_processes_detailed()


@bp.route("/ipc-links")
def get_ipc_links():
return _core().get_ipc_links()


@bp.route("/proc-matrix")
def get_proc_matrix():
return _core().get_proc_matrix()


@bp.route("/proc-timeline")
def get_proc_timeline():
return _core().get_proc_timeline()


@bp.route("/execution-context")
def get_execution_context():
return _core().get_execution_context()


@bp.route("/kernel-dna")
def kernel_dna():
return _core().kernel_dna()


@bp.route("/crypto-realtime")
def crypto_realtime():
return _core().crypto_realtime()


@bp.route("/security-realtime")
def security_realtime():
return _core().security_realtime()


@bp.route("/processes-realtime")
def processes_realtime():
return _core().processes_realtime()


@bp.route("/frontend-logs", methods=["POST", "OPTIONS"])
def ingest_frontend_logs():
return _core().ingest_frontend_logs()
15 changes: 15 additions & 0 deletions kernel_ai/collectors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Low-level readers for /proc, /sys, and related paths (injectable in tests)."""

from kernel_ai.collectors.proc_fs import (
read_diskstats,
read_interrupt_lines,
read_tty_irq_total,
safe_read_text,
)

__all__ = [
"read_diskstats",
"read_interrupt_lines",
"read_tty_irq_total",
"safe_read_text",
]
Binary file not shown.
Binary file not shown.
82 changes: 82 additions & 0 deletions kernel_ai/collectors/proc_fs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
Filesystem collectors for kernel introspection.

Tests should patch functions in ``kernel_ai.collectors.proc_fs`` (or this module's
attributes) rather than the whole webapp.
"""

from __future__ import annotations


def safe_read_text(path: str) -> str | None:
"""Read a small text file; return None on error."""
try:
with open(path, "r", encoding="utf-8", errors="replace") as f:
return f.read().strip()
except (OSError, PermissionError, UnicodeError):
return None


def read_diskstats() -> dict:
"""Parse /proc/diskstats into name -> combined sectors (read+write) counters."""
stats: dict[str, int] = {}
try:
with open("/proc/diskstats", "r", encoding="utf-8", errors="replace") as f:
for line in f:
parts = line.split()
if len(parts) < 14:
continue
name = parts[2]
if name.startswith("loop") or name.startswith("ram"):
continue
try:
sectors_read = int(parts[5])
sectors_written = int(parts[9])
stats[name] = sectors_read + sectors_written
except ValueError:
continue
except OSError:
pass
return stats


def read_tty_irq_total() -> int:
"""Rough TTY/serial IRQ activity from /proc/interrupts."""
total = 0
try:
with open("/proc/interrupts", "r", encoding="utf-8", errors="replace") as f:
for line in f:
lower = line.lower()
if "tty" not in lower and "serial" not in lower:
continue
parts = line.split()
for token in parts[1:9]:
if token.isdigit():
total += int(token)
except OSError:
pass
return total


def read_interrupt_lines():
"""Return list of (line_lower, irq_sum_per_line) for /proc/interrupts."""
out = []
try:
with open("/proc/interrupts", "r", encoding="utf-8", errors="replace") as f:
for line in f:
if ":" not in line:
continue
raw = line.strip()
parts = raw.split()
if len(parts) < 2:
continue
irq_sum = 0
for token in parts[1:]:
if token.isdigit():
irq_sum += int(token)
else:
break
out.append((raw.lower(), irq_sum))
except OSError:
pass
return out
18 changes: 18 additions & 0 deletions kernel_ai/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Application configuration."""
import os
from pathlib import Path

# Project root (parent of ``kernel_ai`` package)
PROJECT_ROOT = Path(__file__).resolve().parent.parent


class Config:
"""Flask config loaded via app.config.from_object(Config)."""

DEBUG = os.getenv("FLASK_DEBUG", "False").lower() == "true"
ENV = os.getenv("FLASK_ENV", "production" if not DEBUG else "development")
STATIC_FOLDER = "static"
TEMPLATES_FOLDER = "templates"
API_PREFIX = "/api"
SEND_FILE_MAX_AGE_DEFAULT = 0 if not DEBUG else 31536000
PROJECT_ROOT = PROJECT_ROOT
34 changes: 34 additions & 0 deletions kernel_ai/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Global Flask hooks (CORS, cache headers)."""
from flask import current_app


def register_hooks(app):
"""Register after_request handler for CORS and static/HTML cache control."""

@app.after_request
def add_headers(response):
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Content-Type"

if response.content_type and "text/html" in response.content_type:
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "0"
return response

if response.content_type and (
"text/javascript" in response.content_type
or "application/javascript" in response.content_type
or "text/css" in response.content_type
or "image/" in response.content_type
):
if current_app.config["DEBUG"]:
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "0"
else:
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
if "Pragma" in response.headers:
del response.headers["Pragma"]
return response
5 changes: 5 additions & 0 deletions kernel_ai/http/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""HTTP route registration."""

from kernel_ai.http.register import register_http_routes

__all__ = ["register_http_routes"]
Binary file added kernel_ai/http/__pycache__/__init__.cpython-310.pyc
Binary file not shown.
Binary file added kernel_ai/http/__pycache__/register.cpython-310.pyc
Binary file not shown.
9 changes: 9 additions & 0 deletions kernel_ai/http/register.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Register Flask blueprints (called after all view implementations are defined in webapp)."""


def register_http_routes(app):
from kernel_ai.api.rest import bp as api_bp
from kernel_ai.views.pages import bp as pages_bp

app.register_blueprint(pages_bp)
app.register_blueprint(api_bp)
Loading
Loading