mirror of https://github.com/gofiber/fiber.git
🔥 v3: remove monitor middleware from core to reduce deps
parent
9af3c21b7a
commit
f3067c14b3
7
go.mod
7
go.mod
|
@ -7,7 +7,6 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d
|
||||
github.com/shirou/gopsutil/v3 v3.22.5
|
||||
github.com/tinylib/msgp v1.1.6
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
github.com/valyala/fasthttp v1.37.0
|
||||
|
@ -16,15 +15,9 @@ require (
|
|||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/klauspost/compress v1.15.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/philhofer/fwd v1.1.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20220401102855-e56b59f40436 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
github.com/tklauser/numcpus v0.4.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
||||
)
|
||||
|
|
31
go.sum
31
go.sum
|
@ -1,43 +1,21 @@
|
|||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d h1:ICMDEgNgR5xFW6ZDeMKTtmh07YiLr7GkDw897I2DwKg=
|
||||
github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d/go.mod h1:jrsy/bTK2n5uybo7bAvtLGzmuzAbxp+nKS8bzgrZURE=
|
||||
github.com/savsgio/gotils v0.0.0-20220401102855-e56b59f40436 h1:sfTahD3f2BSjx9U3R4K09PkNuZZWthT7g6vzTIXNWkM=
|
||||
github.com/savsgio/gotils v0.0.0-20220401102855-e56b59f40436/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
|
||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.37.0 h1:7WHCyI7EAkQMVmrfBhWTCOaeROb1aCBiTopx63LkMbE=
|
||||
|
@ -47,8 +25,6 @@ github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+
|
|||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
|
@ -63,16 +39,13 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -86,8 +59,4 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
# Monitor
|
||||
Monitor middleware for [Fiber](https://github.com/gofiber/fiber) that reports server metrics, inspired by [express-status-monitor](https://github.com/RafalWilinski/express-status-monitor)
|
||||
|
||||
:warning: **Warning:** Monitor is still in beta, API might change in the future!
|
||||
|
||||

|
||||
|
||||
### Signatures
|
||||
```go
|
||||
func New() fiber.Handler
|
||||
```
|
||||
|
||||
### Examples
|
||||
Import the middleware package and assign it to a route.
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/monitor"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/metrics", monitor.New(monitor.Config{Title: "MyService Metrics Page"}))
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
You can also access the API endpoint with
|
||||
`curl -X GET -H "Accept: application/json" http://localhost:3000/metrics` which returns:
|
||||
```json
|
||||
{"pid":{ "cpu":0.4568381746582226, "ram":20516864, "conns":3 },
|
||||
"os": { "cpu":8.759124087593099, "ram":3997155328, "conns":44,
|
||||
"total_ram":8245489664, "load_avg":0.51 }}
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
```go
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// Metrics page title
|
||||
//
|
||||
// Optional. Default: "Fiber Monitor"
|
||||
Title string
|
||||
|
||||
// Refresh period
|
||||
//
|
||||
// Optional. Default: 3 seconds
|
||||
Refresh time.Duration
|
||||
|
||||
// To disable serving HTML, you can make true this option.
|
||||
//
|
||||
// Optional. Default: false
|
||||
APIOnly bool
|
||||
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
}
|
||||
```
|
||||
|
||||
## Default Config
|
||||
|
||||
```go
|
||||
var ConfigDefault = Config{
|
||||
Title: "Fiber Monitor",
|
||||
Refresh: 3 * time.Second,
|
||||
APIOnly: false,
|
||||
Next: nil,
|
||||
}
|
||||
```
|
|
@ -1,91 +0,0 @@
|
|||
package monitor
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// Metrics page title
|
||||
//
|
||||
// Optional. Default: "Fiber Monitor"
|
||||
Title string
|
||||
|
||||
// Refresh period
|
||||
//
|
||||
// Optional. Default: 3 seconds
|
||||
Refresh time.Duration
|
||||
|
||||
// Whether the service should expose only the monitoring API.
|
||||
//
|
||||
// Optional. Default: false
|
||||
APIOnly bool
|
||||
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// customized indexHtml
|
||||
index string
|
||||
}
|
||||
|
||||
var ConfigDefault = Config{
|
||||
Title: defaultTitle,
|
||||
Refresh: defaultRefresh,
|
||||
APIOnly: false,
|
||||
Next: nil,
|
||||
index: newIndex(defaultTitle, defaultRefresh),
|
||||
}
|
||||
|
||||
func configDefault(config ...Config) Config {
|
||||
// Users can change ConfigDefault.Title/Refresh which then
|
||||
// become incompatible with ConfigDefault.index
|
||||
if ConfigDefault.Title != defaultTitle || ConfigDefault.Refresh != defaultRefresh {
|
||||
|
||||
if ConfigDefault.Refresh < minRefresh {
|
||||
ConfigDefault.Refresh = minRefresh
|
||||
}
|
||||
// update default index with new default title/refresh
|
||||
ConfigDefault.index = newIndex(ConfigDefault.Title, ConfigDefault.Refresh)
|
||||
}
|
||||
|
||||
// Return default config if nothing provided
|
||||
if len(config) < 1 {
|
||||
return ConfigDefault
|
||||
}
|
||||
|
||||
// Override default config
|
||||
cfg := config[0]
|
||||
|
||||
// Set default values
|
||||
if cfg.Title == "" {
|
||||
cfg.Title = ConfigDefault.Title
|
||||
}
|
||||
|
||||
if cfg.Refresh == 0 {
|
||||
cfg.Refresh = ConfigDefault.Refresh
|
||||
}
|
||||
|
||||
if cfg.Title == ConfigDefault.Title && cfg.Refresh == ConfigDefault.Refresh {
|
||||
cfg.index = ConfigDefault.index
|
||||
} else {
|
||||
if cfg.Refresh < minRefresh {
|
||||
cfg.Refresh = minRefresh
|
||||
}
|
||||
// update cfg.index with custom title/refresh
|
||||
cfg.index = newIndex(cfg.Title, cfg.Refresh)
|
||||
}
|
||||
|
||||
if cfg.Next == nil {
|
||||
cfg.Next = ConfigDefault.Next
|
||||
}
|
||||
|
||||
if !cfg.APIOnly {
|
||||
cfg.APIOnly = ConfigDefault.APIOnly
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
package monitor
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// returns index with new title/refresh
|
||||
func newIndex(title string, refresh time.Duration) string {
|
||||
|
||||
timeout := refresh.Milliseconds() - timeoutDiff
|
||||
if timeout < timeoutDiff {
|
||||
timeout = timeoutDiff
|
||||
}
|
||||
ts := strconv.FormatInt(timeout, 10)
|
||||
|
||||
index := strings.ReplaceAll(indexHtml, "$TITLE", title)
|
||||
return strings.ReplaceAll(index, "$TIMEOUT", ts)
|
||||
}
|
||||
|
||||
const (
|
||||
defaultTitle = "Fiber Monitor"
|
||||
|
||||
defaultRefresh = 3 * time.Second
|
||||
timeoutDiff = 200 // timeout will be Refresh (in millisconds) - timeoutDiff
|
||||
minRefresh = timeoutDiff * time.Millisecond
|
||||
|
||||
// parametrized by $TITLE and $TIMEOUT
|
||||
indexHtml = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;900&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9/dist/Chart.bundle.min.js"></script>
|
||||
<title>$TITLE</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font: 16px / 1.6 'Roboto', sans-serif;
|
||||
}
|
||||
.wrapper {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 0;
|
||||
}
|
||||
.title {
|
||||
text-align: center;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
.title h1 {
|
||||
font-size: 1.8em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
.row .column:first-child { width: 35%; }
|
||||
.row .column:last-child { width: 65%; }
|
||||
.metric {
|
||||
color: #777;
|
||||
font-weight: 900;
|
||||
}
|
||||
h2 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 2.2em;
|
||||
}
|
||||
h2 span {
|
||||
font-size: 12px;
|
||||
color: #777;
|
||||
}
|
||||
h2 span.ram_os { color: rgba(255, 150, 0, .8); }
|
||||
h2 span.ram_total { color: rgba(0, 200, 0, .8); }
|
||||
canvas {
|
||||
width: 200px;
|
||||
height: 180px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section class="wrapper">
|
||||
<div class="title"><h1>$TITLE</h1></div>
|
||||
<section class="charts">
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<div class="metric">CPU Usage</div>
|
||||
<h2 id="cpuMetric">0.00%</h2>
|
||||
</div>
|
||||
<div class="column">
|
||||
<canvas id="cpuChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<div class="metric">Memory Usage</div>
|
||||
<h2 id="ramMetric" title="PID used / OS used / OS total">0.00 MB</h2>
|
||||
</div>
|
||||
<div class="column">
|
||||
<canvas id="ramChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<div class="metric">Response Time</div>
|
||||
<h2 id="rtimeMetric">0ms</h2>
|
||||
</div>
|
||||
<div class="column">
|
||||
<canvas id="rtimeChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<div class="metric">Open Connections</div>
|
||||
<h2 id="connsMetric">0</h2>
|
||||
</div>
|
||||
<div class="column">
|
||||
<canvas id="connsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<script>
|
||||
function formatBytes(bytes, decimals = 1) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
Chart.defaults.global.legend.display = false;
|
||||
Chart.defaults.global.defaultFontSize = 8;
|
||||
Chart.defaults.global.animation.duration = 1000;
|
||||
Chart.defaults.global.animation.easing = 'easeOutQuart';
|
||||
Chart.defaults.global.elements.line.backgroundColor = 'rgba(0, 172, 215, 0.25)';
|
||||
Chart.defaults.global.elements.line.borderColor = 'rgba(0, 172, 215, 1)';
|
||||
Chart.defaults.global.elements.line.borderWidth = 2;
|
||||
|
||||
const options = {
|
||||
scales: {
|
||||
yAxes: [{ ticks: { beginAtZero: true }}],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
unitStepSize: 30,
|
||||
unit: 'second'
|
||||
},
|
||||
gridlines: { display: false }
|
||||
}]
|
||||
},
|
||||
tooltips: { enabled: false },
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: false
|
||||
};
|
||||
const cpuMetric = document.querySelector('#cpuMetric');
|
||||
const ramMetric = document.querySelector('#ramMetric');
|
||||
const rtimeMetric = document.querySelector('#rtimeMetric');
|
||||
const connsMetric = document.querySelector('#connsMetric');
|
||||
|
||||
const cpuChartCtx = document.querySelector('#cpuChart').getContext('2d');
|
||||
const ramChartCtx = document.querySelector('#ramChart').getContext('2d');
|
||||
const rtimeChartCtx = document.querySelector('#rtimeChart').getContext('2d');
|
||||
const connsChartCtx = document.querySelector('#connsChart').getContext('2d');
|
||||
|
||||
const cpuChart = createChart(cpuChartCtx);
|
||||
const ramChart = createChart(ramChartCtx);
|
||||
const rtimeChart = createChart(rtimeChartCtx);
|
||||
const connsChart = createChart(connsChartCtx);
|
||||
|
||||
const charts = [cpuChart, ramChart, rtimeChart, connsChart];
|
||||
|
||||
function createChart(ctx) {
|
||||
return new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: '',
|
||||
data: [],
|
||||
lineTension: 0.2,
|
||||
pointRadius: 0,
|
||||
}]
|
||||
},
|
||||
options
|
||||
});
|
||||
}
|
||||
ramChart.data.datasets.push({
|
||||
data: [],
|
||||
lineTension: 0.2,
|
||||
pointRadius: 0,
|
||||
backgroundColor: 'rgba(255, 200, 0, .6)',
|
||||
borderColor: 'rgba(255, 150, 0, .8)',
|
||||
})
|
||||
ramChart.data.datasets.push({
|
||||
data: [],
|
||||
lineTension: 0.2,
|
||||
pointRadius: 0,
|
||||
backgroundColor: 'rgba(0, 255, 0, .4)',
|
||||
borderColor: 'rgba(0, 200, 0, .8)',
|
||||
})
|
||||
function update(json, rtime) {
|
||||
cpu = json.pid.cpu.toFixed(1);
|
||||
cpuOS = json.os.cpu.toFixed(1);
|
||||
|
||||
cpuMetric.innerHTML = cpu + '% <span>' + cpuOS + '%</span>';
|
||||
ramMetric.innerHTML = formatBytes(json.pid.ram) + '<span> / </span><span class="ram_os">' + formatBytes(json.os.ram) +
|
||||
'<span><span> / </span><span class="ram_total">' + formatBytes(json.os.total_ram) + '</span>';
|
||||
rtimeMetric.innerHTML = rtime + 'ms <span>client</span>';
|
||||
connsMetric.innerHTML = json.pid.conns + ' <span>' + json.os.conns + '</span>';
|
||||
|
||||
cpuChart.data.datasets[0].data.push(cpu);
|
||||
ramChart.data.datasets[2].data.push((json.os.total_ram / 1e6).toFixed(2));
|
||||
ramChart.data.datasets[1].data.push((json.os.ram / 1e6).toFixed(2));
|
||||
ramChart.data.datasets[0].data.push((json.pid.ram / 1e6).toFixed(2));
|
||||
rtimeChart.data.datasets[0].data.push(rtime);
|
||||
connsChart.data.datasets[0].data.push(json.pid.conns);
|
||||
|
||||
const timestamp = new Date().getTime();
|
||||
|
||||
charts.forEach(chart => {
|
||||
if (chart.data.labels.length > 50) {
|
||||
chart.data.datasets.forEach(function (dataset) { dataset.data.shift(); });
|
||||
chart.data.labels.shift();
|
||||
}
|
||||
chart.data.labels.push(timestamp);
|
||||
chart.update();
|
||||
});
|
||||
setTimeout(fetchJSON, $TIMEOUT)
|
||||
}
|
||||
function fetchJSON() {
|
||||
var t1 = ''
|
||||
var t0 = performance.now()
|
||||
fetch(window.location.href, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(res => {
|
||||
t1 = performance.now()
|
||||
return res.json()
|
||||
})
|
||||
.then(res => { update(res, Math.round(t1 - t0)) })
|
||||
.catch(console.error);
|
||||
}
|
||||
fetchJSON()
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
)
|
|
@ -1,129 +0,0 @@
|
|||
package monitor
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
type stats struct {
|
||||
PID statsPID `json:"pid"`
|
||||
OS statsOS `json:"os"`
|
||||
}
|
||||
|
||||
type statsPID struct {
|
||||
CPU float64 `json:"cpu"`
|
||||
RAM uint64 `json:"ram"`
|
||||
Conns int `json:"conns"`
|
||||
}
|
||||
|
||||
type statsOS struct {
|
||||
CPU float64 `json:"cpu"`
|
||||
RAM uint64 `json:"ram"`
|
||||
TotalRAM uint64 `json:"total_ram"`
|
||||
LoadAvg float64 `json:"load_avg"`
|
||||
Conns int `json:"conns"`
|
||||
}
|
||||
|
||||
var (
|
||||
monitPidCpu atomic.Value
|
||||
monitPidRam atomic.Value
|
||||
monitPidConns atomic.Value
|
||||
|
||||
monitOsCpu atomic.Value
|
||||
monitOsRam atomic.Value
|
||||
monitOsTotalRam atomic.Value
|
||||
monitOsLoadAvg atomic.Value
|
||||
monitOsConns atomic.Value
|
||||
)
|
||||
|
||||
var (
|
||||
mutex sync.RWMutex
|
||||
once sync.Once
|
||||
data = &stats{}
|
||||
)
|
||||
|
||||
// New creates a new middleware handler
|
||||
func New(config ...Config) fiber.Handler {
|
||||
// Set default config
|
||||
cfg := configDefault(config...)
|
||||
|
||||
// Start routine to update statistics
|
||||
once.Do(func() {
|
||||
p, _ := process.NewProcess(int32(os.Getpid()))
|
||||
|
||||
updateStatistics(p)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(cfg.Refresh)
|
||||
|
||||
updateStatistics(p)
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
// Return new handler
|
||||
return func(c fiber.Ctx) error {
|
||||
// Don't execute middleware if Next returns true
|
||||
if cfg.Next != nil && cfg.Next(c) {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
if c.Method() != fiber.MethodGet {
|
||||
return fiber.ErrMethodNotAllowed
|
||||
}
|
||||
if c.Get(fiber.HeaderAccept) == fiber.MIMEApplicationJSON || cfg.APIOnly {
|
||||
mutex.Lock()
|
||||
data.PID.CPU = monitPidCpu.Load().(float64)
|
||||
data.PID.RAM = monitPidRam.Load().(uint64)
|
||||
data.PID.Conns = monitPidConns.Load().(int)
|
||||
|
||||
data.OS.CPU = monitOsCpu.Load().(float64)
|
||||
data.OS.RAM = monitOsRam.Load().(uint64)
|
||||
data.OS.TotalRAM = monitOsTotalRam.Load().(uint64)
|
||||
data.OS.LoadAvg = monitOsLoadAvg.Load().(float64)
|
||||
data.OS.Conns = monitOsConns.Load().(int)
|
||||
mutex.Unlock()
|
||||
return c.Status(fiber.StatusOK).JSON(data)
|
||||
}
|
||||
c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
|
||||
return c.Status(fiber.StatusOK).SendString(cfg.index)
|
||||
}
|
||||
}
|
||||
|
||||
func updateStatistics(p *process.Process) {
|
||||
pidCpu, _ := p.CPUPercent()
|
||||
monitPidCpu.Store(pidCpu / 10)
|
||||
|
||||
if osCpu, _ := cpu.Percent(0, false); len(osCpu) > 0 {
|
||||
monitOsCpu.Store(osCpu[0])
|
||||
}
|
||||
|
||||
if pidMem, _ := p.MemoryInfo(); pidMem != nil {
|
||||
monitPidRam.Store(pidMem.RSS)
|
||||
}
|
||||
|
||||
if osMem, _ := mem.VirtualMemory(); osMem != nil {
|
||||
monitOsRam.Store(osMem.Used)
|
||||
monitOsTotalRam.Store(osMem.Total)
|
||||
}
|
||||
|
||||
if loadAvg, _ := load.Avg(); loadAvg != nil {
|
||||
monitOsLoadAvg.Store(loadAvg.Load1)
|
||||
}
|
||||
|
||||
pidConns, _ := net.ConnectionsPid("tcp", p.Pid)
|
||||
monitPidConns.Store(len(pidConns))
|
||||
|
||||
osConns, _ := net.Connections("tcp")
|
||||
monitOsConns.Store(len(osConns))
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
package monitor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/utils"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
func Test_Monitor_405(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/", New())
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, 405, resp.StatusCode)
|
||||
}
|
||||
|
||||
func Test_Monitor_Html(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
// defaults
|
||||
app.Get("/", New())
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, 200, resp.StatusCode)
|
||||
utils.AssertEqual(t, fiber.MIMETextHTMLCharsetUTF8,
|
||||
resp.Header.Get(fiber.HeaderContentType))
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, true, bytes.Contains(buf, []byte("<title>"+defaultTitle+"</title>")))
|
||||
timeoutLine := fmt.Sprintf("setTimeout(fetchJSON, %d)",
|
||||
defaultRefresh.Milliseconds()-timeoutDiff)
|
||||
utils.AssertEqual(t, true, bytes.Contains(buf, []byte(timeoutLine)))
|
||||
|
||||
// custom config
|
||||
conf := Config{Title: "New " + defaultTitle, Refresh: defaultRefresh + time.Second}
|
||||
app.Get("/custom", New(conf))
|
||||
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/custom", nil))
|
||||
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, 200, resp.StatusCode)
|
||||
utils.AssertEqual(t, fiber.MIMETextHTMLCharsetUTF8,
|
||||
resp.Header.Get(fiber.HeaderContentType))
|
||||
buf, err = io.ReadAll(resp.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, true, bytes.Contains(buf, []byte("<title>"+conf.Title+"</title>")))
|
||||
timeoutLine = fmt.Sprintf("setTimeout(fetchJSON, %d)",
|
||||
conf.Refresh.Milliseconds()-timeoutDiff)
|
||||
utils.AssertEqual(t, true, bytes.Contains(buf, []byte(timeoutLine)))
|
||||
}
|
||||
|
||||
// go test -run Test_Monitor_JSON -race
|
||||
func Test_Monitor_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/", New())
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Set(fiber.HeaderAccept, fiber.MIMEApplicationJSON)
|
||||
resp, err := app.Test(req)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, 200, resp.StatusCode)
|
||||
utils.AssertEqual(t, fiber.MIMEApplicationJSON, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, true, bytes.Contains(b, []byte("pid")))
|
||||
utils.AssertEqual(t, true, bytes.Contains(b, []byte("os")))
|
||||
}
|
||||
|
||||
// go test -v -run=^$ -bench=Benchmark_Monitor -benchmem -count=4
|
||||
func Benchmark_Monitor(b *testing.B) {
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/", New())
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
fctx := &fasthttp.RequestCtx{}
|
||||
fctx.Request.Header.SetMethod("GET")
|
||||
fctx.Request.SetRequestURI("/")
|
||||
fctx.Request.Header.Set(fiber.HeaderAccept, fiber.MIMEApplicationJSON)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
h(fctx)
|
||||
}
|
||||
})
|
||||
|
||||
utils.AssertEqual(b, 200, fctx.Response.Header.StatusCode())
|
||||
utils.AssertEqual(b,
|
||||
fiber.MIMEApplicationJSON,
|
||||
string(fctx.Response.Header.Peek(fiber.HeaderContentType)))
|
||||
}
|
||||
|
||||
// go test -run Test_Monitor_Next
|
||||
func Test_Monitor_Next(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Use("/", New(Config{
|
||||
Next: func(_ fiber.Ctx) bool {
|
||||
return true
|
||||
},
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/", nil))
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, 404, resp.StatusCode)
|
||||
}
|
||||
|
||||
// go test -run Test_Monitor_APIOnly -race
|
||||
func Test_Monitor_APIOnly(t *testing.T) {
|
||||
//t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/", New(Config{
|
||||
APIOnly: true,
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Set(fiber.HeaderAccept, fiber.MIMEApplicationJSON)
|
||||
resp, err := app.Test(req)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, 200, resp.StatusCode)
|
||||
utils.AssertEqual(t, fiber.MIMEApplicationJSON, resp.Header.Get(fiber.HeaderContentType))
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, true, bytes.Contains(b, []byte("pid")))
|
||||
utils.AssertEqual(t, true, bytes.Contains(b, []byte("os")))
|
||||
}
|
Loading…
Reference in New Issue