A Prometheus exporter for the ARRIS CM3500B CE (EuroDOCSIS 3.0 / DOCSIS 3.1) cable modem. Scrapes the modem's web interface and exposes metrics in Prometheus format. Includes a Grafana dashboard and Prometheus alerting rules.
Licensed under AGPL-3.0-or-later.
nix develop --command bash -c "cargo build --release"
./target/release/cm3500-b-ce-exporter --password YOUR_PASSWORDMODEM_PASSWORD=YOUR_PASSWORD docker compose up -dThis starts the exporter, Prometheus, and Grafana with the dashboard pre-loaded:
- Exporter: http://localhost:10044/metrics
- Prometheus: http://localhost:9090
- Grafana: http://localhost:3000 (admin/admin)
cm3500-b-ce-exporter --password <PASSWORD> [OPTIONS]
Options:
--modem-url <URL> Modem base URL [default: https://192.168.100.1]
--username <USER> Login username [default: admin]
--password <PASS> Login password (required)
--listen <ADDR> Listen address [default: 0.0.0.0:10044]
--disable-prometheus Disable the Prometheus /metrics HTTP endpoint entirely
--interval <SECONDS> Scrape interval [default: 30]
--state-file <PATH> Write connection state changes to this JSON file
--capacity-file <PATH> Write configured upstream/downstream capacity to this JSON file
--state-down-threshold <N> Consecutive bad scrapes/states before reporting down [default: 3]
--state-up-threshold <N> Consecutive good scrapes before reporting recovered [default: 2]
--capacity-margin-percent <PERCENT>
Percent of configured service flow bandwidth to expose as shaped capacity [default: 95]
--enable-spectrum Actively trigger a modem spectrum scan and export spectrum metrics
--spectrum-interval <SECONDS>
Interval between active spectrum scans [default: 900]
--otlp-endpoint <URL> OTLP HTTP base URL or /v1/metrics endpoint to push metrics and logs to
--otlp-header <KEY=VALUE> OTLP header, can be repeated (e.g. Authorization=Basic...)Push metrics and modem event logs directly to Grafana Cloud without running Prometheus:
cm3500-b-ce-exporter \
--password YOUR_PASSWORD \
--otlp-endpoint https://otlp-gateway-eu-west-0.grafana.net/otlp \
--otlp-header "Authorization=Basic $(echo -n 'INSTANCE_ID:API_KEY' | base64)"Also works with any OpenTelemetry Collector:
cm3500-b-ce-exporter \
--password YOUR_PASSWORD \
--otlp-endpoint http://localhost:4318/v1/metricsTo run in OTLP-only mode without binding any HTTP port:
cm3500-b-ce-exporter \
--password YOUR_PASSWORD \
--otlp-endpoint http://localhost:4318/v1/metrics \
--disable-prometheusBoth Prometheus /metrics and OTLP push can run simultaneously. If --otlp-endpoint points at an OTLP base URL like https://.../otlp, the exporter sends metrics to /v1/metrics and logs to /v1/logs under that base. When --disable-prometheus is set, the exporter does not bind any HTTP port.
The exporter can optionally write machine-readable JSON files for router/firewall automation.
Example:
{
"status": "up",
"timestamp": "1748850000",
"modem_up": true,
"cm_status": "OPERATIONAL",
"dhcp_state": "bound",
"degraded": false,
"reason": null
}status is hysteresis-aware:
up- healthydegraded- transitional or partially unhealthydown- enough consecutive bad scrapes/states to declare failure
Example:
{
"timestamp": "1748850000",
"upstream_bps": 128000000,
"downstream_bps": 1126400000,
"shaped_upstream_bps": 121600000,
"shaped_downstream_bps": 1070080000,
"source": "service_flow_config",
"valid": true
}Files are written atomically and only rewritten when their semantic content changes, making them suitable for systemd.path or other file-watch based automation.
Example systemd integration files are provided in examples/systemd/.
When --enable-spectrum is set, the exporter performs an active modem spectrum scan and exposes the results as Prometheus and OTLP metrics. Spectrum scans use their own interval via --spectrum-interval and the most recent successful scan result is cached between normal scrape cycles.
The exporter scrapes the modem on a background interval (default: 30s) and serves the last successful result from /metrics. This avoids doing a full modem login and multi-page fetch on every Prometheus scrape and keeps scrape latency predictable despite the modem's cookie-based session handling.
| Metric | Type | Description |
|---|---|---|
cm3500_up |
gauge | Whether the modem scrape was successful |
cm3500_scrape_duration_seconds |
gauge | Duration of the last scrape |
cm3500_info |
gauge | Modem hardware/firmware info (labels: model, serial, firmware, etc.) |
cm3500_uptime_seconds |
gauge | System uptime in seconds |
cm3500_cm_status |
gauge | Operational status label |
cm3500_interface_up |
gauge | Interface operational status (1 = Up) |
cm3500_cpe_static |
gauge | Number of static CPE devices detected |
cm3500_cpe_dynamic |
gauge | Number of dynamic CPE devices detected |
| Metric | Type | Description |
|---|---|---|
cm3500_dhcp_state |
gauge | DHCP state (bound, etc.) |
cm3500_dhcp_lease_total_seconds |
gauge | DHCP lease duration |
cm3500_dhcp_lease_remaining_seconds |
gauge | DHCP lease time remaining |
cm3500_dhcp_rebind_remaining_seconds |
gauge | DHCP rebind time remaining |
cm3500_dhcp_renew_remaining_seconds |
gauge | DHCP renew time remaining |
| Metric | Type | Labels | Description |
|---|---|---|---|
cm3500_downstream_qam_power_dbmv |
gauge | channel, dcid, frequency_mhz, modulation | Power level in dBmV |
cm3500_downstream_qam_snr_db |
gauge | channel, dcid, frequency_mhz, modulation | Signal-to-noise ratio in dB |
cm3500_downstream_qam_octets_total |
counter | channel, dcid | Total octets received |
cm3500_downstream_qam_corrected_total |
counter | channel, dcid | Corrected FEC errors |
cm3500_downstream_qam_uncorrectable_total |
counter | channel, dcid | Uncorrectable FEC errors |
| Metric | Type | Labels | Description |
|---|---|---|---|
cm3500_downstream_ofdm_channel_width_mhz |
gauge | channel, fft_type | Channel width in MHz |
cm3500_downstream_ofdm_active_subcarriers |
gauge | channel | Number of active subcarriers |
cm3500_downstream_ofdm_rxmer_db |
gauge | channel, measurement (pilot/plc/data) | Receive MER in dB |
| Metric | Type | Labels | Description |
|---|---|---|---|
cm3500_upstream_qam_power_dbmv |
gauge | channel, ucid, frequency_mhz, modulation, channel_type | Transmit power in dBmV |
cm3500_upstream_qam_symbol_rate_ksym |
gauge | channel, ucid | Symbol rate in kSym/s |
| Metric | Type | Labels | Description |
|---|---|---|---|
cm3500_upstream_ofdma_tx_power_dbmv |
gauge | channel, fft_type | Transmit power in dBmV |
cm3500_upstream_ofdma_channel_width_mhz |
gauge | channel | Channel width in MHz |
cm3500_upstream_ofdma_active_subcarriers |
gauge | channel | Number of active subcarriers |
cm3500_upstream_ofdma_active_subcarrier_range_mhz |
gauge | channel, edge | First/last active subcarrier positions |
| Metric | Type | Description |
|---|---|---|
cm3500_qos_packets_total |
counter | Total packets per QoS service flow (labels: sfid, direction, primary, service_class) |
cm3500_docsis_state |
gauge | Overall DOCSIS registration state |
cm3500_docsis_phase |
gauge | Per-phase registration status (labels: phase, status) |
cm3500_bpi_state |
gauge | Baseline Privacy Interface state |
cm3500_tod_state |
gauge | Time of Day acquisition state |
| Metric | Type | Description |
|---|---|---|
cm3500_event_log_entries |
gauge | Entry count in the event log by event ID |
cm3500_event_t3_timeouts |
gauge | T3 timeouts (No Ranging Response) in event log |
cm3500_event_t4_timeouts |
gauge | T4 timeouts in event log |
cm3500_event_dhcp_failures |
gauge | DHCP failures in event log |
cm3500_event_dhcp_renew_warnings |
gauge | DHCP renew warnings in event log |
cm3500_event_profile_changes |
gauge | Upstream profile assignment changes |
cm3500_event_dhcp_renew_no_response |
gauge | DHCP renew sent with no response events |
cm3500_event_upstream_active_profile |
gauge | Last seen upstream profile ID from event log |
cm3500_event_ofdma_profile_id |
gauge | Last seen OFDM/OFDMA profile ID from event log |
| Metric | Type | Description |
|---|---|---|
cm3500_product_ethernet_phy_info |
gauge | Ethernet PHY capability label from the modem |
cm3500_product_logging_components_enabled |
gauge | Enabled internal logging components by debug group |
cm3500_spectrum_chunks |
gauge | Number of spectrum chunks returned by a scan |
cm3500_spectrum_chunk_power_dbmv |
gauge | Per-chunk min/avg/max power summary |
cm3500_spectrum_chunk_metadata |
gauge | Per-chunk span/bin spacing/RBW metadata |
cm3500_spectrum_bin_power_dbmv |
gauge | Per-bin spectrum power from the modem scan |
| Metric | Type | Labels | Description |
|---|---|---|---|
cm3500_qos_max_traffic_rate_kbps |
gauge | direction, flow_index | Maximum traffic rate from config |
cm3500_qos_min_reserved_rate_kbps |
gauge | direction, flow_index | Minimum reserved rate from config |
cm3500_qos_max_traffic_burst |
gauge | direction, flow_index | Maximum traffic burst from config |
cm3500_qos_traffic_priority |
gauge | direction, flow_index | Traffic priority from config |
| Event ID | Description |
|---|---|
82000200 |
T3 timeout — No Ranging Response received |
82000300 |
T4 timeout |
68001202 |
DHCP failed — Solicit sent, no Advertise received |
68010100 |
DHCP RENEW sent — No response for IPv4 |
68010300 |
DHCP RENEW WARNING — Field invalid in response |
68010700 |
Primary lease failed, IPv4 fallback initiated |
67061601 |
US profile assignment change |
74010100 |
CM-STATUS message (OFDM/OFDMA profile change) |
75012500 |
US Diplexer Mode Initialization |
75012600 |
DS Diplexer Mode Initialization |
69010200 |
Software download initiated |
69011200 |
Software download successful |
90000000 |
MIMO event |
Two sets of alert rules and dashboards are provided:
| Set | Dashboard | Alert rules | Use when |
|---|---|---|---|
| Prometheus | grafana/dashboard.json |
alert_rules_common.yml + plant-specific |
Scraping /metrics directly with Prometheus |
| OTLP | grafana/dashboard_otlp.json |
alert_rules_otlp.yml |
Pushing via OTLP to an OTel Collector → Prometheus backend |
Always load alert_rules_common.yml, then choose one plant-specific file:
alert_rules_eurodocsis.yml— Vodafone DE, Liberty Global, Virgin Media O2, etc.alert_rules_docsis_us.yml— Comcast, Spectrum, Cox, etc.
Apply to all deployments regardless of plant type:
| Alert | Severity | Condition |
|---|---|---|
CM3500ModemDown |
critical | Scrape failing for 2m |
CM3500ScrapeSlow |
warning | Scrape taking >5s for 5m |
CM3500Rebooted |
info | Uptime decreased |
CM3500NotOperational |
critical | Status != OPERATIONAL |
CM3500InterfaceDown |
critical | Interface not Up for 2m |
CM3500DownstreamSNRLow |
warning | SNR < 33 dB for 5m |
CM3500DownstreamSNRCritical |
critical | SNR < 23 dB (64QAM) or < 30 dB (256QAM) |
CM3500UncorrectableErrors |
warning | Uncorrectable FEC errors increasing |
CM3500T3Timeouts |
warning | T3 timeouts increasing over 10m |
CM3500T4Timeouts |
warning | T4 timeouts increasing over 10m |
CM3500DHCPFailures |
warning | DHCP failures increasing over 10m |
CM3500DHCPRenewWarnings |
warning | DHCP renew warnings > 5 in 10m |
CM3500ProfileChanges |
info | US profile changes increasing over 10m |
CM3500DHCPLeaseExpiring |
warning | Lease remaining < 60s for 2m |
CM3500OFDMRxMERLow |
warning | OFDM data RxMER < 31 dB |
CM3500OFDMRxMERCritical |
critical | OFDM data RxMER < 25 dB |
CM3500DHCPRenewNoResponse |
warning | DHCP renew no-response events increasing |
CM3500UpstreamProfileDowngraded |
warning | Upstream profile ID decreased over 30m |
CM3500UpstreamPowerHigh |
warning | Upstream OFDMA power > 49 dBmV |
CM3500UpstreamPowerDeltaHigh |
warning | Upstream SC-QAM tilt > 6 dBmV |
For European cable plants. Key thresholds:
- Downstream low power: -5 dBmV (European outlet spec 56–73 dBuV; plant tilt makes lower channels vulnerable)
- Upstream warning: 49 dBmV (Vodafone DE plants degrade past 50 dBmV on bonded channels)
- Upstream critical: 53 dBmV (EuroDOCSIS bonded-channel maximum)
| Alert | Severity | Condition |
|---|---|---|
CM3500EuroDownstreamPowerLow |
warning | DS power < -5 dBmV for 5m |
CM3500EuroDownstreamPowerHigh |
warning | DS power > +12 dBmV for 5m |
CM3500EuroUpstreamPowerHigh |
warning | US SC-QAM power > 49 dBmV for 5m |
CM3500EuroUpstreamPowerCritical |
critical | US SC-QAM power > 53 dBmV for 5m |
CM3500EuroUpstreamOFDMAHigh |
warning | US OFDMA power > 49 dBmV for 5m |
CM3500EuroUpstreamOFDMACritical |
critical | US OFDMA power > 53 dBmV for 5m |
For North American cable plants. Key thresholds:
- Downstream low power: -7 dBmV (standard ISP operating window -7 to +7 dBmV)
- Upstream warning: 51 dBmV (bonded 4ch ATDMA/64QAM maximum)
- Upstream critical: 55 dBmV (well above bonded-channel limit)
| Alert | Severity | Condition |
|---|---|---|
CM3500USDownstreamPowerLow |
warning | DS power < -7 dBmV for 5m |
CM3500USDownstreamPowerHigh |
warning | DS power > +12 dBmV for 5m |
CM3500USUpstreamPowerHigh |
warning | US SC-QAM power > 51 dBmV for 5m |
CM3500USUpstreamPowerCritical |
critical | US SC-QAM power > 55 dBmV for 5m |
CM3500USUpstreamOFDMAHigh |
warning | US OFDMA power > 51 dBmV for 5m |
CM3500USUpstreamOFDMACritical |
critical | US OFDMA power > 55 dBmV for 5m |
Sections:
- Overview — Status, uptime, DHCP lease gauge, BPI state, CPE count, Ethernet PHY, scrape duration, modem info
- Downstream SC-QAM — Power timeseries with DOCSIS thresholds, SNR timeseries, corrected/uncorrectable error bars, power tilt bar chart
- Downstream OFDM — RxMER timeseries (pilot/plc/data), channel details table
- Upstream — Power timeseries (QAM + OFDMA), power delta/tilt gauge, active profile, OFDMA profile ID, symbol rate
- RF Spectrum — Full-band spectrum analyzer, chunk power summary, product logging components
- DHCP & Lease — Lease/renew/rebind timers, DHCP renew no-response, QoS shaper gauge, service flow table
- Event Log — Critical event stats, event bar chart by ID, event trends over time
- DOCSIS State — Registration phases, QoS service flows
- Collector Health — Scrape duration timeline
Same panels and sections as the Prometheus dashboard, but uses OTLP metric names (e.g. hw_cable_modem_downstream_power instead of cm3500_downstream_qam_power_dbmv) and OTLP attribute labels (e.g. cable_channel_id instead of channel, cable_channel_kind for SC-QAM vs OFDMA).
Use this dashboard when the exporter pushes metrics via OTLP to an OTel Collector that writes to a Prometheus-compatible backend (Grafana Mimir, Thanos, Cortex).
A single combined file with all three groups (common + EuroDOCSIS + US DOCSIS) using OTLP metric names. All alert names are suffixed with [OTLP] in their summary to distinguish them from Prometheus-native alerts.
Load this file instead of the three Prometheus rule files when using an OTLP pipeline.
scrape_configs:
- job_name: 'cm3500'
static_configs:
- targets: ['localhost:10044']
scrape_interval: 30s
rule_files:
- "alert_rules_common.yml"
- "alert_rules_eurodocsis.yml" # or alert_rules_docsis_us.ymlThe test suite includes fixture-based OTLP snapshot tests for both metrics and modem event logs.
Snapshot files:
tests/fixtures/otlp_metrics_snapshot.txttests/fixtures/otlp_logs_snapshot.txt
If OTLP output changes intentionally, run the test suite, inspect the assertion diff, update the snapshot files, and rerun tests.
cm3500-b-ce-exporter/
├── src/
│ ├── main.rs # CLI, HTTP server, background scraper/cache
│ ├── client.rs # Modem HTTP client (cookie auth, auto re-login)
│ ├── parser.rs # HTML parsing for all endpoints
│ ├── metrics.rs # Prometheus text format rendering
│ └── otlp.rs # OTLP/HTTP JSON push for metrics and event logs
├── grafana/
│ ├── dashboard.json # Grafana dashboard (Prometheus metric names)
│ ├── dashboard_otlp.json # Grafana dashboard (OTLP/OpenTelemetry metric names)
│ └── provisioning/ # Auto-provisioning configs
│ ├── dashboards/
│ └── datasources/
├── prometheus/
│ ├── prometheus.yml # Prometheus config
│ ├── alert_rules_common.yml # Common alerts (all regions)
│ ├── alert_rules_eurodocsis.yml # EuroDOCSIS power thresholds
│ ├── alert_rules_docsis_us.yml # US DOCSIS power thresholds
│ └── alert_rules_otlp.yml # OTLP metric name alerts (all regions combined)
├── flake.nix # Nix dev shell
├── Cargo.toml
├── Dockerfile
├── docker-compose.yml
└── README.md