This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

configs.gg23.internet: monitor the Vodafone Station, latency and speedtests, replace HA SpeedTest

+928
+392
configs/gg23/internet-hass.nix
··· 1 + # Home Assistant side of ./internet.nix: sensors that query VictoriaMetrics on 2 + # styx (the gg23 monitoring server) and an "Internet" dashboard explaining 3 + # why the internet is slow. Both are mounted into the container as a package 4 + # and a YAML dashboard (configs/hass loads /config/packages); generated as 5 + # JSON, which HA's YAML loader reads as well. 6 + { 7 + lib, 8 + pkgs, 9 + self, 10 + ... 11 + }: 12 + let 13 + inherit (import ../monitoring/default.nix) ports; 14 + # IPv4: VictoriaMetrics doesn't listen on IPv6, styx.r resolves to it first. 15 + vm = "http://${self.inputs.kartei.hosts.styx.nets.retiolum.ip4.addr}:${toString ports.victoriametrics}/api/v1/query"; 16 + 17 + # booked Vodafone cable rate in Mbit/s (speedtests from acheron: ~920/50) 18 + line = { 19 + down = 1000; 20 + up = 50; 21 + }; 22 + 23 + ping = sel: { 24 + latency = ''avg(rate(smokeping_response_duration_seconds_sum{host="acheron",${sel}}[5m]) / rate(smokeping_response_duration_seconds_count{host="acheron",${sel}}[5m])) * 1000''; 25 + p95 = ''histogram_quantile(0.95, sum(rate(smokeping_response_duration_seconds_bucket{host="acheron",${sel}}[5m])) by (le)) * 1000''; 26 + loss = ''100 * (1 - sum(rate(smokeping_response_duration_seconds_count{host="acheron",${sel}}[5m])) / sum(rate(smokeping_requests_total{host="acheron",${sel}}[5m])))''; 27 + }; 28 + internet = ping ''target_name=~"cloudflare|google"''; 29 + cmts = ping ''target_name="cmts"''; 30 + station = ping ''target_name="station"''; 31 + wan = dir: ''rate(host_network_${dir}_bytes_total{host="acheron",device="et0"}[2m]) * 8 / 1e6''; 32 + docsis = ''{host="acheron",channel_type="SC-QAM"}''; 33 + 34 + # id -> sensor.internet_<id> 35 + sensors = { 36 + latency = { 37 + name = "Latency"; 38 + query = internet.latency; 39 + unit = "ms"; 40 + icon = "mdi:timer-outline"; 41 + }; 42 + latency_p95 = { 43 + name = "Latency p95"; 44 + query = internet.p95; 45 + unit = "ms"; 46 + icon = "mdi:timer-alert-outline"; 47 + }; 48 + loss = { 49 + name = "Packet loss"; 50 + query = internet.loss; 51 + unit = "%"; 52 + icon = "mdi:lan-disconnect"; 53 + }; 54 + cmts_latency = { 55 + name = "Cable latency"; 56 + query = cmts.latency; 57 + unit = "ms"; 58 + icon = "mdi:timer-outline"; 59 + }; 60 + cmts_loss = { 61 + name = "Cable packet loss"; 62 + query = cmts.loss; 63 + unit = "%"; 64 + icon = "mdi:lan-disconnect"; 65 + }; 66 + station_latency = { 67 + name = "Station latency"; 68 + query = station.latency; 69 + unit = "ms"; 70 + icon = "mdi:router-network"; 71 + }; 72 + station_loss = { 73 + name = "Station packet loss"; 74 + query = station.loss; 75 + unit = "%"; 76 + icon = "mdi:router-network"; 77 + }; 78 + download = { 79 + name = "Download"; 80 + query = wan "receive"; 81 + unit = "Mbit/s"; 82 + device_class = "data_rate"; 83 + icon = "mdi:download"; 84 + }; 85 + upload = { 86 + name = "Upload"; 87 + query = wan "transmit"; 88 + unit = "Mbit/s"; 89 + device_class = "data_rate"; 90 + icon = "mdi:upload"; 91 + }; 92 + # hourly, ./internet-speedtest.nix; last result within 2h 93 + speedtest_download = { 94 + name = "Speedtest download"; 95 + query = ''last_over_time(speedtest_download_bits_per_second{host="acheron"}[2h]) / 1e6''; 96 + unit = "Mbit/s"; 97 + device_class = "data_rate"; 98 + icon = "mdi:speedometer"; 99 + }; 100 + speedtest_upload = { 101 + name = "Speedtest upload"; 102 + query = ''last_over_time(speedtest_upload_bits_per_second{host="acheron"}[2h]) / 1e6''; 103 + unit = "Mbit/s"; 104 + device_class = "data_rate"; 105 + icon = "mdi:speedometer"; 106 + }; 107 + speedtest_latency = { 108 + name = "Speedtest latency"; 109 + query = ''last_over_time(speedtest_latency_seconds{host="acheron"}[2h]) * 1000''; 110 + unit = "ms"; 111 + icon = "mdi:timer-outline"; 112 + }; 113 + ds_snr_min = { 114 + name = "Downstream SNR min"; 115 + query = ''min(vodafone_station_docsis_snr_db{direction="downstream",host="acheron"})''; 116 + unit = "dB"; 117 + icon = "mdi:signal"; 118 + }; 119 + ds_power_min = { 120 + name = "Downstream power min"; 121 + query = ''min(vodafone_station_docsis_power_dbmv{direction="downstream",${lib.removePrefix "{" docsis})''; 122 + unit = "dBmV"; 123 + icon = "mdi:arrow-down-bold"; 124 + }; 125 + ds_power_max = { 126 + name = "Downstream power max"; 127 + query = ''max(vodafone_station_docsis_power_dbmv{direction="downstream",${lib.removePrefix "{" docsis})''; 128 + unit = "dBmV"; 129 + icon = "mdi:arrow-down-bold"; 130 + }; 131 + us_power_max = { 132 + name = "Upstream power max"; 133 + query = ''max(vodafone_station_docsis_power_dbmv{direction="upstream",${lib.removePrefix "{" docsis})''; 134 + unit = "dBmV"; 135 + icon = "mdi:arrow-up-bold"; 136 + }; 137 + unlocked = { 138 + name = "Unlocked channels"; 139 + query = ''count(vodafone_station_docsis_locked{host="acheron"} == 0) or vector(0)''; 140 + icon = "mdi:lock-open-alert"; 141 + }; 142 + docsis_online = { 143 + name = "DOCSIS online"; 144 + query = ''max(vodafone_station_docsis_online{host="acheron"})''; 145 + icon = "mdi:cable-data"; 146 + }; 147 + station_up = { 148 + name = "Station scrape"; 149 + query = ''max(vodafone_station_up{host="acheron"})''; 150 + icon = "mdi:router-wireless"; 151 + }; 152 + station_uptime = { 153 + name = "Station uptime"; 154 + query = ''max(vodafone_station_uptime_seconds{host="acheron"})''; 155 + unit = "s"; 156 + device_class = "duration"; 157 + icon = "mdi:restart"; 158 + }; 159 + }; 160 + 161 + restEntry = id: s: { 162 + resource = vm; 163 + params.query = s.query; 164 + scan_interval = 60; 165 + timeout = 20; 166 + sensor = [ 167 + ( 168 + { 169 + name = "Internet ${s.name}"; 170 + unique_id = "gg23_internet_${id}"; 171 + # no series yet (e.g. rate() right after a restart): unavailable 172 + availability = "{{ value_json is defined and value_json.data.result | length > 0 }}"; 173 + value_template = "{{ (value_json.data.result[0].value[1] | float) | round(2) }}"; 174 + state_class = "measurement"; 175 + inherit (s) icon; 176 + } 177 + // lib.optionalAttrs (s ? unit) { unit_of_measurement = s.unit; } 178 + // lib.optionalAttrs (s ? device_class) { inherit (s) device_class; } 179 + ) 180 + ]; 181 + }; 182 + 183 + # HA derives the entity id from the name ("Internet Latency p95" -> 184 + # sensor.internet_latency_p95); unique_id doesn't influence it. 185 + e = 186 + id: 187 + "sensor.internet_" + lib.toLower (lib.replaceStrings [ " " ] [ "_" ] (sensors.${id}.name or id)); 188 + 189 + # Each check: the problem it names, in the order things are upstream of 190 + # each other (LAN cable -> Station -> cable segment -> backbone). Thresholds 191 + # for DOCSIS levels follow the usual Vodafone cable level tables. 192 + diagnosis = '' 193 + {%- set n = namespace(p=[]) -%} 194 + {%- if states('${e "station_up"}') in ['0.0', '0'] -%} 195 + {%- set n.p = n.p + ['Station web UI not scrapeable'] -%} 196 + {%- endif -%} 197 + {%- if states('${e "docsis_online"}') in ['0.0', '0'] -%} 198 + {%- set n.p = n.p + ['Cable modem not DOCSIS online'] -%} 199 + {%- endif -%} 200 + {%- if states('${e "station_loss"}') | float(0) > 1 -%} 201 + {%- set n.p = n.p + ['Loss acheron->Station ' ~ states('${e "station_loss"}') ~ '% (LAN cable/port)'] -%} 202 + {%- endif -%} 203 + {%- if states('${e "unlocked"}') | float(0) > 0 -%} 204 + {%- set n.p = n.p + [states('${e "unlocked"}') | int ~ ' DOCSIS channels unlocked'] -%} 205 + {%- endif -%} 206 + {%- if states('${e "ds_snr_min"}') | float(99) < 33 -%} 207 + {%- set n.p = n.p + ['Downstream SNR low (' ~ states('${e "ds_snr_min"}') ~ ' dB)'] -%} 208 + {%- endif -%} 209 + {%- if states('${e "ds_power_min"}') | float(0) < -4 or states('${e "ds_power_max"}') | float(0) > 13 -%} 210 + {%- set n.p = n.p + ['Downstream level out of range (' ~ states('${e "ds_power_min"}') ~ '..' ~ states('${e "ds_power_max"}') ~ ' dBmV)'] -%} 211 + {%- endif -%} 212 + {%- if states('${e "us_power_max"}') | float(0) > 51 -%} 213 + {%- set n.p = n.p + ['Upstream level high (' ~ states('${e "us_power_max"}') ~ ' dBmV): return path attenuated/noisy'] -%} 214 + {%- endif -%} 215 + {%- if states('${e "cmts_loss"}') | float(0) > 1 -%} 216 + {%- set n.p = n.p + ['Loss on the cable segment ' ~ states('${e "cmts_loss"}') ~ '%'] -%} 217 + {%- endif -%} 218 + {%- if states('${e "cmts_latency"}') | float(0) > 30 -%} 219 + {%- set n.p = n.p + ['Cable segment slow (' ~ states('${e "cmts_latency"}') ~ ' ms to CMTS): node congested'] -%} 220 + {%- endif -%} 221 + {%- if states('${e "loss"}') | float(0) > 1 and states('${e "cmts_loss"}') | float(0) <= 1 -%} 222 + {%- set n.p = n.p + ['Loss behind the CMTS ' ~ states('${e "loss"}') ~ '% (Vodafone backbone)'] -%} 223 + {%- endif -%} 224 + {#- the hourly speedtest saturates the line itself: skip the 5 min window after a result -#} 225 + {%- set st = states['${e "speedtest_download"}'] -%} 226 + {%- set after_speedtest = st is not none and (now() - st.last_changed).total_seconds() < 420 -%} 227 + {%- if states('${e "latency_p95"}') | float(0) > 60 and not after_speedtest -%} 228 + {%- set n.p = n.p + ['Latency spikes (p95 ' ~ states('${e "latency_p95"}') ~ ' ms) at ' ~ states('${e "download"}') ~ '/' ~ states('${e "upload"}') ~ ' Mbit/s: bufferbloat or congestion'] -%} 229 + {%- endif -%} 230 + {%- if states('${e "station_uptime"}') | float(99999) < 3600 -%} 231 + {%- set n.p = n.p + ['Station rebooted in the last hour'] -%} 232 + {%- endif -%} 233 + {%- if states('${e "speedtest_download"}') | float(9999) < 0.3 * ${toString line.down} -%} 234 + {%- set n.p = n.p + ['Speedtest only ' ~ states('${e "speedtest_download"}') ~ ' of ${toString line.down} Mbit/s down'] -%} 235 + {%- endif -%} 236 + {%- if states('${e "speedtest_upload"}') | float(9999) < 0.3 * ${toString line.up} -%} 237 + {%- set n.p = n.p + ['Speedtest only ' ~ states('${e "speedtest_upload"}') ~ ' of ${toString line.up} Mbit/s up'] -%} 238 + {%- endif -%} 239 + ''; 240 + 241 + package = { 242 + rest = lib.mapAttrsToList restEntry sensors; 243 + template = [ 244 + { 245 + sensor = [ 246 + { 247 + name = "Internet diagnosis"; 248 + unique_id = "gg23_internet_diagnosis"; 249 + icon = "mdi:stethoscope"; 250 + # the problem itself when there is one, else the count 251 + state = "${diagnosis}{{ 'OK' if not n.p else n.p[0] if n.p | count == 1 else n.p | count ~ ' problems' }}"; 252 + attributes.problems = "${diagnosis}{{ ('- ' ~ n.p | join('\\n- ')) if n.p else 'nothing found' }}"; 253 + } 254 + ]; 255 + } 256 + ]; 257 + lovelace.dashboards.internet-gg23 = { 258 + mode = "yaml"; 259 + title = "Internet"; 260 + icon = "mdi:wan"; 261 + show_in_sidebar = true; 262 + filename = "dashboards/internet.yaml"; 263 + }; 264 + }; 265 + 266 + graph = title: hours: ids: { 267 + type = "history-graph"; 268 + inherit title; 269 + hours_to_show = hours; 270 + entities = map e ids; 271 + }; 272 + 273 + dashboard = { 274 + title = "Internet"; 275 + views = [ 276 + { 277 + title = "Internet"; 278 + path = "internet"; 279 + type = "sections"; 280 + max_columns = 3; 281 + sections = [ 282 + { 283 + type = "grid"; 284 + cards = [ 285 + { 286 + type = "markdown"; 287 + title = "Why does it suck?"; 288 + content = '' 289 + **{{ states('${e "diagnosis"}') }}** 290 + 291 + {{ state_attr('${e "diagnosis"}', 'problems') }} 292 + 293 + Path: acheron → Station (LAN) → CMTS (cable) → internet. 294 + Loss/latency appearing first at a hop points at that segment. 295 + ''; 296 + } 297 + { 298 + type = "entities"; 299 + title = "Now (5 min)"; 300 + entities = map e [ 301 + "download" 302 + "upload" 303 + "latency" 304 + "latency_p95" 305 + "loss" 306 + "cmts_latency" 307 + "cmts_loss" 308 + "station_latency" 309 + "station_loss" 310 + ]; 311 + } 312 + ]; 313 + } 314 + { 315 + type = "grid"; 316 + cards = [ 317 + (graph "Latency" 24 [ 318 + "latency" 319 + "latency_p95" 320 + "cmts_latency" 321 + "station_latency" 322 + ]) 323 + (graph "Packet loss" 24 [ 324 + "loss" 325 + "cmts_loss" 326 + "station_loss" 327 + ]) 328 + (graph "Throughput (WAN)" 24 [ 329 + "download" 330 + "upload" 331 + ]) 332 + { 333 + type = "entities"; 334 + title = "Speedtest (hourly, from acheron)"; 335 + entities = map e [ 336 + "speedtest_download" 337 + "speedtest_upload" 338 + "speedtest_latency" 339 + ]; 340 + } 341 + (graph "Speedtest" 168 [ 342 + "speedtest_download" 343 + "speedtest_upload" 344 + ]) 345 + ]; 346 + } 347 + { 348 + type = "grid"; 349 + cards = [ 350 + { 351 + type = "entities"; 352 + title = "Cable modem"; 353 + entities = map e [ 354 + "docsis_online" 355 + "unlocked" 356 + "ds_snr_min" 357 + "ds_power_min" 358 + "ds_power_max" 359 + "us_power_max" 360 + "station_uptime" 361 + "station_up" 362 + ]; 363 + } 364 + (graph "DOCSIS levels" 168 [ 365 + "ds_snr_min" 366 + "ds_power_min" 367 + "ds_power_max" 368 + "us_power_max" 369 + ]) 370 + { 371 + type = "markdown"; 372 + content = '' 373 + Good ranges: downstream -4..13 dBmV, SNR ≥ 33 dB (256QAM), 374 + upstream 35..51 dBmV. Per channel and longer history: 375 + Grafana "Internet" on styx.r:${toString ports.grafana}. 376 + ''; 377 + } 378 + ]; 379 + } 380 + ]; 381 + } 382 + ]; 383 + }; 384 + 385 + json = name: value: pkgs.writeText name (builtins.toJSON value); 386 + in 387 + { 388 + virtualisation.oci-containers.containers.homeassistant.volumes = [ 389 + "${json "internet.yaml" package}:/config/packages/internet.yaml:ro" 390 + "${json "internet-dashboard.yaml" dashboard}:/config/dashboards/internet.yaml:ro" 391 + ]; 392 + }
+114
configs/gg23/internet-speedtest.nix
··· 1 + # Hourly speedtest from acheron, replacing Home Assistant's SpeedTest 2 + # integration: speedtest.net geolocates our Vodafone IP near Sicily, so HA 3 + # only offered servers in Italy/Tunisia/Algeria, and its single-threaded 4 + # Python client collapsed on their 500+ ms latency (42 Mbit/s on a 1000/50 5 + # line). speedtest-go runs parallel connections against a German server. 6 + # 7 + # Each run appends one JSON line to results.jsonl (also the history); the 8 + # Vector agent tails it into VictoriaMetrics. A run moves ~1.4 GB and 9 + # saturates the line for ~30 s, hence hourly and not more often. 10 + { 11 + config, 12 + lib, 13 + pkgs, 14 + ... 15 + }: 16 + let 17 + dir = "/var/lib/speedtest"; 18 + in 19 + { 20 + # Static user: a DynamicUser's state lives under /var/lib/private (0700), 21 + # where Vector (itself a DynamicUser) can't read it. 22 + users.users.speedtest = { 23 + isSystemUser = true; 24 + group = "speedtest"; 25 + }; 26 + users.groups.speedtest = { }; 27 + 28 + systemd.services.speedtest = { 29 + description = "Speedtest against a German speedtest.net server"; 30 + after = [ "network-online.target" ]; 31 + wants = [ "network-online.target" ]; 32 + serviceConfig = { 33 + Type = "oneshot"; 34 + User = "speedtest"; 35 + Group = "speedtest"; 36 + StateDirectory = "speedtest"; 37 + StateDirectoryMode = "0755"; 38 + UMask = "0022"; 39 + TimeoutStartSec = "5min"; 40 + }; 41 + script = '' 42 + ${lib.getExe pkgs.speedtest-go} --jsonl --filter-cc DE >> ${dir}/results.jsonl 43 + ''; 44 + }; 45 + 46 + systemd.timers.speedtest = { 47 + wantedBy = [ "timers.target" ]; 48 + timerConfig = { 49 + OnCalendar = "hourly"; 50 + RandomizedDelaySec = "10min"; 51 + }; 52 + }; 53 + 54 + monitoring.extraMetrics = [ "speedtest_metrics" ]; 55 + 56 + services.vector.settings = { 57 + sources.speedtest_raw = { 58 + type = "file"; 59 + include = [ "${dir}/results.jsonl" ]; 60 + }; 61 + # speedtest-go: speeds in bytes/s, latencies in ns 62 + transforms.speedtest = { 63 + type = "remap"; 64 + inputs = [ "speedtest_raw" ]; 65 + source = '' 66 + r = parse_json!(.message) 67 + s = object!(r.server) 68 + . = { 69 + "download": to_float!(s.dl_speed) * 8, 70 + "upload": to_float!(s.ul_speed) * 8, 71 + "latency": to_float!(s.latency) / 1000000000, 72 + "jitter": to_float!(s.jitter) / 1000000000, 73 + "server": string!(s.sponsor) + " " + string!(s.name), 74 + "server_id": string!(s.id), 75 + } 76 + ''; 77 + drop_on_error = true; 78 + }; 79 + transforms.speedtest_metrics = { 80 + type = "log_to_metric"; 81 + inputs = [ "speedtest" ]; 82 + metrics = 83 + map 84 + (m: { 85 + type = "gauge"; 86 + field = m.field; 87 + name = "speedtest_${m.name}"; 88 + tags = { 89 + host = config.networking.hostName; 90 + server = "{{server}}"; 91 + server_id = "{{server_id}}"; 92 + }; 93 + }) 94 + [ 95 + { 96 + field = "download"; 97 + name = "download_bits_per_second"; 98 + } 99 + { 100 + field = "upload"; 101 + name = "upload_bits_per_second"; 102 + } 103 + { 104 + field = "latency"; 105 + name = "latency_seconds"; 106 + } 107 + { 108 + field = "jitter"; 109 + name = "jitter_seconds"; 110 + } 111 + ]; 112 + }; 113 + }; 114 + }
+113
configs/gg23/internet.nix
··· 1 + # Why is the gg23 internet slow? Runs on acheron, the gg23 router, whose WAN 2 + # (et0) sits behind the Vodafone Station (ARRIS TG3442DE cable modem, LAN 3 + # 192.168.0.1). Exports, via the Vector agent (configs/monitoring/agent.nix) 4 + # into VictoriaMetrics on the monitoring servers: 5 + # - the Station's DOCSIS channel power/SNR/lock and its status, scraped from 6 + # its web UI (tools/vodafone-station-exporter) 7 + # - latency and loss to the Station, the CMTS and the internet 8 + # (smokeping_prober) 9 + # - hourly speedtests (./internet-speedtest.nix) 10 + # WAN throughput is in the host metrics already (et0 on acheron). Grafana 11 + # shows it as "Internet", Home Assistant as the "Internet" dashboard 12 + # (./internet-hass.nix). 13 + { 14 + config, 15 + lib, 16 + pkgs, 17 + self, 18 + ... 19 + }: 20 + let 21 + exporterPort = 9764; 22 + smokeping = config.services.prometheus.exporters.smokeping; 23 + 24 + # target label -> address. `cmts` is the first hop behind the cable modem 25 + # (91.64.96.254 = gateway of the WAN 91.64.96.0/24, from mtr). If Vodafone 26 + # moves us to another subnet it shows as 100% loss here: update it then. 27 + targets = { 28 + station = "192.168.0.1"; 29 + cmts = "91.64.96.254"; 30 + cloudflare = "1.1.1.1"; 31 + google = "8.8.8.8"; 32 + cloudflare6 = "2606:4700:4700::1111"; 33 + google6 = "2001:4860:4860::8888"; 34 + }; 35 + in 36 + { 37 + imports = [ 38 + ./internet-hass.nix 39 + ./internet-speedtest.nix 40 + ]; 41 + 42 + clan.core.vars.generators.vodafone-station.prompts.password = { 43 + description = "Vodafone Station (192.168.0.1) web UI admin password, passage admin/gg23/vodafone-router/pass"; 44 + type = "hidden"; 45 + persist = true; 46 + }; 47 + 48 + systemd.services.vodafone-station-exporter = { 49 + description = "Prometheus exporter for the Vodafone Station"; 50 + wantedBy = [ "multi-user.target" ]; 51 + after = [ "network-online.target" ]; 52 + wants = [ "network-online.target" ]; 53 + serviceConfig = { 54 + ExecStart = lib.escapeShellArgs [ 55 + (lib.getExe self.packages.${pkgs.stdenv.hostPlatform.system}.vodafone-station-exporter) 56 + "--listen" 57 + "127.0.0.1:${toString exporterPort}" 58 + "--password-file" 59 + "%d/password" 60 + ]; 61 + LoadCredential = [ 62 + "password:${config.clan.core.vars.generators.vodafone-station.files.password.path}" 63 + ]; 64 + DynamicUser = true; 65 + Restart = "on-failure"; 66 + RestartSec = 10; 67 + }; 68 + }; 69 + 70 + services.prometheus.exporters.smokeping = { 71 + enable = true; 72 + listenAddress = "127.0.0.1"; 73 + hosts = lib.attrValues targets; 74 + }; 75 + 76 + monitoring.extraMetrics = [ "internet_metrics" ]; 77 + 78 + services.vector.settings = { 79 + # The Station is slow: one login + three pages take ~6s. Every scrape 80 + # logs in and out again, so keep the interval relaxed. 81 + sources.vodafone_station_raw = { 82 + type = "prometheus_scrape"; 83 + endpoints = [ "http://127.0.0.1:${toString exporterPort}/metrics" ]; 84 + scrape_interval_secs = 60; 85 + scrape_timeout_secs = 45; 86 + }; 87 + sources.smokeping_raw = { 88 + type = "prometheus_scrape"; 89 + endpoints = [ 90 + "http://127.0.0.1:${toString smokeping.port}${smokeping.telemetryPath}" 91 + ]; 92 + scrape_interval_secs = 30; 93 + }; 94 + # smokeping_prober labels the probed address `host`, which is our 95 + # machine label everywhere else: move it to `target` (plus the name from 96 + # `targets`), then set host like the agent does. 97 + transforms.internet_metrics = { 98 + type = "remap"; 99 + inputs = [ 100 + "vodafone_station_raw" 101 + "smokeping_raw" 102 + ]; 103 + source = '' 104 + names = ${builtins.toJSON (lib.mapAttrs' (n: v: lib.nameValuePair v n) targets)} 105 + if exists(.tags.host) { 106 + .tags.target = del(.tags.host) 107 + .tags.target_name = get(names, [.tags.target]) ?? .tags.target 108 + } 109 + .tags.host = "${config.networking.hostName}" 110 + ''; 111 + }; 112 + }; 113 + }
+1
machines/acheron/config.nix
··· 7 7 ../../configs/hass 8 8 ../../configs/snapserver.nix 9 9 ../../configs/gg23/audio/hub.nix 10 + ../../configs/gg23/internet.nix 10 11 ]; 11 12 12 13 services.earlyoom.enable = true;
+308
configs/monitoring/dashboards/internet.json
··· 1 + { 2 + "uid": "internet", 3 + "title": "Internet (gg23)", 4 + "schemaVersion": 39, 5 + "time": { 6 + "from": "now-24h", 7 + "to": "now" 8 + }, 9 + "refresh": "1m", 10 + "panels": [ 11 + { 12 + "id": 1, 13 + "type": "timeseries", 14 + "title": "Latency (avg, 1m)", 15 + "gridPos": { 16 + "x": 0, 17 + "y": 0, 18 + "w": 12, 19 + "h": 9 20 + }, 21 + "datasource": { 22 + "type": "victoriametrics-metrics-datasource", 23 + "uid": "victoriametrics" 24 + }, 25 + "fieldConfig": { 26 + "defaults": { 27 + "unit": "s" 28 + } 29 + }, 30 + "targets": [ 31 + { 32 + "refId": "A", 33 + "expr": "rate(smokeping_response_duration_seconds_sum{host=\"acheron\"}[1m]) / rate(smokeping_response_duration_seconds_count{host=\"acheron\"}[1m])", 34 + "legendFormat": "{{target_name}}" 35 + } 36 + ] 37 + }, 38 + { 39 + "id": 2, 40 + "type": "timeseries", 41 + "title": "Latency p95 (5m)", 42 + "gridPos": { 43 + "x": 12, 44 + "y": 0, 45 + "w": 12, 46 + "h": 9 47 + }, 48 + "datasource": { 49 + "type": "victoriametrics-metrics-datasource", 50 + "uid": "victoriametrics" 51 + }, 52 + "fieldConfig": { 53 + "defaults": { 54 + "unit": "s" 55 + } 56 + }, 57 + "targets": [ 58 + { 59 + "refId": "A", 60 + "expr": "histogram_quantile(0.95, sum(rate(smokeping_response_duration_seconds_bucket{host=\"acheron\"}[5m])) by (le, target_name))", 61 + "legendFormat": "{{target_name}}" 62 + } 63 + ] 64 + }, 65 + { 66 + "id": 3, 67 + "type": "timeseries", 68 + "title": "Packet loss (1m)", 69 + "gridPos": { 70 + "x": 0, 71 + "y": 9, 72 + "w": 12, 73 + "h": 8 74 + }, 75 + "datasource": { 76 + "type": "victoriametrics-metrics-datasource", 77 + "uid": "victoriametrics" 78 + }, 79 + "fieldConfig": { 80 + "defaults": { 81 + "unit": "percentunit", 82 + "min": 0 83 + } 84 + }, 85 + "targets": [ 86 + { 87 + "refId": "A", 88 + "expr": "1 - sum(rate(smokeping_response_duration_seconds_count{host=\"acheron\"}[1m])) by (target_name) / sum(rate(smokeping_requests_total{host=\"acheron\"}[1m])) by (target_name)", 89 + "legendFormat": "{{target_name}}" 90 + } 91 + ] 92 + }, 93 + { 94 + "id": 4, 95 + "type": "timeseries", 96 + "title": "WAN throughput (et0)", 97 + "gridPos": { 98 + "x": 12, 99 + "y": 9, 100 + "w": 12, 101 + "h": 8 102 + }, 103 + "datasource": { 104 + "type": "victoriametrics-metrics-datasource", 105 + "uid": "victoriametrics" 106 + }, 107 + "fieldConfig": { 108 + "defaults": { 109 + "unit": "bps" 110 + } 111 + }, 112 + "targets": [ 113 + { 114 + "refId": "A", 115 + "expr": "rate(host_network_receive_bytes_total{host=\"acheron\",device=\"et0\"}[1m]) * 8", 116 + "legendFormat": "down" 117 + }, 118 + { 119 + "refId": "B", 120 + "expr": "-rate(host_network_transmit_bytes_total{host=\"acheron\",device=\"et0\"}[1m]) * 8", 121 + "legendFormat": "up" 122 + } 123 + ] 124 + }, 125 + { 126 + "id": 5, 127 + "type": "timeseries", 128 + "title": "Downstream power", 129 + "gridPos": { 130 + "x": 0, 131 + "y": 17, 132 + "w": 8, 133 + "h": 9 134 + }, 135 + "datasource": { 136 + "type": "victoriametrics-metrics-datasource", 137 + "uid": "victoriametrics" 138 + }, 139 + "fieldConfig": { 140 + "defaults": { 141 + "unit": "suffix: dBmV" 142 + } 143 + }, 144 + "targets": [ 145 + { 146 + "refId": "A", 147 + "expr": "vodafone_station_docsis_power_dbmv{host=\"acheron\",direction=\"downstream\"}", 148 + "legendFormat": "{{channel_type}} {{frequency_mhz}} MHz" 149 + } 150 + ] 151 + }, 152 + { 153 + "id": 6, 154 + "type": "timeseries", 155 + "title": "Downstream SNR", 156 + "gridPos": { 157 + "x": 8, 158 + "y": 17, 159 + "w": 8, 160 + "h": 9 161 + }, 162 + "datasource": { 163 + "type": "victoriametrics-metrics-datasource", 164 + "uid": "victoriametrics" 165 + }, 166 + "fieldConfig": { 167 + "defaults": { 168 + "unit": "suffix: dB" 169 + } 170 + }, 171 + "targets": [ 172 + { 173 + "refId": "A", 174 + "expr": "vodafone_station_docsis_snr_db{host=\"acheron\",direction=\"downstream\"}", 175 + "legendFormat": "{{channel_type}} {{frequency_mhz}} MHz" 176 + } 177 + ] 178 + }, 179 + { 180 + "id": 7, 181 + "type": "timeseries", 182 + "title": "Upstream power", 183 + "gridPos": { 184 + "x": 16, 185 + "y": 17, 186 + "w": 8, 187 + "h": 9 188 + }, 189 + "datasource": { 190 + "type": "victoriametrics-metrics-datasource", 191 + "uid": "victoriametrics" 192 + }, 193 + "fieldConfig": { 194 + "defaults": { 195 + "unit": "suffix: dBmV" 196 + } 197 + }, 198 + "targets": [ 199 + { 200 + "refId": "A", 201 + "expr": "vodafone_station_docsis_power_dbmv{host=\"acheron\",direction=\"upstream\"}", 202 + "legendFormat": "{{channel_type}} {{frequency_mhz}} MHz" 203 + } 204 + ] 205 + }, 206 + { 207 + "id": 8, 208 + "type": "timeseries", 209 + "title": "Modulation", 210 + "gridPos": { 211 + "x": 0, 212 + "y": 26, 213 + "w": 12, 214 + "h": 8 215 + }, 216 + "datasource": { 217 + "type": "victoriametrics-metrics-datasource", 218 + "uid": "victoriametrics" 219 + }, 220 + "fieldConfig": { 221 + "defaults": {} 222 + }, 223 + "targets": [ 224 + { 225 + "refId": "A", 226 + "expr": "vodafone_station_docsis_modulation_qam{host=\"acheron\"}", 227 + "legendFormat": "{{direction}} {{channel_type}} {{frequency_mhz}} MHz" 228 + } 229 + ] 230 + }, 231 + { 232 + "id": 9, 233 + "type": "timeseries", 234 + "title": "Station", 235 + "gridPos": { 236 + "x": 12, 237 + "y": 26, 238 + "w": 12, 239 + "h": 8 240 + }, 241 + "datasource": { 242 + "type": "victoriametrics-metrics-datasource", 243 + "uid": "victoriametrics" 244 + }, 245 + "fieldConfig": { 246 + "defaults": {} 247 + }, 248 + "targets": [ 249 + { 250 + "refId": "A", 251 + "expr": "vodafone_station_up{host=\"acheron\"}", 252 + "legendFormat": "scrape ok" 253 + }, 254 + { 255 + "refId": "B", 256 + "expr": "vodafone_station_docsis_online{host=\"acheron\"}", 257 + "legendFormat": "DOCSIS online" 258 + }, 259 + { 260 + "refId": "C", 261 + "expr": "count(vodafone_station_docsis_locked{host=\"acheron\"} == 0) or vector(0)", 262 + "legendFormat": "unlocked channels" 263 + }, 264 + { 265 + "refId": "D", 266 + "expr": "vodafone_station_uptime_seconds{host=\"acheron\"} / 86400", 267 + "legendFormat": "uptime (days)" 268 + } 269 + ] 270 + }, 271 + { 272 + "id": 10, 273 + "type": "timeseries", 274 + "title": "Speedtest (hourly)", 275 + "gridPos": { 276 + "x": 0, 277 + "y": 34, 278 + "w": 24, 279 + "h": 8 280 + }, 281 + "datasource": { 282 + "type": "victoriametrics-metrics-datasource", 283 + "uid": "victoriametrics" 284 + }, 285 + "fieldConfig": { 286 + "defaults": { 287 + "unit": "bps", 288 + "custom": { 289 + "drawStyle": "points", 290 + "pointSize": 6 291 + } 292 + } 293 + }, 294 + "targets": [ 295 + { 296 + "refId": "A", 297 + "expr": "speedtest_download_bits_per_second{host=\"acheron\"}", 298 + "legendFormat": "down {{server}}" 299 + }, 300 + { 301 + "refId": "B", 302 + "expr": "speedtest_upload_bits_per_second{host=\"acheron\"}", 303 + "legendFormat": "up {{server}}" 304 + } 305 + ] 306 + } 307 + ] 308 + }