🔥 v3: remove monitor middleware from core to reduce deps

pull/1979/head
Muhammed Efe Çetin 2022-08-08 11:22:28 +03:00
parent 9af3c21b7a
commit f3067c14b3
No known key found for this signature in database
GPG Key ID: 0AA4D45CBAA86F73
7 changed files with 0 additions and 744 deletions

7
go.mod
View File

@ -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
View File

@ -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=

View File

@ -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!
![](https://i.imgur.com/nHAtBpJ.gif)
### 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,
}
```

View File

@ -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
}

View File

@ -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>`
)

View File

@ -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))
}

View File

@ -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")))
}