The AS map previously exploded ~4.4M base_attrs AS_PATH rows live, three times per load (one per panel), ~1.8s each — slow enough that navigating away cancelled the queries mid-flight. Add mv_as_adjacency: undirected consecutive-AS pairs with occurrence counts over the full RIB (17k rows), refreshed hourly by pg_cron via REFRESH ... CONCURRENTLY. The dashboard panels now read the view in ~1ms. Min-occurrence options rescaled for full-RIB counts (2000/5000/10000/50000, default 2000 -> ~63-node graph). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
71 lines
5.3 KiB
JSON
71 lines
5.3 KiB
JSON
{
|
|
"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 adjacency graph derived from consecutive AS pairs in observed AS_PATHs. Reads the mv_as_adjacency materialized view (full RIB, refreshed hourly by pg_cron) so panels load instantly. Edge label = how many times that adjacency appears. Raise 'Min occurrences' to thin the graph; set 'Focus AS' for a 1-hop view around one AS.",
|
|
"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"}],
|
|
"liveNow": false,
|
|
"panels": [
|
|
{
|
|
"datasource": {"type": "postgres","uid": "obmp_postgres"},
|
|
"description": "Each node is an AS (enriched from info_asn whois data); each edge is an adjacency seen in the AS_PATH data. Edge label is the occurrence count.",
|
|
"fieldConfig": {"defaults": {},"overrides": []},
|
|
"gridPos": {"h": 24,"w": 24,"x": 0,"y": 0},
|
|
"id": 1,
|
|
"options": {"nodes": {"mainStatUnit": "","secondaryStatUnit": ""}},
|
|
"targets": [
|
|
{
|
|
"datasource": {"type": "postgres","uid": "obmp_postgres"},
|
|
"format": "table",
|
|
"rawSql": "WITH edges AS (\n SELECT asn_a, asn_b, occ FROM mv_as_adjacency\n WHERE occ >= $min_occ\n AND ('$focus_as' = '' OR asn_a::text = '$focus_as' OR asn_b::text = '$focus_as')\n ORDER BY occ DESC LIMIT 300\n),\nnlist AS (SELECT asn_a AS asn FROM edges UNION SELECT asn_b FROM edges),\ndeg AS (SELECT asn, COUNT(*) AS d FROM (SELECT asn_a AS asn FROM edges UNION ALL SELECT asn_b FROM edges) z GROUP BY asn)\nSELECT n.asn::text AS id,\n COALESCE(NULLIF(ia.as_name,''),'AS'||n.asn) AS title,\n 'AS ' || n.asn AS mainstat,\n COALESCE(NULLIF(ia.country,''),'?') || ' · ' || dg.d::text || ' links' AS secondarystat,\n COALESCE(NULLIF(ia.org_name,''),'—') AS detail__org,\n COALESCE(NULLIF(ia.country,''),'—') AS detail__country,\n dg.d::text AS detail__degree\nFROM nlist n\nLEFT JOIN info_asn ia ON ia.asn = n.asn\nLEFT JOIN deg dg ON dg.asn = n.asn\nORDER BY dg.d DESC",
|
|
"refId": "nodes"
|
|
},
|
|
{
|
|
"datasource": {"type": "postgres","uid": "obmp_postgres"},
|
|
"format": "table",
|
|
"rawSql": "SELECT asn_a::text || '-' || asn_b::text AS id,\n asn_a::text AS source, asn_b::text AS target,\n occ AS mainstat,\n occ::text || ' paths' AS detail__occurrences\nFROM mv_as_adjacency\nWHERE occ >= $min_occ\n AND ('$focus_as' = '' OR asn_a::text = '$focus_as' OR asn_b::text = '$focus_as')\nORDER BY occ DESC LIMIT 300",
|
|
"refId": "edges"
|
|
}
|
|
],
|
|
"title": "AS Adjacency Graph",
|
|
"type": "nodeGraph"
|
|
},
|
|
{
|
|
"datasource": {"type": "postgres","uid": "obmp_postgres"},
|
|
"description": "The strongest AS adjacencies, with whois names for both endpoints.",
|
|
"fieldConfig": {"defaults": {"custom": {"align": "auto","displayMode": "auto"}},"overrides": [{"matcher": {"id": "byName","options": "Occurrences"},"properties": [{"id": "custom.displayMode","value": "gradient-gauge"},{"id": "color","value": {"mode": "continuous-BlPu"}}]}]},
|
|
"gridPos": {"h": 10,"w": 24,"x": 0,"y": 24},
|
|
"id": 2,
|
|
"options": {"showHeader": true,"sortBy": [{"desc": true,"displayName": "Occurrences"}]},
|
|
"targets": [
|
|
{
|
|
"datasource": {"type": "postgres","uid": "obmp_postgres"},
|
|
"format": "table",
|
|
"rawSql": "SELECT e.asn_a AS \"AS A\",\n COALESCE(NULLIF(ax.as_name,''),'—') AS \"Name A\",\n e.asn_b AS \"AS B\",\n COALESCE(NULLIF(ay.as_name,''),'—') AS \"Name B\",\n e.occ AS \"Occurrences\"\nFROM (\n SELECT asn_a, asn_b, occ FROM mv_as_adjacency\n WHERE occ >= $min_occ\n AND ('$focus_as' = '' OR asn_a::text = '$focus_as' OR asn_b::text = '$focus_as')\n ORDER BY occ DESC LIMIT 300\n) e\nLEFT JOIN info_asn ax ON ax.asn = e.asn_a\nLEFT JOIN info_asn ay ON ay.asn = e.asn_b\nORDER BY e.occ DESC",
|
|
"refId": "A"
|
|
}
|
|
],
|
|
"title": "Top AS Adjacencies",
|
|
"type": "table"
|
|
}
|
|
],
|
|
"schemaVersion": 36,
|
|
"style": "dark",
|
|
"tags": ["obmp", "obmp-nav", "obmp-maps", "asn"],
|
|
"templating": {
|
|
"list": [
|
|
{"name": "min_occ","type": "custom","label": "Min occurrences","description": "Only draw adjacencies seen at least this many times across the RIB. Raise it to thin a cluttered graph.","query": "2000,5000,10000,50000","current": {"text": "2000","value": "2000"},"options": [{"text": "2000","value": "2000","selected": true},{"text": "5000","value": "5000","selected": false},{"text": "10000","value": "10000","selected": false},{"text": "50000","value": "50000","selected": false}],"hide": 0},
|
|
{"name": "focus_as","type": "textbox","label": "Focus AS","description": "Optional. Enter an ASN to show only adjacencies touching that AS (1-hop view). Leave blank for the full graph.","query": "","current": {"text": "","value": ""},"options": [{"text": "","value": "","selected": true}],"hide": 0}
|
|
]
|
|
},
|
|
"time": {"from": "now-6h","to": "now"},
|
|
"timepicker": {},
|
|
"timezone": "",
|
|
"title": "AS Relationship Map",
|
|
"uid": "as-rel-map",
|
|
"version": 1,
|
|
"weekStart": ""
|
|
}
|