├── .gitignore ├── Makefile ├── README.md ├── grafana └── Quilibrium Network-1697907942161.json └── quilibrium-node-exporter.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | next_frame_number 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/usr/bin/env bash 2 | 3 | ifndef GIT_ORG 4 | GIT_ORG = sirouk 5 | endif 6 | 7 | ifndef GIT_REPO 8 | GIT_REPO = quilibrium-node-exporter 9 | endif 10 | 11 | ifndef SOURCE_PATH 12 | SOURCE_PATH=~/quilibrium-node-exporter 13 | endif 14 | 15 | ifndef REPO_PATH 16 | REPO_PATH=~/${GIT_REPO} 17 | endif 18 | 19 | 20 | 21 | check: 22 | cd ${SOURCE_PATH} 23 | ls 24 | curl 127.0.0.1:8380/metrics 25 | 26 | 27 | install: 28 | cd ${SOURCE_PATH} 29 | sudo apt install -y python3 python3-pip jq 30 | python3 -m pip install flask 31 | python3 -m pip install base58 32 | 33 | @if [ ! -d ${REPO_PATH} ]; then \ 34 | mkdir -p ${REPO_PATH} && cd ${REPO_PATH} && git clone https://github.com/${GIT_ORG}/${GIT_REPO} .; \ 35 | elif [ -d ${REPO_PATH}/.git ]; then \ 36 | cd ${REPO_PATH}; \ 37 | else \ 38 | echo "Directory exists but is not a git repository. Please handle manually."; \ 39 | fi 40 | 41 | 42 | update: install 43 | cd ${REPO_PATH} 44 | git pull 45 | 46 | 47 | start-exporter: 48 | cd ${SOURCE_PATH} && python3 quilibrium-node-exporter.py 49 | 50 | 51 | start-cron: 52 | @cd ${REPO_PATH}; \ 53 | ps aux | grep "python3 quilibrium-node-exporter.py" | grep -v "grep" > /dev/null; \ 54 | ps_exit_status=$$?; \ 55 | if [ $$ps_exit_status -ne 0 ]; then \ 56 | echo "Starting Quilibrium Node Exporter..."; \ 57 | cd ${REPO_PATH} && make start-exporter >> ${REPO_PATH}/exporter.log 2>&1 & \ 58 | else \ 59 | echo "Quilibrium Node Exporter is already running"; \ 60 | fi 61 | 62 | 63 | stop-cron: 64 | @cd ${REPO_PATH}; \ 65 | ps aux | grep "python3 quilibrium-node-exporter.py" | grep -v "grep" > /dev/null; \ 66 | ps_exit_status=$$?; \ 67 | if [ $$ps_exit_status -ne 0 ]; then \ 68 | echo "Quilibrium Node Exporter is not running!"; \ 69 | cd ${REPO_PATH} && make start-exporter >> ${REPO_PATH}/exporter.log 2>&1 & \ 70 | else \ 71 | echo "Stopping Quilibrium Node Exporter..."; \ 72 | pkill -f "python3 quilibrium-node-exporter.py"; \ 73 | fi 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quilibrium Node Exporter 2 | 3 | This Quilibrium metrics exporter is a tool that should simplify the task of exposing metrics from your Quilibrium network node for consumption by Prometheus. This is a basic start, but will eventually provide robust data to help you monitor node health, performance, and other vital statistics. 4 | 5 | I will continue to add additional metrics as they become available. 6 | 7 | Feel free to contribute! 8 | 9 | 10 | ## Features 11 | 12 | - Designed to work out-of-the-box with the latest release of [Quilibrium node](https://github.com/quilibriumnetwork/ceremonyclient) 13 | - Gathers detailed metrics from the Quilibrium node into a single call 14 | - Suitable for monitoring an individual node 15 | 16 | ## Prerequisites 17 | 18 | - curl 19 | `sudo apt install curl -y` 20 | - Python 3.x and pip 21 | `sudo apt install python3 python3-pip` 22 | - Pip base58 and flask packages 23 | `python3 -m pip install base58 flask` 24 | - A running instance of Quilibrium network node with REST endpoint exposed (locally) 25 | - Prometheus and Grafana 26 | 27 | ## Installation 28 | 29 | 1. Clone the repository: 30 | 31 | ```bash 32 | cd ~ 33 | git clone https://github.com/sirouk/quilibrium-node-exporter 34 | 35 | ``` 36 | 37 | 2. Install the required packages: 38 | 39 | ```bash 40 | cd ~/quilibrium-node-exporter 41 | make install 42 | ``` 43 | 44 | ## Usage 45 | 46 | 3. Expost the Quilibrium REST API: 47 | 48 | Edit your Quilibrium node config: 49 | ```bash 50 | nano /path/to/quilibrium/ceremonyclient/node/.config/config.yml 51 | ``` 52 | 53 | Add to the bottom or edit an existing entry, and save: 54 | ``` 55 | listenGrpcMultiaddr: /ip4/127.0.0.1/tcp/8378 56 | listenRESTMultiaddr: /ip4/127.0.0.1/tcp/8379 57 | ``` 58 | 59 | Note: You must open the gRPC if you want REST to work! 60 | 61 | 62 | 3. Start the exporter: 63 | 64 | ```bash 65 | make start-cron 66 | ``` 67 | 68 | By default, the exporter will run on port 8380. You can run this in a cron by the minute, it will only run once. 69 | 70 | To stop: 71 | ```bash 72 | make stop-cron 73 | ``` 74 | 4. Test the output: 75 | 76 | ```bash 77 | make check 78 | ``` 79 | 80 | You will see the repo files and one output frame: 81 | ``` 82 | cd ~/quilibrium-node-exporter 83 | ls 84 | exporter.log grafana Makefile next_frame_number quilibrium-node-exporter.py README.md 85 | curl 127.0.0.1:8380/metrics 86 | Quilibrium_LatestFrame_truncatedClockFrames_frameNumber{filter="NAAb50MsLmZpraAnl4hoKrn2JnGxtTirmVBGlNmBy9M="} 37414 87 | Quilibrium_LatestFrame_truncatedClockFrames_timestamp{filter="NAAb50MsLmZpraAnl4hoKrn2JnGxtTirmVBGlNmBy9M="} 1697364469374 88 | Quilibrium_LatestFrame_truncatedClockFrames_difficulty{filter="NAAb50MsLmZpraAnl4hoKrn2JnGxtTirmVBGlNmBy9M="} 10000 89 | ... 90 | Quilibrium_NetworkInfo_peerScore{peerId="QmdYYNKRvZYThJ7tP2A3RHfed3xrazNBiSLUrDnqNr83EZ",multiaddrs="['/ip4/198.98.109.189/udp/8336/quic']"} 100 91 | ... 92 | Quilibrium_PeerInfo_maxFrame{peerId="QmZfPwUNk3hFSVRvMtijqxBYkxmrPix2Zd7gbZdZQe65Dp",multiaddrs="['/ip4/147.135.62.9/udp/8336/quic']"} 209 93 | Quilibrium_PeerInfo_timestamp{peerId="QmZfPwUNk3hFSVRvMtijqxBYkxmrPix2Zd7gbZdZQe65Dp",multiaddrs="['/ip4/147.135.62.9/udp/8336/quic']"} 1699245754932 94 | ... 95 | Quilibrium_PeerInfo_uncooperativePeerInfo_maxFrame{peerId="QmaffYBcwMgMNz5KhkhpJuWg6kChX7TmdZ8hexz8de8TWA",multiaddrs="['/ip4/70.187.187.239/udp/8336/quic']"} 53799 96 | ... 97 | Quilibrium_TokenInfo_confirmedTokenSupply 90585200000000000 98 | Quilibrium_TokenInfo_unconfirmedTokenSupply 90585200000000000 99 | ``` 100 | 101 | 4. Configure Prometheus to scrape from the exporter. 102 | 103 | Add the following to your `prometheus.yml`: 104 | ```yaml 105 | scrape_configs: 106 | - job_name: 'quilibrium_node_exporter' 107 | static_configs: 108 | - targets: ['localhost:8380'] 109 | ``` 110 | 111 | 5. Visualize the metrics in Grafana by connecting them to your Prometheus data source and creating custom dashboards. 112 | 113 | Configure Grafana with a Prometheus datasource URL of `http://localhost:9090` and import the JSON located in this repo under `/grafana` for a copy of the Quilibrium Network Dashboard. 114 | 115 | 116 | ## Setting up Grafana on a Subdomain (optional) 117 | 118 | - Modify your domain DNS to include an A record with the name you prefer and the IP of your server. 119 | - Uncomment `domain = ` in `grafana.ini` and set it to your full subdomain: 120 | 121 | ```bash 122 | sudo nano /etc/grafana/grafana.ini 123 | ``` 124 | 125 | Example: 126 | ``` 127 | somesubdomain.yourdomain.tld 128 | ``` 129 | 130 | 131 | - Set up the reverse proxy using Nginx: 132 | 133 | Install Nginx and create a site: 134 | ```bash 135 | sudo apt update 136 | sudo apt install nginx 137 | 138 | ``` 139 | 140 | Add the server block: 141 | ```bash 142 | sudo nano /etc/nginx/sites-available/somesubdomain.yourdomain.tld 143 | ``` 144 | 145 | Contents of server block (80 for now, but will be updated by certbot) 146 | ``` 147 | server { 148 | listen 80; 149 | server_name somesubdomain.yourdomain.tld; 150 | 151 | location / { 152 | proxy_pass http://localhost:3000; # Forward requests to Grafana 153 | proxy_set_header Host $host; # Pass the host header - important for virtual hosting 154 | proxy_set_header X-Real-IP $remote_addr; # Pass the real client IP to Grafana 155 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Manage the forwarded-for header 156 | proxy_set_header X-Forwarded-Proto $scheme; # Manage the forwarded-proto header 157 | } 158 | } 159 | ``` 160 | 161 | Enable the site for nginx to serve: 162 | ```bash 163 | sudo ln -s /etc/nginx/sites-available/somesubdomain.yourdomain.tld /etc/nginx/sites-enabled/ 164 | sudo nginx -t 165 | sudo systemctl restart nginx 166 | sudo systemctl enable nginx 167 | ``` 168 | 169 | - Allow the site through the firewall for HTTP (DCV) and HTTPS traffic: 170 | 171 | ```bash 172 | sudo ufw allow 80 173 | sudo ufw allow 443 174 | ``` 175 | 176 | - Set up SSL with Let's Encrypt: 177 | 178 | ```bash 179 | sudo apt install certbot python3-certbot-nginx 180 | sudo certbot --nginx -d somesubdomain.yourdomain.tld 181 | ``` 182 | 183 | 184 | ## Related Quilibrium community developments 185 | 186 | Another community member build a handy Quilibrium rust client: 187 | 188 | https://github.com/agostbiro/quilibrium-rs/tree/main/crates/quilclient 189 | 190 | 191 | ## Contributing 192 | 193 | I love contributions! Feel free to open an issue or submit a PR! 194 | 195 | ## License 196 | 197 | This project is licensed under the MIT License. 198 | -------------------------------------------------------------------------------- /grafana/Quilibrium Network-1697907942161.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "grafana", 8 | "uid": "-- Grafana --" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "type": "dashboard" 15 | } 16 | ] 17 | }, 18 | "editable": true, 19 | "fiscalYearStartMonth": 0, 20 | "graphTooltip": 0, 21 | "id": 1, 22 | "links": [], 23 | "liveNow": false, 24 | "panels": [ 25 | { 26 | "collapsed": false, 27 | "gridPos": { 28 | "h": 1, 29 | "w": 24, 30 | "x": 0, 31 | "y": 0 32 | }, 33 | "id": 10, 34 | "panels": [], 35 | "title": "Peer Info", 36 | "type": "row" 37 | }, 38 | { 39 | "datasource": { 40 | "name": "Prometheus", 41 | "type": "prometheus" 42 | }, 43 | "fieldConfig": { 44 | "defaults": { 45 | "color": { 46 | "mode": "thresholds" 47 | }, 48 | "mappings": [], 49 | "thresholds": { 50 | "mode": "absolute", 51 | "steps": [ 52 | { 53 | "color": "green", 54 | "value": null 55 | }, 56 | { 57 | "color": "red", 58 | "value": 80 59 | } 60 | ] 61 | }, 62 | "unit": "locale" 63 | }, 64 | "overrides": [] 65 | }, 66 | "gridPos": { 67 | "h": 5, 68 | "w": 3, 69 | "x": 0, 70 | "y": 1 71 | }, 72 | "id": 4, 73 | "options": { 74 | "colorMode": "value", 75 | "graphMode": "area", 76 | "justifyMode": "auto", 77 | "orientation": "auto", 78 | "reduceOptions": { 79 | "calcs": [ 80 | "lastNotNull" 81 | ], 82 | "fields": "", 83 | "values": false 84 | }, 85 | "textMode": "auto" 86 | }, 87 | "pluginVersion": "10.1.5", 88 | "targets": [ 89 | { 90 | "datasource": { 91 | "name": "Prometheus", 92 | "type": "prometheus" 93 | }, 94 | "editorMode": "code", 95 | "exemplar": false, 96 | "expr": "count(Quilibrium_PeerInfo_maxFrame{}) + 1", 97 | "instant": true, 98 | "legendFormat": "__auto", 99 | "range": false, 100 | "refId": "A" 101 | } 102 | ], 103 | "title": "Discovered Peers", 104 | "type": "stat" 105 | }, 106 | { 107 | "datasource": { 108 | "name": "Prometheus", 109 | "type": "prometheus" 110 | }, 111 | "fieldConfig": { 112 | "defaults": { 113 | "color": { 114 | "mode": "palette-classic" 115 | }, 116 | "custom": { 117 | "axisCenteredZero": false, 118 | "axisColorMode": "text", 119 | "axisLabel": "", 120 | "axisPlacement": "auto", 121 | "barAlignment": 0, 122 | "drawStyle": "line", 123 | "fillOpacity": 0, 124 | "gradientMode": "none", 125 | "hideFrom": { 126 | "legend": false, 127 | "tooltip": false, 128 | "viz": false 129 | }, 130 | "insertNulls": false, 131 | "lineInterpolation": "linear", 132 | "lineStyle": { 133 | "fill": "solid" 134 | }, 135 | "lineWidth": 1, 136 | "pointSize": 5, 137 | "scaleDistribution": { 138 | "log": 2, 139 | "type": "log" 140 | }, 141 | "showPoints": "never", 142 | "spanNulls": true, 143 | "stacking": { 144 | "group": "A", 145 | "mode": "none" 146 | }, 147 | "thresholdsStyle": { 148 | "mode": "area" 149 | } 150 | }, 151 | "mappings": [], 152 | "thresholds": { 153 | "mode": "percentage", 154 | "steps": [ 155 | { 156 | "color": "red", 157 | "value": null 158 | }, 159 | { 160 | "color": "green", 161 | "value": 90 162 | } 163 | ] 164 | } 165 | }, 166 | "overrides": [] 167 | }, 168 | "gridPos": { 169 | "h": 10, 170 | "w": 20, 171 | "x": 3, 172 | "y": 1 173 | }, 174 | "id": 1, 175 | "options": { 176 | "legend": { 177 | "calcs": [ 178 | "lastNotNull" 179 | ], 180 | "displayMode": "table", 181 | "placement": "bottom", 182 | "showLegend": true, 183 | "sortBy": "Last *", 184 | "sortDesc": true 185 | }, 186 | "tooltip": { 187 | "mode": "single", 188 | "sort": "none" 189 | } 190 | }, 191 | "targets": [ 192 | { 193 | "datasource": { 194 | "name": "Prometheus", 195 | "type": "prometheus" 196 | }, 197 | "disableTextWrap": false, 198 | "editorMode": "code", 199 | "exemplar": false, 200 | "expr": "Quilibrium_PeerInfo_maxFrame{peerId=~\"$Peer\", multiaddrs=~\"$Address\"}", 201 | "format": "time_series", 202 | "fullMetaSearch": false, 203 | "includeNullMetadata": true, 204 | "instant": false, 205 | "interval": "", 206 | "legendFormat": "{{peerId}} ({{multiaddrs}})", 207 | "range": true, 208 | "refId": "A", 209 | "useBackend": false 210 | } 211 | ], 212 | "title": "PeerInfo_maxFrame", 213 | "transparent": true, 214 | "type": "timeseries" 215 | }, 216 | { 217 | "datasource": { 218 | "name": "Prometheus", 219 | "type": "prometheus" 220 | }, 221 | "fieldConfig": { 222 | "defaults": { 223 | "color": { 224 | "mode": "thresholds" 225 | }, 226 | "mappings": [], 227 | "thresholds": { 228 | "mode": "absolute", 229 | "steps": [ 230 | { 231 | "color": "green", 232 | "value": null 233 | }, 234 | { 235 | "color": "red", 236 | "value": 80 237 | } 238 | ] 239 | }, 240 | "unit": "locale" 241 | }, 242 | "overrides": [] 243 | }, 244 | "gridPos": { 245 | "h": 5, 246 | "w": 3, 247 | "x": 0, 248 | "y": 6 249 | }, 250 | "id": 5, 251 | "options": { 252 | "colorMode": "value", 253 | "graphMode": "area", 254 | "justifyMode": "auto", 255 | "orientation": "auto", 256 | "reduceOptions": { 257 | "calcs": [ 258 | "lastNotNull" 259 | ], 260 | "fields": "", 261 | "values": false 262 | }, 263 | "textMode": "auto" 264 | }, 265 | "pluginVersion": "10.1.5", 266 | "targets": [ 267 | { 268 | "datasource": { 269 | "name": "Prometheus", 270 | "type": "prometheus" 271 | }, 272 | "editorMode": "code", 273 | "exemplar": false, 274 | "expr": "count(Quilibrium_NetworkInfo_peerScore{}) +1", 275 | "instant": true, 276 | "legendFormat": "__auto", 277 | "range": false, 278 | "refId": "A" 279 | } 280 | ], 281 | "title": "Network Peers", 282 | "type": "stat" 283 | }, 284 | { 285 | "collapsed": false, 286 | "gridPos": { 287 | "h": 1, 288 | "w": 24, 289 | "x": 0, 290 | "y": 11 291 | }, 292 | "id": 9, 293 | "panels": [], 294 | "title": "Network", 295 | "type": "row" 296 | }, 297 | { 298 | "datasource": { 299 | "name": "Prometheus", 300 | "type": "prometheus" 301 | }, 302 | "fieldConfig": { 303 | "defaults": { 304 | "color": { 305 | "mode": "thresholds" 306 | }, 307 | "mappings": [], 308 | "thresholds": { 309 | "mode": "absolute", 310 | "steps": [ 311 | { 312 | "color": "green", 313 | "value": null 314 | } 315 | ] 316 | }, 317 | "unit": "locale" 318 | }, 319 | "overrides": [] 320 | }, 321 | "gridPos": { 322 | "h": 5, 323 | "w": 3, 324 | "x": 0, 325 | "y": 12 326 | }, 327 | "id": 6, 328 | "options": { 329 | "colorMode": "value", 330 | "graphMode": "area", 331 | "justifyMode": "auto", 332 | "orientation": "auto", 333 | "reduceOptions": { 334 | "calcs": [ 335 | "lastNotNull" 336 | ], 337 | "fields": "", 338 | "values": false 339 | }, 340 | "textMode": "auto" 341 | }, 342 | "pluginVersion": "10.1.5", 343 | "targets": [ 344 | { 345 | "datasource": { 346 | "name": "Prometheus", 347 | "type": "prometheus" 348 | }, 349 | "editorMode": "code", 350 | "exemplar": false, 351 | "expr": "max(Quilibrium_LatestFrame_truncatedClockFrames_frameNumber)", 352 | "instant": true, 353 | "legendFormat": "__auto", 354 | "range": false, 355 | "refId": "A" 356 | } 357 | ], 358 | "title": "Version", 359 | "type": "stat" 360 | }, 361 | { 362 | "datasource": { 363 | "name": "Prometheus", 364 | "type": "prometheus" 365 | }, 366 | "fieldConfig": { 367 | "defaults": { 368 | "color": { 369 | "mode": "palette-classic" 370 | }, 371 | "custom": { 372 | "axisCenteredZero": false, 373 | "axisColorMode": "text", 374 | "axisGridShow": true, 375 | "axisLabel": "", 376 | "axisPlacement": "auto", 377 | "barAlignment": 0, 378 | "drawStyle": "line", 379 | "fillOpacity": 0, 380 | "gradientMode": "none", 381 | "hideFrom": { 382 | "legend": false, 383 | "tooltip": false, 384 | "viz": false 385 | }, 386 | "insertNulls": false, 387 | "lineInterpolation": "linear", 388 | "lineStyle": { 389 | "fill": "solid" 390 | }, 391 | "lineWidth": 1, 392 | "pointSize": 5, 393 | "scaleDistribution": { 394 | "log": 2, 395 | "type": "log" 396 | }, 397 | "showPoints": "never", 398 | "spanNulls": false, 399 | "stacking": { 400 | "group": "A", 401 | "mode": "none" 402 | }, 403 | "thresholdsStyle": { 404 | "mode": "off" 405 | } 406 | }, 407 | "mappings": [], 408 | "thresholds": { 409 | "mode": "absolute", 410 | "steps": [ 411 | { 412 | "color": "green", 413 | "value": null 414 | } 415 | ] 416 | } 417 | }, 418 | "overrides": [] 419 | }, 420 | "gridPos": { 421 | "h": 10, 422 | "w": 20, 423 | "x": 3, 424 | "y": 12 425 | }, 426 | "id": 7, 427 | "options": { 428 | "legend": { 429 | "calcs": [], 430 | "displayMode": "list", 431 | "placement": "bottom", 432 | "showLegend": false 433 | }, 434 | "tooltip": { 435 | "mode": "single", 436 | "sort": "none" 437 | } 438 | }, 439 | "targets": [ 440 | { 441 | "datasource": { 442 | "name": "Prometheus", 443 | "type": "prometheus" 444 | }, 445 | "editorMode": "code", 446 | "expr": "Quilibrium_LatestFrame_truncatedClockFrames_frameNumber", 447 | "instant": false, 448 | "legendFormat": "__auto", 449 | "range": true, 450 | "refId": "A" 451 | }, 452 | { 453 | "datasource": { 454 | "name": "Prometheus", 455 | "type": "prometheus" 456 | }, 457 | "editorMode": "code", 458 | "expr": "Quilibrium_LatestFrame_truncatedClockFrames_difficulty", 459 | "hide": false, 460 | "instant": false, 461 | "legendFormat": "__auto", 462 | "range": true, 463 | "refId": "B" 464 | } 465 | ], 466 | "title": "Version & Difficulty", 467 | "transparent": true, 468 | "type": "timeseries" 469 | }, 470 | { 471 | "datasource": { 472 | "name": "Prometheus", 473 | "type": "prometheus" 474 | }, 475 | "fieldConfig": { 476 | "defaults": { 477 | "color": { 478 | "mode": "thresholds" 479 | }, 480 | "mappings": [], 481 | "thresholds": { 482 | "mode": "absolute", 483 | "steps": [ 484 | { 485 | "color": "green", 486 | "value": null 487 | } 488 | ] 489 | }, 490 | "unit": "locale" 491 | }, 492 | "overrides": [] 493 | }, 494 | "gridPos": { 495 | "h": 5, 496 | "w": 3, 497 | "x": 0, 498 | "y": 17 499 | }, 500 | "id": 11, 501 | "options": { 502 | "colorMode": "value", 503 | "graphMode": "area", 504 | "justifyMode": "auto", 505 | "orientation": "auto", 506 | "reduceOptions": { 507 | "calcs": [ 508 | "lastNotNull" 509 | ], 510 | "fields": "", 511 | "values": false 512 | }, 513 | "textMode": "auto" 514 | }, 515 | "pluginVersion": "10.1.5", 516 | "targets": [ 517 | { 518 | "datasource": { 519 | "name": "Prometheus", 520 | "type": "prometheus" 521 | }, 522 | "editorMode": "code", 523 | "exemplar": false, 524 | "expr": "Quilibrium_LatestFrame_truncatedClockFrames_difficulty", 525 | "instant": true, 526 | "legendFormat": "__auto", 527 | "range": false, 528 | "refId": "A" 529 | } 530 | ], 531 | "title": "Difficulty", 532 | "type": "stat" 533 | }, 534 | { 535 | "datasource": { 536 | "type": "prometheus", 537 | "name":"Prometheus" 538 | }, 539 | "fieldConfig": { 540 | "defaults": { 541 | "color": { 542 | "mode": "thresholds" 543 | }, 544 | "mappings": [], 545 | "thresholds": { 546 | "mode": "absolute", 547 | "steps": [ 548 | { 549 | "color": "green", 550 | "value": null 551 | } 552 | ] 553 | }, 554 | "unit": "locale" 555 | }, 556 | "overrides": [] 557 | }, 558 | "gridPos": { 559 | "h": 7, 560 | "w": 3, 561 | "x": 0, 562 | "y": 22 563 | }, 564 | "id": 12, 565 | "options": { 566 | "colorMode": "value", 567 | "graphMode": "area", 568 | "justifyMode": "auto", 569 | "orientation": "auto", 570 | "reduceOptions": { 571 | "calcs": [ 572 | "lastNotNull" 573 | ], 574 | "fields": "", 575 | "values": false 576 | }, 577 | "textMode": "auto" 578 | }, 579 | "pluginVersion": "10.1.5", 580 | "targets": [ 581 | { 582 | "datasource": { 583 | "name": "Prometheus", 584 | "type": "prometheus" 585 | }, 586 | "editorMode": "code", 587 | "exemplar": false, 588 | "expr": "Quilibrium_TokenInfo_confirmedTokenSupply / 8000000000", 589 | "instant": true, 590 | "legendFormat": "__auto", 591 | "range": false, 592 | "refId": "A" 593 | } 594 | ], 595 | "title": "Quil Supply", 596 | "type": "stat" 597 | }, 598 | { 599 | "datasource": { 600 | "type": "prometheus", 601 | "name":"Prometheus" 602 | }, 603 | "fieldConfig": { 604 | "defaults": { 605 | "color": { 606 | "mode": "palette-classic" 607 | }, 608 | "custom": { 609 | "axisCenteredZero": false, 610 | "axisColorMode": "text", 611 | "axisGridShow": true, 612 | "axisLabel": "", 613 | "axisPlacement": "auto", 614 | "barAlignment": 0, 615 | "drawStyle": "line", 616 | "fillOpacity": 0, 617 | "gradientMode": "none", 618 | "hideFrom": { 619 | "legend": false, 620 | "tooltip": false, 621 | "viz": false 622 | }, 623 | "insertNulls": false, 624 | "lineInterpolation": "linear", 625 | "lineStyle": { 626 | "fill": "solid" 627 | }, 628 | "lineWidth": 1, 629 | "pointSize": 5, 630 | "scaleDistribution": { 631 | "log": 2, 632 | "type": "log" 633 | }, 634 | "showPoints": "never", 635 | "spanNulls": false, 636 | "stacking": { 637 | "group": "A", 638 | "mode": "none" 639 | }, 640 | "thresholdsStyle": { 641 | "mode": "off" 642 | } 643 | }, 644 | "mappings": [], 645 | "thresholds": { 646 | "mode": "absolute", 647 | "steps": [ 648 | { 649 | "color": "green", 650 | "value": null 651 | } 652 | ] 653 | } 654 | }, 655 | "overrides": [] 656 | }, 657 | "gridPos": { 658 | "h": 7, 659 | "w": 20, 660 | "x": 3, 661 | "y": 22 662 | }, 663 | "id": 13, 664 | "options": { 665 | "legend": { 666 | "calcs": [], 667 | "displayMode": "list", 668 | "placement": "bottom", 669 | "showLegend": false 670 | }, 671 | "tooltip": { 672 | "mode": "single", 673 | "sort": "none" 674 | } 675 | }, 676 | "targets": [ 677 | { 678 | "datasource": { 679 | "name": "Prometheus", 680 | "type": "prometheus" 681 | }, 682 | "editorMode": "code", 683 | "expr": "Quilibrium_TokenInfo_confirmedTokenSupply / 8000000000", 684 | "instant": false, 685 | "legendFormat": "__auto", 686 | "range": true, 687 | "refId": "A" 688 | } 689 | ], 690 | "title": "Confirmed Quil Supply", 691 | "transparent": true, 692 | "type": "timeseries" 693 | }, 694 | { 695 | "datasource": { 696 | "type": "prometheus", 697 | "name":"Prometheus" 698 | }, 699 | "fieldConfig": { 700 | "defaults": { 701 | "color": { 702 | "mode": "continuous-BlPu" 703 | }, 704 | "mappings": [], 705 | "thresholds": { 706 | "mode": "absolute", 707 | "steps": [ 708 | { 709 | "color": "green", 710 | "value": null 711 | } 712 | ] 713 | }, 714 | "unit": "locale" 715 | }, 716 | "overrides": [] 717 | }, 718 | "gridPos": { 719 | "h": 7, 720 | "w": 3, 721 | "x": 0, 722 | "y": 29 723 | }, 724 | "id": 15, 725 | "options": { 726 | "colorMode": "value", 727 | "graphMode": "area", 728 | "justifyMode": "auto", 729 | "orientation": "auto", 730 | "reduceOptions": { 731 | "calcs": [ 732 | "lastNotNull" 733 | ], 734 | "fields": "", 735 | "values": false 736 | }, 737 | "textMode": "auto" 738 | }, 739 | "pluginVersion": "10.1.5", 740 | "targets": [ 741 | { 742 | "datasource": { 743 | "name": "Prometheus", 744 | "type": "prometheus" 745 | }, 746 | "editorMode": "code", 747 | "exemplar": false, 748 | "expr": "Quilibrium_TokenInfo_unconfirmedTokenSupply / 8000000000", 749 | "instant": true, 750 | "legendFormat": "__auto", 751 | "range": false, 752 | "refId": "A" 753 | } 754 | ], 755 | "title": "UC Quil Supply", 756 | "type": "stat" 757 | }, 758 | { 759 | "datasource": { 760 | "type": "prometheus", 761 | "name":"Prometheus" 762 | }, 763 | "fieldConfig": { 764 | "defaults": { 765 | "color": { 766 | "mode": "continuous-BlPu" 767 | }, 768 | "custom": { 769 | "axisCenteredZero": false, 770 | "axisColorMode": "text", 771 | "axisGridShow": true, 772 | "axisLabel": "", 773 | "axisPlacement": "auto", 774 | "barAlignment": 0, 775 | "drawStyle": "line", 776 | "fillOpacity": 0, 777 | "gradientMode": "none", 778 | "hideFrom": { 779 | "legend": false, 780 | "tooltip": false, 781 | "viz": false 782 | }, 783 | "insertNulls": false, 784 | "lineInterpolation": "linear", 785 | "lineStyle": { 786 | "fill": "solid" 787 | }, 788 | "lineWidth": 1, 789 | "pointSize": 5, 790 | "scaleDistribution": { 791 | "log": 2, 792 | "type": "log" 793 | }, 794 | "showPoints": "never", 795 | "spanNulls": false, 796 | "stacking": { 797 | "group": "A", 798 | "mode": "none" 799 | }, 800 | "thresholdsStyle": { 801 | "mode": "off" 802 | } 803 | }, 804 | "mappings": [], 805 | "thresholds": { 806 | "mode": "absolute", 807 | "steps": [ 808 | { 809 | "color": "green", 810 | "value": null 811 | } 812 | ] 813 | } 814 | }, 815 | "overrides": [] 816 | }, 817 | "gridPos": { 818 | "h": 7, 819 | "w": 20, 820 | "x": 3, 821 | "y": 29 822 | }, 823 | "id": 14, 824 | "options": { 825 | "legend": { 826 | "calcs": [], 827 | "displayMode": "list", 828 | "placement": "bottom", 829 | "showLegend": false 830 | }, 831 | "tooltip": { 832 | "mode": "single", 833 | "sort": "none" 834 | } 835 | }, 836 | "targets": [ 837 | { 838 | "datasource": { 839 | "name": "Prometheus", 840 | "type": "prometheus" 841 | }, 842 | "editorMode": "code", 843 | "exemplar": false, 844 | "expr": "Quilibrium_TokenInfo_unconfirmedTokenSupply / 8000000000", 845 | "instant": false, 846 | "legendFormat": "__auto", 847 | "range": true, 848 | "refId": "A" 849 | } 850 | ], 851 | "title": "Unconfirmed Quil Supply", 852 | "transparent": true, 853 | "type": "timeseries" 854 | }, 855 | { 856 | "datasource": { 857 | "type": "prometheus", 858 | "name":"Prometheus" 859 | }, 860 | "fieldConfig": { 861 | "defaults": { 862 | "color": { 863 | "mode": "continuous-YlRd" 864 | }, 865 | "mappings": [], 866 | "thresholds": { 867 | "mode": "absolute", 868 | "steps": [ 869 | { 870 | "color": "green", 871 | "value": null 872 | } 873 | ] 874 | }, 875 | "unit": "locale" 876 | }, 877 | "overrides": [] 878 | }, 879 | "gridPos": { 880 | "h": 7, 881 | "w": 3, 882 | "x": 0, 883 | "y": 36 884 | }, 885 | "id": 16, 886 | "options": { 887 | "colorMode": "value", 888 | "graphMode": "area", 889 | "justifyMode": "auto", 890 | "orientation": "auto", 891 | "reduceOptions": { 892 | "calcs": [ 893 | "lastNotNull" 894 | ], 895 | "fields": "", 896 | "values": false 897 | }, 898 | "textMode": "auto" 899 | }, 900 | "pluginVersion": "10.1.5", 901 | "targets": [ 902 | { 903 | "datasource": { 904 | "name": "Prometheus", 905 | "type": "prometheus" 906 | }, 907 | "editorMode": "code", 908 | "exemplar": false, 909 | "expr": "Quilibrium_TokenInfo_ownedTokens / 8000000000", 910 | "instant": true, 911 | "legendFormat": "__auto", 912 | "range": false, 913 | "refId": "A" 914 | } 915 | ], 916 | "title": "Owned Quil", 917 | "type": "stat" 918 | }, 919 | { 920 | "datasource": { 921 | "type": "prometheus", 922 | "name":"Prometheus" 923 | }, 924 | "fieldConfig": { 925 | "defaults": { 926 | "color": { 927 | "mode": "continuous-YlRd" 928 | }, 929 | "custom": { 930 | "axisCenteredZero": false, 931 | "axisColorMode": "text", 932 | "axisGridShow": true, 933 | "axisLabel": "", 934 | "axisPlacement": "auto", 935 | "barAlignment": 0, 936 | "drawStyle": "line", 937 | "fillOpacity": 0, 938 | "gradientMode": "none", 939 | "hideFrom": { 940 | "legend": false, 941 | "tooltip": false, 942 | "viz": false 943 | }, 944 | "insertNulls": false, 945 | "lineInterpolation": "linear", 946 | "lineStyle": { 947 | "fill": "solid" 948 | }, 949 | "lineWidth": 1, 950 | "pointSize": 5, 951 | "scaleDistribution": { 952 | "log": 2, 953 | "type": "log" 954 | }, 955 | "showPoints": "never", 956 | "spanNulls": false, 957 | "stacking": { 958 | "group": "A", 959 | "mode": "none" 960 | }, 961 | "thresholdsStyle": { 962 | "mode": "off" 963 | } 964 | }, 965 | "mappings": [], 966 | "thresholds": { 967 | "mode": "absolute", 968 | "steps": [ 969 | { 970 | "color": "green", 971 | "value": null 972 | } 973 | ] 974 | } 975 | }, 976 | "overrides": [] 977 | }, 978 | "gridPos": { 979 | "h": 7, 980 | "w": 20, 981 | "x": 3, 982 | "y": 36 983 | }, 984 | "id": 17, 985 | "options": { 986 | "legend": { 987 | "calcs": [], 988 | "displayMode": "list", 989 | "placement": "bottom", 990 | "showLegend": false 991 | }, 992 | "tooltip": { 993 | "mode": "single", 994 | "sort": "none" 995 | } 996 | }, 997 | "targets": [ 998 | { 999 | "datasource": { 1000 | "name": "Prometheus", 1001 | "type": "prometheus" 1002 | }, 1003 | "editorMode": "code", 1004 | "exemplar": false, 1005 | "expr": "Quilibrium_TokenInfo_ownedTokens / 8000000000", 1006 | "instant": false, 1007 | "legendFormat": "__auto", 1008 | "range": true, 1009 | "refId": "A" 1010 | } 1011 | ], 1012 | "title": "Owned Quil Tokens", 1013 | "transparent": true, 1014 | "type": "timeseries" 1015 | }, 1016 | { 1017 | "collapsed": false, 1018 | "gridPos": { 1019 | "h": 1, 1020 | "w": 24, 1021 | "x": 0, 1022 | "y": 44 1023 | }, 1024 | "id": 8, 1025 | "panels": [], 1026 | "title": "Other Peer Info", 1027 | "type": "row" 1028 | }, 1029 | { 1030 | "datasource": { 1031 | "name": "Prometheus", 1032 | "type": "prometheus" 1033 | }, 1034 | "fieldConfig": { 1035 | "defaults": { 1036 | "color": { 1037 | "mode": "palette-classic" 1038 | }, 1039 | "custom": { 1040 | "axisCenteredZero": false, 1041 | "axisColorMode": "text", 1042 | "axisLabel": "", 1043 | "axisPlacement": "auto", 1044 | "barAlignment": 0, 1045 | "drawStyle": "line", 1046 | "fillOpacity": 0, 1047 | "gradientMode": "none", 1048 | "hideFrom": { 1049 | "legend": false, 1050 | "tooltip": false, 1051 | "viz": false 1052 | }, 1053 | "insertNulls": false, 1054 | "lineInterpolation": "linear", 1055 | "lineWidth": 1, 1056 | "pointSize": 5, 1057 | "scaleDistribution": { 1058 | "type": "linear" 1059 | }, 1060 | "showPoints": "auto", 1061 | "spanNulls": false, 1062 | "stacking": { 1063 | "group": "A", 1064 | "mode": "none" 1065 | }, 1066 | "thresholdsStyle": { 1067 | "mode": "off" 1068 | } 1069 | }, 1070 | "mappings": [], 1071 | "thresholds": { 1072 | "mode": "absolute", 1073 | "steps": [ 1074 | { 1075 | "color": "green", 1076 | "value": null 1077 | }, 1078 | { 1079 | "color": "red", 1080 | "value": 80 1081 | } 1082 | ] 1083 | } 1084 | }, 1085 | "overrides": [ 1086 | { 1087 | "__systemRef": "hideSeriesFrom", 1088 | "matcher": { 1089 | "id": "byNames", 1090 | "options": { 1091 | "mode": "exclude", 1092 | "names": [ 1093 | "{__name__=\"Quilibrium_NetworkInfo_peerScore\", instance=\"localhost:8380\", job=\"quilibrium_node_exporter\", multiaddrs=\"['/ip4/118.100.182.99/udp/8336/quic']\", peerId=\"EiA+KiRdIaZ0l8JnM7p7b4ixAXsgnetDVV0b92i/0mKkfQ==\"}" 1094 | ], 1095 | "prefix": "All except:", 1096 | "readOnly": true 1097 | } 1098 | }, 1099 | "properties": [ 1100 | { 1101 | "id": "custom.hideFrom", 1102 | "value": { 1103 | "legend": false, 1104 | "tooltip": false, 1105 | "viz": true 1106 | } 1107 | } 1108 | ] 1109 | } 1110 | ] 1111 | }, 1112 | "gridPos": { 1113 | "h": 10, 1114 | "w": 23, 1115 | "x": 0, 1116 | "y": 45 1117 | }, 1118 | "id": 2, 1119 | "options": { 1120 | "legend": { 1121 | "calcs": [ 1122 | "lastNotNull" 1123 | ], 1124 | "displayMode": "table", 1125 | "placement": "bottom", 1126 | "showLegend": true, 1127 | "sortBy": "Last *", 1128 | "sortDesc": true 1129 | }, 1130 | "tooltip": { 1131 | "mode": "single", 1132 | "sort": "none" 1133 | } 1134 | }, 1135 | "targets": [ 1136 | { 1137 | "datasource": { 1138 | "name": "Prometheus", 1139 | "type": "prometheus" 1140 | }, 1141 | "editorMode": "code", 1142 | "expr": "Quilibrium_NetworkInfo_peerScore{peerId=~\"$Peer\", multiaddrs=~\"$Address\"}", 1143 | "instant": false, 1144 | "legendFormat": "{{peerId}} ({{multiaddrs}})", 1145 | "range": true, 1146 | "refId": "A" 1147 | } 1148 | ], 1149 | "title": "NetworkInfo_peerScore", 1150 | "type": "timeseries" 1151 | }, 1152 | { 1153 | "datasource": { 1154 | "name": "Prometheus", 1155 | "type": "prometheus" 1156 | }, 1157 | "fieldConfig": { 1158 | "defaults": { 1159 | "color": { 1160 | "mode": "palette-classic" 1161 | }, 1162 | "custom": { 1163 | "axisCenteredZero": false, 1164 | "axisColorMode": "text", 1165 | "axisLabel": "", 1166 | "axisPlacement": "auto", 1167 | "barAlignment": 0, 1168 | "drawStyle": "line", 1169 | "fillOpacity": 0, 1170 | "gradientMode": "none", 1171 | "hideFrom": { 1172 | "legend": false, 1173 | "tooltip": false, 1174 | "viz": false 1175 | }, 1176 | "insertNulls": false, 1177 | "lineInterpolation": "linear", 1178 | "lineWidth": 1, 1179 | "pointSize": 5, 1180 | "scaleDistribution": { 1181 | "type": "linear" 1182 | }, 1183 | "showPoints": "never", 1184 | "spanNulls": false, 1185 | "stacking": { 1186 | "group": "A", 1187 | "mode": "none" 1188 | }, 1189 | "thresholdsStyle": { 1190 | "mode": "off" 1191 | } 1192 | }, 1193 | "mappings": [], 1194 | "thresholds": { 1195 | "mode": "absolute", 1196 | "steps": [ 1197 | { 1198 | "color": "green", 1199 | "value": null 1200 | }, 1201 | { 1202 | "color": "red", 1203 | "value": 80 1204 | } 1205 | ] 1206 | } 1207 | }, 1208 | "overrides": [] 1209 | }, 1210 | "gridPos": { 1211 | "h": 10, 1212 | "w": 23, 1213 | "x": 0, 1214 | "y": 55 1215 | }, 1216 | "id": 3, 1217 | "options": { 1218 | "legend": { 1219 | "calcs": [ 1220 | "lastNotNull" 1221 | ], 1222 | "displayMode": "table", 1223 | "placement": "bottom", 1224 | "showLegend": true 1225 | }, 1226 | "tooltip": { 1227 | "mode": "single", 1228 | "sort": "none" 1229 | } 1230 | }, 1231 | "targets": [ 1232 | { 1233 | "datasource": { 1234 | "name": "Prometheus", 1235 | "type": "prometheus" 1236 | }, 1237 | "editorMode": "code", 1238 | "expr": "Quilibrium_PeerInfo_uncooperativePeerInfo_maxFrame{}", 1239 | "instant": false, 1240 | "legendFormat": "{{peerId}} ({{multiaddrs}})", 1241 | "range": true, 1242 | "refId": "A" 1243 | } 1244 | ], 1245 | "title": "PeerInfo_uncooperativePeerInfo_maxFrame", 1246 | "type": "timeseries" 1247 | } 1248 | ], 1249 | "refresh": "5s", 1250 | "schemaVersion": 38, 1251 | "style": "dark", 1252 | "tags": [], 1253 | "templating": { 1254 | "list": [ 1255 | { 1256 | "allValue": ".*", 1257 | "current": { 1258 | "selected": false, 1259 | "text": "All", 1260 | "value": "$__all" 1261 | }, 1262 | "datasource": { 1263 | "type": "prometheus", 1264 | "name":"Prometheus" 1265 | }, 1266 | "definition": "label_values(peerId)", 1267 | "hide": 0, 1268 | "includeAll": true, 1269 | "multi": false, 1270 | "name": "Peer", 1271 | "options": [], 1272 | "query": { 1273 | "query": "label_values(peerId)", 1274 | "refId": "PrometheusVariableQueryEditor-VariableQuery" 1275 | }, 1276 | "refresh": 1, 1277 | "regex": "", 1278 | "skipUrlSync": false, 1279 | "sort": 0, 1280 | "type": "query" 1281 | }, 1282 | { 1283 | "allValue": ".*", 1284 | "current": { 1285 | "selected": false, 1286 | "text": "All", 1287 | "value": "$__all" 1288 | }, 1289 | "datasource": { 1290 | "type": "prometheus", 1291 | "name":"Prometheus" 1292 | }, 1293 | "definition": "label_values(peerId)", 1294 | "hide": 0, 1295 | "includeAll": true, 1296 | "multi": false, 1297 | "name": "Address", 1298 | "options": [], 1299 | "query": { 1300 | "query": "label_values(peerId)", 1301 | "refId": "PrometheusVariableQueryEditor-VariableQuery" 1302 | }, 1303 | "refresh": 1, 1304 | "regex": "", 1305 | "skipUrlSync": false, 1306 | "sort": 1, 1307 | "type": "query" 1308 | } 1309 | ] 1310 | }, 1311 | "time": { 1312 | "from": "now-15m", 1313 | "to": "now" 1314 | }, 1315 | "timepicker": {}, 1316 | "timezone": "", 1317 | "title": "Quilibrium Network", 1318 | "uid": "a0335ec6-f97e-4c46-a098-2d0c0d8022c3", 1319 | "version": 19, 1320 | "weekStart": "" 1321 | } 1322 | -------------------------------------------------------------------------------- /quilibrium-node-exporter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | import base58 4 | from flask import Flask, jsonify, Response 5 | import subprocess 6 | import json 7 | 8 | 9 | FRAME_FILE = "next_frame_number" 10 | NODE_ENDPOINT = "http://127.0.0.1:8379/quilibrium.node.node.pb.NodeService" 11 | 12 | base58_keys = ["peerId"] 13 | bigEndianKeys = ["unconfirmedTokenSupply", "confirmedTokenSupply", "ownedTokens"] 14 | 15 | app = Flask(__name__) 16 | 17 | 18 | def fetch_data(command): 19 | result = subprocess.run(command, capture_output=True, text=True) 20 | return json.loads(result.stdout) 21 | 22 | def is_valid_base64(input_string): 23 | try: 24 | decoded_string = base64.b64decode(input_string, validate=True) 25 | return True 26 | except Exception as e: 27 | return False 28 | 29 | def get_last_frame(): 30 | if os.path.exists(FRAME_FILE): 31 | try: 32 | with open(FRAME_FILE, 'r') as file: 33 | return int(file.read().strip()) 34 | except ValueError: 35 | # If there's an error converting the file content to an integer, 36 | # write zero to the file and return zero. 37 | print("Error reading the frame number. Writing zero to the file.") 38 | set_next_frame(0) 39 | return 0 40 | except Exception as e: 41 | # If there's any other kind of error, log it and handle as needed. 42 | print(f"An unexpected error occurred: {e}") 43 | # Handle other exceptions as needed. 44 | return get_peer_max_frame() # Assuming this is a function defined elsewhere. 45 | 46 | def set_next_frame(frame_number): 47 | try: 48 | with open(FRAME_FILE, 'w') as file: 49 | file.write(str(frame_number)) 50 | except Exception as e: 51 | # If an error occurs, write zero to the file 52 | print(f"Error occurred: {e}. Writing zero to the file.") 53 | try: 54 | with open(FRAME_FILE, 'w') as file: 55 | file.write('0') 56 | except Exception as e: 57 | # Handle the case where writing zero also fails 58 | print(f"Failed to write zero to the file: {e}") 59 | 60 | def get_peer_max_frame(): 61 | peer_info = fetch_data(['curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetPeerInfo']) 62 | max_frames = 0 63 | for peer in peer_info.get('peerInfo'): 64 | peer_mf = int(peer.get('maxFrame', 0)) 65 | if peer_mf > max_frames: 66 | max_frames = peer_mf 67 | return max_frames 68 | 69 | def fetch_frame_data(from_frame, to_frame): 70 | command = [ 71 | 'curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetFrames', 72 | '-H', 'Content-Type: application/json', 73 | '-d', f'{{"from_frame_number":{from_frame}, "to_frame_number":{to_frame}, "include_candidates": true}}' 74 | ] 75 | #print(command) 76 | result = subprocess.run(command, capture_output=True, text=True) 77 | return json.loads(result.stdout) 78 | 79 | def get_latest_frame(): 80 | last_frame = get_last_frame() 81 | last_data = [] 82 | direction = -1 # start by going backwards 83 | 84 | while True: 85 | from_frame = last_frame 86 | to_frame = last_frame + 1 87 | 88 | #print(f"Trying frame: {to_frame}") 89 | 90 | # fetching data 91 | data = fetch_frame_data(from_frame, to_frame) 92 | #print("Data:") 93 | #print(data) 94 | 95 | frame_exists = data.get('truncatedClockFrames') and \ 96 | data['truncatedClockFrames'][0].get('frameNumber') 97 | 98 | # If frame was not provided even once 99 | if not frame_exists and not last_data: 100 | break 101 | 102 | # If frame doesn't exist and we're going backwards 103 | if not frame_exists and direction == -1: 104 | last_frame += direction 105 | 106 | # If frame exists and we're going backwards 107 | elif frame_exists and direction == -1: 108 | direction = 1 # change direction to forwards 109 | last_data = data # update last_data 110 | last_frame += direction 111 | 112 | # If frame exists and we're going forwards 113 | elif frame_exists and direction == 1: 114 | last_data = data # update last_data 115 | last_frame += direction 116 | 117 | # If frame doesn't exist and we're going forwards 118 | elif not frame_exists and direction == 1: 119 | break 120 | 121 | # Update the file to contain the next frame pointer 122 | set_next_frame(from_frame) 123 | return last_data 124 | 125 | 126 | @app.route('/metrics') 127 | def combined_data(): 128 | # Use the NODE_ENDPOINT variable in the curl commands 129 | latest_frame = get_latest_frame() 130 | network_info = fetch_data(['curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetNetworkInfo']) 131 | peer_info = fetch_data(['curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetPeerInfo']) 132 | token_info = fetch_data(['curl', '-sX', 'POST', f'{NODE_ENDPOINT}/GetTokenInfo']) 133 | 134 | # Combine the data 135 | quil_metrics = { 136 | "LatestFrame": latest_frame, 137 | "NetworkInfo": network_info, 138 | "PeerInfo": peer_info, 139 | "TokenInfo": {"tokenInfo": [token_info]} 140 | } 141 | #print(quil_metrics) 142 | 143 | # Return the combined data as JSON 144 | #return jsonify(quil_metrics) 145 | 146 | # Format the data for Prometheus 147 | prometheus_data = "\n".join(format_to_prometheus(quil_metrics)) 148 | 149 | # Return the formatted data with the appropriate content type 150 | return Response(prometheus_data, content_type="text/plain") 151 | 152 | def format_to_prometheus(data): 153 | """Format data to Prometheus text format ensuring non-repetitive key names.""" 154 | output = [] 155 | 156 | for main_key, main_value in data.items(): 157 | if isinstance(main_value, dict): 158 | for sub_key, sub_value in main_value.items(): 159 | if isinstance(sub_value, list): 160 | for index, item in enumerate(sub_value): 161 | labels = {} 162 | # Avoid extending the key name with the same value 163 | metric_name = f"Quilibrium_{main_key}" if main_key.lower() == sub_key.lower() else f"Quilibrium_{main_key}_{sub_key}" 164 | 165 | for key, value in item.items(): 166 | #print(value) 167 | 168 | if key in bigEndianKeys and is_valid_base64(value): 169 | # Base64 decode the value 170 | decoded_bytes = base64.b64decode(value) 171 | 172 | # Convert the value to big endian 173 | value = int.from_bytes(decoded_bytes, byteorder='big') 174 | 175 | # Combine the base metric name with the current key to create a unique metric name 176 | complete_metric_name = metric_name + f"_{key}" 177 | label_str = ",".join([f'{k}="{v}"' for k, v in labels.items()]) 178 | label_part = "{" + label_str + "}" if label_str else "" 179 | metric = f"{complete_metric_name}{label_part} {int(value)}" 180 | output.append(metric) 181 | 182 | elif key in base58_keys and is_valid_base64(value): 183 | # Base64 decode the value 184 | decoded_bytes = base64.b64decode(value) 185 | # Base58 encode the decoded bytes 186 | encoded_peerId = base58.b58encode(decoded_bytes).decode('utf-8') 187 | # Update the labels dictionary with the newly encoded value 188 | labels[key] = encoded_peerId 189 | 190 | # Check if the value is an integer or an integer represented as a string 191 | elif isinstance(value, int) or (isinstance(value, str) and value.isdigit()): 192 | # Combine the base metric name with the current key to create a unique metric name 193 | complete_metric_name = metric_name + f"_{key}" 194 | label_str = ",".join([f'{k}="{v}"' for k, v in labels.items()]) 195 | label_part = "{" + label_str + "}" if label_str else "" 196 | metric = f"{complete_metric_name}{label_part} {int(value)}" 197 | output.append(metric) 198 | 199 | else: 200 | labels[key] = value 201 | 202 | return output 203 | 204 | 205 | if __name__ == '__main__': 206 | app.run(host='127.0.0.1', port=8380) 207 | --------------------------------------------------------------------------------