driftwatch
Data & model drift detection for Rust — population stability index (PSI), KL/JS divergence, distributional hypothesis tests (Kolmogorov–Smirnov, chi-square), live windowing for served models, and a pluggable alerting hook.
driftwatch fills a gap in the Rust ML ecosystem: there was no equivalent of
Python's Evidently or
WhyLabs for computing how far a live data or prediction
distribution has moved from a trusted reference (training/baseline)
distribution. This crate computes drift; it deliberately does not try to
replace the operational observability tooling (tracing, metrics,
Prometheus) that already does its job well — it plugs into it.
use ;
// Fit a reference distribution per feature from baseline data.
let baseline: = .map.collect;
let reference =
fit_continuous
.unwrap;
let mut monitor = new;
monitor.add_feature;
// Check a live batch that has shifted upward.
let live: = .map.collect;
let report = monitor.check.unwrap;
println!;
assert!;
What's covered
| Capability | driftwatch | Evidently / WhyLabs |
|---|---|---|
| PSI (population stability index) | ✅ | ✅ |
| KL / JS divergence | ✅ | ✅ |
| KS test / chi-square test (with p-values) | ✅ | ✅ |
| Per-feature + aggregate dataset-drift report | ✅ | ✅ |
| Prediction drift preset | ✅ | ✅ |
| Label / concept drift (rolling score) | ✅ | ✅ |
| Live windowing for served models | ✅ | ✅ |
| Pluggable alerting (log / webhook / custom) | ✅ | ✅ |
Prometheus / metrics export |
✅ | partial |
| Live dashboard UI (self-refreshing web page) | ✅ (dashboard feature) |
✅ (signature feature) |
| Static exportable HTML report file | ✅ | ✅ |
| Data-quality profiling (missing / schema) | ✅ (drift-focused) | ✅ |
| Streaming / online divergence estimation | ✅ (streaming feature, approximate) |
partial |
Still out of scope
These are named plainly rather than left for you to discover by surprise:
- No hosted, multi-user dashboard service. The
dashboardfeature is a single-process live UI you serve yourself — not a persistent, authenticated, multi-tenant monitoring platform. - No exact online two-sample tests. Streaming drift reconstructs the live distribution from a quantile sketch, so online PSI/KL/JS are approximate (they converge to the batch values within sketch accuracy). Exact KS / chi-square remain windowed, batch operations.
- Data-quality profiling is drift-adjacent, not a full data-validation suite. It covers missing-value rates, basic per-feature stats, and schema conformance (range / category / kind) — not arbitrary business-rule validation.
Metrics at a glance
- PSI — the credit-risk workhorse. Threshold convention (not a
mathematical fact):
< 0.1no significant change,0.1–0.25moderate,> 0.25significant. - KL divergence — directional (
D_KL(live ‖ reference)by default; it is asymmetric, so the direction is documented). - JS divergence — symmetric and bounded to
[0, ln 2]; the one to reach for when comparing a single bounded score across features. - KS test — two-sample, on raw samples, so it is insensitive to the binning-strategy choice the binned metrics depend on.
- Chi-square test — a homogeneity test for categorical features, adding a significance judgment to PSI's magnitude-only signal.
Both binning strategies are provided; equal-frequency (quantile) binning is
the default convention for PSI and what you should usually reach for.
Zero-frequency bins (including a novel category that appears only in live data
— itself a strong drift signal) are handled by epsilon smoothing so the
divergence metrics never return inf/NaN.
Feature flags
All optional integrations are off by default, keeping the core crate dependency-light.
| Feature | Pulls in | Adds |
|---|---|---|
alerting-log |
tracing |
LogAlerter — structured tracing event |
alerting-webhook |
reqwest, serde |
WebhookAlerter — POST JSON to any URL |
prometheus-export |
metrics |
drift-score gauges on your existing /metrics |
label-drift |
model-selection-rs |
LabelDriftMonitor over its Scorer trait |
dashboard |
axum, tokio, serde |
Dashboard — a live self-refreshing web UI + JSON API |
streaming |
sketches-ddsketch |
OnlineDistribution, StreamingMonitor, PageHinkleyDetector |
Data-quality profiling (DatasetProfile, Schema) and the static HtmlReport
are part of the core crate — no feature flag, no extra dependencies.
Custom alerting to any other destination (Slack, PagerDuty, a database) is a
matter of implementing the one-method Alerter trait yourself.
Live dashboard
Enable the dashboard feature for a self-contained, auto-refreshing web UI that
shows the latest per-feature verdicts, scores, and a dataset-drift banner. You
own the cadence — keep calling check and hand each report to the dashboard:
use Dashboard;
let dashboard = with_title;
// From your serving loop, after each DatasetMonitor::check:
// dashboard.update(report);
// Serve it standalone…
dashboard.serve.await?;
// …or mount it into an existing axum app:
// Router::new().nest("/drift", dashboard.router())
The page is a single HTML document with inlined CSS/JS and no external assets; it
polls /api/report (JSON) every two seconds. The dashboard renders drift — it
does not compute or schedule it, and it runs no hosted/multi-user infrastructure.
Static HTML report
For a "save this run" artifact — attach it to a CI job, email it — render a
DriftReport to a standalone HTML file with inline-SVG reference-vs-live
histograms. Dependency-free (no plotting stack), no external assets:
use HtmlReport;
new.with_title.save?;
Data-quality profiling
Drift asks "has the distribution moved?"; profiling asks "is this batch even
well-formed?". Derive a Schema from your reference distributions and validate
each live batch — missing/unexpected features, kind mismatches, out-of-range
values, novel categories, and null-rate breaches:
use ;
let schema = from_references;
let report = schema.validate;
assert!;
let mut profile = new;
profile.profile_continuous; // count, missing rate, min/max/mean/std
Non-finite values are counted as missing rather than erroring.
Streaming / online drift
Enable streaming to absorb values one at a time into a quantile sketch and
query drift at any instant — no window to buffer or re-bin — plus a Page-Hinkley
change-point detector for scalar signals:
use ;
let mut monitor = new;
monitor.add_feature?; // PSI threshold
monitor.update?; // per request, O(1)-ish, bounded memory
let report = monitor.report?; // online PSI right now
let mut ph = new;
if let Some = ph.update
Online PSI/KL/JS are approximate (reconstructed from the sketch); they converge to the batch values within sketch accuracy.
Examples
basic_drift_check— reference vs. drifted live batch, printed report,LogAlerterfiring.axum_integration— aLiveWindowfed by a request handler, atokio::time::intervalloop driving periodic checks, drift gauges on/metrics.label_drift— a simulated degrading-accuracy scenario using thelabel-driftfeature.live_dashboard— a simulated serving system whose data drifts over time, served on a live dashboard at127.0.0.1:8080(--features dashboard).html_report— render a standalonereport.htmlwith reference-vs-live distribution charts.data_quality— profile a batch and validate it against a schema derived from the reference data.streaming_drift— online PSI and a Page-Hinkley change-point detector over a drifting stream (--features streaming).
MSRV & license
MSRV 1.75. Licensed under the MIT license.