1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-25 21:05:56 +00:00

publish v12.2.0-alpha6

This commit is contained in:
Gerasimos (Makis) Maropoulos
2022-02-18 22:19:33 +02:00
parent 4899fe95f4
commit 41026c9209
21 changed files with 984 additions and 284 deletions

View File

@@ -51,6 +51,32 @@ func (f *Template) SetOutput(dest io.Writer) {
const defaultTmplText = "{{.Now.Format .TimeFormat}}|{{.Latency}}|{{.Code}}|{{.Method}}|{{.Path}}|{{.IP}}|{{.RequestValuesLine}}|{{.BytesReceivedLine}}|{{.BytesSentLine}}|{{.Request}}|{{.Response}}|\n"
func (f *Template) LogText(log *Log) (string, error) {
var err error
// A template may be executed safely in parallel, although if parallel
// executions share a Writer the output may be interleaved.
// We solve that using a buffer pool, no locks when template is executing (x2 performance boost).
temp := f.ac.bufPool.Get().(*bytes.Buffer)
if f.TmplName != "" {
err = f.Tmpl.ExecuteTemplate(temp, f.TmplName, log)
} else {
err = f.Tmpl.Execute(temp, log)
}
if err != nil {
f.ac.bufPool.Put(temp)
return "", err
}
text := temp.String()
temp.Reset()
f.ac.bufPool.Put(temp)
return text, nil
}
// Format prints the logs in text/template format.
func (f *Template) Format(log *Log) (bool, error) {
var err error

View File

@@ -0,0 +1,33 @@
package monitor
import (
"expvar"
"strconv"
"sync/atomic"
)
// Uint64 completes the expvar metric interface, holds an uint64 value.
type Uint64 struct {
value uint64
}
// Set sets v to value.
func (v *Uint64) Set(value uint64) {
atomic.StoreUint64(&v.value, value)
}
// Value returns the underline uint64 value.
func (v *Uint64) Value() uint64 {
return atomic.LoadUint64(&v.value)
}
// String returns the text representation of the underline uint64 value.
func (v *Uint64) String() string {
return strconv.FormatUint(atomic.LoadUint64(&v.value), 10)
}
func newUint64(name string) *Uint64 {
v := new(Uint64)
expvar.Publish(name, v)
return v
}

View File

@@ -0,0 +1,105 @@
package monitor
import (
"bytes"
"fmt"
"os"
"time"
"github.com/kataras/iris/v12/context"
"github.com/shirou/gopsutil/v3/process"
)
func init() {
context.SetHandlerName("iris/middleware/monitor.*", "iris.monitor")
}
// Options holds the optional fields for the Monitor structure.
type Options struct {
// Optional process id, defaults to the current one.
PID int32 `json:"pid" yaml:"PID"`
RefreshInterval time.Duration `json:"refresh_interval" yaml:"RefreshInterval"`
ViewRefreshInterval time.Duration `json:"view_refresh_interval" yaml:"ViewRefreshInterval"`
// If more than zero enables line animation. Defaults to zero.
ViewAnimationInterval time.Duration `json:"view_animation_interval" yaml:"ViewAnimationInterval"`
// The title of the monitor HTML document.
ViewTitle string `json:"view_title" yaml:"ViewTitle"`
}
// Monitor tracks and renders the server's process and operating system statistics.
//
// Look its `Stats` and `View` methods.
// Initialize with the `New` package-level function.
type Monitor struct {
opts Options
Holder *StatsHolder
viewBody []byte
}
// New returns a new Monitor.
// Metrics stored through expvar standard package:
// - pid_cpu
// - pid_ram
// - pid_conns
// - os_cpu
// - os_ram
// - os_total_ram
// - os_load_avg
// - os_conns
//
// Check https://github.com/iris-contrib/middleware/tree/master/expmetric
// which can be integrated with datadog or other platforms.
func New(opts Options) *Monitor {
if opts.PID == 0 {
opts.PID = int32(os.Getpid())
}
if opts.RefreshInterval <= 0 {
opts.RefreshInterval = 2 * opts.RefreshInterval
}
if opts.ViewRefreshInterval <= 0 {
opts.ViewRefreshInterval = opts.RefreshInterval
}
viewRefreshIntervalBytes := []byte(fmt.Sprintf("%d", opts.ViewRefreshInterval.Milliseconds()))
viewBody := bytes.Replace(defaultViewBody, viewRefreshIntervalTmplVar, viewRefreshIntervalBytes, 1)
viewAnimationIntervalBytes := []byte(fmt.Sprintf("%d", opts.ViewAnimationInterval.Milliseconds()))
viewBody = bytes.Replace(viewBody, viewAnimationIntervalTmplVar, viewAnimationIntervalBytes, 2)
viewTitleBytes := []byte(opts.ViewTitle)
viewBody = bytes.Replace(viewBody, viewTitleTmplVar, viewTitleBytes, 2)
proc, err := process.NewProcess(opts.PID)
if err != nil {
panic(err)
}
sh := startNewStatsHolder(proc, opts.RefreshInterval)
m := &Monitor{
opts: opts,
Holder: sh,
viewBody: viewBody,
}
return m
}
// Stop terminates the retrieve stats loop for
// the process and the operating system statistics.
// No other monitor instance should be initialized after the first Stop call.
func (m *Monitor) Stop() {
m.Holder.Stop()
}
// Stats sends the stats as json.
func (m *Monitor) Stats(ctx *context.Context) {
ctx.JSON(m.Holder.GetStats())
}
// View renders a default view for the stats.
func (m *Monitor) View(ctx *context.Context) {
ctx.ContentType("text/html")
ctx.Write(m.viewBody)
}

205
middleware/monitor/stats.go Normal file
View File

@@ -0,0 +1,205 @@
package monitor
import (
"expvar"
"sync"
"time"
"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"
)
// Stats holds the process and operating system statistics values.
//
// Note that each statistic has its own expvar metric that you can use
// to render e.g. through statsd. Available values:
// * pid_cpu
// * pid_ram
// * pid_conns
// * os_cpu
// * os_ram
// * os_total_ram
// * os_load_avg
// * os_conns
type Stats struct {
PIDCPU float64 `json:"pid_cpu" yaml:"PIDCPU"`
PIDRAM uint64 `json:"pid_ram" yaml:"PIDRAM"`
PIDConns int64 `json:"pid_conns" yaml:"PIDConns"`
OSCPU float64 `json:"os_cpu" yaml:"OSCPU"`
OSRAM uint64 `json:"os_ram" yaml:"OSRAM"`
OSTotalRAM uint64 `json:"os_total_ram" yaml:"OSTotalRAM"`
OSLoadAvg float64 `json:"os_load_avg" yaml:"OSLoadAvg"`
OSConns int64 `json:"os_conns" yaml:"OSConns"`
}
// StatsHolder holds and refreshes the statistics.
type StatsHolder struct {
proc *process.Process
stats *Stats
mu sync.RWMutex
started bool
closeCh chan struct{}
errCh chan error
}
func startNewStatsHolder(proc *process.Process, refreshInterval time.Duration) *StatsHolder {
sh := newStatsHolder(proc)
sh.start(refreshInterval)
return sh
}
func newStatsHolder(proc *process.Process) *StatsHolder {
sh := &StatsHolder{
proc: proc,
stats: new(Stats),
closeCh: make(chan struct{}),
errCh: make(chan error, 1),
}
return sh
}
// Err returns a read-only channel which may be filled with errors
// came from the refresh stats operation.
func (sh *StatsHolder) Err() <-chan error {
return sh.errCh
}
// Stop terminates the routine retrieves the stats.
// Note that no other monitor can be initialized after Stop.
func (sh *StatsHolder) Stop() {
if !sh.started {
return
}
sh.closeCh <- struct{}{}
sh.started = false
}
func (sh *StatsHolder) start(refreshInterval time.Duration) {
if sh.started {
return
}
sh.started = true
once.Do(func() {
go func() {
ticker := time.NewTicker(refreshInterval)
defer ticker.Stop()
for {
select {
case <-sh.closeCh:
// close(sh.errCh)
return
case <-ticker.C:
err := refresh(sh.proc)
if err != nil {
// push the error to the channel and continue the execution,
// the only way to stop it is through its "Stop" method.
sh.errCh <- err
}
}
}
}()
})
}
var (
once = new(sync.Once)
metricPidCPU = expvar.NewFloat("pid_cpu")
metricPidRAM = newUint64("pid_ram")
metricPidConns = expvar.NewInt("pid_conns")
metricOsCPU = expvar.NewFloat("os_cpu")
metricOsRAM = newUint64("os_ram")
metricOsTotalRAM = newUint64("os_total_ram")
metricOsLoadAvg = expvar.NewFloat("os_load_avg")
metricOsConns = expvar.NewInt("os_conns")
)
// refresh updates the process and operating system statistics.
func refresh(proc *process.Process) error {
// Collect the stats.
//
// Process.
pidCPU, err := proc.CPUPercent()
if err != nil {
return err
}
pidRAM, err := proc.MemoryInfo()
if err != nil {
return err
}
pidConns, err := net.ConnectionsPid("tcp", proc.Pid)
if err != nil {
return err
}
// Operating System.
osCPU, err := cpu.Percent(0, false)
if err != nil {
return err
}
osRAM, err := mem.VirtualMemory()
if err != nil {
return err
}
osLoadAvg, err := load.Avg()
if err != nil {
return err
}
osConns, err := net.Connections("tcp")
if err != nil {
return err
}
// Update the fields.
//
// Process.
metricPidCPU.Set(pidCPU / 10)
metricPidRAM.Set(pidRAM.RSS)
metricPidConns.Set(int64(len(pidConns)))
// Operating System.
if len(osCPU) > 0 {
metricOsCPU.Set(osCPU[0])
}
metricOsRAM.Set(osRAM.Used)
metricOsTotalRAM.Set(osRAM.Total)
metricOsLoadAvg.Set(osLoadAvg.Load1)
metricOsConns.Set(int64(len(osConns)))
return nil
}
// GetStats returns a copy of the latest stats available.
func (sh *StatsHolder) GetStats() Stats {
sh.mu.Lock()
statsCopy := Stats{
PIDCPU: metricPidCPU.Value(),
PIDRAM: metricPidRAM.Value(),
PIDConns: metricPidConns.Value(),
OSCPU: metricOsCPU.Value(),
OSRAM: metricOsRAM.Value(),
OSTotalRAM: metricOsTotalRAM.Value(),
OSLoadAvg: metricOsLoadAvg.Value(),
OSConns: metricOsConns.Value(),
}
sh.mu.Unlock()
return statsCopy
}

View File

@@ -0,0 +1,15 @@
package monitor
var (
viewRefreshIntervalTmplVar = []byte(`{{.ViewRefreshInterval}}`)
viewAnimationIntervalTmplVar = []byte(`{{.ViewAnimationInterval}}`)
viewTitleTmplVar = []byte(`{{.ViewTitle}}`)
defaultViewBody = []byte(`<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;900&display=swap"rel=stylesheet><script src=https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js></script><script src=https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js></script><title>` + string(viewTitleTmplVar) + `</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:2em;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:1.8em}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><section class=wrapper><div class=title><h1>` + string(viewTitleTmplVar) + `</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>
Chart.defaults.plugins.legend.display=!1,Chart.defaults.font.size=8,Chart.defaults.elements.line.backgroundColor="rgba(0, 172, 215, 0.25)",Chart.defaults.elements.line.borderColor="rgba(0, 172, 215, 1)",Chart.defaults.elements.line.borderWidth=2,Chart.defaults.plugins.tooltip.enabled=!1,Chart.defaults.elements.line.tension=.2,Chart.defaults.elements.point.radius=0,Chart.defaults.animation.duration="` + string(viewAnimationIntervalTmplVar) + `",Chart.defaults.animation.easing="easeOutQuart";const options={scales:{y:{beginAtZero:!0},x:{type:"time",time:{stepSize:30,unit:"second"},grid:{display:!1}}},responsive:!0,maintainAspectRatio:!1,animation:` + string(viewAnimationIntervalTmplVar) + `>0};
const cpuMetric=document.querySelector("#cpuMetric"),ramMetric=document.querySelector("#ramMetric"),rtimeMetric=document.querySelector("#rtimeMetric"),connsMetric=document.querySelector("#connsMetric"),cpuChartCtx=document.querySelector("#cpuChart").getContext("2d"),ramChartCtx=document.querySelector("#ramChart").getContext("2d"),rtimeChartCtx=document.querySelector("#rtimeChart").getContext("2d"),connsChartCtx=document.querySelector("#connsChart").getContext("2d"),cpuChart=createChart(cpuChartCtx),ramChart=createChart(ramChartCtx),rtimeChart=createChart(rtimeChartCtx),connsChart=createChart(connsChartCtx),charts=[cpuChart,ramChart,rtimeChart,connsChart];
function createChart(t){return new Chart(t,{type:"line",data:{labels:[],datasets:[{label:"",data:[],fill:"start"}]},options:options})}
ramChart.data.datasets.push({data:[],fill:"start",backgroundColor:["rgba(255, 200, 0, .6)"],borderColor:["rgba(255, 150, 0, .8)"]}),ramChart.data.datasets.push({data:[],fill:"start",backgroundColor:["rgba(0, 255, 0, .4)"],borderColor:["rgba(0, 200, 0, .8)"]});
function formatBytes(a,b=2,k=1024){with(Math){let d=floor(log(a)/log(k));return 0==a?"0 Bytes":parseFloat((a/pow(k,d)).toFixed(max(0,b)))+" "+["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"][d]}}
function refreshChart(a,t){cpu=a.pid_cpu.toFixed(1),cpuOS=a.os_cpu.toFixed(1),cpuMetric.innerHTML=cpu+"% <span>"+cpuOS+"%</span>",ramMetric.innerHTML=formatBytes(a.pid_ram)+'<span> / </span><span class="ram_os">'+formatBytes(a.os_ram)+'<span><span> / </span><span class="ram_total">'+formatBytes(a.os_total_ram)+"</span>",rtimeMetric.innerHTML=t+"ms <span>client</span>",connsMetric.innerHTML=a.pid_conns+" <span>"+a.os_conns+"</span>",cpuChart.data.datasets[0].data.push(cpu),ramChart.data.datasets[2].data.push((a.os_total_ram/1e6).toFixed(2)),ramChart.data.datasets[1].data.push((a.os_ram/1e6).toFixed(2)),ramChart.data.datasets[0].data.push((a.pid_ram/1e6).toFixed(2)),rtimeChart.data.datasets[0].data.push(t),connsChart.data.datasets[0].data.push(a.pid_conns);const s=(new Date).getTime();charts.forEach(a=>{a.data.labels.length>50&&(a.data.datasets.forEach(function(a){a.data.shift()}),a.data.labels.shift()),a.data.labels.push(s),a.update()}),setTimeout(fetchData," ` + string(viewRefreshIntervalTmplVar) + `")}
function fetchData(){var e="",n=performance.now();fetch(window.location.href,{method:"POST",headers:{Accept:"application/json"},credentials:"same-origin"}).then(n=>(e=performance.now(),n.json())).then(o=>{refreshChart(o,Math.round(e-n))}).catch(console.error)}fetchData();</script></body></html>`)
)