├── .gitignore ├── LICENSE ├── README.md ├── example ├── client │ └── client.go └── server │ └── server.go ├── ginprom-service-k8s.json ├── ginprom-service.json └── middleware.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # IDE 15 | .idea/ 16 | .vscode/ 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) chenjiandongx 2019~now 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
3 | Prometheus metrics exporter for Gin. Inspired by Depado/ginprom. 4 |
5 | 6 | ### 🔰 Installation 7 | 8 | ```shell 9 | $ go get -u github.com/chenjiandongx/ginprom 10 | ``` 11 | 12 | ### 📝 Usage 13 | 14 | It's easy to get started with ginprom, only a few lines of code needed. 15 | 16 | ```golang 17 | import ( 18 | "github.com/chenjiandongx/ginprom" 19 | "github.com/gin-gonic/gin" 20 | "github.com/prometheus/client_golang/prometheus/promhttp" 21 | ) 22 | 23 | func main() { 24 | r := gin.Default() 25 | // use prometheus metrics exporter middleware. 26 | // 27 | // ginprom.PromMiddleware() expects a ginprom.PromOpts{} poniter. 28 | // It is used for filtering labels by regex. `nil` will pass every requests. 29 | // 30 | // ginprom promethues-labels: 31 | // `status`, `endpoint`, `method` 32 | // 33 | // for example: 34 | // 1). I don't want to record the 404 status request. That's easy for it. 35 | // ginprom.PromMiddleware(&ginprom.PromOpts{ExcludeRegexStatus: "404"}) 36 | // 37 | // 2). And I wish to ignore endpoints started with `/prefix`. 38 | // ginprom.PromMiddleware(&ginprom.PromOpts{ExcludeRegexEndpoint: "^/prefix"}) 39 | r.Use(ginprom.PromMiddleware(nil)) 40 | 41 | // register the `/metrics` route. 42 | r.GET("/metrics", ginprom.PromHandler(promhttp.Handler())) 43 | 44 | // your working routes 45 | r.GET("/", func(c *gin.Context) { 46 | c.JSON(http.StatusOK, gin.H{"message": "home"}) 47 | }) 48 | } 49 | ``` 50 | 51 | ### 🎉 Metrics 52 | 53 | Details about exposed Prometheus metrics. 54 | 55 | | Name | Type | Exposed Information | 56 | | ---- | ---- | ---------------------| 57 | | service_uptime | Counter | HTTP service uptime. | 58 | | service_http_request_count_total | Counter | Total number of HTTP requests made. | 59 | | service_http_request_duration_seconds | Histogram | HTTP request latencies in seconds. | 60 | | service_http_request_size_bytes | Summary | HTTP request sizes in bytes. | 61 | | service_http_response_size_bytes | Summary |HTTP response sizes in bytes. | 62 | 63 | 64 | ### 📊 Grafana 65 | 66 | Although Promethues offers a simple dashboard, Grafana is clearly a better choice. [Grafana configuration](./ginprom-service.json). 67 | 68 |  69 | 70 | 71 | ### 📃 LICENSE 72 | 73 | MIT [©chenjiandongx](https://github.com/chenjiandongx) 74 | -------------------------------------------------------------------------------- /example/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | const maxCurrency = 10 13 | 14 | func floodRequest() { 15 | // reused client object 16 | client := &http.Client{} 17 | endpoints := []string{"/", "/index", "/forbidden", "/badreq"} 18 | for { 19 | u := fmt.Sprintf("http://localhost:4433%s", endpoints[rand.Int()%4]) 20 | req, _ := http.NewRequest(http.MethodGet, u, nil) 21 | if _, err := client.Do(req); err != nil { 22 | log.Printf("request error: %v", err) 23 | } 24 | time.Sleep(time.Millisecond * 250) 25 | } 26 | } 27 | 28 | func main() { 29 | wg := sync.WaitGroup{} 30 | wg.Add(1) 31 | 32 | for i := 0; i < maxCurrency; i++ { 33 | go floodRequest() 34 | } 35 | wg.Wait() 36 | } 37 | -------------------------------------------------------------------------------- /example/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/chenjiandongx/ginprom" 10 | "github.com/gin-gonic/gin" 11 | "github.com/prometheus/client_golang/prometheus/promhttp" 12 | ) 13 | 14 | func init() { 15 | rand.Seed(time.Now().Unix()) 16 | } 17 | 18 | func zzZ() { 19 | time.Sleep(time.Millisecond * time.Duration(rand.Int()%1000)) 20 | } 21 | 22 | func main() { 23 | r := gin.Default() 24 | r.Use(ginprom.PromMiddleware(nil)) 25 | 26 | r.GET("/metrics", ginprom.PromHandler(promhttp.Handler())) 27 | 28 | r.GET("/", func(c *gin.Context) { 29 | zzZ() 30 | c.JSON(http.StatusOK, gin.H{ 31 | "message": "home", 32 | }) 33 | }) 34 | 35 | r.GET("/index", func(c *gin.Context) { 36 | zzZ() 37 | c.JSON(http.StatusOK, gin.H{ 38 | "message": "index", 39 | }) 40 | }) 41 | 42 | r.GET("/forbidden", func(c *gin.Context) { 43 | zzZ() 44 | c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ 45 | "message": "forbidden", 46 | }) 47 | }) 48 | 49 | r.GET("/badreq", func(c *gin.Context) { 50 | zzZ() 51 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ 52 | "message": "badreq", 53 | }) 54 | }) 55 | 56 | if err := r.Run(":4433"); err != nil { 57 | log.Fatalf("run server error: %v", err) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ginprom-service-k8s.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": 1, 18 | "id": 3, 19 | "iteration": 1577457684434, 20 | "links": [], 21 | "panels": [ 22 | { 23 | "cacheTimeout": null, 24 | "colorBackground": false, 25 | "colorValue": true, 26 | "colors": [ 27 | "#5794F2", 28 | "#5794F2", 29 | "#5794F2" 30 | ], 31 | "datasource": "$datasource", 32 | "format": "dateTimeAsIso", 33 | "gauge": { 34 | "maxValue": 100, 35 | "minValue": 0, 36 | "show": false, 37 | "thresholdLabels": false, 38 | "thresholdMarkers": true 39 | }, 40 | "gridPos": { 41 | "h": 6, 42 | "w": 6, 43 | "x": 0, 44 | "y": 0 45 | }, 46 | "id": 14, 47 | "interval": null, 48 | "links": [], 49 | "mappingType": 1, 50 | "mappingTypes": [ 51 | { 52 | "name": "value to text", 53 | "value": 1 54 | }, 55 | { 56 | "name": "range to text", 57 | "value": 2 58 | } 59 | ], 60 | "maxDataPoints": 100, 61 | "nullPointMode": "connected", 62 | "nullText": null, 63 | "options": {}, 64 | "postfix": "", 65 | "postfixFontSize": "50%", 66 | "prefix": "", 67 | "prefixFontSize": "50%", 68 | "rangeMaps": [ 69 | { 70 | "from": "null", 71 | "text": "N/A", 72 | "to": "null" 73 | } 74 | ], 75 | "sparkline": { 76 | "fillColor": "rgba(31, 118, 189, 0.18)", 77 | "full": false, 78 | "lineColor": "rgb(31, 120, 193)", 79 | "show": false 80 | }, 81 | "tableColumn": "", 82 | "targets": [ 83 | { 84 | "expr": "max((time() - service_uptime{job=~\"$job\"}) * 1000)", 85 | "format": "time_series", 86 | "intervalFactor": 1, 87 | "refId": "A" 88 | } 89 | ], 90 | "thresholds": "", 91 | "timeFrom": null, 92 | "timeShift": null, 93 | "title": "Service last updated", 94 | "type": "singlestat", 95 | "valueFontSize": "100%", 96 | "valueMaps": [ 97 | { 98 | "op": "=", 99 | "text": "N/A", 100 | "value": "null" 101 | } 102 | ], 103 | "valueName": "current" 104 | }, 105 | { 106 | "aliasColors": {}, 107 | "bars": false, 108 | "dashLength": 10, 109 | "dashes": false, 110 | "datasource": "$datasource", 111 | "decimals": null, 112 | "fill": 1, 113 | "fillGradient": 0, 114 | "gridPos": { 115 | "h": 12, 116 | "w": 18, 117 | "x": 6, 118 | "y": 0 119 | }, 120 | "id": 2, 121 | "legend": { 122 | "alignAsTable": true, 123 | "avg": true, 124 | "current": true, 125 | "max": true, 126 | "min": false, 127 | "rightSide": false, 128 | "show": true, 129 | "total": false, 130 | "values": true 131 | }, 132 | "lines": true, 133 | "linewidth": 2, 134 | "links": [], 135 | "nullPointMode": "null as zero", 136 | "options": { 137 | "dataLinks": [] 138 | }, 139 | "percentage": false, 140 | "pointradius": 2, 141 | "points": false, 142 | "renderer": "flot", 143 | "seriesOverrides": [], 144 | "spaceLength": 10, 145 | "stack": false, 146 | "steppedLine": false, 147 | "targets": [ 148 | { 149 | "expr": "sum(rate(service_http_request_count_total{job=~\"$job\", exported_endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])) by (exported_endpoint)", 150 | "format": "time_series", 151 | "intervalFactor": 1, 152 | "legendFormat": "{{ exported_endpoint }}", 153 | "refId": "A" 154 | } 155 | ], 156 | "thresholds": [], 157 | "timeFrom": null, 158 | "timeRegions": [], 159 | "timeShift": null, 160 | "title": "Requests QPS", 161 | "tooltip": { 162 | "shared": true, 163 | "sort": 0, 164 | "value_type": "individual" 165 | }, 166 | "type": "graph", 167 | "xaxis": { 168 | "buckets": null, 169 | "mode": "time", 170 | "name": null, 171 | "show": true, 172 | "values": [] 173 | }, 174 | "yaxes": [ 175 | { 176 | "format": "short", 177 | "label": null, 178 | "logBase": 1, 179 | "max": null, 180 | "min": null, 181 | "show": true 182 | }, 183 | { 184 | "format": "short", 185 | "label": null, 186 | "logBase": 1, 187 | "max": null, 188 | "min": null, 189 | "show": true 190 | } 191 | ], 192 | "yaxis": { 193 | "align": false, 194 | "alignLevel": null 195 | } 196 | }, 197 | { 198 | "cacheTimeout": null, 199 | "colorBackground": false, 200 | "colorValue": true, 201 | "colors": [ 202 | "#299c46", 203 | "#73BF69", 204 | "#d44a3a" 205 | ], 206 | "datasource": "$datasource", 207 | "format": "s", 208 | "gauge": { 209 | "maxValue": 100, 210 | "minValue": 0, 211 | "show": false, 212 | "thresholdLabels": false, 213 | "thresholdMarkers": true 214 | }, 215 | "gridPos": { 216 | "h": 6, 217 | "w": 6, 218 | "x": 0, 219 | "y": 6 220 | }, 221 | "id": 12, 222 | "interval": null, 223 | "links": [], 224 | "mappingType": 1, 225 | "mappingTypes": [ 226 | { 227 | "name": "value to text", 228 | "value": 1 229 | }, 230 | { 231 | "name": "range to text", 232 | "value": 2 233 | } 234 | ], 235 | "maxDataPoints": 100, 236 | "nullPointMode": "connected", 237 | "nullText": null, 238 | "options": {}, 239 | "pluginVersion": "6.2.1", 240 | "postfix": "", 241 | "postfixFontSize": "50%", 242 | "prefix": "", 243 | "prefixFontSize": "50%", 244 | "rangeMaps": [ 245 | { 246 | "from": "null", 247 | "text": "N/A", 248 | "to": "null" 249 | } 250 | ], 251 | "sparkline": { 252 | "fillColor": "rgba(31, 118, 189, 0.18)", 253 | "full": false, 254 | "lineColor": "rgb(31, 120, 193)", 255 | "show": false 256 | }, 257 | "tableColumn": "", 258 | "targets": [ 259 | { 260 | "expr": "max(service_uptime{job=~\"$job\"})", 261 | "format": "time_series", 262 | "intervalFactor": 1, 263 | "refId": "A" 264 | } 265 | ], 266 | "thresholds": "", 267 | "timeFrom": null, 268 | "timeShift": null, 269 | "title": "Service Uptime", 270 | "type": "singlestat", 271 | "valueFontSize": "110%", 272 | "valueMaps": [ 273 | { 274 | "op": "=", 275 | "text": "N/A", 276 | "value": "null" 277 | } 278 | ], 279 | "valueName": "current" 280 | }, 281 | { 282 | "aliasColors": {}, 283 | "bars": false, 284 | "dashLength": 10, 285 | "dashes": false, 286 | "datasource": "$datasource", 287 | "fill": 1, 288 | "fillGradient": 0, 289 | "gridPos": { 290 | "h": 11, 291 | "w": 12, 292 | "x": 0, 293 | "y": 12 294 | }, 295 | "id": 4, 296 | "legend": { 297 | "alignAsTable": true, 298 | "avg": true, 299 | "current": true, 300 | "max": true, 301 | "min": false, 302 | "show": true, 303 | "total": false, 304 | "values": true 305 | }, 306 | "lines": true, 307 | "linewidth": 2, 308 | "links": [], 309 | "nullPointMode": "null as zero", 310 | "options": { 311 | "dataLinks": [] 312 | }, 313 | "percentage": false, 314 | "pointradius": 2, 315 | "points": false, 316 | "renderer": "flot", 317 | "seriesOverrides": [], 318 | "spaceLength": 10, 319 | "stack": false, 320 | "steppedLine": false, 321 | "targets": [ 322 | { 323 | "expr": "sum(rate(service_http_request_size_bytes_sum{job=~\"$job\", exported_endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])) by (exported_endpoint)\n/\nsum(rate(service_http_request_size_bytes_count{job=~\"$job\", exported_endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])) by (exported_endpoint)", 324 | "format": "time_series", 325 | "intervalFactor": 1, 326 | "legendFormat": "{{ exported_endpoint }}", 327 | "refId": "A" 328 | } 329 | ], 330 | "thresholds": [], 331 | "timeFrom": null, 332 | "timeRegions": [], 333 | "timeShift": null, 334 | "title": "Request size(bytes) per second", 335 | "tooltip": { 336 | "shared": true, 337 | "sort": 0, 338 | "value_type": "individual" 339 | }, 340 | "type": "graph", 341 | "xaxis": { 342 | "buckets": null, 343 | "mode": "time", 344 | "name": null, 345 | "show": true, 346 | "values": [] 347 | }, 348 | "yaxes": [ 349 | { 350 | "format": "short", 351 | "label": null, 352 | "logBase": 1, 353 | "max": null, 354 | "min": null, 355 | "show": true 356 | }, 357 | { 358 | "format": "short", 359 | "label": null, 360 | "logBase": 1, 361 | "max": null, 362 | "min": null, 363 | "show": true 364 | } 365 | ], 366 | "yaxis": { 367 | "align": false, 368 | "alignLevel": null 369 | } 370 | }, 371 | { 372 | "aliasColors": {}, 373 | "bars": false, 374 | "dashLength": 10, 375 | "dashes": false, 376 | "datasource": "$datasource", 377 | "fill": 1, 378 | "fillGradient": 0, 379 | "gridPos": { 380 | "h": 11, 381 | "w": 12, 382 | "x": 12, 383 | "y": 12 384 | }, 385 | "id": 8, 386 | "legend": { 387 | "alignAsTable": true, 388 | "avg": true, 389 | "current": true, 390 | "max": true, 391 | "min": false, 392 | "show": true, 393 | "total": false, 394 | "values": true 395 | }, 396 | "lines": true, 397 | "linewidth": 2, 398 | "links": [], 399 | "nullPointMode": "null as zero", 400 | "options": { 401 | "dataLinks": [] 402 | }, 403 | "percentage": false, 404 | "pointradius": 2, 405 | "points": false, 406 | "renderer": "flot", 407 | "seriesOverrides": [], 408 | "spaceLength": 10, 409 | "stack": false, 410 | "steppedLine": false, 411 | "targets": [ 412 | { 413 | "expr": "sum(rate(service_http_request_duration_seconds_sum{job=~\"$job\", exported_endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])\r) by (exported_endpoint)\n/\r\nsum(rate(service_http_request_duration_seconds_count{job=~\"$job\", exported_endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])) by (exported_endpoint)", 414 | "format": "time_series", 415 | "intervalFactor": 1, 416 | "legendFormat": "{{ exported_endpoint }}", 417 | "refId": "A" 418 | } 419 | ], 420 | "thresholds": [], 421 | "timeFrom": null, 422 | "timeRegions": [], 423 | "timeShift": null, 424 | "title": "Duration per request", 425 | "tooltip": { 426 | "shared": true, 427 | "sort": 0, 428 | "value_type": "individual" 429 | }, 430 | "type": "graph", 431 | "xaxis": { 432 | "buckets": null, 433 | "mode": "time", 434 | "name": null, 435 | "show": true, 436 | "values": [] 437 | }, 438 | "yaxes": [ 439 | { 440 | "format": "short", 441 | "label": null, 442 | "logBase": 1, 443 | "max": null, 444 | "min": null, 445 | "show": true 446 | }, 447 | { 448 | "format": "short", 449 | "label": null, 450 | "logBase": 1, 451 | "max": null, 452 | "min": null, 453 | "show": true 454 | } 455 | ], 456 | "yaxis": { 457 | "align": false, 458 | "alignLevel": null 459 | } 460 | }, 461 | { 462 | "aliasColors": {}, 463 | "bars": false, 464 | "dashLength": 10, 465 | "dashes": false, 466 | "datasource": "$datasource", 467 | "fill": 1, 468 | "fillGradient": 0, 469 | "gridPos": { 470 | "h": 12, 471 | "w": 12, 472 | "x": 0, 473 | "y": 23 474 | }, 475 | "id": 10, 476 | "legend": { 477 | "avg": true, 478 | "current": true, 479 | "max": true, 480 | "min": false, 481 | "show": true, 482 | "total": false, 483 | "values": true 484 | }, 485 | "lines": true, 486 | "linewidth": 2, 487 | "links": [], 488 | "nullPointMode": "null as zero", 489 | "options": { 490 | "dataLinks": [] 491 | }, 492 | "percentage": false, 493 | "pointradius": 2, 494 | "points": false, 495 | "renderer": "flot", 496 | "seriesOverrides": [], 497 | "spaceLength": 10, 498 | "stack": false, 499 | "steppedLine": false, 500 | "targets": [ 501 | { 502 | "expr": "histogram_quantile($quantile, sum(rate(service_http_request_duration_seconds_bucket{job=~\"$job\", exported_endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])) by (le))", 503 | "format": "time_series", 504 | "intervalFactor": 1, 505 | "legendFormat": "{{ exported_endpoint }}", 506 | "refId": "A" 507 | } 508 | ], 509 | "thresholds": [], 510 | "timeFrom": null, 511 | "timeRegions": [], 512 | "timeShift": null, 513 | "title": "Quantile of requests", 514 | "tooltip": { 515 | "shared": true, 516 | "sort": 0, 517 | "value_type": "individual" 518 | }, 519 | "type": "graph", 520 | "xaxis": { 521 | "buckets": null, 522 | "mode": "time", 523 | "name": null, 524 | "show": true, 525 | "values": [] 526 | }, 527 | "yaxes": [ 528 | { 529 | "format": "short", 530 | "label": null, 531 | "logBase": 1, 532 | "max": null, 533 | "min": null, 534 | "show": true 535 | }, 536 | { 537 | "format": "short", 538 | "label": null, 539 | "logBase": 1, 540 | "max": null, 541 | "min": null, 542 | "show": true 543 | } 544 | ], 545 | "yaxis": { 546 | "align": false, 547 | "alignLevel": null 548 | } 549 | }, 550 | { 551 | "aliasColors": {}, 552 | "bars": false, 553 | "dashLength": 10, 554 | "dashes": false, 555 | "datasource": "$datasource", 556 | "description": "", 557 | "fill": 1, 558 | "fillGradient": 0, 559 | "gridPos": { 560 | "h": 12, 561 | "w": 12, 562 | "x": 12, 563 | "y": 23 564 | }, 565 | "id": 6, 566 | "legend": { 567 | "alignAsTable": true, 568 | "avg": true, 569 | "current": true, 570 | "max": true, 571 | "min": false, 572 | "show": true, 573 | "total": false, 574 | "values": true 575 | }, 576 | "lines": true, 577 | "linewidth": 2, 578 | "links": [], 579 | "nullPointMode": "null as zero", 580 | "options": { 581 | "dataLinks": [] 582 | }, 583 | "percentage": false, 584 | "pointradius": 2, 585 | "points": false, 586 | "renderer": "flot", 587 | "seriesOverrides": [], 588 | "spaceLength": 10, 589 | "stack": false, 590 | "steppedLine": false, 591 | "targets": [ 592 | { 593 | "expr": "sum(rate(service_http_response_size_bytes_sum{job=~\"$job\", exported_endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])) by (exported_endpoint)\n/\nsum(rate(service_http_response_size_bytes_count{job=~\"$job\", exported_endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])) by (exported_endpoint)", 594 | "format": "time_series", 595 | "intervalFactor": 1, 596 | "legendFormat": "{{ exported_endpoint }}", 597 | "refId": "A" 598 | } 599 | ], 600 | "thresholds": [], 601 | "timeFrom": null, 602 | "timeRegions": [], 603 | "timeShift": null, 604 | "title": "Response size(bytes) per second", 605 | "tooltip": { 606 | "shared": true, 607 | "sort": 0, 608 | "value_type": "individual" 609 | }, 610 | "type": "graph", 611 | "xaxis": { 612 | "buckets": null, 613 | "mode": "time", 614 | "name": null, 615 | "show": true, 616 | "values": [] 617 | }, 618 | "yaxes": [ 619 | { 620 | "format": "short", 621 | "label": null, 622 | "logBase": 1, 623 | "max": null, 624 | "min": null, 625 | "show": true 626 | }, 627 | { 628 | "format": "short", 629 | "label": null, 630 | "logBase": 1, 631 | "max": null, 632 | "min": null, 633 | "show": true 634 | } 635 | ], 636 | "yaxis": { 637 | "align": false, 638 | "alignLevel": null 639 | } 640 | } 641 | ], 642 | "refresh": false, 643 | "schemaVersion": 20, 644 | "style": "dark", 645 | "tags": [ 646 | "Service" 647 | ], 648 | "templating": { 649 | "list": [ 650 | { 651 | "allValue": null, 652 | "current": { 653 | "text": "", 654 | "value": "" 655 | }, 656 | "datasource": "$datasource", 657 | "definition": "label_values(service_http_request_count_total, job)", 658 | "hide": 0, 659 | "includeAll": false, 660 | "label": "Job", 661 | "multi": false, 662 | "name": "job", 663 | "options": [], 664 | "query": "label_values(service_http_request_count_total, job)", 665 | "refresh": 1, 666 | "regex": "", 667 | "skipUrlSync": false, 668 | "sort": 1, 669 | "tagValuesQuery": "", 670 | "tags": [], 671 | "tagsQuery": "", 672 | "type": "query", 673 | "useTags": false 674 | }, 675 | { 676 | "current": { 677 | "text": "Prometheus", 678 | "value": "Prometheus" 679 | }, 680 | "hide": 2, 681 | "includeAll": false, 682 | "label": null, 683 | "multi": false, 684 | "name": "datasource", 685 | "options": [], 686 | "query": "prometheus", 687 | "refresh": 1, 688 | "regex": "", 689 | "skipUrlSync": false, 690 | "type": "datasource" 691 | }, 692 | { 693 | "auto": false, 694 | "auto_count": 30, 695 | "auto_min": "10s", 696 | "current": { 697 | "text": "1m", 698 | "value": "1m" 699 | }, 700 | "hide": 0, 701 | "label": "Interval", 702 | "name": "interval", 703 | "options": [ 704 | { 705 | "selected": true, 706 | "text": "1m", 707 | "value": "1m" 708 | }, 709 | { 710 | "selected": false, 711 | "text": "10m", 712 | "value": "10m" 713 | }, 714 | { 715 | "selected": false, 716 | "text": "30m", 717 | "value": "30m" 718 | }, 719 | { 720 | "selected": false, 721 | "text": "1h", 722 | "value": "1h" 723 | }, 724 | { 725 | "selected": false, 726 | "text": "6h", 727 | "value": "6h" 728 | }, 729 | { 730 | "selected": false, 731 | "text": "12h", 732 | "value": "12h" 733 | }, 734 | { 735 | "selected": false, 736 | "text": "1d", 737 | "value": "1d" 738 | }, 739 | { 740 | "selected": false, 741 | "text": "7d", 742 | "value": "7d" 743 | }, 744 | { 745 | "selected": false, 746 | "text": "14d", 747 | "value": "14d" 748 | }, 749 | { 750 | "selected": false, 751 | "text": "30d", 752 | "value": "30d" 753 | } 754 | ], 755 | "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d", 756 | "refresh": 2, 757 | "skipUrlSync": false, 758 | "type": "interval" 759 | }, 760 | { 761 | "allValue": null, 762 | "current": { 763 | "text": "All", 764 | "value": "$__all" 765 | }, 766 | "datasource": "Prometheus", 767 | "definition": "label_values(service_http_request_count_total{exported_endpoint!~\"/metrics\", job=~\"$job\"}, exported_endpoint)", 768 | "hide": 0, 769 | "includeAll": true, 770 | "label": "Endpoint", 771 | "multi": false, 772 | "name": "endpoint", 773 | "options": [], 774 | "query": "label_values(service_http_request_count_total{exported_endpoint!~\"/metrics\", job=~\"$job\"}, exported_endpoint)", 775 | "refresh": 1, 776 | "regex": "", 777 | "skipUrlSync": false, 778 | "sort": 0, 779 | "tagValuesQuery": "", 780 | "tags": [], 781 | "tagsQuery": "", 782 | "type": "query", 783 | "useTags": false 784 | }, 785 | { 786 | "allValue": null, 787 | "current": { 788 | "text": "GET", 789 | "value": "GET" 790 | }, 791 | "datasource": "Prometheus", 792 | "definition": "label_values(service_http_request_count_total, method)", 793 | "hide": 0, 794 | "includeAll": true, 795 | "label": "Method", 796 | "multi": false, 797 | "name": "method", 798 | "options": [], 799 | "query": "label_values(service_http_request_count_total, method)", 800 | "refresh": 1, 801 | "regex": "", 802 | "skipUrlSync": false, 803 | "sort": 0, 804 | "tagValuesQuery": "", 805 | "tags": [], 806 | "tagsQuery": "", 807 | "type": "query", 808 | "useTags": false 809 | }, 810 | { 811 | "allValue": null, 812 | "current": { 813 | "text": "200", 814 | "value": "200" 815 | }, 816 | "datasource": "Prometheus", 817 | "definition": "label_values(service_http_request_count_total, status)", 818 | "hide": 0, 819 | "includeAll": true, 820 | "label": "Status", 821 | "multi": false, 822 | "name": "status", 823 | "options": [], 824 | "query": "label_values(service_http_request_count_total, status)", 825 | "refresh": 1, 826 | "regex": "", 827 | "skipUrlSync": false, 828 | "sort": 0, 829 | "tagValuesQuery": "", 830 | "tags": [], 831 | "tagsQuery": "", 832 | "type": "query", 833 | "useTags": false 834 | }, 835 | { 836 | "allValue": null, 837 | "current": { 838 | "text": "0.25", 839 | "value": "0.25" 840 | }, 841 | "hide": 0, 842 | "includeAll": false, 843 | "label": "Quantile", 844 | "multi": false, 845 | "name": "quantile", 846 | "options": [ 847 | { 848 | "selected": false, 849 | "text": "0.95", 850 | "value": "0.95" 851 | }, 852 | { 853 | "selected": false, 854 | "text": "0.75", 855 | "value": "0.75" 856 | }, 857 | { 858 | "selected": true, 859 | "text": "0.25", 860 | "value": "0.25" 861 | } 862 | ], 863 | "query": "0.95,0.75,0.25", 864 | "skipUrlSync": false, 865 | "type": "custom" 866 | } 867 | ] 868 | }, 869 | "time": { 870 | "from": "now-1h", 871 | "to": "now" 872 | }, 873 | "timepicker": { 874 | "refresh_intervals": [ 875 | "10s", 876 | "30s", 877 | "1m", 878 | "5m", 879 | "15m", 880 | "30m", 881 | "1h", 882 | "2h", 883 | "1d" 884 | ], 885 | "time_options": [ 886 | "5m", 887 | "15m", 888 | "1h", 889 | "6h", 890 | "12h", 891 | "24h", 892 | "2d", 893 | "7d", 894 | "30d" 895 | ] 896 | }, 897 | "timezone": "browser", 898 | "title": "HTTP Services", 899 | "uid": "EyaoypOZz", 900 | "version": 6 901 | } -------------------------------------------------------------------------------- /ginprom-service.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": 242, 19 | "iteration": 1608172494931, 20 | "links": [], 21 | "panels": [ 22 | { 23 | "cacheTimeout": null, 24 | "colorBackground": false, 25 | "colorValue": true, 26 | "colors": [ 27 | "#5794F2", 28 | "#5794F2", 29 | "#5794F2" 30 | ], 31 | "datasource": "$datasource", 32 | "format": "dateTimeAsUS", 33 | "gauge": { 34 | "maxValue": 100, 35 | "minValue": 0, 36 | "show": false, 37 | "thresholdLabels": false, 38 | "thresholdMarkers": true 39 | }, 40 | "gridPos": { 41 | "h": 6, 42 | "w": 6, 43 | "x": 0, 44 | "y": 0 45 | }, 46 | "id": 14, 47 | "interval": null, 48 | "links": [], 49 | "mappingType": 1, 50 | "mappingTypes": [ 51 | { 52 | "name": "value to text", 53 | "value": 1 54 | }, 55 | { 56 | "name": "range to text", 57 | "value": 2 58 | } 59 | ], 60 | "maxDataPoints": 100, 61 | "nullPointMode": "connected", 62 | "nullText": null, 63 | "postfix": "", 64 | "postfixFontSize": "50%", 65 | "prefix": "", 66 | "prefixFontSize": "50%", 67 | "rangeMaps": [ 68 | { 69 | "from": "null", 70 | "text": "N/A", 71 | "to": "null" 72 | } 73 | ], 74 | "sparkline": { 75 | "fillColor": "rgba(31, 118, 189, 0.18)", 76 | "full": false, 77 | "lineColor": "rgb(31, 120, 193)", 78 | "show": false 79 | }, 80 | "tableColumn": "", 81 | "targets": [ 82 | { 83 | "expr": "(time() - service_uptime{job=~\"$job\"}) * 1000", 84 | "format": "time_series", 85 | "intervalFactor": 1, 86 | "refId": "A" 87 | } 88 | ], 89 | "thresholds": "", 90 | "timeFrom": null, 91 | "timeShift": null, 92 | "title": "Service last updated", 93 | "type": "singlestat", 94 | "valueFontSize": "110%", 95 | "valueMaps": [ 96 | { 97 | "op": "=", 98 | "text": "N/A", 99 | "value": "null" 100 | } 101 | ], 102 | "valueName": "current" 103 | }, 104 | { 105 | "aliasColors": {}, 106 | "bars": false, 107 | "dashLength": 10, 108 | "dashes": false, 109 | "datasource": "$datasource", 110 | "decimals": null, 111 | "fill": 1, 112 | "fillGradient": 0, 113 | "gridPos": { 114 | "h": 12, 115 | "w": 18, 116 | "x": 6, 117 | "y": 0 118 | }, 119 | "hiddenSeries": false, 120 | "id": 2, 121 | "legend": { 122 | "alignAsTable": true, 123 | "avg": true, 124 | "current": true, 125 | "max": true, 126 | "min": false, 127 | "rightSide": false, 128 | "show": true, 129 | "total": false, 130 | "values": true 131 | }, 132 | "lines": true, 133 | "linewidth": 2, 134 | "links": [], 135 | "nullPointMode": "null as zero", 136 | "options": { 137 | "dataLinks": [] 138 | }, 139 | "percentage": false, 140 | "pointradius": 2, 141 | "points": false, 142 | "renderer": "flot", 143 | "seriesOverrides": [], 144 | "spaceLength": 10, 145 | "stack": false, 146 | "steppedLine": false, 147 | "targets": [ 148 | { 149 | "expr": "rate(service_http_request_count_total{job=~\"$job\", endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])", 150 | "format": "time_series", 151 | "interval": "", 152 | "intervalFactor": 1, 153 | "legendFormat": "{{ endpoint }}-{{method}}-{{status}}", 154 | "refId": "A" 155 | } 156 | ], 157 | "thresholds": [], 158 | "timeFrom": null, 159 | "timeRegions": [], 160 | "timeShift": null, 161 | "title": "Requests QPS", 162 | "tooltip": { 163 | "shared": true, 164 | "sort": 0, 165 | "value_type": "individual" 166 | }, 167 | "type": "graph", 168 | "xaxis": { 169 | "buckets": null, 170 | "mode": "time", 171 | "name": null, 172 | "show": true, 173 | "values": [] 174 | }, 175 | "yaxes": [ 176 | { 177 | "format": "short", 178 | "label": null, 179 | "logBase": 1, 180 | "max": null, 181 | "min": null, 182 | "show": true 183 | }, 184 | { 185 | "format": "short", 186 | "label": null, 187 | "logBase": 1, 188 | "max": null, 189 | "min": null, 190 | "show": true 191 | } 192 | ], 193 | "yaxis": { 194 | "align": false, 195 | "alignLevel": null 196 | } 197 | }, 198 | { 199 | "cacheTimeout": null, 200 | "colorBackground": false, 201 | "colorValue": true, 202 | "colors": [ 203 | "#299c46", 204 | "#73BF69", 205 | "#d44a3a" 206 | ], 207 | "datasource": "$datasource", 208 | "format": "s", 209 | "gauge": { 210 | "maxValue": 100, 211 | "minValue": 0, 212 | "show": false, 213 | "thresholdLabels": false, 214 | "thresholdMarkers": true 215 | }, 216 | "gridPos": { 217 | "h": 6, 218 | "w": 6, 219 | "x": 0, 220 | "y": 6 221 | }, 222 | "id": 12, 223 | "interval": null, 224 | "links": [], 225 | "mappingType": 1, 226 | "mappingTypes": [ 227 | { 228 | "name": "value to text", 229 | "value": 1 230 | }, 231 | { 232 | "name": "range to text", 233 | "value": 2 234 | } 235 | ], 236 | "maxDataPoints": 100, 237 | "nullPointMode": "connected", 238 | "nullText": null, 239 | "pluginVersion": "6.2.1", 240 | "postfix": "", 241 | "postfixFontSize": "50%", 242 | "prefix": "", 243 | "prefixFontSize": "50%", 244 | "rangeMaps": [ 245 | { 246 | "from": "null", 247 | "text": "N/A", 248 | "to": "null" 249 | } 250 | ], 251 | "sparkline": { 252 | "fillColor": "rgba(31, 118, 189, 0.18)", 253 | "full": false, 254 | "lineColor": "rgb(31, 120, 193)", 255 | "show": false 256 | }, 257 | "tableColumn": "", 258 | "targets": [ 259 | { 260 | "expr": "service_uptime{job=~\"$job\"}", 261 | "format": "time_series", 262 | "intervalFactor": 1, 263 | "refId": "A" 264 | } 265 | ], 266 | "thresholds": "", 267 | "timeFrom": null, 268 | "timeShift": null, 269 | "title": "Service Uptime", 270 | "type": "singlestat", 271 | "valueFontSize": "110%", 272 | "valueMaps": [ 273 | { 274 | "op": "=", 275 | "text": "N/A", 276 | "value": "null" 277 | } 278 | ], 279 | "valueName": "current" 280 | }, 281 | { 282 | "aliasColors": {}, 283 | "bars": false, 284 | "dashLength": 10, 285 | "dashes": false, 286 | "datasource": "$datasource", 287 | "fill": 1, 288 | "fillGradient": 0, 289 | "gridPos": { 290 | "h": 11, 291 | "w": 12, 292 | "x": 0, 293 | "y": 12 294 | }, 295 | "hiddenSeries": false, 296 | "id": 4, 297 | "legend": { 298 | "alignAsTable": true, 299 | "avg": true, 300 | "current": true, 301 | "max": true, 302 | "min": false, 303 | "show": true, 304 | "total": false, 305 | "values": true 306 | }, 307 | "lines": true, 308 | "linewidth": 2, 309 | "links": [], 310 | "nullPointMode": "null as zero", 311 | "options": { 312 | "dataLinks": [] 313 | }, 314 | "percentage": false, 315 | "pointradius": 2, 316 | "points": false, 317 | "renderer": "flot", 318 | "seriesOverrides": [], 319 | "spaceLength": 10, 320 | "stack": false, 321 | "steppedLine": false, 322 | "targets": [ 323 | { 324 | "expr": "rate(service_http_request_size_bytes_sum{job=~\"$job\", endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])\n/\nrate(service_http_request_size_bytes_count{job=~\"$job\", endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])", 325 | "format": "time_series", 326 | "interval": "", 327 | "intervalFactor": 1, 328 | "legendFormat": "{{ endpoint }}-{{method}}-{{status}}", 329 | "refId": "A" 330 | } 331 | ], 332 | "thresholds": [], 333 | "timeFrom": null, 334 | "timeRegions": [], 335 | "timeShift": null, 336 | "title": "Request size(bytes) per second", 337 | "tooltip": { 338 | "shared": true, 339 | "sort": 0, 340 | "value_type": "individual" 341 | }, 342 | "type": "graph", 343 | "xaxis": { 344 | "buckets": null, 345 | "mode": "time", 346 | "name": null, 347 | "show": true, 348 | "values": [] 349 | }, 350 | "yaxes": [ 351 | { 352 | "format": "bytes", 353 | "label": null, 354 | "logBase": 1, 355 | "max": null, 356 | "min": null, 357 | "show": true 358 | }, 359 | { 360 | "format": "short", 361 | "label": null, 362 | "logBase": 1, 363 | "max": null, 364 | "min": null, 365 | "show": true 366 | } 367 | ], 368 | "yaxis": { 369 | "align": false, 370 | "alignLevel": null 371 | } 372 | }, 373 | { 374 | "aliasColors": {}, 375 | "bars": false, 376 | "dashLength": 10, 377 | "dashes": false, 378 | "datasource": "$datasource", 379 | "fill": 1, 380 | "fillGradient": 0, 381 | "gridPos": { 382 | "h": 11, 383 | "w": 12, 384 | "x": 12, 385 | "y": 12 386 | }, 387 | "hiddenSeries": false, 388 | "id": 8, 389 | "legend": { 390 | "alignAsTable": true, 391 | "avg": true, 392 | "current": true, 393 | "max": true, 394 | "min": false, 395 | "show": true, 396 | "total": false, 397 | "values": true 398 | }, 399 | "lines": true, 400 | "linewidth": 2, 401 | "links": [], 402 | "nullPointMode": "null as zero", 403 | "options": { 404 | "dataLinks": [] 405 | }, 406 | "percentage": false, 407 | "pointradius": 2, 408 | "points": false, 409 | "renderer": "flot", 410 | "seriesOverrides": [], 411 | "spaceLength": 10, 412 | "stack": false, 413 | "steppedLine": false, 414 | "targets": [ 415 | { 416 | "expr": "rate(service_http_request_duration_seconds_sum{job=~\"$job\", endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])\r\n/\r\nrate(service_http_request_duration_seconds_count{job=~\"$job\", endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])", 417 | "format": "time_series", 418 | "interval": "", 419 | "intervalFactor": 1, 420 | "legendFormat": "{{ endpoint }}-{{method}}-{{status}}", 421 | "refId": "A" 422 | } 423 | ], 424 | "thresholds": [], 425 | "timeFrom": null, 426 | "timeRegions": [], 427 | "timeShift": null, 428 | "title": "Duration per request", 429 | "tooltip": { 430 | "shared": true, 431 | "sort": 0, 432 | "value_type": "individual" 433 | }, 434 | "type": "graph", 435 | "xaxis": { 436 | "buckets": null, 437 | "mode": "time", 438 | "name": null, 439 | "show": true, 440 | "values": [] 441 | }, 442 | "yaxes": [ 443 | { 444 | "format": "s", 445 | "label": null, 446 | "logBase": 1, 447 | "max": null, 448 | "min": null, 449 | "show": true 450 | }, 451 | { 452 | "format": "short", 453 | "label": null, 454 | "logBase": 1, 455 | "max": null, 456 | "min": null, 457 | "show": true 458 | } 459 | ], 460 | "yaxis": { 461 | "align": false, 462 | "alignLevel": null 463 | } 464 | }, 465 | { 466 | "aliasColors": {}, 467 | "bars": false, 468 | "dashLength": 10, 469 | "dashes": false, 470 | "datasource": "$datasource", 471 | "fill": 1, 472 | "fillGradient": 0, 473 | "gridPos": { 474 | "h": 12, 475 | "w": 12, 476 | "x": 0, 477 | "y": 23 478 | }, 479 | "hiddenSeries": false, 480 | "id": 10, 481 | "legend": { 482 | "avg": true, 483 | "current": true, 484 | "max": true, 485 | "min": false, 486 | "show": true, 487 | "total": false, 488 | "values": true 489 | }, 490 | "lines": true, 491 | "linewidth": 2, 492 | "links": [], 493 | "nullPointMode": "null as zero", 494 | "options": { 495 | "dataLinks": [] 496 | }, 497 | "percentage": false, 498 | "pointradius": 2, 499 | "points": false, 500 | "renderer": "flot", 501 | "seriesOverrides": [], 502 | "spaceLength": 10, 503 | "stack": false, 504 | "steppedLine": false, 505 | "targets": [ 506 | { 507 | "expr": "histogram_quantile($quantile, sum(rate(service_http_request_duration_seconds_bucket{job=~\"$job\", endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])) by (le))", 508 | "format": "time_series", 509 | "interval": "", 510 | "intervalFactor": 1, 511 | "legendFormat": "{{ endpoint }}", 512 | "refId": "A" 513 | } 514 | ], 515 | "thresholds": [], 516 | "timeFrom": null, 517 | "timeRegions": [], 518 | "timeShift": null, 519 | "title": "Quantile of requests", 520 | "tooltip": { 521 | "shared": true, 522 | "sort": 0, 523 | "value_type": "individual" 524 | }, 525 | "type": "graph", 526 | "xaxis": { 527 | "buckets": null, 528 | "mode": "time", 529 | "name": null, 530 | "show": true, 531 | "values": [] 532 | }, 533 | "yaxes": [ 534 | { 535 | "format": "s", 536 | "label": null, 537 | "logBase": 1, 538 | "max": null, 539 | "min": null, 540 | "show": true 541 | }, 542 | { 543 | "format": "short", 544 | "label": null, 545 | "logBase": 1, 546 | "max": null, 547 | "min": null, 548 | "show": true 549 | } 550 | ], 551 | "yaxis": { 552 | "align": false, 553 | "alignLevel": null 554 | } 555 | }, 556 | { 557 | "aliasColors": {}, 558 | "bars": false, 559 | "dashLength": 10, 560 | "dashes": false, 561 | "datasource": "$datasource", 562 | "description": "", 563 | "fill": 1, 564 | "fillGradient": 0, 565 | "gridPos": { 566 | "h": 12, 567 | "w": 12, 568 | "x": 12, 569 | "y": 23 570 | }, 571 | "hiddenSeries": false, 572 | "id": 6, 573 | "legend": { 574 | "alignAsTable": true, 575 | "avg": true, 576 | "current": true, 577 | "max": true, 578 | "min": false, 579 | "show": true, 580 | "total": false, 581 | "values": true 582 | }, 583 | "lines": true, 584 | "linewidth": 2, 585 | "links": [], 586 | "nullPointMode": "null as zero", 587 | "options": { 588 | "dataLinks": [] 589 | }, 590 | "percentage": false, 591 | "pointradius": 2, 592 | "points": false, 593 | "renderer": "flot", 594 | "seriesOverrides": [], 595 | "spaceLength": 10, 596 | "stack": false, 597 | "steppedLine": false, 598 | "targets": [ 599 | { 600 | "expr": "rate(service_http_response_size_bytes_sum{job=~\"$job\", endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])\n/\nrate(service_http_response_size_bytes_count{job=~\"$job\", endpoint=~\"$endpoint\", method=~\"$method\", status=~\"$status\"}[$interval])", 601 | "format": "time_series", 602 | "interval": "", 603 | "intervalFactor": 1, 604 | "legendFormat": "{{ endpoint }}-{{method}}-{{status}}", 605 | "refId": "A" 606 | } 607 | ], 608 | "thresholds": [], 609 | "timeFrom": null, 610 | "timeRegions": [], 611 | "timeShift": null, 612 | "title": "Response size(bytes) per second", 613 | "tooltip": { 614 | "shared": true, 615 | "sort": 0, 616 | "value_type": "individual" 617 | }, 618 | "type": "graph", 619 | "xaxis": { 620 | "buckets": null, 621 | "mode": "time", 622 | "name": null, 623 | "show": true, 624 | "values": [] 625 | }, 626 | "yaxes": [ 627 | { 628 | "format": "bytes", 629 | "label": null, 630 | "logBase": 1, 631 | "max": null, 632 | "min": null, 633 | "show": true 634 | }, 635 | { 636 | "format": "short", 637 | "label": null, 638 | "logBase": 1, 639 | "max": null, 640 | "min": null, 641 | "show": true 642 | } 643 | ], 644 | "yaxis": { 645 | "align": false, 646 | "alignLevel": null 647 | } 648 | } 649 | ], 650 | "refresh": false, 651 | "schemaVersion": 22, 652 | "style": "dark", 653 | "tags": [ 654 | "Service" 655 | ], 656 | "templating": { 657 | "list": [ 658 | { 659 | "allValue": null, 660 | "current": { 661 | "text": "", 662 | "value": "" 663 | }, 664 | "datasource": "$datasource", 665 | "definition": "label_values(service_http_request_count_total, job)", 666 | "hide": 0, 667 | "includeAll": false, 668 | "index": -1, 669 | "label": "Job", 670 | "multi": false, 671 | "name": "job", 672 | "options": [], 673 | "query": "label_values(service_http_request_count_total, job)", 674 | "refresh": 1, 675 | "regex": "", 676 | "skipUrlSync": false, 677 | "sort": 0, 678 | "tagValuesQuery": "", 679 | "tags": [], 680 | "tagsQuery": "", 681 | "type": "query", 682 | "useTags": false 683 | }, 684 | { 685 | "current": { 686 | "text": "Prometheus", 687 | "value": "Prometheus" 688 | }, 689 | "hide": 2, 690 | "includeAll": false, 691 | "label": null, 692 | "multi": false, 693 | "name": "datasource", 694 | "options": [], 695 | "query": "prometheus", 696 | "refresh": 1, 697 | "regex": "", 698 | "skipUrlSync": false, 699 | "type": "datasource" 700 | }, 701 | { 702 | "auto": false, 703 | "auto_count": 30, 704 | "auto_min": "10s", 705 | "current": { 706 | "selected": false, 707 | "text": "1m", 708 | "value": "1m" 709 | }, 710 | "hide": 0, 711 | "label": "Interval", 712 | "name": "interval", 713 | "options": [ 714 | { 715 | "selected": true, 716 | "text": "1m", 717 | "value": "1m" 718 | }, 719 | { 720 | "selected": false, 721 | "text": "10m", 722 | "value": "10m" 723 | }, 724 | { 725 | "selected": false, 726 | "text": "30m", 727 | "value": "30m" 728 | }, 729 | { 730 | "selected": false, 731 | "text": "1h", 732 | "value": "1h" 733 | }, 734 | { 735 | "selected": false, 736 | "text": "6h", 737 | "value": "6h" 738 | }, 739 | { 740 | "selected": false, 741 | "text": "12h", 742 | "value": "12h" 743 | }, 744 | { 745 | "selected": false, 746 | "text": "1d", 747 | "value": "1d" 748 | }, 749 | { 750 | "selected": false, 751 | "text": "7d", 752 | "value": "7d" 753 | }, 754 | { 755 | "selected": false, 756 | "text": "14d", 757 | "value": "14d" 758 | }, 759 | { 760 | "selected": false, 761 | "text": "30d", 762 | "value": "30d" 763 | } 764 | ], 765 | "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d", 766 | "refresh": 2, 767 | "skipUrlSync": false, 768 | "type": "interval" 769 | }, 770 | { 771 | "allValue": null, 772 | "current": { 773 | "selected": false, 774 | "text": "All", 775 | "value": "$__all" 776 | }, 777 | "datasource": "$datasource", 778 | "definition": "label_values(service_http_request_count_total{endpoint!~\"/metrics\", status!=\"404\", job=~\"$job\"}, endpoint)", 779 | "hide": 0, 780 | "includeAll": true, 781 | "index": -1, 782 | "label": "Endpoint", 783 | "multi": false, 784 | "name": "endpoint", 785 | "options": [], 786 | "query": "label_values(service_http_request_count_total{endpoint!~\"/metrics\", status!=\"404\", job=~\"$job\"}, endpoint)", 787 | "refresh": 1, 788 | "regex": "", 789 | "skipUrlSync": false, 790 | "sort": 0, 791 | "tagValuesQuery": "", 792 | "tags": [], 793 | "tagsQuery": "", 794 | "type": "query", 795 | "useTags": false 796 | }, 797 | { 798 | "allValue": null, 799 | "current": { 800 | "text": "GET", 801 | "value": "GET" 802 | }, 803 | "datasource": "$datasource", 804 | "definition": "label_values(service_http_request_count_total, method)", 805 | "hide": 0, 806 | "includeAll": true, 807 | "index": -1, 808 | "label": "Method", 809 | "multi": false, 810 | "name": "method", 811 | "options": [], 812 | "query": "label_values(service_http_request_count_total, method)", 813 | "refresh": 1, 814 | "regex": "", 815 | "skipUrlSync": false, 816 | "sort": 0, 817 | "tagValuesQuery": "", 818 | "tags": [], 819 | "tagsQuery": "", 820 | "type": "query", 821 | "useTags": false 822 | }, 823 | { 824 | "allValue": null, 825 | "current": { 826 | "text": "200", 827 | "value": "200" 828 | }, 829 | "datasource": "$datasource", 830 | "definition": "label_values(service_http_request_count_total, status)", 831 | "hide": 0, 832 | "includeAll": true, 833 | "index": -1, 834 | "label": "Status", 835 | "multi": false, 836 | "name": "status", 837 | "options": [], 838 | "query": "label_values(service_http_request_count_total, status)", 839 | "refresh": 1, 840 | "regex": "", 841 | "skipUrlSync": false, 842 | "sort": 0, 843 | "tagValuesQuery": "", 844 | "tags": [], 845 | "tagsQuery": "", 846 | "type": "query", 847 | "useTags": false 848 | }, 849 | { 850 | "allValue": null, 851 | "current": { 852 | "tags": [], 853 | "text": "0.95", 854 | "value": "0.95" 855 | }, 856 | "hide": 0, 857 | "includeAll": false, 858 | "label": "Quantile", 859 | "multi": false, 860 | "name": "quantile", 861 | "options": [ 862 | { 863 | "selected": true, 864 | "text": "0.95", 865 | "value": "0.95" 866 | }, 867 | { 868 | "selected": false, 869 | "text": "0.75", 870 | "value": "0.75" 871 | }, 872 | { 873 | "selected": false, 874 | "text": "0.25", 875 | "value": "0.25" 876 | } 877 | ], 878 | "query": "0.95,0.75,0.25", 879 | "skipUrlSync": false, 880 | "type": "custom" 881 | } 882 | ] 883 | }, 884 | "time": { 885 | "from": "now-30m", 886 | "to": "now" 887 | }, 888 | "timepicker": { 889 | "refresh_intervals": [ 890 | "10s", 891 | "30s", 892 | "1m", 893 | "5m", 894 | "15m", 895 | "30m", 896 | "1h", 897 | "2h", 898 | "1d" 899 | ], 900 | "time_options": [ 901 | "5m", 902 | "15m", 903 | "1h", 904 | "6h", 905 | "12h", 906 | "24h", 907 | "2d", 908 | "7d", 909 | "30d" 910 | ] 911 | }, 912 | "timezone": "browser", 913 | "title": "Prometheus Service", 914 | "uid": "EyaoypOZz", 915 | "variables": { 916 | "list": [] 917 | }, 918 | "version": 9 919 | } -------------------------------------------------------------------------------- /middleware.go: -------------------------------------------------------------------------------- 1 | package ginprom 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "regexp" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/prometheus/client_golang/prometheus" 11 | ) 12 | 13 | const namespace = "service" 14 | 15 | var ( 16 | labels = []string{"status", "endpoint", "method"} 17 | 18 | uptime = prometheus.NewCounterVec( 19 | prometheus.CounterOpts{ 20 | Namespace: namespace, 21 | Name: "uptime", 22 | Help: "HTTP service uptime.", 23 | }, nil, 24 | ) 25 | 26 | reqCount = prometheus.NewCounterVec( 27 | prometheus.CounterOpts{ 28 | Namespace: namespace, 29 | Name: "http_request_count_total", 30 | Help: "Total number of HTTP requests made.", 31 | }, labels, 32 | ) 33 | 34 | reqDuration = prometheus.NewHistogramVec( 35 | prometheus.HistogramOpts{ 36 | Namespace: namespace, 37 | Name: "http_request_duration_seconds", 38 | Help: "HTTP request latencies in seconds.", 39 | }, labels, 40 | ) 41 | 42 | reqSizeBytes = prometheus.NewSummaryVec( 43 | prometheus.SummaryOpts{ 44 | Namespace: namespace, 45 | Name: "http_request_size_bytes", 46 | Help: "HTTP request sizes in bytes.", 47 | }, labels, 48 | ) 49 | 50 | respSizeBytes = prometheus.NewSummaryVec( 51 | prometheus.SummaryOpts{ 52 | Namespace: namespace, 53 | Name: "http_response_size_bytes", 54 | Help: "HTTP response sizes in bytes.", 55 | }, labels, 56 | ) 57 | ) 58 | 59 | // init registers the prometheus metrics 60 | func init() { 61 | prometheus.MustRegister(uptime, reqCount, reqDuration, reqSizeBytes, respSizeBytes) 62 | go recordUptime() 63 | } 64 | 65 | // recordUptime increases service uptime per second. 66 | func recordUptime() { 67 | for range time.Tick(time.Second) { 68 | uptime.WithLabelValues().Inc() 69 | } 70 | } 71 | 72 | // calcRequestSize returns the size of request object. 73 | func calcRequestSize(r *http.Request) float64 { 74 | size := 0 75 | if r.URL != nil { 76 | size = len(r.URL.String()) 77 | } 78 | 79 | size += len(r.Method) 80 | size += len(r.Proto) 81 | 82 | for name, values := range r.Header { 83 | size += len(name) 84 | for _, value := range values { 85 | size += len(value) 86 | } 87 | } 88 | size += len(r.Host) 89 | 90 | // r.Form and r.MultipartForm are assumed to be included in r.URL. 91 | if r.ContentLength != -1 { 92 | size += int(r.ContentLength) 93 | } 94 | return float64(size) 95 | } 96 | 97 | type RequestLabelMappingFn func(c *gin.Context) string 98 | 99 | // PromOpts represents the Prometheus middleware Options. 100 | // It is used for filtering labels by regex. 101 | type PromOpts struct { 102 | ExcludeRegexStatus string 103 | ExcludeRegexEndpoint string 104 | ExcludeRegexMethod string 105 | EndpointLabelMappingFn RequestLabelMappingFn 106 | } 107 | 108 | // NewDefaultOpts return the default ProOpts 109 | func NewDefaultOpts() *PromOpts { 110 | return &PromOpts{ 111 | EndpointLabelMappingFn: func(c *gin.Context) string { 112 | //by default do nothing, return URL as is 113 | return c.Request.URL.Path 114 | }, 115 | } 116 | } 117 | 118 | // checkLabel returns the match result of labels. 119 | // Return true if regex-pattern compiles failed. 120 | func (po *PromOpts) checkLabel(label, pattern string) bool { 121 | if pattern == "" { 122 | return true 123 | } 124 | 125 | matched, err := regexp.MatchString(pattern, label) 126 | if err != nil { 127 | return true 128 | } 129 | return !matched 130 | } 131 | 132 | // PromMiddleware returns a gin.HandlerFunc for exporting some Web metrics 133 | func PromMiddleware(promOpts *PromOpts) gin.HandlerFunc { 134 | // make sure promOpts is not nil 135 | if promOpts == nil { 136 | promOpts = NewDefaultOpts() 137 | } 138 | 139 | // make sure EndpointLabelMappingFn is callable 140 | if promOpts.EndpointLabelMappingFn == nil { 141 | promOpts.EndpointLabelMappingFn = func(c *gin.Context) string { 142 | return c.Request.URL.Path 143 | } 144 | } 145 | 146 | return func(c *gin.Context) { 147 | start := time.Now() 148 | c.Next() 149 | 150 | status := fmt.Sprintf("%d", c.Writer.Status()) 151 | endpoint := promOpts.EndpointLabelMappingFn(c) 152 | method := c.Request.Method 153 | 154 | lvs := []string{status, endpoint, method} 155 | 156 | isOk := promOpts.checkLabel(status, promOpts.ExcludeRegexStatus) && 157 | promOpts.checkLabel(endpoint, promOpts.ExcludeRegexEndpoint) && 158 | promOpts.checkLabel(method, promOpts.ExcludeRegexMethod) 159 | 160 | if !isOk { 161 | return 162 | } 163 | // no response content will return -1 164 | respSize := c.Writer.Size() 165 | if respSize < 0 { 166 | respSize = 0 167 | } 168 | reqCount.WithLabelValues(lvs...).Inc() 169 | reqDuration.WithLabelValues(lvs...).Observe(time.Since(start).Seconds()) 170 | reqSizeBytes.WithLabelValues(lvs...).Observe(calcRequestSize(c.Request)) 171 | respSizeBytes.WithLabelValues(lvs...).Observe(float64(respSize)) 172 | } 173 | } 174 | 175 | // PromHandler wrappers the standard http.Handler to gin.HandlerFunc 176 | func PromHandler(handler http.Handler) gin.HandlerFunc { 177 | return func(c *gin.Context) { 178 | handler.ServeHTTP(c.Writer, c.Request) 179 | } 180 | } 181 | --------------------------------------------------------------------------------