Auto-commit via make git (triggered by NFDOS)
This commit is contained in:
parent
254b0dae19
commit
9be53dc380
@ -35,49 +35,49 @@
|
|||||||
'configure.ac'
|
'configure.ac'
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
'AM_CONDITIONAL' => 1,
|
|
||||||
'AC_DEFUN' => 1,
|
|
||||||
'AM_SUBST_NOTMAKE' => 1,
|
|
||||||
'AM_SILENT_RULES' => 1,
|
|
||||||
'm4_pattern_forbid' => 1,
|
|
||||||
'AC_CONFIG_MACRO_DIR' => 1,
|
|
||||||
'AM_PROG_INSTALL_STRIP' => 1,
|
|
||||||
'AM_SANITY_CHECK' => 1,
|
|
||||||
'AM_PROG_CC_C_O' => 1,
|
|
||||||
'AM_PYTHON_CHECK_VERSION' => 1,
|
|
||||||
'AM_PROG_INSTALL_SH' => 1,
|
|
||||||
'AM_INIT_AUTOMAKE' => 1,
|
'AM_INIT_AUTOMAKE' => 1,
|
||||||
'_AM_IF_OPTION' => 1,
|
|
||||||
'AC_CONFIG_MACRO_DIR_TRACE' => 1,
|
|
||||||
'm4_pattern_allow' => 1,
|
|
||||||
'm4_include' => 1,
|
|
||||||
'_AM_MANGLE_OPTION' => 1,
|
|
||||||
'AM_SET_DEPDIR' => 1,
|
|
||||||
'_AM_PROG_CC_C_O' => 1,
|
|
||||||
'AU_DEFUN' => 1,
|
|
||||||
'include' => 1,
|
|
||||||
'AM_MISSING_PROG' => 1,
|
|
||||||
'AM_PATH_PYTHON' => 1,
|
|
||||||
'AM_MAKE_INCLUDE' => 1,
|
|
||||||
'_AM_SET_OPTION' => 1,
|
|
||||||
'_AM_OUTPUT_DEPENDENCY_COMMANDS' => 1,
|
|
||||||
'AM_AUX_DIR_EXPAND' => 1,
|
|
||||||
'AM_RUN_LOG' => 1,
|
|
||||||
'AM_DEP_TRACK' => 1,
|
'AM_DEP_TRACK' => 1,
|
||||||
'_AM_CONFIG_MACRO_DIRS' => 1,
|
'_AM_SET_OPTIONS' => 1,
|
||||||
'AM_SET_CURRENT_AUTOMAKE_VERSION' => 1,
|
'_AM_PROG_CC_C_O' => 1,
|
||||||
'_AM_AUTOCONF_VERSION' => 1,
|
'include' => 1,
|
||||||
'_m4_warn' => 1,
|
|
||||||
'AM_AUTOMAKE_VERSION' => 1,
|
|
||||||
'_AM_DEPENDENCIES' => 1,
|
|
||||||
'_AM_SUBST_NOTMAKE' => 1,
|
|
||||||
'AM_MISSING_HAS_RUN' => 1,
|
'AM_MISSING_HAS_RUN' => 1,
|
||||||
'_AC_AM_CONFIG_HEADER_HOOK' => 1,
|
'_AM_AUTOCONF_VERSION' => 1,
|
||||||
'_AM_PROG_TAR' => 1,
|
'_AM_OUTPUT_DEPENDENCY_COMMANDS' => 1,
|
||||||
'AM_OUTPUT_DEPENDENCY_COMMANDS' => 1,
|
|
||||||
'AM_SET_LEADING_DOT' => 1,
|
|
||||||
'AC_DEFUN_ONCE' => 1,
|
'AC_DEFUN_ONCE' => 1,
|
||||||
'_AM_SET_OPTIONS' => 1
|
'AM_SANITY_CHECK' => 1,
|
||||||
|
'_AC_AM_CONFIG_HEADER_HOOK' => 1,
|
||||||
|
'AU_DEFUN' => 1,
|
||||||
|
'AM_PATH_PYTHON' => 1,
|
||||||
|
'_AM_SUBST_NOTMAKE' => 1,
|
||||||
|
'AM_MISSING_PROG' => 1,
|
||||||
|
'AM_PROG_INSTALL_SH' => 1,
|
||||||
|
'm4_pattern_forbid' => 1,
|
||||||
|
'm4_include' => 1,
|
||||||
|
'AC_DEFUN' => 1,
|
||||||
|
'AM_PYTHON_CHECK_VERSION' => 1,
|
||||||
|
'_AM_CONFIG_MACRO_DIRS' => 1,
|
||||||
|
'_AM_DEPENDENCIES' => 1,
|
||||||
|
'm4_pattern_allow' => 1,
|
||||||
|
'AM_SILENT_RULES' => 1,
|
||||||
|
'AM_SET_LEADING_DOT' => 1,
|
||||||
|
'AM_PROG_CC_C_O' => 1,
|
||||||
|
'AC_CONFIG_MACRO_DIR' => 1,
|
||||||
|
'AM_AUX_DIR_EXPAND' => 1,
|
||||||
|
'_AM_IF_OPTION' => 1,
|
||||||
|
'AM_RUN_LOG' => 1,
|
||||||
|
'AM_AUTOMAKE_VERSION' => 1,
|
||||||
|
'AC_CONFIG_MACRO_DIR_TRACE' => 1,
|
||||||
|
'AM_MAKE_INCLUDE' => 1,
|
||||||
|
'AM_SUBST_NOTMAKE' => 1,
|
||||||
|
'_m4_warn' => 1,
|
||||||
|
'AM_OUTPUT_DEPENDENCY_COMMANDS' => 1,
|
||||||
|
'_AM_SET_OPTION' => 1,
|
||||||
|
'AM_CONDITIONAL' => 1,
|
||||||
|
'_AM_PROG_TAR' => 1,
|
||||||
|
'AM_SET_CURRENT_AUTOMAKE_VERSION' => 1,
|
||||||
|
'AM_SET_DEPDIR' => 1,
|
||||||
|
'AM_PROG_INSTALL_STRIP' => 1,
|
||||||
|
'_AM_MANGLE_OPTION' => 1
|
||||||
}
|
}
|
||||||
], 'Autom4te::Request' ),
|
], 'Autom4te::Request' ),
|
||||||
bless( [
|
bless( [
|
||||||
@ -92,65 +92,65 @@
|
|||||||
'configure.ac'
|
'configure.ac'
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
'AC_FC_PP_DEFINE' => 1,
|
'AM_PROG_MKDIR_P' => 1,
|
||||||
'AC_REQUIRE_AUX_FILE' => 1,
|
'AC_FC_FREEFORM' => 1,
|
||||||
'_AM_SUBST_NOTMAKE' => 1,
|
'AC_FC_SRCEXT' => 1,
|
||||||
'AM_PROG_CXX_C_O' => 1,
|
|
||||||
'AC_PROG_LIBTOOL' => 1,
|
|
||||||
'_m4_warn' => 1,
|
|
||||||
'AM_AUTOMAKE_VERSION' => 1,
|
'AM_AUTOMAKE_VERSION' => 1,
|
||||||
'AC_CANONICAL_TARGET' => 1,
|
'AC_CANONICAL_TARGET' => 1,
|
||||||
'AC_CONFIG_FILES' => 1,
|
|
||||||
'AC_FC_SRCEXT' => 1,
|
|
||||||
'AC_CONFIG_LINKS' => 1,
|
|
||||||
'AM_PROG_F77_C_O' => 1,
|
|
||||||
'AC_DEFINE_TRACE_LITERAL' => 1,
|
|
||||||
'AM_ENABLE_MULTILIB' => 1,
|
|
||||||
'AM_EXTRA_RECURSIVE_TARGETS' => 1,
|
|
||||||
'_AM_COND_ENDIF' => 1,
|
|
||||||
'_AM_MAKEFILE_INCLUDE' => 1,
|
|
||||||
'AM_PROG_AR' => 1,
|
|
||||||
'AC_CANONICAL_HOST' => 1,
|
|
||||||
'AC_FC_PP_SRCEXT' => 1,
|
|
||||||
'AM_PATH_GUILE' => 1,
|
|
||||||
'AM_MAINTAINER_MODE' => 1,
|
|
||||||
'_LT_AC_TAGCONFIG' => 1,
|
|
||||||
'sinclude' => 1,
|
|
||||||
'AM_GNU_GETTEXT' => 1,
|
|
||||||
'AC_CONFIG_AUX_DIR' => 1,
|
|
||||||
'AC_CANONICAL_SYSTEM' => 1,
|
|
||||||
'_AM_COND_IF' => 1,
|
|
||||||
'm4_include' => 1,
|
|
||||||
'AC_CANONICAL_BUILD' => 1,
|
|
||||||
'AH_OUTPUT' => 1,
|
|
||||||
'AM_XGETTEXT_OPTION' => 1,
|
|
||||||
'AC_INIT' => 1,
|
|
||||||
'LT_INIT' => 1,
|
|
||||||
'include' => 1,
|
|
||||||
'AM_PROG_MKDIR_P' => 1,
|
|
||||||
'AM_PROG_CC_C_O' => 1,
|
'AM_PROG_CC_C_O' => 1,
|
||||||
'AC_LIBSOURCE' => 1,
|
'AC_FC_PP_SRCEXT' => 1,
|
||||||
'm4_pattern_forbid' => 1,
|
'AH_OUTPUT' => 1,
|
||||||
'AM_NLS' => 1,
|
'AC_CANONICAL_SYSTEM' => 1,
|
||||||
'm4_sinclude' => 1,
|
|
||||||
'_AM_COND_ELSE' => 1,
|
|
||||||
'AC_CONFIG_LIBOBJ_DIR' => 1,
|
|
||||||
'AM_GNU_GETTEXT_INTL_SUBDIR' => 1,
|
|
||||||
'AM_CONDITIONAL' => 1,
|
|
||||||
'AC_SUBST' => 1,
|
|
||||||
'AM_PROG_FC_C_O' => 1,
|
|
||||||
'AM_MAKEFILE_INCLUDE' => 1,
|
|
||||||
'AM_SILENT_RULES' => 1,
|
'AM_SILENT_RULES' => 1,
|
||||||
'LT_CONFIG_LTDL_DIR' => 1,
|
'AC_CONFIG_AUX_DIR' => 1,
|
||||||
'AC_CONFIG_SUBDIRS' => 1,
|
'_LT_AC_TAGCONFIG' => 1,
|
||||||
'LT_SUPPORTED_TAG' => 1,
|
'AC_CONFIG_LIBOBJ_DIR' => 1,
|
||||||
'AC_FC_FREEFORM' => 1,
|
'_AM_COND_IF' => 1,
|
||||||
'm4_pattern_allow' => 1,
|
|
||||||
'AM_POT_TOOLS' => 1,
|
'AM_POT_TOOLS' => 1,
|
||||||
|
'_AM_MAKEFILE_INCLUDE' => 1,
|
||||||
|
'AC_LIBSOURCE' => 1,
|
||||||
|
'AM_CONDITIONAL' => 1,
|
||||||
|
'AM_EXTRA_RECURSIVE_TARGETS' => 1,
|
||||||
|
'AM_PROG_F77_C_O' => 1,
|
||||||
|
'_AM_COND_ENDIF' => 1,
|
||||||
|
'_m4_warn' => 1,
|
||||||
|
'AM_MAINTAINER_MODE' => 1,
|
||||||
|
'AM_GNU_GETTEXT_INTL_SUBDIR' => 1,
|
||||||
|
'AC_FC_PP_DEFINE' => 1,
|
||||||
|
'_AM_SUBST_NOTMAKE' => 1,
|
||||||
|
'AM_MAKEFILE_INCLUDE' => 1,
|
||||||
|
'm4_sinclude' => 1,
|
||||||
|
'AM_NLS' => 1,
|
||||||
|
'AC_SUBST' => 1,
|
||||||
'AM_PROG_MOC' => 1,
|
'AM_PROG_MOC' => 1,
|
||||||
'AC_CONFIG_HEADERS' => 1,
|
'AM_PROG_CXX_C_O' => 1,
|
||||||
|
'AC_CANONICAL_HOST' => 1,
|
||||||
|
'AC_CANONICAL_BUILD' => 1,
|
||||||
|
'AC_CONFIG_LINKS' => 1,
|
||||||
|
'AC_CONFIG_FILES' => 1,
|
||||||
|
'include' => 1,
|
||||||
|
'LT_CONFIG_LTDL_DIR' => 1,
|
||||||
'AC_SUBST_TRACE' => 1,
|
'AC_SUBST_TRACE' => 1,
|
||||||
'AM_INIT_AUTOMAKE' => 1
|
'AM_PROG_FC_C_O' => 1,
|
||||||
|
'sinclude' => 1,
|
||||||
|
'AM_INIT_AUTOMAKE' => 1,
|
||||||
|
'AM_PATH_GUILE' => 1,
|
||||||
|
'AC_PROG_LIBTOOL' => 1,
|
||||||
|
'm4_pattern_allow' => 1,
|
||||||
|
'AC_CONFIG_SUBDIRS' => 1,
|
||||||
|
'AC_DEFINE_TRACE_LITERAL' => 1,
|
||||||
|
'AC_REQUIRE_AUX_FILE' => 1,
|
||||||
|
'AC_INIT' => 1,
|
||||||
|
'm4_pattern_forbid' => 1,
|
||||||
|
'AM_PROG_AR' => 1,
|
||||||
|
'AM_ENABLE_MULTILIB' => 1,
|
||||||
|
'm4_include' => 1,
|
||||||
|
'AM_GNU_GETTEXT' => 1,
|
||||||
|
'LT_INIT' => 1,
|
||||||
|
'AM_XGETTEXT_OPTION' => 1,
|
||||||
|
'_AM_COND_ELSE' => 1,
|
||||||
|
'AC_CONFIG_HEADERS' => 1,
|
||||||
|
'LT_SUPPORTED_TAG' => 1
|
||||||
}
|
}
|
||||||
], 'Autom4te::Request' )
|
], 'Autom4te::Request' )
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,7 +6,7 @@ SRC="$NEUROTRON_HOME/src"
|
|||||||
|
|
||||||
export PYTHONHOME="/usr"
|
export PYTHONHOME="/usr"
|
||||||
export PYTHONPATH="$SRC:/usr/lib/python3.13:/usr/lib/python3.13/site-packages"
|
export PYTHONPATH="$SRC:/usr/lib/python3.13:/usr/lib/python3.13/site-packages"
|
||||||
export PATH="/usr/bin:/bin:/sbin:/usr/sbin:$NEUROTRON_HOME/bin:$PATH"
|
export PATH="/sbin:/bin:/usr/sbin:/usr/bin:$PATH"
|
||||||
|
|
||||||
# Arrancar o cérebro principal como módulo do package
|
# Arrancar o cérebro principal como módulo do package
|
||||||
exec "$PYTHON" -m neurotron "$@"
|
exec "$PYTHON" -m neurotron "$@"
|
||||||
13
neurotron.in
13
neurotron.in
@ -4,9 +4,18 @@ PYTHON="/usr/bin/python3"
|
|||||||
NEUROTRON_HOME="/opt/kernel/neurotron"
|
NEUROTRON_HOME="/opt/kernel/neurotron"
|
||||||
SRC="$NEUROTRON_HOME/src"
|
SRC="$NEUROTRON_HOME/src"
|
||||||
|
|
||||||
|
# Garante diretórios básicos
|
||||||
|
mkdir -p /proc /sys /dev
|
||||||
|
|
||||||
|
# Montar proc, sysfs e devtmpfs (idempotente, falha silenciosa se já montado)
|
||||||
|
mount -t proc proc /proc 2>/dev/null || true
|
||||||
|
mount -t sysfs sys /sys 2>/dev/null || true
|
||||||
|
mount -t devtmpfs devtmpfs /dev 2>/dev/null || true
|
||||||
|
|
||||||
|
# Ambiente Python minimalista
|
||||||
export PYTHONHOME="/usr"
|
export PYTHONHOME="/usr"
|
||||||
export PYTHONPATH="$SRC:/usr/lib/python3.13:/usr/lib/python3.13/site-packages"
|
export PYTHONPATH="$SRC:/usr/lib/python3.13:/usr/lib/python3.13/site-packages"
|
||||||
export PATH="/usr/bin:/bin:/sbin:/usr/sbin:$NEUROTRON_HOME/bin:$PATH"
|
export PATH="/sbin:/bin:/usr/sbin:/usr/bin:$PATH"
|
||||||
|
|
||||||
# Arrancar o cérebro principal como módulo do package
|
# Arrancar o cérebro principal como módulo do package
|
||||||
exec "$PYTHON" -m neurotron "$@"
|
exec "$PYTHON" -m neurotron "$@"
|
||||||
|
|||||||
@ -21,35 +21,69 @@ from neurotron.cortex import Cortex
|
|||||||
|
|
||||||
def dashboard_loop(ctx: Cortex):
|
def dashboard_loop(ctx: Cortex):
|
||||||
"""
|
"""
|
||||||
Renderização minimalista do estado atual.
|
Dashboard com scroll “real”.
|
||||||
Apenas mostra informações de alto nível.
|
Mantém:
|
||||||
Tudo mais pertence ao logbus (à direita do ecrã).
|
- Linha 1: estado
|
||||||
|
- Linha 2: separador
|
||||||
|
- Linhas 3..102: janela de 100 linhas do logbus
|
||||||
"""
|
"""
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
while True:
|
MAX_LINES = 40 # janela visível
|
||||||
uptime = int(time.time() - start)
|
LOG_START_ROW = 3 # linha onde os logs começam
|
||||||
h = uptime // 3600
|
|
||||||
m = (uptime % 3600) // 60
|
|
||||||
s = uptime % 60
|
|
||||||
|
|
||||||
mode = ctx.mode.upper()
|
# limpar ecrã e esconder cursor
|
||||||
tick = ctx.tick
|
sys.stdout.write("\033[2J\033[H\033[?25l")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
# linha de estado (esquerda)
|
try:
|
||||||
line = (
|
while True:
|
||||||
f"UP: {h:02}:{m:02}:{s:02} "
|
uptime = int(time.time() - start)
|
||||||
f"TICK: {tick:0.2f}s "
|
h = uptime // 3600
|
||||||
f"MODO: {mode:10}"
|
m = (uptime % 3600) // 60
|
||||||
)
|
s = uptime % 60
|
||||||
|
|
||||||
# escreve sempre na coluna fixa
|
mode = (ctx.mode or "").upper()
|
||||||
sys.stdout.write("\033[1;1H" + line + "\033[K")
|
tick = ctx.tick
|
||||||
|
|
||||||
|
if mode == "PERSISTENT":
|
||||||
|
mode_str = "\033[1;34mPERSISTENT\033[0m"
|
||||||
|
elif mode == "DIAGNOSTIC":
|
||||||
|
mode_str = "\033[1;33mDIAGNOSTIC\033[0m"
|
||||||
|
else:
|
||||||
|
mode_str = mode
|
||||||
|
|
||||||
|
header = (
|
||||||
|
f"UP: {h:02}:{m:02}:{s:02} "
|
||||||
|
f"TICK: {tick:0.2f}s "
|
||||||
|
f"MODO: {mode_str}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Header
|
||||||
|
sys.stdout.write("\033[1;1H" + header + "\033[K")
|
||||||
|
sys.stdout.write("\033[2;1H" + "─" * 80 + "\033[K")
|
||||||
|
|
||||||
|
# ----------------------------------------------
|
||||||
|
# SCROLL WINDOW (esta é a magia ✨)
|
||||||
|
# ----------------------------------------------
|
||||||
|
logs = logbus.tail(MAX_LINES)
|
||||||
|
row = LOG_START_ROW
|
||||||
|
|
||||||
|
for line in logs:
|
||||||
|
truncated = line[:256]
|
||||||
|
sys.stdout.write(f"\033[{row};1H{truncated}\033[K")
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# Limpar linhas abaixo caso sobrem
|
||||||
|
# sys.stdout.write(f"\033[{row};1H\033[J")
|
||||||
|
# sys.stdout.flush()
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
sys.stdout.write("\033[?25h")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
time.sleep(0.2)
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# CICLO COGNITIVO
|
# CICLO COGNITIVO
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -68,13 +102,14 @@ def cognitive_loop(ctx: Cortex):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logbus.error(f"Fatal no ciclo cognitivo: {repr(e)}")
|
logbus.error(f"Fatal no ciclo cognitivo: {repr(e)}")
|
||||||
|
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
for line in tb.splitlines():
|
for line in tb.splitlines():
|
||||||
logbus.error(line)
|
logbus.error(line)
|
||||||
|
|
||||||
ctx.shutdown("fatal exception")
|
ctx.shutdown("fatal exception")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# MAIN
|
# MAIN
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@ -8,8 +8,8 @@ from time import sleep
|
|||||||
|
|
||||||
from neurotron.logbus import logbus
|
from neurotron.logbus import logbus
|
||||||
from neurotron.disk_agent import DiskAgent
|
from neurotron.disk_agent import DiskAgent
|
||||||
from neurotron.echo_agent import EchoAgent
|
|
||||||
from neurotron.vitalsigns_agent import VitalSigns
|
from neurotron.vitalsigns_agent import VitalSigns
|
||||||
|
from neurotron.echo_agent import EchoAgent
|
||||||
|
|
||||||
from .hippocampus import Hippocampus
|
from .hippocampus import Hippocampus
|
||||||
from .perception import Perception
|
from .perception import Perception
|
||||||
@ -26,12 +26,18 @@ from .neurotron_config import (
|
|||||||
TELEMETRY_MAXLEN, TELEMETRY_FLUSH_EVERY_TICKS,
|
TELEMETRY_MAXLEN, TELEMETRY_FLUSH_EVERY_TICKS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Cortex:
|
class Cortex:
|
||||||
def __init__(self, runtime_dir, log_dir, tick_seconds=NEUROTRON_TICK):
|
def __init__(self, runtime_dir, log_dir, tick_seconds=NEUROTRON_TICK):
|
||||||
self.runtime_dir = Path(runtime_dir)
|
self.runtime_dir = Path(runtime_dir)
|
||||||
self.log_dir = Path(log_dir)
|
self.log_dir = Path(log_dir)
|
||||||
|
|
||||||
self.tick = float(tick_seconds)
|
# tick pode vir como string → convertemos sempre
|
||||||
|
try:
|
||||||
|
self.tick = float(tick_seconds)
|
||||||
|
except:
|
||||||
|
self.tick = 1.0
|
||||||
|
|
||||||
self.mode = NEUROTRON_MODE
|
self.mode = NEUROTRON_MODE
|
||||||
self._tick_count = 0
|
self._tick_count = 0
|
||||||
|
|
||||||
@ -43,7 +49,6 @@ class Cortex:
|
|||||||
self.bus = defaultdict(lambda: deque(maxlen=32))
|
self.bus = defaultdict(lambda: deque(maxlen=32))
|
||||||
self.telemetry = deque(maxlen=TELEMETRY_MAXLEN)
|
self.telemetry = deque(maxlen=TELEMETRY_MAXLEN)
|
||||||
|
|
||||||
# ordem é importante
|
|
||||||
self.neurons = [
|
self.neurons = [
|
||||||
DiskAgent(self),
|
DiskAgent(self),
|
||||||
VitalSigns(self),
|
VitalSigns(self),
|
||||||
@ -82,11 +87,17 @@ class Cortex:
|
|||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
def observe(self):
|
def observe(self):
|
||||||
for n in self.neurons:
|
for n in self.neurons:
|
||||||
n.observe()
|
try:
|
||||||
|
n.observe()
|
||||||
|
except Exception as e:
|
||||||
|
logbus.error(f"{n.name}.observe: {e}")
|
||||||
|
|
||||||
def think(self):
|
def think(self):
|
||||||
for n in self.neurons:
|
for n in self.neurons:
|
||||||
n.think()
|
try:
|
||||||
|
n.think()
|
||||||
|
except Exception as e:
|
||||||
|
logbus.error(f"{n.name}.think: {e}")
|
||||||
|
|
||||||
def act(self):
|
def act(self):
|
||||||
action = self.bus_consume("actions")
|
action = self.bus_consume("actions")
|
||||||
@ -96,14 +107,15 @@ class Cortex:
|
|||||||
# echo (debug)
|
# echo (debug)
|
||||||
if action.get("action") == "echo":
|
if action.get("action") == "echo":
|
||||||
res = self.motor.run("echo", [action.get("text", "")])
|
res = self.motor.run("echo", [action.get("text", "")])
|
||||||
if res.get("stdout"):
|
msg = res.get("stdout", "").strip()
|
||||||
logbus.info(f"[echo] {res['stdout'].strip()}")
|
if msg:
|
||||||
|
logbus.info(f"[echo] {msg}")
|
||||||
|
|
||||||
def rest(self):
|
def rest(self):
|
||||||
if HEARTBEAT_ENABLED:
|
if HEARTBEAT_ENABLED:
|
||||||
self._heartbeat()
|
self._heartbeat()
|
||||||
|
|
||||||
sleep(self.tick)
|
sleep(self._safe_float(self.tick, fallback=1.0))
|
||||||
self._tick_count += 1
|
self._tick_count += 1
|
||||||
|
|
||||||
if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0:
|
if self._tick_count % NEUROTRON_DIAG_EVERY_TICKS == 0:
|
||||||
@ -112,15 +124,26 @@ class Cortex:
|
|||||||
if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0:
|
if self._tick_count % TELEMETRY_FLUSH_EVERY_TICKS == 0:
|
||||||
self._flush_telemetry()
|
self._flush_telemetry()
|
||||||
|
|
||||||
|
# ----------------------------------------
|
||||||
|
# helpers
|
||||||
|
# ----------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def _safe_float(x, fallback=0.0):
|
||||||
|
try:
|
||||||
|
return float(x)
|
||||||
|
except:
|
||||||
|
return fallback
|
||||||
|
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
# heartbeat
|
# heartbeat
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
def _heartbeat(self):
|
def _heartbeat(self):
|
||||||
snap = self.perception.snapshot()
|
snap = self.perception.snapshot()
|
||||||
cpu = snap.get("cpu_percent")
|
|
||||||
mem = snap.get("mem_percent")
|
cpu = self._safe_float(snap.get("cpu_percent"), 0.0)
|
||||||
|
mem = self._safe_float(snap.get("mem_percent"), 0.0)
|
||||||
load = snap.get("loadavg")
|
load = snap.get("loadavg")
|
||||||
load1 = load[0] if load else None
|
load1 = self._safe_float(load[0] if load else 0.0, 0.0)
|
||||||
|
|
||||||
self.telemetry.append({
|
self.telemetry.append({
|
||||||
"ts": time.time(),
|
"ts": time.time(),
|
||||||
@ -130,25 +153,38 @@ class Cortex:
|
|||||||
"tick": self.tick,
|
"tick": self.tick,
|
||||||
})
|
})
|
||||||
|
|
||||||
logbus.heart(f"cpu={cpu}% mem={mem}% tick={self.tick:.2f}s")
|
# 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
|
# diag / homeostase
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
def _run_diag(self):
|
def _run_diag(self):
|
||||||
state, snap = self.diagnostic.run_exam()
|
state, snap = self.diagnostic.run_exam()
|
||||||
logbus.diag(f"estado={state} cpu={snap['cpu']} mem={snap['mem']} load1={snap['load1']}")
|
|
||||||
|
|
||||||
old = self.tick
|
cpu = snap.get("cpu")
|
||||||
|
mem = snap.get("mem")
|
||||||
|
load1 = snap.get("load1")
|
||||||
|
|
||||||
|
logbus.diag(f"estado={state} cpu={cpu} mem={mem} load1={load1}")
|
||||||
|
|
||||||
|
old = self._safe_float(self.tick, 1.0)
|
||||||
|
|
||||||
if state == "CRITICAL":
|
if state == "CRITICAL":
|
||||||
self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP)
|
self.tick = min(NEUROTRON_TICK_MAX, old + NEUROTRON_TICK_STEP)
|
||||||
elif state == "ALERT":
|
elif state == "ALERT":
|
||||||
self.tick = min(NEUROTRON_TICK_MAX, self.tick + NEUROTRON_TICK_STEP / 2)
|
self.tick = min(NEUROTRON_TICK_MAX, old + NEUROTRON_TICK_STEP / 2)
|
||||||
elif state == "STABLE":
|
elif state == "STABLE":
|
||||||
self.tick = max(NEUROTRON_TICK_MIN, self.tick - NEUROTRON_TICK_STEP / 2)
|
self.tick = max(NEUROTRON_TICK_MIN, old - NEUROTRON_TICK_STEP / 2)
|
||||||
|
|
||||||
if old != self.tick:
|
if self.tick != old:
|
||||||
logbus.info(f"tick ajustado {old:.2f}s → {self.tick:.2f}s")
|
try:
|
||||||
|
logbus.info(f"tick ajustado {old:.2f}s → {self.tick:.2f}s")
|
||||||
|
except:
|
||||||
|
logbus.info(f"tick ajustado {old} → {self.tick}")
|
||||||
|
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
# telemetria
|
# telemetria
|
||||||
@ -170,4 +206,3 @@ class Cortex:
|
|||||||
q = self.bus[ch]
|
q = self.bus[ch]
|
||||||
return q.popleft() if q else None
|
return q.popleft() if q else None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,106 +1,229 @@
|
|||||||
# neurotron/disk_agent.py
|
# neurotron/disk_agent.py
|
||||||
import os
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from neurotron.neuron import Neuron
|
from neurotron.neuron import Neuron
|
||||||
from neurotron.logbus import logbus
|
from neurotron.logbus import logbus
|
||||||
|
|
||||||
DISK_CANDIDATES = ["/dev/vda", "/dev/sda", "/dev/vdb"]
|
|
||||||
MOUNT_POINT = "/mnt/nfdos"
|
MOUNT_POINT = "/mnt/nfdos"
|
||||||
|
|
||||||
|
|
||||||
class DiskAgent(Neuron):
|
class DiskAgent(Neuron):
|
||||||
|
"""
|
||||||
|
DiskAgent minimalista e pragmático:
|
||||||
|
|
||||||
|
- Descobre o primeiro disco "real" em /sys/block (vda, sda, nvme0n1, etc)
|
||||||
|
- Se existir uma partição (ex: /dev/vda1), usa essa.
|
||||||
|
- Caso contrário, usa o disco inteiro (ex: /dev/vda).
|
||||||
|
- Verifica se já existe filesystem:
|
||||||
|
- se sim → monta
|
||||||
|
- se não → mkfs.ext4 e depois monta
|
||||||
|
- Ao montar com sucesso:
|
||||||
|
- muda ctx.mode → "persistent"
|
||||||
|
- cria /mnt/nfdos/{data,logs,dna}
|
||||||
|
"""
|
||||||
|
|
||||||
name = "DiskAgent"
|
name = "DiskAgent"
|
||||||
|
|
||||||
def __init__(self, ctx):
|
def __init__(self, ctx):
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.state = "unknown" # unknown → no_disk → found → mounted
|
self.state = "unknown" # unknown → found → fs_ok → mounted
|
||||||
self.last_msg = None
|
|
||||||
self.cooldown = 0
|
self.cooldown = 0
|
||||||
|
self.last_msg = None
|
||||||
|
self.dev = None # disco base (ex: /dev/vda)
|
||||||
|
self.target = None # device efetivo (ex: /dev/vda1 ou /dev/vda)
|
||||||
|
|
||||||
# --------------------------------
|
# ------------------------------------------------------------------
|
||||||
# HELPER
|
# HELPERS
|
||||||
# --------------------------------
|
# ------------------------------------------------------------------
|
||||||
def _emit_once(self, msg):
|
|
||||||
|
def _emit_once(self, msg: str):
|
||||||
if msg != self.last_msg:
|
if msg != self.last_msg:
|
||||||
logbus.disk(msg)
|
logbus.disk(msg)
|
||||||
self.last_msg = msg
|
self.last_msg = msg
|
||||||
|
|
||||||
def _run(self, cmd):
|
def _run_ok(self, cmd: list[str]) -> bool:
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
cmd,
|
cmd,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
check=True
|
check=True,
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# --------------------------------
|
def _run_capture(self, cmd: list[str]) -> dict:
|
||||||
# OBSERVE
|
"""
|
||||||
# --------------------------------
|
Executa comando e captura stdout/stderr + returncode.
|
||||||
|
Não levanta exceção, devolve estrutura amigável.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
text=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"ok": (proc.returncode == 0),
|
||||||
|
"rc": proc.returncode,
|
||||||
|
"out": (proc.stdout or "").strip(),
|
||||||
|
"err": (proc.stderr or "").strip(),
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"ok": False,
|
||||||
|
"rc": -1,
|
||||||
|
"out": "",
|
||||||
|
"err": repr(e),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _find_real_disks(self):
|
||||||
|
"""
|
||||||
|
Procura discos reais em /sys/block:
|
||||||
|
- aceita: vda, sda, hda, nvme0n1, etc.
|
||||||
|
- ignora: loop*, ram*, dm-*, sr*
|
||||||
|
Converte para /dev/<name> se existir.
|
||||||
|
"""
|
||||||
|
disks = []
|
||||||
|
sysblock = Path("/sys/block")
|
||||||
|
if not sysblock.exists():
|
||||||
|
return disks
|
||||||
|
|
||||||
|
for dev in sysblock.iterdir():
|
||||||
|
name = dev.name
|
||||||
|
if name.startswith(("ram", "loop", "dm-", "sr")):
|
||||||
|
continue
|
||||||
|
|
||||||
|
path = Path("/dev") / name
|
||||||
|
if path.exists():
|
||||||
|
disks.append(str(path))
|
||||||
|
|
||||||
|
return disks
|
||||||
|
|
||||||
|
def _pick_target(self, base_dev: str) -> str:
|
||||||
|
"""
|
||||||
|
Escolhe o device real a usar:
|
||||||
|
- se existir <dev>1 (ex: /dev/vda1), usa essa partição;
|
||||||
|
- caso contrário, usa o disco inteiro.
|
||||||
|
"""
|
||||||
|
# tentar partição "1"
|
||||||
|
p1 = f"{base_dev}1"
|
||||||
|
if Path(p1).exists():
|
||||||
|
return p1
|
||||||
|
|
||||||
|
# se for NVMe, a convenção é ex: /dev/nvme0n1p1
|
||||||
|
if "nvme" in base_dev:
|
||||||
|
p_nvme = base_dev + "p1"
|
||||||
|
if Path(p_nvme).exists():
|
||||||
|
return p_nvme
|
||||||
|
|
||||||
|
# fallback → disco inteiro
|
||||||
|
return base_dev
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# CICLO
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def observe(self):
|
def observe(self):
|
||||||
# corre só 1x por 10 ticks
|
# Evita spam excessivo
|
||||||
if self.cooldown > 0:
|
if self.cooldown > 0:
|
||||||
self.cooldown -= 1
|
self.cooldown -= 1
|
||||||
return
|
return
|
||||||
self.cooldown = 10
|
self.cooldown = 10
|
||||||
|
|
||||||
# 1) Procurar disco
|
# ESTADO: já montado → nada para fazer
|
||||||
dev = None
|
if self.state == "mounted":
|
||||||
for d in DISK_CANDIDATES:
|
return
|
||||||
if Path(d).exists():
|
|
||||||
dev = d
|
|
||||||
break
|
|
||||||
|
|
||||||
if not dev:
|
# 1) Encontrar disco base
|
||||||
self._emit_once("Nenhum disco encontrado — modo VOLATILE")
|
disks = self._find_real_disks()
|
||||||
|
if not disks:
|
||||||
|
self._emit_once("Nenhum disco de bloco encontrado — VOLATILE")
|
||||||
self.state = "no_disk"
|
self.state = "no_disk"
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2) Novo disco encontrado
|
dev = disks[0]
|
||||||
|
target = self._pick_target(dev)
|
||||||
|
|
||||||
|
self.dev = dev
|
||||||
|
self.target = target
|
||||||
|
|
||||||
|
# ESTADO: unknown → found
|
||||||
if self.state == "unknown":
|
if self.state == "unknown":
|
||||||
self._emit_once(f"Disco detectado: {dev}")
|
if target == dev:
|
||||||
|
self._emit_once(f"Disco detectado: {dev} (sem partições visíveis, usando disco inteiro)")
|
||||||
|
else:
|
||||||
|
self._emit_once(f"Disco detectado: {dev} (usando {target})")
|
||||||
self.state = "found"
|
self.state = "found"
|
||||||
|
return
|
||||||
|
|
||||||
# 3) Tentar identificar filesystem
|
# 2) Verificar se device existe mesmo (evitar "Can't lookup blockdev")
|
||||||
try:
|
if not Path(self.target).exists():
|
||||||
blkid = subprocess.run(
|
self._emit_once(f"{self.target}: ainda não existe em /dev — aguardando…")
|
||||||
["blkid", dev],
|
self.cooldown = 5
|
||||||
text=True,
|
return
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
).stdout.strip()
|
|
||||||
except FileNotFoundError:
|
|
||||||
# blkid não existe → fallback gentle
|
|
||||||
logbus.debug("[disk] blkid não encontrado — fallback ativo")
|
|
||||||
blkid = ""
|
|
||||||
|
|
||||||
if blkid:
|
# 3) ESTADO: found → verificar filesystem
|
||||||
self._emit_once("Sistema de ficheiros válido encontrado")
|
if self.state == "found":
|
||||||
else:
|
res_blkid = self._run_capture(["blkid", self.target])
|
||||||
self._emit_once("Disco virgem detectado — não formatado")
|
blkid_out = res_blkid["out"]
|
||||||
|
|
||||||
# 4) Montar
|
if "TYPE=" in blkid_out:
|
||||||
Path(MOUNT_POINT).mkdir(parents=True, exist_ok=True)
|
self._emit_once("Filesystem existente detectado")
|
||||||
|
self.state = "fs_ok"
|
||||||
|
return
|
||||||
|
|
||||||
if self._run(["mount", dev, MOUNT_POINT]):
|
# Não há TYPE= → precisamos formatar
|
||||||
if self.state != "mounted":
|
self._emit_once("Formatação filesystem (mke2fs) — journaling ON")
|
||||||
self._emit_once(f"Montado em {MOUNT_POINT}")
|
|
||||||
|
# BusyBox mke2fs não aceita -t, e o e2fsprogs aceita.
|
||||||
|
cmd = ["/sbin/mke2fs", "-t", "ext4", "-F", self.target]
|
||||||
|
res_mkfs = self._run_capture(cmd)
|
||||||
|
|
||||||
|
if not res_mkfs["ok"]:
|
||||||
|
logbus.error(f"[mkfs] Falhou (rc={res_mkfs['rc']})")
|
||||||
|
if res_mkfs["err"]:
|
||||||
|
logbus.error(f"[mkfs.err] {res_mkfs['err']}")
|
||||||
|
if res_mkfs["out"]:
|
||||||
|
logbus.error(f"[mkfs.out] {res_mkfs['out']}")
|
||||||
|
self._emit_once("Erro na formatação — retry mais tarde")
|
||||||
|
self.cooldown = 20
|
||||||
|
return
|
||||||
|
|
||||||
|
self._emit_once("Filesystem criado (mke2fs OK) 🎉")
|
||||||
|
self.state = "fs_ok"
|
||||||
|
return
|
||||||
|
|
||||||
|
# 4) ESTADO: fs_ok → montar
|
||||||
|
if self.state == "fs_ok":
|
||||||
|
Path(MOUNT_POINT).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# --- montar FS ---
|
||||||
|
opts = "rw,noatime"
|
||||||
|
cmd = ["mount", "-o", opts, self.target, MOUNT_POINT]
|
||||||
|
res_mount = self._run_capture(cmd)
|
||||||
|
|
||||||
|
if res_mount["ok"]:
|
||||||
|
self._emit_once(f"Montado em {MOUNT_POINT} ({self.target})")
|
||||||
self.state = "mounted"
|
self.state = "mounted"
|
||||||
|
|
||||||
# 5) Trocar Neurotron → modo persistente
|
|
||||||
if self.ctx.mode != "persistent":
|
if self.ctx.mode != "persistent":
|
||||||
self.ctx.mode = "persistent"
|
self.ctx.mode = "persistent"
|
||||||
logbus.info("Modo alterado → PERSISTENT")
|
logbus.info("Modo alterado → PERSISTENT")
|
||||||
|
|
||||||
# 6) Estrutura básica
|
|
||||||
for d in ["data", "logs", "dna"]:
|
for d in ["data", "logs", "dna"]:
|
||||||
Path(MOUNT_POINT, d).mkdir(exist_ok=True)
|
Path(MOUNT_POINT, d).mkdir(exist_ok=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._emit_once("Falha ao montar — mantendo VOLATILE")
|
logbus.error(f"[mount] Falhou (rc={res_mount['rc']})")
|
||||||
|
if res_mount["err"]:
|
||||||
|
logbus.error(f"[mount.err] {res_mount['err']}")
|
||||||
|
if res_mount["out"]:
|
||||||
|
logbus.error(f"[mount.out] {res_mount['out']}")
|
||||||
|
self._emit_once("Falha ao montar — retry mais tarde")
|
||||||
|
self.cooldown = 15
|
||||||
|
|
||||||
|
return
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
# neurotron/echo_agent.py
|
# neurotron/echo_agent.py
|
||||||
|
|
||||||
from neurotron.neuron import Neuron
|
from neurotron.neuron import Neuron
|
||||||
from neurotron.logbus import logbus
|
from neurotron.logbus import logbus
|
||||||
|
|
||||||
@ -7,21 +8,28 @@ class EchoAgent(Neuron):
|
|||||||
|
|
||||||
def __init__(self, ctx):
|
def __init__(self, ctx):
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.last_cpu = None
|
self.last = None
|
||||||
|
|
||||||
def think(self):
|
def think(self):
|
||||||
msg = self.consume("vitals")
|
"""
|
||||||
if not msg:
|
O EchoAgent apenas ecoa valores vitais para debug.
|
||||||
return
|
Deve ser 100% seguro: cpu/mem podem ser '?' quando /proc
|
||||||
|
ainda não está estável. Nunca deve formatar com {:.2f}.
|
||||||
cpu = msg.get("cpu_percent")
|
"""
|
||||||
if cpu is None:
|
|
||||||
return
|
snap = self.ctx.perception.snapshot()
|
||||||
|
|
||||||
|
cpu = snap.get("cpu_percent")
|
||||||
|
mem = snap.get("mem_percent")
|
||||||
|
|
||||||
|
# Se não forem números, convertemos para string limpa
|
||||||
|
cpu_str = f"{cpu}" if isinstance(cpu, (int, float)) else str(cpu)
|
||||||
|
mem_str = f"{mem}" if isinstance(mem, (int, float)) else str(mem)
|
||||||
|
|
||||||
|
msg = f"CPU={cpu_str}% MEM={mem_str}%"
|
||||||
|
|
||||||
|
# Evita spam
|
||||||
|
if msg != self.last:
|
||||||
|
logbus.info(f"[echo] {msg}")
|
||||||
|
self.last = msg
|
||||||
|
|
||||||
# só fala se variar > 5%
|
|
||||||
if self.last_cpu is None or abs(cpu - self.last_cpu) >= 5:
|
|
||||||
self.publish("actions", {
|
|
||||||
"action": "echo",
|
|
||||||
"text": f"CPU {cpu:.1f}%"
|
|
||||||
})
|
|
||||||
self.last_cpu = cpu
|
|
||||||
|
|||||||
@ -1,29 +1,76 @@
|
|||||||
# neurotron/logbus.py
|
# neurotron/logbus.py
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import sys
|
import sys
|
||||||
import threading
|
|
||||||
|
|
||||||
class LogBus:
|
class LogBus:
|
||||||
def __init__(self):
|
"""
|
||||||
self.lock = threading.Lock()
|
Canal central de logs do Neurotron.
|
||||||
|
- escreve tudo em stdout (texto simples, sem Rich)
|
||||||
|
- mantém um buffer em memória para o dashboard (tail)
|
||||||
|
"""
|
||||||
|
|
||||||
def emit(self, level, msg):
|
def __init__(self, maxlen: int = 1000):
|
||||||
"""Escreve logs com timestamp e nível padronizado."""
|
self._buffer = deque(maxlen=maxlen)
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# núcleo
|
||||||
|
# -------------------------
|
||||||
|
def _emit(self, level: str, msg: str):
|
||||||
ts = datetime.utcnow().strftime("%H:%M:%S")
|
ts = datetime.utcnow().strftime("%H:%M:%S")
|
||||||
line = f"[{ts}] [{level}] {msg}\n"
|
line = f"[{ts}] [{level}] {msg}"
|
||||||
|
|
||||||
with self.lock:
|
# guarda em memória para o dashboard
|
||||||
sys.stdout.write(line)
|
self._buffer.append(line)
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
# atalhos
|
# escreve em stdout (uma linha)
|
||||||
def info(self, msg): self.emit("info", msg)
|
sys.stdout.write(line + "\n")
|
||||||
def warn(self, msg): self.emit("warn", msg)
|
sys.stdout.flush()
|
||||||
def error(self, msg): self.emit("error", msg)
|
|
||||||
def disk(self, msg): self.emit("disk", msg)
|
|
||||||
def diag(self, msg): self.emit("diag", msg)
|
|
||||||
def heart(self, msg): self.emit("heart", msg)
|
|
||||||
def debug(self, msg): self.emit("debug", msg)
|
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# API pública de logs
|
||||||
|
# -------------------------
|
||||||
|
def info(self, msg: str):
|
||||||
|
self._emit("info", msg)
|
||||||
|
|
||||||
|
def warn(self, msg: str):
|
||||||
|
self._emit("warn", msg)
|
||||||
|
|
||||||
|
def error(self, msg: str):
|
||||||
|
self._emit("error", msg)
|
||||||
|
|
||||||
|
def debug(self, msg: str):
|
||||||
|
self._emit("debug", msg)
|
||||||
|
|
||||||
|
def disk(self, msg: str):
|
||||||
|
self._emit("disk", msg)
|
||||||
|
|
||||||
|
def heart(self, msg: str):
|
||||||
|
# heartbeat mini e compacto
|
||||||
|
self._emit("heart", msg)
|
||||||
|
|
||||||
|
def diag(self, msg: str):
|
||||||
|
self._emit("diag", msg)
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# Integração com código antigo (emit)
|
||||||
|
# -------------------------
|
||||||
|
def emit(self, msg: str):
|
||||||
|
# fallback para chamadas antigas tipo logbus.emit("...")
|
||||||
|
self.info(msg)
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# Para o dashboard
|
||||||
|
# -------------------------
|
||||||
|
def tail(self, n: int = 150):
|
||||||
|
"""Devolve as últimas n linhas de log como lista de strings."""
|
||||||
|
if n <= 0:
|
||||||
|
return []
|
||||||
|
buf = list(self._buffer)
|
||||||
|
return buf[-n:]
|
||||||
|
|
||||||
|
|
||||||
|
# instância global
|
||||||
logbus = LogBus()
|
logbus = LogBus()
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,30 @@
|
|||||||
# neurotron/vitalsigns_agent.py
|
# neurotron/vitalsigns_agent.py
|
||||||
|
|
||||||
from neurotron.neuron import Neuron
|
from neurotron.neuron import Neuron
|
||||||
|
from neurotron.logbus import logbus
|
||||||
|
|
||||||
class VitalSigns(Neuron):
|
class VitalSigns(Neuron):
|
||||||
"""
|
|
||||||
Observa sinais vitais (CPU, MEM, LOAD)
|
|
||||||
e publica em "vitals".
|
|
||||||
"""
|
|
||||||
name = "VitalSigns"
|
name = "VitalSigns"
|
||||||
|
|
||||||
def observe(self):
|
def __init__(self, ctx):
|
||||||
|
super().__init__(ctx)
|
||||||
|
self.last = None
|
||||||
|
|
||||||
|
def think(self):
|
||||||
snap = self.ctx.perception.snapshot()
|
snap = self.ctx.perception.snapshot()
|
||||||
self.publish("vitals", snap)
|
|
||||||
self.ctx.memory.remember("observe.vitals", snap)
|
cpu = snap.get("cpu_percent")
|
||||||
|
mem = snap.get("mem_percent")
|
||||||
|
load = snap.get("loadavg") or ["?", "?", "?"]
|
||||||
|
|
||||||
|
cpu_str = f"{cpu}" if isinstance(cpu, (int, float)) else str(cpu)
|
||||||
|
mem_str = f"{mem}" if isinstance(mem, (int, float)) else str(mem)
|
||||||
|
load1 = load[0]
|
||||||
|
|
||||||
|
msg = f"Vitals CPU={cpu_str}% MEM={mem_str}% load1={load1}"
|
||||||
|
|
||||||
|
# evita spam
|
||||||
|
if msg != self.last:
|
||||||
|
logbus.debug(msg)
|
||||||
|
self.last = msg
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user