Observability stack

April 28, 2026 · View on GitHub

observability_generate_bundle is the project's headline operator tool. A single MCP call (or single CLI subcommand, when the CLI catches up) takes a scenario + parameters and returns a deployment- ready bundle:

  • The OpenSIPS cfg with the prometheus module injected.
  • Per-category Grafana dashboard JSON files (overview, dialogs, dispatcher, rtpengine, TLS/security, IMS — only the ones that match the modules your scenario actually loads).
  • A Prometheus scrape_configs YAML stanza pointing at every OpenSIPS instance you list.
  • Grafana provisioning files (datasource + dashboard provider).
  • A docker-compose.monitoring.yml for a side-car Prometheus + Grafana stack.
  • A README explaining how to deploy what you just received.

The point is to make "I deployed an OpenSIPS scenario" and "I can see what it's doing" the same operation — not two separate weeks of work.


Why this exists

The pre-bundle workflow was:

  1. Generate the cfg (cfg_generate).
  2. Manually add loadmodule "prometheus.so" + loadmodule "httpd.so" to that cfg.
  3. Stand up Prometheus separately. Configure scrape job by hand.
  4. Stand up Grafana separately. Create datasource by hand.
  5. Hand-pick dashboards from docs/learn/grafana/. Import each.
  6. Realise the dashboards reference modules your scenario doesn't load (empty panels everywhere).
  7. Spend a Tuesday afternoon trimming them.

The bundle generator collapses that to one call, and the dashboard selection is driven by the cfg's loaded modules — so a config that loads dispatcher.so + rtpengine.so gets the dispatcher and media dashboards but not the IMS one. Empty panels go away.


The fast path

observability_generate_bundle(
    scenario="residential_pbx",
    params={
        "db_url": "mysql://opensips:CHANGE_ME@db:3306/opensips",
        "domain": "pbx.example.com"
    },
    targets=["10.0.0.10:8888", "10.0.0.11:8888"]
)

Returns a dict. Save it to disk:

observability_save_bundle(
    bundle=<the dict>,
    out_dir="/tmp/opensips-monitoring-build"
)

You now have a directory containing:

/tmp/opensips-monitoring-build/
├── README.md                  ← how to deploy what's in this directory
├── opensips.cfg               ← scenario cfg with prometheus injected
├── docker-compose.monitoring.yml
├── prometheus/
│   └── prometheus.yml         ← scrape config + global settings
├── grafana/
│   ├── provisioning/
│   │   ├── datasources/prometheus.yml
│   │   └── dashboards/opensips.yml
│   └── dashboards/
│       ├── 01-overview.json
│       ├── 02-dialogs-and-calls.json
│       ├── 03-routing-and-dispatch.json
│       └── ...               ← only the categories your scenario triggered
cd /tmp/opensips-monitoring-build
docker compose -f docker-compose.monitoring.yml up -d
# Grafana on http://localhost:3000 (admin / admin by default — change it)
# Prometheus on http://localhost:9090

The OpenSIPS instances at the targets you specified become scrape sources. As soon as their prometheus module is up on :8888/metrics, panels populate.


When to reach for which tool

You want...Use
Everything (cfg + dashboards + Prometheus + Grafana + compose), in one callobservability_generate_bundle
Same, but starting from a cfg you already hand-editedobservability_generate_bundle_from_cfg
Just the dashboards (Grafana already running)observability_generate_dashboards
Just the Prometheus scrape stanzaobservability_generate_prometheus_scrape
Just the Grafana provisioning filesobservability_generate_grafana_provisioning
Just the side-car monitoring docker-composeobservability_generate_monitoring_compose
Add prometheus + httpd to an existing cfgobservability_inject_prometheus
Preview which dashboards would fire for a module setobservability_categories_for_modules
List every available panel categoryobservability_list_panel_categories
Persist a bundle dict to diskobservability_save_bundle

Why the dashboard set adapts to the scenario

Each panel category is bound to a set of OpenSIPS modules in src/opensips_mcp/observability/panel_library.py. The dashboard builder reads loadmodule directives from the cfg under construction and picks every category whose triggers match.

Examples:

  • class4_sbc loads dispatcher.so + dialog.so + tm.so + sl.so → triggers overview, dialogs, dispatcher, security. No IMS, no RTPEngine, no TLS dashboards.
  • webrtc_gateway loads tls_mgm.so + rtpengine.so + dialog.so → triggers overview, dialogs, rtpengine, tls/security.
  • ims_cx_scscf loads aaa_diameter.so + dialog.so + ... → triggers overview, dialogs, ims.

The result: dashboards you import always have data. Empty panels are eliminated by construction, not by post-hoc cleanup.


Coexistence with the existing static dashboards

The five hand-curated JSON files under docs/learn/grafana/ predate the generator. They are still valid: import any of them directly into Grafana if you want the canonical panel set without going through the bundle.

The generator's output is a superset — same panel taxonomy, but filtered to your scenario, plus the surrounding deployment glue. Use the static files as reference; use the generator for actual deployments.


Production refinements

The generated bundle is lab-grade. Before using it in production:

  1. Change Grafana's admin password. The default docker-compose sets admin/admin; rotate immediately.
  2. Replace SQLite with a real Grafana DB if you want HA on the monitoring stack itself.
  3. Add TLS to Prometheus and Grafana. The generator emits HTTP; wrap with nginx / Caddy / your platform's TLS termination.
  4. Tighten Prometheus retention. Default 15-day retention is fine for evaluation; for production tune --storage.tsdb.retention.time.
  5. Wire alerts. The bundle does not include alert rules. Pair with kube-prometheus-stack's alerts or the rules at docs/learn/grafana/ (when those land — currently only dashboards ship in static form).
  6. Audit access to /metrics. OpenSIPS' Prometheus exporter exposes everything; either bind it to localhost only and put a reverse proxy in front, or restrict via Kubernetes NetworkPolicy.

Limitations

  • The generator does NOT add alert rules. Ship those separately via prometheus.yml rule_files.
  • The generator's docker-compose is for the monitoring stack only — it does not orchestrate OpenSIPS itself. Pair with the project's Helm chart at deploy/helm/opensips-mcp/ or your existing Docker / systemd setup.
  • The Grafana dashboards reference standard OpenSIPS Prometheus metric names. If you've added custom statistics via the xstats module or via script_route, those won't appear unless you extend the panel library.
  • The bundle is idempotent in name but not idempotent on disk — re-running observability_save_bundle will overwrite files in the target directory. If you've hand-tuned the generated Grafana dashboards, save your changes elsewhere before re-running.

Where to go next

  • post-deploy-smoke-test.md — run after deploying the bundle to confirm the metrics endpoint is alive and Grafana can scrape it.
  • grafana/ — the static dashboard reference set.
  • ha-failover-playbook.md — once observability is in place, set up alerts on dispatcher_inactive_destinations, clusterer_state_changed, dialog:active_dialogs deltas to catch failover events.