Skip to content

About

Four-phase study of lightweight streaming anomaly detectors for switch telemetry under a 100 us / 100 byte budget, with parity-verified C implementations and a 96-byte unified detector

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Network Telemetry Anomaly Detection

On-device, lightweight time-series anomaly detection for network switches: which streaming algorithms stay accurate under a hard < 100 µs / < 100 byte budget, with C versions checked against the Python reference.

Python C NumPy SciPy React Vite Apache ECharts pytest

Overview

Network switches emit a lot of telemetry (link utilisation, packet rate, queue depth, error counters, jitter) at sub-second granularity. The usual approach is to ship all of it to a central collector and run analytics there, or fall back to static thresholds on the device. This project asks a narrower question: can you run useful anomaly detection on the switch itself, on the ARM-class control-plane processor, where you get a few kilobytes of RAM and need to answer in microseconds?

That turns into a concrete resource budget. Each detector sees one sample at a time, keeps state in under 100 bytes per monitored metric, updates in under 100 microseconds, works on short observation windows of 10–50 samples, and has to be implementable in plain C with O(1) (or near-O(1)) arithmetic. The work is a four-phase empirical study. It starts with about 15 candidate time-series techniques, narrows them down, benchmarks them on real ISP traffic and on labelled synthetic anomalies, and ends with a production build where every detector has a portable C twin that is numerically checked against its Python reference.

It started as work for HPE (Aruba/HP CPP), and the target hardware framing is HPE Aruba switches. The repo is laid out as research: theory, single-detector benchmark, ensemble, then a production phase that measures both detection quality and real CPU and memory cost and picks a winner under the budget. The headline recommendation is the deriv first-difference z-score detector at 4.9 ns/sample and 20 bytes, which turns out to be Pareto-dominant: the cheapest and the most accurate overall.

Key Features

  • Four-phase study, each building on the last: algorithm theory, single-detector benchmark, confirmation-gated ensemble, then a production build with C twins.
  • About 20 streaming detectors covering statistical (EWMA/EWMV z-score), robust (median + MAD, Hampel), change-point (CUSUM, Page-Hinkley), derivative (first-difference), spectral (lag-k autocorrelation drop), and several ensembles (layered, voting, cascade, plus a single all-in-one unified detector).
  • One shared detector contract: every detector, single or ensemble, implements the same update(x) -> score >= 0 streaming interface, returns 0.0 during warm-up, and decides via score >= threshold. The registry is the single source of truth for which detectors exist and what anomaly types each targets.
  • Real datasets: CESNET-TimeSeries24 (10-minute aggregated ISP traffic, 283 IPs) in Phase 2, plus 14 NAB streams (realTraffic + realKnownCause) alongside labelled synthetic anomalies in Phase 4.
  • Labelled anomaly injection: ground-truth burst/spike, gradual drift, periodicity loss, and transient anomalies at configurable magnitude, duration and position, so every benchmark is reproducible and scored against known labels.
  • Parity-checked C99 twin: tsad.c ports nine single detectors to portable C. A parity.c harness checks that the C output matches the Python reference to ≤ 1e-4, and bench.c measures per-sample latency with QueryPerformanceCounter.
  • Two-axis selection: detectors are scored on detection quality (VUS-PR, F1, MCC, latency, false-positive rate) and measured cost (ns/sample, state bytes), then chosen by a Pareto/scorecard analysis behind a hard budget gate.
  • Per-phase interactive dashboards: each phase has a React + Vite + Apache ECharts dashboard, and the earlier ones build to a single self-contained HTML file you open in a browser.
  • Tests: pytest suites across the phases cover the detector contract, the anomaly injector, the sliding-window buffer, the budget gate, and C-to-Python parity.

How It Works

Phase 1: Algorithm study (theory)

The starting point was a written study of about 15 candidate time-series techniques against the on-device evaluation criteria. Nine were rejected on memory, compute or history grounds (ADWIN, DDM, Kalman filtering, Matrix Profile, Spectral Residual, SAX, ARIMA, PELT, Binary Segmentation), because they need too much state, too much compute, or long histories that don't fit a 10–50 sample window. Six finalists were carried forward for testing: Z-Score, MAD, EWMA, CUSUM, Page-Hinkley, and Sliding-Window stats. Phase 1/ holds the algorithm study document, the evaluation criteria spec, and three PDFs with my notes on some of the algorithms.

Phase 2: Single-detector benchmark

The six finalists were implemented against a shared DetectorBase contract and run on real CESNET-TimeSeries24 traffic. The data pipeline standardises the per-IP CSV schema, uses the n_bytes signal by default, and feeds it through a fixed-capacity circular buffer that keeps mean and variance in O(1) with Welford's online algorithm, so nothing is recomputed per update. A labelled injector adds burst / rate-shift / drift / transient anomalies with known positions.

The sweep is 6 detectors × 4 window sizes (10, 20, 30, 50) × 4 anomaly types × 30 trials = 2,880 runs, scored on TPR, FPR, F1, detection latency and AUC. The main finding: no single detector wins across all anomaly classes. MAD leads on bursts and transients, EWMA on rate shifts, Page-Hinkley and CUSUM on drifts, and precision collapses under the class imbalance (a handful of anomalous samples in a ~280-sample series). That led to the ensemble in Phase 3.

Phase 3: Two-layer confirmation-gated ensemble

Phase 3 sits on top of Phase 2. It reuses every Phase 2 detector, hyperparameter, dataset, RNG seed and harness through a bridge module, so results stay row-comparable. It adds three classes:

  • ConfirmationGate(child, n) wraps any detector and only fires after n consecutive child alarms, which suppresses the singleton false alarms that MAD/Z-Score throw on tail noise.
  • VotingLayer(children, mode="AND"|"OR") combines gated children. The score is the max child score (so it stays ROC-compatible), and it reports the vote count.
  • TwoLayerEnsemble(spike_layer, sustained_layer) pairs a high-precision spike pipeline (gated MAD AND gated Z-Score) with a high-recall sustained pipeline (gated EWMA OR gated CUSUM), joined by a top-level OR with per-layer attribution so the dashboard can show which layer caught each anomaly.

The re-benchmark is 14 detectors × 4 windows × 4 types × 30 trials = 6,720 runs. Confirmation gating cuts false positives sharply (MAD 14.6 → 5.6 %, Z-Score 5.1 → 0.74 %, CUSUM 12 → 5.7 % FPR) while the ensemble keeps recall within ~4 percentage points of the best single detector.

Phase 4: Production build with a C twin

Phase 4 is the main deliverable. It rebuilds a fresh field of detectors as a self-contained product and evaluates them on two axes at once: detection quality and real measured cost.

  • Detectors: nine single (ewma_z, robust_z, hampel, cusum, page_hinkley, ewmv_adaptive, deriv, acf_periodicity, and a deliberately heavy heavy_baseline control) plus three combined (layered = EWMA→CUSUM OR-fusion; voting = 4-member soft vote; cascade = a cheap EWMA pre-filter gating an expensive robust confirm). An improvement study and a unified-detector search add anomaly-aware "hold" baselines, confirmation-gated variants, and the unified detector, which brings the registry to about 20 entries.
  • Datasets: labelled synthetic generators inject the four anomaly types at {4, 6, 9}σ across multiple seeds, plus 14 real NAB streams for external validity.
  • Quality metrics: PR-AUC and threshold-free VUS-PR (the headline), F1 / precision / recall / MCC at the best operating point, an event-tolerant F1 (±2-sample) at the operator-tuned threshold for point anomalies, a NAB-like early-detection score, detection latency, and false positives per 1000 samples.
  • Cost metrics: per-sample time from the C twin via QueryPerformanceCounter, float32 state-byte footprint (tsad_state_bytes), a Python cross-check, and an ARM-cycle projection. The < 100 µs / < 100 byte budget is a hard gate in selection.

The runtime side (what would ship to the switch) and the evaluation harness (what chose it) share the same detector contract. The C twin in src/c/ is a single TsadDetector struct plus tsad_init / tsad_update / tsad_state_bytes, with a ring buffer for the window-based detectors and plain scalar recursion for the O(1) ones. A parity harness streams the same input through C and Python and asserts the scores agree.

The unified detector

A search across parallel strategies produced one streaming unit, unified, that covers all four anomaly types in 96 bytes of shared state (five float scalars, an integer period, a 17-deep ring buffer, and counters). Three heads read that shared state and the output is the max of their normalised scores:

  • a derivative head with an anomaly-aware "hold" baseline, for spikes and transients,
  • a held EWMA control-chart head with a clipped output, for drift,
  • a gated ACF-drop head that only arms when the signal is actually periodic, for periodicity loss.

Three choices keep the heads from drowning each other out: the drift output is clipped so a legitimate step can't out-shout a spike, the periodicity head stays silent on aperiodic signals, and all heads share one state block instead of four. That is what lets a single 96-byte unit do the job of a naive 4-detector vote that would cost about 424 bytes.

The Final Pipeline/ folder packages this detector as a standalone deliverable: Python, C and JavaScript versions, a parity check, and a React dashboard that runs the detector live in the browser or through a small local server (server.py). It has its own README.

Results / Highlights

These figures come from the committed Phase 4 results (Phase 4/results/selection.json, the measured C cost data in Phase 4/results/c_cost.csv, and Phase 4/report/Phase4_Report.md).

  • Recommended default: deriv (first-difference z-score), window 50: 4.9 ns/sample, 20 bytes, Pareto-dominant (cheapest and most accurate overall), F1 of about 0.69 at its best threshold, near-zero detection latency, and almost no accuracy loss down to a 10-sample window.
  • Memory, not compute, is the binding constraint. On the x86 host every detector runs far under the 100 µs budget, from 4.9 ns/sample (deriv) to about 2.0 µs/sample (heavy_baseline). What breaks the budget is bytes: window-buffer detectors hold a float[window] ring and exceed 100 bytes past about window 22, while the O(1) recursive detectors (16–32 bytes) fit at any window, and those same detectors are also the most window-robust.
  • A single 96-byte unified detector reaches event-F1 ≥ 0.90 on all four controlled anomaly types at window 30–50 (spike 0.98, drift 0.91, periodicity 1.00, transient 0.98; min 0.91), once a spike is defined at a detectable ≥ 6σ magnitude and scored with the ±2-sample event metric. It is the top detector by VUS-PR overall.
  • Condition → algorithm map (each empirical winner matches its design intent): drift → ewmv_adaptive (F1 ≈ 0.84), periodicity → acf_periodicity (F1 ≈ 0.87), spike and transient → deriv (F1 ≈ 0.71 / 0.74).
  • Confirmation gating cuts false positives 50–85 % (Phase 3) while keeping recall within ~4 points of the best single detector.
  • Scale: Phase 2 ran 2,880 trials, Phase 3 ran 6,720, and Phase 4 totals roughly 24,800 evaluation runs across its core sweep and improvement studies, with ~180+ tests across the phases and C↔Python parity verified to ≤ 1e-4.
  • Limits: a 4σ single-sample spike is within normal noise and is a detection limit for any lightweight causal detector. Real NAB traffic, with anomalies not labelled by type, stays below 0.90, which is expected for single-metric detection under this budget.

Tech Stack

  • Languages: Python 3.10+, C (C99), JavaScript, PowerShell
  • Data / analysis: NumPy, SciPy, pandas (Phase 2/3), Matplotlib, seaborn, tqdm. Phase 4's evaluation pipeline depends only on NumPy and the Python standard library, so it runs on memory-constrained hosts.
  • On-device twin: portable C99 compiled with MinGW-w64 / gcc (-O2), benchmarked for ns/sample and state bytes and checked against the Python reference
  • Dashboards: React 18, Vite, Apache ECharts
  • Testing: pytest (detector contract, injector, window buffer, budget gate, parity)
  • Datasets: CESNET-TimeSeries24 (Zenodo), Numenta Anomaly Benchmark (NAB)

Getting Started

Prerequisites

  • Python 3.10+
  • A C compiler for the Phase 4 twin: MinGW-w64 / gcc (the build script is PowerShell)
  • Node.js 18+ and npm (only if you want to (re)build the dashboards)
  • The CESNET sample archive for Phase 2 (download instructions below)

Installation

git clone https://github.com/DCode-v05/Network-Telemetry.git
cd Network-Telemetry

# Phase 2 environment
cd "Phase 2"
python -m venv env
env\Scripts\activate          # Windows
# source env/bin/activate      # macOS / Linux
pip install -r requirements.txt

For Phase 2 you also need the CESNET-TimeSeries24 sample (ip_addresses_sample.tar.gz, ~171 MB) from its Zenodo release. Extract the per-IP CSVs into Phase 2/data/ip_addresses_sample/. If they land under an agg_10_minutes/ subfolder, point DATA_DIR in Phase 2/config.py at it.

Running

# Phase 2: single-detector benchmark (2,880 trials)
cd "Phase 2"
python main.py
pytest tests/ -v

# Phase 3: ensemble benchmark (reuses Phase 2's env + data)
cd "../Phase 3"
python main.py --quick --no_plot --no_dashboard      # ~30s smoke run
python main.py --compare_phase2_csv "../Phase 2/results/csv/aggregated_results.csv"
pytest tests/ -v

# Phase 4: production build + C twin (downloads NAB on first run)
powershell -NoProfile -File "Phase 4\scripts\run_all.ps1"   # data -> sweep -> C build/bench -> selection -> figures -> dashboard -> tests
python -m pytest "Phase 4\tests" -q

To stream a single signal through any Phase 4 detector and watch live alerts:

cd "Phase 4\src\python"
python -m cli.stream_demo --detector deriv --window 50 --synthetic spike

The Final Pipeline has its own steps (run_all.ps1, then npm run dev in dashboard/). See Final Pipeline/README.md.

Usage

  • Run a benchmark. Phase 2/main.py runs the full single-detector sweep. Phase 3/main.py runs the ensemble sweep and can compare row-for-row against Phase 2 with --compare_phase2_csv. Phase 4/scripts/run_all.ps1 runs the whole production pipeline end to end.
  • Tune it. Phase 2 sweep and detector parameters live in Phase 2/config.py. The Phase 3 ensemble settings (confirmation N, layer membership, voting mode) live in Phase 3/config.py and inherit everything else from Phase 2.
  • Read the outputs. Per-trial CSVs land under each phase's results/csv/. Phase 4 writes results/selection.json (the recommendation), the measured C cost CSV, and the report in report/Phase4_Report.md.
  • Explore the dashboards. Phase 2's "Signal Lab" (KPIs, performance-matrix heatmap, window-sensitivity lines) and Phase 3's "Ensemble Command" (architecture diagram, confirmation-gate effect, leaderboard) build to a self-contained HTML file you open in a browser. Phase 4's dashboard shows accuracy-vs-window, Pareto and cost-budget views.
  • Deploy the C twin. Phase 4/src/c/tsad.c + tsad.h is the artifact meant to run on-device: tsad_init, tsad_update(x) -> score, tsad_state_bytes.

Project Structure

Network-Telemetry/
├── Phase 1/                          # Algorithm study (theory) + evaluation criteria spec
│   ├── Algorithm_Study_Document.md   # 15 candidates analysed, 6 finalists, 9 rejected
│   ├── HPE_Evaluation_Criteria_Specification.md
│   └── *.pdf                         # my notes on MAD, Kalman, Page-Hinkley, SAX, Z-Score, DDM
│
├── Phase 2/                          # Single-detector benchmark on real CESNET traffic
│   ├── src/pipeline/                 # CESNET loader + O(1) Welford sliding-window buffer
│   ├── src/injector/                 # Labelled burst / rate-shift / drift / transient injection
│   ├── src/detectors/                # base.py contract + zscore, mad, ewma, cusum, page_hinkley, …
│   ├── src/evaluation/               # 2,880-trial harness, metrics, plots
│   ├── dashboard/web/                # React + Vite + ECharts "Signal Lab"
│   ├── tests/                        # pipeline / injector / detector tests
│   ├── docs/                         # phase documentation and findings
│   ├── main.py · config.py · requirements.txt
│
├── Phase 3/                          # Two-layer confirmation-gated ensemble (builds on P2)
│   ├── _phase2_bridge.py             # Re-exports Phase 2 so results stay comparable
│   ├── ensemble/                     # confirmation_gate, voting_layer, two_layer_ensemble
│   ├── evaluation/                   # 6,720-trial harness + gate-FP-reduction metrics
│   ├── dashboard/web/                # React + Vite + ECharts "Ensemble Command"
│   ├── docs/                         # phase documentation
│   └── tests/                        # gate / voting / ensemble + parametrised base-contract
│
├── Phase 4/                          # Production build: quality + low cost, with a C twin
│   ├── src/python/tsad/              # Detector library (core contract, detectors, ensembles, registry)
│   ├── src/python/eval/              # sweep_runner, metrics_intel, profile_cost, figures (numpy + stdlib)
│   ├── src/python/selection/         # scorecard, pareto, mapping, select -> selection.json
│   ├── src/python/datasets/          # synthetic generators, injectors, NAB loaders
│   ├── src/python/cli/stream_demo.py # Stream a signal through any detector -> live alerts
│   ├── src/c/                        # tsad.c/.h + parity.c + bench.c + build.ps1 (the on-device twin)
│   ├── dashboard/                    # React + Vite + ECharts (accuracy-vs-window, Pareto, cost/budget)
│   ├── results/                      # selection.json, metrics.json, measured C cost
│   ├── report/Phase4_Report.md       # full results and reasoning, with figures
│   ├── docs/                         # detector contract, architecture and results
│   ├── tests/                        # contract, budget gate, C<->Python parity
│   └── scripts/run_all.ps1           # One-shot end-to-end pipeline
│
├── Final Pipeline/                   # The 96-byte unified detector as a standalone package
│   ├── python/ · c/                  # Python and C99 versions, parity check
│   ├── dashboard/                    # React dashboard with the detector running live in the browser
│   ├── server.py · run_all.ps1
│   └── README.md
│
└── README.md

Contact

Portfolio: DenistanLinkedIn: denistanb
GitHub: DCode-v05LeetCode: Denistan_B
Email: denistanb05@gmail.com

Made with ❤️ by Denistan B

About

Four-phase study of lightweight streaming anomaly detectors for switch telemetry under a 100 us / 100 byte budget, with parity-verified C implementations and a 96-byte unified detector

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages