550c98f275
Reorganización física de crates/: - core/ (mezclaba 6 propósitos) se divide en protocol/, init/, runtime/, compat/ - shared/ (3 crates) se redistribuye en protocol/ e init/ - lapaloma (sub-módulo de ui_engine) se promueve a modules/pineal/ Renames de proyectos: - shipote → shuma (runtime de sandboxes) - nouser → akasha (explorador de Mónadas) - yahweh → nahual (motor GPUI, antes ui_engine/) - lapaloma → pineal (data-viz agnóstica) Fraccionamiento UI → core agnóstico: - vista-core (DeckState + snap, 175 LOC, 5 tests verdes) - barra-core (Task + render_html + sanitize, 90 LOC, 5 tests verdes) - vista-web y barra-web ahora son thin DOM bindings Documentación nueva: - 16 SDDs por subdirectorio (≤80 LOC c/u): protocol/init/runtime/compat + 10 módulos + apps/ - docs/STATUS.md con cifras reales por proyecto - docs/ROADMAP.md con plan a finalización (6 hitos, ~6-8 semanas) - CHANGELOG.md particionado en docs/changelog/<proyecto>.md (7 buckets) Automatización: - scripts/reorg.py — script idempotente que: git mv directorios, renombra package names, recomputa path = refs, reescribe imports rust, actualiza workspace Cargo.toml. Soporta --dry-run. - scripts/split-changelog.py — particiona CHANGELOG por componente. Validación: - cargo check --workspace pasa (124 crates + 2 nuevos cores). - 10 tests adicionales (5 en vista-core + 5 en barra-core) verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
123 lines
3.4 KiB
Rust
123 lines
3.4 KiB
Rust
//! Ring buffer en memoria para capturar stdout/stderr de comandos.
|
|
//!
|
|
//! Tamaño fijo por comando (config: `MAX_LOG_BYTES`). Cuando se llena,
|
|
//! descarta los bytes más viejos. Pensado para diagnostico rápido, no
|
|
//! para retención histórica — eso es trabajo de un journald-like aparte.
|
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
/// Bytes máximos retenidos por comando. 64 KiB cubre logs típicos sin
|
|
/// abusar de memoria si el daemon tiene cientos de comandos vivos.
|
|
pub const MAX_LOG_BYTES: usize = 64 * 1024;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct LogBuf {
|
|
inner: Arc<Mutex<Inner>>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Inner {
|
|
/// Bytes raw. Cuando se acerca al cap, descartamos head para mantener
|
|
/// el tail.
|
|
buf: Vec<u8>,
|
|
cap: usize,
|
|
/// Total escrito alguna vez (no decrementado al recortar).
|
|
written_total: u64,
|
|
}
|
|
|
|
impl LogBuf {
|
|
pub fn new() -> Self {
|
|
Self::with_cap(MAX_LOG_BYTES)
|
|
}
|
|
|
|
pub fn with_cap(cap: usize) -> Self {
|
|
Self {
|
|
inner: Arc::new(Mutex::new(Inner {
|
|
buf: Vec::with_capacity(cap.min(4096)),
|
|
cap,
|
|
written_total: 0,
|
|
})),
|
|
}
|
|
}
|
|
|
|
pub fn append(&self, data: &[u8]) {
|
|
let Ok(mut g) = self.inner.lock() else { return };
|
|
g.written_total += data.len() as u64;
|
|
g.buf.extend_from_slice(data);
|
|
// Recorte cuando excede cap (con un pequeño slack para evitar
|
|
// shift en cada append). El usuario ve sólo el tail.
|
|
if g.buf.len() > g.cap + 1024 {
|
|
let drop = g.buf.len() - g.cap;
|
|
g.buf.drain(..drop);
|
|
}
|
|
}
|
|
|
|
/// Devuelve el tail de hasta `n` bytes (o todo si `n=0`).
|
|
pub fn tail(&self, n: usize) -> Vec<u8> {
|
|
let g = match self.inner.lock() {
|
|
Ok(g) => g,
|
|
Err(_) => return Vec::new(),
|
|
};
|
|
if n == 0 || n >= g.buf.len() {
|
|
return g.buf.clone();
|
|
}
|
|
g.buf[g.buf.len() - n..].to_vec()
|
|
}
|
|
|
|
/// Cuántos bytes hay actualmente en el buffer.
|
|
pub fn len(&self) -> usize {
|
|
self.inner.lock().map(|g| g.buf.len()).unwrap_or(0)
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
self.len() == 0
|
|
}
|
|
|
|
pub fn written_total(&self) -> u64 {
|
|
self.inner.lock().map(|g| g.written_total).unwrap_or(0)
|
|
}
|
|
}
|
|
|
|
impl Default for LogBuf {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn append_and_tail_basic() {
|
|
let lb = LogBuf::with_cap(100);
|
|
lb.append(b"hello ");
|
|
lb.append(b"world\n");
|
|
let t = lb.tail(0);
|
|
assert_eq!(t, b"hello world\n");
|
|
}
|
|
|
|
#[test]
|
|
fn cap_drops_oldest() {
|
|
let lb = LogBuf::with_cap(10);
|
|
lb.append(&[b'a'; 8]);
|
|
lb.append(&[b'b'; 8]);
|
|
// Después del recorte, debe quedar ~10 bytes pero el slack
|
|
// permite hasta 10+1024. Como pasamos slack, no se recorta aún
|
|
// en este caso (16 bytes < 10+1024). Forzamos un append grande.
|
|
lb.append(&[b'c'; 2048]);
|
|
assert!(lb.len() <= 10 + 1024);
|
|
let t = lb.tail(0);
|
|
// El tail debe contener 'c's (los más recientes).
|
|
assert!(t.iter().filter(|&&b| b == b'c').count() > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn written_total_tracks_all() {
|
|
let lb = LogBuf::with_cap(10);
|
|
lb.append(b"abcdef");
|
|
lb.append(b"ghijkl");
|
|
assert_eq!(lb.written_total(), 12);
|
|
}
|
|
}
|