Files

140 lines
7.0 KiB
TeX

%% SCRAP: architecture/heartbeat_csv_export
%% SOURCE: docs/working/architecture/heartbeat_csv_export.md
%% STATUS: WORKING
%% FITS: dev-guide/ch-heartbeat, user-guide/ch-heartbeat, cookbook/ch-heartbeat
%% EDITORIAL: lifted — prose rewritten to press voice
%% TODO(bob): reconcile implementation status. This source describes
%% heartbeat_capture_tick_snapshot() / heartbeat_emit_tick_row() as live,
%% but .claude/CLAUDE.md records heartbeat_export_csv() as "not yet
%% implemented." Confirm which functions ship in the current tree before
%% promoting this to CURRENT.
\section{Heartbeat CSV Export}
\label{sec:heartbeat:csv}
The heartbeat subsystem emits real-time multivariate VM metrics to
\texttt{stderr} in CSV form, exposing StarForth's adaptive runtime to
external analysis. Each row carries eleven performance indicators sampled
once per heartbeat tick — nominally every millisecond — producing a
continuous time series of the running system's internal state.
The design favours streaming over buffering. Metrics are read directly from
VM state with no intermediate copy, each row is flushed immediately so that
downstream tools see data as it is produced, and the stream is confined to
\texttt{stderr} to keep \texttt{stdout} reserved for test output. The
emitting thread is a background \texttt{pthread} decoupled from the main
interpreter, so capture does not perturb execution timing beyond a fixed,
small cost per tick.
\subsection{CSV Format}
The VM emits raw rows only; column names are supplied separately by analysis
tooling. The reference header is:
\begin{lstlisting}[language=bash]
tick_number,elapsed_ns,tick_interval_ns,cache_hits_delta,bucket_hits_delta,
word_executions_delta,hot_word_count,avg_word_heat,window_width,
predicted_label_hits,estimated_jitter_ns
\end{lstlisting}
The \emph{Status} column below reflects which fields carry live values and
which currently emit a placeholder zero pending delta-tracking work.
\begin{table}[ht]
\centering
\caption{Heartbeat CSV columns}
\label{tab:heartbeat:columns}
\begin{tabular}{lllp{5.4cm}l}
\toprule
Column & Type & Unit & Description & Status \\
\midrule
\texttt{tick\_number} & \texttt{uint32} & count & Monotonic tick counter since VM start & live \\
\texttt{elapsed\_ns} & \texttt{uint64} & ns & Time since VM initialization & live \\
\texttt{tick\_interval\_ns} & \texttt{uint64} & ns & Time since previous tick (actual vs.\ nominal 1\,ms) & live \\
\texttt{cache\_hits\_delta} & \texttt{uint32} & count & Hotwords cache hits since last tick & TODO \\
\texttt{bucket\_hits\_delta} & \texttt{uint32} & count & Bucket-level cache hits since last tick & TODO \\
\texttt{word\_executions\_delta}& \texttt{uint32} & count & FORTH word executions since last tick & TODO \\
\texttt{hot\_word\_count} & \texttt{uint64} & count & Words at or above the heat promotion threshold ($\geq 10$) & live \\
\texttt{avg\_word\_heat} & \texttt{double} & heat & Mean execution heat across the dictionary (\Qtype{} $\to$ double) & live \\
\texttt{window\_width} & \texttt{uint32} & entries & Current rolling window effective size (adaptive) & live \\
\texttt{predicted\_label\_hits} & \texttt{uint32} & count & Successful pipelining speculation hits & TODO \\
\texttt{estimated\_jitter\_ns} & \texttt{double} & ns & Absolute deviation from the nominal 1\,ms interval & live \\
\bottomrule
\end{tabular}
\end{table}
\subsection{Capturing Metrics}
The simplest capture redirects \texttt{stderr} to a file and discards
\texttt{stdout}:
\begin{lstlisting}[language=bash]
# Capture metrics, discard test output
./build/amd64/fastest/starforth --doe 2>heartbeat.csv 1>/dev/null
# Keep both streams, separated
./build/amd64/fastest/starforth --doe 1>test_output.txt 2>heartbeat.csv
\end{lstlisting}
Because rows are flushed as they are produced, the stream can be monitored
live — for example piped through \texttt{csvlook} from \texttt{csvkit}, or
followed with \texttt{tail -f}. For post-processing, prepend the reference
header and load the result into R or pandas:
\begin{lstlisting}[language=bash]
{ echo "tick_number,elapsed_ns,tick_interval_ns,cache_hits_delta,\
bucket_hits_delta,word_executions_delta,hot_word_count,avg_word_heat,\
window_width,predicted_label_hits,estimated_jitter_ns"; \
cat heartbeat.csv; } > analysis.csv
\end{lstlisting}
\subsection{Analysis Use Cases}
The eleven-dimensional series supports three broad classes of analysis. For
dynamics modeling, the trajectory admits phase-space reconstruction to locate
attractor basins in the adaptive parameter space, Lyapunov-exponent
estimation to quantify stability, and mutual-information analysis to discover
coupling between metrics. For tuning validation, window-width adaptation can
be correlated against prediction accuracy, heat-decay behaviour against stale
word accumulation, and jitter against thread-scheduling effects. For workload
profiling, the same data yields hot-word churn rate, execution density
(\texttt{word\_executions\_delta} over \texttt{tick\_interval\_ns}), and cache
efficiency relative to working-set size.
\subsection{Implementation}
Capture is performed by \texttt{heartbeat\_capture\_tick\_snapshot()}, which
reads the monotonic tick counter, computes elapsed and inter-tick time,
walks the dictionary to count hot words and their mean heat, samples the
rolling window size, and estimates jitter as the absolute difference between
the actual and nominal interval. The walk is linear in dictionary size
(on the order of a few hundred words). Emission is performed by
\texttt{heartbeat\_emit\_tick\_row()}, which formats the eleven values and
flushes \texttt{stderr} immediately. Both are invoked once per cycle from the
background heartbeat thread.
The thread runs without the original 50\,ms startup delay, so metrics begin
flowing immediately even for short-running DoE workloads. The trade-off is
that the first few ticks may show transient values during the word-registration
phase.
\subsection{Known Limitations}
Three groups of fields are not yet wired to live deltas and currently emit
zero: the cache, bucket, and word-execution counters all require per-tick
baseline tracking in the heartbeat state. Separately, the source notes an
uninitialized \texttt{run\_start\_ns}, which can produce implausibly large
\texttt{elapsed\_ns} values in the first ticks until the field is seeded at
VM initialization, and a missing aggregation of per-word prefetch hits behind
\texttt{predicted\_label\_hits}.
%% TODO(bob): the source proposes concrete C fixes for each limitation
%% (delta fields in HeartbeatState, run_start_ns seeding in vm_init,
%% prefetch-hit accumulation). Promote these only after confirming against
%% the current tree — see the header note on implementation-status drift.
The runtime cost is small: emission adds well under one percent CPU at the
nominal tick rate, the snapshot is a stack-allocated structure of under a
hundred bytes, and the output volume is on the order of a hundred kilobytes
per second of execution.