promforecast is an open-source
forecasting service for Prometheus-compatible metrics. It reads a YAML
config, queries a long-term TSDB (VictoriaMetrics by default), runs
forecasting models, and exposes the predictions on /metrics
— so a forecast becomes a first-class metric: graphable in Grafana,
alertable through Alertmanager, indistinguishable from anything else in
your monitoring stack.
The idea is simple: you already know what your metrics did. promforecast tells you what they’re about to do, in the same language your stack already speaks.
What it is
A small Python service that runs as a normal Kubernetes
Deployment next to your existing Prometheus. It never
touches production Prometheus — all reads go through the long-term TSDB
(VictoriaMetrics, Mimir, Thanos all work). An internal scheduler refits
the models on an interval; between refits, /metrics serves
the most recent forecast snapshot.
It’s stateless. There’s no database to operate.
Models are refit on a schedule and the results live in the scraped
/metrics endpoint, so a restart just means the next run
reproduces everything.
It’s config-driven. Adding a metric to forecast is a YAML change, not a code change — point a PromQL query at the thing you care about and the forecast shows up as a metric.
What it can do
- Forecast any PromQL query. Whatever you can express in PromQL — CPU saturation, disk fill, request rate, queue lag, business KPIs — can be forecast. PromQL in, Prometheus exposition format out.
- Multiple models, automatically chosen. Ships with
AutoARIMA, AutoETS, SeasonalNaive, MSTL and Theta (from Nixtla’s
statsforecast). You can emit every model side by side, or let it cross-validate and pick the lowest-error model per series — and fall back to a safe baseline when even the winner fits the data poorly. - Confidence bands. Forecasts come with upper/lower bounds at the confidence levels you choose (e.g. 80% and 95%).
- Free anomaly signal. It can emit how far the current actual value sits from the forecast band. A good forecast plus a large deviation is a baseline-aware anomaly alert — no separate anomaly system required.
- A quality score per forecast. Each series gets a 0.0–1.0 reliability score combining backtest error, band width and data coverage, so you can hide or gate the forecasts you shouldn’t trust.
- Accuracy you can alert on. Backtest error (MAPE/MASE) is emitted as metrics across multiple horizons, so “this model has degraded” is just another alert.
Built to behave in a real monitoring stack
- Forecasts are just metrics. Same labels, same retention path, same dashboards. You compose with the recording rules and alerts you already know, rather than learning a new system.
- Cardinality guardrails. Hard limits on series-per-query and total series, with configurable overflow behaviour (drop lowest priority, reject, or sample) — forecasting is a cardinality multiplier and the safety controls are first-class.
- Doesn’t reinvent Prometheus. It emits raw forecasts; alerting goes to Alertmanager, visualisation to Grafana, derived metrics to recording rules.
- Drop-in install. A Helm chart for the forecaster, and an umbrella chart that bundles VictoriaMetrics, dashboards and example alerts for a one-command start.
A few examples
Install it next to an existing Prometheus with Helm:
helm repo add promforecast https://esops-dev.github.io/promforecast
helm install promforecast promforecast/promforecast-stackForecasting a metric is a few lines of YAML. Point a query at node CPU and ask for a 24-hour forecast:
datasource:
url: http://victoriametrics:8428/
defaults:
lookback: 14d
step: 5m
horizon: 24h
models: [AutoARIMA, SeasonalNaive]
confidence_levels: [80, 95]
groups:
- name: node_capacity
queries:
- id: node_cpu_busy_pct
promql: |
100 - (avg by (instance)
(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)That id becomes the metric name. The forecaster now
exposes, on /metrics:
node_cpu_busy_pct_forecast{instance="web-1", group="node_capacity", model="AutoARIMA", horizon="24h"} 72.4
node_cpu_busy_pct_forecast_lower{instance="web-1", group="node_capacity", model="AutoARIMA", level="95"} 61.0
node_cpu_busy_pct_forecast_upper{instance="web-1", group="node_capacity", model="AutoARIMA", level="95"} 83.8
Because it’s just a metric, “will this disk fill within a day?” is an ordinary PromQL alert against the forecast:
- alert: ForecastDiskFillsSoon
expr: node_filesystem_avail_bytes_forecast{horizon="24h"} < 5368709120 # 5 GiB
for: 15m
labels:
severity: warningLet the forecaster pick the best model per series and fall back to a safe baseline when the fit is poor:
defaults:
models: [AutoARIMA, AutoETS, SeasonalNaive]
accuracy:
evaluate: true
auto_select: true # cross-validate, emit only the winner
fallback_model: SeasonalNaive # used when the winner still fits badly
fallback_mape_threshold: 50 # percentTurn on the deviation and quality signals, then alert only on deviations from forecasts you actually trust:
defaults:
emission:
deviation: true
quality: true- alert: ForecastDeviation
# actual is outside its predicted band, and the forecast is reliable enough
expr: |
forecast_deviation_outside_band == 1
and on (id, group) forecast_quality_score > 0.6
for: 15mOperational metrics
Beyond the forecasts themselves, it exposes the metrics you’d want to run it in production — last run timestamp, run duration, series counts, drops, failures, fit duration, and the accuracy/quality scores — so the forecaster monitors itself the same way everything else in your stack does:
forecast_accuracy_mape{id, group, model, horizon}
forecast_quality_score{id, group, model}
forecast_last_run_timestamp_seconds{group}
forecast_series_dropped_total{group, reason}
forecast_failures_total{group, reason}
In short
If you run Prometheus and keep eyeballing graphs to guess when something will saturate, promforecast turns that guess into a metric. Write a PromQL query, get a forecast with confidence bands, a quality score and a deviation signal — all in plain Prometheus exposition format, ready for the dashboards and alerts you already have.