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
20 changes: 16 additions & 4 deletions kernel_ai/services/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,22 @@
import psutil

from kernel_ai.services.infra_utils import resolve_binary
from kernel_ai.state import NETWORK_STACK_PREV, TRACEROUTE_CACHE, TRACEROUTE_CACHE_TTL_SECONDS

logger = logging.getLogger(__name__)

_NETWORK_STACK_PREV_DEFAULT = {
"timestamp": None,
"tcpext_retrans": None,
"ip_in": None,
"ip_out": None,
"ip_discards": None,
"iface_rx": None,
"iface_tx": None,
"iface_drops": None,
}
_TRACEROUTE_CACHE_DEFAULT = {}
_TRACEROUTE_CACHE_TTL_SECONDS_DEFAULT = 60


def get_active_connections():
"""Get active network connections."""
Expand Down Expand Up @@ -200,7 +212,7 @@ def _get_ss_tcp_metrics():


def get_network_stack_realtime(network_stack_prev=None):
network_stack_prev = NETWORK_STACK_PREV if network_stack_prev is None else network_stack_prev
network_stack_prev = _NETWORK_STACK_PREV_DEFAULT if network_stack_prev is None else network_stack_prev
now = time.time()
iface = _get_default_iface()
pernic = psutil.net_io_counters(pernic=True)
Expand Down Expand Up @@ -377,8 +389,8 @@ def get_route_hint(remote_ip):


def get_traceroute_info(remote_ip, max_hops=8, traceroute_cache=None, cache_ttl_seconds=None):
traceroute_cache = TRACEROUTE_CACHE if traceroute_cache is None else traceroute_cache
cache_ttl_seconds = TRACEROUTE_CACHE_TTL_SECONDS if cache_ttl_seconds is None else cache_ttl_seconds
traceroute_cache = _TRACEROUTE_CACHE_DEFAULT if traceroute_cache is None else traceroute_cache
cache_ttl_seconds = _TRACEROUTE_CACHE_TTL_SECONDS_DEFAULT if cache_ttl_seconds is None else cache_ttl_seconds
try:
target_ip = ipaddress.ip_address(remote_ip)
if target_ip.is_loopback or target_ip.is_unspecified:
Expand Down
8 changes: 6 additions & 2 deletions kernel_ai/services/system_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
import psutil

from kernel_ai.collectors import proc_fs as _proc_fs
from kernel_ai.state import FILESYSTEM_PREV

logger = logging.getLogger(__name__)

_FILESYSTEM_PREV_DEFAULT = {
"timestamp": None,
"write_bytes": None,
}


def get_filesystem_blocks(filesystem_prev=None):
filesystem_prev = FILESYSTEM_PREV if filesystem_prev is None else filesystem_prev
filesystem_prev = _FILESYSTEM_PREV_DEFAULT if filesystem_prev is None else filesystem_prev
now = time.time()
try:
usage = psutil.disk_usage("/")
Expand Down
71 changes: 70 additions & 1 deletion kernel_ai/state.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"""Process-wide mutable state (caches, deltas for realtime metrics)."""
"""Process-wide mutable state and per-app state container."""

from __future__ import annotations

import os
from copy import deepcopy
from dataclasses import dataclass
from threading import Lock
from typing import Any

TRACEROUTE_CACHE = {}
TRACEROUTE_CACHE_TTL_SECONDS = 60
Expand Down Expand Up @@ -48,3 +54,66 @@
}
FRONTEND_LOG_WRITE_LOCK = Lock()
FRONTEND_LOG_FILE = os.getenv("FRONTEND_LOG_FILE", "/opt/ring0/kernel-ai/logs/frontend-events.jsonl")


@dataclass
class RuntimeState:
traceroute_cache: dict
traceroute_cache_ttl_seconds: int
network_stack_prev: dict
devices_prev: dict
filesystem_prev: dict
crypto_prev: dict
entropy_prev: dict
exec_context_prev: dict
security_prev: dict
frontend_log_write_lock: Lock
frontend_log_file: str


def create_state_container(frontend_log_file: str | None = None) -> RuntimeState:
"""Create isolated mutable runtime state for one Flask app instance."""
return RuntimeState(
traceroute_cache={},
traceroute_cache_ttl_seconds=TRACEROUTE_CACHE_TTL_SECONDS,
network_stack_prev=deepcopy(NETWORK_STACK_PREV),
devices_prev=deepcopy(DEVICES_PREV),
filesystem_prev=deepcopy(FILESYSTEM_PREV),
crypto_prev=deepcopy(CRYPTO_PREV),
entropy_prev=deepcopy(ENTROPY_PREV),
exec_context_prev=deepcopy(EXEC_CONTEXT_PREV),
security_prev=deepcopy(SECURITY_PREV),
frontend_log_write_lock=Lock(),
frontend_log_file=frontend_log_file or FRONTEND_LOG_FILE,
)


_LEGACY_STATE = RuntimeState(
traceroute_cache=TRACEROUTE_CACHE,
traceroute_cache_ttl_seconds=TRACEROUTE_CACHE_TTL_SECONDS,
network_stack_prev=NETWORK_STACK_PREV,
devices_prev=DEVICES_PREV,
filesystem_prev=FILESYSTEM_PREV,
crypto_prev=CRYPTO_PREV,
entropy_prev=ENTROPY_PREV,
exec_context_prev=EXEC_CONTEXT_PREV,
security_prev=SECURITY_PREV,
frontend_log_write_lock=FRONTEND_LOG_WRITE_LOCK,
frontend_log_file=FRONTEND_LOG_FILE,
)


def attach_state_container(app: Any, state: RuntimeState | None = None) -> RuntimeState:
"""Attach runtime state container to Flask app extensions."""
runtime_state = state or create_state_container()
app.extensions["kernel_ai_state"] = runtime_state
return runtime_state


def get_state_container(app: Any | None) -> RuntimeState:
"""Get app state when available, fallback to process-global legacy state."""
if app is not None:
runtime_state = getattr(app, "extensions", {}).get("kernel_ai_state")
if runtime_state is not None:
return runtime_state
return _LEGACY_STATE
2 changes: 2 additions & 0 deletions kernel_ai/webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from kernel_ai.hooks import register_hooks
from kernel_ai.http.register import register_http_routes
from kernel_ai.prometheus_setup import init_prometheus
from kernel_ai.state import attach_state_container

def _ensure_prometheus_mpdir():
# Gunicorn -w N: set PROMETHEUS_MULTIPROC_DIR before workers import this module.
Expand Down Expand Up @@ -39,6 +40,7 @@ def create_app():
template_folder=os.path.join(root, "templates"),
)
app.config.from_object(Config)
attach_state_container(app)
init_prometheus(app)
register_hooks(app)
register_http_routes(app)
Expand Down
Loading