Auto-commit via make git (triggered by NFDOS)
This commit is contained in:
parent
705fa58f3c
commit
88f340af14
@ -15,6 +15,7 @@ from .hippocampus import Hippocampus
|
||||
from .perception import Perception
|
||||
from .motor import Motor
|
||||
from .autodiagnostic import AutoDiagnostic
|
||||
from .telemetry import TelemetryV5 # <-- NOVO
|
||||
|
||||
from .neurotron_config import (
|
||||
NEUROTRON_MODE, NEUROTRON_TICK,
|
||||
@ -22,7 +23,6 @@ from .neurotron_config import (
|
||||
NEUROTRON_DIAG_EVERY_TICKS,
|
||||
NEUROTRON_DATASET_PATH,
|
||||
HEARTBEAT_ENABLED,
|
||||
NEUROTRON_THRESHOLDS,
|
||||
TELEMETRY_MAXLEN, TELEMETRY_FLUSH_EVERY_TICKS,
|
||||
)
|
||||
|
||||
@ -32,7 +32,6 @@ class Cortex:
|
||||
self.runtime_dir = Path(runtime_dir)
|
||||
self.log_dir = Path(log_dir)
|
||||
|
||||
# tick pode vir como string → convertemos sempre
|
||||
try:
|
||||
self.tick = float(tick_seconds)
|
||||
except:
|
||||
@ -55,13 +54,16 @@ class Cortex:
|
||||
EchoAgent(self),
|
||||
]
|
||||
|
||||
# ---- NOVO: Telemetria V5 ----
|
||||
self.tele = TelemetryV5(event_callback=self._telemetry_event)
|
||||
|
||||
self._booted = False
|
||||
|
||||
self.telemetry_path = Path(NEUROTRON_DATASET_PATH) / "telemetry.json"
|
||||
self.telemetry_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# ----------------------------------------
|
||||
# Boot
|
||||
# boot
|
||||
# ----------------------------------------
|
||||
def boot(self):
|
||||
if self._booted:
|
||||
@ -71,7 +73,7 @@ class Cortex:
|
||||
self._booted = True
|
||||
|
||||
# ----------------------------------------
|
||||
# Shutdown + Fatal
|
||||
# shutdown
|
||||
# ----------------------------------------
|
||||
def shutdown(self, reason=""):
|
||||
logbus.warn(f"Shutdown pedido — {reason}")
|
||||
@ -104,7 +106,6 @@ class Cortex:
|
||||
if not action:
|
||||
return
|
||||
|
||||
# echo (debug)
|
||||
if action.get("action") == "echo":
|
||||
res = self.motor.run("echo", [action.get("text", "")])
|
||||
msg = res.get("stdout", "").strip()
|
||||
@ -112,15 +113,26 @@ class Cortex:
|
||||
logbus.info(f"[echo] {msg}")
|
||||
|
||||
def rest(self):
|
||||
# ------- Telemetria V5 ------
|
||||
try:
|
||||
tele = self.tele.step()
|
||||
self.telemetry.append(tele)
|
||||
except Exception as e:
|
||||
logbus.error(f"telemetry_step: {e}")
|
||||
|
||||
# ------- Heartbeat visual ------
|
||||
if HEARTBEAT_ENABLED:
|
||||
self._heartbeat()
|
||||
|
||||
# ------- Tick ------
|
||||
sleep(self._safe_float(self.tick, fallback=1.0))
|
||||
self._tick_count += 1
|
||||
|
||||
# ------- Diag -------
|
||||
if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0:
|
||||
self._run_diag()
|
||||
|
||||
# ------- Persistência -------
|
||||
if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0:
|
||||
self._flush_telemetry()
|
||||
|
||||
@ -135,7 +147,7 @@ class Cortex:
|
||||
return fallback
|
||||
|
||||
# ----------------------------------------
|
||||
# heartbeat
|
||||
# heartbeat — apenas visual, não cognitivo
|
||||
# ----------------------------------------
|
||||
def _heartbeat(self):
|
||||
snap = self.perception.snapshot()
|
||||
@ -145,22 +157,24 @@ class Cortex:
|
||||
load = snap.get("loadavg")
|
||||
load1 = self._safe_float(load[0] if load else 0.0, 0.0)
|
||||
|
||||
self.telemetry.append({
|
||||
"ts": time.time(),
|
||||
"cpu": cpu,
|
||||
"mem": mem,
|
||||
"load1": load1,
|
||||
"tick": self.tick,
|
||||
})
|
||||
|
||||
# log em modo seguro
|
||||
try:
|
||||
logbus.heart(f"cpu={cpu}% mem={mem}% tick={self.tick:.2f}s")
|
||||
except:
|
||||
logbus.heart(f"cpu={cpu}% mem={mem}% tick={self.tick}")
|
||||
|
||||
# ----------------------------------------
|
||||
# diag / homeostase
|
||||
# telemetria persistência
|
||||
# ----------------------------------------
|
||||
def _flush_telemetry(self):
|
||||
try:
|
||||
data = list(self.telemetry)
|
||||
self.telemetry_path.write_text(json.dumps(data))
|
||||
self.memory.remember("telemetry.flush", {})
|
||||
except Exception as e:
|
||||
logbus.error(f"telemetry_flush: {e}")
|
||||
|
||||
# ----------------------------------------
|
||||
# diag + homeostase
|
||||
# ----------------------------------------
|
||||
def _run_diag(self):
|
||||
state, snap = self.diagnostic.run_exam()
|
||||
@ -181,20 +195,13 @@ class Cortex:
|
||||
self.tick = max(NEUROTRON_TICK_MIN, old - NEUROTRON_TICK_STEP / 2)
|
||||
|
||||
if self.tick != old:
|
||||
try:
|
||||
logbus.info(f"tick ajustado {old:.2f}s → {self.tick:.2f}s")
|
||||
except:
|
||||
logbus.info(f"tick ajustado {old} → {self.tick}")
|
||||
logbus.info(f"tick ajustado {old:.2f}s → {self.tick:.2f}s")
|
||||
|
||||
# ----------------------------------------
|
||||
# telemetria
|
||||
# callback eventos telemétricos (Hippocampus)
|
||||
# ----------------------------------------
|
||||
def _flush_telemetry(self):
|
||||
try:
|
||||
self.telemetry_path.write_text(json.dumps(list(self.telemetry)))
|
||||
self.memory.remember("telemetry.flush", {})
|
||||
except Exception as e:
|
||||
logbus.error(f"telemetry error: {e}")
|
||||
def _telemetry_event(self, event_name, payload):
|
||||
self.memory.remember(f"telemetry.event.{event_name}", payload)
|
||||
|
||||
# ----------------------------------------
|
||||
# bus interno
|
||||
|
||||
251
src/neurotron/telemetry.py
Normal file
251
src/neurotron/telemetry.py
Normal file
@ -0,0 +1,251 @@
|
||||
"""
|
||||
telemetry.py — Telemetria V5 do Neurotron
|
||||
-----------------------------------------
|
||||
Responsável por:
|
||||
• Medições básicas (CPU, MEM, LOAD, IO)
|
||||
• Delta entre ciclos
|
||||
• Aceleração do delta
|
||||
• Temperatura virtual (fadiga cognitiva)
|
||||
• Jitter cognitivo (latência entre ciclos)
|
||||
• FS Health (blocos, erros, RO-mode)
|
||||
• Eventos telemétricos (V5)
|
||||
• Classificação de estados cognitivos
|
||||
|
||||
Este módulo é completamente independente:
|
||||
o Cortex só chama TelemetryV5.step() a cada ciclo.
|
||||
"""
|
||||
|
||||
import time
|
||||
import os
|
||||
import psutil
|
||||
|
||||
|
||||
class TelemetryV5:
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Inicialização
|
||||
# ----------------------------------------------------------
|
||||
def __init__(self, event_callback=None):
|
||||
"""
|
||||
event_callback(event_name, payload_dict)
|
||||
"""
|
||||
self.event_callback = event_callback
|
||||
|
||||
self.last_raw = None
|
||||
self.last_delta = None
|
||||
self.last_ts = None
|
||||
|
||||
self.temperature = 0.0
|
||||
self.jitter_avg = 0.0
|
||||
|
||||
# manter último estado cognitivo
|
||||
self.last_state = "stable"
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Coleta RAW (CPU, MEM, LOAD, IO)
|
||||
# ----------------------------------------------------------
|
||||
def collect_raw(self):
|
||||
now = time.monotonic()
|
||||
|
||||
cpu = psutil.cpu_percent(interval=None)
|
||||
mem = psutil.virtual_memory().percent
|
||||
load = os.getloadavg()[0] # 1-min loadavg
|
||||
|
||||
# IO delta é manual, usamos psutil
|
||||
io = psutil.disk_io_counters()
|
||||
disk_total = io.read_bytes + io.write_bytes
|
||||
|
||||
return {
|
||||
"ts": now,
|
||||
"cpu": cpu,
|
||||
"mem": mem,
|
||||
"load": load,
|
||||
"disk": disk_total,
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Delta = diferença entre ciclos
|
||||
# ----------------------------------------------------------
|
||||
def compute_delta(self, raw):
|
||||
if self.last_raw is None:
|
||||
self.last_raw = raw
|
||||
return {k: 0 for k in raw.keys() if k != "ts"}
|
||||
|
||||
delta = {
|
||||
"cpu": raw["cpu"] - self.last_raw["cpu"],
|
||||
"mem": raw["mem"] - self.last_raw["mem"],
|
||||
"load": raw["load"] - self.last_raw["load"],
|
||||
"disk": raw["disk"] - self.last_raw["disk"],
|
||||
}
|
||||
|
||||
self.last_delta = delta
|
||||
self.last_raw = raw
|
||||
|
||||
return delta
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Aceleração = variação do delta
|
||||
# ----------------------------------------------------------
|
||||
def compute_accel(self, delta):
|
||||
if self.last_delta is None:
|
||||
return {k: 0 for k in delta.keys()}
|
||||
|
||||
accel = {k: delta[k] - self.last_delta[k] for k in delta.keys()}
|
||||
return accel
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Jitter Cognitivo
|
||||
# ----------------------------------------------------------
|
||||
def compute_jitter(self, now):
|
||||
if self.last_ts is None:
|
||||
self.last_ts = now
|
||||
return 0.0
|
||||
|
||||
jitter = now - self.last_ts
|
||||
self.last_ts = now
|
||||
|
||||
# média móvel
|
||||
self.jitter_avg = (self.jitter_avg * 0.9) + (jitter * 0.1)
|
||||
return jitter
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Temperatura Virtual (fadiga cognitiva)
|
||||
# ----------------------------------------------------------
|
||||
def compute_temperature(self, raw, delta):
|
||||
# modelo simplificado
|
||||
score = (
|
||||
raw["cpu"] * 0.6 +
|
||||
raw["load"] * 0.3 +
|
||||
abs(delta["cpu"]) * 0.2 +
|
||||
raw["mem"] * 0.1
|
||||
)
|
||||
|
||||
# decay + acumulação
|
||||
self.temperature = max(0, self.temperature * 0.90 + score * 0.10)
|
||||
return self.temperature
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# FS Health
|
||||
# ----------------------------------------------------------
|
||||
def compute_fs_health(self):
|
||||
status = {
|
||||
"read_only": False,
|
||||
"io_errors": 0,
|
||||
"free_percent": None
|
||||
}
|
||||
|
||||
try:
|
||||
st = psutil.disk_usage("/")
|
||||
status["free_percent"] = st.free / st.total * 100
|
||||
except Exception:
|
||||
status["free_percent"] = None
|
||||
|
||||
# leitura de erros EXT4 se existir
|
||||
ext4_err_path = "/sys/fs/ext4/sda1/errors_count"
|
||||
if os.path.exists(ext4_err_path):
|
||||
try:
|
||||
with open(ext4_err_path) as f:
|
||||
status["io_errors"] = int(f.read().strip())
|
||||
except:
|
||||
pass
|
||||
|
||||
# detetar RO
|
||||
try:
|
||||
with open("/tmp/fs_test_rw", "w") as f:
|
||||
f.write("x")
|
||||
except Exception:
|
||||
status["read_only"] = True
|
||||
|
||||
return status
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Classificação de Estado Cognitivo
|
||||
# ----------------------------------------------------------
|
||||
def classify_state(self, temp, jitter):
|
||||
if temp < 25:
|
||||
return "stable"
|
||||
elif temp < 45:
|
||||
return "warm"
|
||||
elif temp < 65:
|
||||
return "hot"
|
||||
elif temp < 85:
|
||||
return "critical"
|
||||
else:
|
||||
return "recovery"
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Eventos TeleMétricos V5
|
||||
# ----------------------------------------------------------
|
||||
def detect_events(self, raw, delta, accel, temp, jitter, fs):
|
||||
events = []
|
||||
|
||||
# picos
|
||||
if delta["cpu"] > 15 or accel["cpu"] > 10:
|
||||
events.append("temp_rising_fast")
|
||||
|
||||
# zona de stress
|
||||
if raw["cpu"] > 85:
|
||||
events.append("enter_stress_zone")
|
||||
|
||||
# suspeita de loop
|
||||
if jitter > (self.jitter_avg * 3):
|
||||
events.append("loop_suspect")
|
||||
|
||||
# fs
|
||||
if fs["read_only"] or fs["io_errors"] > 0:
|
||||
events.append("fs_warning")
|
||||
|
||||
# recuperação
|
||||
if self.last_state == "critical" and temp < 50:
|
||||
events.append("recovering")
|
||||
|
||||
# exportar via callback
|
||||
if self.event_callback:
|
||||
for e in events:
|
||||
self.event_callback(e, {
|
||||
"raw": raw,
|
||||
"delta": delta,
|
||||
"accel": accel,
|
||||
"temp": temp,
|
||||
"jitter": jitter,
|
||||
"fs": fs,
|
||||
})
|
||||
|
||||
return events
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# STEP — chamada a cada ciclo cognitivo
|
||||
# ----------------------------------------------------------
|
||||
def step(self):
|
||||
"""
|
||||
Faz um ciclo completo da Telemetria V5:
|
||||
- raw → delta → accel
|
||||
- jitter
|
||||
- temp
|
||||
- fs health
|
||||
- estado
|
||||
- eventos
|
||||
"""
|
||||
|
||||
raw = self.collect_raw()
|
||||
delta = self.compute_delta(raw)
|
||||
accel = self.compute_accel(delta)
|
||||
jitter = self.compute_jitter(raw["ts"])
|
||||
temp = self.compute_temperature(raw, delta)
|
||||
fs = self.compute_fs_health()
|
||||
|
||||
state = self.classify_state(temp, jitter)
|
||||
events = self.detect_events(raw, delta, accel, temp, jitter, fs)
|
||||
|
||||
self.last_state = state
|
||||
|
||||
return {
|
||||
"raw": raw,
|
||||
"delta": delta,
|
||||
"accel": accel,
|
||||
"jitter": jitter,
|
||||
"temp": temp,
|
||||
"fs": fs,
|
||||
"state": state,
|
||||
"events": events,
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user