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:
@@ -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
|
||||
|
||||
33
middleware/monitor/expvar_uint64.go
Normal file
33
middleware/monitor/expvar_uint64.go
Normal 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
|
||||
}
|
||||
105
middleware/monitor/monitor.go
Normal file
105
middleware/monitor/monitor.go
Normal 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
205
middleware/monitor/stats.go
Normal 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
|
||||
}
|
||||
15
middleware/monitor/view.go
Normal file
15
middleware/monitor/view.go
Normal 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>`)
|
||||
)
|
||||
Reference in New Issue
Block a user