Skip to content

Prometheus Metrics

flowwler exposes Prometheus metrics at GET /metrics on the HTTP listen address (default [::]:9731).

curl -s http://localhost:9731/metrics | grep flowwler_

A pre-built Grafana dashboard covering flow ingestion health, group traffic rates, active mitigations, BGP session state, and IRR/NetBox/URL prefix freshness is available at assets/grafana-flowwler-overview.json. Import steps and recommended alert rules are at the bottom of this page.


Infrastructure metrics

Metric Type Labels Description
flowwler_info Gauge version, go_version Always 1. Carries build metadata as labels. Use for version alerting and dashboard annotations.
flowwler_up Gauge component 1 if the named component is running, 0 if it has stopped or failed. Known components: bgp, http, netflow, sflow.

Router flow ingestion metrics

All four counters carry router (the exporting router's IP address) and protocol (netflow, ipfix, or sflow) labels.

Metric Type Labels Description
flowwler_router_flows_received_total Counter router, protocol Total decoded flow records accepted from the router.
flowwler_router_bytes_decoded_total Counter router, protocol Total raw UDP payload bytes decoded from flow packets.
flowwler_router_discarded_flows_total Counter router, protocol Flow records where the router reported no output interface (ifIndex 0 or 0xFFFFFFFF), indicating the router itself dropped the traffic.
flowwler_router_discarded_bytes_total Counter router, protocol Sampling-scaled estimated bytes dropped by the router (no output interface). Both sFlow (frame_length × sampling_rate) and NetFlow/IPFIX (IN_BYTES × sampling_rate) values are already scaled. Aggregates across all destination IPs on that router - not filtered to any group or victim. Use flowwler_mitigation_discard_bps / _pps for per-victim rates.
flowwler_router_up Gauge router, protocol 1 while the router is actively sending flow packets, 0 after 120 seconds of silence. Set to 1 on every received UDP packet (even if decoding fails); drops to 0 when the 30-second staleness checker detects inactivity. Use this metric to alert on routers that have stopped exporting flow data.
flowwler_router_sampling_rate Gauge router, protocol Most recently observed 1-in-N sampling rate applied to flow records from the router. Updated from sFlow per-sample headers and from NetFlow SAMPLING_INTERVAL (field 34) / FLOW_SAMPLER_RANDOM_INTERVAL (field 50) fields (including Options Data records). Falls back to the per-router learned rate, then to default_sampling_rate config. Published for any non-zero rate, including 1 (no sampling). Absent entirely if no packets have been received from the router yet.

NetFlow-specific counters

Metric Type Labels Description
flowwler_netflow_templates_stored_total Counter router NetFlow v9/IPFIX templates written to the template cache. A persistent value of 0 after receiving packets means only data sets have arrived - wait for the next template refresh interval.
flowwler_netflow_template_cache_misses_total Counter router Data sets skipped because no matching template was cached yet. A non-zero value during startup is normal; a sustained high rate after warm-up indicates a template delivery problem.

IRR metrics

These metrics are updated after every IRR resolution - at startup, on SIGHUP, and every 12 hours by the background refresh.

Metric Type Labels Description
flowwler_irr_asns Gauge group Number of ASNs returned by the last AS-SET expansion for the group.
flowwler_irr_routes Gauge group, ip_version Number of prefixes returned by the last resolution. ip_version is 4 or 6.
flowwler_irr_resolve_duration_seconds Gauge group Wall-clock seconds taken by the last full IRR resolution (AS-SET expansion + all per-ASN prefix queries).
flowwler_irr_last_refresh_timestamp_seconds Gauge group Unix timestamp of the last successful IRR resolution. Use this to alert on stale prefix data.

NetBox metrics

These metrics are updated after every NetBox resolution - at startup, on SIGHUP, and every 12 hours by the background refresh.

Metric Type Labels Description
flowwler_netbox_routes Gauge group, ip_version Number of prefixes returned by the last resolution. ip_version is 4 or 6.
flowwler_netbox_resolve_duration_seconds Gauge group Wall-clock seconds taken by the last NetBox resolution.
flowwler_netbox_last_refresh_timestamp_seconds Gauge group Unix timestamp of the last successful NetBox resolution. Use this to alert on stale prefix data.

URL subnet metrics

These metrics are updated every time URL-based prefix lists are fetched — at startup, on SIGHUP, and on POST /api/v1/reload/sources.

The group label follows one of two conventions depending on the source:

  • Victim-subnet import (groups[].url_subnets): group is the group name (e.g. "mygroup").
  • Whitelist URL import (groups[].whitelist.url_subnets): group is "group/<name>". The global whitelist (defaults.whitelist.url_subnets) uses "defaults".
Metric Type Labels Description
flowwler_urlsubnets_routes Gauge group, ip_version Number of prefixes returned by the last URL fetch for the group. ip_version is 4 or 6. For whitelist sources, counts the total across all configured URLs for that scope.
flowwler_urlsubnets_resolve_duration_seconds Gauge group Total wall-clock seconds spent fetching all URL sources for the group during the last resolution pass.
flowwler_urlsubnets_last_refresh_timestamp_seconds Gauge group Unix timestamp of the last successful URL fetch pass for the group. Use this to alert on stale prefix data.
flowwler_urlsubnets_list_count Gauge group Number of URL import sources configured for the group (always 1 for victim-subnet imports; ≥ 1 for whitelist scopes).

Group metrics

These metrics are keyed by the logical group name as defined in the configuration.

Metric Type Labels Description
flowwler_group_up Gauge group 1 if the group is present in the active configuration, 0 if it has been removed by a hot-reload. Never set for groups that have never been loaded.
flowwler_group_bps Gauge group, direction Aggregate bits-per-second for the group, broken out by direction: inbound (traffic destined toward group IPs) and outbound (traffic originating from group IPs), each summed across all IPs visible in the sliding window.
flowwler_group_pps Gauge group, direction Aggregate packets-per-second for the group by direction (inbound / outbound).

flowwler_group_bps and flowwler_group_pps both carry a direction label and are published for both inbound and outbound traffic. They reflect the smoothed rate computed by the aggregator - bits per second and packets per second respectively. The window depth is controlled by aggregation_window in config (default 10s). Values are updated every 1 second. Outbound tracking requires the flow exporter to include the source IP in flow records (standard in all NetFlow v5/v9/IPFIX and sFlow implementations).


Rule metrics

These mirror the group metrics above at the per-rule level, for groups using rule-level escalation (match.rules).

Metric Type Labels Description
flowwler_rule_up Gauge group, rule 1 if the rule is present in the active configuration, 0 if it has been removed by a hot-reload.
flowwler_rule_bps Gauge group, rule Aggregate bits-per-second of traffic matching the rule, summed across all victim IPs in the group.
flowwler_rule_pps Gauge group, rule Aggregate packets-per-second of traffic matching the rule, summed across all victim IPs in the group.

Attack metrics

Metric Type Labels Description
flowwler_attack_level Gauge group, victim_ip, rule Current escalation level for a victim IP. 0 = idle, 1+ = active level. rule="" for group-level escalation, rule name for rule-level escalation.
flowwler_attack_entered_timestamp_seconds Gauge group, victim_ip, rule Unix timestamp when the current escalation level was entered. Reset on every level change, including re-activation from hold-down. Absent while idle. rule="" for group-level escalation. Use time() - flowwler_attack_entered_timestamp_seconds for time-at-current-level.
flowwler_attack_holddown_end_timestamp_seconds Gauge group, victim_ip, rule Unix timestamp when the hold-down period is scheduled to end. Present only in HoldDown phase, deleted on transition back to Active (re-spike) or Idle (expiry). rule="" for group-level escalation. Use flowwler_attack_holddown_end_timestamp_seconds - time() for remaining hold-down seconds.

Mitigation metrics

Metric Type Labels Description
flowwler_mitigation_active Gauge group, mitigation_type Number of currently active BGP mitigations. mitigation_type is blackhole, subnet-blackhole, or flowspec. group="manual" for mitigations triggered via the REST API.
flowwler_mitigation_flowspec_rules Gauge group Number of currently announced FlowSpec NLRI rules. Updated on every activate, withdraw, and periodic rule refresh. Each top-N attacker source (or consolidated traffic pattern) produces one rule. group="manual" for rules announced via the REST API.
flowwler_mitigation_start_timestamp_seconds Gauge group, victim_ip, rule, mitigation_type Unix timestamp when the current BGP mitigation started for a victim and rule. Absent when no mitigation is active. group="manual", rule="" for REST API mitigations. Use time() - flowwler_mitigation_start_timestamp_seconds for mitigation duration.
flowwler_mitigation_events_total Counter group, victim_ip, rule, mitigation_type, action Cumulative mitigation lifecycle events. action is activate, withdraw, or refresh (FlowSpec rule set updated mid-attack). group="manual", rule="" for REST API mitigations.
flowwler_mitigation_effectiveness Gauge group, victim_ip Fraction of victim traffic (0–1) that routers are already discarding internally (discard_bps / total_bps). Non-zero only while a mitigation is active. Rises toward 1 as the BGP blackhole or FlowSpec rule takes effect.
flowwler_mitigation_discard_bps Gauge group, victim_ip Raw bits per second being discarded by routers for the victim while a mitigation is active. Deleted when the last mitigation for the victim ends.
flowwler_mitigation_discard_pps Gauge group, victim_ip Raw packets per second being discarded by routers for the victim while a mitigation is active. Deleted when the last mitigation for the victim ends.
flowwler_rule_matches_total Counter group, rule Cumulative number of ticks on which a named rule matched traffic for the group. Useful for spotting rules that never fire.
flowwler_pktlen_derived_total Counter group, rule Cumulative number of ticks on which a rule's pkt_len match relied on a bytes/packets-derived approximation because the exporter reported no native MIN_PKT_LNGTH/MAX_PKT_LNGTH. A high rate relative to flowwler_rule_matches_total for the same group/rule means that rule's pkt_len filtering is running on estimates, not real exporter telemetry — see PktLenRange.

BGP metrics

Metric Type Labels Description
flowwler_bgp_peer_up Gauge peer 1 if the BGP session to the peer is in the Established state, 0 otherwise.
flowwler_bgp_peer_updates Gauge peer, direction BGP UPDATE messages exchanged with the peer. direction is sent or received.
flowwler_bgp_routes Gauge ip_version, nlri Routes in the GoBGP global RIB. ip_version is 4 or 6; nlri is unicast or flowspec.

BGP metrics are polled every 15 seconds from the embedded GoBGP instance.


Whitelist metrics

Metric Type Labels Description
flowwler_whitelist_prefixes Gauge scope Number of prefixes loaded in each whitelist scope. scope="defaults" for the global whitelist; the group name for per-group overrides. Updated on startup and after every reload.

PCAP metrics

These metrics are only non-zero when pcap.enabled: true in the configuration.

Metric Type Labels Description
flowwler_pcap_captures_active Gauge Number of currently open per-attack PCAP capture files.
flowwler_pcap_files_created_total Counter Total PCAP capture files opened since startup.
flowwler_pcap_records_written_total Counter Total flow records written to PCAP files.
flowwler_pcap_tap_dropped_total Counter Flow records dropped by the PCAP tap because the internal buffer was full (indicates the PCAP write loop is falling behind).

Notes on victim_ip labels for subnet states

Metrics for carpet bomb attacks (subnet-level escalation via match.subnet) use the subnet CIDR (e.g. 10.1.2.0/24) as the victim_ip label value instead of a single host address. This makes subnet states unambiguously distinguishable in Prometheus queries — they always contain a / in the label value.


Useful PromQL

Flow ingestion health

# Is the router sending flows? (alert if 0 for > 2× active-flow-timeout)
rate(flowwler_router_flows_received_total[2m])

# Template cache miss rate - should be near 0 after warm-up
rate(flowwler_netflow_template_cache_misses_total[5m])

# Fraction of received flows that the router is already dropping
rate(flowwler_router_discarded_flows_total[1m])
  / rate(flowwler_router_flows_received_total[1m])

# Sampling rate per router (should match router config)
flowwler_router_sampling_rate

Attack detection and mitigation

# Current inbound traffic rate per group (bits and packets)
flowwler_group_bps{direction="inbound"}
flowwler_group_pps{direction="inbound"}

# Current outbound traffic rate per group (detect compromised hosts / amplification sources)
flowwler_group_bps{direction="outbound"}
flowwler_group_pps{direction="outbound"}

# Active mitigations (any type)
sum by (group) (flowwler_mitigation_active)

# How effectively is the current mitigation working? (approaches 1 when the router is discarding)
flowwler_mitigation_effectiveness

# Raw discard rate - useful for capacity/alerting dashboards
flowwler_mitigation_discard_bps
flowwler_mitigation_discard_pps

# Cumulative activations in the last 24 hours (per victim)
increase(flowwler_mitigation_events_total{action="activate"}[24h])

# Active mitigations table: group, victim, rule, type, start time, duration so far
flowwler_mitigation_start_timestamp_seconds

# How long has the current mitigation been active? (seconds)
time() - flowwler_mitigation_start_timestamp_seconds

# How long has the victim been at the current attack level? (seconds, per rule)
time() - flowwler_attack_entered_timestamp_seconds

# Seconds remaining in hold-down per rule (negative = overdue, should not persist)
flowwler_attack_holddown_end_timestamp_seconds - time()

BGP health

# Is the BGP session to the router established?
flowwler_bgp_peer_up

# Active blackhole / FlowSpec routes in the RIB
flowwler_bgp_routes

IRR prefix health

# Seconds since the last successful IRR refresh per group
time() - flowwler_irr_last_refresh_timestamp_seconds

# How long did the last resolution take?
flowwler_irr_resolve_duration_seconds

# How many ASNs and routes were resolved?
flowwler_irr_asns
flowwler_irr_routes

NetBox prefix health

# Seconds since the last successful NetBox refresh per group
time() - flowwler_netbox_last_refresh_timestamp_seconds

# How long did the last resolution take?
flowwler_netbox_resolve_duration_seconds

# How many routes were resolved per IP version?
flowwler_netbox_routes

URL subnet prefix health

# Seconds since the last successful URL fetch per group
time() - flowwler_urlsubnets_last_refresh_timestamp_seconds

# How long did the last fetch pass take?
flowwler_urlsubnets_resolve_duration_seconds

# How many prefixes were fetched per IP version?
flowwler_urlsubnets_routes

# How many URL sources are configured per group?
flowwler_urlsubnets_list_count

# BGP session down
- alert: FlowwlerBGPSessionDown
  expr: flowwler_bgp_peer_up == 0
  for: 1m
  annotations:
    summary: "BGP session to {{ $labels.peer }} is down"

# No flows received from a router for 3 minutes
- alert: FlowwlerNoFlows
  expr: rate(flowwler_router_flows_received_total[3m]) == 0
  for: 3m
  annotations:
    summary: "No flows from router {{ $labels.router }} ({{ $labels.protocol }})"

# A configured group has been removed (hot-reload removed it from config)
- alert: FlowwlerGroupRemoved
  expr: flowwler_group_up == 0
  for: 1m
  annotations:
    summary: "Group {{ $labels.group }} is no longer in the active configuration"

# Mitigation active
- alert: FlowwlerMitigationActive
  expr: flowwler_mitigation_start_timestamp_seconds > 0
  annotations:
    summary: "Active mitigation: group={{ $labels.group }} victim={{ $labels.victim_ip }} rule={{ $labels.rule }} type={{ $labels.mitigation_type }}"

# IRR prefix data not refreshed in the last 24 hours
- alert: FlowwlerIRRStale
  expr: time() - flowwler_irr_last_refresh_timestamp_seconds > 86400
  for: 5m
  annotations:
    summary: "IRR prefix data for group {{ $labels.group }} has not been refreshed in over 24 hours"

# NetBox prefix data not refreshed in the last 24 hours
- alert: FlowwlerNetBoxStale
  expr: time() - flowwler_netbox_last_refresh_timestamp_seconds > 86400
  for: 5m
  annotations:
    summary: "NetBox prefix data for group {{ $labels.group }} has not been refreshed in over 24 hours"

# URL subnet prefix data not refreshed in the last 24 hours
- alert: FlowwlerURLSubnetsStale
  expr: time() - flowwler_urlsubnets_last_refresh_timestamp_seconds > 86400
  for: 5m
  annotations:
    summary: "URL subnet prefix data for group {{ $labels.group }} has not been refreshed in over 24 hours"

Grafana dashboard

A pre-built Grafana dashboard is included at assets/grafana-flowwler-overview.json. It covers flow ingestion health, group traffic rates, active mitigations, BGP session state, and IRR/NetBox/URL prefix freshness.

Grafana flowwler overview dashboard

Import steps:

  1. In Grafana, go to Dashboards → Import.
  2. Upload assets/grafana-flowwler-overview.json or paste its contents.
  3. Select your Prometheus data source and click Import.