Compare commits

..

No commits in common. "6d3387dfe57592e43760b75a7341c246fa7cb1c2" and "8ac156ce86ab965ac935e865e7d9784f71fa1dd6" have entirely different histories.

51 changed files with 4038 additions and 9383 deletions

View File

@ -19,19 +19,6 @@ OBMP_DOMAIN=changeme.example.com
# cookie is valid across subpaths/subdomains.
OBMP_COOKIE_DOMAIN=example.com
# Container memory limits. Lab defaults shown; raise for production
# (see docs/production-sizing.md). psql-app's limit must exceed its MEM heap.
PSQL_MEM_LIMIT=6g
PSQL_APP_MEM_LIMIT=4g
KAFKA_MEM_LIMIT=4g
# gNMI streaming telemetry (telegraf, test profile). GNMI_ADDRESSES is a
# quoted, comma-separated host:port list — add a router here once gNMI/grpc
# is enabled on it and the management path is reachable.
GNMI_ADDRESSES="10.100.0.100:57400", "10.100.0.200:57400"
GNMI_USERNAME=changeme
GNMI_PASSWORD=changeme
# ---------------------------------------------------------------------------
# ExaBGP route injector (test profile)
# ---------------------------------------------------------------------------

View File

@ -18,7 +18,6 @@ services:
restart: unless-stopped
container_name: obmp-zookeeper
image: confluentinc/cp-zookeeper:7.1.1
mem_limit: 1g
volumes:
- ${OBMP_DATA_ROOT}/zk-data:/var/lib/zookeeper/data
- ${OBMP_DATA_ROOT}/zk-log:/var/lib/zookeeper/log
@ -30,8 +29,6 @@ services:
restart: unless-stopped
container_name: obmp-kafka
image: confluentinc/cp-kafka:7.1.1
# Raise KAFKA_MEM_LIMIT for production (full-table initial dumps are bursty).
mem_limit: ${KAFKA_MEM_LIMIT:-4g}
# Change the mount point to where you want to store Kafka data.
# Normally 80GB or more
@ -88,7 +85,6 @@ services:
restart: unless-stopped
container_name: obmp-grafana
image: grafana/grafana:9.1.7
mem_limit: 1g
ports:
- "3000:3000"
volumes:
@ -129,8 +125,6 @@ services:
restart: unless-stopped
container_name: obmp-psql
image: openbmp/postgres:2.2.1
# Raise PSQL_MEM_LIMIT for production (see docs/production-sizing.md).
mem_limit: ${PSQL_MEM_LIMIT:-6g}
privileged: true
shm_size: 1536m
sysctls:
@ -154,7 +148,6 @@ services:
restart: unless-stopped
container_name: obmp-collector
image: openbmp/collector:2.2.3
mem_limit: 2g
sysctls:
- net.ipv4.tcp_keepalive_intvl=30
- net.ipv4.tcp_keepalive_probes=5
@ -170,9 +163,6 @@ services:
restart: unless-stopped
container_name: obmp-psql-app
image: openbmp/psql-app:2.2.2
# mem_limit must exceed the MEM (JVM heap) env below. Raise both for
# production — see docs/production-sizing.md.
mem_limit: ${PSQL_APP_MEM_LIMIT:-4g}
sysctls:
- net.ipv4.tcp_keepalive_intvl=30
- net.ipv4.tcp_keepalive_probes=5
@ -217,7 +207,6 @@ services:
restart: unless-stopped
container_name: obmp-exabgp
profiles: ["test"]
mem_limit: 512m
build:
context: ./exabgp
dockerfile: Dockerfile
@ -242,7 +231,6 @@ services:
restart: unless-stopped
container_name: obmp-exabgp-ui
profiles: ["test"]
mem_limit: 256m
build:
context: ./exabgp-ui
dockerfile: Dockerfile
@ -257,7 +245,6 @@ services:
container_name: obmp-influxdb
profiles: ["test"]
image: influxdb:2.7
mem_limit: 2g
ports:
- "8086:8086"
volumes:
@ -275,7 +262,6 @@ services:
restart: unless-stopped
container_name: obmp-telegraf
profiles: ["test"]
mem_limit: 512m
build:
context: ./telegraf
dockerfile: Dockerfile
@ -284,11 +270,6 @@ services:
- influxdb
environment:
- INFLUXDB_TOKEN=openbmp-telemetry-token
# gNMI fleet — quoted, comma-separated host:port list. Default = the two
# ESXi CORE routers; extend via GNMI_ADDRESSES in .env for more routers.
- 'GNMI_ADDRESSES=${GNMI_ADDRESSES:-"10.100.0.100:57400", "10.100.0.200:57400"}'
- GNMI_USERNAME=${GNMI_USERNAME:-webui}
- GNMI_PASSWORD=${GNMI_PASSWORD:-cisco}
# --- Phase 4: Traffic Generator ---
@ -296,7 +277,6 @@ services:
restart: unless-stopped
container_name: obmp-traffic-gen
profiles: ["test"]
mem_limit: 1g
build:
context: ./traffic-gen
dockerfile: Dockerfile
@ -313,7 +293,6 @@ services:
restart: unless-stopped
container_name: obmp-traffic-gen-ui
profiles: ["test"]
mem_limit: 256m
build:
context: ./traffic-gen-ui
dockerfile: Dockerfile
@ -324,7 +303,6 @@ services:
restart: unless-stopped
container_name: obmp-traffic-gen-responder
profiles: ["test"]
mem_limit: 1g
build:
context: ./traffic-gen
dockerfile: Dockerfile
@ -346,7 +324,6 @@ services:
restart: unless-stopped
container_name: obmp-whois
image: openbmp/whois:2.2.0
mem_limit: 1g
sysctls:
- net.ipv4.tcp_keepalive_intvl=30
- net.ipv4.tcp_keepalive_probes=5
@ -366,7 +343,6 @@ services:
restart: unless-stopped
container_name: obmp-authelia
profiles: ["auth"]
mem_limit: 256m
image: authelia/authelia:4.38
ports:
- "9091:9091"
@ -379,7 +355,6 @@ services:
restart: unless-stopped
container_name: obmp-portal
profiles: ["auth"]
mem_limit: 128m
image: nginx:alpine
ports:
- "8080:80"

View File

@ -1,223 +0,0 @@
# OpenBMP Backup & Restore
How to back up and restore the OpenBMP PostgreSQL database, what the backup
covers, and what it deliberately does not.
---
## What `scripts/pg-backup.sh` backs up
The script runs `pg_dump` inside the `obmp-psql` container and produces a
single timestamped, compressed, custom-format dump of the **entire `openbmp`
database**:
- All BMP/BGP operational tables — `routers`, `bgp_peers`, `ip_rib`,
`base_attrs`, `global_ip_rib`, `l3vpn_rib`, the `ls_*` link-state tables.
- All history / TimescaleDB hypertables — `ip_rib_log`, `peer_event_log`,
`stat_reports`, and the `stats_*` aggregate tables.
- Reference / enrichment data — `geo_ip`, `info_asn`, `info_route`,
`rpki_validator`, `pdb_exchange_peers`.
- Schema objects — table definitions, indexes, views, functions, triggers,
enum types, and the TimescaleDB hypertable configuration.
The dump is taken against a **live database**`pg_dump` uses an MVCC
snapshot, so no downtime and no service stop is required. It is written
atomically (to a `.partial` file, renamed on success) so an interrupted run
never leaves a dump that looks valid but is truncated.
Output: `${OBMP_DATA_ROOT:-/var/openbmp}/backups/openbmp-YYYYMMDD-HHMMSS.dump`
### TimescaleDB note
The OpenBMP database uses TimescaleDB hypertables (`ip_rib_log`,
`peer_event_log`, the `stats_*` tables, with compression policies).
**A `pg_dump` logical backup restores hypertables correctly** — the dump
captures the `_timescaledb_catalog` metadata, and on restore the hypertable
structure, chunks, and compression settings are recreated. No special flags
are needed for the dump. The only requirement is that the **restore target
has the TimescaleDB extension available** — which the `openbmp/postgres`
image provides, so restoring into a fresh `obmp-psql` works out of the box.
---
## Scheduling
Make the script executable once:
```bash
chmod +x scripts/pg-backup.sh
```
Add a cron entry (`crontab -e`) — daily at 02:30, logging to a file:
```cron
30 2 * * * OBMP_DATA_ROOT=/var/openbmp /home/user/obmp-docker/scripts/pg-backup.sh >> /var/openbmp/backups/pg-backup.log 2>&1
```
The cron user must be able to reach the Docker daemon — run it as a user in
the `docker` group, or as root. A systemd timer is an equally valid
alternative.
### Configuration
All settings are environment variables with sensible defaults:
| Variable | Default | Purpose |
|----------|---------|---------|
| `OBMP_DATA_ROOT` | `/var/openbmp` | Base data dir; backups go to `${OBMP_DATA_ROOT}/backups` |
| `OBMP_BACKUP_DIR` | (unset) | Explicit backup dir, overrides the default |
| `OBMP_PG_CONTAINER` | `obmp-psql` | Postgres container name |
| `OBMP_PG_DB` | `openbmp` | Database name |
| `OBMP_PG_USER` | `openbmp` | Database user |
| `OBMP_BACKUP_RETENTION_DAYS` | `14` | Dumps older than this are pruned each run |
Retention only prunes files matching the script's own `openbmp-*.dump`
naming pattern — nothing else in the directory is touched.
### Production recommendations
- **Copy dumps off-host.** A local backup does not survive host loss. Sync
the backup directory to object storage / a backup server (e.g. nightly
`rclone`, `restic`, or your existing ISP backup tooling).
- **Size the backup volume** — at production scale (~100150M NLRIs) the
dump can be tens of GB even compressed. See `docs/production-sizing.md`.
- **Test restores periodically** — an untested backup is not a backup.
- For tighter RPO than once-daily logical dumps, consider PostgreSQL
continuous archiving / PITR (WAL archiving + `pg_basebackup`). That is out
of scope for this script but worth planning for a production deployment.
---
## Restore procedure
This restores a dump into a **fresh, empty** `obmp-psql` database. Restoring
over a populated database risks conflicts — start clean.
### 1. Stop the writers
Stop the services that write to the database so nothing races the restore:
```bash
docker compose -p obmp stop psql-app collector
```
Leave `obmp-psql` running.
### 2. Recreate an empty database
Drop and recreate the `openbmp` database inside the running container:
```bash
docker exec -i obmp-psql psql -U openbmp -d postgres <<'EOSQL'
DROP DATABASE IF EXISTS openbmp;
CREATE DATABASE openbmp OWNER openbmp;
EOSQL
```
> Restoring into a **brand-new container**? Bring `obmp-psql` up first and let
> it initialize, but **do not** create the `config/init_db` trigger file —
> the schema comes from the dump, not from psql-app's first-run migration.
### 3. Restore the dump
Copy the dump into the container and run `pg_restore`:
```bash
DUMP=/var/openbmp/backups/openbmp-YYYYMMDD-HHMMSS.dump
docker cp "${DUMP}" obmp-psql:/tmp/restore.dump
docker exec -i obmp-psql \
pg_restore -U openbmp -d openbmp --no-owner --no-privileges \
--jobs=4 /tmp/restore.dump
docker exec obmp-psql rm -f /tmp/restore.dump
```
- `--no-owner --no-privileges` — the dump was created with the same flags;
objects are recreated owned by the connecting role.
- `--jobs=4` — parallel restore; raise it on a many-core host to speed up the
large `ip_rib` / `ip_rib_log` tables. Custom-format dumps support this.
- Some non-fatal warnings (e.g. about the TimescaleDB extension or existing
objects) are normal. A non-zero exit with only warnings is usually fine —
inspect the output before assuming failure.
Alternatively, stream the restore without `docker cp`:
```bash
docker exec -i obmp-psql pg_restore -U openbmp -d openbmp \
--no-owner --no-privileges < "${DUMP}"
```
(Streaming via stdin disables `--jobs` parallelism — use `docker cp` for
large dumps.)
### 4. Verify
```bash
docker exec -i obmp-psql psql -U openbmp -d openbmp -c "
SELECT (SELECT count(*) FROM routers) AS routers,
(SELECT count(*) FROM bgp_peers) AS peers,
(SELECT count(*) FROM ip_rib) AS rib_rows;"
```
Confirm hypertables came back:
```bash
docker exec -i obmp-psql psql -U openbmp -d openbmp -c "
SELECT hypertable_name FROM timescaledb_information.hypertables;"
```
### 5. Restart the writers
```bash
docker compose -p obmp start collector psql-app
```
The collector reconnects to the routers' BMP sessions and psql-app resumes
consuming from Kafka. Live state catches up from the routers.
---
## What is NOT covered
This backup is **PostgreSQL only**. The following are out of scope and need
their own handling:
- **Kafka data is transient.** The `obmp-kafka` topics are a short-retention
pipeline buffer (`KAFKA_LOG_RETENTION_MINUTES: 720` — 12 hours). They are
not a system of record and do not need backing up. After a restore, routers
re-send BMP and the pipeline refills naturally.
- **InfluxDB telemetry has its own backup.** The gNMI streaming-telemetry
data lives in `obmp-influxdb` (bucket `telemetry`), not in PostgreSQL.
`pg_dump` does not touch it. Back it up separately with the Influx CLI:
```bash
# Backup
docker exec obmp-influxdb influx backup /var/lib/influxdb2/backup \
--token "$INFLUXDB_ADMIN_TOKEN"
docker cp obmp-influxdb:/var/lib/influxdb2/backup \
/var/openbmp/backups/influxdb-$(date +%Y%m%d)
# Restore
docker cp /var/openbmp/backups/influxdb-YYYYMMDD \
obmp-influxdb:/var/lib/influxdb2/restore
docker exec obmp-influxdb influx restore /var/lib/influxdb2/restore \
--token "$INFLUXDB_ADMIN_TOKEN"
```
Telemetry is also less critical than BMP data (30-day retention,
data-plane counters) — back it up if you need historical telemetry to
survive a host loss; otherwise the 30-day window simply re-fills.
- **Grafana** — dashboards and datasources are provisioned from files in the
repo (`obmp-grafana/provisioning/` and `obmp-grafana/dashboards/`), so they
are already version-controlled in git. The Grafana database under
`${OBMP_DATA_ROOT}/grafana` (users, preferences, manually-created
dashboards, alert state) is *not* covered by this script — back up that
directory separately if it holds anything not reproducible from the repo.
- **Configuration & secrets**`.env`, `docker-compose.yml`, and the
`${OBMP_DATA_ROOT}/config` directory. Keep these in version control /
your secrets manager.

View File

@ -1,96 +0,0 @@
# OpenBMP Production Sizing — 40 Full-Table-Edge Routers
Sizing guidance for deploying the OpenBMP stack against a production ISP
network of **40 full-table-edge routers** with gNMI streaming telemetry.
Derived from the OpenBMP `psql-app` sizing guidance and measured lab behavior.
## Workload assumptions
| Parameter | Value |
|-----------|-------|
| Monitored routers | 40, full-table edge |
| BMP RIB scope | Adj-RIB-In (see recommendation below) |
| Full feeds per router | ~23 eBGP peers carrying the full DFZ |
| Routes per full feed | ~1.2M (≈1M IPv4 + ~0.2M IPv6) |
| **Estimated total NLRIs** | **~100150M** in Adj-RIB-In |
| Telemetry | gNMI via Telegraf → InfluxDB, ~50200 interfaces/router, 10 s interval |
| History retention | `ip_rib_log` 4 weeks, LS logs 4 months, `peer_event_log` 1 year |
The NLRI estimate (40 × ~2.5 feeds × 1.2M) places this deployment at the top
of the OpenBMP `psql-app` guidance tier (150M NLRIs → 64 GB heap).
## BMP RIB scope — recommendation
**Deploy with Adj-RIB-In only.** It is the OpenBMP default, is what every
dashboard is built on, and captures the highest-value data — what each peer
advertises. Alternatives and their cost:
- **Loc-RIB** — adds a full post-best-path converged table per router
(~40 × 1.2M ≈ +48M NLRIs). Add later, selectively, only where best-path
analysis is needed; verify the IOS-XR release supports Loc-RIB BMP.
- **Adj-RIB-Out** — multiplies further (per advertised peer). Not recommended
for the initial deployment.
- **Post-policy Adj-RIB-In** — if inbound policy is restrictive this trims
volume meaningfully; with permissive import it is similar to pre-policy.
## Compute & memory
| Component | Lab today | Production target | Rationale |
|-----------|-----------|-------------------|-----------|
| **Total RAM** | 31 GB | **96128 GB** | psql-app heap 4864 GB + PostgreSQL shared_buffers/cache + Kafka 48 GB + InfluxDB + Grafana + collector |
| **CPU** | 8 cores | **1632 vCPU** | PostgreSQL is CPU-bound under full-table churn — lab psql already sustains ~287% (3 cores) at 18 routers |
| `psql-app` JVM heap (`MEM`) | 3 GB | **4864 GB** | OpenBMP guidance: 4 GB ≈ 10M NLRIs, 64 GB ≈ 150M NLRIs |
| `psql-app` container `mem_limit` | 4 GB | **heap + ~8 GB** | Set `PSQL_APP_MEM_LIMIT` above the JVM heap |
| `psql` container `mem_limit` | 6 GB | **4864 GB** | Set `PSQL_MEM_LIMIT`; PostgreSQL wants ~25% as `shared_buffers` and the rest for OS cache |
| `kafka` container `mem_limit` | 4 GB | **812 GB** | Set `KAFKA_MEM_LIMIT`; full-table initial dumps from 40 routers are bursty |
## Storage
| Store | Lab today | Production target | Notes |
|-------|-----------|-------------------|-------|
| **PostgreSQL** | 25 GB | **24 TB NVMe SSD** | `ip_rib` current state (~100150M rows) + `ip_rib_log` history (4-week retention, the dominant grower) + `base_attrs` + `geo_ip` (~7 GB fixed). OpenBMP guidance: 500 GB main + 1 TB TimescaleDB; add headroom. |
| **Kafka** | 0.2 GB | **100500 GB** | 12 h retention; sized for full-table initial-dump bursts × 40 routers |
| **InfluxDB (telemetry)** | minimal | **50200 GB** | 40 routers × ~50200 interfaces × 10 s gNMI × 30 d; compresses well |
| **Total** | — | **~35 TB fast NVMe** | Use NVMe; PostgreSQL random-IO under churn is the bottleneck on slow disks |
Put the PostgreSQL data directory and the TimescaleDB tablespace on NVMe.
`ip_rib_log` 4-week retention is the main storage tuning knob — revisit once
production update volume is measured.
## Architecture
A single host is viable only if large (**≥128 GB RAM, ≥32 vCPU, multi-TB
NVMe**). **Preferred: split services across hosts**
| Host | Services | Profile |
|------|----------|---------|
| **DB host** (heaviest) | postgres | — |
| **Pipeline host** | kafka, zookeeper, collector, psql-app | core |
| **Presentation host** | grafana, influxdb, telegraf, whois | core + telemetry |
Whichever layout: every service already carries a Compose `mem_limit` — raise
`PSQL_MEM_LIMIT` / `PSQL_APP_MEM_LIMIT` / `KAFKA_MEM_LIMIT` in `.env` for the
production hosts.
## PostgreSQL tuning
- `shared_buffers` ≈ 25% of host RAM; large `effective_cache_size`.
- Raise `work_mem` (dashboard aggregate queries) and `maintenance_work_mem`.
- `max_wal_size` already 10 GB — keep or raise for churn bursts.
- Enable parallel query (`max_parallel_workers_per_gather`).
- Aggressive autovacuum on churn tables (`ip_rib`, `base_attrs`, `ip_rib_log`)
— applied in the lab; persist these settings in production provisioning.
- TimescaleDB compression is already enabled on `ip_rib_log` and the `stats_*`
hypertables — keep it.
## Reference bill of materials (single-host option)
| Resource | Spec |
|----------|------|
| CPU | 32 vCPU |
| RAM | 128 GB |
| Storage | 4 TB NVMe SSD |
| Network | 1 GbE+ to the routers' BMP source network |
For the split-host option, divide per the architecture table — the DB host
takes the bulk of RAM and all of the fast storage.

View File

@ -1,488 +0,0 @@
# OpenBMP Production Security Hardening
A prioritized checklist for hardening the OpenBMP Docker stack before exposing
it to a production ISP network of 40 full-table-edge routers. Work top to
bottom — items are ordered roughly by risk reduction per unit effort.
This document **recommends** changes. It does not modify `docker-compose.yml`
or any running service. Apply the changes in a maintenance window and test.
> Threat model in brief: the stack ingests BMP from production routers, stores
> the full DFZ in PostgreSQL, and exposes Grafana to operators. The crown
> jewels are (a) the database, (b) the Grafana admin plane, and (c) the BMP
> ingest port. Everything below protects one of those three.
---
## Priority 0 — Credentials (do this first)
Every service currently ships with the placeholder credential `openbmp` and
related defaults are committed in `docker-compose.yml`:
| Service | Setting | Current value |
|---------|---------|---------------|
| PostgreSQL | `POSTGRES_USER` / `POSTGRES_PASSWORD` | `openbmp` / `openbmp` |
| psql-app | `POSTGRES_PASSWORD` | `openbmp` |
| whois | `POSTGRES_PASSWORD` | `openbmp` |
| Grafana | `GF_SECURITY_ADMIN_PASSWORD` | `openbmp` |
| InfluxDB | `DOCKER_INFLUXDB_INIT_PASSWORD` | `openbmp123` |
| InfluxDB | `DOCKER_INFLUXDB_INIT_ADMIN_TOKEN` | `openbmp-telemetry-token` |
| Grafana datasource | `secureJsonData.password` | `openbmp` (in `openbmp-ds.yml`) |
### 0.1 Move every secret to `.env` (or a secrets manager)
`.env` is git-ignored. As a minimum, replace the hardcoded literals in
`docker-compose.yml` with `${VAR}` references and define them in `.env`:
```env
# .env — never commit this file
POSTGRES_PASSWORD=<long-random-string>
GF_SECURITY_ADMIN_PASSWORD=<long-random-string>
INFLUXDB_ADMIN_PASSWORD=<long-random-string>
INFLUXDB_ADMIN_TOKEN=<long-random-token>
```
```yaml
# docker-compose.yml (recommended edit — operator applies)
grafana:
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:?set in .env}
psql:
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?set in .env}
```
The `:?` form makes the stack fail fast if a secret is missing rather than
silently falling back to a default.
Generate strong values:
```bash
openssl rand -base64 32 # passwords
openssl rand -hex 32 # tokens
```
### 0.2 For a real production deployment, use a secrets manager
`.env` on disk is better than committed literals, but it is still a
plaintext file readable by anyone in the `docker` group. For production:
- **Docker Compose secrets** (`secrets:` block, files mounted at
`/run/secrets/...`) — the lowest-friction upgrade; keep the secret files
outside the repo, `chmod 600`, owned by root.
- **HashiCorp Vault**, **AWS Secrets Manager**, **Bitwarden Secrets**, or your
existing ISP secret store — inject at deploy time via a wrapper that renders
`.env` from the vault and shreds it after `docker compose up`.
Whatever the choice: rotate all six credentials above on first production
deploy — they have been in git history as `openbmp` and must be considered
compromised.
### 0.3 Rotate the Grafana datasource password in lockstep
`obmp-grafana/provisioning/datasources/openbmp-ds.yml` carries
`secureJsonData.password`. It is read at Grafana start. When you change the
PostgreSQL password, update this file too (it supports `$__file{}` and
env-var expansion: `password: $POSTGRES_PASSWORD`) and restart Grafana.
---
## Priority 1 — Network exposure / firewalling
The host currently publishes these ports to `0.0.0.0`: 5000 (BMP), 5432
(PostgreSQL), 9092 (Kafka), 3000 (Grafana), 8086 (InfluxDB), 4300 (whois),
9091 (Authelia). Most should not be world-reachable.
### 1.1 BMP collector (port 5000) — restrict to router management subnets
The collector accepts a BMP session from any source. A rogue BMP feed can
inject bogus routers/peers/prefixes into the database. Firewall it to the
router management subnets only.
`nftables` example (preferred on modern hosts):
```nft
# /etc/nftables.conf — adjust subnets to your router management ranges
table inet obmp {
chain input {
type filter hook input priority 0; policy accept;
# BMP ingest — only from router management subnets
tcp dport 5000 ip saddr { 10.100.0.0/24, 10.100.1.0/24 } accept
tcp dport 5000 drop
}
}
```
`iptables` equivalent:
```bash
iptables -A INPUT -p tcp --dport 5000 -s 10.100.0.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 5000 -s 10.100.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 5000 -j DROP
```
> Docker's `iptables` integration uses the `DOCKER-USER` chain for
> container-published ports. Put the rules above in `DOCKER-USER` so Docker
> does not bypass them:
> ```bash
> iptables -I DOCKER-USER -p tcp --dport 5000 -s 10.100.0.0/24 -j RETURN
> iptables -I DOCKER-USER -p tcp --dport 5000 -s 10.100.1.0/24 -j RETURN
> iptables -A DOCKER-USER -p tcp --dport 5000 -j DROP
> ```
### 1.2 PostgreSQL (5432), Kafka (9092), InfluxDB (8086), whois (4300)
None of these need to be reachable from outside the stack:
- **PostgreSQL** — only `psql-app`, `whois`, and `grafana` connect, all on the
Compose network. Bind the published port to loopback only, or drop the
`ports:` mapping entirely:
```yaml
# docker-compose.yml — psql service
ports:
- "127.0.0.1:5432:5432" # localhost only; or remove entirely
```
- **Kafka 9092** — see Priority 2.
- **InfluxDB 8086** — only Grafana and Telegraf use it; bind to loopback or
drop the mapping (Telegraf uses host networking and reaches it via
localhost; Grafana reaches it on the Compose network).
- **whois 4300** — expose only if you actually offer a public whois service;
otherwise bind to loopback.
For anything that genuinely must be reachable, restrict by source with the
firewall pattern from 1.1.
### 1.3 Grafana (3000) — keep it behind Authelia
Authelia already fronts Grafana (the `auth` profile + `GF_AUTH_PROXY_*`
settings). Make that the *only* path:
- Bind Grafana's published port to loopback: `127.0.0.1:3000:3000`, and let
the reverse proxy / Authelia terminate TLS and reach it internally.
- Do **not** leave port 3000 directly reachable — `GF_AUTH_PROXY_ENABLED=true`
trusts the `Remote-User` header, so any client that can reach 3000 directly
and set that header bypasses authentication entirely.
---
## Priority 2 — Kafka transport security
Kafka is currently **PLAINTEXT** and advertises a host-IP listener:
```yaml
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://obmp-kafka:29092,PLAINTEXT_HOST://${HOST_IP}:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
```
The `obmp-kafka:29092` listener is internal to the Compose network and is the
only one the collector and psql-app use. The `PLAINTEXT_HOST://...:9092`
listener exists only for outside access and is not needed by the core stack.
**Recommended (simplest, most secure): remove the host listener.** If nothing
outside the Compose network consumes Kafka, drop the `9092` port mapping and
the `PLAINTEXT_HOST` advertised listener so Kafka is reachable only on the
internal Docker network:
```yaml
kafka:
# remove the - "9092:9092" ports entry
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://obmp-kafka:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
```
**If external Kafka access is genuinely required** (e.g. a separate analytics
consumer, or the split-host architecture in `production-sizing.md` where
Kafka and the DB are on different hosts), do **not** leave it PLAINTEXT on a
routed network. Enable SASL_SSL on the external listener:
```yaml
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://obmp-kafka:29092,SASL_SSL://${HOST_IP}:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,SASL_SSL:SASL_SSL
KAFKA_SASL_ENABLED_MECHANISMS: SCRAM-SHA-512
KAFKA_SSL_KEYSTORE_LOCATION: /etc/kafka/secrets/kafka.keystore.jks
KAFKA_SSL_KEYSTORE_PASSWORD: ${KAFKA_KEYSTORE_PASSWORD}
KAFKA_SSL_KEY_PASSWORD: ${KAFKA_KEY_PASSWORD}
KAFKA_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/kafka.truststore.jks
KAFKA_SSL_TRUSTSTORE_PASSWORD: ${KAFKA_TRUSTSTORE_PASSWORD}
```
Keep the internal `PLAINTEXT://obmp-kafka:29092` listener for the collector
and psql-app — intra-Compose traffic on a private bridge does not need TLS and
adding SASL there means re-configuring both clients. At minimum, never publish
a PLAINTEXT Kafka listener on an IP that routes beyond the host.
---
## Priority 3 — PostgreSQL hardening
### 3.1 Change the default `openbmp` / `openbmp` credentials
Covered in Priority 0. Note that `POSTGRES_USER`/`POSTGRES_PASSWORD` only take
effect when the data directory is initialized. To rotate on an existing
database, change the password in SQL and update every consumer:
```bash
docker exec -it obmp-psql psql -U openbmp -d openbmp \
-c "ALTER ROLE openbmp WITH PASSWORD '<new-strong-password>';"
```
Then update `POSTGRES_PASSWORD` for `psql-app` and `whois`, the
`secureJsonData.password` in `openbmp-ds.yml`, and restart those services.
### 3.2 Create a least-privilege role for Grafana
Grafana only needs to read. Do not let it connect as the owning role:
```sql
CREATE ROLE grafana_ro LOGIN PASSWORD '<strong-password>';
GRANT CONNECT ON DATABASE openbmp TO grafana_ro;
GRANT USAGE ON SCHEMA public TO grafana_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO grafana_ro;
```
Point `openbmp-ds.yml` at `grafana_ro`. This contains a Grafana compromise to
read-only and blocks SQL-panel writes.
### 3.3 Restrict `pg_hba.conf`
The default OpenBMP image is permissive (`host all all all md5` or similar).
Tighten it so only the stack's own subnet can connect, and require
`scram-sha-256`:
```conf
# pg_hba.conf (inside the obmp-psql container / mounted)
# TYPE DATABASE USER ADDRESS METHOD
local all all scram-sha-256
host openbmp openbmp 172.16.0.0/12 scram-sha-256 # Docker bridge range
host openbmp grafana_ro 172.16.0.0/12 scram-sha-256
hostssl openbmp openbmp 0.0.0.0/0 scram-sha-256 # only if remote DB host
# reject everything else
host all all 0.0.0.0/0 reject
```
Identify the actual Compose network subnet with
`docker network inspect obmp_default` and scope `ADDRESS` to it. Reload with
`docker exec obmp-psql psql -U openbmp -c "SELECT pg_reload_conf();"`.
> `scram-sha-256` requires `password_encryption = scram-sha-256` in
> `postgresql.conf` and that passwords were set/rotated *after* that change.
### 3.4 Enable SSL/TLS
The Grafana datasource already requests `sslmode: "require"` — but the server
must actually present a certificate. In `postgresql.conf`:
```conf
ssl = on
ssl_cert_file = '/var/lib/postgresql/server.crt'
ssl_key_file = '/var/lib/postgresql/server.key'
```
Generate a cert (self-signed is acceptable for an internal DB; use your
internal CA if you have one):
```bash
openssl req -new -x509 -days 825 -nodes -text \
-out server.crt -keyout server.key -subj "/CN=obmp-psql"
chmod 600 server.key # PostgreSQL refuses a world-readable key
```
Mount both files into the container's data directory. For the strongest
posture, move clients to `sslmode: verify-full` once a proper CA chain is in
place. This is most important if PostgreSQL runs on a separate host (the
split-host architecture in `production-sizing.md`) — intra-host Compose
traffic is lower-risk but TLS is still recommended.
### 3.5 Limit listen addresses
If PostgreSQL must accept connections from another host (split-host layout),
keep `listen_addresses` scoped — do not leave it at `*` if a single interface
suffices:
```conf
listen_addresses = 'localhost,172.18.0.1' # loopback + Docker bridge gateway
```
On a single-host deployment, drop the `5432` port mapping entirely (1.2) so
the listener is reachable only on the Compose network.
---
## Priority 4 — Drop `privileged: true` on the `psql` service
```yaml
psql:
privileged: true # <-- remove or replace
shm_size: 1536m
sysctls:
- net.ipv4.tcp_keepalive_intvl=30
- net.ipv4.tcp_keepalive_probes=5
- net.ipv4.tcp_keepalive_time=180
```
**Why it is a risk:** `privileged: true` gives the container *all* Linux
capabilities, disables seccomp/AppArmor confinement, and grants access to all
host devices. A compromise of PostgreSQL — the process most exposed to
untrusted route data — would then be a near-complete host compromise. This is
the single largest container-isolation gap in the stack.
**Why it is probably there:** PostgreSQL needs adequate shared memory and
benefits from the TCP keepalive `sysctls`. The compose file already sets
`shm_size: 1536m` and the `sysctls:` list explicitly — both of which Docker
applies *without* needing privileged mode. So `privileged: true` is most
likely a leftover, not a hard requirement.
**Recommended action — test without it:**
1. In a maintenance window, remove `privileged: true` and start the service.
2. Confirm PostgreSQL starts, the namespaced `sysctls` apply
(`docker exec obmp-psql sysctl net.ipv4.tcp_keepalive_time`), and shared
memory is honored (`docker exec obmp-psql cat /proc/meminfo | grep Shmem`,
and watch for `could not resize shared memory segment` errors in the log).
3. If everything is healthy, leave it removed.
If a specific capability turns out to be needed, add only that one instead of
going fully privileged:
```yaml
psql:
# privileged: true <-- removed
shm_size: 1536m
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
- DAC_OVERRIDE # add only capabilities proven necessary by testing
sysctls:
- net.ipv4.tcp_keepalive_intvl=30
- net.ipv4.tcp_keepalive_probes=5
- net.ipv4.tcp_keepalive_time=180
```
The `sysctls:` block stays — those are namespaced and do not require
privileged mode.
---
## Priority 5 — Container hardening (defense in depth)
Apply across services after the higher-priority items. Test each service
individually — `read_only` in particular will surface paths a service writes
to that then need explicit `tmpfs` mounts.
### 5.1 `no-new-privileges`
Prevents a process inside a container from gaining privileges via setuid
binaries. Safe to apply to every service:
```yaml
security_opt:
- no-new-privileges:true
```
### 5.2 Drop capabilities
Most of these services need almost no Linux capabilities. Start from zero and
add back only what breaks:
```yaml
cap_drop:
- ALL
```
- `grafana`, `whois`, `portal`, `zookeeper` — typically run fine with
`cap_drop: [ALL]`.
- `collector`, `kafka`, `psql`, `psql-app` — drop ALL, then add back any
capability proven necessary (see Priority 4 for `psql`).
- `traffic-gen*` legitimately need `NET_RAW`/`NET_ADMIN` (Scapy) — leave those
`cap_add` entries; they are already minimal.
### 5.3 Read-only root filesystem
Make the root filesystem immutable where the service only writes to known
volumes:
```yaml
grafana:
read_only: true
tmpfs:
- /tmp
# /var/lib/grafana is already a bind mount — writes go there, not to rootfs
portal:
read_only: true # nginx:alpine static site; add tmpfs for nginx
tmpfs:
- /tmp
- /var/cache/nginx
- /var/run
```
`read_only` is straightforward for `grafana`, `portal`, and `whois`. It is
trickier for `psql`, `kafka`, and `zookeeper` (they write to data volumes but
also expect a writable rootfs in places) — test individually and add `tmpfs`
mounts for any write paths, or skip `read_only` for those and rely on
`cap_drop` + `no-new-privileges`.
### 5.4 Pin and scan images
Images are already version-pinned (`grafana:9.1.7`, `cp-kafka:7.1.1`,
`openbmp/postgres:2.2.1`, etc.) — good. Add periodic vulnerability scanning:
```bash
trivy image openbmp/postgres:2.2.1
trivy image grafana/grafana:9.1.7
```
Note Grafana 9.1.7 is old; review Grafana security advisories and plan an
upgrade path. Track CVEs for the pinned Confluent and OpenBMP images too.
### 5.5 Resource limits
Every service already has a `mem_limit`. For production also set `cpus:` (or
`deploy.resources.limits`) so a runaway query or ingest burst cannot starve
the host — this also mitigates local denial-of-service. See
`docs/production-sizing.md` for target values.
---
## Priority 6 — Authelia / access control
Authelia fronts Grafana (ROADMAP C5). For production:
- Enforce **TOTP / 2FA** for all operator accounts; do not allow `one_factor`
for the Grafana route.
- Set short session timeouts and an inactivity expiry in the Authelia config.
- Use strong, unique passwords; back the user store with your IdP / LDAP if
available rather than the file backend.
- Ensure Authelia's own secrets (`jwt_secret`, `session.secret`,
`storage.encryption_key`) are strong and stored as secrets, not literals.
- Confirm the reverse proxy strips any client-supplied `Remote-User` header
before Authelia sets it — otherwise the auth-proxy trust model is bypassable
(see 1.3).
---
## Quick checklist
- [ ] Rotate all six default credentials; remove literals from compose, move to `.env` / secrets manager
- [ ] Update `openbmp-ds.yml` datasource password to match
- [ ] Firewall BMP port 5000 to router management subnets (`DOCKER-USER` chain)
- [ ] Bind 5432 / 8086 / 4300 to loopback or drop the port mappings
- [ ] Bind Grafana 3000 to loopback; reach it only via Authelia
- [ ] Remove the Kafka `PLAINTEXT_HOST` listener + 9092 mapping (or enable SASL_SSL if external access needed)
- [ ] Create `grafana_ro` least-privilege DB role; repoint the datasource
- [ ] Tighten `pg_hba.conf`; require `scram-sha-256`
- [ ] Enable PostgreSQL `ssl = on` with a server certificate
- [ ] Test removing `privileged: true` from `psql`; replace with specific `cap_add` if needed
- [ ] Add `security_opt: [no-new-privileges:true]` to all services
- [ ] Add `cap_drop: [ALL]` and add back only required capabilities
- [ ] Add `read_only: true` + `tmpfs` to `grafana` / `portal` / `whois`
- [ ] Add `cpus:` limits per service
- [ ] Scan images with `trivy`; plan a Grafana upgrade off 9.1.7
- [ ] Enforce TOTP and short sessions in Authelia

View File

@ -1,59 +1,237 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "OpenBMP navigation hub. Start at the NOC Overview, then drill into the operational dashboards.",
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [
{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}
],
"id": 1,
"links": [],
"liveNow": false,
"panels": [
{
"gridPos": {"h": 6,"w": 24,"x": 0,"y": 0},
"id": 1,
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 0
},
"id": 6,
"links": [],
"options": {
"content": "# OpenBMP\n\nBGP Monitoring Protocol analytics. **Start here → [NOC Overview](/d/obmp-noc-overview/noc-overview)** — the at-a-glance health view.\n\n| Tier | What it covers |\n|------|----------------|\n| **NOC Overview** | Is the network healthy right now? Routers, peers, flaps, churn, RPKI, topology |\n| **Operations** | Router & peer inventory, per-router and per-peer detail, session health |\n| **Routing** | Prefix explorer, top talkers & churn, AS-path, communities, RPKI security |\n| **Link State** | IGP topology, nodes, links, TE & Segment Routing |\n| **L3VPN** | VPNv4/VPNv6 RIB and prefix history |\n| **Telemetry** | gNMI interface utilization, errors, BMP+telemetry correlation |\n| **Reference** | Database schema map, RR Loc-RIB diff |\n\nUse the **OBMP Dashboards** dropdown (top-right) or the panels below to navigate.",
"content": "# OpenBMP\n\n*Select a dashboard*",
"mode": "markdown"
},
"pluginVersion": "9.1.7",
"pluginVersion": "8.5.4",
"type": "text"
},
{
"gridPos": {"h": 16,"w": 8,"x": 0,"y": 6},
"id": 2,
"options": {"maxItems": 100,"showHeadings": false,"showRecentlyViewed": false,"showSearch": true,"showStarred": false,"query": "","tags": []},
"pluginVersion": "9.1.7",
"title": "All Dashboards",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": {
"h": 18,
"w": 4,
"x": 0,
"y": 3
},
"id": 7,
"links": [],
"options": {
"maxItems": 41,
"query": "",
"showHeadings": true,
"showRecentlyViewed": false,
"showSearch": true,
"showStarred": false,
"tags": [
"obmp-tops"
]
},
"pluginVersion": "8.5.4",
"tags": [],
"title": "Tops",
"type": "dashlist"
},
{
"gridPos": {"h": 16,"w": 8,"x": 8,"y": 6},
"id": 3,
"options": {"maxItems": 20,"showHeadings": false,"showRecentlyViewed": true,"showSearch": false,"showStarred": false,"query": "","tags": []},
"pluginVersion": "9.1.7",
"title": "Recently Viewed",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": {
"h": 18,
"w": 5,
"x": 4,
"y": 3
},
"id": 8,
"links": [],
"options": {
"maxItems": 41,
"query": "",
"showHeadings": true,
"showRecentlyViewed": false,
"showSearch": true,
"showStarred": false,
"tags": [
"obmp-base"
]
},
"pluginVersion": "8.5.4",
"tags": [],
"title": "Base",
"type": "dashlist"
},
{
"gridPos": {"h": 16,"w": 8,"x": 16,"y": 6},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": {
"h": 18,
"w": 5,
"x": 9,
"y": 3
},
"id": 4,
"options": {"maxItems": 20,"showHeadings": false,"showRecentlyViewed": false,"showSearch": false,"showStarred": true,"query": "","tags": []},
"pluginVersion": "9.1.7",
"title": "Starred",
"links": [],
"options": {
"maxItems": 41,
"query": "",
"showHeadings": true,
"showRecentlyViewed": false,
"showSearch": true,
"showStarred": false,
"tags": [
"obmp-history"
]
},
"pluginVersion": "8.5.4",
"tags": [],
"title": "Prefix History",
"type": "dashlist"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": {
"h": 18,
"w": 5,
"x": 14,
"y": 3
},
"id": 9,
"links": [],
"options": {
"maxItems": 41,
"query": "",
"showHeadings": true,
"showRecentlyViewed": false,
"showSearch": true,
"showStarred": false,
"tags": [
"obmp-l3vpn"
]
},
"pluginVersion": "8.5.4",
"tags": [],
"title": "L3VPN",
"type": "dashlist"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": {
"h": 18,
"w": 5,
"x": 19,
"y": 3
},
"id": 3,
"links": [],
"options": {
"maxItems": 41,
"query": "",
"showHeadings": true,
"showRecentlyViewed": false,
"showSearch": true,
"showStarred": false,
"tags": [
"obmp-linkstate"
]
},
"pluginVersion": "8.5.4",
"tags": [],
"title": "Link State",
"type": "dashlist"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","obmp-nav"],
"templating": {"list": []},
"time": {"from": "now-6h","to": "now"},
"timepicker": {},
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "",
"title": "OBMP-Home",
"uid": "obmp-home",
"version": 2,
"version": 1,
"weekStart": ""
}

View File

@ -1,207 +0,0 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "Network-operations overview — answers 'is the network healthy right now?' at a glance. Counts come from stats_* aggregate tables so the dashboard stays fast at production scale.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}
],
"liveNow": true,
"panels": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Total routers reporting BMP to the collector.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 3,"x": 0,"y": 0},
"id": 1,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"Routers\" FROM routers","refId": "A"}],
"title": "Routers Monitored",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Routers whose BMP session is not up. Should be 0.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "red","value": 1}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 3,"x": 3,"y": 0},
"id": 2,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"Routers Down\" FROM routers WHERE state != 'up'","refId": "A"}],
"title": "Routers Down",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP peers currently up (pre-policy Adj-RIB-In sessions).",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 3,"x": 6,"y": 0},
"id": 3,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"Peers Up\" FROM bgp_peers WHERE isprepolicy = true AND state = 'up'","refId": "A"}],
"title": "Peers Up",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP peers that went down within the selected time range. Investigate any non-zero value. (Removed/decommissioned peers fall outside the range and are not counted.)",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "red","value": 1}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 3,"x": 9,"y": 0},
"id": 4,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"Peers Down\" FROM bgp_peers WHERE isprepolicy = true AND state != 'up' AND $__timeFilter(timestamp)","refId": "A"}],
"title": "Peers Down",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Peer session down-events in the last hour. Sustained flapping needs investigation.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 5}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 3,"x": 12,"y": 0},
"id": 5,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"Flaps (1h)\" FROM peer_event_log WHERE state = 'down' AND timestamp > NOW() - INTERVAL '1 hour'","refId": "A"}],
"title": "Flap Events (1h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Total BGP updates across all peers in the last 5 minutes (from stats_chg_bypeer).",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 3,"x": 15,"y": 0},
"id": 6,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, COALESCE(SUM(updates),0) AS \"RIB Updates (5m)\" FROM stats_chg_bypeer WHERE interval_time > NOW() - INTERVAL '5 minutes'","refId": "A"}],
"title": "RIB Updates (5m)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Routes whose origin AS conflicts with a covering ROA (RPKI-invalid). Potential hijacks or misconfigurations.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "red","value": 1}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 3,"x": 18,"y": 0},
"id": 7,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"RPKI Invalid\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\n AND EXISTS (SELECT 1 FROM rpki_validator rv WHERE rv.prefix >>= r.prefix AND rv.origin_as != ba.origin_as)\n AND NOT EXISTS (SELECT 1 FROM rpki_validator rv WHERE rv.prefix >>= r.prefix AND rv.origin_as = ba.origin_as AND r.prefix_len <= rv.prefix_len_max)","refId": "A"}],
"title": "RPKI Invalid Routes",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP-LS link and node changes in the last hour. A spike indicates topology instability.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 20}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 3,"x": 21,"y": 0},
"id": 8,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time,\n (SELECT count(*) FROM ls_links_log WHERE timestamp > NOW() - INTERVAL '1 hour')\n + (SELECT count(*) FROM ls_nodes_log WHERE timestamp > NOW() - INTERVAL '1 hour') AS \"LS Changes (1h)\"","refId": "A"}],
"title": "LS Topology Changes (1h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Per-peer session state over the selected range. Any gap is a flap.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"custom": {"fillOpacity": 70,"lineWidth": 0,"spanNulls": false},
"mappings": [{"options": {"0": {"color": "red","index": 1,"text": "DOWN"},"1": {"color": "green","index": 0,"text": "UP"}},"type": "value"}],
"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null},{"color": "green","value": 1}]}
}
},
"gridPos": {"h": 9,"w": 12,"x": 0,"y": 4},
"id": 9,
"options": {"alignValue": "left","legend": {"displayMode": "list","placement": "bottom","showLegend": false},"mergeValues": true,"rowHeight": 0.9,"showValue": "never","tooltip": {"mode": "single"}},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT\n $__timeGroupAlias(e.timestamp,'1m'),\n COALESCE(p.name, p.peer_addr::text) AS metric,\n CASE WHEN e.state = 'up' THEN 1 ELSE 0 END AS \"value\"\nFROM peer_event_log e\nJOIN bgp_peers p ON p.hash_id = e.peer_hash_id\nWHERE $__timeFilter(e.timestamp)\nORDER BY 1, 2","refId": "A"}],
"title": "Peer Session State",
"type": "state-timeline"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP update vs withdraw rate across all peers (from stats_chg_bypeer).",
"fieldConfig": {
"defaults": {"color": {"mode": "palette-classic"},"custom": {"axisCenteredZero": false,"axisColorMode": "text","axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 20,"gradientMode": "none","lineInterpolation": "smooth","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": false,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},"unit": "short"},
"overrides": [{"matcher": {"id": "byName","options": "Withdraws"},"properties": [{"id": "color","value": {"fixedColor": "red","mode": "fixed"}}]},{"matcher": {"id": "byName","options": "Updates"},"properties": [{"id": "color","value": {"fixedColor": "green","mode": "fixed"}}]}]
},
"gridPos": {"h": 9,"w": 12,"x": 12,"y": 4},
"id": 10,
"options": {"legend": {"calcs": ["sum"],"displayMode": "table","placement": "bottom","showLegend": true},"tooltip": {"mode": "multi","sort": "none"}},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT\n $__timeGroupAlias(interval_time,'5m'),\n SUM(updates) AS \"Updates\",\n SUM(withdraws) AS \"Withdraws\"\nFROM stats_chg_bypeer\nWHERE $__timeFilter(interval_time)\nGROUP BY 1\nORDER BY 1","refId": "A"}],
"title": "BGP Update Rate",
"type": "timeseries"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Peers that went down within the selected time range. Empty is healthy. Widen the time range to see longer-standing issues. Click a peer to open Peer Detail.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [
{"matcher": {"id": "byName","options": "State"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 0,"text": "DOWN"}},"type": "value"}]}]},
{"matcher": {"id": "byName","options": "Peer"},"properties": [{"id": "links","value": [{"title": "Open Peer Detail","url": "/d/obmp-peer-detail/peer-detail?var-peer_hash=${__data.fields[\"peer_hash_id\"]}"}]}]},
{"matcher": {"id": "byName","options": "peer_hash_id"},"properties": [{"id": "custom.hidden","value": true}]}
]
},
"gridPos": {"h": 9,"w": 12,"x": 0,"y": 13},
"id": 11,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "Last Change"}]},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n p.hash_id AS peer_hash_id,\n COALESCE(p.name, p.peer_addr::text) AS \"Peer\",\n p.peer_addr AS \"Address\",\n p.peer_as AS \"AS\",\n p.state AS \"State\",\n p.timestamp AS \"Last Change\",\n p.error_text AS \"Reason\"\nFROM bgp_peers p\nWHERE p.isprepolicy = true AND p.state != 'up' AND $__timeFilter(p.timestamp)\nORDER BY p.timestamp DESC","refId": "A"}],
"title": "Peers Down",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Most-churned prefixes in the last hour (from stats_chg_byprefix). Click a prefix to open Prefix Explorer.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [
{"matcher": {"id": "byName","options": "Total Changes"},"properties": [{"id": "custom.displayMode","value": "gradient-gauge"},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 50},{"color": "red","value": 500}]}}]},
{"matcher": {"id": "byName","options": "Prefix"},"properties": [{"id": "links","value": [{"title": "Open in Prefix Explorer","url": "/d/prefix-hist/prefix-explorer?var-prefix=${__value.text}"}]}]}
]
},
"gridPos": {"h": 9,"w": 12,"x": 12,"y": 13},
"id": 12,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "Total Changes"}]},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n (host(prefix) || '/' || prefix_len) AS \"Prefix\",\n SUM(updates) AS \"Updates\",\n SUM(withdraws) AS \"Withdraws\",\n SUM(updates + withdraws) AS \"Total Changes\"\nFROM stats_chg_byprefix\nWHERE interval_time > NOW() - INTERVAL '1 hour'\nGROUP BY prefix, prefix_len\nORDER BY \"Total Changes\" DESC\nLIMIT 25","refId": "A"}],
"title": "Top Churning Prefixes (1h)",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Routes whose observed origin AS conflicts with a covering ROA — potential hijacks or leaks.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [{"matcher": {"id": "byName","options": "Status"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"Invalid": {"color": "red","index": 0}},"type": "value"}]}]}]
},
"gridPos": {"h": 9,"w": 12,"x": 0,"y": 22},
"id": 13,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n r.prefix AS \"Prefix\",\n ba.origin_as AS \"Observed Origin AS\",\n rv.origin_as AS \"Authorized AS (ROA)\",\n 'Invalid' AS \"Status\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nJOIN rpki_validator rv ON rv.prefix >>= r.prefix AND rv.origin_as != ba.origin_as\nWHERE r.iswithdrawn = false AND r.isipv4 = true\n AND NOT EXISTS (SELECT 1 FROM rpki_validator rv2 WHERE rv2.prefix >>= r.prefix AND rv2.origin_as = ba.origin_as AND r.prefix_len <= rv2.prefix_len_max)\nORDER BY r.prefix\nLIMIT 50","refId": "A"}],
"title": "RPKI Invalid Routes — Potential Hijacks",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Recent BGP-LS link changes — topology churn over the selected range.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [{"matcher": {"id": "byName","options": "Action"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"updated": {"color": "blue","index": 0},"withdrawn": {"color": "orange","index": 1}},"type": "value"}]}]}]
},
"gridPos": {"h": 9,"w": 12,"x": 12,"y": 22},
"id": 14,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "Time"}]},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n timestamp AS \"Time\",\n COALESCE(interface_addr::text, '') AS \"Local\",\n COALESCE(neighbor_addr::text, '') AS \"Neighbor\",\n CASE WHEN iswithdrawn THEN 'withdrawn' ELSE 'updated' END AS \"Action\"\nFROM ls_links_log\nWHERE $__timeFilter(timestamp)\nORDER BY timestamp DESC\nLIMIT 50","refId": "A"}],
"title": "Recent LS Topology Changes",
"type": "table"
}
],
"refresh": "1m",
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","obmp-nav","noc","overview"],
"time": {"from": "now-6h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "NOC Overview",
"uid": "obmp-noc-overview",
"version": 1
}

View File

@ -2,12 +2,7 @@
"uid": "obmp-learn-07",
"title": "Database Schema Map",
"schemaVersion": 39,
"tags": [
"obmp-learning",
"obmp",
"obmp-nav",
"reference"
],
"tags": ["obmp-learning"],
"editable": true,
"time": {
"from": "now-6h",
@ -21,23 +16,12 @@
"id": 1,
"title": "Table Row Counts",
"type": "table",
"gridPos": {
"h": 12,
"w": 8,
"x": 0,
"y": 0
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 12, "w": 8, "x": 0, "y": 0 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"refId": "A",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"rawSql": "SELECT 'routers' as table_name, count(*) as rows FROM routers\nUNION ALL SELECT 'collectors', count(*) FROM collectors\nUNION ALL SELECT 'bgp_peers', count(*) FROM bgp_peers\nUNION ALL SELECT 'peer_event_log', count(*) FROM peer_event_log\nUNION ALL SELECT 'base_attrs', count(*) FROM base_attrs\nUNION ALL SELECT 'ip_rib', count(*) FROM ip_rib\nUNION ALL SELECT 'ip_rib_log', count(*) FROM ip_rib_log\nUNION ALL SELECT 'l3vpn_rib', count(*) FROM l3vpn_rib\nUNION ALL SELECT 'global_ip_rib', count(*) FROM global_ip_rib\nUNION ALL SELECT 'ls_nodes', count(*) FROM ls_nodes\nUNION ALL SELECT 'ls_links', count(*) FROM ls_links\nUNION ALL SELECT 'ls_prefixes', count(*) FROM ls_prefixes\nUNION ALL SELECT 'ls_nodes_log', count(*) FROM ls_nodes_log\nUNION ALL SELECT 'ls_links_log', count(*) FROM ls_links_log\nUNION ALL SELECT 'ls_prefixes_log', count(*) FROM ls_prefixes_log\nUNION ALL SELECT 'rpki_validator', count(*) FROM rpki_validator\nUNION ALL SELECT 'info_asn', count(*) FROM info_asn\nUNION ALL SELECT 'info_route', count(*) FROM info_route\nUNION ALL SELECT 'stat_reports', count(*) FROM stat_reports\nUNION ALL SELECT 'geo_ip', count(*) FROM geo_ip\nORDER BY table_name",
"format": "table"
}
@ -47,27 +31,17 @@
"id": 2,
"title": "Table Relationships",
"type": "text",
"gridPos": {
"h": 12,
"w": 8,
"x": 8,
"y": 0
},
"gridPos": { "h": 12, "w": 8, "x": 8, "y": 0 },
"options": {
"mode": "markdown",
"content": "## Entity Relationships\n\n### BMP Core Chain\n```\ncollectors\n \u2514\u2500\u2500 routers (collector_hash_id)\n \u2514\u2500\u2500 bgp_peers (router_hash_id)\n \u251c\u2500\u2500 ip_rib (peer_hash_id)\n \u251c\u2500\u2500 ip_rib_log (peer_hash_id)\n \u251c\u2500\u2500 l3vpn_rib (peer_hash_id)\n \u251c\u2500\u2500 ls_nodes (peer_hash_id)\n \u251c\u2500\u2500 ls_links (peer_hash_id)\n \u251c\u2500\u2500 ls_prefixes (peer_hash_id)\n \u251c\u2500\u2500 peer_event_log (peer_hash_id)\n \u2514\u2500\u2500 stat_reports (peer_hash_id)\n```\n\n### Path Attributes\n```\nip_rib \u2500\u2500(base_attr_hash_id)\u2500\u2500\u25ba base_attrs\n \u2502 \u251c\u2500\u2500 as_path (bigint[])\n \u2502 \u251c\u2500\u2500 origin_as\n \u2502 \u251c\u2500\u2500 next_hop\n \u2502 \u251c\u2500\u2500 med / local_pref\n \u2502 \u251c\u2500\u2500 community_list[]\n \u2502 \u251c\u2500\u2500 ext_community_list[]\n \u2502 \u2514\u2500\u2500 large_community_list[]\n \u2502\n \u2514\u2500\u2500(prefix)\u2500\u2500\u25ba global_ip_rib\n \u251c\u2500\u2500 rpki_origin_as\n \u251c\u2500\u2500 irr_origin_as\n \u2514\u2500\u2500 num_peers\n```\n\n### Link-State Topology\n```\nls_nodes \u25c4\u2500\u2500 ls_links (local_node_hash_id, remote_node_hash_id)\nls_nodes \u25c4\u2500\u2500 ls_prefixes (local_node_hash_id)\n```\n\n### Reference Data\n```\nrpki_validator \u2500\u2500(prefix, origin_as)\u2500\u2500\u25ba validates ip_rib\ninfo_asn \u2500\u2500(asn)\u2500\u2500\u25ba enriches base_attrs.origin_as\ninfo_route \u2500\u2500(prefix)\u2500\u2500\u25ba enriches ip_rib.prefix\ngeo_ip \u2500\u2500(ip)\u2500\u2500\u25ba geolocates routers, peers\n```"
"content": "## Entity Relationships\n\n### BMP Core Chain\n```\ncollectors\n └── routers (collector_hash_id)\n └── bgp_peers (router_hash_id)\n ├── ip_rib (peer_hash_id)\n ├── ip_rib_log (peer_hash_id)\n ├── l3vpn_rib (peer_hash_id)\n ├── ls_nodes (peer_hash_id)\n ├── ls_links (peer_hash_id)\n ├── ls_prefixes (peer_hash_id)\n ├── peer_event_log (peer_hash_id)\n └── stat_reports (peer_hash_id)\n```\n\n### Path Attributes\n```\nip_rib ──(base_attr_hash_id)──► base_attrs\n │ ├── as_path (bigint[])\n │ ├── origin_as\n │ ├── next_hop\n │ ├── med / local_pref\n │ ├── community_list[]\n │ ├── ext_community_list[]\n │ └── large_community_list[]\n │\n └──(prefix)──► global_ip_rib\n ├── rpki_origin_as\n ├── irr_origin_as\n └── num_peers\n```\n\n### Link-State Topology\n```\nls_nodes ◄── ls_links (local_node_hash_id, remote_node_hash_id)\nls_nodes ◄── ls_prefixes (local_node_hash_id)\n```\n\n### Reference Data\n```\nrpki_validator ──(prefix, origin_as)──► validates ip_rib\ninfo_asn ──(asn)──► enriches base_attrs.origin_as\ninfo_route ──(prefix)──► enriches ip_rib.prefix\ngeo_ip ──(ip)──► geolocates routers, peers\n```"
}
},
{
"id": 3,
"title": "BMP Core Tables",
"type": "text",
"gridPos": {
"h": 8,
"w": 8,
"x": 16,
"y": 0
},
"gridPos": { "h": 8, "w": 8, "x": 16, "y": 0 },
"options": {
"mode": "markdown",
"content": "## BMP Core Tables\n\n| Table | Purpose | Key Columns |\n|-------|---------|-------------|\n| **routers** | BMP-monitored routers | hash_id, name, ip_address, router_as, state, bgp_id |\n| **collectors** | BMP collector instances | hash_id, admin_id, name, ip_address, router_count |\n| **bgp_peers** | BGP sessions per router | hash_id, router_hash_id, peer_addr, peer_as, state, isl3vpnpeer |\n| **peer_event_log** | Session state history (TimescaleDB) | peer_hash_id, state, timestamp, bmp_reason, bgp_err_code |\n| **stat_reports** | BMP statistics messages | peer_hash_id, prefixes_rejected, num_routes_adj_rib_in, num_routes_local_rib |\n| **users** | Access control | username, password, type (admin/oper) |"
@ -77,12 +51,7 @@
"id": 4,
"title": "RIB & Path Attribute Tables",
"type": "text",
"gridPos": {
"h": 8,
"w": 8,
"x": 16,
"y": 8
},
"gridPos": { "h": 8, "w": 8, "x": 16, "y": 8 },
"options": {
"mode": "markdown",
"content": "## RIB & Path Attribute Tables\n\n| Table | Purpose | Key Columns |\n|-------|---------|-------------|\n| **base_attrs** | BGP path attributes | hash_id, as_path[], as_path_count, origin_as, next_hop, med, local_pref, community_list[], ext_community_list[], large_community_list[], cluster_list, originator_id |\n| **ip_rib** | IPv4/IPv6 unicast RIB | hash_id, peer_hash_id, prefix, prefix_len, origin_as, iswithdrawn, labels, path_id |\n| **ip_rib_log** | RIB change history (TimescaleDB) | peer_hash_id, prefix, prefix_len, origin_as, iswithdrawn, timestamp |\n| **l3vpn_rib** | L3VPN/MPLS VPN routes | hash_id, peer_hash_id, rd, prefix, labels, ext_community_list[] |\n| **l3vpn_rib_log** | L3VPN change history (TimescaleDB) | peer_hash_id, rd, prefix, iswithdrawn, timestamp |\n| **global_ip_rib** | Aggregated prefix summary | prefix, recv_origin_as, rpki_origin_as, irr_origin_as, num_peers |"
@ -92,12 +61,7 @@
"id": 5,
"title": "Link-State Tables",
"type": "text",
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 12
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 12 },
"options": {
"mode": "markdown",
"content": "## Link-State Tables (BGP-LS / RFC 7752)\n\n| Table | Purpose | Key Columns |\n|-------|---------|-------------|\n| **ls_nodes** | IS-IS/OSPF nodes | hash_id, peer_hash_id, igp_router_id, name, protocol, asn, sr_capabilities, isis_area_id |\n| **ls_links** | IS-IS/OSPF links + TE/SR | hash_id, local/remote_node_hash_id, interface_addr, neighbor_addr, igp_metric, **te_def_metric**, **max_link_bw**, **max_resv_bw**, **unreserved_bw**, **admin_group**, **srlg**, **sr_adjacency_sids**, **peer_node_sid**, **protection_type**, **mpls_proto_mask** |\n| **ls_prefixes** | IS-IS/OSPF prefixes | hash_id, local_node_hash_id, prefix, metric, sr_prefix_sids, igp_flags |\n| **ls_nodes_log** | Node change history (TimescaleDB) | Same as ls_nodes + timestamp |\n| **ls_links_log** | Link change history (TimescaleDB) | Same as ls_links + timestamp |\n| **ls_prefixes_log** | Prefix change history (TimescaleDB) | Same as ls_prefixes + timestamp |\n\n**Bold columns** = TE/SR fields not used by any existing dashboard"
@ -107,12 +71,7 @@
"id": 6,
"title": "Statistics Tables",
"type": "text",
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 12
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 12 },
"options": {
"mode": "markdown",
"content": "## Statistics Tables (TimescaleDB Hypertables)\n\n| Table | Purpose | Key Columns |\n|-------|---------|-------------|\n| **stat_reports** | BMP stat messages | peer_hash_id, prefixes_rejected, known_dup_prefixes, num_routes_adj_rib_in |\n| **stats_chg_byprefix** | Per-prefix churn stats | interval_time, peer_hash_id, prefix, updates, withdraws |\n| **stats_chg_byasn** | Per-ASN churn stats | interval_time, peer_hash_id, origin_as, updates, withdraws |\n| **stats_chg_bypeer** | Per-peer churn stats | interval_time, peer_hash_id, updates, withdraws |\n| **stats_peer_rib** | Per-peer RIB size | interval_time, peer_hash_id, v4_prefixes, v6_prefixes |\n| **stats_peer_update_counts** | Update rate statistics | interval_time, peer_hash_id, advertise_avg/min/max, withdraw_avg/min/max |\n| **stats_ip_origins** | Per-ASN prefix counts | interval_time, asn, v4_prefixes, v6_prefixes, v4_with_rpki, v4_with_irr |"
@ -122,12 +81,7 @@
"id": 7,
"title": "Reference & Enrichment Tables",
"type": "text",
"gridPos": {
"h": 6,
"w": 12,
"x": 0,
"y": 20
},
"gridPos": { "h": 6, "w": 12, "x": 0, "y": 20 },
"options": {
"mode": "markdown",
"content": "## Reference & Enrichment Tables\n\n| Table | Purpose | Key Columns |\n|-------|---------|-------------|\n| **rpki_validator** | RPKI ROAs | prefix, prefix_len, prefix_len_max, origin_as |\n| **info_asn** | ASN WHOIS/IRR data | asn, as_name, org_name, country, source |\n| **info_route** | Route IRR data | prefix, prefix_len, origin_as, descr, source |\n| **geo_ip** | IP geolocation (DB-IP) | ip, country, city, latitude, longitude, isp_name |\n| **pdb_exchange_peers** | PeeringDB IXP data | ix_name, peer_name, peer_asn, speed, peer_ipv4/ipv6 |"
@ -137,12 +91,7 @@
"id": 8,
"title": "Views Quick Reference",
"type": "text",
"gridPos": {
"h": 6,
"w": 12,
"x": 12,
"y": 20
},
"gridPos": { "h": 6, "w": 12, "x": 12, "y": 20 },
"options": {
"mode": "markdown",
"content": "## Database Views\n\n| View | Joins | Purpose |\n|------|-------|---------|\n| **v_peers** | bgp_peers + routers + info_asn | Complete peer info with router name and ASN details |\n| **v_ip_routes** | ip_rib + bgp_peers + base_attrs + routers | Full route detail with path attributes |\n| **v_ip_routes_geo** | v_ip_routes + geo_ip | Routes with geolocation |\n| **v_ip_routes_history** | ip_rib_log + base_attrs + bgp_peers + routers | Historical route changes with attributes |\n| **v_l3vpn_routes** | l3vpn_rib + bgp_peers + base_attrs + routers | L3VPN routes with path attributes |\n| **v_l3vpn_routes_history** | l3vpn_rib_log + base_attrs + bgp_peers + routers | Historical L3VPN changes |\n| **v_ls_nodes** | ls_nodes + base_attrs + bgp_peers + routers | Link-state nodes with peer/router info |\n| **v_ls_links** | ls_links + ls_nodes(x2) + routers | Links with local/remote node names + TE fields |\n| **v_ls_prefixes** | ls_prefixes + ls_nodes + routers | LS prefixes with originating node info |\n\n### Enum Types\n- **opstate**: up, down\n- **ls_proto**: IS-IS_L1, IS-IS_L2, OSPFv2, OSPFv3, Direct, Static\n- **ospf_route_type**: Intra, Inter, Ext-1, Ext-2, NSSA-1, NSSA-2\n- **ls_mpls_proto_mask**: MPLS protocol bitmask"
@ -152,23 +101,12 @@
"id": 9,
"title": "LinkState Column Details",
"type": "table",
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 26
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 10, "w": 12, "x": 0, "y": 26 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"refId": "A",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"rawSql": "SELECT column_name, data_type, \n CASE \n WHEN column_name IN ('admin_group','max_link_bw','max_resv_bw','unreserved_bw','te_def_metric','protection_type','srlg','sr_adjacency_sids','peer_node_sid','mpls_proto_mask') THEN 'TE/SR'\n WHEN column_name IN ('hash_id','peer_hash_id','base_attr_hash_id','local_node_hash_id','remote_node_hash_id') THEN 'FK/Key'\n ELSE 'Core'\n END as category\nFROM information_schema.columns \nWHERE table_name = 'ls_links' AND table_schema = 'public'\nORDER BY ordinal_position",
"format": "table"
}
@ -178,40 +116,16 @@
"id": 10,
"title": "ip_rib Column Details",
"type": "table",
"gridPos": {
"h": 10,
"w": 12,
"x": 12,
"y": 26
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 10, "w": 12, "x": 12, "y": 26 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"refId": "A",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"rawSql": "SELECT column_name, data_type,\n CASE \n WHEN column_name IN ('hash_id','peer_hash_id','base_attr_hash_id') THEN 'FK/Key'\n ELSE 'Core'\n END as category\nFROM information_schema.columns \nWHERE table_name = 'ip_rib' AND table_schema = 'public'\nORDER BY ordinal_position",
"format": "table"
}
]
}
],
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
]
}

View File

@ -0,0 +1,160 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "AS path length distribution and analysis. Teaches how BGP AS paths reflect internet topology and how to detect anomalies like route leaks or AS path prepending.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Internet routes typically have 2-5 hops. A /32 or /24 appearing with only 1-hop AS path from an unexpected ASN is a classic hijack indicator. Routes with 10+ hops may indicate prepending.",
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {"fillOpacity": 80,"gradientMode": "none","lineWidth": 0},
"unit": "short"
}
},
"gridPos": {"h": 10,"w": 12,"x": 0,"y": 0},
"id": 1,
"options": {"barRadius": 0,"barWidth": 0.7,"groupWidth": 0.7,"legend": {"calcs": [],"displayMode": "list","placement": "bottom"},"orientation": "auto","tooltip": {"mode": "single"},"xTickLabelRotation": 0,"xTickLabelSpacing": 200},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n ba.as_path_count AS \"AS Path Length (hops)\",\n COUNT(*) AS \"Prefix Count\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false\n AND r.isipv4 = true\n AND ba.as_path_count > 0\nGROUP BY ba.as_path_count\nORDER BY ba.as_path_count",
"refId": "A"
}
],
"title": "AS Path Length Distribution (Active IPv4 Routes)",
"type": "barchart"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Average AS path length on the internet is ~4-5 hops. Your lab has shorter paths since ExaBGP is a single eBGP hop away.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 5},{"color": "red","value": 8}]},
"unit": "short",
"decimals": 1
}
},
"gridPos": {"h": 5,"w": 6,"x": 12,"y": 0},
"id": 2,
"options": {"colorMode": "value","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n ROUND(AVG(ba.as_path_count)::numeric, 1) AS \"Avg AS Path Length\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true AND ba.as_path_count > 0",
"refId": "A"
}
],
"title": "Average AS Path Length",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Routes with only 1-hop AS path are directly connected or possibly hijacked. In your lab, ExaBGP injects routes starting with AS 65100.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 5},{"color": "red","value": 20}]},
"unit": "short"
}
},
"gridPos": {"h": 5,"w": 6,"x": 18,"y": 0},
"id": 3,
"options": {"colorMode": "value","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n COUNT(*) AS \"Direct (1-hop) Routes\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true AND ba.as_path_count = 1",
"refId": "A"
}
],
"title": "1-Hop Routes (Direct/Possible Hijack)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: The longest paths reveal the most AS-level hops in your network. AS path prepending intentionally lengthens paths to make a route less preferred.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [
{"matcher": {"id": "byName","options": "AS Path Length"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 5},{"color": "red","value": 10}]}}]},
{"matcher": {"id": "byName","options": "AS Path"},"properties": [{"id": "custom.width","value": 400}]}
]
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 10},
"id": 4,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "AS Path Length"}]},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n r.prefix AS \"Prefix\",\n ba.as_path_count AS \"AS Path Length\",\n ba.as_path::text AS \"AS Path\",\n ba.origin_as AS \"Origin AS\",\n ba.next_hop AS \"Next Hop\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nORDER BY ba.as_path_count DESC\nLIMIT 30",
"refId": "A"
}
],
"title": "Longest AS Paths (Top 30)",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Origin AS is the rightmost ASN in the AS path — the network that first originated the prefix. Most internet prefixes are originated by their owning organization.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [
{"matcher": {"id": "byName","options": "Route Count"},"properties": [{"id": "custom.displayMode","value": "lcd-gauge"},{"id": "custom.width","value": 200}]}
]
},
"gridPos": {"h": 12,"w": 12,"x": 0,"y": 20},
"id": 5,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "Route Count"}]},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n ba.origin_as AS \"Origin AS\",\n COALESCE(ia.as_name, 'Unknown') AS \"AS Name\",\n COUNT(*) AS \"Route Count\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nLEFT JOIN info_asn ia ON ia.asn = ba.origin_as\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nGROUP BY ba.origin_as, ia.as_name\nORDER BY COUNT(*) DESC\nLIMIT 20",
"refId": "A"
}
],
"title": "Top Origin ASNs by Route Count",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: A transit AS (appearing frequently in AS paths but not as origin) is a carrier. The most frequent transit ASNs in your lab correspond to simulated Tier-1 carriers (174=Cogent, 3356=Lumen, 1299=Telia, etc.)",
"fieldConfig": {
"defaults": {"color": {"mode": "palette-classic"},"custom": {"fillOpacity": 80,"lineWidth": 0},"unit": "short"}
},
"gridPos": {"h": 12,"w": 12,"x": 12,"y": 20},
"id": 6,
"options": {"barRadius": 0,"barWidth": 0.7,"groupWidth": 0.7,"legend": {"calcs": [],"displayMode": "list","placement": "bottom"},"orientation": "horizontal","tooltip": {"mode": "single"},"xTickLabelRotation": 0,"xTickLabelSpacing": 200},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n asn_val AS \"Transit ASN\",\n COUNT(*) AS \"Appearances in AS Paths\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nCROSS JOIN LATERAL unnest(ba.as_path) AS asn_val\nWHERE r.iswithdrawn = false AND asn_val != ba.origin_as\nGROUP BY asn_val\nORDER BY COUNT(*) DESC\nLIMIT 15",
"refId": "A"
}
],
"title": "Most Common Transit ASNs",
"type": "barchart"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","learning","bgp","as-path","topology"],
"time": {"from": "now-1h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "AS Path Analysis",
"uid": "obmp-learn-03",
"version": 1
}

View File

@ -0,0 +1,201 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","target": {"limit": 100,"matchAny": false,"tags": [],"type": "dashboard"},"type": "dashboard"}]},
"description": "Explore BGP path attributes: communities, MED, local-pref and how they influence routing policy decisions.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"datasource": {"type": "datasource","uid": "grafana"},
"gridPos": {"h": 8,"w": 24,"x": 0,"y": 0},
"id": 1,
"options": {
"content": "## BGP Path Attributes — What They Mean\n\n### BGP Communities (RFC 1997)\nCommunities are 32-bit tags attached to routes, written as **ASN:value** (e.g., `65000:100`). They carry policy signals between routers and ASes.\n\n**Well-known communities:**\n| Community | Decimal | Meaning |\n|-----------|---------|----------|\n| `65535:0` | NO_EXPORT | Do not advertise outside this AS or confederation |\n| `65535:1` | NO_ADVERTISE | Do not advertise to any peer |\n| `65535:666` | BLACKHOLE | Drop traffic destined for this prefix (RFC 7999) |\n\nPrivate communities (e.g., `65001:200`) are operator-defined — they may encode region, customer tier, or traffic-engineering intent.\n\n### Local Preference (local-pref)\n- **Scope:** iBGP only — never sent to eBGP peers.\n- **Effect:** Higher local-pref wins. Default is **100**.\n- **Use case:** Prefer one upstream provider over another for all outbound traffic.\n\n### Multi-Exit Discriminator (MED)\n- **Scope:** Sent to directly connected eBGP peers to influence *inbound* traffic.\n- **Effect:** Lower MED wins (when comparing routes from the same AS).\n- **Use case:** Tell a peer which of your links to prefer when sending traffic to you.\n\n> **Tip:** Use the panels below to explore what communities and attributes are actually present in the current RIB. Run `inject.py attributes` to load routes with varied communities and MED values.",
"mode": "markdown"
},
"title": "BGP Attribute Reference — Communities, Local-Pref, MED",
"type": "text"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Each row is a unique community string (format ASN:value) seen across all active routes. High route counts for a community mean many routes share that policy tag. Look for well-known communities: 65535:0 (NO_EXPORT), 65535:1 (NO_ADVERTISE), 65535:666 (BLACKHOLE).",
"fieldConfig": {
"defaults": {"color": {"mode": "thresholds"},"custom": {"align": "auto","displayMode": "auto"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}},
"overrides": [
{"matcher": {"id": "byName","options": "Routes Tagged"},"properties": [{"id": "custom.displayMode","value": "lcd-gauge"},{"id": "color","value": {"mode": "thresholds"}},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "blue","value": null},{"color": "green","value": 10},{"color": "yellow","value": 100}]}}]}
]
},
"gridPos": {"h": 11,"w": 12,"x": 0,"y": 8},
"id": 2,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "Routes Tagged"}]},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n comm AS \"Community\",\n COUNT(*) AS \"Routes Tagged\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nCROSS JOIN LATERAL unnest(ba.community_list) AS comm\nWHERE r.iswithdrawn = false AND ba.community_list IS NOT NULL\nGROUP BY comm\nORDER BY COUNT(*) DESC\nLIMIT 30",
"refId": "A"
}
],
"title": "Top BGP Communities in Current RIB",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Routes with notable BGP attributes — tagged with communities or using non-default local-pref / MED values. These routes carry explicit policy information. Examine the Communities column for operator-defined tags and the Local Pref column to see traffic engineering decisions.",
"fieldConfig": {
"defaults": {"color": {"mode": "thresholds"},"custom": {"align": "auto","displayMode": "auto"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}},
"overrides": [
{"matcher": {"id": "byName","options": "Local Pref"},"properties": [{"id": "custom.displayMode","value": "color-text"},{"id": "color","value": {"mode": "thresholds"}},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 101},{"color": "red","value": 200}]}}]},
{"matcher": {"id": "byName","options": "MED"},"properties": [{"id": "custom.displayMode","value": "color-text"},{"id": "color","value": {"mode": "thresholds"}},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 100}]}}]}
]
},
"gridPos": {"h": 11,"w": 12,"x": 12,"y": 8},
"id": 3,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n r.prefix::text AS \"Prefix\",\n ba.origin_as AS \"Origin AS\",\n ba.community_list::text AS \"Communities\",\n ba.local_pref AS \"Local Pref\",\n ba.med AS \"MED\",\n ba.as_path_count AS \"Path Length\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\n AND (ba.community_list IS NOT NULL OR ba.med IS NOT NULL OR ba.local_pref IS NOT NULL)\nORDER BY r.prefix\nLIMIT 100",
"refId": "A"
}
],
"title": "Routes with Notable Attributes",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: MED (Multi-Exit Discriminator) is used to influence inbound traffic from a directly connected AS. Lower MED is preferred. If most routes show 'Not Set', MED is not being used for traffic engineering. A single dominant MED value means a simple policy; many different values indicate fine-grained control.",
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {"fillOpacity": 80,"lineWidth": 0},
"unit": "short"
}
},
"gridPos": {"h": 9,"w": 12,"x": 0,"y": 19},
"id": 4,
"options": {"barRadius": 0.1,"barWidth": 0.6,"groupWidth": 0.7,"legend": {"displayMode": "list","placement": "bottom"},"orientation": "auto","text": {},"tooltip": {"mode": "single"},"xTickLabelRotation": -30,"xTickLabelSpacing": 100},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n COALESCE(ba.med::text, 'Not Set') AS \"MED Value\",\n COUNT(*) AS \"Route Count\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nGROUP BY ba.med\nORDER BY ba.med NULLS LAST\nLIMIT 20",
"refId": "A"
}
],
"title": "MED Value Distribution",
"type": "barchart"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Local preference is an iBGP attribute — it never crosses AS boundaries. Default is 100. Routes with local-pref above 100 are preferred over the default path; below 100 they are used as last-resort. Non-100 values indicate active traffic-engineering policy. Run 'inject.py attributes' to inject routes with varied local-pref values.",
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {"fillOpacity": 80,"lineWidth": 0},
"unit": "short"
}
},
"gridPos": {"h": 9,"w": 12,"x": 12,"y": 19},
"id": 5,
"options": {"barRadius": 0.1,"barWidth": 0.6,"groupWidth": 0.7,"legend": {"displayMode": "list","placement": "bottom"},"orientation": "auto","text": {},"tooltip": {"mode": "single"},"xTickLabelRotation": -30,"xTickLabelSpacing": 100},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n COALESCE(ba.local_pref::text, 'Not Set') AS \"Local Pref\",\n COUNT(*) AS \"Route Count\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nGROUP BY ba.local_pref\nORDER BY ba.local_pref DESC NULLS LAST\nLIMIT 20",
"refId": "A"
}
],
"title": "Local Preference Value Distribution",
"type": "barchart"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: This count tells you how widely BGP communities are used in your network. A value of 0 means no community tagging — communities are an opt-in feature. Run 'inject.py attributes' to add routes with community strings.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null},{"color": "green","value": 1}]},
"unit": "short",
"mappings": []
}
},
"gridPos": {"h": 5,"w": 8,"x": 0,"y": 28},
"id": 6,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() as time, COUNT(*) AS \"Routes with Communities\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false\n AND ba.community_list IS NOT NULL\n AND array_length(ba.community_list, 1) > 0",
"refId": "A"
}
],
"title": "Routes with Communities",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: The number of distinct community strings seen across all active routes. A diverse set indicates fine-grained policy tagging. A single value means one uniform policy tag is applied.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null},{"color": "green","value": 1},{"color": "yellow","value": 50}]},
"unit": "short",
"mappings": []
}
},
"gridPos": {"h": 5,"w": 8,"x": 8,"y": 28},
"id": 7,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() as time, COUNT(DISTINCT comm) AS \"Unique Communities\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nCROSS JOIN LATERAL unnest(ba.community_list) AS comm\nWHERE r.iswithdrawn = false",
"refId": "A"
}
],
"title": "Unique Community Values",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Routes with a local-pref other than the default (100) have been explicitly policy-engineered. A high count here means your network actively uses local-pref to prefer specific paths. A value of 0 means all paths are at default preference.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 100},{"color": "red","value": 1000}]},
"unit": "short",
"mappings": []
}
},
"gridPos": {"h": 5,"w": 8,"x": 16,"y": 28},
"id": 8,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() as time, COUNT(*) AS \"Custom Local-Pref Routes\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false\n AND ba.local_pref IS NOT NULL\n AND ba.local_pref != 100",
"refId": "A"
}
],
"title": "Routes with Non-Default Local-Pref",
"type": "stat"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","learning","bgp","communities","attributes","policy"],
"time": {"from": "now-1h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "BGP Attribute Explorer",
"uid": "obmp-learn-06",
"version": 1
}

View File

@ -0,0 +1,152 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","target": {"limit": 100,"matchAny": false,"tags": [],"type": "dashboard"},"type": "dashboard"}]},
"description": "Prefix stability analysis and route churn visualization. Teaches how to identify unstable routes and understand BGP churn.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: This chart shows BGP advertisements and withdrawals bucketed per hour. A healthy network has steady low churn. Spikes in withdrawals indicate route instability events — link failures, IBGP reconvergence, or policy changes. Run 'inject.py churn' to generate synthetic churn data and observe it here.",
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {"drawStyle": "bars","fillOpacity": 60,"lineWidth": 1,"spanNulls": false,"stacking": {"group": "A","mode": "none"}},
"unit": "short"
},
"overrides": [
{"matcher": {"id": "byName","options": "Advertisements"},"properties": [{"id": "color","value": {"fixedColor": "green","mode": "fixed"}}]},
{"matcher": {"id": "byName","options": "Withdrawals"},"properties": [{"id": "color","value": {"fixedColor": "red","mode": "fixed"}}]}
]
},
"gridPos": {"h": 9,"w": 24,"x": 0,"y": 0},
"id": 1,
"options": {"legend": {"calcs": ["sum","max"],"displayMode": "list","placement": "bottom"},"tooltip": {"mode": "multi"}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT\n $__timeGroupAlias(timestamp,'1h'),\n SUM(CASE WHEN iswithdrawn = false THEN 1 ELSE 0 END) AS \"Advertisements\",\n SUM(CASE WHEN iswithdrawn = true THEN 1 ELSE 0 END) AS \"Withdrawals\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY 1\nORDER BY 1",
"refId": "A"
}
],
"title": "Advertisements vs Withdrawals Rate (per hour)",
"type": "timeseries"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: A prefix with more than 30 updates per day is considered unstable — it is flapping or being re-announced frequently. The Stability column categorizes each prefix. Run 'inject.py churn' to generate churn data and observe it here. Sort by 'Total Updates' to find the most problematic prefixes.",
"fieldConfig": {
"defaults": {"color": {"mode": "thresholds"},"custom": {"align": "auto","displayMode": "auto"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}},
"overrides": [
{"matcher": {"id": "byName","options": "Stability"},"properties": [{"id": "custom.displayMode","value": "color-text"},{"id": "mappings","value": [{"options": {"Very Stable": {"color": "green","index": 0},"Stable": {"color": "blue","index": 1},"Moderate": {"color": "yellow","index": 2},"Unstable": {"color": "red","index": 3}},"type": "value"}]}]},
{"matcher": {"id": "byName","options": "Total Updates"},"properties": [{"id": "custom.displayMode","value": "lcd-gauge"},{"id": "color","value": {"mode": "thresholds"}},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 7},{"color": "red","value": 30}]}}]}
]
},
"gridPos": {"h": 12,"w": 24,"x": 0,"y": 9},
"id": 2,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "Total Updates"}]},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n prefix::text AS \"Prefix\",\n COUNT(*) AS \"Total Updates\",\n SUM(CASE WHEN iswithdrawn THEN 1 ELSE 0 END) AS \"Withdrawals\",\n SUM(CASE WHEN NOT iswithdrawn THEN 1 ELSE 0 END) AS \"Announcements\",\n MAX(timestamp) AS \"Last Change\",\n CASE\n WHEN COUNT(*) = 1 THEN 'Very Stable'\n WHEN COUNT(*) <= 7 THEN 'Stable'\n WHEN COUNT(*) <= 30 THEN 'Moderate'\n ELSE 'Unstable'\n END AS \"Stability\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY prefix\nORDER BY \"Total Updates\" DESC\nLIMIT 100",
"refId": "A"
}
],
"title": "Top Churning Prefixes",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: This bar chart shows how many prefixes fall into each stability tier. In a healthy network, the vast majority of prefixes should be 'Very Stable' (only announced once during the window). A large 'Unstable' bar is a red flag. Run 'inject.py churn' to shift prefixes into the Unstable tier.",
"fieldConfig": {
"defaults": {
"color": {"mode": "fixed","fixedColor": "blue"},
"custom": {"fillOpacity": 80,"lineWidth": 0},
"unit": "short"
},
"overrides": [
{"matcher": {"id": "byName","options": "1. Very Stable (1 update)"},"properties": [{"id": "color","value": {"fixedColor": "green","mode": "fixed"}}]},
{"matcher": {"id": "byName","options": "2. Stable (2-7 updates)"},"properties": [{"id": "color","value": {"fixedColor": "blue","mode": "fixed"}}]},
{"matcher": {"id": "byName","options": "3. Moderate (8-30 updates)"},"properties": [{"id": "color","value": {"fixedColor": "yellow","mode": "fixed"}}]},
{"matcher": {"id": "byName","options": "4. Unstable (31+ updates)"},"properties": [{"id": "color","value": {"fixedColor": "red","mode": "fixed"}}]}
]
},
"gridPos": {"h": 9,"w": 14,"x": 0,"y": 21},
"id": 3,
"options": {"barRadius": 0.1,"barWidth": 0.6,"groupWidth": 0.7,"legend": {"displayMode": "list","placement": "bottom"},"orientation": "auto","text": {},"tooltip": {"mode": "single"},"xTickLabelRotation": 0,"xTickLabelSpacing": 200},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n CASE\n WHEN cnt = 1 THEN '1. Very Stable (1 update)'\n WHEN cnt <= 7 THEN '2. Stable (2-7 updates)'\n WHEN cnt <= 30 THEN '3. Moderate (8-30 updates)'\n ELSE '4. Unstable (31+ updates)'\n END AS \"Stability Tier\",\n COUNT(*) AS \"Prefix Count\"\nFROM (\n SELECT prefix, COUNT(*) as cnt\n FROM ip_rib_log\n WHERE $__timeFilter(timestamp)\n GROUP BY prefix\n) sub\nGROUP BY 1\nORDER BY 1",
"refId": "A"
}
],
"title": "Prefix Distribution by Stability Tier",
"type": "barchart"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: This is the single most churning prefix in the selected time range. If a prefix appears here repeatedly across time ranges, it may warrant investigation — check the AS path and peers announcing it.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null}]},
"unit": "string",
"mappings": []
}
},
"gridPos": {"h": 5,"w": 10,"x": 14,"y": 21},
"id": 4,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "center","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {"titleSize": 14,"valueSize": 18}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, prefix::text AS \"Most Churned Prefix\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY prefix\nORDER BY COUNT(*) DESC\nLIMIT 1",
"refId": "A"
}
],
"title": "Most Churned Prefix",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: This counts how many distinct prefixes had at least one update event in the selected time window. During a normal steady state this number should be low. After a major routing event (e.g., upstream link failure) you may see thousands of prefixes change simultaneously.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 500},{"color": "red","value": 2000}]},
"unit": "short",
"mappings": []
}
},
"gridPos": {"h": 4,"w": 10,"x": 14,"y": 26},
"id": 5,
"options": {"colorMode": "background","graphMode": "area","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(DISTINCT prefix) AS \"Prefixes with Updates\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)",
"refId": "A"
}
],
"title": "Total Unique Prefixes with Updates",
"type": "stat"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","learning","bgp","churn","stability"],
"time": {"from": "now-24h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "Route Churn & Stability Score",
"uid": "obmp-learn-05",
"version": 1
}

View File

@ -0,0 +1,144 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "BGP peer session health, uptime, and flap analysis. Teaches session stability and how to diagnose flapping peers.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: A healthy BGP mesh shows all peers UP continuously. Any gap in the UP state represents a session flap — investigate the reset reason.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"custom": {"fillOpacity": 70,"lineWidth": 0,"spanNulls": false},
"mappings": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}],
"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null},{"color": "green","value": 1}]}
}
},
"gridPos": {"h": 8,"w": 24,"x": 0,"y": 0},
"id": 1,
"options": {"alignValue": "left","legend": {"displayMode": "list","placement": "bottom"},"mergeValues": true,"rowHeight": 0.9,"showValue": "auto","tooltip": {"mode": "single"}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT\n $__timeGroupAlias(e.timestamp,'1m'),\n COALESCE(p.name, p.peer_addr::text) AS metric,\n CASE WHEN e.state = 'up' THEN 1 ELSE 0 END AS \"value\"\nFROM peer_event_log e\nJOIN bgp_peers p ON p.hash_id = e.peer_hash_id\nWHERE $__timeFilter(e.timestamp)\nORDER BY 1, 2",
"refId": "A"
}
],
"title": "Peer Session State Timeline",
"type": "state-timeline"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Current state of all BGP peers. Learn: 'bmp_reason' tells you why BMP reporting stopped. 'bgp_err_code' shows BGP NOTIFICATION error codes.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [
{"matcher": {"id": "byName","options": "State"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}]}]},
{"matcher": {"id": "byName","options": "Peer"},"properties": [{"id": "custom.width","value": 200}]},
{"matcher": {"id": "byName","options": "AS"},"properties": [{"id": "custom.width","value": 80}]}
]
},
"gridPos": {"h": 12,"w": 24,"x": 0,"y": 8},
"id": 2,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": false,"displayName": "State"}]},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n COALESCE(p.name, p.peer_addr::text) AS \"Peer\",\n p.peer_addr AS \"Address\",\n p.peer_as AS \"AS\",\n p.state AS \"State\",\n p.timestamp AS \"Last State Change\",\n p.error_text AS \"Last Error\",\n p.local_hold_time AS \"Hold Time\"\nFROM bgp_peers p\nWHERE p.isprepolicy = true\nORDER BY p.state, p.peer_addr",
"refId": "A"
}
],
"title": "Current Peer State",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Flap count = number of times a peer went from UP to DOWN. A peer flapping more than 2 times per hour needs investigation.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [
{"matcher": {"id": "byName","options": "Flap Count"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 5}]}}]}
]
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 20},
"id": 3,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "Flap Count"}]},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n COALESCE(p.name, p.peer_addr::text) AS \"Peer\",\n p.peer_addr AS \"Address\",\n p.peer_as AS \"AS\",\n COUNT(CASE WHEN e.state = 'down' THEN 1 END) AS \"Flap Count\",\n MIN(e.timestamp) AS \"First Event\",\n MAX(e.timestamp) AS \"Last Event\"\nFROM peer_event_log e\nJOIN bgp_peers p ON p.hash_id = e.peer_hash_id\nWHERE $__timeFilter(e.timestamp)\nGROUP BY p.name, p.peer_addr, p.peer_as\nORDER BY \"Flap Count\" DESC",
"refId": "A"
}
],
"title": "Peer Flap Analysis",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null},{"color": "yellow","value": 50},{"color": "green","value": 90}]},"unit": "percent","max": 100,"min": 0}},
"gridPos": {"h": 8,"w": 8,"x": 0,"y": 30},
"id": 4,
"options": {"orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"showThresholdLabels": false,"showThresholdMarkers": true,"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n ROUND(100.0 * SUM(CASE WHEN state = 'up' THEN 1 ELSE 0 END) / NULLIF(COUNT(*),0), 1) AS \"Mesh Health %\"\nFROM bgp_peers WHERE isprepolicy = true",
"refId": "A"
}
],
"title": "Overall Peer Mesh Health",
"type": "gauge"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null},{"color": "green","value": 1}]},"unit": "short","mappings": [{"options": {"0": {"color": "red","index": 0,"text": "DOWN"}},"type": "value"}]}},
"gridPos": {"h": 8,"w": 8,"x": 8,"y": 30},
"id": 5,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n SUM(CASE WHEN state = 'up' THEN 1 ELSE 0 END) AS \"Peers UP\"\nFROM bgp_peers WHERE isprepolicy = true",
"refId": "A"
}
],
"title": "Peers Currently UP",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 5}]},"unit": "short"}},
"gridPos": {"h": 8,"w": 8,"x": 16,"y": 30},
"id": 6,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n COUNT(CASE WHEN state = 'down' THEN 1 END) AS \"Flap Events (24h)\"\nFROM peer_event_log\nWHERE timestamp > NOW() - INTERVAL '24 hours' AND state = 'down'",
"refId": "A"
}
],
"title": "Flap Events (24h)",
"type": "stat"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","learning","bgp","peers","flap"],
"time": {"from": "now-24h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "Peer Session Health & Flap Analysis",
"uid": "obmp-learn-02",
"version": 1
}

View File

@ -0,0 +1,150 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "RPKI (Resource Public Key Infrastructure) validation status. Teaches BGP routing security and how RPKI prevents prefix hijacks by validating route origin.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"content": "## What is RPKI?\n\nRPKI (Resource Public Key Infrastructure) is a cryptographic security framework for BGP routing. It lets IP address holders publish **Route Origin Authorizations (ROAs)** stating which ASNs are authorized to originate their prefixes.\n\n### RPKI Validation States\n| State | Meaning |\n|-------|----------|\n| **Valid** | The route's origin AS matches a ROA for this prefix |\n| **Invalid** | A ROA exists but the origin AS or prefix length does NOT match — this route is potentially a hijack |\n| **NotFound** | No ROA exists for this prefix/origin — unprotected, can't be validated |\n\n### How to read this dashboard\n- **Valid %** should be as high as possible (target: 100%)\n- **Invalid routes** are critical — they indicate either a misconfiguration or a prefix hijack\n- Routes with no RPKI data show as **NotFound** — they are not necessarily invalid, just unprotected\n\n> **Lab note:** The RPKI validator table is populated by a cron job in psql-app every 2 hours. If the table shows 0 rows, wait for the cron to run or check `ENABLE_RPKI=1` in docker-compose.yml.",
"datasource": {"type": "datasource","uid": "grafana"},
"gridPos": {"h": 10,"w": 8,"x": 0,"y": 0},
"id": 1,
"options": {"content": "## What is RPKI?\n\nRPKI (Resource Public Key Infrastructure) is a cryptographic security framework for BGP routing. It lets IP address holders publish **Route Origin Authorizations (ROAs)** stating which ASNs are authorized to originate their prefixes.\n\n### RPKI Validation States\n| State | Meaning |\n|-------|----------|\n| **Valid** | The route's origin AS matches a ROA for this prefix |\n| **Invalid** | A ROA exists but the origin AS or prefix length does NOT match — this route is potentially a hijack |\n| **NotFound** | No ROA exists for this prefix/origin — unprotected, can't be validated |\n\n### How to read this dashboard\n- **Valid %** should be as high as possible (target: 100%)\n- **Invalid routes** are critical — they indicate either a misconfiguration or a prefix hijack\n- Routes with no RPKI data show as **NotFound** — they are not necessarily invalid, just unprotected\n\n> **Lab note:** The RPKI validator table is populated by a cron job in psql-app every 2 hours. If the table shows 0 rows, wait for the cron to run or check `ENABLE_RPKI=1` in docker-compose.yml.","mode": "markdown"},
"pluginVersion": "9.1.7",
"title": "RPKI Learning Guide",
"type": "text"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Total ROAs (Route Origin Authorizations) loaded from the RPKI validator. If 0, the cron job has not yet run.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null},{"color": "yellow","value": 1},{"color": "green","value": 100000}]},
"unit": "short"
}
},
"gridPos": {"h": 5,"w": 4,"x": 8,"y": 0},
"id": 2,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(*) AS \"RPKI ROAs Loaded\" FROM rpki_validator",
"refId": "A"
}
],
"title": "RPKI ROAs Loaded",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Routes with a matching valid ROA — origin AS and prefix length both match.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null},{"color": "green","value": 1}]},
"unit": "short"
}
},
"gridPos": {"h": 5,"w": 4,"x": 12,"y": 0},
"id": 3,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(*) AS \"Valid Routes\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nJOIN rpki_validator rv ON rv.prefix >>= r.prefix AND rv.origin_as = ba.origin_as AND r.prefix_len <= rv.prefix_len_max\nWHERE r.iswithdrawn = false AND r.isipv4 = true",
"refId": "A"
}
],
"title": "RPKI Valid Routes",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Routes where a ROA exists but the origin AS does NOT match — high-priority investigation needed.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "red","value": 1}]},
"unit": "short"
}
},
"gridPos": {"h": 5,"w": 4,"x": 16,"y": 0},
"id": 4,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(*) AS \"RPKI Invalid Routes\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\n AND EXISTS (\n SELECT 1 FROM rpki_validator rv\n WHERE rv.prefix >>= r.prefix AND rv.origin_as != ba.origin_as\n )\n AND NOT EXISTS (\n SELECT 1 FROM rpki_validator rv\n WHERE rv.prefix >>= r.prefix AND rv.origin_as = ba.origin_as AND r.prefix_len <= rv.prefix_len_max\n )",
"refId": "A"
}
],
"title": "RPKI Invalid Routes",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: ExaBGP-injected routes (AS 65100) will be NotFound since they use synthetic ASNs not registered in RPKI. Real internet prefixes with valid ROAs will appear as Valid.",
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {"hideFrom": {"legend": false,"tooltip": false,"viz": false}},
"mappings": []
},
"overrides": []
},
"gridPos": {"h": 10,"w": 10,"x": 0,"y": 10},
"id": 5,
"options": {"displayLabels": ["percent","name"],"legend": {"displayMode": "list","placement": "bottom"},"pieType": "donut","tooltip": {"mode": "single"}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n CASE\n WHEN rv_valid.prefix IS NOT NULL THEN 'Valid'\n WHEN rv_any.prefix IS NOT NULL THEN 'Invalid'\n ELSE 'NotFound'\n END AS \"RPKI Status\",\n COUNT(*) AS \"Route Count\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nLEFT JOIN rpki_validator rv_valid\n ON rv_valid.prefix >>= r.prefix AND rv_valid.origin_as = ba.origin_as AND r.prefix_len <= rv_valid.prefix_len_max\nLEFT JOIN rpki_validator rv_any\n ON rv_any.prefix >>= r.prefix AND rv_any.origin_as != ba.origin_as\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nGROUP BY 1\nORDER BY 1",
"refId": "A"
}
],
"title": "RPKI Validation Status Distribution",
"type": "piechart"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Prefixes that have a ROA but the observed origin AS does not match. These are the most security-critical routes — each one represents a potential hijack or misconfiguration.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [
{"matcher": {"id": "byName","options": "Status"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"Invalid": {"color": "red","index": 0},"Valid": {"color": "green","index": 1},"NotFound": {"color": "yellow","index": 2}},"type": "value"}]}]}
]
},
"gridPos": {"h": 14,"w": 14,"x": 10,"y": 10},
"id": 6,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n r.prefix AS \"Prefix\",\n ba.origin_as AS \"Observed Origin AS\",\n rv.origin_as AS \"Authorized Origin AS (ROA)\",\n 'Invalid' AS \"Status\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nJOIN rpki_validator rv ON rv.prefix >>= r.prefix AND rv.origin_as != ba.origin_as\nWHERE r.iswithdrawn = false AND r.isipv4 = true\n AND NOT EXISTS (\n SELECT 1 FROM rpki_validator rv2\n WHERE rv2.prefix >>= r.prefix AND rv2.origin_as = ba.origin_as AND r.prefix_len <= rv2.prefix_len_max\n )\nORDER BY r.prefix\nLIMIT 50",
"refId": "A"
}
],
"title": "RPKI Invalid Routes — Potential Hijacks",
"type": "table"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","learning","bgp","rpki","security"],
"time": {"from": "now-1h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "RPKI Validation Status",
"uid": "obmp-learn-04",
"version": 1
}

View File

@ -0,0 +1,137 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","target": {"limit": 100,"matchAny": false,"tags": [],"type": "dashboard"},"type": "dashboard"}]},
"description": "BGP update and withdrawal rates over time. Teaches what normal BGP traffic looks like and how to detect route churn or instability.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: A healthy network has far more advertisements than withdrawals. A withdrawal spike often signals a link failure or route flap.",
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {"drawStyle": "bars","fillOpacity": 60,"lineWidth": 1,"spanNulls": false,"stacking": {"group": "A","mode": "none"}},
"unit": "short"
},
"overrides": [
{"matcher": {"id": "byName","options": "Advertisements"},"properties": [{"id": "color","value": {"fixedColor": "green","mode": "fixed"}}]},
{"matcher": {"id": "byName","options": "Withdrawals"},"properties": [{"id": "color","value": {"fixedColor": "red","mode": "fixed"}}]}
]
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 0},
"id": 1,
"options": {"legend": {"calcs": ["sum","max"],"displayMode": "list","placement": "bottom"},"tooltip": {"mode": "multi"}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT\n $__timeGroupAlias(timestamp,'5m'),\n SUM(CASE WHEN iswithdrawn = false THEN 1 ELSE 0 END) AS \"Advertisements\",\n SUM(CASE WHEN iswithdrawn = true THEN 1 ELSE 0 END) AS \"Withdrawals\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY 1\nORDER BY 1",
"refId": "A"
}
],
"title": "BGP Updates Over Time — Advertisements vs Withdrawals",
"type": "timeseries"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 100},{"color": "red","value": 1000}]},"unit": "short","mappings": []}},
"gridPos": {"h": 5,"w": 6,"x": 0,"y": 10},
"id": 2,
"options": {"colorMode": "background","graphMode": "area","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(*) AS \"Total Updates (24h)\" FROM ip_rib_log WHERE timestamp > NOW() - INTERVAL '24 hours'",
"refId": "A"
}
],
"title": "Total Updates (24h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Learn: Withdrawal rate above 30% is unusual. Above 50% may indicate a route leak or oscillation event.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 20},{"color": "red","value": 50}]},"unit": "percent","max": 100}},
"gridPos": {"h": 5,"w": 6,"x": 6,"y": 10},
"id": 3,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n ROUND(100.0 * SUM(CASE WHEN iswithdrawn THEN 1 ELSE 0 END) / NULLIF(COUNT(*),0), 1) AS \"Withdrawal Rate %\"\nFROM ip_rib_log\nWHERE timestamp > NOW() - INTERVAL '24 hours'",
"refId": "A"
}
],
"title": "Withdrawal Rate % (24h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1000},{"color": "red","value": 10000}]},"unit": "short"}},
"gridPos": {"h": 5,"w": 6,"x": 12,"y": 10},
"id": 4,
"options": {"colorMode": "value","graphMode": "area","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(DISTINCT peer_hash_id) AS \"Active Peers\" FROM ip_rib_log WHERE timestamp > NOW() - INTERVAL '1 hour'",
"refId": "A"
}
],
"title": "Active Reporting Peers (1h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 500},{"color": "red","value": 2000}]},"unit": "short"}},
"gridPos": {"h": 5,"w": 6,"x": 18,"y": 10},
"id": 5,
"options": {"colorMode": "value","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"text": {}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(DISTINCT prefix) AS \"Unique Prefixes Updated (24h)\" FROM ip_rib_log WHERE timestamp > NOW() - INTERVAL '24 hours'",
"refId": "A"
}
],
"title": "Unique Prefixes Updated (24h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Updates per peer over time. Learn: Peers should have similar update rates. A peer with dramatically more updates may be experiencing instability or receiving a full BGP table with frequent changes.",
"fieldConfig": {
"defaults": {"color": {"mode": "palette-classic"},"custom": {"drawStyle": "line","fillOpacity": 10,"lineWidth": 1,"spanNulls": false},"unit": "short"}
},
"gridPos": {"h": 9,"w": 24,"x": 0,"y": 15},
"id": 6,
"options": {"legend": {"calcs": [],"displayMode": "list","placement": "right"},"tooltip": {"mode": "multi"}},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT\n $__timeGroupAlias(s.interval_time,'30m'),\n COALESCE(p.name, p.peer_addr::text) AS metric,\n SUM(s.advertise_avg + s.withdraw_avg) AS \"Updates\"\nFROM stats_peer_update_counts s\nJOIN bgp_peers p ON p.hash_id = s.peer_hash_id\nWHERE $__timeFilter(s.interval_time)\nGROUP BY 1, 2\nORDER BY 1",
"refId": "A"
}
],
"title": "Update Rate by Peer (30-min buckets)",
"type": "timeseries"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","learning","bgp","churn"],
"time": {"from": "now-24h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "BGP Update Rate & Churn",
"uid": "obmp-learn-01",
"version": 1
}

View File

@ -0,0 +1,238 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": { "type": "datasource", "uid": "grafana" },
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": { "type": "auto" },
"inspect": false
}
},
"overrides": [
{
"matcher": { "id": "byName", "options": "Max BW (B/s)" },
"properties": [{ "id": "unit", "value": "Bps" }]
},
{
"matcher": { "id": "byName", "options": "Max Reservable BW" },
"properties": [{ "id": "unit", "value": "Bps" }]
}
]
},
"gridPos": { "h": 10, "w": 24, "x": 0, "y": 0 },
"id": 1,
"options": {
"showHeader": true,
"sortBy": [{ "desc": false, "displayName": "Local Router" }]
},
"targets": [
{
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"format": "table",
"rawQuery": true,
"rawSql": "SELECT local_router_name as \"Local Router\",\n remote_router_name as \"Remote Router\",\n interface_addr::text as \"Interface IP\",\n neighbor_addr::text as \"Neighbor IP\",\n max_link_bw as \"Max BW (B/s)\",\n max_resv_bw as \"Max Reservable BW\",\n unreserved_bw as \"Unreserved BW\",\n igp_metric as \"IGP Metric\",\n te_def_metric as \"TE Metric\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY local_router_name, remote_router_name",
"refId": "A"
}
],
"title": "Link Capacity Inventory (from BGP-LS)",
"type": "table"
},
{
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisLabel": "Bandwidth (B/s)",
"fillOpacity": 80,
"gradientMode": "none",
"lineWidth": 1,
"scaleDistribution": { "type": "linear" },
"showValue": "auto",
"stacking": { "group": "A", "mode": "none" }
},
"unit": "Bps"
},
"overrides": []
},
"gridPos": { "h": 10, "w": 12, "x": 0, "y": 10 },
"id": 2,
"options": {
"barRadius": 0.1,
"barWidth": 0.8,
"groupWidth": 0.7,
"legend": { "calcs": [], "displayMode": "list", "placement": "bottom" },
"orientation": "horizontal",
"tooltip": { "mode": "single", "sort": "none" },
"xTickLabelRotation": 0
},
"targets": [
{
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"format": "table",
"rawQuery": true,
"rawSql": "SELECT local_router_name || ' -> ' || remote_router_name as \"Link\",\n COALESCE(max_link_bw, 0) as \"Max Bandwidth\",\n COALESCE(max_resv_bw, 0) as \"Max Reservable\",\n COALESCE(max_link_bw, 0) - COALESCE(max_resv_bw, 0) as \"Unreserved Gap\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\n AND max_link_bw IS NOT NULL AND max_link_bw > 0\nORDER BY max_link_bw DESC",
"refId": "A"
}
],
"title": "Capacity vs Reservable Bandwidth",
"type": "barchart"
},
{
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"fieldConfig": {
"defaults": {
"color": { "mode": "thresholds" },
"thresholds": {
"mode": "percentage",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 50 },
{ "color": "orange", "value": 75 },
{ "color": "red", "value": 90 }
]
},
"unit": "percentunit",
"max": 1,
"min": 0
},
"overrides": []
},
"gridPos": { "h": 10, "w": 12, "x": 12, "y": 10 },
"id": 3,
"options": {
"minVizHeight": 75,
"minVizWidth": 75,
"orientation": "auto",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": true
},
"showThresholdLabels": false,
"showThresholdMarkers": true,
"sizing": "auto"
},
"targets": [
{
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"format": "table",
"rawQuery": true,
"rawSql": "SELECT local_router_name || ' -> ' || remote_router_name as \"Link\",\n CASE WHEN max_link_bw > 0 \n THEN 1.0 - (COALESCE(max_resv_bw, 0)::float / max_link_bw::float)\n ELSE 0 END as \"Reservation Ratio\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\n AND max_link_bw IS NOT NULL AND max_link_bw > 0\nORDER BY \"Reservation Ratio\" DESC\nLIMIT 10",
"refId": "A"
}
],
"title": "Bandwidth Reservation Ratio (Higher = More Reserved)",
"type": "gauge"
},
{
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"custom": {
"axisBorderShow": false,
"fillOpacity": 80,
"gradientMode": "none",
"lineWidth": 1,
"showValue": "auto",
"stacking": { "group": "A", "mode": "none" }
}
},
"overrides": []
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 20 },
"id": 4,
"options": {
"barRadius": 0.1,
"barWidth": 0.8,
"groupWidth": 0.7,
"legend": { "calcs": [], "displayMode": "list", "placement": "bottom" },
"orientation": "horizontal",
"tooltip": { "mode": "single", "sort": "none" }
},
"targets": [
{
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"format": "table",
"rawQuery": true,
"rawSql": "SELECT local_router_name || ' -> ' || remote_router_name as \"Link\",\n igp_metric as \"IGP Metric\",\n COALESCE(te_def_metric, 0) as \"TE Default Metric\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY igp_metric DESC\nLIMIT 20",
"refId": "A"
}
],
"title": "IGP Metric vs TE Default Metric",
"type": "barchart"
},
{
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 20 },
"id": 5,
"options": {
"mode": "markdown",
"content": "## What-If: CSPF Path Computation\n\nIn a real MPLS-TE or SR-TE deployment, the headend router runs **Constrained Shortest Path First (CSPF)** to find paths that satisfy:\n\n1. **Bandwidth constraint** - Enough unreserved BW at the required priority\n2. **Admin group (affinity)** - Link colors must match include/exclude masks\n3. **SRLG diversity** - Backup path avoids shared risk with primary\n4. **TE metric optimization** - Minimize TE metric (not IGP metric)\n\n### How BGP-LS Enables This\n\nBGP-LS distributes the complete IGP topology **with TE attributes** to an external controller (PCE, SDN controller). The controller can:\n\n- Build a Traffic Engineering Database (TED)\n- Run CSPF with arbitrary constraints\n- Program SR-TE policies via PCEP or gRPC\n\n### Data Available in This Lab\n\n| Attribute | Source | Available? |\n|-----------|--------|------------|\n| Topology (nodes/links) | BGP-LS | Yes |\n| IGP Metric | BGP-LS | Yes |\n| TE Default Metric | BGP-LS TLV 1092 | Check TE table |\n| Max Link BW | BGP-LS TLV 1089 | Check TE table |\n| Max Reservable BW | BGP-LS TLV 1090 | Check TE table |\n| Unreserved BW | BGP-LS TLV 1091 | Check TE table |\n| Admin Group | BGP-LS TLV 1088 | Check TE table |\n| SRLG | BGP-LS TLV 1096 | Check TE table |\n| SR Node SID | BGP-LS TLV 1034 | Check SR table |\n| SR Adj SID | BGP-LS TLV 1099 | Check SR table |"
},
"title": "CSPF & Traffic Engineering Concepts",
"type": "text"
},
{
"gridPos": { "h": 12, "w": 24, "x": 0, "y": 28 },
"id": 6,
"options": {
"mode": "markdown",
"content": "## Integration Guide: Adding Real-Time Link Utilization\n\nBMP/BGP-LS provides **capacity** data (max bandwidth, reservable bandwidth) but NOT real-time **utilization**. To complete the traffic engineering picture, you need streaming telemetry.\n\n### Architecture\n\n```\n +------------------+\n IOS-XR Routers ---->| OpenBMP Collector|----> PostgreSQL (topology + capacity)\n | +------------------+\n | \n +-- gNMI ----->| Telegraf |----> InfluxDB/Prometheus (utilization)\n +------------------+\n |\n +------------------+\n | Grafana | <-- Mixed datasource queries\n +------------------+\n```\n\n### Step 1: Enable Model-Driven Telemetry on IOS-XR\n\n```\ntelemetry model-driven\n sensor-group INTF-COUNTERS\n sensor-path Cisco-IOS-XR-infra-statsd-oper:infra-statistics/interfaces/interface/latest/generic-counters\n !\n subscription INTF-SUB\n sensor-group-id INTF-COUNTERS sample-interval 30000\n destination-id TELEGRAF\n !\n destination-group TELEGRAF\n address-family ipv4 10.40.40.202 port 57000\n encoding self-describing-gpb\n protocol grpc no-tls\n !\n !\n```\n\n### Step 2: Telegraf Configuration\n\n```toml\n[[inputs.cisco_telemetry_mdt]]\n transport = \"grpc\"\n service_address = \":57000\"\n\n[[outputs.influxdb_v2]]\n urls = [\"http://localhost:8086\"]\n token = \"your-token\"\n organization = \"openbmp\"\n bucket = \"telemetry\"\n```\n\n### Step 3: Grafana Mixed Datasource Query\n\nCombine BGP-LS capacity from PostgreSQL with utilization from InfluxDB:\n\n```\n-- PostgreSQL: Get link capacity\nSELECT interface_addr::text as interface, max_link_bw\nFROM v_ls_links WHERE peer_hash_id = '$peer_hash'\n\n-- InfluxDB: Get interface utilization\nfrom(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"Cisco-IOS-XR-infra-statsd-oper\")\n |> filter(fn: (r) => r._field == \"bytes-received\" or r._field == \"bytes-sent\")\n |> derivative(unit: 1s, nonNegative: true)\n```\n\n### Step 4: Calculate Utilization %\n\nIn Grafana, use **Transformations** to:\n1. Join PostgreSQL capacity with InfluxDB utilization by interface IP\n2. Add calculated field: `utilization_pct = bytes_per_sec / max_link_bw * 100`\n3. Set threshold alerts: >80% = warning, >95% = critical\n\n### Key gNMI Sensor Paths for IOS-XR\n\n| Sensor Path | Data |\n|-------------|------|\n| `Cisco-IOS-XR-infra-statsd-oper:infra-statistics/interfaces/interface/latest/generic-counters` | Interface byte/packet counters |\n| `Cisco-IOS-XR-infra-statsd-oper:infra-statistics/interfaces/interface/latest/data-rate` | Current data rate (bits/sec) |\n| `Cisco-IOS-XR-mpls-te-oper:mpls-te/tunnels/summary` | MPLS-TE tunnel summary |\n| `Cisco-IOS-XR-ip-rsvp-oper:rsvp/interface-briefs` | RSVP interface reservations |\n| `Cisco-IOS-XR-segment-routing-ms-oper:srms/policy` | SR-MPLS policy state |\n\n### RFC 8571: Performance Metrics via BGP-LS\n\nIf routers support RFC 8571, these metrics flow through BGP-LS automatically:\n- **Unidirectional Link Delay** (TLV 1114) - microseconds\n- **Min/Max Link Delay** (TLV 1115)\n- **Delay Variation (jitter)** (TLV 1116)\n- **Link Loss** (TLV 1117) - percentage\n- **Residual Bandwidth** (TLV 1118)\n- **Available Bandwidth** (TLV 1119)\n- **Utilized Bandwidth** (TLV 1120)\n\nThese would appear in the `ls_links` table if the OpenBMP parser supports them."
},
"title": "Integration Guide: Streaming Telemetry for Link Utilization",
"type": "text"
}
],
"schemaVersion": 39,
"tags": ["obmp-learning"],
"templating": {
"list": [
{
"current": {},
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"definition": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0",
"hide": 0,
"includeAll": false,
"label": "BGP Peer",
"multi": false,
"name": "peer_hash",
"options": [],
"query": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
}
]
},
"time": { "from": "now-6h", "to": "now" },
"timepicker": {},
"timezone": "",
"title": "Link Utilization & TE Thought Experiment",
"uid": "obmp-learn-10",
"version": 1
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,345 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": []
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 0},
"id": 1,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT local_router_name as \"Local Router\", \n remote_router_name as \"Remote Router\",\n igp_metric as \"IGP Metric\",\n te_def_metric as \"TE Metric\",\n max_link_bw as \"Max BW (B/s)\",\n max_resv_bw as \"Max Reservable BW\",\n unreserved_bw as \"Unreserved BW\",\n admin_group as \"Admin Group\",\n protection_type as \"Protection\",\n srlg as \"SRLG\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY local_router_name, remote_router_name",
"refId": "A"
}
],
"title": "TE Link Capacity Map",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {"color": {"mode": "palette-classic"}},
"overrides": []
},
"gridPos": {"h": 10,"w": 12,"x": 0,"y": 10},
"id": 2,
"options": {
"barRadius": 0,
"barWidth": 0.97,
"groupWidth": 0.7,
"legend": {"displayMode": "list","placement": "bottom"},
"orientation": "auto",
"showValue": "auto",
"stacking": "none",
"tooltip": {"mode": "single","sort": "none"},
"xTickLabelRotation": -45
},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT local_router_name || ' -> ' || remote_router_name as \"Link\",\n igp_metric as \"IGP Metric\",\n COALESCE(te_def_metric, igp_metric) as \"TE Metric\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY igp_metric DESC",
"refId": "A"
}
],
"title": "IGP Metric vs TE Metric Comparison",
"type": "barchart"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {"color": {"mode": "palette-classic"}},
"overrides": []
},
"gridPos": {"h": 10,"w": 6,"x": 12,"y": 10},
"id": 3,
"options": {
"legend": {"displayMode": "list","placement": "bottom"},
"pieType": "pie",
"reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": true},
"tooltip": {"mode": "single","sort": "none"}
},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT COALESCE(admin_group::text, 'None') as \"Admin Group\",\n COUNT(*) as \"Link Count\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nGROUP BY admin_group\nORDER BY \"Link Count\" DESC",
"refId": "A"
}
],
"title": "Admin Group Distribution",
"type": "piechart"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {"color": {"mode": "palette-classic"}},
"overrides": []
},
"gridPos": {"h": 10,"w": 6,"x": 18,"y": 10},
"id": 4,
"options": {
"legend": {"displayMode": "list","placement": "bottom"},
"pieType": "pie",
"reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": true},
"tooltip": {"mode": "single","sort": "none"}
},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT COALESCE(protection_type, 'None') as \"Protection Type\",\n COUNT(*) as \"Link Count\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nGROUP BY protection_type\nORDER BY \"Link Count\" DESC",
"refId": "A"
}
],
"title": "Link Protection Types",
"type": "piechart"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": []
},
"gridPos": {"h": 8,"w": 12,"x": 0,"y": 20},
"id": 5,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT nodename as \"Node\",\n routerid as \"Router ID\",\n protocol as \"Protocol\",\n sr_capabilities as \"SR Capabilities (SRGB)\"\nFROM v_ls_nodes\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY nodename",
"refId": "A"
}
],
"title": "SR Node Capabilities",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": []
},
"gridPos": {"h": 8,"w": 12,"x": 12,"y": 20},
"id": 6,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT n.nodename as \"Node\",\n p.prefix::text as \"Prefix\",\n p.prefix_len as \"Len\",\n p.metric as \"Metric\",\n p.sr_prefix_sids as \"Prefix SID\",\n p.protocol::text as \"Protocol\"\nFROM ls_prefixes p\nJOIN ls_nodes n ON n.hash_id = p.local_node_hash_id \n AND n.peer_hash_id = p.peer_hash_id\nWHERE p.peer_hash_id = '$peer_hash' AND p.iswithdrawn = false\nORDER BY n.nodename, p.prefix",
"refId": "A"
}
],
"title": "SR Prefix SIDs",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": []
},
"gridPos": {"h": 8,"w": 12,"x": 0,"y": 28},
"id": 7,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT local_router_name as \"Local\",\n remote_router_name as \"Remote\",\n sr_adjacency_sids as \"Adjacency SIDs\",\n peer_node_sid as \"Peer Node SID\",\n mpls_proto_mask::text as \"MPLS Proto\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY local_router_name, remote_router_name",
"refId": "A"
}
],
"title": "SR Adjacency SIDs",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": []
},
"gridPos": {"h": 8,"w": 12,"x": 12,"y": 28},
"id": 8,
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT srlg as \"SRLG Value\",\n COUNT(*) as \"Link Count\",\n string_agg(DISTINCT local_router_name || ' -> ' || remote_router_name, ', ') as \"Links\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false \n AND srlg IS NOT NULL AND srlg != ''\nGROUP BY srlg\nORDER BY COUNT(*) DESC",
"refId": "A"
}
],
"title": "SRLG Groups",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}
},
"overrides": []
},
"gridPos": {"h": 4,"w": 5,"x": 0,"y": 36},
"id": 9,
"options": {"colorMode": "value","graphMode": "area","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_links WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND te_def_metric IS NOT NULL",
"refId": "A"
}
],
"title": "Links with TE Metric",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}
},
"overrides": []
},
"gridPos": {"h": 4,"w": 5,"x": 5,"y": 36},
"id": 10,
"options": {"colorMode": "value","graphMode": "area","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_links WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND max_link_bw IS NOT NULL AND max_link_bw > 0",
"refId": "A"
}
],
"title": "Links with Bandwidth",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}
},
"overrides": []
},
"gridPos": {"h": 4,"w": 5,"x": 10,"y": 36},
"id": 11,
"options": {"colorMode": "value","graphMode": "area","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_links WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND srlg IS NOT NULL AND srlg != ''",
"refId": "A"
}
],
"title": "Links with SRLG",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}
},
"overrides": []
},
"gridPos": {"h": 4,"w": 5,"x": 15,"y": 36},
"id": 12,
"options": {"colorMode": "value","graphMode": "area","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_nodes WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND sr_capabilities IS NOT NULL AND sr_capabilities != ''",
"refId": "A"
}
],
"title": "Nodes with SR",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}
},
"overrides": []
},
"gridPos": {"h": 4,"w": 4,"x": 20,"y": 36},
"id": 13,
"options": {"colorMode": "value","graphMode": "area","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_links WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND sr_adjacency_sids IS NOT NULL AND sr_adjacency_sids != ''",
"refId": "A"
}
],
"title": "Links with Adj SID",
"type": "stat"
},
{
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 40},
"id": 14,
"options": {
"code": {"language": "plaintext","showLineNumbers": false,"showMiniMap": false},
"content": "## Traffic Engineering & Segment Routing Analytics\n\nThis dashboard exposes TE and SR attributes from BGP-LS (RFC 7752) that OpenBMP collects but existing dashboards don't display.\n\n### TE Fields (from ls_links)\n- **admin_group**: Link color/affinity bitmap for RSVP-TE constraints\n- **max_link_bw / max_resv_bw**: Link capacity in bytes/sec\n- **unreserved_bw**: Available bandwidth per priority level\n- **te_def_metric**: TE metric (may differ from IGP metric)\n- **protection_type**: FRR protection (unprotected, shared, dedicated, etc.)\n- **srlg**: Shared Risk Link Group for diverse path computation\n\n### SR Fields\n- **sr_capabilities**: Node SRGB (Segment Routing Global Block) range\n- **sr_prefix_sids**: Prefix SID for SR-MPLS forwarding\n- **sr_adjacency_sids**: Adjacency SIDs for SR-TE path steering\n- **peer_node_sid**: BGP EPE SID (RFC 9086)\n\n### Notes\n- NULL values indicate the router is not advertising that TLV\n- To enable TE metrics on IOS-XR: `mpls traffic-eng` under IS-IS\n- To enable SR: `segment-routing mpls` under IS-IS with prefix-sid-map",
"mode": "markdown"
},
"title": "About This Dashboard",
"type": "text"
}
],
"schemaVersion": 39,
"tags": ["obmp-learning"],
"templating": {
"list": [
{
"current": {},
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"definition": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0",
"hide": 0,
"includeAll": false,
"label": "BGP Peer",
"multi": false,
"name": "peer_hash",
"options": [],
"query": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
}
]
},
"time": {"from": "now-6h","to": "now"},
"timepicker": {},
"timezone": "",
"title": "TE & Segment Routing Analytics",
"uid": "obmp-learn-08",
"version": 1
}

View File

@ -1,11 +1,7 @@
{
"uid": "obmp-learn-09",
"title": "Topology Change & Anomaly Detection",
"tags": [
"obmp-learning",
"obmp",
"obmp-nav"
],
"tags": ["obmp-learning"],
"editable": true,
"schemaVersion": 39,
"time": {
@ -33,16 +29,8 @@
"id": 1,
"title": "Link State Changes Over Time",
"type": "timeseries",
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT $__timeGroupAlias(timestamp, '5m') as time,\n SUM(CASE WHEN iswithdrawn = false THEN 1 ELSE 0 END) as \"Links Up\",\n SUM(CASE WHEN iswithdrawn = true THEN 1 ELSE 0 END) as \"Links Down\"\nFROM ls_links_log\nWHERE $__timeFilter(timestamp) AND peer_hash_id = '$peer_hash'\nGROUP BY 1 ORDER BY 1",
@ -55,16 +43,8 @@
"id": 2,
"title": "Node Changes Over Time",
"type": "timeseries",
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT $__timeGroupAlias(timestamp, '5m') as time,\n SUM(CASE WHEN iswithdrawn = false THEN 1 ELSE 0 END) as \"Nodes Appeared\",\n SUM(CASE WHEN iswithdrawn = true THEN 1 ELSE 0 END) as \"Nodes Withdrawn\"\nFROM ls_nodes_log\nWHERE $__timeFilter(timestamp) AND peer_hash_id = '$peer_hash'\nGROUP BY 1 ORDER BY 1",
@ -77,16 +57,8 @@
"id": 3,
"title": "BGP Peer Session Events",
"type": "timeseries",
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT $__timeGroupAlias(pel.timestamp, '5m') as time,\n SUM(CASE WHEN pel.state = 'up' THEN 1 ELSE 0 END) as \"Sessions Up\",\n SUM(CASE WHEN pel.state = 'down' THEN 1 ELSE 0 END) as \"Sessions Down\"\nFROM peer_event_log pel\nWHERE $__timeFilter(pel.timestamp)\nGROUP BY 1 ORDER BY 1",
@ -99,16 +71,8 @@
"id": 4,
"title": "RIB Update Rate",
"type": "timeseries",
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT $__timeGroupAlias(timestamp, '5m') as time,\n SUM(CASE WHEN iswithdrawn = false THEN 1 ELSE 0 END) as \"Advertisements\",\n SUM(CASE WHEN iswithdrawn = true THEN 1 ELSE 0 END) as \"Withdrawals\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY 1 ORDER BY 1",
@ -121,16 +85,8 @@
"id": 5,
"title": "Origin AS Changes (Potential Hijacks)",
"type": "table",
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 16
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 10, "w": 12, "x": 0, "y": 16 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT DISTINCT ON (r1.prefix, r1.prefix_len)\n r1.prefix::text as \"Prefix\",\n r1.prefix_len as \"Len\",\n r1.origin_as as \"Current Origin AS\",\n r2.origin_as as \"Previous Origin AS\",\n r1.timestamp as \"Changed At\"\nFROM ip_rib_log r1\nJOIN ip_rib_log r2 ON r1.prefix = r2.prefix \n AND r1.prefix_len = r2.prefix_len\n AND r1.timestamp > r2.timestamp\nWHERE r1.origin_as != r2.origin_as\n AND $__timeFilter(r1.timestamp)\nORDER BY r1.prefix, r1.prefix_len, r1.timestamp DESC\nLIMIT 50",
@ -143,16 +99,8 @@
"id": 6,
"title": "Most Churned Prefixes",
"type": "table",
"gridPos": {
"h": 10,
"w": 12,
"x": 12,
"y": 16
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 10, "w": 12, "x": 12, "y": 16 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT prefix::text as \"Prefix\",\n prefix_len as \"Len\",\n COUNT(*) as \"Total Updates\",\n SUM(CASE WHEN iswithdrawn THEN 1 ELSE 0 END) as \"Withdrawals\",\n MIN(timestamp) as \"First Seen\",\n MAX(timestamp) as \"Last Change\",\n CASE \n WHEN COUNT(*) <= 2 THEN 'Stable'\n WHEN COUNT(*) <= 10 THEN 'Moderate'\n ELSE 'Unstable'\n END as \"Stability\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY prefix, prefix_len\nHAVING COUNT(*) > 1\nORDER BY COUNT(*) DESC\nLIMIT 30",
@ -165,16 +113,8 @@
"id": 7,
"title": "Recent Link State Changes",
"type": "table",
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 26
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 10, "w": 24, "x": 0, "y": 26 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT l.timestamp as \"Time\",\n CASE WHEN l.iswithdrawn THEN 'DOWN' ELSE 'UP' END as \"State\",\n ln.name as \"Local Node\",\n l.local_igp_router_id as \"Local IGP ID\",\n rn.name as \"Remote Node\",\n l.remote_igp_router_id as \"Remote IGP ID\",\n l.igp_metric as \"IGP Metric\",\n l.protocol::text as \"Protocol\"\nFROM ls_links_log l\nLEFT JOIN ls_nodes ln ON ln.hash_id = l.local_node_hash_id AND ln.peer_hash_id = l.peer_hash_id\nLEFT JOIN ls_nodes rn ON rn.hash_id = l.remote_node_hash_id AND rn.peer_hash_id = l.peer_hash_id\nWHERE $__timeFilter(l.timestamp) AND l.peer_hash_id = '$peer_hash'\nORDER BY l.timestamp DESC\nLIMIT 50",
@ -187,16 +127,8 @@
"id": 8,
"title": "Multi-Peer Route Consistency",
"type": "table",
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 36
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 36 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT r.prefix::text as \"Prefix\",\n r.prefix_len as \"Len\",\n COUNT(DISTINCT r.peer_hash_id) as \"Peer Count\",\n COUNT(DISTINCT ba.origin_as) as \"Distinct Origins\",\n COUNT(DISTINCT ba.as_path_count) as \"Distinct Path Lengths\",\n string_agg(DISTINCT ba.origin_as::text, ', ') as \"Origin ASNs\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nGROUP BY r.prefix, r.prefix_len\nHAVING COUNT(DISTINCT ba.origin_as) > 1\nORDER BY COUNT(DISTINCT ba.origin_as) DESC\nLIMIT 30",
@ -209,16 +141,8 @@
"id": 9,
"title": "Active Peers",
"type": "stat",
"gridPos": {
"h": 4,
"w": 4,
"x": 0,
"y": 44
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 4, "w": 4, "x": 0, "y": 44 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT COUNT(*) FROM bgp_peers WHERE state = 'up'",
@ -231,16 +155,8 @@
"id": 10,
"title": "Total LS Links",
"type": "stat",
"gridPos": {
"h": 4,
"w": 4,
"x": 4,
"y": 44
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 4, "w": 4, "x": 4, "y": 44 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT COUNT(*) FROM ls_links WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false",
@ -253,16 +169,8 @@
"id": 11,
"title": "Total LS Nodes",
"type": "stat",
"gridPos": {
"h": 4,
"w": 4,
"x": 8,
"y": 44
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 4, "w": 4, "x": 8, "y": 44 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT COUNT(*) FROM ls_nodes WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false",
@ -275,16 +183,8 @@
"id": 12,
"title": "RIB Updates (24h)",
"type": "stat",
"gridPos": {
"h": 4,
"w": 4,
"x": 12,
"y": 44
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 4, "w": 4, "x": 12, "y": 44 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT COUNT(*) FROM ip_rib_log WHERE timestamp > NOW() - INTERVAL '24 hours'",
@ -297,16 +197,8 @@
"id": 13,
"title": "Link Changes (24h)",
"type": "stat",
"gridPos": {
"h": 4,
"w": 4,
"x": 16,
"y": 44
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 4, "w": 4, "x": 16, "y": 44 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT COUNT(*) FROM ls_links_log WHERE timestamp > NOW() - INTERVAL '24 hours' AND peer_hash_id = '$peer_hash'",
@ -319,16 +211,8 @@
"id": 14,
"title": "Origin Changes (24h)",
"type": "stat",
"gridPos": {
"h": 4,
"w": 4,
"x": 20,
"y": 44
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"gridPos": { "h": 4, "w": 4, "x": 20, "y": 44 },
"datasource": { "type": "postgres", "uid": "obmp_postgres" },
"targets": [
{
"rawSql": "SELECT COUNT(DISTINCT r1.prefix) FROM ip_rib_log r1\nJOIN ip_rib_log r2 ON r1.prefix = r2.prefix AND r1.prefix_len = r2.prefix_len AND r1.timestamp > r2.timestamp\nWHERE r1.origin_as != r2.origin_as AND r1.timestamp > NOW() - INTERVAL '24 hours'",
@ -341,29 +225,11 @@
"id": 15,
"title": "About This Dashboard",
"type": "text",
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 36
},
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 36 },
"options": {
"mode": "markdown",
"content": "## Topology Change & Anomaly Detection\n\nThis dashboard provides heuristic analysis of BMP data to detect network anomalies:\n\n### What to Watch For\n- **Link flaps**: Rapid up/down cycles in the Link State Changes panel indicate instability\n- **Origin AS changes**: Could indicate a route hijack or legitimate migration\n- **Multi-origin prefixes**: Same prefix seen from different origin ASNs across peers\n- **Correlated events**: Peer session drops followed by mass withdrawals indicate convergence events\n\n### Testing with ExaBGP Scenarios\n1. Load `origin_shift` scenario to simulate origin AS changes\n2. Load `hijack_simulation` to see how shorter paths override legitimate routes\n3. Load/unload `churn` scenario repeatedly to generate instability patterns\n\n### Data Sources\n- **ls_links_log / ls_nodes_log**: TimescaleDB hypertables tracking all BGP-LS topology changes\n- **ip_rib_log**: All BGP RIB updates and withdrawals with timestamps\n- **peer_event_log**: BGP session state changes (up/down events)"
}
}
],
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
]
}

View File

@ -1,46 +1,16 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "Combined view of BMP control-plane data (from PostgreSQL) and gNMI data-plane telemetry (from InfluxDB). Correlate BGP peer state with interface traffic patterns.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"templating": {
"list": [
{
"current": {},
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"definition": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> keep(columns: [\"source\"])\n |> distinct(column: \"source\")\n |> sort()",
"hide": 0,
"includeAll": true,
@ -48,7 +18,7 @@
"multi": true,
"name": "router",
"options": [],
"query": "import \"influxdata/influxdb/schema\"\nschema.tagValues(bucket: \"telemetry\", tag: \"source\", predicate: (r) => r._measurement == \"interface_counters\", start: -1h)",
"query": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> keep(columns: [\"source\"])\n |> distinct(column: \"source\")\n |> sort()",
"refresh": 2,
"regex": "",
"type": "query"
@ -57,121 +27,27 @@
},
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Current BGP peer status from the OpenBMP PostgreSQL database. Shows peer address, name, and session state.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": true
},
"color": {"mode": "thresholds"},
"custom": {"align": "auto","displayMode": "auto","filterable": true,"inspect": true},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "state"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background-solid"
},
{
"id": "mappings",
"value": [
{
"options": {
"down": {
"color": "red",
"index": 1,
"text": "DOWN"
},
"up": {
"color": "green",
"index": 0,
"text": "UP"
}
},
"type": "value"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "peer_addr"
},
"properties": [
{
"id": "custom.width",
"value": 160
}
]
},
{
"matcher": {
"id": "byName",
"options": "name"
},
"properties": [
{
"id": "custom.width",
"value": 200
}
]
}
{"matcher": {"id": "byName","options": "state"},"properties": [{"id": "custom.displayMode","value": "color-background-solid"},{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}]}]},
{"matcher": {"id": "byName","options": "peer_addr"},"properties": [{"id": "custom.width","value": 160}]},
{"matcher": {"id": "byName","options": "name"},"properties": [{"id": "custom.width","value": 200}]}
]
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 0
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 0},
"id": 1,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": false,
"displayName": "state"
}
]
},
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": false,"displayName": "state"}]},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "table",
"rawSql": "SELECT\n p.peer_addr,\n COALESCE(p.name, p.peer_addr::text) AS name,\n p.state,\n p.peer_as AS \"AS\",\n p.router_hash_id IS NOT NULL AS \"BMP Active\",\n p.timestamp AS \"Last State Change\"\nFROM bgp_peers p\nWHERE p.isprepolicy = true\nORDER BY p.state, p.peer_addr",
"refId": "A"
@ -181,90 +57,23 @@
"type": "table"
},
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"description": "Interface traffic rates from gNMI streaming telemetry. Shows bytes per second for each interface across selected routers.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"color": {"mode": "palette-classic"},
"custom": {"axisBorderShow": false,"axisCenteredZero": false,"axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 10,"gradientMode": "none","hideFrom": {"legend": false,"tooltip": false,"viz": false},"lineInterpolation": "linear","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": false,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "red","value": 80}]},
"unit": "Bps"
}
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 10
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 10},
"id": 2,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"options": {"legend": {"calcs": ["mean","max"],"displayMode": "table","placement": "bottom"},"tooltip": {"mode": "multi","sort": "desc"}},
"targets": [
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"query": "from(bucket: \"telemetry\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> filter(fn: (r) => r._field == \"in-octets\" or r._field == \"out-octets\")\n |> toFloat()\n |> derivative(unit: 1s, nonNegative: true)\n |> map(fn: (r) => ({r with _value: if r._value < 0.0 then 0.0 else r._value}))",
"refId": "A"
}
@ -273,85 +82,23 @@
"type": "timeseries"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP update activity over time from the OpenBMP PostgreSQL database. Shows peer event transitions and update counts for correlation with traffic patterns.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "bars",
"fillOpacity": 50,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"color": {"mode": "palette-classic"},
"custom": {"axisBorderShow": false,"axisCenteredZero": false,"axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "bars","fillOpacity": 50,"gradientMode": "none","hideFrom": {"legend": false,"tooltip": false,"viz": false},"lineInterpolation": "linear","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": false,"stacking": {"group": "A","mode": "normal"},"thresholdsStyle": {"mode": "off"}},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]},
"unit": "short"
}
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 20
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 20},
"id": 3,
"options": {
"legend": {
"calcs": [
"sum"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"options": {"legend": {"calcs": ["sum"],"displayMode": "table","placement": "bottom"},"tooltip": {"mode": "multi","sort": "desc"}},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"format": "time_series",
"rawSql": "SELECT\n $__timeGroupAlias(e.timestamp, '1m'),\n COALESCE(p.name, p.peer_addr::text) AS metric,\n COUNT(*) AS \"value\"\nFROM peer_event_log e\nJOIN bgp_peers p ON p.hash_id = e.peer_hash_id\nWHERE $__timeFilter(e.timestamp)\nGROUP BY 1, 2\nORDER BY 1",
"refId": "A"
@ -361,23 +108,11 @@
"type": "timeseries"
},
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 30
},
"datasource": {"type": "datasource","uid": "grafana"},
"gridPos": {"h": 6,"w": 24,"x": 0,"y": 30},
"id": 4,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"code": {"language": "plaintext","showLineNumbers": false,"showMiniMap": false},
"content": "## Combined BMP + Telemetry View\n\nThis dashboard integrates two complementary data sources to provide a unified network monitoring view:\n\n### Control Plane (BMP via PostgreSQL)\n- **BGP Peer Status** -- Real-time BGP session state from BMP (OpenBMP)\n- **BGP Update Activity** -- Session transitions and update events from `peer_event_log`\n\n### Data Plane (gNMI via InfluxDB)\n- **Interface Traffic** -- Streaming telemetry byte rates collected via gNMI at 10-second intervals\n\n### Correlation Use Cases\n- A BGP peer flap (control plane) should correlate with a traffic shift on affected interfaces (data plane)\n- Sustained high interface utilization (data plane) may precede BGP session resets due to congestion\n- Compare the number of active BGP peers with interface traffic to validate routing convergence",
"mode": "markdown"
},
@ -387,15 +122,8 @@
],
"schemaVersion": 39,
"style": "dark",
"tags": [
"obmp-telemetry",
"obmp",
"obmp-nav"
],
"time": {
"from": "now-1h",
"to": "now"
},
"tags": ["obmp-telemetry"],
"time": {"from": "now-1h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "Combined BMP + Telemetry View",

View File

@ -1,46 +1,16 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "Interface error and drop counters collected via gNMI streaming telemetry. Helps identify interfaces with packet loss or physical layer issues.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"templating": {
"list": [
{
"current": {},
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"definition": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> keep(columns: [\"source\"])\n |> distinct(column: \"source\")\n |> sort()",
"hide": 0,
"includeAll": true,
@ -48,17 +18,14 @@
"multi": true,
"name": "router",
"options": [],
"query": "import \"influxdata/influxdb/schema\"\nschema.tagValues(bucket: \"telemetry\", tag: \"source\", predicate: (r) => r._measurement == \"interface_counters\", start: -1h)",
"query": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> keep(columns: [\"source\"])\n |> distinct(column: \"source\")\n |> sort()",
"refresh": 2,
"regex": "",
"type": "query"
},
{
"current": {},
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"definition": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> keep(columns: [\"name\"])\n |> distinct(column: \"name\")\n |> sort()",
"hide": 0,
"includeAll": true,
@ -66,7 +33,7 @@
"multi": true,
"name": "interface",
"options": [],
"query": "import \"influxdata/influxdb/schema\"\nschema.tagValues(bucket: \"telemetry\", tag: \"name\", predicate: (r) => r._measurement == \"interface_counters\" and r.source =~ /${router:regex}/, start: -1h)",
"query": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> keep(columns: [\"name\"])\n |> distinct(column: \"name\")\n |> sort()",
"refresh": 2,
"regex": "",
"type": "query"
@ -75,95 +42,23 @@
},
"panels": [
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"description": "Interface error counters over time: input errors, output errors, and CRC errors. A rising trend indicates physical or configuration issues.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"color": {"mode": "palette-classic"},
"custom": {"axisBorderShow": false,"axisCenteredZero": false,"axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 10,"gradientMode": "none","hideFrom": {"legend": false,"tooltip": false,"viz": false},"lineInterpolation": "linear","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": false,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 100
}
]
},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 100}]},
"unit": "short"
}
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 0
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 0},
"id": 1,
"options": {
"legend": {
"calcs": [
"mean",
"max",
"last"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"options": {"legend": {"calcs": ["mean","max","last"],"displayMode": "table","placement": "bottom"},"tooltip": {"mode": "multi","sort": "desc"}},
"targets": [
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"query": "from(bucket: \"telemetry\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> filter(fn: (r) => r.name =~ /${interface:regex}/)\n |> filter(fn: (r) => r._field == \"in-errors\" or r._field == \"out-errors\" or r._field == \"in-fcs-errors\")\n |> toFloat()\n |> derivative(unit: 1s, nonNegative: true)",
"refId": "A"
}
@ -172,95 +67,23 @@
"type": "timeseries"
},
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"description": "Interface drop counters over time: input drops and output drops. Drops indicate congestion or queue overflow.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"color": {"mode": "palette-classic"},
"custom": {"axisBorderShow": false,"axisCenteredZero": false,"axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 10,"gradientMode": "none","hideFrom": {"legend": false,"tooltip": false,"viz": false},"lineInterpolation": "linear","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": false,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 100
}
]
},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 100}]},
"unit": "short"
}
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 10
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 10},
"id": 2,
"options": {
"legend": {
"calcs": [
"mean",
"max",
"last"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"options": {"legend": {"calcs": ["mean","max","last"],"displayMode": "table","placement": "bottom"},"tooltip": {"mode": "multi","sort": "desc"}},
"targets": [
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"query": "from(bucket: \"telemetry\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> filter(fn: (r) => r.name =~ /${interface:regex}/)\n |> filter(fn: (r) => r._field == \"in-discards\" or r._field == \"out-discards\")\n |> toFloat()\n |> derivative(unit: 1s, nonNegative: true)",
"refId": "A"
}
@ -269,201 +92,28 @@
"type": "timeseries"
},
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"description": "Summary table showing the latest error and drop counter values per interface. Useful for quickly identifying problematic interfaces.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": true
},
"color": {"mode": "thresholds"},
"custom": {"align": "auto","displayMode": "auto","filterable": true,"inspect": true},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 100
}
]
}
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 100}]}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "in-errors"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background-solid"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 100
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "out-errors"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background-solid"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 100
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "in-discards"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background-solid"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 100
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "out-discards"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background-solid"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 100
}
]
}
}
]
}
{"matcher": {"id": "byName","options": "in-errors"},"properties": [{"id": "custom.displayMode","value": "color-background-solid"},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 100}]}}]},
{"matcher": {"id": "byName","options": "out-errors"},"properties": [{"id": "custom.displayMode","value": "color-background-solid"},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 100}]}}]},
{"matcher": {"id": "byName","options": "in-discards"},"properties": [{"id": "custom.displayMode","value": "color-background-solid"},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 100}]}}]},
{"matcher": {"id": "byName","options": "out-discards"},"properties": [{"id": "custom.displayMode","value": "color-background-solid"},{"id": "thresholds","value": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 100}]}}]}
]
},
"gridPos": {
"h": 12,
"w": 24,
"x": 0,
"y": 20
},
"gridPos": {"h": 12,"w": 24,"x": 0,"y": 20},
"id": 3,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "in-errors"
}
]
},
"options": {"footer": {"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "in-errors"}]},
"targets": [
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"query": "from(bucket: \"telemetry\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> filter(fn: (r) => r.name =~ /${interface:regex}/)\n |> filter(fn: (r) => r._field == \"in-errors\" or r._field == \"out-errors\" or r._field == \"in-fcs-errors\" or r._field == \"in-discards\" or r._field == \"out-discards\")\n |> toFloat()\n |> last()\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\n |> keep(columns: [\"source\", \"name\", \"in-errors\", \"out-errors\", \"in-fcs-errors\", \"in-discards\", \"out-discards\"])\n |> sort(columns: [\"in-errors\"], desc: true)",
"refId": "A"
}
@ -474,15 +124,8 @@
],
"schemaVersion": 39,
"style": "dark",
"tags": [
"obmp-telemetry",
"obmp",
"obmp-nav"
],
"time": {
"from": "now-1h",
"to": "now"
},
"tags": ["obmp-telemetry"],
"time": {"from": "now-1h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "Interface Errors & Drops",

View File

@ -1,46 +1,16 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "Interface utilization metrics collected via gNMI streaming telemetry from IOS-XR routers. Shows byte rates, packet rates, and top interfaces by traffic volume.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"templating": {
"list": [
{
"current": {},
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"definition": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> keep(columns: [\"source\"])\n |> distinct(column: \"source\")\n |> sort()",
"hide": 0,
"includeAll": true,
@ -48,17 +18,14 @@
"multi": true,
"name": "router",
"options": [],
"query": "import \"influxdata/influxdb/schema\"\nschema.tagValues(bucket: \"telemetry\", tag: \"source\", predicate: (r) => r._measurement == \"interface_counters\", start: -1h)",
"query": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> keep(columns: [\"source\"])\n |> distinct(column: \"source\")\n |> sort()",
"refresh": 2,
"regex": "",
"type": "query"
},
{
"current": {},
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"definition": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> keep(columns: [\"name\"])\n |> distinct(column: \"name\")\n |> sort()",
"hide": 0,
"includeAll": true,
@ -66,7 +33,7 @@
"multi": true,
"name": "interface",
"options": [],
"query": "import \"influxdata/influxdb/schema\"\nschema.tagValues(bucket: \"telemetry\", tag: \"name\", predicate: (r) => r._measurement == \"interface_counters\" and r.source =~ /${router:regex}/, start: -1h)",
"query": "from(bucket: \"telemetry\")\n |> range(start: -1h)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> keep(columns: [\"name\"])\n |> distinct(column: \"name\")\n |> sort()",
"refresh": 2,
"regex": "",
"type": "query"
@ -75,90 +42,23 @@
},
"panels": [
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"description": "Rate of bytes received and sent per interface, computed as the derivative of cumulative counters. Unit: bytes per second.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"color": {"mode": "palette-classic"},
"custom": {"axisBorderShow": false,"axisCenteredZero": false,"axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 10,"gradientMode": "none","hideFrom": {"legend": false,"tooltip": false,"viz": false},"lineInterpolation": "linear","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": false,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "red","value": 80}]},
"unit": "Bps"
}
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 0
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 0},
"id": 1,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"options": {"legend": {"calcs": ["mean","max"],"displayMode": "table","placement": "bottom"},"tooltip": {"mode": "multi","sort": "desc"}},
"targets": [
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"query": "from(bucket: \"telemetry\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> filter(fn: (r) => r.name =~ /${interface:regex}/)\n |> filter(fn: (r) => r._field == \"in-octets\" or r._field == \"out-octets\")\n |> toFloat()\n |> derivative(unit: 1s, nonNegative: true)\n |> map(fn: (r) => ({r with _value: if r._value < 0.0 then 0.0 else r._value}))",
"refId": "A"
}
@ -167,90 +67,23 @@
"type": "timeseries"
},
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"description": "Rate of packets received and sent per interface, computed as the derivative of cumulative counters. Unit: packets per second.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"color": {"mode": "palette-classic"},
"custom": {"axisBorderShow": false,"axisCenteredZero": false,"axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 10,"gradientMode": "none","hideFrom": {"legend": false,"tooltip": false,"viz": false},"lineInterpolation": "linear","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": false,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "red","value": 80}]},
"unit": "pps"
}
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 10
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 10},
"id": 2,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"options": {"legend": {"calcs": ["mean","max"],"displayMode": "table","placement": "bottom"},"tooltip": {"mode": "multi","sort": "desc"}},
"targets": [
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"query": "from(bucket: \"telemetry\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> filter(fn: (r) => r.name =~ /${interface:regex}/)\n |> filter(fn: (r) => r._field == \"in-pkts\" or r._field == \"out-pkts\")\n |> toFloat()\n |> derivative(unit: 1s, nonNegative: true)\n |> map(fn: (r) => ({r with _value: if r._value < 0.0 then 0.0 else r._value}))",
"refId": "A"
}
@ -259,81 +92,23 @@
"type": "timeseries"
},
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"description": "Top interfaces ranked by total bytes (received + sent) over the selected time range.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisLabel": "",
"axisPlacement": "auto",
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"color": {"mode": "palette-classic"},
"custom": {"axisBorderShow": false,"axisCenteredZero": false,"axisLabel": "","axisPlacement": "auto","fillOpacity": 80,"gradientMode": "none","hideFrom": {"legend": false,"tooltip": false,"viz": false},"lineWidth": 1,"scaleDistribution": {"type": "linear"},"thresholdsStyle": {"mode": "off"}},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]},
"unit": "decbytes"
}
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 20
},
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 20},
"id": 3,
"options": {
"barRadius": 0,
"barWidth": 0.6,
"fullHighlight": false,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom"
},
"orientation": "horizontal",
"showValue": "auto",
"stacking": "none",
"tooltip": {
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": 0
},
"options": {"barRadius": 0,"barWidth": 0.6,"fullHighlight": false,"groupWidth": 0.7,"legend": {"calcs": [],"displayMode": "list","placement": "bottom"},"orientation": "horizontal","showValue": "auto","stacking": "none","tooltip": {"mode": "single","sort": "none"},"xTickLabelRotation": 0},
"targets": [
{
"datasource": {
"type": "influxdb",
"uid": "obmp_influxdb"
},
"datasource": {"type": "influxdb","uid": "obmp_influxdb"},
"query": "from(bucket: \"telemetry\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"interface_counters\")\n |> filter(fn: (r) => r.source =~ /${router:regex}/)\n |> filter(fn: (r) => r.name =~ /${interface:regex}/)\n |> filter(fn: (r) => r._field == \"in-octets\" or r._field == \"out-octets\")\n |> toFloat()\n |> derivative(unit: 1s, nonNegative: true)\n |> group(columns: [\"source\", \"name\", \"_field\"])\n |> sum()\n |> group(columns: [\"source\", \"name\"])\n |> sum()\n |> group()\n |> sort(columns: [\"_value\"], desc: true)\n |> limit(n: 20)",
"refId": "A"
}
@ -342,23 +117,11 @@
"type": "barchart"
},
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"gridPos": {
"h": 4,
"w": 24,
"x": 0,
"y": 30
},
"datasource": {"type": "datasource","uid": "grafana"},
"gridPos": {"h": 4,"w": 24,"x": 0,"y": 30},
"id": 4,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"code": {"language": "plaintext","showLineNumbers": false,"showMiniMap": false},
"content": "## Interface Utilization Dashboard\n\nThis dashboard displays real-time interface utilization metrics collected via **gNMI streaming telemetry** from IOS-XR routers.\n\n- **Data source:** InfluxDB (Telegraf gNMI input plugin)\n- **YANG model:** OpenConfig (`openconfig-interfaces`)\n- **Subscription path:** `/interfaces/interface/state/counters`\n- **Sample interval:** 10 seconds\n\nUse the **Router** and **Interface** template variables at the top to filter the view.",
"mode": "markdown"
},
@ -368,15 +131,8 @@
],
"schemaVersion": 39,
"style": "dark",
"tags": [
"obmp-telemetry",
"obmp",
"obmp-nav"
],
"time": {
"from": "now-1h",
"to": "now"
},
"tags": ["obmp-telemetry"],
"time": {"from": "now-1h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "Interface Utilization",

View File

@ -24,29 +24,25 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"id": 3,
"iteration": 1654876929746,
"links": [
{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}
],
"links": [],
"liveNow": false,
"panels": [
{
"aliasColors": {},
"breakPoint": "50%",
"combine": {
"label": "Others",
"threshold": 0
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "IPv4 vs IPv6 prefix count advertised by this ASN.",
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {"hideFrom": {"legend": false,"tooltip": false,"viz": false}},
"decimals": 0,
"mappings": [],
"unit": "none"
},
"overrides": []
},
"decimals": 0,
"fontSize": "80%",
"format": "none",
"gridPos": {
"h": 8,
"w": 5,
@ -54,25 +50,24 @@
"y": 0
},
"id": 6,
"links": [],
"options": {
"displayLabels": ["value"],
"legend": {"calcs": [],"displayMode": "table","placement": "bottom","values": ["value","percent"]},
"pieType": "pie",
"reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},
"tooltip": {"mode": "single","sort": "none"}
"legend": {
"show": true,
"values": true
},
"pluginVersion": "9.1.7",
"legendType": "Under graph",
"links": [],
"maxDataPoints": 3,
"nullPointMode": "connected",
"pieType": "pie",
"strokeWidth": 1,
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"alias": "",
"format": "time_series",
"rawSql": "SELECT\n max(timestamp) as time,\n count(*) as \"ipv4\"\nFROM\n global_ip_rib\nWHERE\n recv_origin_as = [[asn_num]]\n and family(prefix) = 4\nGROUP BY prefix\n",
"refId": "A"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"alias": "",
"format": "time_series",
"rawSql": "SELECT\n max(timestamp) as time,\n count(*) as \"ipv6\"\nFROM\n global_ip_rib\nWHERE\n recv_origin_as = [[asn_num]]\n and family(prefix) = 6\nGROUP BY prefix\n",
@ -80,7 +75,8 @@
}
],
"title": "Advertised IP Addresses",
"type": "piechart"
"type": "grafana-piechart-panel",
"valueName": "total"
},
{
"datasource": {
@ -179,45 +175,99 @@
"type": "stat"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "IPv4/IPv6 prefixes originated by this ASN over time, with RPKI/IRR coverage (from stats_ip_origins).",
"decimals": 0,
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"custom": {"axisCenteredZero": false,"axisColorMode": "text","axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 10,"gradientMode": "none","hideFrom": {"legend": false,"tooltip": false,"viz": false},"lineInterpolation": "linear","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "auto","spanNulls": false,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},
"decimals": 0,
"mappings": [],
"unit": "none"
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 15,
"x": 9,
"y": 0
},
"hiddenSeries": false,
"id": 14,
"links": [],
"options": {
"legend": {"calcs": ["min","max","mean"],"displayMode": "table","placement": "right","showLegend": true},
"tooltip": {"mode": "multi","sort": "none"}
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"hideEmpty": false,
"hideZero": false,
"max": true,
"min": true,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"pluginVersion": "9.1.7",
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "8.5.4",
"pointradius": 5,
"points": true,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"alias": "",
"format": "time_series",
"rawSql": "SELECT\n $__time(interval_time),\n v4_prefixes,v6_prefixes,v4_with_rpki,v6_with_rpki,v4_with_irr,v6_with_irr\nFROM\n stats_ip_origins\nWHERE\n $__timeFilter(interval_time) and asn = [[asn_num]]\nORDER BY interval_time asc\n",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": "24h",
"timeRegions": [],
"title": "Originating Prefix Trend",
"type": "timeseries"
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"decimals": 0,
"format": "none",
"logBase": 1,
"show": true
},
{
"format": "short",
"logBase": 1,
"show": false
}
],
"yaxis": {
"align": false
}
},
{
"datasource": {
@ -934,9 +984,7 @@
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp",
"obmp-nav",
"operations"
"obmp-base"
],
"templating": {
"list": [

View File

@ -24,10 +24,8 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [
{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}
],
"id": 4,
"links": [],
"liveNow": false,
"panels": [
{
@ -184,25 +182,7 @@
]
}
},
"overrides": [
{
"matcher": {"id": "byName","options": "name"},
"properties": [
{"id": "links","value": [{"title": "Open Router Detail","url": "/d/obmp-router-detail/router-detail?var-router_hash=${__data.fields[\"hash_id\"]}"}]}
]
},
{
"matcher": {"id": "byName","options": "hash_id"},
"properties": [{"id": "custom.hidden","value": true}]
},
{
"matcher": {"id": "byName","options": "state"},
"properties": [
{"id": "custom.displayMode","value": "color-background"},
{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}]}
]
}
]
"overrides": []
},
"gridPos": {
"h": 11,
@ -235,7 +215,7 @@
"hide": false,
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select max(r.hash_id::text) as hash_id,max(r.timestamp) as timestamp,r.name,max(ip_address) as ip_address,max(r.state) as state,\n count(*) as peers,max(description) as description, CASE WHEN max(r.state) = 'up' THEN 1 ELSE 0 END as stateBool\n from routers r\n JOIN bgp_peers p on (r.hash_id = p.router_hash_id)\n GROUP BY r.name;",
"rawSql": "select max(r.timestamp) as timestamp,r.name,max(ip_address) as ip_address,max(r.state) as state,\n count(*) as peers,max(description) as description, CASE WHEN max(r.state) = 'up' THEN 1 ELSE 0 END as stateBool\n from routers r\n JOIN bgp_peers p on (r.hash_id = p.router_hash_id)\n GROUP BY r.name;",
"refId": "A",
"select": [
[
@ -417,25 +397,7 @@
]
}
},
"overrides": [
{
"matcher": {"id": "byName","options": "PeerName"},
"properties": [
{"id": "links","value": [{"title": "Open Peer Detail","url": "/d/obmp-peer-detail/peer-detail?var-peer_hash=${__data.fields[\"peer_hash_id\"]}"}]}
]
},
{
"matcher": {"id": "byName","options": "peer_hash_id"},
"properties": [{"id": "custom.hidden","value": true}]
},
{
"matcher": {"id": "byName","options": "State"},
"properties": [
{"id": "custom.displayMode","value": "color-background"},
{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}]}
]
}
]
"overrides": []
},
"gridPos": {
"h": 14,
@ -473,7 +435,7 @@
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": " SELECT\n p.peer_hash_id as peer_hash_id,\n max(RouterName) as \"RouterName\",\n max(PeerName) as \"PeerName\",\n max(PeerIP) as \"PeerIP\",\n max(PeerASN) as \"PeerASN\",\n max(peer_state) as \"State\",\n max(LastModified) as \"LastModified\",\n max(v4_prefixes) as \"IPv4 Prefixes\",\n max(v6_prefixes) as \"IPv6 Prefixes\",\n CASE WHEN max(peer_state) = 'up' THEN 1 ELSE 0 END as stateBool\nFROM v_peers p\n LEFT JOIN stats_peer_rib s ON (p.peer_hash_id = s.peer_hash_id\n AND s.interval_time >= now() - interval '20 minutes')\nGROUP BY p.peer_hash_id;\n",
"rawSql": " SELECT\n max(RouterName) as \"RouterName\",\n max(PeerName) as \"PeerName\",\n max(PeerIP) as \"PeerIP\",\n max(PeerASN) as \"PeerASN\",\n max(peer_state) as \"State\",\n max(LastModified) as \"LastModified\",\n max(v4_prefixes) as \"IPv4 Prefixes\",\n max(v6_prefixes) as \"IPv6 Prefixes\",\n CASE WHEN max(peer_state) = 'up' THEN 1 ELSE 0 END as stateBool\nFROM v_peers p\n LEFT JOIN stats_peer_rib s ON (p.peer_hash_id = s.peer_hash_id\n AND s.interval_time >= now() - interval '20 minutes')\nGROUP BY p.peer_hash_id;\n",
"refId": "A",
"select": [
[
@ -502,9 +464,7 @@
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp",
"obmp-nav",
"operations"
"obmp-base"
],
"templating": {
"list": []

View File

@ -1,536 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"description": "BGP peer session health, uptime, and flap analysis. Teaches session stability and how to diagnose flapping peers.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: A healthy BGP mesh shows all peers UP continuously. Any gap in the UP state represents a session flap \u2014 investigate the reset reason.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"fillOpacity": 70,
"lineWidth": 0,
"spanNulls": false
},
"mappings": [
{
"options": {
"down": {
"color": "red",
"index": 1,
"text": "DOWN"
},
"up": {
"color": "green",
"index": 0,
"text": "UP"
}
},
"type": "value"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 1
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"alignValue": "left",
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"mergeValues": true,
"rowHeight": 0.9,
"showValue": "auto",
"tooltip": {
"mode": "single"
}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT\n $__timeGroupAlias(e.timestamp,'1m'),\n COALESCE(p.name, p.peer_addr::text) AS metric,\n CASE WHEN e.state = 'up' THEN 1 ELSE 0 END AS \"value\"\nFROM peer_event_log e\nJOIN bgp_peers p ON p.hash_id = e.peer_hash_id\nWHERE $__timeFilter(e.timestamp)\nORDER BY 1, 2",
"refId": "A"
}
],
"title": "Peer Session State Timeline",
"type": "state-timeline"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Current state of all BGP peers. Learn: 'bmp_reason' tells you why BMP reporting stopped. 'bgp_err_code' shows BGP NOTIFICATION error codes.",
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "State"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background"
},
{
"id": "mappings",
"value": [
{
"options": {
"down": {
"color": "red",
"index": 1,
"text": "DOWN"
},
"up": {
"color": "green",
"index": 0,
"text": "UP"
}
},
"type": "value"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "Peer"
},
"properties": [
{
"id": "custom.width",
"value": 200
}
]
},
{
"matcher": {
"id": "byName",
"options": "AS"
},
"properties": [
{
"id": "custom.width",
"value": 80
}
]
}
]
},
"gridPos": {
"h": 12,
"w": 24,
"x": 0,
"y": 8
},
"id": 2,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": false,
"displayName": "State"
}
]
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n COALESCE(p.name, p.peer_addr::text) AS \"Peer\",\n p.peer_addr AS \"Address\",\n p.peer_as AS \"AS\",\n p.state AS \"State\",\n p.timestamp AS \"Last State Change\",\n p.error_text AS \"Last Error\",\n p.local_hold_time AS \"Hold Time\"\nFROM bgp_peers p\nWHERE p.isprepolicy = true\nORDER BY p.state, p.peer_addr",
"refId": "A"
}
],
"title": "Current Peer State",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Flap count = number of times a peer went from UP to DOWN. A peer flapping more than 2 times per hour needs investigation.",
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Flap Count"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 5
}
]
}
}
]
}
]
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 20
},
"id": 3,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Flap Count"
}
]
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n COALESCE(p.name, p.peer_addr::text) AS \"Peer\",\n p.peer_addr AS \"Address\",\n p.peer_as AS \"AS\",\n COUNT(CASE WHEN e.state = 'down' THEN 1 END) AS \"Flap Count\",\n MIN(e.timestamp) AS \"First Event\",\n MAX(e.timestamp) AS \"Last Event\"\nFROM peer_event_log e\nJOIN bgp_peers p ON p.hash_id = e.peer_hash_id\nWHERE $__timeFilter(e.timestamp)\nGROUP BY p.name, p.peer_addr, p.peer_as\nORDER BY \"Flap Count\" DESC",
"refId": "A"
}
],
"title": "Peer Flap Analysis",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "yellow",
"value": 50
},
{
"color": "green",
"value": 90
}
]
},
"unit": "percent",
"max": 100,
"min": 0
}
},
"gridPos": {
"h": 8,
"w": 8,
"x": 0,
"y": 30
},
"id": 4,
"options": {
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showThresholdLabels": false,
"showThresholdMarkers": true,
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n ROUND(100.0 * SUM(CASE WHEN state = 'up' THEN 1 ELSE 0 END) / NULLIF(COUNT(*),0), 1) AS \"Mesh Health %\"\nFROM bgp_peers WHERE isprepolicy = true",
"refId": "A"
}
],
"title": "Overall Peer Mesh Health",
"type": "gauge"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 1
}
]
},
"unit": "short",
"mappings": [
{
"options": {
"0": {
"color": "red",
"index": 0,
"text": "DOWN"
}
},
"type": "value"
}
]
}
},
"gridPos": {
"h": 8,
"w": 8,
"x": 8,
"y": 30
},
"id": 5,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n SUM(CASE WHEN state = 'up' THEN 1 ELSE 0 END) AS \"Peers UP\"\nFROM bgp_peers WHERE isprepolicy = true",
"refId": "A"
}
],
"title": "Peers Currently UP",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 5
}
]
},
"unit": "short"
}
},
"gridPos": {
"h": 8,
"w": 8,
"x": 16,
"y": 30
},
"id": 6,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n COUNT(CASE WHEN state = 'down' THEN 1 END) AS \"Flap Events (24h)\"\nFROM peer_event_log\nWHERE timestamp > NOW() - INTERVAL '24 hours' AND state = 'down'",
"refId": "A"
}
],
"title": "Flap Events (24h)",
"type": "stat"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp",
"bgp",
"peers",
"flap",
"obmp-nav"
],
"time": {
"from": "now-24h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Peer Session Health & Flap Analysis",
"uid": "obmp-learn-02",
"version": 1
}

View File

@ -24,11 +24,9 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"id": 5,
"iteration": 1654877090626,
"links": [
{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -51,53 +49,45 @@
"type": "text"
},
{
"circleMaxSize": "15",
"circleMinSize": 2,
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Geolocation of the matched prefix (from the geo_ip table).",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"custom": {"hideFrom": {"legend": false,"tooltip": false,"viz": false}},
"mappings": [],
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}
},
"overrides": []
},
"decimals": 0,
"esMetric": "Count",
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"hideEmpty": false,
"hideZero": false,
"id": 17,
"options": {
"basemap": {"config": {},"name": "Layer 0","type": "default"},
"controls": {"mouseWheelZoom": false,"showAttribution": true,"showDebug": false,"showMeasure": false,"showScale": false,"showZoom": true},
"layers": [
{
"config": {
"showLegend": false,
"style": {
"color": {"fixed": "red"},
"opacity": 0.7,
"rotation": {"fixed": 0,"max": 360,"min": -360,"mode": "mod"},
"size": {"fixed": 8,"max": 15,"min": 2},
"symbol": {"fixed": "img/icons/marker/circle.svg","mode": "fixed"},
"textConfig": {"fontSize": 12,"offsetX": 0,"offsetY": 0,"textAlign": "center","textBaseline": "middle"}
}
},
"location": {"latitude": "latitude","longitude": "longitude","mode": "coords"},
"name": "Prefix Location",
"tooltip": true,
"type": "markers"
}
],
"tooltip": {"mode": "details"},
"view": {"allLayers": true,"id": "zero","lat": 0,"lon": 0,"zoom": 1}
"initialZoom": "1",
"locationData": "table",
"mapCenter": "(0°, 0°)",
"mapCenterLatitude": 0,
"mapCenterLongitude": 0,
"maxDataPoints": 1,
"mouseWheelZoom": false,
"showLegend": false,
"stickyLabels": false,
"tableQueryOptions": {
"geohashField": "geohash",
"labelField": "name",
"latitudeField": "latitude",
"longitudeField": "longitude",
"metricField": "value",
"queryType": "coordinates"
},
"pluginVersion": "9.1.7",
"targets": [
{
"datasource": {
@ -133,8 +123,12 @@
]
}
],
"thresholds": "0,10",
"title": "Prefix Location",
"type": "geomap"
"type": "grafana-worldmap-panel",
"unitPlural": "",
"unitSingle": "",
"valueName": "current"
},
{
"datasource": {
@ -323,19 +317,12 @@
"type": "piechart"
},
{
"columns": [],
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"custom": {"align": "auto","displayMode": "auto","inspect": false},
"mappings": [],
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}
},
"overrides": []
},
"fontSize": "100%",
"gridPos": {
"h": 6,
"w": 24,
@ -344,10 +331,53 @@
},
"id": 12,
"links": [],
"options": {
"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "Time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"decimals": 2,
"mappingType": 1,
"pattern": "raw_output",
"preserveFormat": true,
"sanitize": false,
"thresholds": [],
"type": "string",
"unit": "short"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "string",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@ -382,22 +412,16 @@
}
],
"title": "ASN Info",
"type": "table"
"transform": "table",
"type": "table-old"
},
{
"columns": [],
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"custom": {"align": "auto","displayMode": "auto","inspect": false},
"mappings": [],
"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]}
},
"overrides": []
},
"fontSize": "100%",
"gridPos": {
"h": 4,
"w": 24,
@ -406,10 +430,75 @@
},
"id": 13,
"links": [],
"options": {
"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},
"showHeader": true
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "Time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colorMode": "cell",
"colors": [
"#cca300",
"#e24d42",
"#9ac48a"
],
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"decimals": 0,
"mappingType": 1,
"pattern": "irr_origin_as",
"thresholds": [
"0",
"1"
],
"type": "number",
"unit": "none"
},
{
"alias": "",
"align": "auto",
"colorMode": "cell",
"colors": [
"#cca300",
"#e24d42",
"#9ac48a"
],
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"decimals": 0,
"mappingType": 1,
"pattern": "rpki_origin_as",
"thresholds": [
"0",
"1"
],
"type": "number",
"unit": "none"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "string",
"unit": "short"
}
],
"targets": [
{
"alias": "",
@ -444,7 +533,8 @@
}
],
"title": "Prefix Info",
"type": "table"
"transform": "table",
"type": "table-old"
},
{
"datasource": {
@ -671,9 +761,7 @@
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp",
"obmp-nav",
"operations"
"obmp-base"
],
"templating": {
"list": [

View File

@ -1,200 +0,0 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "Per-peer drilldown — BGP session identity, state history, prefix counts, update/withdraw rate, recent events and negotiated capabilities for a single BGP peer.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}
],
"panels": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Current BGP session state for this peer.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null},{"color": "green","value": 1}]},"mappings": [{"options": {"0": {"color": "red","index": 1,"text": "DOWN"},"1": {"color": "green","index": 0,"text": "UP"}},"type": "value"}],"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 0,"y": 0},
"id": 1,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, CASE WHEN peer_state = 'up' THEN 1 ELSE 0 END AS \"Peer State\"\nFROM v_peers WHERE peer_hash_id = '$peer_hash'::uuid","refId": "A"}],
"title": "Peer State",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "IPv4 prefixes from this peer (latest stats_peer_rib interval).",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 4,"y": 0},
"id": 2,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, COALESCE((SELECT v4_prefixes FROM stats_peer_rib WHERE peer_hash_id = '$peer_hash'::uuid ORDER BY interval_time DESC LIMIT 1),0) AS \"IPv4 Prefixes\"","refId": "A"}],
"title": "IPv4 Prefixes",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "IPv6 prefixes from this peer (latest stats_peer_rib interval).",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 8,"y": 0},
"id": 3,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, COALESCE((SELECT v6_prefixes FROM stats_peer_rib WHERE peer_hash_id = '$peer_hash'::uuid ORDER BY interval_time DESC LIMIT 1),0) AS \"IPv6 Prefixes\"","refId": "A"}],
"title": "IPv6 Prefixes",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Updates received from this peer in the last hour (from stats_chg_bypeer).",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 12,"y": 0},
"id": 4,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, COALESCE(SUM(updates),0) AS \"Updates (1h)\"\nFROM stats_chg_bypeer\nWHERE peer_hash_id = '$peer_hash'::uuid AND interval_time > NOW() - INTERVAL '1 hour'","refId": "A"}],
"title": "Updates (1h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Withdraws received from this peer in the last hour (from stats_chg_bypeer).",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 16,"y": 0},
"id": 5,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, COALESCE(SUM(withdraws),0) AS \"Withdraws (1h)\"\nFROM stats_chg_bypeer\nWHERE peer_hash_id = '$peer_hash'::uuid AND interval_time > NOW() - INTERVAL '1 hour'","refId": "A"}],
"title": "Withdraws (1h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Session down-events for this peer in the last 24 hours.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 5}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 20,"y": 0},
"id": 6,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"Flaps (24h)\"\nFROM peer_event_log\nWHERE peer_hash_id = '$peer_hash'::uuid AND state = 'down' AND timestamp > NOW() - INTERVAL '24 hours'","refId": "A"}],
"title": "Flap Events (24h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Identity and session parameters for the selected peer.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [{"matcher": {"id": "byName","options": "State"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}]}]}]
},
"gridPos": {"h": 5,"w": 24,"x": 0,"y": 4},
"id": 7,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n routername AS \"Router\",\n peername AS \"Peer\",\n host(peerip) AS \"Address\",\n peerasn AS \"Peer AS\",\n as_name AS \"AS Name\",\n peer_state AS \"State\",\n peerholdtime AS \"Hold Time\",\n table_name AS \"Table\",\n lastmodified AS \"Last Change\",\n lastdownmessage AS \"Last Down Message\"\nFROM v_peers\nWHERE peer_hash_id = '$peer_hash'::uuid","refId": "A"}],
"title": "Peer Info",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Session state over the selected range. Any gap to DOWN is a flap.",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"custom": {"fillOpacity": 70,"lineWidth": 0,"spanNulls": false},
"mappings": [{"options": {"0": {"color": "red","index": 1,"text": "DOWN"},"1": {"color": "green","index": 0,"text": "UP"}},"type": "value"}],
"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null},{"color": "green","value": 1}]}
}
},
"gridPos": {"h": 7,"w": 24,"x": 0,"y": 9},
"id": 8,
"options": {"alignValue": "left","legend": {"displayMode": "list","placement": "bottom","showLegend": false},"mergeValues": true,"rowHeight": 0.9,"showValue": "auto","tooltip": {"mode": "single"}},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT\n $__timeGroupAlias(e.timestamp,'1m'),\n 'Session' AS metric,\n CASE WHEN e.state = 'up' THEN 1 ELSE 0 END AS \"value\"\nFROM peer_event_log e\nWHERE e.peer_hash_id = '$peer_hash'::uuid AND $__timeFilter(e.timestamp)\nORDER BY 1","refId": "A"}],
"title": "Session State Timeline",
"type": "state-timeline"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP update vs withdraw rate for this peer (from stats_chg_bypeer).",
"fieldConfig": {
"defaults": {"color": {"mode": "palette-classic"},"custom": {"axisCenteredZero": false,"axisColorMode": "text","axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 20,"gradientMode": "none","lineInterpolation": "smooth","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": false,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},"unit": "short"},
"overrides": [{"matcher": {"id": "byName","options": "Withdraws"},"properties": [{"id": "color","value": {"fixedColor": "red","mode": "fixed"}}]},{"matcher": {"id": "byName","options": "Updates"},"properties": [{"id": "color","value": {"fixedColor": "green","mode": "fixed"}}]}]
},
"gridPos": {"h": 9,"w": 12,"x": 0,"y": 16},
"id": 9,
"options": {"legend": {"calcs": ["sum"],"displayMode": "table","placement": "bottom","showLegend": true},"tooltip": {"mode": "multi","sort": "none"}},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT\n $__timeGroupAlias(interval_time,'5m'),\n SUM(updates) AS \"Updates\",\n SUM(withdraws) AS \"Withdraws\"\nFROM stats_chg_bypeer\nWHERE peer_hash_id = '$peer_hash'::uuid AND $__timeFilter(interval_time)\nGROUP BY 1\nORDER BY 1","refId": "A"}],
"title": "Update / Withdraw Rate",
"type": "timeseries"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Prefix count from this peer over time (from stats_peer_rib).",
"fieldConfig": {
"defaults": {"color": {"mode": "palette-classic"},"custom": {"axisCenteredZero": false,"axisColorMode": "text","axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 20,"gradientMode": "none","lineInterpolation": "smooth","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": true,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},"unit": "short"}
},
"gridPos": {"h": 9,"w": 12,"x": 12,"y": 16},
"id": 10,
"options": {"legend": {"calcs": ["last"],"displayMode": "table","placement": "bottom","showLegend": true},"tooltip": {"mode": "multi","sort": "none"}},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT\n $__timeGroupAlias(interval_time,'5m'),\n MAX(v4_prefixes) AS \"IPv4 Prefixes\",\n MAX(v6_prefixes) AS \"IPv6 Prefixes\"\nFROM stats_peer_rib\nWHERE peer_hash_id = '$peer_hash'::uuid AND $__timeFilter(interval_time)\nGROUP BY 1\nORDER BY 1","refId": "A"}],
"title": "Prefix Count Trend",
"type": "timeseries"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Recent BGP session state changes for this peer.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [{"matcher": {"id": "byName","options": "State"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}]}]}]
},
"gridPos": {"h": 9,"w": 24,"x": 0,"y": 25},
"id": 11,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "Time"}]},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n e.timestamp AS \"Time\",\n e.state AS \"State\",\n e.bmp_reason AS \"BMP Reason\",\n e.bgp_err_code AS \"BGP Err Code\",\n e.bgp_err_subcode AS \"BGP Err Subcode\",\n e.error_text AS \"Reason\"\nFROM peer_event_log e\nWHERE e.peer_hash_id = '$peer_hash'::uuid AND $__timeFilter(e.timestamp)\nORDER BY e.timestamp DESC\nLIMIT 100","refId": "A"}],
"title": "Recent Peer Events",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP capabilities negotiated on this session.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto","cellOptions": {"type": "auto","wrapText": true}}},
"overrides": [
{"matcher": {"id": "byName","options": "Sent Capabilities"},"properties": [{"id": "custom.width","value": 600}]},
{"matcher": {"id": "byName","options": "Received Capabilities"},"properties": [{"id": "custom.width","value": 600}]}
]
},
"gridPos": {"h": 8,"w": 24,"x": 0,"y": 34},
"id": 12,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n sentcapabilities AS \"Sent Capabilities\",\n recvcapabilities AS \"Received Capabilities\"\nFROM v_peers\nWHERE peer_hash_id = '$peer_hash'::uuid","refId": "A"}],
"title": "Negotiated Capabilities",
"type": "table"
}
],
"refresh": "1m",
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","obmp-nav","operations","peer"],
"templating": {
"list": [
{
"current": {},
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"definition": "select peername as __text, peer_hash_id as __value from v_peers where length(peername) > 0",
"hide": 0,
"includeAll": false,
"label": "Peer",
"multi": false,
"name": "peer_hash",
"options": [],
"query": "select peername as __text, peer_hash_id as __value from v_peers where length(peername) > 0",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
}
]
},
"time": {"from": "now-6h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "Peer Detail",
"uid": "obmp-peer-detail",
"version": 1
}

View File

@ -1,170 +0,0 @@
{
"annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]},
"description": "Per-router drilldown — BMP state, peer health, prefix counts, update rate and recent session events for a single monitored router.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}
],
"panels": [
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BMP session state for this router. Should be UP.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "red","value": null},{"color": "green","value": 1}]},"mappings": [{"options": {"0": {"color": "red","index": 1,"text": "DOWN"},"1": {"color": "green","index": 0,"text": "UP"}},"type": "value"}],"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 0,"y": 0},
"id": 1,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, CASE WHEN state = 'up' THEN 1 ELSE 0 END AS \"Router State\"\nFROM routers WHERE hash_id = '$router_hash'::uuid","refId": "A"}],
"title": "Router State",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP peers on this router that are currently up (pre-policy Adj-RIB-In).",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 4,"y": 0},
"id": 2,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"Peers Up\"\nFROM bgp_peers\nWHERE router_hash_id = '$router_hash'::uuid AND isprepolicy = true AND state = 'up'","refId": "A"}],
"title": "Peers Up",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP peers on this router that are not up. Investigate any non-zero value.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "red","value": 1}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 8,"y": 0},
"id": 3,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"Peers Down\"\nFROM bgp_peers\nWHERE router_hash_id = '$router_hash'::uuid AND isprepolicy = true AND state != 'up'","refId": "A"}],
"title": "Peers Down",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Total IPv4 prefixes across this router's peers (latest stats_peer_rib interval per peer).",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 12,"y": 0},
"id": 4,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, COALESCE(SUM(s.v4_prefixes),0) AS \"IPv4 Prefixes\"\nFROM bgp_peers p\nLEFT JOIN LATERAL (SELECT v4_prefixes FROM stats_peer_rib sr WHERE sr.peer_hash_id = p.hash_id ORDER BY interval_time DESC LIMIT 1) s ON true\nWHERE p.router_hash_id = '$router_hash'::uuid AND p.isprepolicy = true","refId": "A"}],
"title": "IPv4 Prefixes",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Total IPv6 prefixes across this router's peers (latest stats_peer_rib interval per peer).",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "blue","value": null}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 16,"y": 0},
"id": 5,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, COALESCE(SUM(s.v6_prefixes),0) AS \"IPv6 Prefixes\"\nFROM bgp_peers p\nLEFT JOIN LATERAL (SELECT v6_prefixes FROM stats_peer_rib sr WHERE sr.peer_hash_id = p.hash_id ORDER BY interval_time DESC LIMIT 1) s ON true\nWHERE p.router_hash_id = '$router_hash'::uuid AND p.isprepolicy = true","refId": "A"}],
"title": "IPv6 Prefixes",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Peer session down-events on this router in the last hour.",
"fieldConfig": {"defaults": {"color": {"mode": "thresholds"},"thresholds": {"mode": "absolute","steps": [{"color": "green","value": null},{"color": "yellow","value": 1},{"color": "red","value": 5}]},"unit": "short"}},
"gridPos": {"h": 4,"w": 4,"x": 20,"y": 0},
"id": 6,
"options": {"colorMode": "background","graphMode": "none","justifyMode": "auto","orientation": "auto","reduceOptions": {"calcs": ["lastNotNull"],"fields": "","values": false},"textMode": "auto"},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT NOW() AS time, count(*) AS \"Flaps (1h)\"\nFROM peer_event_log e\nJOIN bgp_peers p ON p.hash_id = e.peer_hash_id\nWHERE p.router_hash_id = '$router_hash'::uuid AND e.state = 'down' AND e.timestamp > NOW() - INTERVAL '1 hour'","refId": "A"}],
"title": "Flap Events (1h)",
"type": "stat"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Identity and BMP state for the selected router.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [{"matcher": {"id": "byName","options": "State"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}]}]}]
},
"gridPos": {"h": 5,"w": 24,"x": 0,"y": 4},
"id": 7,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n r.name AS \"Router\",\n host(r.ip_address) AS \"Mgmt IP\",\n host(r.bgp_id) AS \"BGP ID\",\n r.router_as AS \"AS\",\n r.state AS \"State\",\n r.timestamp AS \"Last Update\",\n r.description AS \"Description\"\nFROM routers r\nWHERE r.hash_id = '$router_hash'::uuid","refId": "A"}],
"title": "Router Info",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP peers on this router with state, ASN and latest prefix counts. Click a peer to open Peer Detail.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [
{"matcher": {"id": "byName","options": "State"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}]}]},
{"matcher": {"id": "byName","options": "Peer"},"properties": [{"id": "links","value": [{"title": "Open Peer Detail","url": "/d/obmp-peer-detail/peer-detail?var-peer_hash=${__data.fields[\"peer_hash_id\"]}"}]}]},
{"matcher": {"id": "byName","options": "peer_hash_id"},"properties": [{"id": "custom.hidden","value": true}]}
]
},
"gridPos": {"h": 11,"w": 24,"x": 0,"y": 9},
"id": 8,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": false,"displayName": "State"}]},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n p.hash_id AS peer_hash_id,\n COALESCE(NULLIF(p.name,''), p.peer_addr::text) AS \"Peer\",\n host(p.peer_addr) AS \"Address\",\n p.peer_as AS \"AS\",\n p.state AS \"State\",\n COALESCE(s.v4_prefixes,0) AS \"IPv4 Prefixes\",\n COALESCE(s.v6_prefixes,0) AS \"IPv6 Prefixes\",\n p.timestamp AS \"Last Change\"\nFROM bgp_peers p\nLEFT JOIN LATERAL (SELECT v4_prefixes, v6_prefixes FROM stats_peer_rib sr WHERE sr.peer_hash_id = p.hash_id ORDER BY interval_time DESC LIMIT 1) s ON true\nWHERE p.router_hash_id = '$router_hash'::uuid AND p.isprepolicy = true\nORDER BY p.state, p.peer_addr","refId": "A"}],
"title": "Peers",
"type": "table"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "BGP update vs withdraw rate across this router's peers (from stats_chg_bypeer).",
"fieldConfig": {
"defaults": {"color": {"mode": "palette-classic"},"custom": {"axisCenteredZero": false,"axisColorMode": "text","axisLabel": "","axisPlacement": "auto","barAlignment": 0,"drawStyle": "line","fillOpacity": 20,"gradientMode": "none","lineInterpolation": "smooth","lineWidth": 1,"pointSize": 5,"scaleDistribution": {"type": "linear"},"showPoints": "never","spanNulls": false,"stacking": {"group": "A","mode": "none"},"thresholdsStyle": {"mode": "off"}},"unit": "short"},
"overrides": [{"matcher": {"id": "byName","options": "Withdraws"},"properties": [{"id": "color","value": {"fixedColor": "red","mode": "fixed"}}]},{"matcher": {"id": "byName","options": "Updates"},"properties": [{"id": "color","value": {"fixedColor": "green","mode": "fixed"}}]}]
},
"gridPos": {"h": 9,"w": 24,"x": 0,"y": 20},
"id": 9,
"options": {"legend": {"calcs": ["sum"],"displayMode": "table","placement": "bottom","showLegend": true},"tooltip": {"mode": "multi","sort": "none"}},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "time_series","rawSql": "SELECT\n $__timeGroupAlias(c.interval_time,'5m'),\n SUM(c.updates) AS \"Updates\",\n SUM(c.withdraws) AS \"Withdraws\"\nFROM stats_chg_bypeer c\nJOIN bgp_peers p ON p.hash_id = c.peer_hash_id\nWHERE p.router_hash_id = '$router_hash'::uuid AND $__timeFilter(c.interval_time)\nGROUP BY 1\nORDER BY 1","refId": "A"}],
"title": "BGP Update Rate",
"type": "timeseries"
},
{
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"description": "Recent BGP session state changes for this router's peers.",
"fieldConfig": {
"defaults": {"custom": {"align": "auto","displayMode": "auto"}},
"overrides": [{"matcher": {"id": "byName","options": "State"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"down": {"color": "red","index": 1,"text": "DOWN"},"up": {"color": "green","index": 0,"text": "UP"}},"type": "value"}]}]}]
},
"gridPos": {"h": 9,"w": 24,"x": 0,"y": 29},
"id": 10,
"options": {"footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false},"showHeader": true,"sortBy": [{"desc": true,"displayName": "Time"}]},
"targets": [{"datasource": {"type": "postgres","uid": "obmp_postgres"},"format": "table","rawSql": "SELECT\n e.timestamp AS \"Time\",\n COALESCE(NULLIF(p.name,''), p.peer_addr::text) AS \"Peer\",\n host(p.peer_addr) AS \"Address\",\n e.state AS \"State\",\n e.error_text AS \"Reason\"\nFROM peer_event_log e\nJOIN bgp_peers p ON p.hash_id = e.peer_hash_id\nWHERE p.router_hash_id = '$router_hash'::uuid AND $__timeFilter(e.timestamp)\nORDER BY e.timestamp DESC\nLIMIT 100","refId": "A"}],
"title": "Recent Peer Events",
"type": "table"
}
],
"refresh": "1m",
"schemaVersion": 36,
"style": "dark",
"tags": ["obmp","obmp-nav","operations","router"],
"templating": {
"list": [
{
"current": {},
"datasource": {"type": "postgres","uid": "obmp_postgres"},
"definition": "select name as __text, hash_id as __value from routers where length(name) > 0",
"hide": 0,
"includeAll": false,
"label": "Router",
"multi": false,
"name": "router_hash",
"options": [],
"query": "select name as __text, hash_id as __value from routers where length(name) > 0",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
}
]
},
"time": {"from": "now-6h","to": "now"},
"timepicker": {},
"timezone": "browser",
"title": "Router Detail",
"uid": "obmp-router-detail",
"version": 1
}

View File

@ -1,466 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"description": "AS path length distribution and analysis. Teaches how BGP AS paths reflect internet topology and how to detect anomalies like route leaks or AS path prepending.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Internet routes typically have 2-5 hops. A /32 or /24 appearing with only 1-hop AS path from an unexpected ASN is a classic hijack indicator. Routes with 10+ hops may indicate prepending.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"fillOpacity": 80,
"gradientMode": "none",
"lineWidth": 0
},
"unit": "short"
}
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"barRadius": 0,
"barWidth": 0.7,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom"
},
"orientation": "auto",
"tooltip": {
"mode": "single"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 200
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n ba.as_path_count AS \"AS Path Length (hops)\",\n COUNT(*) AS \"Prefix Count\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false\n AND r.isipv4 = true\n AND ba.as_path_count > 0\nGROUP BY ba.as_path_count\nORDER BY ba.as_path_count",
"refId": "A"
}
],
"title": "AS Path Length Distribution (Active IPv4 Routes)",
"type": "barchart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Average AS path length on the internet is ~4-5 hops. Your lab has shorter paths since ExaBGP is a single eBGP hop away.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 5
},
{
"color": "red",
"value": 8
}
]
},
"unit": "short",
"decimals": 1
}
},
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 0
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n ROUND(AVG(ba.as_path_count)::numeric, 1) AS \"Avg AS Path Length\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true AND ba.as_path_count > 0",
"refId": "A"
}
],
"title": "Average AS Path Length",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Routes with only 1-hop AS path are directly connected or possibly hijacked. In your lab, ExaBGP injects routes starting with AS 65100.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 5
},
{
"color": "red",
"value": 20
}
]
},
"unit": "short"
}
},
"gridPos": {
"h": 5,
"w": 6,
"x": 18,
"y": 0
},
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n COUNT(*) AS \"Direct (1-hop) Routes\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true AND ba.as_path_count = 1",
"refId": "A"
}
],
"title": "1-Hop Routes (Direct/Possible Hijack)",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: The longest paths reveal the most AS-level hops in your network. AS path prepending intentionally lengthens paths to make a route less preferred.",
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "AS Path Length"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 5
},
{
"color": "red",
"value": 10
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "AS Path"
},
"properties": [
{
"id": "custom.width",
"value": 400
}
]
}
]
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 10
},
"id": 4,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "AS Path Length"
}
]
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n r.prefix AS \"Prefix\",\n ba.as_path_count AS \"AS Path Length\",\n ba.as_path::text AS \"AS Path\",\n ba.origin_as AS \"Origin AS\",\n ba.next_hop AS \"Next Hop\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nORDER BY ba.as_path_count DESC\nLIMIT 30",
"refId": "A"
}
],
"title": "Longest AS Paths (Top 30)",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Origin AS is the rightmost ASN in the AS path \u2014 the network that first originated the prefix. Most internet prefixes are originated by their owning organization.",
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Route Count"
},
"properties": [
{
"id": "custom.displayMode",
"value": "lcd-gauge"
},
{
"id": "custom.width",
"value": 200
}
]
}
]
},
"gridPos": {
"h": 12,
"w": 12,
"x": 0,
"y": 20
},
"id": 5,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Route Count"
}
]
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n ba.origin_as AS \"Origin AS\",\n COALESCE(ia.as_name, 'Unknown') AS \"AS Name\",\n COUNT(*) AS \"Route Count\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nLEFT JOIN info_asn ia ON ia.asn = ba.origin_as\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nGROUP BY ba.origin_as, ia.as_name\nORDER BY COUNT(*) DESC\nLIMIT 20",
"refId": "A"
}
],
"title": "Top Origin ASNs by Route Count",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: A transit AS (appearing frequently in AS paths but not as origin) is a carrier. The most frequent transit ASNs in your lab correspond to simulated Tier-1 carriers (174=Cogent, 3356=Lumen, 1299=Telia, etc.)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"fillOpacity": 80,
"lineWidth": 0
},
"unit": "short"
}
},
"gridPos": {
"h": 12,
"w": 12,
"x": 12,
"y": 20
},
"id": 6,
"options": {
"barRadius": 0,
"barWidth": 0.7,
"groupWidth": 0.7,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom"
},
"orientation": "horizontal",
"tooltip": {
"mode": "single"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 200
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n asn_val AS \"Transit ASN\",\n COUNT(*) AS \"Appearances in AS Paths\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nCROSS JOIN LATERAL unnest(ba.as_path) AS asn_val\nWHERE r.iswithdrawn = false AND asn_val != ba.origin_as\nGROUP BY asn_val\nORDER BY COUNT(*) DESC\nLIMIT 15",
"refId": "A"
}
],
"title": "Most Common Transit ASNs",
"type": "barchart"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp",
"bgp",
"as-path",
"topology",
"obmp-nav"
],
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "AS Path Analysis",
"uid": "obmp-learn-03",
"version": 1
}

View File

@ -1,623 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"description": "Explore BGP path attributes: communities, MED, local-pref and how they influence routing policy decisions.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"panels": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"content": "## BGP Path Attributes \u2014 What They Mean\n\n### BGP Communities (RFC 1997)\nCommunities are 32-bit tags attached to routes, written as **ASN:value** (e.g., `65000:100`). They carry policy signals between routers and ASes.\n\n**Well-known communities:**\n| Community | Decimal | Meaning |\n|-----------|---------|----------|\n| `65535:0` | NO_EXPORT | Do not advertise outside this AS or confederation |\n| `65535:1` | NO_ADVERTISE | Do not advertise to any peer |\n| `65535:666` | BLACKHOLE | Drop traffic destined for this prefix (RFC 7999) |\n\nPrivate communities (e.g., `65001:200`) are operator-defined \u2014 they may encode region, customer tier, or traffic-engineering intent.\n\n### Local Preference (local-pref)\n- **Scope:** iBGP only \u2014 never sent to eBGP peers.\n- **Effect:** Higher local-pref wins. Default is **100**.\n- **Use case:** Prefer one upstream provider over another for all outbound traffic.\n\n### Multi-Exit Discriminator (MED)\n- **Scope:** Sent to directly connected eBGP peers to influence *inbound* traffic.\n- **Effect:** Lower MED wins (when comparing routes from the same AS).\n- **Use case:** Tell a peer which of your links to prefer when sending traffic to you.\n\n> **Tip:** Use the panels below to explore what communities and attributes are actually present in the current RIB. Run `inject.py attributes` to load routes with varied communities and MED values.",
"mode": "markdown"
},
"title": "BGP Attribute Reference \u2014 Communities, Local-Pref, MED",
"type": "text"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Each row is a unique community string (format ASN:value) seen across all active routes. High route counts for a community mean many routes share that policy tag. Look for well-known communities: 65535:0 (NO_EXPORT), 65535:1 (NO_ADVERTISE), 65535:666 (BLACKHOLE).",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Routes Tagged"
},
"properties": [
{
"id": "custom.displayMode",
"value": "lcd-gauge"
},
{
"id": "color",
"value": {
"mode": "thresholds"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
},
{
"color": "green",
"value": 10
},
{
"color": "yellow",
"value": 100
}
]
}
}
]
}
]
},
"gridPos": {
"h": 11,
"w": 12,
"x": 0,
"y": 8
},
"id": 2,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Routes Tagged"
}
]
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n comm AS \"Community\",\n COUNT(*) AS \"Routes Tagged\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nCROSS JOIN LATERAL unnest(ba.community_list) AS comm\nWHERE r.iswithdrawn = false AND ba.community_list IS NOT NULL\nGROUP BY comm\nORDER BY COUNT(*) DESC\nLIMIT 30",
"refId": "A"
}
],
"title": "Top BGP Communities in Current RIB",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Routes with notable BGP attributes \u2014 tagged with communities or using non-default local-pref / MED values. These routes carry explicit policy information. Examine the Communities column for operator-defined tags and the Local Pref column to see traffic engineering decisions.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Local Pref"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-text"
},
{
"id": "color",
"value": {
"mode": "thresholds"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 101
},
{
"color": "red",
"value": 200
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "MED"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-text"
},
{
"id": "color",
"value": {
"mode": "thresholds"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 100
}
]
}
}
]
}
]
},
"gridPos": {
"h": 11,
"w": 12,
"x": 12,
"y": 8
},
"id": 3,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n r.prefix::text AS \"Prefix\",\n ba.origin_as AS \"Origin AS\",\n ba.community_list::text AS \"Communities\",\n ba.local_pref AS \"Local Pref\",\n ba.med AS \"MED\",\n ba.as_path_count AS \"Path Length\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\n AND (ba.community_list IS NOT NULL OR ba.med IS NOT NULL OR ba.local_pref IS NOT NULL)\nORDER BY r.prefix\nLIMIT 100",
"refId": "A"
}
],
"title": "Routes with Notable Attributes",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: MED (Multi-Exit Discriminator) is used to influence inbound traffic from a directly connected AS. Lower MED is preferred. If most routes show 'Not Set', MED is not being used for traffic engineering. A single dominant MED value means a simple policy; many different values indicate fine-grained control.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"fillOpacity": 80,
"lineWidth": 0
},
"unit": "short"
}
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 19
},
"id": 4,
"options": {
"barRadius": 0.1,
"barWidth": 0.6,
"groupWidth": 0.7,
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"orientation": "auto",
"text": {},
"tooltip": {
"mode": "single"
},
"xTickLabelRotation": -30,
"xTickLabelSpacing": 100
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n COALESCE(ba.med::text, 'Not Set') AS \"MED Value\",\n COUNT(*) AS \"Route Count\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nGROUP BY ba.med\nORDER BY ba.med NULLS LAST\nLIMIT 20",
"refId": "A"
}
],
"title": "MED Value Distribution",
"type": "barchart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Local preference is an iBGP attribute \u2014 it never crosses AS boundaries. Default is 100. Routes with local-pref above 100 are preferred over the default path; below 100 they are used as last-resort. Non-100 values indicate active traffic-engineering policy. Run 'inject.py attributes' to inject routes with varied local-pref values.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"fillOpacity": 80,
"lineWidth": 0
},
"unit": "short"
}
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 19
},
"id": 5,
"options": {
"barRadius": 0.1,
"barWidth": 0.6,
"groupWidth": 0.7,
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"orientation": "auto",
"text": {},
"tooltip": {
"mode": "single"
},
"xTickLabelRotation": -30,
"xTickLabelSpacing": 100
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n COALESCE(ba.local_pref::text, 'Not Set') AS \"Local Pref\",\n COUNT(*) AS \"Route Count\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nGROUP BY ba.local_pref\nORDER BY ba.local_pref DESC NULLS LAST\nLIMIT 20",
"refId": "A"
}
],
"title": "Local Preference Value Distribution",
"type": "barchart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: This count tells you how widely BGP communities are used in your network. A value of 0 means no community tagging \u2014 communities are an opt-in feature. Run 'inject.py attributes' to add routes with community strings.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
},
{
"color": "green",
"value": 1
}
]
},
"unit": "short",
"mappings": []
}
},
"gridPos": {
"h": 5,
"w": 8,
"x": 0,
"y": 28
},
"id": 6,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() as time, COUNT(*) AS \"Routes with Communities\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false\n AND ba.community_list IS NOT NULL\n AND array_length(ba.community_list, 1) > 0",
"refId": "A"
}
],
"title": "Routes with Communities",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: The number of distinct community strings seen across all active routes. A diverse set indicates fine-grained policy tagging. A single value means one uniform policy tag is applied.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "blue",
"value": null
},
{
"color": "green",
"value": 1
},
{
"color": "yellow",
"value": 50
}
]
},
"unit": "short",
"mappings": []
}
},
"gridPos": {
"h": 5,
"w": 8,
"x": 8,
"y": 28
},
"id": 7,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() as time, COUNT(DISTINCT comm) AS \"Unique Communities\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nCROSS JOIN LATERAL unnest(ba.community_list) AS comm\nWHERE r.iswithdrawn = false",
"refId": "A"
}
],
"title": "Unique Community Values",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Routes with a local-pref other than the default (100) have been explicitly policy-engineered. A high count here means your network actively uses local-pref to prefer specific paths. A value of 0 means all paths are at default preference.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 100
},
{
"color": "red",
"value": 1000
}
]
},
"unit": "short",
"mappings": []
}
},
"gridPos": {
"h": 5,
"w": 8,
"x": 16,
"y": 28
},
"id": 8,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() as time, COUNT(*) AS \"Custom Local-Pref Routes\"\nFROM base_attrs ba\nJOIN ip_rib r ON r.base_attr_hash_id = ba.hash_id\nWHERE r.iswithdrawn = false\n AND ba.local_pref IS NOT NULL\n AND ba.local_pref != 100",
"refId": "A"
}
],
"title": "Routes with Non-Default Local-Pref",
"type": "stat"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp",
"bgp",
"communities",
"attributes",
"policy",
"obmp-nav"
],
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "BGP Attribute Explorer",
"uid": "obmp-learn-06",
"version": 1
}

View File

@ -1,540 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"description": "Prefix stability analysis and route churn visualization. Teaches how to identify unstable routes and understand BGP churn.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: This chart shows BGP advertisements and withdrawals bucketed per hour. A healthy network has steady low churn. Spikes in withdrawals indicate route instability events \u2014 link failures, IBGP reconvergence, or policy changes. Run 'inject.py churn' to generate synthetic churn data and observe it here.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"drawStyle": "bars",
"fillOpacity": 60,
"lineWidth": 1,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
}
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Advertisements"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "green",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Withdrawals"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "red",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"legend": {
"calcs": [
"sum",
"max"
],
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT\n $__timeGroupAlias(timestamp,'1h'),\n SUM(CASE WHEN iswithdrawn = false THEN 1 ELSE 0 END) AS \"Advertisements\",\n SUM(CASE WHEN iswithdrawn = true THEN 1 ELSE 0 END) AS \"Withdrawals\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY 1\nORDER BY 1",
"refId": "A"
}
],
"title": "Advertisements vs Withdrawals Rate (per hour)",
"type": "timeseries"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: A prefix with more than 30 updates per day is considered unstable \u2014 it is flapping or being re-announced frequently. The Stability column categorizes each prefix. Run 'inject.py churn' to generate churn data and observe it here. Sort by 'Total Updates' to find the most problematic prefixes.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Stability"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-text"
},
{
"id": "mappings",
"value": [
{
"options": {
"Very Stable": {
"color": "green",
"index": 0
},
"Stable": {
"color": "blue",
"index": 1
},
"Moderate": {
"color": "yellow",
"index": 2
},
"Unstable": {
"color": "red",
"index": 3
}
},
"type": "value"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "Total Updates"
},
"properties": [
{
"id": "custom.displayMode",
"value": "lcd-gauge"
},
{
"id": "color",
"value": {
"mode": "thresholds"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 7
},
{
"color": "red",
"value": 30
}
]
}
}
]
}
]
},
"gridPos": {
"h": 12,
"w": 24,
"x": 0,
"y": 9
},
"id": 2,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Total Updates"
}
]
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n prefix::text AS \"Prefix\",\n COUNT(*) AS \"Total Updates\",\n SUM(CASE WHEN iswithdrawn THEN 1 ELSE 0 END) AS \"Withdrawals\",\n SUM(CASE WHEN NOT iswithdrawn THEN 1 ELSE 0 END) AS \"Announcements\",\n MAX(timestamp) AS \"Last Change\",\n CASE\n WHEN COUNT(*) = 1 THEN 'Very Stable'\n WHEN COUNT(*) <= 7 THEN 'Stable'\n WHEN COUNT(*) <= 30 THEN 'Moderate'\n ELSE 'Unstable'\n END AS \"Stability\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY prefix\nORDER BY \"Total Updates\" DESC\nLIMIT 100",
"refId": "A"
}
],
"title": "Top Churning Prefixes",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: This bar chart shows how many prefixes fall into each stability tier. In a healthy network, the vast majority of prefixes should be 'Very Stable' (only announced once during the window). A large 'Unstable' bar is a red flag. Run 'inject.py churn' to shift prefixes into the Unstable tier.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed",
"fixedColor": "blue"
},
"custom": {
"fillOpacity": 80,
"lineWidth": 0
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "1. Very Stable (1 update)"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "green",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "2. Stable (2-7 updates)"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "blue",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "3. Moderate (8-30 updates)"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "yellow",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "4. Unstable (31+ updates)"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "red",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 9,
"w": 14,
"x": 0,
"y": 21
},
"id": 3,
"options": {
"barRadius": 0.1,
"barWidth": 0.6,
"groupWidth": 0.7,
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"orientation": "auto",
"text": {},
"tooltip": {
"mode": "single"
},
"xTickLabelRotation": 0,
"xTickLabelSpacing": 200
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n CASE\n WHEN cnt = 1 THEN '1. Very Stable (1 update)'\n WHEN cnt <= 7 THEN '2. Stable (2-7 updates)'\n WHEN cnt <= 30 THEN '3. Moderate (8-30 updates)'\n ELSE '4. Unstable (31+ updates)'\n END AS \"Stability Tier\",\n COUNT(*) AS \"Prefix Count\"\nFROM (\n SELECT prefix, COUNT(*) as cnt\n FROM ip_rib_log\n WHERE $__timeFilter(timestamp)\n GROUP BY prefix\n) sub\nGROUP BY 1\nORDER BY 1",
"refId": "A"
}
],
"title": "Prefix Distribution by Stability Tier",
"type": "barchart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: This is the single most churning prefix in the selected time range. If a prefix appears here repeatedly across time ranges, it may warrant investigation \u2014 check the AS path and peers announcing it.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
}
]
},
"unit": "string",
"mappings": []
}
},
"gridPos": {
"h": 5,
"w": 10,
"x": 14,
"y": 21
},
"id": 4,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {
"titleSize": 14,
"valueSize": 18
}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, prefix::text AS \"Most Churned Prefix\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY prefix\nORDER BY COUNT(*) DESC\nLIMIT 1",
"refId": "A"
}
],
"title": "Most Churned Prefix",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: This counts how many distinct prefixes had at least one update event in the selected time window. During a normal steady state this number should be low. After a major routing event (e.g., upstream link failure) you may see thousands of prefixes change simultaneously.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 500
},
{
"color": "red",
"value": 2000
}
]
},
"unit": "short",
"mappings": []
}
},
"gridPos": {
"h": 4,
"w": 10,
"x": 14,
"y": 26
},
"id": 5,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(DISTINCT prefix) AS \"Prefixes with Updates\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)",
"refId": "A"
}
],
"title": "Total Unique Prefixes with Updates",
"type": "stat"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp",
"bgp",
"churn",
"stability",
"obmp-nav"
],
"time": {
"from": "now-24h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Route Churn & Stability Score",
"uid": "obmp-learn-05",
"version": 1
}

View File

@ -1,405 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"description": "RPKI (Resource Public Key Infrastructure) validation status. Teaches BGP routing security and how RPKI prevents prefix hijacks by validating route origin.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"panels": [
{
"content": "## What is RPKI?\n\nRPKI (Resource Public Key Infrastructure) is a cryptographic security framework for BGP routing. It lets IP address holders publish **Route Origin Authorizations (ROAs)** stating which ASNs are authorized to originate their prefixes.\n\n### RPKI Validation States\n| State | Meaning |\n|-------|----------|\n| **Valid** | The route's origin AS matches a ROA for this prefix |\n| **Invalid** | A ROA exists but the origin AS or prefix length does NOT match \u2014 this route is potentially a hijack |\n| **NotFound** | No ROA exists for this prefix/origin \u2014 unprotected, can't be validated |\n\n### How to read this dashboard\n- **Valid %** should be as high as possible (target: 100%)\n- **Invalid routes** are critical \u2014 they indicate either a misconfiguration or a prefix hijack\n- Routes with no RPKI data show as **NotFound** \u2014 they are not necessarily invalid, just unprotected\n\n> **Lab note:** The RPKI validator table is populated by a cron job in psql-app every 2 hours. If the table shows 0 rows, wait for the cron to run or check `ENABLE_RPKI=1` in docker-compose.yml.",
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"gridPos": {
"h": 10,
"w": 8,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"content": "## What is RPKI?\n\nRPKI (Resource Public Key Infrastructure) is a cryptographic security framework for BGP routing. It lets IP address holders publish **Route Origin Authorizations (ROAs)** stating which ASNs are authorized to originate their prefixes.\n\n### RPKI Validation States\n| State | Meaning |\n|-------|----------|\n| **Valid** | The route's origin AS matches a ROA for this prefix |\n| **Invalid** | A ROA exists but the origin AS or prefix length does NOT match \u2014 this route is potentially a hijack |\n| **NotFound** | No ROA exists for this prefix/origin \u2014 unprotected, can't be validated |\n\n### How to read this dashboard\n- **Valid %** should be as high as possible (target: 100%)\n- **Invalid routes** are critical \u2014 they indicate either a misconfiguration or a prefix hijack\n- Routes with no RPKI data show as **NotFound** \u2014 they are not necessarily invalid, just unprotected\n\n> **Lab note:** The RPKI validator table is populated by a cron job in psql-app every 2 hours. If the table shows 0 rows, wait for the cron to run or check `ENABLE_RPKI=1` in docker-compose.yml.",
"mode": "markdown"
},
"pluginVersion": "9.1.7",
"title": "RPKI Learning Guide",
"type": "text"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Total ROAs (Route Origin Authorizations) loaded from the RPKI validator. If 0, the cron job has not yet run.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "yellow",
"value": 1
},
{
"color": "green",
"value": 100000
}
]
},
"unit": "short"
}
},
"gridPos": {
"h": 5,
"w": 4,
"x": 8,
"y": 0
},
"id": 2,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(*) AS \"RPKI ROAs Loaded\" FROM rpki_validator",
"refId": "A"
}
],
"title": "RPKI ROAs Loaded",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Routes with a matching valid ROA \u2014 origin AS and prefix length both match.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 1
}
]
},
"unit": "short"
}
},
"gridPos": {
"h": 5,
"w": 4,
"x": 12,
"y": 0
},
"id": 3,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(*) AS \"Valid Routes\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nJOIN rpki_validator rv ON rv.prefix >>= r.prefix AND rv.origin_as = ba.origin_as AND r.prefix_len <= rv.prefix_len_max\nWHERE r.iswithdrawn = false AND r.isipv4 = true",
"refId": "A"
}
],
"title": "RPKI Valid Routes",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Routes where a ROA exists but the origin AS does NOT match \u2014 high-priority investigation needed.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 1
}
]
},
"unit": "short"
}
},
"gridPos": {
"h": 5,
"w": 4,
"x": 16,
"y": 0
},
"id": 4,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(*) AS \"RPKI Invalid Routes\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nWHERE r.iswithdrawn = false AND r.isipv4 = true\n AND EXISTS (\n SELECT 1 FROM rpki_validator rv\n WHERE rv.prefix >>= r.prefix AND rv.origin_as != ba.origin_as\n )\n AND NOT EXISTS (\n SELECT 1 FROM rpki_validator rv\n WHERE rv.prefix >>= r.prefix AND rv.origin_as = ba.origin_as AND r.prefix_len <= rv.prefix_len_max\n )",
"refId": "A"
}
],
"title": "RPKI Invalid Routes",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: ExaBGP-injected routes (AS 65100) will be NotFound since they use synthetic ASNs not registered in RPKI. Real internet prefixes with valid ROAs will appear as Valid.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": []
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 10,
"x": 0,
"y": 10
},
"id": 5,
"options": {
"displayLabels": [
"percent",
"name"
],
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"pieType": "donut",
"tooltip": {
"mode": "single"
}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n CASE\n WHEN rv_valid.prefix IS NOT NULL THEN 'Valid'\n WHEN rv_any.prefix IS NOT NULL THEN 'Invalid'\n ELSE 'NotFound'\n END AS \"RPKI Status\",\n COUNT(*) AS \"Route Count\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nLEFT JOIN rpki_validator rv_valid\n ON rv_valid.prefix >>= r.prefix AND rv_valid.origin_as = ba.origin_as AND r.prefix_len <= rv_valid.prefix_len_max\nLEFT JOIN rpki_validator rv_any\n ON rv_any.prefix >>= r.prefix AND rv_any.origin_as != ba.origin_as\nWHERE r.iswithdrawn = false AND r.isipv4 = true\nGROUP BY 1\nORDER BY 1",
"refId": "A"
}
],
"title": "RPKI Validation Status Distribution",
"type": "piechart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Prefixes that have a ROA but the observed origin AS does not match. These are the most security-critical routes \u2014 each one represents a potential hijack or misconfiguration.",
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Status"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background"
},
{
"id": "mappings",
"value": [
{
"options": {
"Invalid": {
"color": "red",
"index": 0
},
"Valid": {
"color": "green",
"index": 1
},
"NotFound": {
"color": "yellow",
"index": 2
}
},
"type": "value"
}
]
}
]
}
]
},
"gridPos": {
"h": 14,
"w": 14,
"x": 10,
"y": 10
},
"id": 6,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT\n r.prefix AS \"Prefix\",\n ba.origin_as AS \"Observed Origin AS\",\n rv.origin_as AS \"Authorized Origin AS (ROA)\",\n 'Invalid' AS \"Status\"\nFROM ip_rib r\nJOIN base_attrs ba ON ba.hash_id = r.base_attr_hash_id\nJOIN rpki_validator rv ON rv.prefix >>= r.prefix AND rv.origin_as != ba.origin_as\nWHERE r.iswithdrawn = false AND r.isipv4 = true\n AND NOT EXISTS (\n SELECT 1 FROM rpki_validator rv2\n WHERE rv2.prefix >>= r.prefix AND rv2.origin_as = ba.origin_as AND r.prefix_len <= rv2.prefix_len_max\n )\nORDER BY r.prefix\nLIMIT 50",
"refId": "A"
}
],
"title": "RPKI Invalid Routes \u2014 Potential Hijacks",
"type": "table"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp",
"bgp",
"rpki",
"security",
"obmp-nav"
],
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "RPKI Validation Status",
"uid": "obmp-learn-04",
"version": 1
}

View File

@ -1,465 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"description": "BGP update and withdrawal rates over time. Teaches what normal BGP traffic looks like and how to detect route churn or instability.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: A healthy network has far more advertisements than withdrawals. A withdrawal spike often signals a link failure or route flap.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"drawStyle": "bars",
"fillOpacity": 60,
"lineWidth": 1,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
}
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Advertisements"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "green",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Withdrawals"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "red",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"legend": {
"calcs": [
"sum",
"max"
],
"displayMode": "list",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT\n $__timeGroupAlias(timestamp,'5m'),\n SUM(CASE WHEN iswithdrawn = false THEN 1 ELSE 0 END) AS \"Advertisements\",\n SUM(CASE WHEN iswithdrawn = true THEN 1 ELSE 0 END) AS \"Withdrawals\"\nFROM ip_rib_log\nWHERE $__timeFilter(timestamp)\nGROUP BY 1\nORDER BY 1",
"refId": "A"
}
],
"title": "BGP Updates Over Time \u2014 Advertisements vs Withdrawals",
"type": "timeseries"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 100
},
{
"color": "red",
"value": 1000
}
]
},
"unit": "short",
"mappings": []
}
},
"gridPos": {
"h": 5,
"w": 6,
"x": 0,
"y": 10
},
"id": 2,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(*) AS \"Total Updates (24h)\" FROM ip_rib_log WHERE timestamp > NOW() - INTERVAL '24 hours'",
"refId": "A"
}
],
"title": "Total Updates (24h)",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Learn: Withdrawal rate above 30% is unusual. Above 50% may indicate a route leak or oscillation event.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 20
},
{
"color": "red",
"value": 50
}
]
},
"unit": "percent",
"max": 100
}
},
"gridPos": {
"h": 5,
"w": 6,
"x": 6,
"y": 10
},
"id": 3,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time,\n ROUND(100.0 * SUM(CASE WHEN iswithdrawn THEN 1 ELSE 0 END) / NULLIF(COUNT(*),0), 1) AS \"Withdrawal Rate %\"\nFROM ip_rib_log\nWHERE timestamp > NOW() - INTERVAL '24 hours'",
"refId": "A"
}
],
"title": "Withdrawal Rate % (24h)",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 1000
},
{
"color": "red",
"value": 10000
}
]
},
"unit": "short"
}
},
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 10
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(DISTINCT peer_hash_id) AS \"Active Peers\" FROM ip_rib_log WHERE timestamp > NOW() - INTERVAL '1 hour'",
"refId": "A"
}
],
"title": "Active Reporting Peers (1h)",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 500
},
{
"color": "red",
"value": 2000
}
]
},
"unit": "short"
}
},
"gridPos": {
"h": 5,
"w": 6,
"x": 18,
"y": 10
},
"id": 5,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT NOW() AS time, COUNT(DISTINCT prefix) AS \"Unique Prefixes Updated (24h)\" FROM ip_rib_log WHERE timestamp > NOW() - INTERVAL '24 hours'",
"refId": "A"
}
],
"title": "Unique Prefixes Updated (24h)",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Updates per peer over time. Learn: Peers should have similar update rates. A peer with dramatically more updates may be experiencing instability or receiving a full BGP table with frequent changes.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"drawStyle": "line",
"fillOpacity": 10,
"lineWidth": 1,
"spanNulls": false
},
"unit": "short"
}
},
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 15
},
"id": 6,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "right"
},
"tooltip": {
"mode": "multi"
}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawSql": "SELECT\n $__timeGroupAlias(s.interval_time,'30m'),\n COALESCE(p.name, p.peer_addr::text) AS metric,\n SUM(s.advertise_avg + s.withdraw_avg) AS \"Updates\"\nFROM stats_peer_update_counts s\nJOIN bgp_peers p ON p.hash_id = s.peer_hash_id\nWHERE $__timeFilter(s.interval_time)\nGROUP BY 1, 2\nORDER BY 1",
"refId": "A"
}
],
"title": "Update Rate by Peer (30-min buckets)",
"type": "timeseries"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp",
"bgp",
"churn",
"obmp-nav"
],
"time": {
"from": "now-24h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "BGP Update Rate & Churn",
"uid": "obmp-learn-01",
"version": 1
}

View File

@ -25,19 +25,7 @@
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 7,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -509,9 +497,7 @@
"schemaVersion": 37,
"style": "dark",
"tags": [
"obmp-history",
"obmp",
"obmp-nav"
"obmp-history"
],
"templating": {
"list": [

View File

@ -25,19 +25,7 @@
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 8,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -243,9 +231,7 @@
"schemaVersion": 37,
"style": "dark",
"tags": [
"obmp-history",
"obmp",
"obmp-nav"
"obmp-history"
],
"templating": {
"list": [

View File

@ -26,19 +26,7 @@
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 9,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -153,6 +141,10 @@
"type": "table"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
@ -160,42 +152,46 @@
"decimals": 0,
"fieldConfig": {
"defaults": {
"links": [],
"color": {
"mode": "palette-classic"
},
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"lineWidth": 1,
"fillOpacity": 15,
"showPoints": "never",
"spanNulls": false,
"axisPlacement": "auto"
}
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 7,
"w": 11,
"x": 0,
"y": 6
},
"hiddenSeries": false,
"id": 1,
"links": [],
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"show": true,
"total": true,
"values": true
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "9.1.7",
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "",
@ -226,10 +222,43 @@
]
}
],
"thresholds": [],
"timeRegions": [],
"title": "Prefix Advertisements & Withdrawals",
"type": "timeseries"
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:289",
"format": "none",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:290",
"format": "short",
"logBase": 1,
"show": false
}
],
"yaxis": {
"align": false
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
@ -237,42 +266,49 @@
"decimals": 0,
"fieldConfig": {
"defaults": {
"links": [],
"color": {
"mode": "palette-classic"
},
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"lineWidth": 1,
"fillOpacity": 15,
"showPoints": "never",
"spanNulls": false,
"axisPlacement": "auto"
}
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 7,
"w": 13,
"x": 11,
"y": 6
},
"hiddenSeries": false,
"id": 2,
"links": [],
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"sort": "total",
"sortDesc": true,
"total": true,
"values": true
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "9.1.7",
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "",
@ -302,8 +338,39 @@
]
}
],
"thresholds": [],
"timeRegions": [],
"title": "Changes by Peer",
"type": "timeseries"
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:346",
"decimals": 0,
"format": "none",
"label": "",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:347",
"format": "short",
"logBase": 1,
"show": false
}
],
"yaxis": {
"align": false
}
},
{
"datasource": {
@ -438,9 +505,7 @@
"schemaVersion": 37,
"style": "dark",
"tags": [
"obmp-history",
"obmp",
"obmp-nav"
"obmp-history"
],
"templating": {
"list": [

View File

@ -0,0 +1,780 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 19,
"iteration": 1654877653557,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Prefix found in router's RIB.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"decimals": 0,
"mappings": [],
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 0,
"y": 0
},
"id": 9,
"links": [],
"maxDataPoints": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "table",
"placement": "right",
"values": [
"value",
"percent"
]
},
"pieType": "pie",
"reduceOptions": {
"calcs": [
"sum"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"alias": "",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"group": [],
"hide": false,
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SELECT\n floor(extract(epoch from max(r.timestamp))) as time,\n CASE WHEN v.router_hash_id is null THEN 'Not in Router RIB' ELSE 'In Router Rib' END as metric,\n 1 as value\nFROM routers r\n left join (select distinct router_hash_id\n from v_l3vpn_routes\n where prefix = '$prefix'\n and ('$rd' = '-' OR rd = '$rd')\n and iswithdrawn = false group by router_hash_id) v \n on (r.hash_id = v.router_hash_id)\nWHERE r.state = 'up'\nGROUP BY r.hash_id,v.router_hash_id\norder by time\n\n",
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Router Visibility",
"type": "piechart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Prefix found in peer RIB's",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"decimals": 0,
"mappings": [],
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 6,
"y": 0
},
"id": 10,
"links": [],
"maxDataPoints": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "table",
"placement": "right",
"values": [
"value",
"percent"
]
},
"pieType": "pie",
"reduceOptions": {
"calcs": [
"sum"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"alias": "",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"group": [],
"hide": false,
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SELECT\n floor(extract(epoch from max(p.timestamp))) as time,\n CASE WHEN v.peer_hash_id is null THEN 'Not in Peers RIB' ELSE 'In Peer RIB' END as metric,\n 1 as value\nFROM bgp_peers p\n left join (select peer_hash_id,isipv4\n from l3vpn_rib \n where prefix = '$prefix' and prefix != '0.0.0.0/0'\n AND ('$rd' = '-' OR rd = '$rd')\n and iswithdrawn = false group by peer_hash_id,isipv4) v \n on (p.hash_id = v.peer_hash_id)\nWHERE p.isipv4 = CASE WHEN family('$prefix') = 4 THEN true ELSE false END\n AND p.state = 'up'\nGROUP BY p.hash_id,v.peer_hash_id,p.isipv4\norder by time\n\n",
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Peer Visibility",
"type": "piechart"
},
{
"circleMaxSize": "15",
"circleMinSize": 2,
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"decimals": 0,
"esMetric": "Count",
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"hideEmpty": false,
"hideZero": false,
"id": 17,
"initialZoom": "1",
"locationData": "table",
"mapCenter": "(0°, 0°)",
"mapCenterLatitude": 0,
"mapCenterLongitude": 0,
"maxDataPoints": 1,
"mouseWheelZoom": false,
"showLegend": false,
"stickyLabels": false,
"tableQueryOptions": {
"geohashField": "geohash",
"labelField": "name",
"latitudeField": "latitude",
"longitudeField": "longitude",
"metricField": "value",
"queryType": "coordinates"
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"group": [],
"hide": false,
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SELECT\n 10 as value, latitude, longitude, stateprov as name\nFROM geo_ip\nWHERE\n ip && '$input'\nORDER BY ip desc limit 1",
"refId": "A",
"select": [
[
{
"params": [
"latitude"
],
"type": "column"
}
]
],
"table": "v_ip_routes_geo",
"timeColumn": "lastmodified",
"timeColumnType": "timestamp",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"thresholds": "0,10",
"title": "Prefix Location",
"type": "grafana-worldmap-panel",
"unitPlural": "",
"unitSingle": "",
"valueName": "current"
},
{
"columns": [],
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fontSize": "100%",
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 8
},
"id": 12,
"links": [],
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"styles": [
{
"alias": "Time",
"align": "auto",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "Time",
"type": "date"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"decimals": 2,
"mappingType": 1,
"pattern": "raw_output",
"preserveFormat": true,
"sanitize": false,
"thresholds": [],
"type": "string",
"unit": "short"
},
{
"alias": "",
"align": "auto",
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "string",
"unit": "short"
}
],
"targets": [
{
"alias": "",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select distinct origin_as,i.as_name,org_id,org_name,remarks,address,city,state_prov,country,raw_output,source\n from l3vpn_rib r LEFT JOIN info_asn i ON (i.asn = r.origin_as)\n where r.prefix = '$prefix'\n and ('$rd' = '-' OR rd = '$rd')\n and origin_as > 0\n",
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "ASN Info",
"transform": "table",
"type": "table-old"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": false
},
"decimals": 0,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "locale"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "lastmodified"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss.SSS"
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "prefix"
},
"properties": [
{
"id": "displayName",
"value": "Prefix"
},
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "Prefix History ",
"url": "/d/l3vpn-prefix-hist/prefix-history-by-prefix-l3vpn?orgId=1&var-input=${__value.text}&var-rd=$rd"
}
]
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "origin_as"
},
"properties": [
{
"id": "displayName",
"value": "Origin"
},
{
"id": "unit",
"value": "none"
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "ASN View",
"url": "/grafana/d/asnview/asn-view?orgId=1&var-asn_num=${__value.text}"
}
]
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "iswithdrawn"
},
"properties": [
{
"id": "displayName",
"value": "Withdrawn"
},
{
"id": "unit",
"value": "bool"
},
{
"id": "custom.displayMode",
"value": "color-background-solid"
},
{
"id": "custom.align",
"value": "auto"
},
{
"id": "color",
"value": {
"mode": "continuous-GrYlRd"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "custom.width",
"value": 194
}
]
}
]
},
"gridPos": {
"h": 23,
"w": 24,
"x": 0,
"y": 14
},
"id": 3,
"links": [],
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "8.5.4",
"targets": [
{
"alias": "",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"group": [],
"hide": false,
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select distinct ip.*, \n \tFIRST_VALUE(geo_ip.city) OVER (PARTITION BY ip.prefix ORDER BY geo_ip.ip DESC) as city,\n \tFIRST_VALUE(geo_ip.stateprov) OVER (PARTITION BY ip.prefix ORDER BY geo_ip.ip DESC) as stateprov,\n \tFIRST_VALUE(geo_ip.country) OVER (PARTITION BY ip.prefix ORDER BY geo_ip.ip DESC) as country,\n ls.local_router_name\n\tFROM (SELECT lastmodified,peername,rd,prefix,\n \tiswithdrawn,origin_as,med,localpref,nh,as_path,extcommunities,communities,largecommunities\n from v_l3vpn_routes\n \t\twhere prefix && '$input' \n \t\t AND peer_hash_id in ($peer_hash)\n \t\t AND ('$rd' = '-' OR rd = '$rd')\n \t\tlimit 2000\n \t) ip\n\t\tLEFT JOIN geo_ip on (geo_ip.ip >>= ip.prefix AND geo_ip.ip != '0.0.0.0/0')\n LEFT JOIN v_ls_prefixes ls ON (ls.prefix >>= ip.nh and length(ls.local_router_name) > 0)",
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Looking Glass",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"type": "table"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp-l3vpn"
],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "80.0.0.2",
"value": "80.0.0.2"
},
"hide": 0,
"label": "Prefix/IP",
"name": "input",
"options": [
{
"selected": true,
"text": "80.0.0.2",
"value": "80.0.0.2"
}
],
"query": "80.0.0.2",
"queryValue": "50.227.215.188",
"skipUrlSync": false,
"type": "textbox"
},
{
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"definition": "select name as __text, hash_id as __value from routers where state = 'up'",
"hide": 0,
"includeAll": true,
"label": "Router",
"multi": true,
"name": "router_hash",
"options": [],
"query": "select name as __text, hash_id as __value from routers where state = 'up'",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"definition": "select peername as __text, peer_hash_id as __value from v_peers where router_hash_id in ($router_hash) and recvcapabilities like '% afi=1 safi=128 %';",
"hide": 0,
"includeAll": true,
"label": "Peer",
"multi": true,
"name": "peer_hash",
"options": [],
"query": "select peername as __text, peer_hash_id as __value from v_peers where router_hash_id in ($router_hash) and recvcapabilities like '% afi=1 safi=128 %';",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"isNone": true,
"selected": false,
"text": "None",
"value": ""
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"definition": "select prefix from l3vpn_rib \nwhere prefix >>= '$input' and peer_hash_id in ($peer_hash) and ('$rd' = '-' OR rd = '$rd')\norder by prefix desc limit 1",
"hide": 2,
"includeAll": false,
"multi": false,
"name": "prefix",
"options": [],
"query": "select prefix from l3vpn_rib \nwhere prefix >>= '$input' and peer_hash_id in ($peer_hash) and ('$rd' = '-' OR rd = '$rd')\norder by prefix desc limit 1",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
},
{
"description": "RD in the format of N:N. Set to - for all.",
"hide": 2,
"label": "RD",
"name": "rd",
"query": "-",
"skipUrlSync": false,
"type": "constant"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "",
"title": "Looking Glass - L3VPN",
"uid": "jiQW6VB7k",
"version": 1,
"weekStart": ""
}

View File

@ -26,19 +26,7 @@
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 20,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -157,192 +145,245 @@
"type": "table"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"decimals": 0,
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "none"
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 7,
"w": 12,
"x": 0,
"y": 6
},
"hiddenSeries": false,
"id": 1,
"options": {
"legend": {
"calcs": [
"max",
"mean",
"sum"
],
"displayMode": "table",
"placement": "right",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"total": true,
"values": true
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "9.1.7",
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"group": [],
"hide": false,
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SELECT\n interval_time as time,\n sum(updates) as updates, sum(withdraws) as withdraws\nFROM stats_l3vpn_chg_byprefix s\nWHERE $__timeFilter(interval_time)\n AND peer_hash_id in ($peer_hash)\n ${prefix_clause:raw}\n\ngroup by interval_time\nORDER BY interval_time ASC\n",
"refId": "A"
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"thresholds": [],
"timeRegions": [],
"title": "Prefix Advertisements & Withdrawals",
"type": "timeseries"
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:289",
"format": "none",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:290",
"format": "short",
"logBase": 1,
"show": false
}
],
"yaxis": {
"align": false
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"decimals": 0,
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "none"
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 7,
"w": 12,
"x": 12,
"y": 6
},
"hiddenSeries": false,
"id": 2,
"options": {
"legend": {
"calcs": [
"max",
"mean",
"sum"
],
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Total",
"sortDesc": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": false,
"rightSide": true,
"show": true,
"sort": "total",
"sortDesc": true,
"total": true,
"values": true
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "9.1.7",
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SELECT\n interval_time as time,\n sum(updates) + sum(withdraws) as value,\n left(PeerName,32) as metric\nFROM stats_l3vpn_chg_byprefix s\n JOIN v_peers p ON (s.peer_hash_id = p.peer_hash_id)\nWHERE $__timeFilter(interval_time)\n AND s.peer_hash_id in ($peer_hash)\n ${prefix_clause:raw}\n\nGROUP BY s.interval_time,peername\nORDER BY interval_time ASC\n\n",
"refId": "A"
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"thresholds": [],
"timeRegions": [],
"title": "Changes by Peer",
"type": "timeseries"
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:346",
"decimals": 0,
"format": "none",
"label": "",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:347",
"format": "short",
"logBase": 1,
"show": false
}
],
"yaxis": {
"align": false
}
},
{
"datasource": {
@ -496,11 +537,9 @@
}
],
"refresh": "",
"schemaVersion": 36,
"schemaVersion": 37,
"style": "dark",
"tags": [
"obmp-nav",
"l3vpn",
"obmp-l3vpn"
],
"templating": {

View File

@ -21,39 +21,14 @@
}
]
},
"description": "L3VPN RIB browser combined with the per-prefix Looking Glass: route counts by RD, prefix visibility, geolocation and ASN ownership.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 21,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"iteration": 1654877634754,
"links": [],
"liveNow": false,
"panels": [
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": 20,
"panels": [],
"title": "RIB Browser",
"type": "row"
},
{
"datasource": {
"type": "postgres",
@ -101,7 +76,7 @@
"h": 8,
"w": 11,
"x": 0,
"y": 1
"y": 0
},
"id": 5,
"options": {
@ -125,7 +100,7 @@
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"pluginVersion": "9.1.7",
"pluginVersion": "8.3.4",
"targets": [
{
"datasource": {
@ -133,9 +108,31 @@
"uid": "obmp_postgres"
},
"format": "table",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select\n count(*) as count,\n rd\n from l3vpn_rib\n where\n peer_hash_id in ($peer_hash)\n and ('$rd' = '-' or rd = '$rd')\n and iswithdrawn = false\n group by rd\n",
"refId": "A"
"refId": "A",
"select": [
[
{
"params": [
"latitude"
],
"type": "column"
}
]
],
"table": "v_ip_routes_geo",
"timeColumn": "lastmodified",
"timeColumnType": "timestamp",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Routes Advertised/Active",
@ -211,9 +208,9 @@
},
"gridPos": {
"h": 8,
"w": 13,
"w": 12,
"x": 11,
"y": 1
"y": 0
},
"id": 6,
"options": {
@ -237,7 +234,7 @@
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0
},
"pluginVersion": "9.1.7",
"pluginVersion": "8.3.4",
"targets": [
{
"datasource": {
@ -245,9 +242,31 @@
"uid": "obmp_postgres"
},
"format": "table",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select\n count(*) as count,\n rd\n from l3vpn_rib\n where\n peer_hash_id in ($peer_hash)\n and ('$rd' = '-' OR rd = '$rd')\n and iswithdrawn = true\n group by rd\n",
"refId": "A"
"refId": "A",
"select": [
[
{
"params": [
"latitude"
],
"type": "column"
}
]
],
"table": "v_ip_routes_geo",
"timeColumn": "lastmodified",
"timeColumnType": "timestamp",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Routes Withdrawn/Inactive",
@ -410,10 +429,10 @@
]
},
"gridPos": {
"h": 18,
"h": 23,
"w": 24,
"x": 0,
"y": 9
"y": 8
},
"id": 3,
"links": [],
@ -428,17 +447,39 @@
"showHeader": true,
"sortBy": []
},
"pluginVersion": "9.1.7",
"pluginVersion": "8.5.4",
"targets": [
{
"alias": "",
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"group": [],
"hide": false,
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select distinct ip.*, \n \tFIRST_VALUE(geo_ip.city) OVER (PARTITION BY ip.prefix ORDER BY geo_ip.ip DESC) as city,\n \tFIRST_VALUE(geo_ip.stateprov) OVER (PARTITION BY ip.prefix ORDER BY geo_ip.ip DESC) as stateprov,\n \tFIRST_VALUE(geo_ip.country) OVER (PARTITION BY ip.prefix ORDER BY geo_ip.ip DESC) as country,\n ls.local_router_name\n\tFROM (SELECT lastmodified,peername,rd,prefix,\n \tiswithdrawn,origin_as,med,localpref,nh,as_path,communities,extcommunities\n from v_l3vpn_routes\n \t\twhere \n \t\t peer_hash_id in ($peer_hash)\n \t\t AND ('$rd' = '-' OR rd = '$rd')\n \t\t AND (iswithdrawn in ($state))\n \t\tlimit $limit\n \t) ip\n\t\tLEFT JOIN geo_ip on (geo_ip.ip >>= ip.prefix AND geo_ip.ip != '0.0.0.0/0')\n LEFT JOIN v_ls_prefixes ls ON (ls.prefix >>= ip.nh and length(ls.local_router_name) > 0)",
"refId": "A"
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Looking Glass (RD = $rd)",
@ -451,555 +492,11 @@
}
],
"type": "table"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 27
},
"id": 21,
"panels": [],
"title": "Looking Glass - Prefix Lookup ($input)",
"type": "row"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Prefix found in router's RIB.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"decimals": 0,
"mappings": [],
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 6,
"x": 0,
"y": 28
},
"id": 9,
"links": [],
"maxDataPoints": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "table",
"placement": "right",
"values": [
"value",
"percent"
]
},
"pieType": "pie",
"reduceOptions": {
"calcs": [
"sum"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "9.1.7",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawQuery": true,
"rawSql": "SELECT\n floor(extract(epoch from max(r.timestamp))) as time,\n CASE WHEN v.router_hash_id is null THEN 'Not in Router RIB' ELSE 'In Router Rib' END as metric,\n 1 as value\nFROM routers r\n left join (select distinct router_hash_id\n from v_l3vpn_routes\n where prefix = '$prefix'\n and ('$rd' = '-' OR rd = '$rd')\n and iswithdrawn = false group by router_hash_id) v \n on (r.hash_id = v.router_hash_id)\nWHERE r.state = 'up'\nGROUP BY r.hash_id,v.router_hash_id\norder by time\n\n",
"refId": "A"
}
],
"title": "Router Visibility",
"type": "piechart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Prefix found in peer RIB's",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"decimals": 0,
"mappings": [],
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 6,
"x": 6,
"y": 28
},
"id": 10,
"links": [],
"maxDataPoints": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "table",
"placement": "right",
"values": [
"value",
"percent"
]
},
"pieType": "pie",
"reduceOptions": {
"calcs": [
"sum"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "9.1.7",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"rawQuery": true,
"rawSql": "SELECT\n floor(extract(epoch from max(p.timestamp))) as time,\n CASE WHEN v.peer_hash_id is null THEN 'Not in Peers RIB' ELSE 'In Peer RIB' END as metric,\n 1 as value\nFROM bgp_peers p\n left join (select peer_hash_id,isipv4\n from l3vpn_rib \n where prefix = '$prefix' and prefix != '0.0.0.0/0'\n AND ('$rd' = '-' OR rd = '$rd')\n and iswithdrawn = false group by peer_hash_id,isipv4) v \n on (p.hash_id = v.peer_hash_id)\nWHERE p.isipv4 = CASE WHEN family('$prefix') = 4 THEN true ELSE false END\n AND p.state = 'up'\nGROUP BY p.hash_id,v.peer_hash_id,p.isipv4\norder by time\n\n",
"refId": "A"
}
],
"title": "Peer Visibility",
"type": "piechart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Geolocation of the looked-up prefix.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 28
},
"id": 17,
"options": {
"basemap": {
"config": {},
"name": "Layer 0",
"type": "default"
},
"controls": {
"mouseWheelZoom": true,
"showAttribution": true,
"showDebug": false,
"showMeasure": false,
"showScale": false,
"showZoom": true
},
"layers": [
{
"config": {
"showLegend": false,
"style": {
"color": {
"fixed": "dark-orange"
},
"opacity": 0.4,
"rotation": {
"fixed": 0,
"max": 360,
"min": -360,
"mode": "mod"
},
"size": {
"fixed": 8,
"max": 15,
"min": 2
},
"symbol": {
"fixed": "img/icons/marker/circle.svg",
"mode": "fixed"
},
"textConfig": {
"fontSize": 12,
"offsetX": 0,
"offsetY": 0,
"textAlign": "center",
"textBaseline": "middle"
}
}
},
"location": {
"latitude": "latitude",
"longitude": "longitude",
"mode": "coords"
},
"name": "Prefix Location",
"tooltip": true,
"type": "markers"
}
],
"tooltip": {
"mode": "details"
},
"view": {
"id": "zero",
"lat": 0,
"lon": 0,
"zoom": 1
}
},
"pluginVersion": "9.1.7",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawQuery": true,
"rawSql": "SELECT\n 10 as value, latitude, longitude, stateprov as name\nFROM geo_ip\nWHERE\n ip && '$input'\nORDER BY ip desc limit 1",
"refId": "A"
}
],
"title": "Prefix Location",
"type": "geomap"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"description": "Origin-AS ownership for the looked-up prefix.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 36
},
"id": 12,
"links": [],
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"pluginVersion": "9.1.7",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawQuery": true,
"rawSql": "select distinct origin_as,i.as_name,org_id,org_name,remarks,address,city,state_prov,country,raw_output,source\n from l3vpn_rib r LEFT JOIN info_asn i ON (i.asn = r.origin_as)\n where r.prefix = '$prefix'\n and ('$rd' = '-' OR rd = '$rd')\n and origin_as > 0\n",
"refId": "A"
}
],
"title": "ASN Info",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": false
},
"decimals": 0,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "locale"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "lastmodified"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss.SSS"
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "prefix"
},
"properties": [
{
"id": "displayName",
"value": "Prefix"
},
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "Prefix History ",
"url": "/d/l3vpn-prefix-hist/prefix-history-by-prefix-l3vpn?orgId=1&var-input=${__value.text}&var-rd=$rd"
}
]
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "origin_as"
},
"properties": [
{
"id": "displayName",
"value": "Origin"
},
{
"id": "unit",
"value": "none"
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "ASN View",
"url": "/grafana/d/asnview/asn-view?orgId=1&var-asn_num=${__value.text}"
}
]
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "iswithdrawn"
},
"properties": [
{
"id": "displayName",
"value": "Withdrawn"
},
{
"id": "unit",
"value": "bool"
},
{
"id": "custom.displayMode",
"value": "color-background-solid"
},
{
"id": "custom.align",
"value": "auto"
},
{
"id": "color",
"value": {
"mode": "continuous-GrYlRd"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "custom.width",
"value": 194
}
]
}
]
},
"gridPos": {
"h": 18,
"w": 24,
"x": 0,
"y": 42
},
"id": 13,
"links": [],
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "9.1.7",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawQuery": true,
"rawSql": "select distinct ip.*, \n \tFIRST_VALUE(geo_ip.city) OVER (PARTITION BY ip.prefix ORDER BY geo_ip.ip DESC) as city,\n \tFIRST_VALUE(geo_ip.stateprov) OVER (PARTITION BY ip.prefix ORDER BY geo_ip.ip DESC) as stateprov,\n \tFIRST_VALUE(geo_ip.country) OVER (PARTITION BY ip.prefix ORDER BY geo_ip.ip DESC) as country,\n ls.local_router_name\n\tFROM (SELECT lastmodified,peername,rd,prefix,\n \tiswithdrawn,origin_as,med,localpref,nh,as_path,extcommunities,communities,largecommunities\n from v_l3vpn_routes\n \t\twhere prefix && '$input' \n \t\t AND peer_hash_id in ($peer_hash)\n \t\t AND ('$rd' = '-' OR rd = '$rd')\n \t\tlimit 2000\n \t) ip\n\t\tLEFT JOIN geo_ip on (geo_ip.ip >>= ip.prefix AND geo_ip.ip != '0.0.0.0/0')\n LEFT JOIN v_ls_prefixes ls ON (ls.prefix >>= ip.nh and length(ls.local_router_name) > 0)",
"refId": "A"
}
],
"title": "Looking Glass (Prefix Lookup)",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"type": "table"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp-nav",
"l3vpn",
"obmp-l3vpn"
],
"templating": {
@ -1037,13 +534,10 @@
},
{
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
"isNone": true,
"selected": false,
"text": "None",
"value": ""
},
"datasource": {
"type": "postgres",
@ -1051,7 +545,7 @@
},
"definition": "select peername as __text, peer_hash_id as __value from v_peers where router_hash_id in ($router_hash) and recvcapabilities like '% afi=1 safi=128 %';",
"hide": 0,
"includeAll": true,
"includeAll": false,
"label": "Peer",
"multi": true,
"name": "peer_hash",
@ -1145,7 +639,6 @@
},
"hide": 0,
"includeAll": true,
"label": "State",
"multi": true,
"name": "state",
"options": [
@ -1169,51 +662,6 @@
"queryValue": "",
"skipUrlSync": false,
"type": "custom"
},
{
"current": {
"selected": false,
"text": "80.0.0.2",
"value": "80.0.0.2"
},
"hide": 0,
"label": "Prefix/IP Lookup",
"name": "input",
"options": [
{
"selected": true,
"text": "80.0.0.2",
"value": "80.0.0.2"
}
],
"query": "80.0.0.2",
"queryValue": "50.227.215.188",
"skipUrlSync": false,
"type": "textbox"
},
{
"current": {
"isNone": true,
"selected": false,
"text": "None",
"value": ""
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"definition": "select prefix from l3vpn_rib \nwhere prefix >>= '$input' and peer_hash_id in ($peer_hash) and ('$rd' = '-' OR rd = '$rd')\norder by prefix desc limit 1",
"hide": 2,
"includeAll": false,
"multi": false,
"name": "prefix",
"options": [],
"query": "select prefix from l3vpn_rib \nwhere prefix >>= '$input' and peer_hash_id in ($peer_hash) and ('$rd' = '-' OR rd = '$rd')\norder by prefix desc limit 1",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
}
]
},
@ -1247,8 +695,8 @@
]
},
"timezone": "",
"title": "L3VPN RIB & Looking Glass",
"title": "L3VPN RIB Browser",
"uid": "v-cdzIBnz",
"version": 2,
"version": 1,
"weekStart": ""
}

View File

@ -26,19 +26,7 @@
"graphTooltip": 0,
"id": 14,
"iteration": 1654877691622,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -290,8 +278,6 @@
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp-nav",
"linkstate",
"obmp-linkstate"
],
"templating": {

View File

@ -0,0 +1,479 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 15,
"iteration": 1654877712696,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"decimals": 0,
"mappings": [],
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 7,
"x": 0,
"y": 0
},
"id": 4,
"links": [],
"maxDataPoints": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "table",
"placement": "right",
"values": [
"value",
"percent"
]
},
"pieType": "pie",
"reduceOptions": {
"calcs": [
"sum"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"format": "time_series",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select floor(extract(epoch from max(timestamp))) as time,\n count(*) as value, CASE WHEN iswithdrawn THEN 'WITHDRAWN' ELSE 'ACTIVE' END as metric\nfrom ls_links\nwhere local_node_hash_id = '$local_node_hash_id'\n AND peer_hash_id = '$peer_hash'\ngroup by iswithdrawn\norder by time\n",
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Link States",
"type": "piechart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"decimals": 0,
"mappings": [],
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 6,
"x": 7,
"y": 0
},
"id": 6,
"links": [],
"maxDataPoints": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "table",
"placement": "right",
"values": [
"value"
]
},
"pieType": "pie",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"format": "time_series",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select floor(extract(epoch from max(timestamp))) as time,\n count(*) as value, CASE WHEN mt_id = 2 THEN 'IPv6' ELSE 'IPv4' END as metric\nfrom ls_links\nwhere local_node_hash_id = '$local_node_hash_id'\n AND peer_hash_id = '$peer_hash'\ngroup by mt_id\norder by time\n",
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Links by Type",
"type": "piechart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"inspect": false
},
"decimals": 0,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "timestamp"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss.SSS"
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "seq"
},
"properties": [
{
"id": "unit",
"value": "locale"
}
]
},
{
"matcher": {
"id": "byName",
"options": "state"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background-solid"
},
{
"id": "mappings",
"value": [
{
"options": {
"ACTIVE": {
"color": "semi-dark-green",
"index": 0
},
"WITHDRAWN": {
"color": "semi-dark-red",
"index": 1
}
},
"type": "value"
}
]
}
]
}
]
},
"gridPos": {
"h": 14,
"w": 24,
"x": 0,
"y": 7
},
"id": 2,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"pluginVersion": "8.5.4",
"targets": [
{
"format": "table",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SELECT state,local_router_name,local_igp_routerid,remote_router_name,remote_igp_routerid,mt_id,igp_metric,protocol, timestamp, seq\n FROM v_ls_links\n WHERE local_node_hash_id = '$local_node_hash_id'\n AND peer_hash_id = '$peer_hash'",
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "$local_node_name Links",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"transparent": true,
"type": "table"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp-linkstate"
],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "yyz01-wxbb-crt01-lo0.webex.com",
"value": "367c22e4-57d9-2328-654b-96ea750e0267"
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"definition": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0\n ",
"hide": 0,
"includeAll": false,
"label": "BGP Peer",
"multi": false,
"name": "peer_hash",
"options": [],
"query": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0\n ",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"selected": false,
"text": "AMS10-WXBB-CRT02",
"value": "1ed1da6b-6f57-57aa-92f5-edda59049e9a"
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"definition": "select name as __text, hash_id as __value from ls_nodes where peer_hash_id = '$peer_hash' and not igp_router_id ~ '\\..[1-9A-F]00$'",
"hide": 0,
"includeAll": false,
"label": "ISIS Node",
"multi": false,
"name": "local_node_hash_id",
"options": [],
"query": "select name as __text, hash_id as __value from ls_nodes where peer_hash_id = '$peer_hash' and not igp_router_id ~ '\\..[1-9A-F]00$'",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 5,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"selected": false,
"text": "AMS10-WXBB-CRT02",
"value": "AMS10-WXBB-CRT02"
},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"definition": "select name from ls_nodes where hash_id = '$local_node_hash_id' and peer_hash_id = '$peer_hash'",
"hide": 2,
"includeAll": false,
"multi": false,
"name": "local_node_name",
"options": [],
"query": "select name from ls_nodes where hash_id = '$local_node_hash_id' and peer_hash_id = '$peer_hash'",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "LS Links",
"uid": "MPqNG_sWz",
"version": 1,
"weekStart": ""
}

View File

@ -21,24 +21,12 @@
}
]
},
"description": "Combined BGP-LS node and link inventory for a selected BGP peer.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 16,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"iteration": 1654877745288,
"links": [],
"liveNow": false,
"panels": [
{
@ -66,7 +54,7 @@
"mode": "absolute",
"steps": [
{
"color": "blue",
"color": "green",
"value": null
}
]
@ -77,7 +65,7 @@
},
"gridPos": {
"h": 6,
"w": 4,
"w": 3,
"x": 0,
"y": 0
},
@ -96,19 +84,36 @@
"fields": "",
"values": false
},
"text": {},
"textMode": "auto"
},
"pluginVersion": "9.1.7",
"pluginVersion": "8.5.4",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SELECT count(*)\n FROM ls_nodes where peer_hash_id = '$peer_hash';",
"refId": "A"
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Total Nodes",
@ -139,8 +144,8 @@
},
"gridPos": {
"h": 6,
"w": 10,
"x": 4,
"w": 7,
"x": 3,
"y": 0
},
"id": 8,
@ -168,17 +173,33 @@
"sort": "none"
}
},
"pluginVersion": "9.1.7",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"group": [],
"hide": false,
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select floor(extract(epoch from max(timestamp))) as time,\n count(*) as count, \n CASE WHEN iswithdrawn THEN 'WITHDRAWN' ELSE 'ACTIVE' END as metric\nfrom ls_links\nwhere peer_hash_id = '$peer_hash'\ngroup by iswithdrawn\norder by time\n",
"refId": "A"
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Link States",
@ -209,8 +230,8 @@
},
"gridPos": {
"h": 6,
"w": 10,
"x": 14,
"w": 7,
"x": 10,
"y": 0
},
"id": 9,
@ -238,17 +259,32 @@
"sort": "none"
}
},
"pluginVersion": "9.1.7",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "time_series",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "select floor(extract(epoch from max(timestamp))) as time,\n count(*) as count, \n CASE WHEN mt_id = 2 THEN 'IPv6' ELSE 'IPv4' END as metric\nfrom ls_links \nwhere peer_hash_id = '$peer_hash'\ngroup by metric\norder by time\n",
"refId": "A"
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Links by Type",
@ -353,17 +389,33 @@
},
"showHeader": true
},
"pluginVersion": "9.1.7",
"pluginVersion": "8.5.4",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "SELECT state, nodename, routerid, protocol, timestamp, seq\n FROM v_ls_nodes\n where peer_hash_id = '$peer_hash'\n\n ",
"refId": "A"
"refId": "A",
"select": [
[
{
"params": [
"value"
],
"type": "column"
}
]
],
"timeColumn": "time",
"where": [
{
"name": "$__timeFilter",
"params": [],
"type": "macro"
}
]
}
],
"title": "Backbone ISIS Nodes",
@ -382,146 +434,24 @@
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": false
},
"decimals": 0,
"displayName": "",
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "timestamp"
},
"properties": [
{
"id": "displayName",
"value": "Time"
},
{
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss.SSS"
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "seq"
},
"properties": [
{
"id": "unit",
"value": "locale"
}
]
},
{
"matcher": {
"id": "byName",
"options": "state"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background-solid"
},
{
"id": "mappings",
"value": [
{
"options": {
"ACTIVE": {
"color": "semi-dark-green",
"index": 0
},
"WITHDRAWN": {
"color": "semi-dark-red",
"index": 1
}
},
"type": "value"
}
]
}
]
}
]
},
"gridPos": {
"h": 14,
"h": 2,
"w": 24,
"x": 0,
"y": 19
},
"id": 2,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
"content": "\n\n",
"mode": "markdown"
},
"pluginVersion": "9.1.7",
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawQuery": true,
"rawSql": "SELECT state,local_router_name,local_igp_routerid,remote_router_name,remote_igp_routerid,mt_id,igp_metric,protocol, timestamp, seq\n FROM v_ls_links\n WHERE peer_hash_id = '$peer_hash'",
"refId": "A"
}
],
"title": "Backbone ISIS Links",
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"type": "table"
"pluginVersion": "8.5.4",
"type": "text"
}
],
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp-nav",
"linkstate",
"obmp-linkstate"
],
"templating": {
@ -574,8 +504,8 @@
]
},
"timezone": "",
"title": "LS Nodes & Links",
"title": "LS Nodes",
"uid": "dzdSWlyWz",
"version": 2,
"version": 1,
"weekStart": ""
}

View File

@ -26,19 +26,7 @@
"graphTooltip": 0,
"id": 17,
"iteration": 1654877763755,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -277,8 +265,6 @@
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp-nav",
"linkstate",
"obmp-linkstate"
],
"templating": {

View File

@ -26,19 +26,7 @@
"graphTooltip": 0,
"id": 23,
"iteration": 1654877522167,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -128,8 +116,6 @@
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp-nav",
"linkstate",
"obmp-linkstate"
],
"templating": {

View File

@ -1,761 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT local_router_name as \"Local Router\", \n remote_router_name as \"Remote Router\",\n igp_metric as \"IGP Metric\",\n te_def_metric as \"TE Metric\",\n max_link_bw as \"Max BW (B/s)\",\n max_resv_bw as \"Max Reservable BW\",\n unreserved_bw as \"Unreserved BW\",\n admin_group as \"Admin Group\",\n protection_type as \"Protection\",\n srlg as \"SRLG\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY local_router_name, remote_router_name",
"refId": "A"
}
],
"title": "TE Link Capacity Map",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 10
},
"id": 2,
"options": {
"barRadius": 0,
"barWidth": 0.97,
"groupWidth": 0.7,
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"orientation": "auto",
"showValue": "auto",
"stacking": "none",
"tooltip": {
"mode": "single",
"sort": "none"
},
"xTickLabelRotation": -45
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT local_router_name || ' -> ' || remote_router_name as \"Link\",\n igp_metric as \"IGP Metric\",\n COALESCE(te_def_metric, igp_metric) as \"TE Metric\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY igp_metric DESC",
"refId": "A"
}
],
"title": "IGP Metric vs TE Metric Comparison",
"type": "barchart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 6,
"x": 12,
"y": 10
},
"id": 3,
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"pieType": "pie",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT COALESCE(admin_group::text, 'None') as \"Admin Group\",\n COUNT(*) as \"Link Count\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nGROUP BY admin_group\nORDER BY \"Link Count\" DESC",
"refId": "A"
}
],
"title": "Admin Group Distribution",
"type": "piechart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 6,
"x": 18,
"y": 10
},
"id": 4,
"options": {
"legend": {
"displayMode": "list",
"placement": "bottom"
},
"pieType": "pie",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT COALESCE(protection_type, 'None') as \"Protection Type\",\n COUNT(*) as \"Link Count\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nGROUP BY protection_type\nORDER BY \"Link Count\" DESC",
"refId": "A"
}
],
"title": "Link Protection Types",
"type": "piechart"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 20
},
"id": 5,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT nodename as \"Node\",\n routerid as \"Router ID\",\n protocol as \"Protocol\",\n sr_capabilities as \"SR Capabilities (SRGB)\"\nFROM v_ls_nodes\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY nodename",
"refId": "A"
}
],
"title": "SR Node Capabilities",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 20
},
"id": 6,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT n.nodename as \"Node\",\n p.prefix::text as \"Prefix\",\n p.prefix_len as \"Len\",\n p.metric as \"Metric\",\n p.sr_prefix_sids as \"Prefix SID\",\n p.protocol::text as \"Protocol\"\nFROM ls_prefixes p\nJOIN ls_nodes n ON n.hash_id = p.local_node_hash_id \n AND n.peer_hash_id = p.peer_hash_id\nWHERE p.peer_hash_id = '$peer_hash' AND p.iswithdrawn = false\nORDER BY n.nodename, p.prefix",
"refId": "A"
}
],
"title": "SR Prefix SIDs",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 28
},
"id": 7,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT local_router_name as \"Local\",\n remote_router_name as \"Remote\",\n sr_adjacency_sids as \"Adjacency SIDs\",\n peer_node_sid as \"Peer Node SID\",\n mpls_proto_mask::text as \"MPLS Proto\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false\nORDER BY local_router_name, remote_router_name",
"refId": "A"
}
],
"title": "SR Adjacency SIDs",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"displayMode": "auto"
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 28
},
"id": 8,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT srlg as \"SRLG Value\",\n COUNT(*) as \"Link Count\",\n string_agg(DISTINCT local_router_name || ' -> ' || remote_router_name, ', ') as \"Links\"\nFROM v_ls_links\nWHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false \n AND srlg IS NOT NULL AND srlg != ''\nGROUP BY srlg\nORDER BY COUNT(*) DESC",
"refId": "A"
}
],
"title": "SRLG Groups",
"type": "table"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 5,
"x": 0,
"y": 36
},
"id": 9,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_links WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND te_def_metric IS NOT NULL",
"refId": "A"
}
],
"title": "Links with TE Metric",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 5,
"x": 5,
"y": 36
},
"id": 10,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_links WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND max_link_bw IS NOT NULL AND max_link_bw > 0",
"refId": "A"
}
],
"title": "Links with Bandwidth",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 5,
"x": 10,
"y": 36
},
"id": 11,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_links WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND srlg IS NOT NULL AND srlg != ''",
"refId": "A"
}
],
"title": "Links with SRLG",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 5,
"x": 15,
"y": 36
},
"id": 12,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_nodes WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND sr_capabilities IS NOT NULL AND sr_capabilities != ''",
"refId": "A"
}
],
"title": "Nodes with SR",
"type": "stat"
},
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 4,
"x": 20,
"y": 36
},
"id": 13,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"format": "table",
"rawSql": "SELECT COUNT(*) FROM v_ls_links WHERE peer_hash_id = '$peer_hash' AND iswithdrawn = false AND sr_adjacency_sids IS NOT NULL AND sr_adjacency_sids != ''",
"refId": "A"
}
],
"title": "Links with Adj SID",
"type": "stat"
},
{
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 40
},
"id": 14,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "## Traffic Engineering & Segment Routing Analytics\n\nThis dashboard exposes TE and SR attributes from BGP-LS (RFC 7752) that OpenBMP collects but existing dashboards don't display.\n\n### TE Fields (from ls_links)\n- **admin_group**: Link color/affinity bitmap for RSVP-TE constraints\n- **max_link_bw / max_resv_bw**: Link capacity in bytes/sec\n- **unreserved_bw**: Available bandwidth per priority level\n- **te_def_metric**: TE metric (may differ from IGP metric)\n- **protection_type**: FRR protection (unprotected, shared, dedicated, etc.)\n- **srlg**: Shared Risk Link Group for diverse path computation\n\n### SR Fields\n- **sr_capabilities**: Node SRGB (Segment Routing Global Block) range\n- **sr_prefix_sids**: Prefix SID for SR-MPLS forwarding\n- **sr_adjacency_sids**: Adjacency SIDs for SR-TE path steering\n- **peer_node_sid**: BGP EPE SID (RFC 9086)\n\n### Notes\n- NULL values indicate the router is not advertising that TLV\n- To enable TE metrics on IOS-XR: `mpls traffic-eng` under IS-IS\n- To enable SR: `segment-routing mpls` under IS-IS with prefix-sid-map",
"mode": "markdown"
},
"title": "About This Dashboard",
"type": "text"
}
],
"schemaVersion": 39,
"tags": [
"obmp-learning",
"obmp",
"obmp-nav"
],
"templating": {
"list": [
{
"current": {},
"datasource": {
"type": "postgres",
"uid": "obmp_postgres"
},
"definition": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0",
"hide": 0,
"includeAll": false,
"label": "BGP Peer",
"multi": false,
"name": "peer_hash",
"options": [],
"query": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "TE & Segment Routing Analytics",
"uid": "obmp-learn-08",
"version": 1
}

View File

@ -26,19 +26,7 @@
"graphTooltip": 0,
"id": 11,
"iteration": 1654876675775,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -961,9 +949,7 @@
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp-tops",
"obmp",
"obmp-nav"
"obmp-tops"
],
"templating": {
"list": [

View File

@ -26,19 +26,7 @@
"graphTooltip": 0,
"id": 12,
"iteration": 1654876366831,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"obmp-nav"
],
"title": "OBMP Dashboards",
"type": "dashboards"
}
],
"links": [],
"liveNow": false,
"panels": [
{
@ -1280,9 +1268,7 @@
"schemaVersion": 36,
"style": "dark",
"tags": [
"obmp-tops",
"obmp",
"obmp-nav"
"obmp-tops"
],
"templating": {
"list": [

View File

@ -1,71 +0,0 @@
# OpenBMP — Grafana contact points & notification policy provisioning
# Grafana 9.1.7 (apiVersion: 1)
#
# Defines WHERE alert notifications go (contact points) and WHICH alerts go
# there (the notification policy tree). Pairs with obmp-alerts.yaml in this
# directory.
#
# ----------------------------------------------------------------------
# OPERATOR REVIEW — this file ships with PLACEHOLDERS. Fill them in.
# ----------------------------------------------------------------------
# * The 'obmp-ops' contact point below has BOTH an email and a webhook
# receiver as examples. Delete whichever you do not use and fill in real
# values for the one you keep.
# * EMAIL requires Grafana SMTP to be configured (the [smtp] section of
# grafana.ini, or GF_SMTP_* env vars on the obmp-grafana container).
# Without working SMTP the email receiver silently fails.
# * WEBHOOK url: point it at your alerting system (Slack incoming webhook,
# PagerDuty Events API, Mattermost, an internal handler, etc.).
# * After editing, restart Grafana and verify under
# Alerting > Contact points > (test).
# ----------------------------------------------------------------------
apiVersion: 1
# --- Contact points ----------------------------------------------------
contactPoints:
- orgId: 1
name: obmp-ops
receivers:
# ---- Email receiver (requires Grafana SMTP configured) ----
- uid: obmp-ops-email
type: email
settings:
# REPLACE with the real NOC / on-call distribution address(es).
# Comma-separate multiple recipients.
addresses: noc@example.net
singleEmail: false
disableResolveMessage: false
# ---- Webhook receiver (Slack / PagerDuty / internal handler) ----
# Delete this block if you only use email.
- uid: obmp-ops-webhook
type: webhook
settings:
# REPLACE with your real webhook endpoint.
url: https://hooks.example.net/services/REPLACE-ME
httpMethod: POST
disableResolveMessage: false
# --- Notification policy tree -----------------------------------------
# The root policy routes every alert from obmp-alerts.yaml to 'obmp-ops'.
# Sub-routes split by the `severity` label so critical alerts can page
# faster / repeat sooner than warnings.
policies:
- orgId: 1
receiver: obmp-ops
# Group alerts that share these labels into a single notification.
group_by: ['alertname', 'service']
# Timing for the default (warning-ish) path.
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
# Critical alerts (peer down, router BMP down): notify fast, repeat
# more often until resolved.
- receiver: obmp-ops
matchers:
- severity = critical
group_wait: 10s
group_interval: 2m
repeat_interval: 1h

View File

@ -1,270 +0,0 @@
# OpenBMP — Grafana unified-alerting rule provisioning
# Grafana 9.1.7 (apiVersion: 1)
#
# Provisioned alert rules for the OpenBMP BGP-monitoring stack. They query the
# PostgreSQL datasource (uid: obmp_postgres) and fire on BGP peer/router
# session loss, peer flap storms, and RPKI-invalid routes.
#
# ----------------------------------------------------------------------
# DEPLOYMENT
# ----------------------------------------------------------------------
# This file is read by Grafana from /etc/grafana/provisioning/alerting/.
# The compose stack bind-mounts ${OBMP_DATA_ROOT}/grafana/provisioning into
# the container, so copy this directory there and restart Grafana:
#
# cp -r obmp-grafana/provisioning/alerting ${OBMP_DATA_ROOT}/grafana/provisioning/
# docker compose -p obmp restart grafana
#
# Pair it with contact-points.yaml (in this directory) for notifications.
#
# ----------------------------------------------------------------------
# OPERATOR REVIEW — fields you should check before relying on these
# ----------------------------------------------------------------------
# * folderUID: '1001' — reuses the existing 'OBMP-Base' dashboard folder so
# the rules have a home in the UI. Change it to a dedicated alerting
# folder UID if you prefer; the folder must already exist in Grafana.
# * datasourceUid: obmp_postgres — confirmed correct for this stack.
# * Thresholds and `for:` durations below are reasonable starting points.
# Tune them against your production baseline (40 full-table routers will
# have a different normal flap/churn profile than the lab).
# * The reduce/threshold expression UIDs (B, C) and refIds are internal to
# each rule; do not rename them without updating the matching references.
# * Alert-rule provisioning YAML is intricate. These definitions are
# intentionally minimal and well-commented. After first load, open each
# rule in the Grafana UI (Alerting > Alert rules) and confirm it
# evaluates without error before depending on it for paging.
# ----------------------------------------------------------------------
apiVersion: 1
groups:
- orgId: 1
name: OpenBMP BGP Health
folder: OBMP-Base
# How often every rule in this group is evaluated.
interval: 1m
rules:
# ------------------------------------------------------------------
# (a) BGP peer down within the last 15 minutes
# ------------------------------------------------------------------
# bgp_peers.state is an enum ('up'/'down'); .timestamp is the last
# state-change time. A peer whose state is 'down' AND changed within
# the last 15 min indicates a recent session loss.
- uid: obmp-peer-down
title: BGP Peer Down (recent)
condition: C
for: 5m
data:
- refId: A
relativeTimeRange: { from: 600, to: 0 }
datasourceUid: obmp_postgres
model:
refId: A
datasource: { type: postgres, uid: obmp_postgres }
format: table
rawSql: >
SELECT count(*)::float8 AS value
FROM bgp_peers
WHERE state = 'down'
AND timestamp > (now() AT TIME ZONE 'utc') - interval '15 minutes';
- refId: B
datasourceUid: __expr__
model:
refId: B
type: reduce
datasource: { type: __expr__, uid: __expr__ }
expression: A
reducer: last
- refId: C
datasourceUid: __expr__
model:
refId: C
type: threshold
datasource: { type: __expr__, uid: __expr__ }
expression: B
# Fire when one or more peers went down in the last 15 min.
conditions:
- evaluator: { type: gt, params: [0] }
labels:
severity: critical
service: bmp
annotations:
summary: One or more BGP peers went down in the last 15 minutes
description: >
{{ $values.B }} BGP peer(s) are in state 'down' with a state
change within the last 15 minutes. Check the OBMP peer
inventory and the affected routers.
# ------------------------------------------------------------------
# (b) Peer flap storm — >5 down-events for one peer in 1 hour
# ------------------------------------------------------------------
# peer_event_log records every peer state transition. Counting 'down'
# events per peer over the last hour detects a flapping session even
# if the peer is currently 'up'. The inner query groups per peer; the
# outer takes the worst offender's count.
- uid: obmp-peer-flap-storm
title: BGP Peer Flap Storm
condition: C
for: 0m
data:
- refId: A
relativeTimeRange: { from: 3600, to: 0 }
datasourceUid: obmp_postgres
model:
refId: A
datasource: { type: postgres, uid: obmp_postgres }
format: table
rawSql: >
SELECT coalesce(max(c), 0)::float8 AS value
FROM (
SELECT count(*) AS c
FROM peer_event_log
WHERE state = 'down'
AND timestamp > (now() AT TIME ZONE 'utc') - interval '1 hour'
GROUP BY peer_hash_id
) s;
- refId: B
datasourceUid: __expr__
model:
refId: B
type: reduce
datasource: { type: __expr__, uid: __expr__ }
expression: A
reducer: last
- refId: C
datasourceUid: __expr__
model:
refId: C
type: threshold
datasource: { type: __expr__, uid: __expr__ }
expression: B
# >5 down-events for a single peer within 1h = flap storm.
conditions:
- evaluator: { type: gt, params: [5] }
labels:
severity: warning
service: bmp
annotations:
summary: A BGP peer is flapping (more than 5 resets in the last hour)
description: >
At least one peer has logged {{ $values.B }} 'down' events in
peer_event_log within the last hour. Investigate link/session
instability on the affected peer.
# ------------------------------------------------------------------
# (c) RPKI-invalid routes present
# ------------------------------------------------------------------
# ip_rib has no RPKI column on this schema, so validity is derived by
# joining against rpki_validator (ROA cache, refreshed by the psql-app
# RPKI cron). A route is "invalid" when a covering ROA exists for the
# prefix but NO ROA matches its origin AS.
#
# NOTE: rpki_validator is empty until ENABLE_RPKI=1 has run at least
# once (every ~2h). Until then this rule correctly reports 0.
- uid: obmp-rpki-invalid
title: RPKI-Invalid Routes Present
condition: C
for: 10m
data:
- refId: A
relativeTimeRange: { from: 600, to: 0 }
datasourceUid: obmp_postgres
model:
refId: A
datasource: { type: postgres, uid: obmp_postgres }
format: table
rawSql: >
SELECT count(*)::float8 AS value
FROM ip_rib r
WHERE r.iswithdrawn = false
AND r.origin_as IS NOT NULL
AND EXISTS (
SELECT 1 FROM rpki_validator v
WHERE r.prefix <<= v.prefix
AND r.prefix_len BETWEEN masklen(v.prefix) AND v.prefix_len_max
)
AND NOT EXISTS (
SELECT 1 FROM rpki_validator v2
WHERE r.prefix <<= v2.prefix
AND r.prefix_len BETWEEN masklen(v2.prefix) AND v2.prefix_len_max
AND v2.origin_as = r.origin_as
);
- refId: B
datasourceUid: __expr__
model:
refId: B
type: reduce
datasource: { type: __expr__, uid: __expr__ }
expression: A
reducer: last
- refId: C
datasourceUid: __expr__
model:
refId: C
type: threshold
datasource: { type: __expr__, uid: __expr__ }
expression: B
# Any RPKI-invalid route is worth surfacing. Raise the param
# (e.g. to 10) if you expect a steady-state baseline of
# invalids and only want to alert on spikes.
conditions:
- evaluator: { type: gt, params: [0] }
labels:
severity: warning
service: routing-security
annotations:
summary: RPKI-invalid routes are present in the RIB
description: >
{{ $values.B }} route(s) in ip_rib are RPKI-invalid (a covering
ROA exists but none matches the route's origin AS). Possible
mis-origination or hijack — review the RPKI Validation dashboard.
# ------------------------------------------------------------------
# (d) Router BMP session down
# ------------------------------------------------------------------
# routers.state is the BMP session state for each monitored router.
# 'down' means the router's BMP feed to the collector has dropped.
- uid: obmp-router-bmp-down
title: Router BMP Session Down
condition: C
for: 5m
data:
- refId: A
relativeTimeRange: { from: 600, to: 0 }
datasourceUid: obmp_postgres
model:
refId: A
datasource: { type: postgres, uid: obmp_postgres }
format: table
rawSql: >
SELECT count(*)::float8 AS value
FROM routers
WHERE state = 'down';
- refId: B
datasourceUid: __expr__
model:
refId: B
type: reduce
datasource: { type: __expr__, uid: __expr__ }
expression: A
reducer: last
- refId: C
datasourceUid: __expr__
model:
refId: C
type: threshold
datasource: { type: __expr__, uid: __expr__ }
expression: B
# Any router with a down BMP session.
conditions:
- evaluator: { type: gt, params: [0] }
labels:
severity: critical
service: bmp
annotations:
summary: One or more routers have a down BMP session
description: >
{{ $values.B }} router(s) are in BMP state 'down' — the
collector is no longer receiving BMP from them. Check the
router BMP config and reachability to the collector on port 5000.

View File

@ -27,7 +27,7 @@ providers:
# <int> Org id. Default to 1
orgId: 1
# <string> name of the dashboard folder.
folder: 'OBMP-Operations'
folder: 'OBMP-Base'
# <string> folder UID. will be automatically generated if not specified
folderUid: '1001'
# <string> provider type. Default to 'file'
@ -47,7 +47,7 @@ providers:
# <int> Org id. Default to 1
orgId: 1
# <string> name of the dashboard folder.
folder: 'OBMP-Routing'
folder: 'OBMP-History'
# <string> folder UID. will be automatically generated if not specified
folderUid: '1002'
# <string> provider type. Default to 'file'
@ -63,6 +63,26 @@ providers:
path: /var/lib/grafana/dashboards/obmp/History-1002
# <bool> use folder names from filesystem to create folders in Grafana
foldersFromFilesStructure: false
- name: 'OpenBMP-Tops'
# <int> Org id. Default to 1
orgId: 1
# <string> name of the dashboard folder.
folder: 'OBMP-Tops'
# <string> folder UID. will be automatically generated if not specified
folderUid: '1003'
# <string> provider type. Default to 'file'
type: file
# <bool> disable dashboard deletion
disableDeletion: false
# <int> how often Grafana will scan for changed dashboards
updateIntervalSeconds: 30
# <bool> allow updating provisioned dashboards from the UI
allowUiUpdates: true
options:
# <string, required> path to dashboard files on disk. Required when using the 'file' type
path: /var/lib/grafana/dashboards/obmp/Tops-1003
# <bool> use folder names from filesystem to create folders in Grafana
foldersFromFilesStructure: false
- name: 'OpenBMP-LinkState'
# <int> Org id. Default to 1
orgId: 1
@ -105,7 +125,7 @@ providers:
foldersFromFilesStructure: false
- name: 'OpenBMP-Learning'
orgId: 1
folder: 'OBMP-Reference'
folder: 'OBMP-Learning'
folderUid: '2001'
type: file
disableDeletion: false

View File

@ -1,105 +0,0 @@
#!/usr/bin/env bash
#
# pg-backup.sh — logical backup of the OpenBMP PostgreSQL database.
#
# Performs a `pg_dump` of the `openbmp` database inside the obmp-psql
# container, writes a timestamped compressed dump to a backup directory,
# and prunes dumps older than the configured retention.
#
# Usage:
# ./pg-backup.sh
#
# Configuration (environment variables, all optional):
# OBMP_DATA_ROOT Base data dir. Default: /var/openbmp
# Backups go to ${OBMP_DATA_ROOT}/backups unless
# OBMP_BACKUP_DIR is set.
# OBMP_BACKUP_DIR Explicit backup directory. Overrides the default.
# OBMP_PG_CONTAINER Postgres container name. Default: obmp-psql
# OBMP_PG_DB Database name. Default: openbmp
# OBMP_PG_USER Database user. Default: openbmp
# OBMP_BACKUP_RETENTION_DAYS Prune dumps older than N days. Default: 14
#
# Output format:
# pg_dump custom format (-Fc), gzip-level compressed by pg_dump itself.
# Restore with `pg_restore` — see docs/backup-restore.md.
#
# This script is idempotent and safe to run repeatedly. It does not stop
# the database; pg_dump takes a consistent MVCC snapshot of a live DB.
#
# Make it executable once:
# chmod +x scripts/pg-backup.sh
#
# ----------------------------------------------------------------------
# Scheduling via cron
# ----------------------------------------------------------------------
# Run `crontab -e` and add (daily at 02:30, log to a file):
#
# 30 2 * * * OBMP_DATA_ROOT=/var/openbmp /home/user/obmp-docker/scripts/pg-backup.sh >> /var/openbmp/backups/pg-backup.log 2>&1
#
# The script must be able to reach the Docker daemon, so run it as a user
# in the `docker` group (or root). For systemd-based hosts a
# systemd timer is an equally good alternative to cron.
# ----------------------------------------------------------------------
set -euo pipefail
# --- Configuration -----------------------------------------------------
OBMP_DATA_ROOT="${OBMP_DATA_ROOT:-/var/openbmp}"
BACKUP_DIR="${OBMP_BACKUP_DIR:-${OBMP_DATA_ROOT}/backups}"
PG_CONTAINER="${OBMP_PG_CONTAINER:-obmp-psql}"
PG_DB="${OBMP_PG_DB:-openbmp}"
PG_USER="${OBMP_PG_USER:-openbmp}"
RETENTION_DAYS="${OBMP_BACKUP_RETENTION_DAYS:-14}"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
DUMP_NAME="openbmp-${TIMESTAMP}.dump"
DUMP_PATH="${BACKUP_DIR}/${DUMP_NAME}"
DUMP_TMP="${DUMP_PATH}.partial"
log() { printf '%s [pg-backup] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*"; }
fail() { log "ERROR: $*" >&2; exit 1; }
# --- Pre-flight checks -------------------------------------------------
command -v docker >/dev/null 2>&1 || fail "docker command not found in PATH"
if ! docker inspect -f '{{.State.Running}}' "${PG_CONTAINER}" 2>/dev/null | grep -q true; then
fail "container '${PG_CONTAINER}' is not running"
fi
mkdir -p "${BACKUP_DIR}" || fail "cannot create backup directory ${BACKUP_DIR}"
# --- Backup ------------------------------------------------------------
# Write to a .partial file first, then atomically rename on success so a
# crashed/interrupted run never leaves a truncated dump that looks valid.
log "starting backup of database '${PG_DB}' from container '${PG_CONTAINER}'"
if docker exec "${PG_CONTAINER}" \
pg_dump -U "${PG_USER}" -d "${PG_DB}" -Fc --no-owner --no-privileges \
> "${DUMP_TMP}"; then
mv -f "${DUMP_TMP}" "${DUMP_PATH}"
else
rm -f "${DUMP_TMP}"
fail "pg_dump failed; no backup written"
fi
DUMP_SIZE="$(du -h "${DUMP_PATH}" | cut -f1)"
log "backup complete: ${DUMP_PATH} (${DUMP_SIZE})"
# --- Prune old backups -------------------------------------------------
# Only prune files matching our own naming pattern, so nothing else in the
# directory (logs, manual dumps) is touched.
log "pruning dumps older than ${RETENTION_DAYS} days"
PRUNED=0
while IFS= read -r -d '' old; do
rm -f "${old}"
log " removed $(basename "${old}")"
PRUNED=$((PRUNED + 1))
done < <(find "${BACKUP_DIR}" -maxdepth 1 -type f \
-name 'openbmp-*.dump' -mtime "+${RETENTION_DAYS}" -print0)
log "pruned ${PRUNED} old dump(s)"
# Also clean up any stale .partial files from previous crashed runs.
find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'openbmp-*.dump.partial' \
-mtime +1 -delete 2>/dev/null || true
log "done"

View File

@ -17,16 +17,15 @@
# INPUT PLUGINS #
###############################################################################
## gNMI targets — driven by environment variables so the telemetry fleet can
## scale without editing this file. Set in .env:
## GNMI_ADDRESSES — quoted, comma-separated host:port list, e.g.
## GNMI_ADDRESSES="10.0.0.1:57400", "10.0.0.2:57400"
## GNMI_USERNAME / GNMI_PASSWORD — gNMI credentials (uniform across the fleet)
## Every target must have gNMI/grpc enabled and be reachable on the gRPC port.
## CORE routers (directly reachable on port 57400 from host)
## R9K routers (10.100.0.1-7) are blocked by CML management network filtering
[[inputs.gnmi]]
addresses = [ ${GNMI_ADDRESSES} ]
username = "${GNMI_USERNAME}"
password = "${GNMI_PASSWORD}"
addresses = [
"10.100.0.100:57400",
"10.100.0.200:57400"
]
username = "webui"
password = "cisco"
## No TLS (lab environment)
enable_tls = false