├── .ansible-lint ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── stale.yml ├── .gitignore ├── .yamllint ├── LICENSE ├── README.md ├── ansible.cfg ├── example.config.yml ├── example.inventory.ini ├── files ├── power-consumption.json └── starlink-overview.json ├── images ├── internet-monitoring.png └── pi-hole.png ├── internet-monitoring ├── Grafana-Org-Stats.json ├── README.md ├── blackbox │ └── config │ │ └── blackbox.yml ├── grafana │ └── provisioning │ │ ├── dashboards │ │ ├── dashboard.yml │ │ ├── internet-connection.json │ │ └── node-exporter-full.json │ │ └── datasources │ │ └── datasource.yml └── prometheus │ └── alert.rules ├── main.yml ├── requirements.yml ├── tasks ├── airgradient.yml ├── debian-libseccomp-update.yml ├── docker.yml ├── handlers.yml ├── internet-monitoring.yml ├── pi-hole.yml ├── shelly-plug.yml └── starlink.yml └── templates ├── airgradient-air-quality.json.j2 ├── docker-compose.yml.j2 ├── grafana-config.monitoring.j2 ├── pi-hole-docker-compose.yml.j2 ├── prometheus-pinghosts.yaml.j2 ├── prometheus.yml.j2 ├── shelly-plug-docker-compose.yml.j2 └── starlink-docker-compose.yml.j2 /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | skip_list: 3 | - 'fqcn-builtins' 4 | - 'no-handler' 5 | - 'no-changed-when' 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | --- 3 | github: geerlingguy 4 | patreon: geerlingguy 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 'on': 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | schedule: 9 | - cron: "3 2 * * 0" 10 | 11 | jobs: 12 | 13 | lint: 14 | name: Lint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Check out the codebase. 18 | uses: actions/checkout@v2 19 | 20 | - name: Set up Python 3. 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: '3.x' 24 | 25 | - name: Install test dependencies. 26 | run: pip3 install yamllint ansible-core ansible-lint 27 | 28 | - name: Lint code. 29 | run: | 30 | yamllint . 31 | ansible-lint 32 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Close inactive issues 3 | 'on': 4 | schedule: 5 | - cron: "55 21 * * 1" # semi-random time 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v8 15 | with: 16 | days-before-stale: 120 17 | days-before-close: 60 18 | exempt-issue-labels: bug,pinned,security,planned 19 | exempt-pr-labels: bug,pinned,security,planned 20 | stale-issue-label: "stale" 21 | stale-pr-label: "stale" 22 | stale-issue-message: | 23 | This issue has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 24 | 25 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 26 | close-issue-message: | 27 | This issue has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 28 | stale-pr-message: | 29 | This pr has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 30 | 31 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 32 | close-pr-message: | 33 | This pr has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 34 | repo-token: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | inventory.ini 2 | config.yml 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: 6 | max: 120 7 | level: warning 8 | 9 | ignore: | 10 | .github/workflows/stale.yml 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Jeff Geerling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Internet Pi 2 | 3 | [![CI](https://github.com/geerlingguy/internet-pi/workflows/CI/badge.svg?event=push)](https://github.com/geerlingguy/internet-pi/actions?query=workflow%3ACI) 4 | 5 | **A Raspberry Pi Configuration for Internet connectivity** 6 | 7 | I have had a couple Pis doing random Internet-related duties for years. It's finally time to formalize their configs and make all the DNS/ad-blocking/monitoring stuff encapsulated into one Ansible project. 8 | 9 | So that's what this is. 10 | 11 | ## Features 12 | 13 | **Internet Monitoring**: Installs Prometheus and Grafana, along with a few Docker containers to monitor your Internet connection with Speedtest.net speedtests and HTTP tests so you can see uptime, ping stats, and speedtest results over time. 14 | 15 | ![Internet Monitoring Dashboard in Grafana](images/internet-monitoring.png) 16 | 17 | **Pi-hole**: Installs the Pi-hole Docker configuration so you can use Pi-hole for network-wide ad-blocking and local DNS. Make sure to update your network router config to direct all DNS queries through your Raspberry Pi if you want to use Pi-hole effectively! 18 | 19 | ![Pi-hole on the Internet Pi](images/pi-hole.png) 20 | 21 | Other features: 22 | 23 | - **Shelly Plug Monitoring**: Installs a [`shelly-plug-prometheus` exporter](https://github.com/geerlingguy/shelly-plug-prometheus) and a Grafana dashboard, which tracks and displays power usage on a Shelly Plug running on the local network. (Disabled by default. Enable and configure using the `shelly_plug_*` vars in `config.yml`.) 24 | - **AirGradient Monitoring**: Configures [`airgradient-prometheus`](https://github.com/geerlingguy/airgradient-prometheus) and a Grafana dashboard, which tracks and displays air quality over time via one or more AirGradient DIY monitors. (Disabled by default. Enable and configure using the `airgradient_enable` var in `config.yml`. See example configuration for ability to monitor multiple AirGradient DIY stations.) 25 | - **Starlink Monitoring**: Installs a [`starlink` prometheus exporter](https://github.com/danopstech/starlink_exporter) and a Grafana dashboard, which tracks and displays Starlink statistics. (Disabled by default. Enable and configure using the `starlink_enable` var in `config.yml`.) 26 | 27 | **IMPORTANT NOTE**: If you use the included Internet monitoring, it will download a decently-large amount of data through your Internet connection on a daily basis. Don't use it, or tune the `internet-monitoring` setup to not run the speedtests as often, if you have a metered connection! 28 | 29 | ## Recommended Pi and OS 30 | 31 | You should use a Raspberry Pi 4 model B or better. The Pi 4 and later generations of Pi include a full gigabit network interface and enough I/O to reliably measure fast Internet connections. 32 | 33 | Older Pis work, but have many limitations, like a slower CPU and sometimes very-slow NICs that limit the speed test capability to 100 Mbps or 300 Mbps on the Pi 3 model B+. 34 | 35 | Other computers and VMs may run this configuration as well, but it is only regularly tested on a Raspberry Pi. 36 | 37 | The configuration is tested against Raspberry Pi OS, both 64-bit and 32-bit, and runs great on that or a generic Debian installation. 38 | 39 | It should also work with Ubuntu for Pi, or Arch Linux, but has not been tested on other operating systems. 40 | 41 | ## Setup 42 | 43 | 1. [Install Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html). The easiest way (especially on Pi or a Debian system) is via Pip: 44 | 1. (If on Pi/Debian): `sudo apt-get install -y python3-pip` 45 | 2. (Everywhere): `pip3 install ansible` 46 | 3. If you get an error like "externally-managed-environment", follow [this guide to fix it](https://www.jeffgeerling.com/blog/2023/how-solve-error-externally-managed-environment-when-installing-pip3), then run `pip3 install ansible` again. 47 | 4. Make sure Ansible is in your PATH: `export PATH=$PATH:~/.local/bin` (and consider [adding it permanently](https://askubuntu.com/a/1113838)). 48 | 2. Clone this repository: `git clone https://github.com/geerlingguy/internet-pi.git`, then enter the repository directory: `cd internet-pi`. 49 | 3. Install requirements: `ansible-galaxy collection install -r requirements.yml` (if you see `ansible-galaxy: command not found`, restart your SSH session or reboot the Pi and try again) 50 | 4. Make copies of the following files and customize them to your liking: 51 | - `example.inventory.ini` to `inventory.ini` (replace IP address with your Pi's IP, or comment that line and uncomment the `connection=local` line if you're running it on the Pi you're setting up). 52 | - `example.config.yml` to `config.yml` 53 | 5. Run the playbook: `ansible-playbook main.yml` 54 | 55 | > **If running locally on the Pi**: You may encounter an error like "Error while fetching server API version" or "connect: permission denied". If you do, please either reboot or log out and log back in, then run the playbook again. 56 | 57 | ## Usage 58 | 59 | ### Pi-hole 60 | 61 | Visit the Pi's IP address (e.g. http://192.168.1.10/admin) and use the `pihole_password` you configured in your `config.yml` file. An existing pi-hole installation can be left unaltered by disabling the setup of this project's installation in your `config.yml` (`pihole_enable: false`) 62 | 63 | ### Grafana 64 | 65 | Visit the Pi's IP address with port 3030 (e.g. http://192.168.1.10:3030/), and log in with username `admin` and the password `monitoring_grafana_admin_password` you configured in your `config.yml`. 66 | 67 | To find the dashboard, navigate to Dashboards, click Browse, then go to the Internet connection dashboard. If you star this dashboard, it will appear on the Grafana home page. 68 | 69 | > Note: The `monitoring_grafana_admin_password` is only used the first time Grafana starts up; if you need to change it later, do it via Grafana's admin UI. 70 | 71 | ### Prometheus 72 | 73 | A number of default Prometheus job configurations are included out of the box, but if you would like to add more to the `prometheus.yml` file, you can add a block of text that will be added to the end of the `scrape_configs` using the `prometheus_extra_scrape_configs` variable, for example: 74 | 75 | ```yaml 76 | prometheus_extra_scrape_configs: | 77 | - job_name: 'customjob' 78 | scrape_interval: 5s 79 | static_configs: 80 | - targets: ['192.168.1.1:9100'] 81 | ``` 82 | 83 | You can also add more targets to monitor via the node exporter dashboard, say if you have a number of servers or other Pis you want to monitor on this instance. Just add them to the list, after the `nodeexp:9100` entry for the main Pi: 84 | 85 | ```yaml 86 | prometheus_node_exporter_targets: 87 | - 'nodeexp:9100' 88 | # Add more targets here 89 | - 'another-server.local:9100' 90 | ``` 91 | 92 | ## Updating 93 | 94 | ### pi-hole 95 | 96 | To upgrade Pi-hole to the latest version, run the following commands: 97 | 98 | ```bash 99 | cd ~/pi-hole # 100 | docker compose pull # pulls the latest images 101 | docker compose up -d --no-deps # restarts containers with newer images 102 | docker system prune --all # deletes unused images 103 | ``` 104 | 105 | ### Configurations and internet-monitoring images 106 | 107 | Upgrades for the other configurations are similar (go into the directory, and run the same `docker compose` commands. Make sure to `cd` into the `config_dir` that you use in your `config.yml` file. 108 | 109 | Alternatively, you may update the initial `config.yml` in the the repo folder and re-run the main playbook: `ansible-playbook main.yml`. At some point in the future, a dedicated upgrade playbook may be added, but for now, upgrades may be performed manually as shown above. 110 | 111 | ## Backups 112 | 113 | A guide for backing up the configurations and historical data will be posted here as part of [Issue #194: Create Backup guide](https://github.com/geerlingguy/internet-pi/issues/194). 114 | 115 | ## Uninstall 116 | 117 | To remove `internet-pi` from your system, run the following commands (assuming the default install location of `~`, your home directory): 118 | 119 | ```bash 120 | # Enter the internet-monitoring directory. 121 | cd ~/internet-monitoring 122 | 123 | # Shut down internet-monitoring containers and delete data volumes. 124 | docker compose down -v 125 | 126 | # Enter the pi-hole directory. 127 | cd ~/pi-hole 128 | 129 | # Shutdown pi-hole containers and delete data volumes. 130 | docker compose down -v 131 | 132 | # Delete all the unused container images, volumes, etc. from the system. 133 | docker system prune -af 134 | ``` 135 | 136 | Do the same thing for any of the other optional directories added by this project (e.g. `shelly-plug-prometheus`, `starlink-exporter`, etc.). 137 | 138 | You can then delete the `internet-monitoring`, `pi-hole`, etc. folders and everything will be gone from your system. 139 | 140 | ## License 141 | 142 | MIT 143 | 144 | ## Author 145 | 146 | This project was created in 2021 by [Jeff Geerling](https://www.jeffgeerling.com/). 147 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | nocows = True 3 | inventory = ./inventory.ini 4 | interpreter_python = auto_silent 5 | -------------------------------------------------------------------------------- /example.config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Location where configuration files will be stored. 3 | config_dir: '~' 4 | 5 | # Domain names configuration (related services need to be enabled). 6 | domain_name_enable: false 7 | domain_name: 'home.local' 8 | domain_pihole: 'pihole' # to access pihole via: http://pihole.home.local 9 | domain_grafana: 'grafana' # to access grafana via: http://grafana.home.local 10 | domain_prometheus: 'prometheus' # to access prometheus via: http://prometheus.home.local 11 | 12 | # Pi-hole configuration. 13 | pihole_enable: true 14 | pihole_hostname: pihole 15 | pihole_timezone: America/Chicago 16 | pihole_password: "change-this-password" 17 | 18 | # Internet monitoring configuration. 19 | monitoring_enable: true 20 | monitoring_grafana_admin_password: "admin" 21 | monitoring_speedtest_interval: 60m 22 | monitoring_ping_interval: 5s 23 | monitoring_ping_hosts: # [URL];[HUMAN_READABLE_NAME] 24 | - http://www.google.com/;google.com 25 | - https://github.com/;github.com 26 | - https://www.apple.com/;apple.com 27 | 28 | # Prometheus configuration. 29 | prometheus_monitor_prometheus: false 30 | prometheus_node_exporter_targets: 31 | - 'nodeexp:9100' 32 | prometheus_extra_scrape_configs: '' 33 | prometheus_tsdb_retention_time: 90d 34 | 35 | # Shelly Plug configuration. (Also requires `monitoring_enable`) 36 | shelly_plug_enable: false 37 | shelly_plug_hostname: my-shelly-plug-host-or-ip 38 | shelly_plug_http_username: username 39 | shelly_plug_http_password: "password" 40 | 41 | # AirGradient configuration. (Also requires `monitoring_enable`) 42 | airgradient_enable: false 43 | airgradient_sensors: 44 | # ID Should be limited to no more than 38 characters, in the set a-z. 45 | - id: livingroom 46 | ip: "192.168.0.123" 47 | port: 9925 48 | 49 | # Starlink configuration. (Also requires `monitoring_enable`) 50 | starlink_enable: false 51 | -------------------------------------------------------------------------------- /example.inventory.ini: -------------------------------------------------------------------------------- 1 | [internet_pi] 2 | 10.0.100.52 ansible_user=pi 3 | 4 | # Comment out the previous line and uncomment this to run inside Raspberry Pi. 5 | # 127.0.0.1 ansible_connection=local ansible_user=pi 6 | -------------------------------------------------------------------------------- /files/power-consumption.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 2, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "datasource": "prometheus", 23 | "description": "", 24 | "fieldConfig": { 25 | "defaults": { 26 | "color": { 27 | "mode": "thresholds" 28 | }, 29 | "custom": {}, 30 | "mappings": [], 31 | "max": 500, 32 | "min": 0, 33 | "thresholds": { 34 | "mode": "absolute", 35 | "steps": [ 36 | { 37 | "color": "green", 38 | "value": null 39 | }, 40 | { 41 | "color": "#EAB839", 42 | "value": 150 43 | }, 44 | { 45 | "color": "red", 46 | "value": 300 47 | } 48 | ] 49 | }, 50 | "unit": "watt" 51 | }, 52 | "overrides": [] 53 | }, 54 | "gridPos": { 55 | "h": 8, 56 | "w": 12, 57 | "x": 0, 58 | "y": 0 59 | }, 60 | "id": 6, 61 | "options": { 62 | "reduceOptions": { 63 | "calcs": [ 64 | "lastNotNull" 65 | ], 66 | "fields": "", 67 | "values": false 68 | }, 69 | "showThresholdLabels": false, 70 | "showThresholdMarkers": true, 71 | "text": {} 72 | }, 73 | "pluginVersion": "7.4.5", 74 | "targets": [ 75 | { 76 | "expr": "power", 77 | "interval": "", 78 | "legendFormat": "Watts", 79 | "queryType": "randomWalk", 80 | "refId": "A" 81 | } 82 | ], 83 | "title": "Power Consumption (Latest)", 84 | "type": "gauge" 85 | }, 86 | { 87 | "datasource": "prometheus", 88 | "fieldConfig": { 89 | "defaults": { 90 | "color": { 91 | "mode": "thresholds" 92 | }, 93 | "custom": {}, 94 | "mappings": [], 95 | "max": 500, 96 | "min": 0, 97 | "thresholds": { 98 | "mode": "absolute", 99 | "steps": [ 100 | { 101 | "color": "green", 102 | "value": null 103 | }, 104 | { 105 | "color": "#EAB839", 106 | "value": 150 107 | }, 108 | { 109 | "color": "red", 110 | "value": 300 111 | } 112 | ] 113 | }, 114 | "unit": "watt" 115 | }, 116 | "overrides": [] 117 | }, 118 | "gridPos": { 119 | "h": 8, 120 | "w": 12, 121 | "x": 12, 122 | "y": 0 123 | }, 124 | "id": 4, 125 | "options": { 126 | "reduceOptions": { 127 | "calcs": [ 128 | "mean" 129 | ], 130 | "fields": "", 131 | "values": false 132 | }, 133 | "showThresholdLabels": false, 134 | "showThresholdMarkers": true, 135 | "text": {} 136 | }, 137 | "pluginVersion": "7.4.5", 138 | "targets": [ 139 | { 140 | "expr": "power", 141 | "interval": "", 142 | "legendFormat": "Watts", 143 | "queryType": "randomWalk", 144 | "refId": "A" 145 | } 146 | ], 147 | "title": "Power Consumption (Average)", 148 | "type": "gauge" 149 | }, 150 | { 151 | "aliasColors": {}, 152 | "bars": false, 153 | "dashLength": 10, 154 | "dashes": false, 155 | "datasource": "prometheus", 156 | "description": "", 157 | "fieldConfig": { 158 | "defaults": { 159 | "custom": {}, 160 | "unit": "watt" 161 | }, 162 | "overrides": [] 163 | }, 164 | "fill": 1, 165 | "fillGradient": 0, 166 | "gridPos": { 167 | "h": 8, 168 | "w": 24, 169 | "x": 0, 170 | "y": 8 171 | }, 172 | "hiddenSeries": false, 173 | "id": 2, 174 | "legend": { 175 | "avg": false, 176 | "current": false, 177 | "max": false, 178 | "min": false, 179 | "show": false, 180 | "total": false, 181 | "values": false 182 | }, 183 | "lines": true, 184 | "linewidth": 1, 185 | "nullPointMode": "null", 186 | "options": { 187 | "alertThreshold": true 188 | }, 189 | "percentage": false, 190 | "pluginVersion": "7.4.5", 191 | "pointradius": 1, 192 | "points": true, 193 | "renderer": "flot", 194 | "seriesOverrides": [], 195 | "spaceLength": 10, 196 | "stack": false, 197 | "steppedLine": false, 198 | "targets": [ 199 | { 200 | "expr": "power", 201 | "interval": "", 202 | "legendFormat": "Watts", 203 | "queryType": "randomWalk", 204 | "refId": "A" 205 | } 206 | ], 207 | "thresholds": [], 208 | "timeFrom": null, 209 | "timeRegions": [], 210 | "timeShift": null, 211 | "title": "Power Consumption", 212 | "tooltip": { 213 | "shared": true, 214 | "sort": 0, 215 | "value_type": "individual" 216 | }, 217 | "type": "graph", 218 | "xaxis": { 219 | "buckets": null, 220 | "mode": "time", 221 | "name": null, 222 | "show": true, 223 | "values": [] 224 | }, 225 | "yaxes": [ 226 | { 227 | "$$hashKey": "object:169", 228 | "decimals": null, 229 | "format": "watt", 230 | "label": null, 231 | "logBase": 1, 232 | "max": null, 233 | "min": "0", 234 | "show": true 235 | }, 236 | { 237 | "$$hashKey": "object:170", 238 | "decimals": null, 239 | "format": "short", 240 | "label": null, 241 | "logBase": 1, 242 | "max": null, 243 | "min": null, 244 | "show": true 245 | } 246 | ], 247 | "yaxis": { 248 | "align": false, 249 | "alignLevel": null 250 | } 251 | } 252 | ], 253 | "refresh": "5m", 254 | "schemaVersion": 27, 255 | "style": "dark", 256 | "tags": [ 257 | "power" 258 | ], 259 | "templating": { 260 | "list": [] 261 | }, 262 | "time": { 263 | "from": "now-6h", 264 | "to": "now" 265 | }, 266 | "timepicker": {}, 267 | "timezone": "", 268 | "title": "Power consumption", 269 | "uid": "i_aeo-uMz", 270 | "version": 3 271 | } -------------------------------------------------------------------------------- /files/starlink-overview.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "description": "Starlink dish metrics", 16 | "editable": true, 17 | "gnetId": null, 18 | "graphTooltip": 1, 19 | "links": [ 20 | { 21 | "icon": "info", 22 | "tags": [], 23 | "targetBlank": true, 24 | "title": "Starlink Debug", 25 | "tooltip": "", 26 | "type": "link", 27 | "url": "http://192.168.100.1/support/debug" 28 | }, 29 | { 30 | "icon": "info", 31 | "tags": [], 32 | "targetBlank": true, 33 | "title": "Starlink Statistics", 34 | "tooltip": "", 35 | "type": "link", 36 | "url": "http://192.168.100.1/support/statistics" 37 | }, 38 | { 39 | "icon": "bolt", 40 | "includeVars": false, 41 | "keepTime": false, 42 | "tags": [], 43 | "targetBlank": true, 44 | "title": "Speedtest.net", 45 | "tooltip": "", 46 | "type": "link", 47 | "url": "https://www.speedtest.net/" 48 | } 49 | ], 50 | "panels": [ 51 | { 52 | "aliasColors": {}, 53 | "bars": false, 54 | "dashLength": 10, 55 | "dashes": false, 56 | "datasource": "prometheus", 57 | "fieldConfig": { 58 | "defaults": { 59 | "custom": {}, 60 | "unit": "short" 61 | }, 62 | "overrides": [] 63 | }, 64 | "fill": 1, 65 | "fillGradient": 0, 66 | "gridPos": { 67 | "h": 8, 68 | "w": 16, 69 | "x": 0, 70 | "y": 0 71 | }, 72 | "hiddenSeries": false, 73 | "id": 8, 74 | "interval": "1s", 75 | "legend": { 76 | "alignAsTable": false, 77 | "avg": true, 78 | "current": true, 79 | "max": true, 80 | "min": true, 81 | "show": true, 82 | "total": false, 83 | "values": true 84 | }, 85 | "lines": true, 86 | "linewidth": 1, 87 | "nullPointMode": "null", 88 | "options": { 89 | "alertThreshold": true 90 | }, 91 | "percentage": false, 92 | "pluginVersion": "7.4.5", 93 | "pointradius": 2, 94 | "points": false, 95 | "renderer": "flot", 96 | "seriesOverrides": [ 97 | { 98 | "$$hashKey": "object:934", 99 | "alias": "Download", 100 | "color": "#FADE2A" 101 | } 102 | ], 103 | "spaceLength": 10, 104 | "stack": false, 105 | "steppedLine": false, 106 | "targets": [ 107 | { 108 | "exemplar": false, 109 | "expr": "starlink_dish_uplink_throughput_bytes / 1000000", 110 | "instant": false, 111 | "interval": "", 112 | "legendFormat": "Upload", 113 | "refId": "A" 114 | }, 115 | { 116 | "exemplar": false, 117 | "expr": "starlink_dish_downlink_throughput_bytes /1000000", 118 | "hide": false, 119 | "interval": "", 120 | "legendFormat": "Download", 121 | "refId": "B" 122 | } 123 | ], 124 | "thresholds": [], 125 | "timeFrom": null, 126 | "timeRegions": [], 127 | "timeShift": null, 128 | "title": "Actual Throughput", 129 | "tooltip": { 130 | "shared": true, 131 | "sort": 0, 132 | "value_type": "individual" 133 | }, 134 | "type": "graph", 135 | "xaxis": { 136 | "buckets": null, 137 | "mode": "time", 138 | "name": null, 139 | "show": true, 140 | "values": [] 141 | }, 142 | "yaxes": [ 143 | { 144 | "$$hashKey": "object:95", 145 | "decimals": null, 146 | "format": "short", 147 | "label": "Mbps", 148 | "logBase": 1, 149 | "max": null, 150 | "min": null, 151 | "show": true 152 | }, 153 | { 154 | "$$hashKey": "object:96", 155 | "format": "short", 156 | "label": "Upload (Mbps)", 157 | "logBase": 1, 158 | "max": null, 159 | "min": null, 160 | "show": false 161 | } 162 | ], 163 | "yaxis": { 164 | "align": false, 165 | "alignLevel": 0 166 | } 167 | }, 168 | { 169 | "datasource": "prometheus", 170 | "fieldConfig": { 171 | "defaults": { 172 | "color": { 173 | "mode": "thresholds" 174 | }, 175 | "custom": {}, 176 | "mappings": [], 177 | "max": 9, 178 | "min": 0, 179 | "thresholds": { 180 | "mode": "absolute", 181 | "steps": [ 182 | { 183 | "color": "red", 184 | "value": null 185 | }, 186 | { 187 | "color": "orange", 188 | "value": 5 189 | }, 190 | { 191 | "color": "yellow", 192 | "value": 6 193 | }, 194 | { 195 | "color": "green", 196 | "value": 8 197 | } 198 | ] 199 | }, 200 | "unit": "short" 201 | }, 202 | "overrides": [] 203 | }, 204 | "gridPos": { 205 | "h": 5, 206 | "w": 4, 207 | "x": 16, 208 | "y": 0 209 | }, 210 | "id": 19, 211 | "interval": "1s", 212 | "options": { 213 | "reduceOptions": { 214 | "calcs": [ 215 | "lastNotNull" 216 | ], 217 | "fields": "", 218 | "values": false 219 | }, 220 | "showThresholdLabels": false, 221 | "showThresholdMarkers": true, 222 | "text": {} 223 | }, 224 | "pluginVersion": "7.4.5", 225 | "targets": [ 226 | { 227 | "exemplar": false, 228 | "expr": "starlink_dish_snr", 229 | "interval": "", 230 | "legendFormat": "", 231 | "refId": "A" 232 | } 233 | ], 234 | "title": "Current SNR", 235 | "type": "gauge" 236 | }, 237 | { 238 | "datasource": "prometheus", 239 | "fieldConfig": { 240 | "defaults": { 241 | "color": { 242 | "fixedColor": "rgb(245, 245, 245)", 243 | "mode": "fixed" 244 | }, 245 | "custom": {}, 246 | "mappings": [ 247 | { 248 | "from": "", 249 | "id": 1, 250 | "text": "Unknown", 251 | "to": "", 252 | "type": 1, 253 | "value": "0" 254 | }, 255 | { 256 | "from": "", 257 | "id": 2, 258 | "text": "Connected", 259 | "to": "", 260 | "type": 1, 261 | "value": "1" 262 | }, 263 | { 264 | "from": "", 265 | "id": 3, 266 | "text": "Searching", 267 | "to": "", 268 | "type": 1, 269 | "value": "2" 270 | }, 271 | { 272 | "from": "", 273 | "id": 4, 274 | "text": "Booting", 275 | "to": "", 276 | "type": 1, 277 | "value": "3" 278 | } 279 | ], 280 | "thresholds": { 281 | "mode": "absolute", 282 | "steps": [ 283 | { 284 | "color": "green", 285 | "value": null 286 | } 287 | ] 288 | } 289 | }, 290 | "overrides": [] 291 | }, 292 | "gridPos": { 293 | "h": 3, 294 | "w": 2, 295 | "x": 20, 296 | "y": 0 297 | }, 298 | "id": 34, 299 | "options": { 300 | "colorMode": "value", 301 | "graphMode": "none", 302 | "justifyMode": "auto", 303 | "orientation": "auto", 304 | "reduceOptions": { 305 | "calcs": [ 306 | "lastNotNull" 307 | ], 308 | "fields": "", 309 | "values": false 310 | }, 311 | "text": {}, 312 | "textMode": "auto" 313 | }, 314 | "pluginVersion": "7.4.5", 315 | "targets": [ 316 | { 317 | "exemplar": false, 318 | "expr": "starlink_dish_state", 319 | "interval": "", 320 | "legendFormat": "", 321 | "refId": "A" 322 | } 323 | ], 324 | "title": "State", 325 | "type": "stat" 326 | }, 327 | { 328 | "datasource": "prometheus", 329 | "description": "", 330 | "fieldConfig": { 331 | "defaults": { 332 | "color": { 333 | "mode": "thresholds" 334 | }, 335 | "custom": {}, 336 | "mappings": [], 337 | "min": 0, 338 | "thresholds": { 339 | "mode": "absolute", 340 | "steps": [ 341 | { 342 | "color": "green", 343 | "value": null 344 | }, 345 | { 346 | "color": "yellow", 347 | "value": 300 348 | }, 349 | { 350 | "color": "orange", 351 | "value": 600 352 | }, 353 | { 354 | "color": "red", 355 | "value": 900 356 | } 357 | ] 358 | }, 359 | "unit": "s" 360 | }, 361 | "overrides": [] 362 | }, 363 | "gridPos": { 364 | "h": 3, 365 | "w": 2, 366 | "x": 22, 367 | "y": 0 368 | }, 369 | "id": 4, 370 | "options": { 371 | "colorMode": "value", 372 | "graphMode": "area", 373 | "justifyMode": "auto", 374 | "orientation": "auto", 375 | "reduceOptions": { 376 | "calcs": [ 377 | "lastNotNull" 378 | ], 379 | "fields": "", 380 | "values": false 381 | }, 382 | "text": {}, 383 | "textMode": "auto" 384 | }, 385 | "pluginVersion": "7.4.5", 386 | "targets": [ 387 | { 388 | "exemplar": false, 389 | "expr": "starlink_dish_last_24h_obstructed_seconds", 390 | "format": "time_series", 391 | "interval": "", 392 | "legendFormat": "", 393 | "refId": "A" 394 | } 395 | ], 396 | "timeFrom": null, 397 | "title": "24h Obstructed Time", 398 | "transformations": [], 399 | "type": "stat" 400 | }, 401 | { 402 | "datasource": "prometheus", 403 | "fieldConfig": { 404 | "defaults": { 405 | "color": { 406 | "mode": "thresholds" 407 | }, 408 | "custom": {}, 409 | "mappings": [ 410 | { 411 | "from": "", 412 | "id": 1, 413 | "text": "False", 414 | "to": "", 415 | "type": 1, 416 | "value": "0" 417 | }, 418 | { 419 | "from": "", 420 | "id": 2, 421 | "text": "True", 422 | "to": "", 423 | "type": 1, 424 | "value": "1" 425 | } 426 | ], 427 | "thresholds": { 428 | "mode": "absolute", 429 | "steps": [ 430 | { 431 | "color": "green", 432 | "value": null 433 | }, 434 | { 435 | "color": "red", 436 | "value": 1 437 | } 438 | ] 439 | } 440 | }, 441 | "overrides": [] 442 | }, 443 | "gridPos": { 444 | "h": 2, 445 | "w": 2, 446 | "x": 20, 447 | "y": 3 448 | }, 449 | "id": 13, 450 | "options": { 451 | "colorMode": "value", 452 | "graphMode": "none", 453 | "justifyMode": "auto", 454 | "orientation": "auto", 455 | "reduceOptions": { 456 | "calcs": [ 457 | "lastNotNull" 458 | ], 459 | "fields": "", 460 | "values": false 461 | }, 462 | "text": {}, 463 | "textMode": "auto" 464 | }, 465 | "pluginVersion": "7.4.5", 466 | "targets": [ 467 | { 468 | "exemplar": false, 469 | "expr": "starlink_dish_alert_slow_eth_speeds", 470 | "interval": "", 471 | "legendFormat": "", 472 | "refId": "A" 473 | } 474 | ], 475 | "title": "Slow Ethernet", 476 | "type": "stat" 477 | }, 478 | { 479 | "datasource": "prometheus", 480 | "fieldConfig": { 481 | "defaults": { 482 | "color": { 483 | "mode": "thresholds" 484 | }, 485 | "custom": {}, 486 | "mappings": [ 487 | { 488 | "from": "", 489 | "id": 1, 490 | "text": "False", 491 | "to": "", 492 | "type": 1, 493 | "value": "0" 494 | }, 495 | { 496 | "from": "", 497 | "id": 2, 498 | "text": "True", 499 | "to": "", 500 | "type": 1, 501 | "value": "1" 502 | } 503 | ], 504 | "thresholds": { 505 | "mode": "absolute", 506 | "steps": [ 507 | { 508 | "color": "green", 509 | "value": null 510 | }, 511 | { 512 | "color": "red", 513 | "value": 1 514 | } 515 | ] 516 | } 517 | }, 518 | "overrides": [] 519 | }, 520 | "gridPos": { 521 | "h": 2, 522 | "w": 2, 523 | "x": 22, 524 | "y": 3 525 | }, 526 | "id": 16, 527 | "options": { 528 | "colorMode": "value", 529 | "graphMode": "none", 530 | "justifyMode": "auto", 531 | "orientation": "auto", 532 | "reduceOptions": { 533 | "calcs": [ 534 | "lastNotNull" 535 | ], 536 | "fields": "", 537 | "values": false 538 | }, 539 | "text": {}, 540 | "textMode": "auto" 541 | }, 542 | "pluginVersion": "7.4.5", 543 | "targets": [ 544 | { 545 | "exemplar": false, 546 | "expr": "starlink_dish_alert_unexpected_location", 547 | "interval": "", 548 | "legendFormat": "", 549 | "refId": "A" 550 | } 551 | ], 552 | "title": "Unexpected Location", 553 | "type": "stat" 554 | }, 555 | { 556 | "datasource": "prometheus", 557 | "description": "", 558 | "fieldConfig": { 559 | "defaults": { 560 | "custom": {} 561 | }, 562 | "overrides": [] 563 | }, 564 | "gridPos": { 565 | "h": 6, 566 | "w": 4, 567 | "x": 16, 568 | "y": 5 569 | }, 570 | "id": 2, 571 | "options": { 572 | "config": { 573 | "displayModeBar": false 574 | }, 575 | "data": [ 576 | { 577 | "line": { 578 | "color": "red", 579 | "width": 2 580 | }, 581 | "mode": "lines", 582 | "type": "scatter" 583 | } 584 | ], 585 | "layout": { 586 | "font": { 587 | "color": "darkgrey" 588 | }, 589 | "margin": { 590 | "b": 20, 591 | "t": 30 592 | }, 593 | "paper_bgcolor": "rgba(0,0,0,0)", 594 | "plot_bgcolor": "rgba(0,0,0,0)", 595 | "xaxis": { 596 | "autorange": false, 597 | "range": [ 598 | "2000-06-13 20:19:55.8287", 599 | "2000-06-18 15:40:49.7421" 600 | ], 601 | "type": "date" 602 | } 603 | }, 604 | "onclick": "\n\n", 605 | "script": "var data = [\n {\n r: [data.series[0].fields[1].values.buffer[0] * 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"0°-30°\",\n marker: {color: \"rgb(255,0,0)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 0° and 30°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, data.series[1].fields[1].values.buffer[0] * 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"30°-60°\",\n marker: {color: \"rgb(255,125,0)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 30° and 60°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, data.series[2].fields[1].values.buffer[0] * 100, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"60°-90°\",\n marker: {color: \"rgb(255,225,0)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 60° and 90°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, 0, data.series[3].fields[1].values.buffer[0] * 100, 0, 0, 0, 0, 0, 0, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"90°-120°\",\n marker: {color: \"rgb(125,255,0)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 90° and 120°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, 0, 0, data.series[4].fields[1].values.buffer[0] * 100, 0, 0, 0, 0, 0, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"120°-150°\",\n marker: {color: \"rgb(0,255,0)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 120° and 150°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, 0, 0, 0, data.series[5].fields[1].values.buffer[0] * 100, 0, 0, 0, 0, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"150°-180°\",\n marker: {color: \"rgb(0,255,125)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 150° and 180°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, 0, 0, 0, 0, data.series[6].fields[1].values.buffer[0] * 100, 0, 0, 0, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"180°-210°\",\n marker: {color: \"rgb(0,255,255)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 180° and 210°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, 0, 0, 0, 0, 0, data.series[7].fields[1].values.buffer[0] * 100, 0, 0, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"210°-240°\",\n marker: {color: \"rgb(0,125,255)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 210° and 240°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, 0, 0, 0, 0, 0, 0, data.series[8].fields[1].values.buffer[0] * 100, 0, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"240°-270°\",\n marker: {color: \"rgb(0,0,255)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 240° and 270°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, 0, 0, 0, 0, 0, 0, 0, data.series[9].fields[1].values.buffer[0] * 100, 0, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"270°-300°\",\n marker: {color: \"rgb(125,0,255)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 270° and 300°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, data.series[10].fields[1].values.buffer[0] * 100, 0],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"300°-330°\",\n marker: {color: \"rgb(255,0,255)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 300° and 330°\",\n hoverinfo: \"text+r\"\n },\n {\n r: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, data.series[11].fields[1].values.buffer[0] * 100],\n theta: [\"0\", \"30\", \"60\", \"90\", \"120\", \"150\", \"180\", \"210\", \"240\", \"270\", \"300\", \"330\"],\n name: \"330°-360°\",\n marker: {color: \"rgb(255,0,125)\"},\n type: \"barpolar\",\n showlegend: false,\n offset: 0,\n hovertext: \"Obstructions between 330° and 360°\",\n hoverinfo: \"text+r\"\n }\n]\nvar layout = {\n font: {size: 12},\n legend: {font: {size: 10}},\n polar: {\n barmode: \"overlay\",\n bargap: 0,\n radialaxis: {ticksuffix: \"%\", angle: 45, dtick: 10},\n angularaxis: {direction: \"clockwise\"}\n }\n}\n\nreturn {data: data, layout: layout}\n" 606 | }, 607 | "pluginVersion": "7.4.5", 608 | "targets": [ 609 | { 610 | "exemplar": false, 611 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"0\"}", 612 | "instant": true, 613 | "interval": "", 614 | "legendFormat": "", 615 | "refId": "A" 616 | }, 617 | { 618 | "exemplar": false, 619 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"1\"}", 620 | "hide": false, 621 | "instant": true, 622 | "interval": "", 623 | "legendFormat": "", 624 | "refId": "B" 625 | }, 626 | { 627 | "exemplar": false, 628 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"2\"}", 629 | "hide": false, 630 | "instant": true, 631 | "interval": "", 632 | "legendFormat": "", 633 | "refId": "C" 634 | }, 635 | { 636 | "exemplar": false, 637 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"3\"}", 638 | "hide": false, 639 | "instant": true, 640 | "interval": "", 641 | "legendFormat": "", 642 | "refId": "D" 643 | }, 644 | { 645 | "exemplar": false, 646 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"4\"}", 647 | "hide": false, 648 | "instant": true, 649 | "interval": "", 650 | "legendFormat": "", 651 | "refId": "E" 652 | }, 653 | { 654 | "exemplar": false, 655 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"5\"}", 656 | "hide": false, 657 | "instant": true, 658 | "interval": "", 659 | "legendFormat": "", 660 | "refId": "F" 661 | }, 662 | { 663 | "exemplar": false, 664 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"6\"}", 665 | "hide": false, 666 | "instant": true, 667 | "interval": "", 668 | "legendFormat": "", 669 | "refId": "G" 670 | }, 671 | { 672 | "exemplar": false, 673 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"7\"}", 674 | "hide": false, 675 | "instant": true, 676 | "interval": "", 677 | "legendFormat": "", 678 | "refId": "H" 679 | }, 680 | { 681 | "exemplar": false, 682 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"8\"}", 683 | "hide": false, 684 | "instant": true, 685 | "interval": "", 686 | "legendFormat": "", 687 | "refId": "I" 688 | }, 689 | { 690 | "exemplar": false, 691 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"9\"}", 692 | "hide": false, 693 | "instant": true, 694 | "interval": "", 695 | "legendFormat": "", 696 | "refId": "J" 697 | }, 698 | { 699 | "exemplar": false, 700 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"10\"}", 701 | "hide": false, 702 | "instant": true, 703 | "interval": "", 704 | "legendFormat": "", 705 | "refId": "K" 706 | }, 707 | { 708 | "exemplar": false, 709 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio{wedge=\"11\"}", 710 | "hide": false, 711 | "instant": true, 712 | "interval": "", 713 | "legendFormat": "", 714 | "refId": "L" 715 | } 716 | ], 717 | "title": "Wedge Fraction Obstructions", 718 | "type": "ae3e-plotly-panel" 719 | }, 720 | { 721 | "datasource": "prometheus", 722 | "fieldConfig": { 723 | "defaults": { 724 | "color": { 725 | "mode": "thresholds" 726 | }, 727 | "custom": {}, 728 | "mappings": [ 729 | { 730 | "from": "", 731 | "id": 1, 732 | "text": "False", 733 | "to": "", 734 | "type": 1, 735 | "value": "0" 736 | }, 737 | { 738 | "from": "", 739 | "id": 2, 740 | "text": "True", 741 | "to": "", 742 | "type": 1, 743 | "value": "1" 744 | } 745 | ], 746 | "thresholds": { 747 | "mode": "absolute", 748 | "steps": [ 749 | { 750 | "color": "green", 751 | "value": null 752 | }, 753 | { 754 | "color": "red", 755 | "value": 1 756 | } 757 | ] 758 | } 759 | }, 760 | "overrides": [] 761 | }, 762 | "gridPos": { 763 | "h": 2, 764 | "w": 2, 765 | "x": 20, 766 | "y": 5 767 | }, 768 | "id": 6, 769 | "interval": "1s", 770 | "options": { 771 | "colorMode": "value", 772 | "graphMode": "none", 773 | "justifyMode": "auto", 774 | "orientation": "auto", 775 | "reduceOptions": { 776 | "calcs": [ 777 | "lastNotNull" 778 | ], 779 | "fields": "", 780 | "values": false 781 | }, 782 | "text": {}, 783 | "textMode": "auto" 784 | }, 785 | "pluginVersion": "7.4.5", 786 | "targets": [ 787 | { 788 | "exemplar": false, 789 | "expr": "starlink_dish_currently_obstructed", 790 | "interval": "", 791 | "legendFormat": "", 792 | "refId": "A" 793 | } 794 | ], 795 | "title": "Currently Obstructed", 796 | "type": "stat" 797 | }, 798 | { 799 | "datasource": "prometheus", 800 | "fieldConfig": { 801 | "defaults": { 802 | "color": { 803 | "mode": "thresholds" 804 | }, 805 | "custom": {}, 806 | "mappings": [ 807 | { 808 | "from": "", 809 | "id": 1, 810 | "text": "False", 811 | "to": "", 812 | "type": 1, 813 | "value": "0" 814 | }, 815 | { 816 | "from": "", 817 | "id": 2, 818 | "text": "True", 819 | "to": "", 820 | "type": 1, 821 | "value": "1" 822 | } 823 | ], 824 | "thresholds": { 825 | "mode": "absolute", 826 | "steps": [ 827 | { 828 | "color": "green", 829 | "value": null 830 | }, 831 | { 832 | "color": "purple", 833 | "value": 1 834 | } 835 | ] 836 | } 837 | }, 838 | "overrides": [] 839 | }, 840 | "gridPos": { 841 | "h": 2, 842 | "w": 2, 843 | "x": 22, 844 | "y": 5 845 | }, 846 | "id": 29, 847 | "interval": "1s", 848 | "options": { 849 | "colorMode": "value", 850 | "graphMode": "none", 851 | "justifyMode": "auto", 852 | "orientation": "auto", 853 | "reduceOptions": { 854 | "calcs": [ 855 | "lastNotNull" 856 | ], 857 | "fields": "", 858 | "values": false 859 | }, 860 | "text": {}, 861 | "textMode": "auto" 862 | }, 863 | "pluginVersion": "7.4.5", 864 | "targets": [ 865 | { 866 | "expr": "max(speedtest_up)", 867 | "instant": false, 868 | "interval": "", 869 | "legendFormat": "", 870 | "refId": "A" 871 | } 872 | ], 873 | "title": "Speed Test", 874 | "type": "stat" 875 | }, 876 | { 877 | "datasource": "prometheus", 878 | "fieldConfig": { 879 | "defaults": { 880 | "color": { 881 | "mode": "thresholds" 882 | }, 883 | "custom": {}, 884 | "mappings": [ 885 | { 886 | "from": "", 887 | "id": 1, 888 | "text": "False", 889 | "to": "", 890 | "type": 1, 891 | "value": "0" 892 | }, 893 | { 894 | "from": "", 895 | "id": 2, 896 | "text": "True", 897 | "to": "", 898 | "type": 1, 899 | "value": "1" 900 | } 901 | ], 902 | "thresholds": { 903 | "mode": "absolute", 904 | "steps": [ 905 | { 906 | "color": "green", 907 | "value": null 908 | }, 909 | { 910 | "color": "red", 911 | "value": 1 912 | } 913 | ] 914 | } 915 | }, 916 | "overrides": [] 917 | }, 918 | "gridPos": { 919 | "h": 2, 920 | "w": 2, 921 | "x": 20, 922 | "y": 7 923 | }, 924 | "id": 14, 925 | "options": { 926 | "colorMode": "value", 927 | "graphMode": "none", 928 | "justifyMode": "auto", 929 | "orientation": "auto", 930 | "reduceOptions": { 931 | "calcs": [ 932 | "lastNotNull" 933 | ], 934 | "fields": "", 935 | "values": false 936 | }, 937 | "text": {}, 938 | "textMode": "auto" 939 | }, 940 | "pluginVersion": "7.4.5", 941 | "targets": [ 942 | { 943 | "exemplar": false, 944 | "expr": "starlink_dish_alert_mast_not_near_vertical", 945 | "interval": "", 946 | "legendFormat": "", 947 | "refId": "A" 948 | } 949 | ], 950 | "title": "Mast Not Near Vertical", 951 | "type": "stat" 952 | }, 953 | { 954 | "datasource": "prometheus", 955 | "fieldConfig": { 956 | "defaults": { 957 | "color": { 958 | "mode": "thresholds" 959 | }, 960 | "custom": {}, 961 | "mappings": [ 962 | { 963 | "from": "", 964 | "id": 1, 965 | "text": "False", 966 | "to": "", 967 | "type": 1, 968 | "value": "0" 969 | }, 970 | { 971 | "from": "", 972 | "id": 2, 973 | "text": "True", 974 | "to": "", 975 | "type": 1, 976 | "value": "1" 977 | } 978 | ], 979 | "thresholds": { 980 | "mode": "absolute", 981 | "steps": [ 982 | { 983 | "color": "green", 984 | "value": null 985 | }, 986 | { 987 | "color": "red", 988 | "value": 1 989 | } 990 | ] 991 | } 992 | }, 993 | "overrides": [] 994 | }, 995 | "gridPos": { 996 | "h": 2, 997 | "w": 2, 998 | "x": 22, 999 | "y": 7 1000 | }, 1001 | "id": 17, 1002 | "options": { 1003 | "colorMode": "value", 1004 | "graphMode": "none", 1005 | "justifyMode": "auto", 1006 | "orientation": "auto", 1007 | "reduceOptions": { 1008 | "calcs": [ 1009 | "lastNotNull" 1010 | ], 1011 | "fields": "", 1012 | "values": false 1013 | }, 1014 | "text": {}, 1015 | "textMode": "auto" 1016 | }, 1017 | "pluginVersion": "7.4.5", 1018 | "targets": [ 1019 | { 1020 | "exemplar": false, 1021 | "expr": "starlink_dish_alert_thermal_throttle", 1022 | "interval": "", 1023 | "legendFormat": "", 1024 | "refId": "A" 1025 | } 1026 | ], 1027 | "title": "Thermal throttle", 1028 | "type": "stat" 1029 | }, 1030 | { 1031 | "aliasColors": {}, 1032 | "bars": false, 1033 | "dashLength": 10, 1034 | "dashes": false, 1035 | "datasource": "prometheus", 1036 | "fieldConfig": { 1037 | "defaults": { 1038 | "custom": {} 1039 | }, 1040 | "overrides": [] 1041 | }, 1042 | "fill": 1, 1043 | "fillGradient": 0, 1044 | "gridPos": { 1045 | "h": 8, 1046 | "w": 16, 1047 | "x": 0, 1048 | "y": 8 1049 | }, 1050 | "hiddenSeries": false, 1051 | "id": 10, 1052 | "interval": "1s", 1053 | "legend": { 1054 | "alignAsTable": false, 1055 | "avg": true, 1056 | "current": true, 1057 | "hideEmpty": false, 1058 | "hideZero": false, 1059 | "max": true, 1060 | "min": true, 1061 | "rightSide": false, 1062 | "show": true, 1063 | "total": false, 1064 | "values": true 1065 | }, 1066 | "lines": true, 1067 | "linewidth": 1, 1068 | "nullPointMode": "null", 1069 | "options": { 1070 | "alertThreshold": true 1071 | }, 1072 | "percentage": false, 1073 | "pluginVersion": "7.4.5", 1074 | "pointradius": 2, 1075 | "points": false, 1076 | "renderer": "flot", 1077 | "seriesOverrides": [ 1078 | { 1079 | "$$hashKey": "object:94", 1080 | "alias": "Ping Drop", 1081 | "bars": true, 1082 | "color": "#F2495C", 1083 | "lines": false, 1084 | "yaxis": 2 1085 | }, 1086 | { 1087 | "$$hashKey": "object:613", 1088 | "alias": "Latency", 1089 | "color": "#5794F2" 1090 | } 1091 | ], 1092 | "spaceLength": 10, 1093 | "stack": false, 1094 | "steppedLine": false, 1095 | "targets": [ 1096 | { 1097 | "exemplar": false, 1098 | "expr": "starlink_dish_pop_ping_latency_seconds", 1099 | "interval": "", 1100 | "legendFormat": "Latency", 1101 | "refId": "A" 1102 | }, 1103 | { 1104 | "exemplar": false, 1105 | "expr": "starlink_dish_pop_ping_drop_ratio", 1106 | "hide": false, 1107 | "interval": "", 1108 | "legendFormat": "Ping Drop", 1109 | "refId": "B" 1110 | } 1111 | ], 1112 | "thresholds": [], 1113 | "timeFrom": null, 1114 | "timeRegions": [], 1115 | "timeShift": null, 1116 | "title": "Latency/Drop", 1117 | "tooltip": { 1118 | "shared": true, 1119 | "sort": 0, 1120 | "value_type": "individual" 1121 | }, 1122 | "type": "graph", 1123 | "xaxis": { 1124 | "buckets": null, 1125 | "mode": "time", 1126 | "name": null, 1127 | "show": true, 1128 | "values": [] 1129 | }, 1130 | "yaxes": [ 1131 | { 1132 | "$$hashKey": "object:201", 1133 | "format": "ms", 1134 | "label": "Time", 1135 | "logBase": 1, 1136 | "max": null, 1137 | "min": "0", 1138 | "show": true 1139 | }, 1140 | { 1141 | "$$hashKey": "object:202", 1142 | "format": "percentunit", 1143 | "label": "Drop", 1144 | "logBase": 1, 1145 | "max": "1", 1146 | "min": "0", 1147 | "show": true 1148 | } 1149 | ], 1150 | "yaxis": { 1151 | "align": false, 1152 | "alignLevel": null 1153 | } 1154 | }, 1155 | { 1156 | "datasource": "prometheus", 1157 | "fieldConfig": { 1158 | "defaults": { 1159 | "color": { 1160 | "mode": "thresholds" 1161 | }, 1162 | "custom": {}, 1163 | "mappings": [ 1164 | { 1165 | "from": "", 1166 | "id": 1, 1167 | "text": "False", 1168 | "to": "", 1169 | "type": 1, 1170 | "value": "0" 1171 | }, 1172 | { 1173 | "from": "", 1174 | "id": 2, 1175 | "text": "True", 1176 | "to": "", 1177 | "type": 1, 1178 | "value": "1" 1179 | } 1180 | ], 1181 | "thresholds": { 1182 | "mode": "absolute", 1183 | "steps": [ 1184 | { 1185 | "color": "green", 1186 | "value": null 1187 | }, 1188 | { 1189 | "color": "red", 1190 | "value": 1 1191 | } 1192 | ] 1193 | } 1194 | }, 1195 | "overrides": [] 1196 | }, 1197 | "gridPos": { 1198 | "h": 2, 1199 | "w": 2, 1200 | "x": 20, 1201 | "y": 9 1202 | }, 1203 | "id": 12, 1204 | "options": { 1205 | "colorMode": "value", 1206 | "graphMode": "none", 1207 | "justifyMode": "auto", 1208 | "orientation": "auto", 1209 | "reduceOptions": { 1210 | "calcs": [ 1211 | "lastNotNull" 1212 | ], 1213 | "fields": "", 1214 | "values": false 1215 | }, 1216 | "text": {}, 1217 | "textMode": "auto" 1218 | }, 1219 | "pluginVersion": "7.4.5", 1220 | "targets": [ 1221 | { 1222 | "exemplar": false, 1223 | "expr": "starlink_dish_alert_motors_stuck", 1224 | "interval": "", 1225 | "legendFormat": "", 1226 | "refId": "A" 1227 | } 1228 | ], 1229 | "title": "Motors Stuck", 1230 | "type": "stat" 1231 | }, 1232 | { 1233 | "datasource": "prometheus", 1234 | "fieldConfig": { 1235 | "defaults": { 1236 | "color": { 1237 | "mode": "thresholds" 1238 | }, 1239 | "custom": {}, 1240 | "mappings": [ 1241 | { 1242 | "from": "", 1243 | "id": 1, 1244 | "text": "False", 1245 | "to": "", 1246 | "type": 1, 1247 | "value": "0" 1248 | }, 1249 | { 1250 | "from": "", 1251 | "id": 2, 1252 | "text": "True", 1253 | "to": "", 1254 | "type": 1, 1255 | "value": "1" 1256 | } 1257 | ], 1258 | "thresholds": { 1259 | "mode": "absolute", 1260 | "steps": [ 1261 | { 1262 | "color": "green", 1263 | "value": null 1264 | }, 1265 | { 1266 | "color": "red", 1267 | "value": 1 1268 | } 1269 | ] 1270 | } 1271 | }, 1272 | "overrides": [] 1273 | }, 1274 | "gridPos": { 1275 | "h": 2, 1276 | "w": 2, 1277 | "x": 22, 1278 | "y": 9 1279 | }, 1280 | "id": 15, 1281 | "options": { 1282 | "colorMode": "value", 1283 | "graphMode": "none", 1284 | "justifyMode": "auto", 1285 | "orientation": "auto", 1286 | "reduceOptions": { 1287 | "calcs": [ 1288 | "lastNotNull" 1289 | ], 1290 | "fields": "", 1291 | "values": false 1292 | }, 1293 | "text": {}, 1294 | "textMode": "auto" 1295 | }, 1296 | "pluginVersion": "7.4.5", 1297 | "targets": [ 1298 | { 1299 | "exemplar": false, 1300 | "expr": "starlink_dish_alert_thermal_shutdown", 1301 | "interval": "", 1302 | "legendFormat": "", 1303 | "refId": "A" 1304 | } 1305 | ], 1306 | "title": "Thermal Shutdown", 1307 | "type": "stat" 1308 | }, 1309 | { 1310 | "aliasColors": {}, 1311 | "bars": false, 1312 | "dashLength": 10, 1313 | "dashes": false, 1314 | "datasource": "prometheus", 1315 | "fieldConfig": { 1316 | "defaults": { 1317 | "custom": {}, 1318 | "unit": "short" 1319 | }, 1320 | "overrides": [] 1321 | }, 1322 | "fill": 1, 1323 | "fillGradient": 0, 1324 | "gridPos": { 1325 | "h": 4, 1326 | "w": 4, 1327 | "x": 16, 1328 | "y": 11 1329 | }, 1330 | "hiddenSeries": false, 1331 | "id": 37, 1332 | "legend": { 1333 | "avg": false, 1334 | "current": false, 1335 | "max": false, 1336 | "min": false, 1337 | "show": false, 1338 | "total": false, 1339 | "values": false 1340 | }, 1341 | "lines": true, 1342 | "linewidth": 1, 1343 | "nullPointMode": "null", 1344 | "options": { 1345 | "alertThreshold": true 1346 | }, 1347 | "percentage": false, 1348 | "pluginVersion": "7.4.5", 1349 | "pointradius": 2, 1350 | "points": false, 1351 | "renderer": "flot", 1352 | "seriesOverrides": [], 1353 | "spaceLength": 10, 1354 | "stack": false, 1355 | "steppedLine": false, 1356 | "targets": [ 1357 | { 1358 | "exemplar": false, 1359 | "expr": "starlink_dish_wedge_fraction_obstruction_ratio * 100 > 0 ", 1360 | "interval": "", 1361 | "legendFormat": "{{wedge}}", 1362 | "refId": "A" 1363 | } 1364 | ], 1365 | "thresholds": [], 1366 | "timeFrom": null, 1367 | "timeRegions": [], 1368 | "timeShift": null, 1369 | "title": "Wedge Fraction Obstructions", 1370 | "tooltip": { 1371 | "shared": true, 1372 | "sort": 0, 1373 | "value_type": "individual" 1374 | }, 1375 | "type": "graph", 1376 | "xaxis": { 1377 | "buckets": null, 1378 | "mode": "time", 1379 | "name": null, 1380 | "show": true, 1381 | "values": [] 1382 | }, 1383 | "yaxes": [ 1384 | { 1385 | "$$hashKey": "object:327", 1386 | "format": "short", 1387 | "label": "Percent", 1388 | "logBase": 1, 1389 | "max": null, 1390 | "min": "0", 1391 | "show": true 1392 | }, 1393 | { 1394 | "$$hashKey": "object:328", 1395 | "format": "short", 1396 | "label": null, 1397 | "logBase": 1, 1398 | "max": null, 1399 | "min": null, 1400 | "show": false 1401 | } 1402 | ], 1403 | "yaxis": { 1404 | "align": false, 1405 | "alignLevel": null 1406 | } 1407 | }, 1408 | { 1409 | "aliasColors": {}, 1410 | "bars": false, 1411 | "dashLength": 10, 1412 | "dashes": false, 1413 | "datasource": "prometheus", 1414 | "fieldConfig": { 1415 | "defaults": { 1416 | "custom": {}, 1417 | "unit": "short" 1418 | }, 1419 | "overrides": [] 1420 | }, 1421 | "fill": 1, 1422 | "fillGradient": 0, 1423 | "gridPos": { 1424 | "h": 4, 1425 | "w": 4, 1426 | "x": 20, 1427 | "y": 11 1428 | }, 1429 | "hiddenSeries": false, 1430 | "id": 36, 1431 | "legend": { 1432 | "avg": false, 1433 | "current": false, 1434 | "max": false, 1435 | "min": false, 1436 | "show": false, 1437 | "total": false, 1438 | "values": false 1439 | }, 1440 | "lines": true, 1441 | "linewidth": 1, 1442 | "nullPointMode": "null", 1443 | "options": { 1444 | "alertThreshold": true 1445 | }, 1446 | "percentage": false, 1447 | "pluginVersion": "7.4.5", 1448 | "pointradius": 2, 1449 | "points": false, 1450 | "renderer": "flot", 1451 | "seriesOverrides": [], 1452 | "spaceLength": 10, 1453 | "stack": false, 1454 | "steppedLine": false, 1455 | "targets": [ 1456 | { 1457 | "exemplar": false, 1458 | "expr": "starlink_dish_first_nonempty_slot_seconds", 1459 | "interval": "", 1460 | "legendFormat": "", 1461 | "refId": "A" 1462 | } 1463 | ], 1464 | "thresholds": [], 1465 | "timeFrom": null, 1466 | "timeRegions": [], 1467 | "timeShift": null, 1468 | "title": "Seconds To Non-empty Slot", 1469 | "tooltip": { 1470 | "shared": true, 1471 | "sort": 0, 1472 | "value_type": "individual" 1473 | }, 1474 | "type": "graph", 1475 | "xaxis": { 1476 | "buckets": null, 1477 | "mode": "time", 1478 | "name": null, 1479 | "show": true, 1480 | "values": [] 1481 | }, 1482 | "yaxes": [ 1483 | { 1484 | "$$hashKey": "object:327", 1485 | "format": "short", 1486 | "label": null, 1487 | "logBase": 2, 1488 | "max": null, 1489 | "min": "0", 1490 | "show": true 1491 | }, 1492 | { 1493 | "$$hashKey": "object:328", 1494 | "format": "short", 1495 | "label": null, 1496 | "logBase": 1, 1497 | "max": null, 1498 | "min": null, 1499 | "show": false 1500 | } 1501 | ], 1502 | "yaxis": { 1503 | "align": false, 1504 | "alignLevel": null 1505 | } 1506 | }, 1507 | { 1508 | "datasource": "prometheus", 1509 | "fieldConfig": { 1510 | "defaults": { 1511 | "custom": {} 1512 | }, 1513 | "overrides": [] 1514 | }, 1515 | "gridPos": { 1516 | "h": 1, 1517 | "w": 8, 1518 | "x": 16, 1519 | "y": 15 1520 | }, 1521 | "id": 23, 1522 | "options": { 1523 | "content": "\n ", 1524 | "mode": "markdown" 1525 | }, 1526 | "pluginVersion": "7.4.5", 1527 | "timeFrom": null, 1528 | "timeShift": null, 1529 | "title": "Best Speedtest.net In Last 24 Hours", 1530 | "type": "text" 1531 | }, 1532 | { 1533 | "aliasColors": {}, 1534 | "bars": false, 1535 | "dashLength": 10, 1536 | "dashes": false, 1537 | "datasource": "prometheus", 1538 | "fieldConfig": { 1539 | "defaults": { 1540 | "custom": {} 1541 | }, 1542 | "overrides": [] 1543 | }, 1544 | "fill": 0, 1545 | "fillGradient": 0, 1546 | "gridPos": { 1547 | "h": 7, 1548 | "w": 16, 1549 | "x": 0, 1550 | "y": 16 1551 | }, 1552 | "hiddenSeries": false, 1553 | "id": 26, 1554 | "interval": "1s", 1555 | "legend": { 1556 | "alignAsTable": false, 1557 | "avg": true, 1558 | "current": true, 1559 | "hideEmpty": false, 1560 | "hideZero": false, 1561 | "max": true, 1562 | "min": true, 1563 | "rightSide": false, 1564 | "show": true, 1565 | "total": false, 1566 | "values": true 1567 | }, 1568 | "lines": true, 1569 | "linewidth": 1, 1570 | "nullPointMode": "null", 1571 | "options": { 1572 | "alertThreshold": true 1573 | }, 1574 | "percentage": false, 1575 | "pluginVersion": "7.4.5", 1576 | "pointradius": 2, 1577 | "points": false, 1578 | "renderer": "flot", 1579 | "seriesOverrides": [ 1580 | { 1581 | "$$hashKey": "object:631", 1582 | "alias": "Obstructed", 1583 | "bars": true, 1584 | "color": "#F2495C", 1585 | "lines": false, 1586 | "yaxis": 2 1587 | } 1588 | ], 1589 | "spaceLength": 10, 1590 | "stack": false, 1591 | "steppedLine": false, 1592 | "targets": [ 1593 | { 1594 | "exemplar": false, 1595 | "expr": "starlink_dish_snr", 1596 | "interval": "", 1597 | "legendFormat": "SNR", 1598 | "refId": "A" 1599 | }, 1600 | { 1601 | "exemplar": false, 1602 | "expr": "starlink_dish_currently_obstructed", 1603 | "hide": false, 1604 | "interval": "", 1605 | "legendFormat": "Obstructed", 1606 | "refId": "B" 1607 | } 1608 | ], 1609 | "thresholds": [], 1610 | "timeFrom": null, 1611 | "timeRegions": [], 1612 | "timeShift": null, 1613 | "title": "SNR", 1614 | "tooltip": { 1615 | "shared": true, 1616 | "sort": 0, 1617 | "value_type": "individual" 1618 | }, 1619 | "type": "graph", 1620 | "xaxis": { 1621 | "buckets": null, 1622 | "mode": "time", 1623 | "name": null, 1624 | "show": true, 1625 | "values": [] 1626 | }, 1627 | "yaxes": [ 1628 | { 1629 | "$$hashKey": "object:201", 1630 | "decimals": 0, 1631 | "format": "short", 1632 | "label": "SNR", 1633 | "logBase": 1, 1634 | "max": "9", 1635 | "min": "0", 1636 | "show": true 1637 | }, 1638 | { 1639 | "$$hashKey": "object:202", 1640 | "format": "percentunit", 1641 | "label": "Obstructed", 1642 | "logBase": 1, 1643 | "max": "1", 1644 | "min": "0", 1645 | "show": true 1646 | } 1647 | ], 1648 | "yaxis": { 1649 | "align": true, 1650 | "alignLevel": 0 1651 | } 1652 | }, 1653 | { 1654 | "datasource": "prometheus", 1655 | "fieldConfig": { 1656 | "defaults": { 1657 | "color": { 1658 | "fixedColor": "yellow", 1659 | "mode": "fixed" 1660 | }, 1661 | "custom": {}, 1662 | "mappings": [], 1663 | "thresholds": { 1664 | "mode": "absolute", 1665 | "steps": [ 1666 | { 1667 | "color": "rgb(131, 135, 131)", 1668 | "value": null 1669 | } 1670 | ] 1671 | }, 1672 | "unit": "Bps" 1673 | }, 1674 | "overrides": [] 1675 | }, 1676 | "gridPos": { 1677 | "h": 2, 1678 | "w": 3, 1679 | "x": 16, 1680 | "y": 16 1681 | }, 1682 | "id": 21, 1683 | "options": { 1684 | "colorMode": "value", 1685 | "graphMode": "none", 1686 | "justifyMode": "auto", 1687 | "orientation": "auto", 1688 | "reduceOptions": { 1689 | "calcs": [ 1690 | "lastNotNull" 1691 | ], 1692 | "fields": "", 1693 | "values": false 1694 | }, 1695 | "text": {}, 1696 | "textMode": "auto" 1697 | }, 1698 | "pluginVersion": "7.4.5", 1699 | "targets": [ 1700 | { 1701 | "exemplar": false, 1702 | "expr": "max(max_over_time(speedtest_download_bits_per_second[1d:5m]))", 1703 | "interval": "", 1704 | "legendFormat": "", 1705 | "refId": "A" 1706 | } 1707 | ], 1708 | "timeFrom": null, 1709 | "title": "Download", 1710 | "type": "stat" 1711 | }, 1712 | { 1713 | "datasource": "prometheus", 1714 | "fieldConfig": { 1715 | "defaults": { 1716 | "color": { 1717 | "fixedColor": "purple", 1718 | "mode": "fixed" 1719 | }, 1720 | "custom": {}, 1721 | "mappings": [], 1722 | "thresholds": { 1723 | "mode": "absolute", 1724 | "steps": [ 1725 | { 1726 | "color": "rgb(131, 135, 131)", 1727 | "value": null 1728 | } 1729 | ] 1730 | }, 1731 | "unit": "Bps" 1732 | }, 1733 | "overrides": [] 1734 | }, 1735 | "gridPos": { 1736 | "h": 2, 1737 | "w": 3, 1738 | "x": 19, 1739 | "y": 16 1740 | }, 1741 | "id": 24, 1742 | "options": { 1743 | "colorMode": "value", 1744 | "graphMode": "none", 1745 | "justifyMode": "auto", 1746 | "orientation": "auto", 1747 | "reduceOptions": { 1748 | "calcs": [ 1749 | "lastNotNull" 1750 | ], 1751 | "fields": "", 1752 | "values": false 1753 | }, 1754 | "text": {}, 1755 | "textMode": "auto" 1756 | }, 1757 | "pluginVersion": "7.4.5", 1758 | "targets": [ 1759 | { 1760 | "exemplar": false, 1761 | "expr": "max(max_over_time(speedtest_upload_bits_per_second[1d:5m]))", 1762 | "interval": "", 1763 | "legendFormat": "", 1764 | "refId": "A" 1765 | } 1766 | ], 1767 | "title": "Upload", 1768 | "type": "stat" 1769 | }, 1770 | { 1771 | "datasource": "prometheus", 1772 | "fieldConfig": { 1773 | "defaults": { 1774 | "color": { 1775 | "fixedColor": "blue", 1776 | "mode": "fixed" 1777 | }, 1778 | "custom": {}, 1779 | "mappings": [], 1780 | "thresholds": { 1781 | "mode": "absolute", 1782 | "steps": [ 1783 | { 1784 | "color": "rgb(131, 135, 131)", 1785 | "value": null 1786 | } 1787 | ] 1788 | }, 1789 | "unit": "ms" 1790 | }, 1791 | "overrides": [] 1792 | }, 1793 | "gridPos": { 1794 | "h": 2, 1795 | "w": 2, 1796 | "x": 22, 1797 | "y": 16 1798 | }, 1799 | "id": 25, 1800 | "options": { 1801 | "colorMode": "value", 1802 | "graphMode": "none", 1803 | "justifyMode": "auto", 1804 | "orientation": "auto", 1805 | "reduceOptions": { 1806 | "calcs": [ 1807 | "lastNotNull" 1808 | ], 1809 | "fields": "", 1810 | "values": false 1811 | }, 1812 | "text": {}, 1813 | "textMode": "auto" 1814 | }, 1815 | "pluginVersion": "7.4.5", 1816 | "targets": [ 1817 | { 1818 | "exemplar": false, 1819 | "expr": "min(min_over_time(speedtest_ping_latency_milliseconds[1d:5m]))", 1820 | "interval": "", 1821 | "legendFormat": "", 1822 | "refId": "A" 1823 | } 1824 | ], 1825 | "title": "Latency", 1826 | "type": "stat" 1827 | }, 1828 | { 1829 | "aliasColors": {}, 1830 | "bars": false, 1831 | "dashLength": 10, 1832 | "dashes": false, 1833 | "datasource": "prometheus", 1834 | "fieldConfig": { 1835 | "defaults": { 1836 | "custom": {} 1837 | }, 1838 | "overrides": [] 1839 | }, 1840 | "fill": 1, 1841 | "fillGradient": 0, 1842 | "gridPos": { 1843 | "h": 5, 1844 | "w": 8, 1845 | "x": 16, 1846 | "y": 18 1847 | }, 1848 | "hiddenSeries": false, 1849 | "id": 32, 1850 | "interval": "1s", 1851 | "legend": { 1852 | "avg": true, 1853 | "current": false, 1854 | "max": true, 1855 | "min": true, 1856 | "show": true, 1857 | "total": false, 1858 | "values": true 1859 | }, 1860 | "lines": true, 1861 | "linewidth": 1, 1862 | "nullPointMode": "null", 1863 | "options": { 1864 | "alertThreshold": true 1865 | }, 1866 | "percentage": false, 1867 | "pluginVersion": "7.4.5", 1868 | "pointradius": 2, 1869 | "points": false, 1870 | "renderer": "flot", 1871 | "seriesOverrides": [ 1872 | { 1873 | "$$hashKey": "object:446", 1874 | "alias": "Scrape Duration", 1875 | "color": "rgb(191, 191, 191)" 1876 | } 1877 | ], 1878 | "spaceLength": 10, 1879 | "stack": false, 1880 | "steppedLine": false, 1881 | "targets": [ 1882 | { 1883 | "exemplar": false, 1884 | "expr": "scrape_duration_seconds", 1885 | "interval": "", 1886 | "legendFormat": "{{ job }}", 1887 | "refId": "A" 1888 | } 1889 | ], 1890 | "thresholds": [], 1891 | "timeFrom": null, 1892 | "timeRegions": [], 1893 | "timeShift": null, 1894 | "title": "prometheus Scrape Duration", 1895 | "tooltip": { 1896 | "shared": true, 1897 | "sort": 0, 1898 | "value_type": "individual" 1899 | }, 1900 | "type": "graph", 1901 | "xaxis": { 1902 | "buckets": null, 1903 | "mode": "time", 1904 | "name": null, 1905 | "show": true, 1906 | "values": [] 1907 | }, 1908 | "yaxes": [ 1909 | { 1910 | "$$hashKey": "object:50", 1911 | "format": "s", 1912 | "label": null, 1913 | "logBase": 10, 1914 | "max": null, 1915 | "min": null, 1916 | "show": true 1917 | }, 1918 | { 1919 | "$$hashKey": "object:51", 1920 | "format": "short", 1921 | "label": null, 1922 | "logBase": 1, 1923 | "max": null, 1924 | "min": null, 1925 | "show": true 1926 | } 1927 | ], 1928 | "yaxis": { 1929 | "align": false, 1930 | "alignLevel": null 1931 | } 1932 | } 1933 | ], 1934 | "refresh": "5s", 1935 | "schemaVersion": 27, 1936 | "style": "dark", 1937 | "tags": [], 1938 | "templating": { 1939 | "list": [] 1940 | }, 1941 | "time": { 1942 | "from": "now-30m", 1943 | "to": "now" 1944 | }, 1945 | "timepicker": { 1946 | "hidden": false, 1947 | "nowDelay": "", 1948 | "refresh_intervals": [ 1949 | "5s", 1950 | "10s", 1951 | "30s", 1952 | "1m", 1953 | "5m" 1954 | ] 1955 | }, 1956 | "timezone": "", 1957 | "title": "Starlink Overview", 1958 | "uid": "GG3mnflGz", 1959 | "version": 3 1960 | } -------------------------------------------------------------------------------- /images/internet-monitoring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/internet-pi/37a3186f566a5063ca55a28378493dc7deda5611/images/internet-monitoring.png -------------------------------------------------------------------------------- /images/pi-hole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/internet-pi/37a3186f566a5063ca55a28378493dc7deda5611/images/pi-hole.png -------------------------------------------------------------------------------- /internet-monitoring/Grafana-Org-Stats.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "grafana", 15 | "id": "grafana", 16 | "name": "Grafana", 17 | "version": "4.1.2" 18 | }, 19 | { 20 | "type": "panel", 21 | "id": "graph", 22 | "name": "Graph", 23 | "version": "" 24 | }, 25 | { 26 | "type": "datasource", 27 | "id": "prometheus", 28 | "name": "Prometheus", 29 | "version": "1.0.0" 30 | } 31 | ], 32 | "annotations": { 33 | "list": [] 34 | }, 35 | "editable": true, 36 | "gnetId": null, 37 | "graphTooltip": 0, 38 | "hideControls": false, 39 | "id": null, 40 | "links": [], 41 | "rows": [ 42 | { 43 | "collapse": false, 44 | "height": "250px", 45 | "panels": [ 46 | { 47 | "aliasColors": {}, 48 | "bars": false, 49 | "datasource": "${DS_PROMETHEUS}", 50 | "fill": 1, 51 | "id": 1, 52 | "legend": { 53 | "avg": false, 54 | "current": false, 55 | "max": false, 56 | "min": false, 57 | "show": true, 58 | "total": false, 59 | "values": false 60 | }, 61 | "lines": true, 62 | "linewidth": 1, 63 | "links": [], 64 | "nullPointMode": "null", 65 | "percentage": false, 66 | "pointradius": 5, 67 | "points": false, 68 | "renderer": "flot", 69 | "seriesOverrides": [], 70 | "span": 6, 71 | "stack": false, 72 | "steppedLine": false, 73 | "targets": [ 74 | { 75 | "expr": "topk(5, github_repo_stars)", 76 | "intervalFactor": 2, 77 | "legendFormat": "{{repo}}", 78 | "metric": "github_repo_stars", 79 | "refId": "A", 80 | "step": 60 81 | } 82 | ], 83 | "thresholds": [], 84 | "timeFrom": null, 85 | "timeShift": null, 86 | "title": "Top 5 Repos", 87 | "tooltip": { 88 | "shared": true, 89 | "sort": 0, 90 | "value_type": "individual" 91 | }, 92 | "type": "graph", 93 | "xaxis": { 94 | "mode": "time", 95 | "name": null, 96 | "show": true, 97 | "values": [] 98 | }, 99 | "yaxes": [ 100 | { 101 | "format": "short", 102 | "label": null, 103 | "logBase": 1, 104 | "max": null, 105 | "min": null, 106 | "show": true 107 | }, 108 | { 109 | "format": "short", 110 | "label": null, 111 | "logBase": 1, 112 | "max": null, 113 | "min": null, 114 | "show": true 115 | } 116 | ] 117 | }, 118 | { 119 | "aliasColors": {}, 120 | "bars": false, 121 | "datasource": "${DS_PROMETHEUS}", 122 | "fill": 1, 123 | "id": 2, 124 | "legend": { 125 | "avg": false, 126 | "current": false, 127 | "max": false, 128 | "min": false, 129 | "show": true, 130 | "total": false, 131 | "values": false 132 | }, 133 | "lines": true, 134 | "linewidth": 1, 135 | "links": [], 136 | "nullPointMode": "null", 137 | "percentage": false, 138 | "pointradius": 5, 139 | "points": false, 140 | "renderer": "flot", 141 | "seriesOverrides": [], 142 | "span": 6, 143 | "stack": false, 144 | "steppedLine": false, 145 | "targets": [ 146 | { 147 | "expr": "sum(github_repo_stars)", 148 | "intervalFactor": 2, 149 | "legendFormat": "Total Github Stars", 150 | "metric": "github_repo_stars", 151 | "refId": "A", 152 | "step": 60 153 | } 154 | ], 155 | "thresholds": [], 156 | "timeFrom": null, 157 | "timeShift": null, 158 | "title": "Total GitHub Stars", 159 | "tooltip": { 160 | "shared": true, 161 | "sort": 0, 162 | "value_type": "individual" 163 | }, 164 | "type": "graph", 165 | "xaxis": { 166 | "mode": "time", 167 | "name": null, 168 | "show": true, 169 | "values": [] 170 | }, 171 | "yaxes": [ 172 | { 173 | "format": "short", 174 | "label": null, 175 | "logBase": 1, 176 | "max": null, 177 | "min": null, 178 | "show": true 179 | }, 180 | { 181 | "format": "short", 182 | "label": null, 183 | "logBase": 1, 184 | "max": null, 185 | "min": null, 186 | "show": true 187 | } 188 | ] 189 | } 190 | ], 191 | "repeat": null, 192 | "repeatIteration": null, 193 | "repeatRowId": null, 194 | "showTitle": false, 195 | "title": "Dashboard Row", 196 | "titleSize": "h6" 197 | } 198 | ], 199 | "schemaVersion": 14, 200 | "style": "dark", 201 | "tags": [], 202 | "templating": { 203 | "list": [] 204 | }, 205 | "time": { 206 | "from": "now-6h", 207 | "to": "now" 208 | }, 209 | "timepicker": { 210 | "refresh_intervals": [ 211 | "5s", 212 | "10s", 213 | "30s", 214 | "1m", 215 | "5m", 216 | "15m", 217 | "30m", 218 | "1h", 219 | "2h", 220 | "1d" 221 | ], 222 | "time_options": [ 223 | "5m", 224 | "15m", 225 | "1h", 226 | "6h", 227 | "12h", 228 | "24h", 229 | "2d", 230 | "7d", 231 | "30d" 232 | ] 233 | }, 234 | "timezone": "browser", 235 | "title": "Org Stats", 236 | "version": 0 237 | } -------------------------------------------------------------------------------- /internet-monitoring/README.md: -------------------------------------------------------------------------------- 1 | # Internet Monitoring Docker Stack with Prometheus + Grafana 2 | 3 | > This repository is a fork from [maxandersen/internet-monitoring](https://github.com/maxandersen/internet-monitoring), tailored for use on a Raspberry Pi. It has only been tested on a Raspberry Pi 4 running Pi OS 64-bit beta. 4 | > 5 | > This has also recently been merged into the internet-pi repository, so there could be a few little things that need tweaking. 6 | 7 | Stand-up a Docker [Prometheus](http://prometheus.io/) stack containing Prometheus, Grafana with [blackbox-exporter](https://github.com/prometheus/blackbox_exporter), and [speedtest-exporter](https://github.com/MiguelNdeCarvalho/speedtest-exporter) to collect and graph home Internet reliability and throughput. 8 | 9 | ## Pre-requisites 10 | 11 | Make sure Docker and [Docker Compose](https://docs.docker.com/compose/install/) are installed on your Docker host machine. 12 | 13 | ## Quick Start 14 | 15 | Follow the directions inside the main README file in this repository (make sure you have `monitoring_enable: true` in your `config.yml` before running the Ansible playbook). 16 | 17 | Go to [http://localhost:3030/d/o9mIe_Aik/internet-connection](http://localhost:3030/d/o9mIe_Aik/internet-connection) (change `localhost` to your docker host ip/name). 18 | 19 | ## Configuration 20 | 21 | To change what hosts you ping you change the `targets` section in [/prometheus/pinghosts.yaml](./prometheus/pinghosts.yaml) file. 22 | 23 | For speedtest the only relevant configuration is how often you want the check to happen. It is at 30 minutes by default which might be too much if you have limit on downloads. This is changed by editing `scrape_interval` under `speedtest` in [/prometheus/prometheus.yml](./prometheus/prometheus.yml). 24 | 25 | Once configurations are done, run the following command: 26 | 27 | $ docker compose up -d 28 | 29 | That's it. docker-compose builds the entire Grafana and Prometheus stack automagically. 30 | 31 | The Grafana Dashboard is now accessible via: `http://:3030` for example http://localhost:3030 32 | 33 | username - admin 34 | password - wonka (Password is stored in the `config.monitoring` env file) 35 | 36 | The DataSource and Dashboard for Grafana are automatically provisioned. 37 | 38 | If all works it should be available at http://localhost:3030/d/o9mIe_Aik/internet-connection - if no data shows up try change the timeduration to something smaller. 39 | 40 |
41 | 42 | ## Interesting urls 43 | 44 | http://localhost:9090/targets shows status of monitored targets as seen from prometheus - in this case which hosts being pinged and speedtest. note: speedtest will take a while before it shows as UP as it takes about 30s to respond. 45 | 46 | http://localhost:9090/graph?g0.expr=probe_http_status_code&g0.tab=1 shows prometheus value for `probe_http_status_code` for each host. You can edit/play with additional values. Useful to check everything is okey in prometheus (in case Grafana is not showing the data you expect). 47 | 48 | http://localhost:9115 blackbox exporter endpoint. Lets you see what have failed/succeded. 49 | 50 | http://localhost:9798/metrics speedtest exporter endpoint. Does take about 30 seconds to show its result as it runs an actual speedtest when requested. 51 | 52 | ## Thanks and a disclaimer 53 | 54 | Thanks to @maxandersen for making the original project this fork is based on. 55 | 56 | Thanks to @vegasbrianc work on making a [super easy docker](https://github.com/vegasbrianc/github-monitoring) stack for running prometheus and grafana. 57 | 58 | This setup is not secured in any way, so please only use on non-public networks, or find a way to secure it on your own. 59 | -------------------------------------------------------------------------------- /internet-monitoring/blackbox/config/blackbox.yml: -------------------------------------------------------------------------------- 1 | --- 2 | modules: 3 | http_2xx: 4 | prober: http 5 | http: 6 | preferred_ip_protocol: "ip4" 7 | http_post_2xx: 8 | prober: http 9 | http: 10 | method: POST 11 | tcp_connect: 12 | prober: tcp 13 | pop3s_banner: 14 | prober: tcp 15 | tcp: 16 | query_response: 17 | - expect: "^+OK" 18 | tls: true 19 | tls_config: 20 | insecure_skip_verify: false 21 | ssh_banner: 22 | prober: tcp 23 | tcp: 24 | query_response: 25 | - expect: "^SSH-2.0-" 26 | irc_banner: 27 | prober: tcp 28 | tcp: 29 | query_response: 30 | - send: "NICK prober" 31 | - send: "USER prober prober prober :prober" 32 | - expect: "PING :([^ ]+)" 33 | send: "PONG ${1}" 34 | - expect: "^:[^ ]+ 001" 35 | icmp: 36 | prober: icmp 37 | -------------------------------------------------------------------------------- /internet-monitoring/grafana/provisioning/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: 1 3 | 4 | providers: 5 | - name: 'prometheus' 6 | orgId: 1 7 | folder: '' 8 | type: file 9 | disableDeletion: false 10 | editable: true 11 | allowUiUpdates: true 12 | options: 13 | path: /etc/grafana/provisioning/dashboards 14 | -------------------------------------------------------------------------------- /internet-monitoring/grafana/provisioning/dashboards/internet-connection.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "datasource", 8 | "uid": "grafana" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "limit": 100, 14 | "name": "Annotations & Alerts", 15 | "showIn": 0, 16 | "type": "dashboard" 17 | } 18 | ] 19 | }, 20 | "editable": true, 21 | "fiscalYearStartMonth": 0, 22 | "graphTooltip": 0, 23 | "id": 1, 24 | "links": [], 25 | "panels": [ 26 | { 27 | "datasource": "prometheus", 28 | "fieldConfig": { 29 | "defaults": { 30 | "color": { 31 | "mode": "thresholds" 32 | }, 33 | "mappings": [], 34 | "thresholds": { 35 | "mode": "absolute", 36 | "steps": [ 37 | { 38 | "color": "#299c46", 39 | "value": null 40 | }, 41 | { 42 | "color": "blue", 43 | "value": 100 44 | } 45 | ] 46 | }, 47 | "unit": "bps" 48 | }, 49 | "overrides": [] 50 | }, 51 | "gridPos": { 52 | "h": 9, 53 | "w": 6, 54 | "x": 0, 55 | "y": 0 56 | }, 57 | "id": 8, 58 | "maxDataPoints": 10000, 59 | "options": { 60 | "minVizHeight": 75, 61 | "minVizWidth": 75, 62 | "orientation": "horizontal", 63 | "reduceOptions": { 64 | "calcs": [ 65 | "mean" 66 | ], 67 | "fields": "", 68 | "values": false 69 | }, 70 | "showThresholdLabels": false, 71 | "showThresholdMarkers": true, 72 | "sizing": "auto", 73 | "text": {} 74 | }, 75 | "pluginVersion": "10.4.1", 76 | "targets": [ 77 | { 78 | "datasource": "prometheus", 79 | "expr": "speedtest_download_bits_per_second{}", 80 | "format": "time_series", 81 | "interval": "", 82 | "intervalFactor": 1, 83 | "legendFormat": "", 84 | "refId": "A" 85 | } 86 | ], 87 | "title": "Download", 88 | "type": "gauge" 89 | }, 90 | { 91 | "datasource": "prometheus", 92 | "fieldConfig": { 93 | "defaults": { 94 | "color": { 95 | "mode": "thresholds" 96 | }, 97 | "mappings": [], 98 | "thresholds": { 99 | "mode": "absolute", 100 | "steps": [ 101 | { 102 | "color": "green", 103 | "value": null 104 | }, 105 | { 106 | "color": "blue", 107 | "value": 20 108 | } 109 | ] 110 | }, 111 | "unit": "bps" 112 | }, 113 | "overrides": [] 114 | }, 115 | "gridPos": { 116 | "h": 9, 117 | "w": 6, 118 | "x": 6, 119 | "y": 0 120 | }, 121 | "id": 10, 122 | "maxDataPoints": 10000, 123 | "options": { 124 | "minVizHeight": 75, 125 | "minVizWidth": 75, 126 | "orientation": "horizontal", 127 | "reduceOptions": { 128 | "calcs": [ 129 | "mean" 130 | ], 131 | "fields": "", 132 | "values": false 133 | }, 134 | "showThresholdLabels": false, 135 | "showThresholdMarkers": true, 136 | "sizing": "auto", 137 | "text": {} 138 | }, 139 | "pluginVersion": "10.4.1", 140 | "targets": [ 141 | { 142 | "datasource": "prometheus", 143 | "expr": "speedtest_upload_bits_per_second{}", 144 | "format": "time_series", 145 | "interval": "", 146 | "intervalFactor": 1, 147 | "legendFormat": "", 148 | "refId": "A" 149 | } 150 | ], 151 | "title": "Upload", 152 | "type": "gauge" 153 | }, 154 | { 155 | "datasource": "prometheus", 156 | "fieldConfig": { 157 | "defaults": { 158 | "color": { 159 | "mode": "thresholds" 160 | }, 161 | "decimals": 2, 162 | "mappings": [], 163 | "thresholds": { 164 | "mode": "absolute", 165 | "steps": [ 166 | { 167 | "color": "green", 168 | "value": null 169 | }, 170 | { 171 | "color": "red", 172 | "value": 80 173 | } 174 | ] 175 | }, 176 | "unit": "ms" 177 | }, 178 | "overrides": [] 179 | }, 180 | "gridPos": { 181 | "h": 9, 182 | "w": 12, 183 | "x": 12, 184 | "y": 0 185 | }, 186 | "id": 12, 187 | "maxDataPoints": 10000, 188 | "options": { 189 | "minVizHeight": 75, 190 | "minVizWidth": 75, 191 | "orientation": "horizontal", 192 | "reduceOptions": { 193 | "calcs": [ 194 | "mean" 195 | ], 196 | "fields": "", 197 | "values": false 198 | }, 199 | "showThresholdLabels": false, 200 | "showThresholdMarkers": true, 201 | "sizing": "auto", 202 | "text": {} 203 | }, 204 | "pluginVersion": "10.4.1", 205 | "targets": [ 206 | { 207 | "datasource": "prometheus", 208 | "expr": "speedtest_ping_latency_milliseconds", 209 | "format": "time_series", 210 | "instant": false, 211 | "interval": "", 212 | "intervalFactor": 1, 213 | "legendFormat": "", 214 | "refId": "A" 215 | } 216 | ], 217 | "title": "Speedtest Ping", 218 | "type": "gauge" 219 | }, 220 | { 221 | "datasource": "prometheus", 222 | "fieldConfig": { 223 | "defaults": { 224 | "color": { 225 | "mode": "palette-classic" 226 | }, 227 | "custom": { 228 | "axisBorderShow": false, 229 | "axisCenteredZero": false, 230 | "axisColorMode": "text", 231 | "axisLabel": "", 232 | "axisPlacement": "auto", 233 | "barAlignment": 0, 234 | "drawStyle": "line", 235 | "fillOpacity": 50, 236 | "gradientMode": "none", 237 | "hideFrom": { 238 | "graph": false, 239 | "legend": false, 240 | "tooltip": false, 241 | "viz": false 242 | }, 243 | "insertNulls": false, 244 | "lineInterpolation": "linear", 245 | "lineWidth": 0, 246 | "pointSize": 5, 247 | "scaleDistribution": { 248 | "type": "linear" 249 | }, 250 | "showPoints": "never", 251 | "spanNulls": true, 252 | "stacking": { 253 | "group": "A", 254 | "mode": "none" 255 | }, 256 | "thresholdsStyle": { 257 | "mode": "off" 258 | } 259 | }, 260 | "mappings": [], 261 | "thresholds": { 262 | "mode": "absolute", 263 | "steps": [ 264 | { 265 | "color": "green", 266 | "value": null 267 | }, 268 | { 269 | "color": "red", 270 | "value": 80 271 | } 272 | ] 273 | }, 274 | "unit": "bps" 275 | }, 276 | "overrides": [] 277 | }, 278 | "gridPos": { 279 | "h": 9, 280 | "w": 12, 281 | "x": 0, 282 | "y": 9 283 | }, 284 | "id": 6, 285 | "maxDataPoints": 10000, 286 | "options": { 287 | "graph": {}, 288 | "legend": { 289 | "calcs": [], 290 | "displayMode": "list", 291 | "placement": "right", 292 | "showLegend": true 293 | }, 294 | "tooltip": { 295 | "mode": "multi", 296 | "sort": "none" 297 | } 298 | }, 299 | "pluginVersion": "7.4.5", 300 | "targets": [ 301 | { 302 | "datasource": "prometheus", 303 | "expr": "speedtest_download_bits_per_second{}", 304 | "format": "time_series", 305 | "interval": "", 306 | "intervalFactor": 1, 307 | "legendFormat": "Download", 308 | "refId": "A" 309 | }, 310 | { 311 | "datasource": "prometheus", 312 | "expr": "speedtest_upload_bits_per_second{}", 313 | "hide": false, 314 | "interval": "", 315 | "legendFormat": "Upload", 316 | "refId": "B" 317 | } 318 | ], 319 | "title": "Speedtest Graph", 320 | "type": "timeseries" 321 | }, 322 | { 323 | "datasource": "prometheus", 324 | "fieldConfig": { 325 | "defaults": { 326 | "color": { 327 | "mode": "continuous-GrYlRd" 328 | }, 329 | "custom": { 330 | "fillOpacity": 70, 331 | "hideFrom": { 332 | "legend": false, 333 | "tooltip": false, 334 | "viz": false 335 | }, 336 | "insertNulls": false, 337 | "lineWidth": 0, 338 | "spanNulls": false 339 | }, 340 | "fieldMinMax": false, 341 | "mappings": [ 342 | { 343 | "options": { 344 | "0": { 345 | "color": "semi-dark-red", 346 | "index": 0, 347 | "text": "Down" 348 | }, 349 | "1": { 350 | "color": "green", 351 | "index": 1, 352 | "text": "Up" 353 | } 354 | }, 355 | "type": "value" 356 | } 357 | ], 358 | "thresholds": { 359 | "mode": "absolute", 360 | "steps": [ 361 | { 362 | "color": "green", 363 | "value": null 364 | } 365 | ] 366 | } 367 | }, 368 | "overrides": [] 369 | }, 370 | "gridPos": { 371 | "h": 9, 372 | "w": 12, 373 | "x": 12, 374 | "y": 9 375 | }, 376 | "id": 21, 377 | "options": { 378 | "alignValue": "left", 379 | "legend": { 380 | "displayMode": "list", 381 | "placement": "bottom", 382 | "showLegend": true 383 | }, 384 | "mergeValues": true, 385 | "rowHeight": 0.9, 386 | "showValue": "auto", 387 | "tooltip": { 388 | "mode": "single", 389 | "sort": "none" 390 | } 391 | }, 392 | "targets": [ 393 | { 394 | "datasource": "prometheus", 395 | "expr": "probe_success", 396 | "format": "time_series", 397 | "interval": "", 398 | "intervalFactor": 1, 399 | "legendFormat": "{{instance}}", 400 | "refId": "A" 401 | } 402 | ], 403 | "title": "Uptime", 404 | "type": "state-timeline" 405 | }, 406 | { 407 | "aliasColors": {}, 408 | "bars": false, 409 | "dashLength": 10, 410 | "dashes": false, 411 | "datasource": "prometheus", 412 | "fieldConfig": { 413 | "defaults": { 414 | "color": { 415 | "mode": "palette-classic" 416 | }, 417 | "custom": { 418 | "axisBorderShow": false, 419 | "axisCenteredZero": false, 420 | "axisColorMode": "text", 421 | "axisLabel": "", 422 | "axisPlacement": "auto", 423 | "axisSoftMax": 5, 424 | "axisSoftMin": 0, 425 | "barAlignment": 0, 426 | "drawStyle": "line", 427 | "fillOpacity": 10, 428 | "gradientMode": "none", 429 | "hideFrom": { 430 | "legend": false, 431 | "tooltip": false, 432 | "viz": false 433 | }, 434 | "insertNulls": false, 435 | "lineInterpolation": "linear", 436 | "lineWidth": 1, 437 | "pointSize": 5, 438 | "scaleDistribution": { 439 | "type": "linear" 440 | }, 441 | "showPoints": "never", 442 | "spanNulls": false, 443 | "stacking": { 444 | "group": "A", 445 | "mode": "none" 446 | }, 447 | "thresholdsStyle": { 448 | "mode": "off" 449 | } 450 | }, 451 | "mappings": [], 452 | "thresholds": { 453 | "mode": "absolute", 454 | "steps": [ 455 | { 456 | "color": "green", 457 | "value": null 458 | }, 459 | { 460 | "color": "red", 461 | "value": 80 462 | } 463 | ] 464 | }, 465 | "unit": "s" 466 | }, 467 | "overrides": [] 468 | }, 469 | "fill": 1, 470 | "fillGradient": 0, 471 | "gridPos": { 472 | "h": 9, 473 | "w": 24, 474 | "x": 0, 475 | "y": 18 476 | }, 477 | "id": 4, 478 | "options": { 479 | "legend": { 480 | "calcs": [], 481 | "displayMode": "list", 482 | "placement": "bottom", 483 | "showLegend": true 484 | }, 485 | "tooltip": { 486 | "mode": "multi", 487 | "sort": "none" 488 | } 489 | }, 490 | "pluginVersion": "9.3.2", 491 | "repeat": "host", 492 | "repeatDirection": "h", 493 | "targets": [ 494 | { 495 | "datasource": "prometheus", 496 | "expr": "sum(probe_http_duration_seconds) by (instance)", 497 | "format": "time_series", 498 | "intervalFactor": 1, 499 | "legendFormat": "{{instance}}", 500 | "refId": "A" 501 | } 502 | ], 503 | "title": "HTTP Request Duration", 504 | "type": "timeseries" 505 | }, 506 | { 507 | "datasource": "prometheus", 508 | "fieldConfig": { 509 | "defaults": { 510 | "color": { 511 | "mode": "palette-classic" 512 | }, 513 | "custom": { 514 | "axisBorderShow": false, 515 | "axisCenteredZero": false, 516 | "axisColorMode": "text", 517 | "axisLabel": "", 518 | "axisPlacement": "auto", 519 | "axisSoftMin": 0, 520 | "barAlignment": 0, 521 | "drawStyle": "line", 522 | "fillOpacity": 10, 523 | "gradientMode": "none", 524 | "hideFrom": { 525 | "legend": false, 526 | "tooltip": false, 527 | "viz": false 528 | }, 529 | "insertNulls": false, 530 | "lineInterpolation": "linear", 531 | "lineWidth": 1, 532 | "pointSize": 5, 533 | "scaleDistribution": { 534 | "type": "linear" 535 | }, 536 | "showPoints": "never", 537 | "spanNulls": true, 538 | "stacking": { 539 | "group": "A", 540 | "mode": "none" 541 | }, 542 | "thresholdsStyle": { 543 | "mode": "off" 544 | } 545 | }, 546 | "mappings": [], 547 | "thresholds": { 548 | "mode": "absolute", 549 | "steps": [ 550 | { 551 | "color": "green", 552 | "value": null 553 | }, 554 | { 555 | "color": "red", 556 | "value": 80 557 | } 558 | ] 559 | }, 560 | "unit": "ms" 561 | }, 562 | "overrides": [] 563 | }, 564 | "gridPos": { 565 | "h": 9, 566 | "w": 24, 567 | "x": 0, 568 | "y": 27 569 | }, 570 | "id": 20, 571 | "options": { 572 | "legend": { 573 | "calcs": [], 574 | "displayMode": "list", 575 | "placement": "bottom", 576 | "showLegend": true 577 | }, 578 | "tooltip": { 579 | "mode": "multi", 580 | "sort": "none" 581 | } 582 | }, 583 | "pluginVersion": "9.3.2", 584 | "repeatDirection": "h", 585 | "targets": [ 586 | { 587 | "datasource": "prometheus", 588 | "editorMode": "code", 589 | "expr": "speedtest_jitter_latency_milliseconds{}\n", 590 | "format": "time_series", 591 | "intervalFactor": 1, 592 | "legendFormat": "{{instance}}", 593 | "range": true, 594 | "refId": "A" 595 | } 596 | ], 597 | "title": "Speedtest Jitter", 598 | "type": "timeseries" 599 | }, 600 | { 601 | "datasource": "prometheus", 602 | "fieldConfig": { 603 | "defaults": { 604 | "color": { 605 | "mode": "palette-classic" 606 | }, 607 | "custom": { 608 | "axisBorderShow": false, 609 | "axisCenteredZero": false, 610 | "axisColorMode": "text", 611 | "axisLabel": "", 612 | "axisPlacement": "auto", 613 | "axisSoftMax": 5, 614 | "axisSoftMin": 0, 615 | "barAlignment": 0, 616 | "drawStyle": "line", 617 | "fillOpacity": 10, 618 | "gradientMode": "none", 619 | "hideFrom": { 620 | "legend": false, 621 | "tooltip": false, 622 | "viz": false 623 | }, 624 | "insertNulls": false, 625 | "lineInterpolation": "linear", 626 | "lineWidth": 1, 627 | "pointSize": 5, 628 | "scaleDistribution": { 629 | "type": "linear" 630 | }, 631 | "showPoints": "never", 632 | "spanNulls": false, 633 | "stacking": { 634 | "group": "A", 635 | "mode": "none" 636 | }, 637 | "thresholdsStyle": { 638 | "mode": "off" 639 | } 640 | }, 641 | "mappings": [], 642 | "thresholds": { 643 | "mode": "absolute", 644 | "steps": [ 645 | { 646 | "color": "green" 647 | }, 648 | { 649 | "color": "red", 650 | "value": 80 651 | } 652 | ] 653 | }, 654 | "unit": "s" 655 | }, 656 | "overrides": [] 657 | }, 658 | "gridPos": { 659 | "h": 9, 660 | "w": 24, 661 | "x": 0, 662 | "y": 36 663 | }, 664 | "id": 18, 665 | "options": { 666 | "legend": { 667 | "calcs": [], 668 | "displayMode": "list", 669 | "placement": "bottom", 670 | "showLegend": true 671 | }, 672 | "tooltip": { 673 | "mode": "multi", 674 | "sort": "none" 675 | } 676 | }, 677 | "pluginVersion": "9.3.2", 678 | "repeatDirection": "h", 679 | "targets": [ 680 | { 681 | "datasource": "prometheus", 682 | "editorMode": "code", 683 | "expr": "sum(probe_http_duration_seconds{phase=\"connect\"}) by (instance)", 684 | "format": "time_series", 685 | "intervalFactor": 1, 686 | "legendFormat": "{{instance}}", 687 | "range": true, 688 | "refId": "A" 689 | } 690 | ], 691 | "title": "HTTP Request Duration - Connect", 692 | "type": "timeseries" 693 | }, 694 | { 695 | "datasource": "prometheus", 696 | "fieldConfig": { 697 | "defaults": { 698 | "color": { 699 | "mode": "palette-classic" 700 | }, 701 | "custom": { 702 | "axisBorderShow": false, 703 | "axisCenteredZero": false, 704 | "axisColorMode": "text", 705 | "axisLabel": "", 706 | "axisPlacement": "auto", 707 | "axisSoftMax": 5, 708 | "axisSoftMin": 0, 709 | "barAlignment": 0, 710 | "drawStyle": "line", 711 | "fillOpacity": 10, 712 | "gradientMode": "none", 713 | "hideFrom": { 714 | "legend": false, 715 | "tooltip": false, 716 | "viz": false 717 | }, 718 | "insertNulls": false, 719 | "lineInterpolation": "linear", 720 | "lineWidth": 1, 721 | "pointSize": 5, 722 | "scaleDistribution": { 723 | "type": "linear" 724 | }, 725 | "showPoints": "never", 726 | "spanNulls": false, 727 | "stacking": { 728 | "group": "A", 729 | "mode": "none" 730 | }, 731 | "thresholdsStyle": { 732 | "mode": "off" 733 | } 734 | }, 735 | "mappings": [], 736 | "thresholds": { 737 | "mode": "absolute", 738 | "steps": [ 739 | { 740 | "color": "green" 741 | }, 742 | { 743 | "color": "red", 744 | "value": 80 745 | } 746 | ] 747 | }, 748 | "unit": "s" 749 | }, 750 | "overrides": [] 751 | }, 752 | "gridPos": { 753 | "h": 9, 754 | "w": 24, 755 | "x": 0, 756 | "y": 45 757 | }, 758 | "id": 17, 759 | "options": { 760 | "legend": { 761 | "calcs": [], 762 | "displayMode": "list", 763 | "placement": "bottom", 764 | "showLegend": true 765 | }, 766 | "tooltip": { 767 | "mode": "multi", 768 | "sort": "none" 769 | } 770 | }, 771 | "pluginVersion": "9.3.2", 772 | "repeatDirection": "h", 773 | "targets": [ 774 | { 775 | "datasource": "prometheus", 776 | "editorMode": "code", 777 | "expr": "sum(probe_http_duration_seconds{phase=\"processing\"}) by (instance)", 778 | "format": "time_series", 779 | "intervalFactor": 1, 780 | "legendFormat": "{{instance}}", 781 | "range": true, 782 | "refId": "A" 783 | } 784 | ], 785 | "title": "HTTP Request Duration - Processing", 786 | "type": "timeseries" 787 | }, 788 | { 789 | "datasource": "prometheus", 790 | "fieldConfig": { 791 | "defaults": { 792 | "color": { 793 | "mode": "palette-classic" 794 | }, 795 | "custom": { 796 | "axisBorderShow": false, 797 | "axisCenteredZero": false, 798 | "axisColorMode": "text", 799 | "axisLabel": "", 800 | "axisPlacement": "auto", 801 | "axisSoftMax": 5, 802 | "axisSoftMin": 0, 803 | "barAlignment": 0, 804 | "drawStyle": "line", 805 | "fillOpacity": 10, 806 | "gradientMode": "none", 807 | "hideFrom": { 808 | "legend": false, 809 | "tooltip": false, 810 | "viz": false 811 | }, 812 | "insertNulls": false, 813 | "lineInterpolation": "linear", 814 | "lineWidth": 1, 815 | "pointSize": 5, 816 | "scaleDistribution": { 817 | "type": "linear" 818 | }, 819 | "showPoints": "never", 820 | "spanNulls": false, 821 | "stacking": { 822 | "group": "A", 823 | "mode": "none" 824 | }, 825 | "thresholdsStyle": { 826 | "mode": "off" 827 | } 828 | }, 829 | "mappings": [], 830 | "thresholds": { 831 | "mode": "absolute", 832 | "steps": [ 833 | { 834 | "color": "green" 835 | }, 836 | { 837 | "color": "red", 838 | "value": 80 839 | } 840 | ] 841 | }, 842 | "unit": "s" 843 | }, 844 | "overrides": [] 845 | }, 846 | "gridPos": { 847 | "h": 9, 848 | "w": 24, 849 | "x": 0, 850 | "y": 54 851 | }, 852 | "id": 16, 853 | "options": { 854 | "legend": { 855 | "calcs": [], 856 | "displayMode": "list", 857 | "placement": "bottom", 858 | "showLegend": true 859 | }, 860 | "tooltip": { 861 | "mode": "multi", 862 | "sort": "none" 863 | } 864 | }, 865 | "pluginVersion": "9.3.2", 866 | "repeatDirection": "h", 867 | "targets": [ 868 | { 869 | "datasource": "prometheus", 870 | "editorMode": "code", 871 | "expr": "sum(probe_http_duration_seconds{phase=\"resolve\"}) by (instance)", 872 | "format": "time_series", 873 | "intervalFactor": 1, 874 | "legendFormat": "{{instance}}", 875 | "range": true, 876 | "refId": "A" 877 | } 878 | ], 879 | "title": "HTTP Request Duration - Resolve", 880 | "type": "timeseries" 881 | }, 882 | { 883 | "datasource": "prometheus", 884 | "fieldConfig": { 885 | "defaults": { 886 | "color": { 887 | "mode": "palette-classic" 888 | }, 889 | "custom": { 890 | "axisBorderShow": false, 891 | "axisCenteredZero": false, 892 | "axisColorMode": "text", 893 | "axisLabel": "", 894 | "axisPlacement": "auto", 895 | "axisSoftMax": 5, 896 | "axisSoftMin": 0, 897 | "barAlignment": 0, 898 | "drawStyle": "line", 899 | "fillOpacity": 10, 900 | "gradientMode": "none", 901 | "hideFrom": { 902 | "legend": false, 903 | "tooltip": false, 904 | "viz": false 905 | }, 906 | "insertNulls": false, 907 | "lineInterpolation": "linear", 908 | "lineWidth": 1, 909 | "pointSize": 5, 910 | "scaleDistribution": { 911 | "type": "linear" 912 | }, 913 | "showPoints": "never", 914 | "spanNulls": false, 915 | "stacking": { 916 | "group": "A", 917 | "mode": "none" 918 | }, 919 | "thresholdsStyle": { 920 | "mode": "off" 921 | } 922 | }, 923 | "mappings": [], 924 | "thresholds": { 925 | "mode": "absolute", 926 | "steps": [ 927 | { 928 | "color": "green" 929 | }, 930 | { 931 | "color": "red", 932 | "value": 80 933 | } 934 | ] 935 | }, 936 | "unit": "s" 937 | }, 938 | "overrides": [] 939 | }, 940 | "gridPos": { 941 | "h": 9, 942 | "w": 24, 943 | "x": 0, 944 | "y": 63 945 | }, 946 | "id": 15, 947 | "options": { 948 | "legend": { 949 | "calcs": [], 950 | "displayMode": "list", 951 | "placement": "bottom", 952 | "showLegend": true 953 | }, 954 | "tooltip": { 955 | "mode": "multi", 956 | "sort": "none" 957 | } 958 | }, 959 | "pluginVersion": "9.3.2", 960 | "repeatDirection": "h", 961 | "targets": [ 962 | { 963 | "datasource": "prometheus", 964 | "editorMode": "code", 965 | "expr": "sum(probe_http_duration_seconds{phase=\"tls\"}) by (instance)", 966 | "format": "time_series", 967 | "intervalFactor": 1, 968 | "legendFormat": "{{instance}}", 969 | "range": true, 970 | "refId": "A" 971 | } 972 | ], 973 | "title": "HTTP Request Duration - TLS", 974 | "type": "timeseries" 975 | }, 976 | { 977 | "datasource": "prometheus", 978 | "fieldConfig": { 979 | "defaults": { 980 | "color": { 981 | "mode": "palette-classic" 982 | }, 983 | "custom": { 984 | "axisBorderShow": false, 985 | "axisCenteredZero": false, 986 | "axisColorMode": "text", 987 | "axisLabel": "", 988 | "axisPlacement": "auto", 989 | "axisSoftMax": 5, 990 | "axisSoftMin": 0, 991 | "barAlignment": 0, 992 | "drawStyle": "line", 993 | "fillOpacity": 10, 994 | "gradientMode": "none", 995 | "hideFrom": { 996 | "legend": false, 997 | "tooltip": false, 998 | "viz": false 999 | }, 1000 | "insertNulls": false, 1001 | "lineInterpolation": "linear", 1002 | "lineWidth": 1, 1003 | "pointSize": 5, 1004 | "scaleDistribution": { 1005 | "type": "linear" 1006 | }, 1007 | "showPoints": "never", 1008 | "spanNulls": false, 1009 | "stacking": { 1010 | "group": "A", 1011 | "mode": "none" 1012 | }, 1013 | "thresholdsStyle": { 1014 | "mode": "off" 1015 | } 1016 | }, 1017 | "mappings": [], 1018 | "thresholds": { 1019 | "mode": "absolute", 1020 | "steps": [ 1021 | { 1022 | "color": "green" 1023 | }, 1024 | { 1025 | "color": "red", 1026 | "value": 80 1027 | } 1028 | ] 1029 | }, 1030 | "unit": "s" 1031 | }, 1032 | "overrides": [] 1033 | }, 1034 | "gridPos": { 1035 | "h": 9, 1036 | "w": 24, 1037 | "x": 0, 1038 | "y": 72 1039 | }, 1040 | "id": 19, 1041 | "options": { 1042 | "legend": { 1043 | "calcs": [], 1044 | "displayMode": "list", 1045 | "placement": "bottom", 1046 | "showLegend": true 1047 | }, 1048 | "tooltip": { 1049 | "mode": "multi", 1050 | "sort": "none" 1051 | } 1052 | }, 1053 | "pluginVersion": "9.3.2", 1054 | "repeatDirection": "h", 1055 | "targets": [ 1056 | { 1057 | "datasource": "prometheus", 1058 | "editorMode": "code", 1059 | "expr": "sum(probe_http_duration_seconds{phase=\"transfer\"}) by (instance)", 1060 | "format": "time_series", 1061 | "intervalFactor": 1, 1062 | "legendFormat": "{{instance}}", 1063 | "range": true, 1064 | "refId": "A" 1065 | } 1066 | ], 1067 | "title": "HTTP Request Duration - Transfer", 1068 | "type": "timeseries" 1069 | } 1070 | ], 1071 | "refresh": "", 1072 | "schemaVersion": 39, 1073 | "tags": [ 1074 | "speedtest", 1075 | "ping" 1076 | ], 1077 | "templating": { 1078 | "list": [] 1079 | }, 1080 | "time": { 1081 | "from": "now-7d", 1082 | "to": "now" 1083 | }, 1084 | "timepicker": { 1085 | "refresh_intervals": [ 1086 | "5s", 1087 | "10s", 1088 | "30s", 1089 | "1m", 1090 | "5m", 1091 | "15m", 1092 | "30m", 1093 | "1h", 1094 | "2h", 1095 | "1d" 1096 | ], 1097 | "time_options": [ 1098 | "5m", 1099 | "15m", 1100 | "1h", 1101 | "6h", 1102 | "12h", 1103 | "24h", 1104 | "2d", 1105 | "7d", 1106 | "30d" 1107 | ] 1108 | }, 1109 | "timezone": "browser", 1110 | "title": "Internet connection", 1111 | "uid": "o9mIe_Aik", 1112 | "version": 10, 1113 | "weekStart": "" 1114 | } -------------------------------------------------------------------------------- /internet-monitoring/grafana/provisioning/datasources/datasource.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: 1 3 | 4 | # list of datasources that should be deleted from the database 5 | deleteDatasources: 6 | - name: prometheus 7 | orgId: 1 8 | 9 | # list of datasources to insert/update depending 10 | # whats available in the database 11 | datasources: 12 | - name: prometheus 13 | type: prometheus 14 | access: proxy 15 | orgId: 1 16 | url: http://prometheus:9090 17 | password: 18 | user: 19 | database: 20 | basicAuth: true 21 | basicAuthUser: admin 22 | basicAuthPassword: foobar 23 | withCredentials: 24 | isDefault: 25 | jsonData: 26 | graphiteVersion: "1.1" 27 | tlsAuth: false 28 | tlsAuthWithCACert: false 29 | secureJsonData: 30 | tlsCACert: "..." 31 | tlsClientCert: "..." 32 | tlsClientKey: "..." 33 | version: 1 34 | editable: true 35 | -------------------------------------------------------------------------------- /internet-monitoring/prometheus/alert.rules: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: example 3 | rules: 4 | 5 | # Alert for any instance that is unreachable for >5 minutes. 6 | - alert: service_down 7 | expr: up == 0 8 | for: 2m 9 | labels: 10 | severity: page 11 | annotations: 12 | summary: "Instance {{ $labels.instance }} down" 13 | description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 2 minutes." 14 | 15 | - alert: high_load 16 | expr: node_load1 > 0.5 17 | for: 2m 18 | labels: 19 | severity: page 20 | annotations: 21 | summary: "Instance {{ $labels.instance }} under high load" 22 | description: "{{ $labels.instance }} of job {{ $labels.job }} is under high load." 23 | -------------------------------------------------------------------------------- /main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Configure Internet Pi. 3 | hosts: internet_pi 4 | become: true 5 | 6 | pre_tasks: 7 | - name: Load configuration (with defaults from example file). 8 | ansible.builtin.include_vars: "{{ item }}" 9 | loop: 10 | - example.config.yml 11 | - config.yml 12 | 13 | - name: Ensure apt cache is up to date. 14 | ansible.builtin.apt: 15 | update_cache: true 16 | cache_valid_time: 3600 17 | when: 18 | - ansible_facts.os_family == "Debian" 19 | 20 | - name: Ensure pacman cache is up to date 21 | community.general.pacman: 22 | update_cache: true 23 | when: 24 | - ansible_facts.os_family == "Archlinux" 25 | 26 | handlers: 27 | - name: Include handlers. 28 | ansible.builtin.import_tasks: tasks/handlers.yml 29 | 30 | tasks: 31 | - name: Setup Docker. 32 | ansible.builtin.import_tasks: tasks/docker.yml 33 | 34 | - name: Set up Internet Monitoring. 35 | ansible.builtin.import_tasks: tasks/internet-monitoring.yml 36 | when: monitoring_enable 37 | 38 | - name: Set up Pi Hole. 39 | ansible.builtin.import_tasks: tasks/pi-hole.yml 40 | when: pihole_enable 41 | 42 | - name: Set up Shelly Plug Monitoring. 43 | ansible.builtin.import_tasks: tasks/shelly-plug.yml 44 | when: shelly_plug_enable 45 | 46 | - name: Set up Air Gradient Monitoring. 47 | ansible.builtin.import_tasks: tasks/airgradient.yml 48 | when: airgradient_enable 49 | 50 | - name: Set up Starlink Monitoring. 51 | ansible.builtin.import_tasks: tasks/starlink.yml 52 | when: starlink_enable 53 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | collections: 3 | - ansible.posix 4 | - community.docker 5 | - community.general 6 | -------------------------------------------------------------------------------- /tasks/airgradient.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Copy airgradient dashboard configs to Grafana. 3 | ansible.builtin.template: 4 | src: templates/airgradient-air-quality.json.j2 5 | dest: "{{ config_dir }}/internet-monitoring/grafana/provisioning/dashboards/airgradient-{{ item.id }}.json" 6 | mode: 0644 7 | become: false 8 | loop: "{{ airgradient_sensors }}" 9 | notify: Restart internet-monitoring 10 | -------------------------------------------------------------------------------- /tasks/debian-libseccomp-update.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add Buster backports apt key. 3 | ansible.builtin.apt_key: 4 | keyserver: hkp://keyserver.ubuntu.com:80 5 | id: '{{ item }}' 6 | loop: 7 | - 04EE7237B7D453EC 8 | - 648ACFD622F3D138 9 | - 0E98404D386FA1D9 10 | - 6ED0E7B82643E131 11 | 12 | - name: Add Buster backports for fixed libseccomp2. 13 | ansible.builtin.apt_repository: 14 | repo: deb http://httpredir.debian.org/debian buster-backports main contrib non-free 15 | state: present 16 | filename: debian-backports 17 | 18 | - name: Install >libseccomp2.4.4 to fix 32-bit OS issue. 19 | ansible.builtin.apt: 20 | name: libseccomp2 21 | state: latest # noqa package-latest 22 | default_release: buster-backports 23 | -------------------------------------------------------------------------------- /tasks/docker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check if Docker is already present. 3 | ansible.builtin.command: which docker 4 | failed_when: false 5 | changed_when: false 6 | check_mode: false 7 | register: docker_command_result 8 | 9 | - name: Download Docker install convenience script. 10 | ansible.builtin.get_url: 11 | url: https://get.docker.com/ 12 | dest: /tmp/get-docker.sh 13 | mode: 0775 14 | when: docker_command_result.rc == 1 15 | 16 | - name: Run Docker install convenience script. 17 | ansible.builtin.command: /tmp/get-docker.sh 18 | environment: 19 | CHANNEL: stable 20 | when: docker_command_result.rc == 1 21 | 22 | - name: Ensure Docker is started. 23 | ansible.builtin.service: 24 | name: docker 25 | state: started 26 | enabled: true 27 | 28 | - name: Ensure dependencies are installed (Debian). 29 | ansible.builtin.apt: 30 | name: 31 | - libffi-dev 32 | - libssl-dev 33 | - python3-dev 34 | - python3-pip 35 | - git 36 | - rsync 37 | - docker-compose-plugin 38 | - resolvconf 39 | state: present 40 | when: ansible_facts.os_family == "Debian" 41 | 42 | - name: Ensure dependencies are installed (Archlinux). 43 | community.general.pacman: 44 | name: 45 | - libffi 46 | - openssl 47 | - base-devel 48 | - python-pip 49 | - git 50 | - rsync 51 | - docker-compose-plugin 52 | state: present 53 | when: ansible_facts.os_family == "Archlinux" 54 | 55 | - name: "Ensure user is added to the docker group: {{ ansible_user }}" 56 | ansible.builtin.user: 57 | name: "{{ ansible_user }}" 58 | groups: docker 59 | append: true 60 | 61 | # reset_connection doesn't support conditionals. 62 | - name: Reset connection so docker group is picked up. 63 | meta: reset_connection 64 | -------------------------------------------------------------------------------- /tasks/handlers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart pi-hole 3 | community.docker.docker_compose_v2: 4 | project_src: "{{ config_dir }}/pi-hole/" 5 | build: never 6 | state: restarted 7 | become: false 8 | 9 | - name: Restart internet-monitoring 10 | community.docker.docker_compose_v2: 11 | project_src: "{{ config_dir }}/internet-monitoring/" 12 | build: never 13 | state: restarted 14 | become: false 15 | 16 | - name: Restart shelly-plug-prometheus 17 | community.docker.docker_compose_v2: 18 | project_src: "{{ config_dir }}/shelly-plug-prometheus/" 19 | build: never 20 | state: restarted 21 | become: false 22 | 23 | - name: Restart starlink-exporter 24 | community.docker.docker_compose_v2: 25 | project_src: "{{ config_dir }}/starlink-exporter/" 26 | build: never 27 | state: restarted 28 | become: false 29 | -------------------------------------------------------------------------------- /tasks/internet-monitoring.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Gather package facts. 3 | ansible.builtin.package_facts: 4 | manager: auto 5 | when: ansible_facts.userspace_bits == '32' 6 | 7 | - name: Upgrade libseccomp2 to latest version (32-bit Debian). 8 | ansible.builtin.import_tasks: tasks/debian-libseccomp-update.yml 9 | when: 10 | - ansible_facts.os_family == "Debian" 11 | - ansible_facts.userspace_bits == '32' 12 | - ansible_facts.packages['libseccomp2'][0]['version'] is version('2.4.4', '<') 13 | 14 | - name: Synchronize internet-monitoring directory. 15 | ansible.posix.synchronize: 16 | src: internet-monitoring 17 | dest: "{{ config_dir }}/" 18 | delete: false 19 | recursive: true 20 | perms: false 21 | become: false 22 | 23 | # This is an upgrade task from 1.x to 2.x - can be removed for 3.x. 24 | - name: Ensure internet-monitoring directory is not a Git repository. 25 | ansible.builtin.file: 26 | path: "{{ config_dir }}/internet-monitoring/.git/" 27 | state: absent 28 | become: false 29 | 30 | - name: Copy templated internet-monitoring files into place. 31 | ansible.builtin.template: 32 | src: templates/{{ item.src }} 33 | dest: "{{ config_dir }}/internet-monitoring/{{ item.dest }}" 34 | mode: 0644 35 | loop: 36 | - src: docker-compose.yml.j2 37 | dest: docker-compose.yml 38 | - src: grafana-config.monitoring.j2 39 | dest: grafana/config.monitoring 40 | - src: prometheus.yml.j2 41 | dest: prometheus/prometheus.yml 42 | - src: prometheus-pinghosts.yaml.j2 43 | dest: prometheus/pinghosts.yaml 44 | notify: Restart internet-monitoring 45 | become: false 46 | 47 | - name: Ensure internet-monitoring environment is running. 48 | community.docker.docker_compose_v2: 49 | project_src: "{{ config_dir }}/internet-monitoring/" 50 | build: never 51 | become: false 52 | -------------------------------------------------------------------------------- /tasks/pi-hole.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create Pi-hole folder on Pi. 3 | ansible.builtin.file: 4 | path: "{{ config_dir }}/pi-hole" 5 | state: directory 6 | mode: 0755 7 | become: false 8 | register: pi_hole_dir 9 | 10 | - name: Copy Pi-hole docker-compose template to Pi. 11 | ansible.builtin.template: 12 | src: templates/pi-hole-docker-compose.yml.j2 13 | dest: "{{ pi_hole_dir.path }}/docker-compose.yml" 14 | mode: '0640' 15 | become: false 16 | notify: Restart pi-hole 17 | 18 | # TODO: The first time this playbook is run, the `pi` user may not be added 19 | # to the `docker` group, so this task may fail. 20 | - name: Ensure Pi-hole is running. 21 | community.docker.docker_compose_v2: 22 | project_src: "{{ pi_hole_dir.path }}/" 23 | build: never 24 | become: false 25 | 26 | - name: Ensure resolveconf exists. 27 | ansible.builtin.file: 28 | path: "/etc/resolvconf.conf" 29 | state: touch 30 | mode: "744" 31 | 32 | - name: Update resolveconf for local name server use. 33 | ansible.builtin.lineinfile: 34 | line: "name_servers=127.0.0.1" 35 | dest: "/etc/resolvconf.conf" 36 | regexp: "^#?name_servers=" 37 | register: resolvconf_file 38 | 39 | - name: Regenerate resolvconf if file is changed. 40 | ansible.builtin.command: resolvconf -u 41 | changed_when: false 42 | when: resolvconf_file is changed 43 | -------------------------------------------------------------------------------- /tasks/shelly-plug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Clone shelly-plug-prometheus repo to Pi. 3 | ansible.builtin.git: 4 | repo: https://github.com/geerlingguy/shelly-plug-prometheus 5 | dest: "{{ config_dir }}/shelly-plug-prometheus/" 6 | version: master 7 | accept_hostkey: true 8 | become: false 9 | notify: Restart shelly-plug-prometheus 10 | 11 | - name: Copy Shelly Plug docker-compose template to Pi. 12 | ansible.builtin.template: 13 | src: templates/shelly-plug-docker-compose.yml.j2 14 | dest: "{{ config_dir }}/shelly-plug-prometheus/docker-compose.yml" 15 | mode: '0640' 16 | become: false 17 | notify: Restart shelly-plug-prometheus 18 | 19 | - name: Ensure Shelly Plug Prometheus exporter is running. 20 | community.docker.docker_compose_v2: 21 | project_src: "{{ config_dir }}/shelly-plug-prometheus/" 22 | build: never 23 | become: false 24 | 25 | - name: Copy shelly dashboard config to grafana 26 | ansible.builtin.copy: 27 | src: files/power-consumption.json 28 | dest: "{{ config_dir }}/internet-monitoring/grafana/provisioning/dashboards/" 29 | mode: 0644 30 | become: false 31 | -------------------------------------------------------------------------------- /tasks/starlink.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure Starlink directory exists. 3 | file: 4 | path: "{{ config_dir }}/starlink-exporter" 5 | state: directory 6 | mode: 0755 7 | become: false 8 | 9 | - name: Copy Starlink docker-compose template to Pi. 10 | ansible.builtin.template: 11 | src: templates/starlink-docker-compose.yml.j2 12 | dest: "{{ config_dir }}/starlink-exporter/docker-compose.yml" 13 | mode: '0640' 14 | become: false 15 | notify: Restart starlink-exporter 16 | 17 | - name: Ensure Starlink Prometheus exporter is running. 18 | community.docker.docker_compose_v2: 19 | project_src: "{{ config_dir }}/starlink-exporter/" 20 | build: never 21 | become: false 22 | 23 | - name: Copy starlink dashboard config to grafana. 24 | ansible.builtin.copy: 25 | src: files/starlink-overview.json 26 | dest: "{{ config_dir }}/internet-monitoring/grafana/provisioning/dashboards/" 27 | mode: 0644 28 | become: false 29 | -------------------------------------------------------------------------------- /templates/airgradient-air-quality.json.j2: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "links": [], 19 | "panels": [ 20 | { 21 | "datasource": "prometheus", 22 | "fieldConfig": { 23 | "defaults": { 24 | "color": { 25 | "mode": "thresholds" 26 | }, 27 | "mappings": [], 28 | "thresholds": { 29 | "mode": "absolute", 30 | "steps": [ 31 | { 32 | "color": "green", 33 | "value": null 34 | }, 35 | { 36 | "color": "#EAB839", 37 | "value": 1000 38 | }, 39 | { 40 | "color": "red", 41 | "value": 2000 42 | } 43 | ] 44 | }, 45 | "unit": "ppm" 46 | }, 47 | "overrides": [] 48 | }, 49 | "gridPos": { 50 | "h": 6, 51 | "w": 6, 52 | "x": 0, 53 | "y": 0 54 | }, 55 | "id": 5, 56 | "options": { 57 | "reduceOptions": { 58 | "calcs": [ 59 | "lastNotNull" 60 | ], 61 | "fields": "", 62 | "values": false 63 | }, 64 | "showThresholdLabels": false, 65 | "showThresholdMarkers": true, 66 | "text": {} 67 | }, 68 | "pluginVersion": "7.5.6", 69 | "targets": [ 70 | { 71 | "exemplar": true, 72 | "expr": "rco2{job=\"airgradient-{{ item.id }}\"}", 73 | "interval": "", 74 | "legendFormat": "", 75 | "queryType": "randomWalk", 76 | "refId": "A" 77 | } 78 | ], 79 | "title": "CO2 (Latest)", 80 | "type": "gauge" 81 | }, 82 | { 83 | "datasource": "prometheus", 84 | "description": "", 85 | "fieldConfig": { 86 | "defaults": { 87 | "color": { 88 | "mode": "thresholds" 89 | }, 90 | "mappings": [], 91 | "thresholds": { 92 | "mode": "absolute", 93 | "steps": [ 94 | { 95 | "color": "green", 96 | "value": null 97 | }, 98 | { 99 | "color": "#EAB839", 100 | "value": 10 101 | }, 102 | { 103 | "color": "red", 104 | "value": 20 105 | } 106 | ] 107 | }, 108 | "unit": "conμgm3" 109 | }, 110 | "overrides": [] 111 | }, 112 | "gridPos": { 113 | "h": 6, 114 | "w": 6, 115 | "x": 6, 116 | "y": 0 117 | }, 118 | "id": 6, 119 | "options": { 120 | "reduceOptions": { 121 | "calcs": [ 122 | "lastNotNull" 123 | ], 124 | "fields": "", 125 | "values": false 126 | }, 127 | "showThresholdLabels": false, 128 | "showThresholdMarkers": true, 129 | "text": {} 130 | }, 131 | "pluginVersion": "7.5.6", 132 | "targets": [ 133 | { 134 | "exemplar": true, 135 | "expr": "pm02{job=\"airgradient-{{ item.id }}\"}", 136 | "interval": "", 137 | "legendFormat": "", 138 | "queryType": "randomWalk", 139 | "refId": "A" 140 | } 141 | ], 142 | "title": "PM2.5 (Latest)", 143 | "type": "gauge" 144 | }, 145 | { 146 | "datasource": "prometheus", 147 | "description": "", 148 | "fieldConfig": { 149 | "defaults": { 150 | "color": { 151 | "mode": "thresholds" 152 | }, 153 | "mappings": [], 154 | "thresholds": { 155 | "mode": "absolute", 156 | "steps": [ 157 | { 158 | "color": "green", 159 | "value": null 160 | }, 161 | { 162 | "color": "#6ED0E0", 163 | "value": 0 164 | }, 165 | { 166 | "color": "green", 167 | "value": 28 168 | }, 169 | { 170 | "color": "#EAB839", 171 | "value": 40 172 | }, 173 | { 174 | "color": "red", 175 | "value": 50 176 | } 177 | ] 178 | }, 179 | "unit": "fahrenheit" 180 | }, 181 | "overrides": [] 182 | }, 183 | "gridPos": { 184 | "h": 5, 185 | "w": 5, 186 | "x": 12, 187 | "y": 0 188 | }, 189 | "id": 7, 190 | "options": { 191 | "colorMode": "value", 192 | "graphMode": "area", 193 | "justifyMode": "auto", 194 | "orientation": "auto", 195 | "reduceOptions": { 196 | "calcs": [ 197 | "lastNotNull" 198 | ], 199 | "fields": "", 200 | "values": false 201 | }, 202 | "text": {}, 203 | "textMode": "auto" 204 | }, 205 | "pluginVersion": "7.5.6", 206 | "targets": [ 207 | { 208 | "exemplar": true, 209 | "expr": "atmp{job=\"airgradient-{{ item.id }}\"} * 1.8 + 32", 210 | "hide": false, 211 | "interval": "", 212 | "legendFormat": "", 213 | "queryType": "randomWalk", 214 | "refId": "A" 215 | } 216 | ], 217 | "title": "Temperature", 218 | "type": "stat" 219 | }, 220 | { 221 | "datasource": "prometheus", 222 | "description": "", 223 | "fieldConfig": { 224 | "defaults": { 225 | "color": { 226 | "mode": "thresholds" 227 | }, 228 | "mappings": [], 229 | "thresholds": { 230 | "mode": "absolute", 231 | "steps": [ 232 | { 233 | "color": "green", 234 | "value": null 235 | }, 236 | { 237 | "color": "#6ED0E0", 238 | "value": 0 239 | }, 240 | { 241 | "color": "green", 242 | "value": 28 243 | }, 244 | { 245 | "color": "#EAB839", 246 | "value": 40 247 | }, 248 | { 249 | "color": "red", 250 | "value": 50 251 | } 252 | ] 253 | }, 254 | "unit": "percent" 255 | }, 256 | "overrides": [] 257 | }, 258 | "gridPos": { 259 | "h": 5, 260 | "w": 5, 261 | "x": 12, 262 | "y": 5 263 | }, 264 | "id": 8, 265 | "options": { 266 | "colorMode": "value", 267 | "graphMode": "area", 268 | "justifyMode": "auto", 269 | "orientation": "auto", 270 | "reduceOptions": { 271 | "calcs": [ 272 | "lastNotNull" 273 | ], 274 | "fields": "", 275 | "values": false 276 | }, 277 | "text": {}, 278 | "textMode": "auto" 279 | }, 280 | "pluginVersion": "7.5.6", 281 | "targets": [ 282 | { 283 | "exemplar": true, 284 | "expr": "rhum{job=\"airgradient-{{ item.id }}\"}", 285 | "interval": "", 286 | "legendFormat": "", 287 | "queryType": "randomWalk", 288 | "refId": "A" 289 | } 290 | ], 291 | "title": "Relative Humidity", 292 | "type": "stat" 293 | }, 294 | { 295 | "aliasColors": {}, 296 | "bars": false, 297 | "dashLength": 10, 298 | "dashes": false, 299 | "datasource": "prometheus", 300 | "description": "", 301 | "fieldConfig": { 302 | "defaults": { 303 | "unit": "ppm" 304 | }, 305 | "overrides": [] 306 | }, 307 | "fill": 1, 308 | "fillGradient": 0, 309 | "gridPos": { 310 | "h": 4, 311 | "w": 12, 312 | "x": 0, 313 | "y": 6 314 | }, 315 | "hiddenSeries": false, 316 | "id": 2, 317 | "legend": { 318 | "alignAsTable": false, 319 | "avg": false, 320 | "current": false, 321 | "max": false, 322 | "min": false, 323 | "rightSide": false, 324 | "show": false, 325 | "total": false, 326 | "values": false 327 | }, 328 | "lines": true, 329 | "linewidth": 1, 330 | "maxDataPoints": null, 331 | "nullPointMode": "null", 332 | "options": { 333 | "alertThreshold": true 334 | }, 335 | "percentage": false, 336 | "pluginVersion": "7.5.6", 337 | "pointradius": 2, 338 | "points": false, 339 | "renderer": "flot", 340 | "seriesOverrides": [], 341 | "spaceLength": 10, 342 | "stack": false, 343 | "steppedLine": false, 344 | "targets": [ 345 | { 346 | "exemplar": true, 347 | "expr": "rco2{job=\"airgradient-{{ item.id }}\"}", 348 | "interval": "", 349 | "legendFormat": "CO2", 350 | "queryType": "randomWalk", 351 | "refId": "A" 352 | } 353 | ], 354 | "thresholds": [ 355 | { 356 | "$$hashKey": "object:279", 357 | "colorMode": "warning", 358 | "fill": true, 359 | "line": false, 360 | "op": "gt", 361 | "value": 1000, 362 | "yaxis": "left" 363 | }, 364 | { 365 | "$$hashKey": "object:285", 366 | "colorMode": "critical", 367 | "fill": true, 368 | "line": false, 369 | "op": "gt", 370 | "value": 2000, 371 | "yaxis": "left" 372 | } 373 | ], 374 | "timeFrom": null, 375 | "timeRegions": [], 376 | "timeShift": null, 377 | "title": "CO2 Over Time", 378 | "tooltip": { 379 | "shared": true, 380 | "sort": 0, 381 | "value_type": "individual" 382 | }, 383 | "type": "graph", 384 | "xaxis": { 385 | "buckets": null, 386 | "mode": "time", 387 | "name": null, 388 | "show": true, 389 | "values": [] 390 | }, 391 | "yaxes": [ 392 | { 393 | "$$hashKey": "object:48", 394 | "format": "ppm", 395 | "label": "", 396 | "logBase": 1, 397 | "max": null, 398 | "min": null, 399 | "show": true 400 | }, 401 | { 402 | "$$hashKey": "object:49", 403 | "format": "short", 404 | "label": null, 405 | "logBase": 1, 406 | "max": null, 407 | "min": null, 408 | "show": true 409 | } 410 | ], 411 | "yaxis": { 412 | "align": false, 413 | "alignLevel": null 414 | } 415 | }, 416 | { 417 | "aliasColors": {}, 418 | "bars": false, 419 | "dashLength": 10, 420 | "dashes": false, 421 | "datasource": "prometheus", 422 | "description": "", 423 | "fieldConfig": { 424 | "defaults": {}, 425 | "overrides": [] 426 | }, 427 | "fill": 1, 428 | "fillGradient": 0, 429 | "gridPos": { 430 | "h": 4, 431 | "w": 12, 432 | "x": 0, 433 | "y": 10 434 | }, 435 | "hiddenSeries": false, 436 | "id": 3, 437 | "legend": { 438 | "alignAsTable": false, 439 | "avg": false, 440 | "current": false, 441 | "max": false, 442 | "min": false, 443 | "rightSide": false, 444 | "show": false, 445 | "total": false, 446 | "values": false 447 | }, 448 | "lines": true, 449 | "linewidth": 1, 450 | "maxDataPoints": null, 451 | "nullPointMode": "null", 452 | "options": { 453 | "alertThreshold": true 454 | }, 455 | "percentage": false, 456 | "pluginVersion": "7.5.6", 457 | "pointradius": 2, 458 | "points": false, 459 | "renderer": "flot", 460 | "seriesOverrides": [], 461 | "spaceLength": 10, 462 | "stack": false, 463 | "steppedLine": false, 464 | "targets": [ 465 | { 466 | "exemplar": true, 467 | "expr": "pm02{job=\"airgradient-{{ item.id }}\"}", 468 | "interval": "", 469 | "legendFormat": "PM2.5", 470 | "queryType": "randomWalk", 471 | "refId": "A" 472 | } 473 | ], 474 | "thresholds": [ 475 | { 476 | "$$hashKey": "object:188", 477 | "colorMode": "warning", 478 | "fill": true, 479 | "line": false, 480 | "op": "gt", 481 | "value": 10, 482 | "yaxis": "left" 483 | }, 484 | { 485 | "$$hashKey": "object:194", 486 | "colorMode": "critical", 487 | "fill": true, 488 | "line": false, 489 | "op": "gt", 490 | "value": 50, 491 | "yaxis": "left" 492 | } 493 | ], 494 | "timeFrom": null, 495 | "timeRegions": [], 496 | "timeShift": null, 497 | "title": "PM2.5 Over Time", 498 | "tooltip": { 499 | "shared": true, 500 | "sort": 0, 501 | "value_type": "individual" 502 | }, 503 | "type": "graph", 504 | "xaxis": { 505 | "buckets": null, 506 | "mode": "time", 507 | "name": null, 508 | "show": true, 509 | "values": [] 510 | }, 511 | "yaxes": [ 512 | { 513 | "$$hashKey": "object:48", 514 | "format": "conμgm3", 515 | "label": "", 516 | "logBase": 1, 517 | "max": null, 518 | "min": null, 519 | "show": true 520 | }, 521 | { 522 | "$$hashKey": "object:49", 523 | "format": "short", 524 | "label": null, 525 | "logBase": 1, 526 | "max": null, 527 | "min": null, 528 | "show": true 529 | } 530 | ], 531 | "yaxis": { 532 | "align": false, 533 | "alignLevel": null 534 | } 535 | } 536 | ], 537 | "refresh": "1m", 538 | "schemaVersion": 27, 539 | "style": "dark", 540 | "tags": [], 541 | "templating": { 542 | "list": [] 543 | }, 544 | "time": { 545 | "from": "now-2d", 546 | "to": "now" 547 | }, 548 | "timepicker": {}, 549 | "timezone": "", 550 | "title": "AirGradient - {{ item.id }}", 551 | "uid": "ag{{ item.id }}", 552 | "version": 1 553 | } -------------------------------------------------------------------------------- /templates/docker-compose.yml.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | --- 3 | volumes: 4 | prometheus_data: {} 5 | grafana_data: {} 6 | 7 | networks: 8 | front-tier: 9 | name: internet-monitoring-front-tier 10 | back-tier: 11 | name: internet-monitoring-back-tier 12 | 13 | services: 14 | {% if domain_name_enable %} 15 | nginx-proxy: 16 | image: nginxproxy/nginx-proxy:latest 17 | restart: always 18 | ports: 19 | - "80:80" 20 | networks: 21 | - back-tier 22 | - front-tier 23 | volumes: 24 | - /etc/localtime:/etc/localtime:ro 25 | - /var/run/docker.sock:/tmp/docker.sock:ro 26 | {% endif %} 27 | 28 | prometheus: 29 | image: prom/prometheus:latest 30 | restart: always 31 | volumes: 32 | - /etc/localtime:/etc/localtime:ro 33 | - ./prometheus/:/etc/prometheus/ 34 | - prometheus_data:/prometheus 35 | command: 36 | - '--config.file=/etc/prometheus/prometheus.yml' 37 | - '--storage.tsdb.path=/prometheus' 38 | - '--storage.tsdb.retention.time={{ prometheus_tsdb_retention_time }}' 39 | - '--web.console.libraries=/usr/share/prometheus/console_libraries' 40 | - '--web.console.templates=/usr/share/prometheus/consoles' 41 | ports: 42 | - 9090:9090 43 | links: 44 | - ping:ping 45 | - speedtest:speedtest 46 | networks: 47 | - back-tier 48 | {% if domain_name_enable and domain_name and domain_prometheus %} 49 | depends_on: 50 | - nginx-proxy 51 | environment: 52 | - VIRTUAL_HOST={{ domain_prometheus }}.{{ domain_name }} 53 | - VIRTUAL_PORT=9090 54 | {% endif %} 55 | 56 | grafana: 57 | image: grafana/grafana:latest 58 | restart: always 59 | volumes: 60 | - /etc/localtime:/etc/localtime:ro 61 | - grafana_data:/var/lib/grafana 62 | - ./grafana/provisioning/:/etc/grafana/provisioning/ 63 | ports: 64 | - 3030:3000 65 | env_file: 66 | - ./grafana/config.monitoring 67 | networks: 68 | - back-tier 69 | - front-tier 70 | depends_on: 71 | - prometheus 72 | {% if domain_name_enable and domain_name and domain_grafana %} 73 | - nginx-proxy 74 | environment: 75 | - VIRTUAL_HOST={{ domain_grafana }}.{{ domain_name }} 76 | - VIRTUAL_PORT=3000 77 | {% endif %} 78 | 79 | ping: 80 | image: prom/blackbox-exporter:latest 81 | restart: always 82 | volumes: 83 | - /etc/localtime:/etc/localtime:ro 84 | - ./blackbox/config:/config 85 | expose: 86 | - 9115 87 | ports: 88 | - 9115:9115 89 | tty: true 90 | stdin_open: true 91 | command: 92 | - '--config.file=/config/blackbox.yml' 93 | networks: 94 | - back-tier 95 | 96 | speedtest: 97 | image: miguelndecarvalho/speedtest-exporter:latest 98 | restart: always 99 | healthcheck: 100 | test: "wget --no-verbose --tries=1 --spider http://0.0.0.0:9798/" 101 | timeout: 10s 102 | expose: 103 | - 9798 104 | ports: 105 | - 9798:9798 106 | networks: 107 | - back-tier 108 | 109 | nodeexp: 110 | image: prom/node-exporter:latest 111 | restart: always 112 | privileged: true 113 | volumes: 114 | - /etc/localtime:/etc/localtime:ro 115 | - /proc:/host/proc:ro 116 | - /sys:/host/sys:ro 117 | - /:/rootfs:ro 118 | ports: 119 | - 9100:9100 120 | command: 121 | - '--path.procfs=/host/proc' 122 | - '--path.sysfs=/host/sys' 123 | - --collector.filesystem.ignored-mount-points 124 | - "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)" 125 | networks: 126 | - back-tier 127 | -------------------------------------------------------------------------------- /templates/grafana-config.monitoring.j2: -------------------------------------------------------------------------------- 1 | GF_SECURITY_ADMIN_PASSWORD={{ monitoring_grafana_admin_password }} 2 | GF_USERS_ALLOW_SIGN_UP=false 3 | GF_INSTALL_PLUGINS=flant-statusmap-panel,ae3e-plotly-panel 4 | -------------------------------------------------------------------------------- /templates/pi-hole-docker-compose.yml.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | --- 3 | networks: 4 | {% if domain_name_enable and domain_name and domain_pihole %} 5 | {# Make sure pihole and nginx-proxy are in the same network #} 6 | front-tier: 7 | name: internet-monitoring-front-tier 8 | external: true 9 | {% endif %} 10 | {# pihole and pihole-exporter need to share network with Prometheus #} 11 | back-tier: 12 | name: internet-monitoring-back-tier 13 | external: true 14 | 15 | 16 | # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/ 17 | services: 18 | pihole: 19 | container_name: pihole 20 | image: pihole/pihole:latest 21 | restart: unless-stopped 22 | hostname: '{{ pihole_hostname }}' 23 | ports: 24 | - "53:53/tcp" 25 | - "53:53/udp" 26 | - "67:67/udp" 27 | {% if domain_name_enable and domain_name and domain_pihole %} 28 | - "38080:80/tcp" 29 | {% else %} 30 | - "80:80/tcp" 31 | {% endif %} 32 | - "443:443/tcp" 33 | environment: 34 | TZ: '{{ pihole_timezone }}' 35 | FTLCONF_webserver_api_password: '{{ pihole_password }}' 36 | FTLCONF_dns_reply_host_IPv4: '{{ ansible_facts['default_ipv4']['address'] }}' 37 | FTLCONF_dns_listeningMode: 'SINGLE' 38 | FTLCONF_dns_interface: '{{ ansible_facts['default_ipv4']['interface'] }}' 39 | {% if domain_name_enable and domain_name and domain_pihole %} 40 | VIRTUAL_HOST: {{ domain_pihole }}.{{ domain_name }} 41 | VIRTUAL_PORT: 80 42 | PROXY_LOCATION: {{ domain_pihole }} 43 | {% endif %} 44 | networks: 45 | - back-tier 46 | {% if domain_name_enable and domain_name and domain_pihole %} 47 | - front-tier 48 | {% endif %} 49 | dns: 50 | - 127.0.0.1 51 | - 8.8.8.8 52 | volumes: 53 | - /etc/localtime:/etc/localtime:ro 54 | - './etc-pihole/:/etc/pihole/' 55 | - './etc-dnsmasq.d/:/etc/dnsmasq.d/' 56 | cap_add: 57 | - NET_ADMIN 58 | {% if domain_name_enable and domain_name %} 59 | # adds hostname mappings to pihole /etc/hosts file 60 | extra_hosts: 61 | # # LAN hostnames for other docker containers using nginx-proxy 62 | - "{{ domain_name }}:{{ ansible_facts['default_ipv4']['address'] }}" 63 | {% if domain_name_enable and domain_name and domain_pihole %} 64 | - "{{ domain_pihole }}.{{ domain_name }}:{{ ansible_facts['default_ipv4']['address'] }}" 65 | {% endif %} 66 | {% if domain_name_enable and domain_name and domain_grafana %} 67 | - "{{ domain_grafana }}.{{ domain_name }}:{{ ansible_facts['default_ipv4']['address'] }}" 68 | {% endif %} 69 | {% if domain_name_enable and domain_name and domain_prometheus %} 70 | - "{{ domain_prometheus }}.{{ domain_name }}:{{ ansible_facts['default_ipv4']['address'] }}" 71 | {% endif %} 72 | {% endif %} 73 | 74 | pihole-exporter: 75 | container_name: pihole-exporter 76 | image: ekofr/pihole-exporter:latest 77 | restart: unless-stopped 78 | hostname: 'pihole-exporter' 79 | networks: 80 | - back-tier 81 | ports: 82 | - "9617:9617" 83 | environment: 84 | PIHOLE_HOSTNAME: '{{ pihole_hostname }}' 85 | PIHOLE_PASSWORD: '{{ pihole_password }}' 86 | INTERVAL: '30s' 87 | PORT: 9617 88 | -------------------------------------------------------------------------------- /templates/prometheus-pinghosts.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | - targets: 3 | {{ monitoring_ping_hosts | to_nice_yaml() | indent(4, True) }} 4 | -------------------------------------------------------------------------------- /templates/prometheus.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | global: 3 | scrape_interval: 15s 4 | evaluation_interval: 15s 5 | scrape_timeout: 10s 6 | external_labels: 7 | monitor: 'Alertmanager' 8 | 9 | rule_files: 10 | - 'alert.rules' 11 | 12 | scrape_configs: 13 | {% if prometheus_monitor_prometheus %} 14 | - job_name: 'prometheus' 15 | scrape_interval: 5s 16 | static_configs: 17 | - targets: ['localhost:9090'] 18 | {% endif %} 19 | 20 | - job_name: 'speedtest' 21 | metrics_path: /metrics 22 | scrape_interval: {{ monitoring_speedtest_interval }} 23 | scrape_timeout: 60s # running speedtest needs time to complete 24 | static_configs: 25 | - targets: ['speedtest:9798'] 26 | 27 | {% if shelly_plug_enable %} 28 | - job_name: 'shelly-plug' 29 | metrics_path: /metrics 30 | scrape_interval: 1m 31 | static_configs: 32 | - targets: ['172.17.0.1:9924'] 33 | {% endif %} 34 | 35 | {% if airgradient_enable %} 36 | {% for sensor in airgradient_sensors %} 37 | - job_name: 'airgradient-{{ sensor.id }}' 38 | metrics_path: /metrics 39 | scrape_interval: 30s 40 | static_configs: 41 | - targets: ['{{ sensor.ip }}:{{ sensor.port }}'] 42 | {% endfor %} 43 | {% endif %} 44 | 45 | {% if starlink_enable %} 46 | - job_name: 'starlink' 47 | metrics_path: /metrics 48 | static_configs: 49 | - targets: ['172.17.0.1:9817'] 50 | {% endif %} 51 | 52 | - job_name: 'ping' 53 | metrics_path: /probe 54 | scrape_interval: {{ monitoring_ping_interval }} 55 | params: 56 | module: [http_2xx] # Look for a HTTP 200 response. 57 | file_sd_configs: 58 | - files: 59 | - pinghosts.yaml 60 | relabel_configs: 61 | - source_labels: [__address__] 62 | regex: '(.*);(.*)' # first is the url, thus unique for instance 63 | target_label: instance 64 | replacement: $1 65 | - source_labels: [__address__] 66 | regex: '(.*);(.*)' # second is humanname to use in charts 67 | target_label: humanname 68 | replacement: $2 69 | - source_labels: [instance] 70 | target_label: __param_target 71 | - target_label: __address__ 72 | replacement: ping:9115 # The blackbox exporter's real hostname:port. 73 | 74 | - job_name: 'node' 75 | static_configs: 76 | - targets: {{ prometheus_node_exporter_targets | to_yaml }} 77 | 78 | {% if pihole_enable %} 79 | - job_name: '{{ pihole_hostname }}' 80 | static_configs: 81 | - targets: ['pihole-exporter:9617'] 82 | {% endif %} 83 | 84 | {% filter indent(width=2,first=True) %} 85 | {{ prometheus_extra_scrape_configs }} 86 | {% endfilter %} 87 | -------------------------------------------------------------------------------- /templates/shelly-plug-docker-compose.yml.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | --- 3 | services: 4 | shelly-plug: 5 | container_name: shelly-plug 6 | image: php:8-apache 7 | ports: 8 | - "9924:80" 9 | environment: 10 | SHELLY_HOSTNAME: '{{ shelly_plug_hostname }}' 11 | SHELLY_HTTP_USERNAME: '{{ shelly_plug_http_username }}' 12 | SHELLY_HTTP_PASSWORD: '{{ shelly_plug_http_password }}' 13 | volumes: 14 | - './:/var/www/html' 15 | restart: unless-stopped 16 | -------------------------------------------------------------------------------- /templates/starlink-docker-compose.yml.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | --- 3 | services: 4 | starlink-exporter: 5 | container_name: starlink-exporter 6 | image: danopstech/starlink_exporter 7 | ports: 8 | - "9817:9817" 9 | restart: unless-stopped 10 | --------------------------------------------------------------------------------