mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-10-07 07:00:52 +08:00
291 lines
5.4 KiB
Go
291 lines
5.4 KiB
Go
package plugin_debug
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/shirou/gopsutil/v4/cpu"
|
|
"github.com/shirou/gopsutil/v4/process"
|
|
)
|
|
|
|
//go:embed static/*
|
|
var staticFS embed.FS
|
|
var staticFSHandler = http.FileServer(http.FS(staticFS))
|
|
|
|
type update struct {
|
|
Ts int64
|
|
BytesAllocated uint64
|
|
GcPause uint64
|
|
CPUUser float64
|
|
CPUSys float64
|
|
Block int
|
|
Goroutine int
|
|
Heap int
|
|
Mutex int
|
|
Threadcreate int
|
|
}
|
|
|
|
type consumer struct {
|
|
id uint
|
|
c chan update
|
|
}
|
|
|
|
type server struct {
|
|
consumers []consumer
|
|
consumersMutex sync.RWMutex
|
|
}
|
|
|
|
type SimplePair struct {
|
|
Ts uint64
|
|
Value uint64
|
|
}
|
|
|
|
type CPUPair struct {
|
|
Ts uint64
|
|
User float64
|
|
Sys float64
|
|
}
|
|
|
|
type PprofPair struct {
|
|
Ts uint64
|
|
Block int
|
|
Goroutine int
|
|
Heap int
|
|
Mutex int
|
|
Threadcreate int
|
|
}
|
|
|
|
type DataStorage struct {
|
|
BytesAllocated []SimplePair
|
|
GcPauses []SimplePair
|
|
CPUUsage []CPUPair
|
|
Pprof []PprofPair
|
|
}
|
|
|
|
const (
|
|
maxCount int = 86400
|
|
)
|
|
|
|
var (
|
|
data DataStorage
|
|
lastPause uint32
|
|
mutex sync.RWMutex
|
|
lastConsumerID uint
|
|
s server
|
|
upgrader = websocket.Upgrader{
|
|
ReadBufferSize: 1024,
|
|
WriteBufferSize: 1024,
|
|
}
|
|
prevSysTime float64
|
|
prevUserTime float64
|
|
myProcess *process.Process
|
|
)
|
|
|
|
func init() {
|
|
|
|
myProcess, _ = process.NewProcess(int32(os.Getpid()))
|
|
|
|
// preallocate arrays in data, helps save on reallocations caused by append()
|
|
// when maxCount is large
|
|
data.BytesAllocated = make([]SimplePair, 0, maxCount)
|
|
data.GcPauses = make([]SimplePair, 0, maxCount)
|
|
data.CPUUsage = make([]CPUPair, 0, maxCount)
|
|
data.Pprof = make([]PprofPair, 0, maxCount)
|
|
|
|
go s.gatherData()
|
|
}
|
|
|
|
func (s *server) gatherData() {
|
|
timer := time.Tick(time.Second)
|
|
|
|
for now := range timer {
|
|
nowUnix := now.Unix()
|
|
|
|
var ms runtime.MemStats
|
|
runtime.ReadMemStats(&ms)
|
|
|
|
u := update{
|
|
Ts: nowUnix * 1000,
|
|
Block: pprof.Lookup("block").Count(),
|
|
Goroutine: pprof.Lookup("goroutine").Count(),
|
|
Heap: pprof.Lookup("heap").Count(),
|
|
Mutex: pprof.Lookup("mutex").Count(),
|
|
Threadcreate: pprof.Lookup("threadcreate").Count(),
|
|
}
|
|
data.Pprof = append(data.Pprof, PprofPair{
|
|
uint64(nowUnix) * 1000,
|
|
u.Block,
|
|
u.Goroutine,
|
|
u.Heap,
|
|
u.Mutex,
|
|
u.Threadcreate,
|
|
})
|
|
|
|
cpuTimes, err := myProcess.Times()
|
|
if err != nil {
|
|
cpuTimes = &cpu.TimesStat{}
|
|
}
|
|
|
|
if prevUserTime != 0 {
|
|
u.CPUUser = cpuTimes.User - prevUserTime
|
|
u.CPUSys = cpuTimes.System - prevSysTime
|
|
data.CPUUsage = append(data.CPUUsage, CPUPair{uint64(nowUnix) * 1000, u.CPUUser, u.CPUSys})
|
|
}
|
|
|
|
prevUserTime = cpuTimes.User
|
|
prevSysTime = cpuTimes.System
|
|
|
|
mutex.Lock()
|
|
|
|
bytesAllocated := ms.Alloc
|
|
u.BytesAllocated = bytesAllocated
|
|
data.BytesAllocated = append(data.BytesAllocated, SimplePair{uint64(nowUnix) * 1000, bytesAllocated})
|
|
if lastPause == 0 || lastPause != ms.NumGC {
|
|
gcPause := ms.PauseNs[(ms.NumGC+255)%256]
|
|
u.GcPause = gcPause
|
|
data.GcPauses = append(data.GcPauses, SimplePair{uint64(nowUnix) * 1000, gcPause})
|
|
lastPause = ms.NumGC
|
|
}
|
|
|
|
if len(data.BytesAllocated) > maxCount {
|
|
data.BytesAllocated = data.BytesAllocated[len(data.BytesAllocated)-maxCount:]
|
|
}
|
|
|
|
if len(data.GcPauses) > maxCount {
|
|
data.GcPauses = data.GcPauses[len(data.GcPauses)-maxCount:]
|
|
}
|
|
|
|
mutex.Unlock()
|
|
|
|
s.sendToConsumers(u)
|
|
}
|
|
}
|
|
|
|
func (s *server) sendToConsumers(u update) {
|
|
s.consumersMutex.RLock()
|
|
defer s.consumersMutex.RUnlock()
|
|
|
|
for _, c := range s.consumers {
|
|
c.c <- u
|
|
}
|
|
}
|
|
|
|
func (s *server) removeConsumer(id uint) {
|
|
s.consumersMutex.Lock()
|
|
defer s.consumersMutex.Unlock()
|
|
|
|
var consumerID uint
|
|
var consumerFound bool
|
|
|
|
for i, c := range s.consumers {
|
|
if c.id == id {
|
|
consumerFound = true
|
|
consumerID = uint(i)
|
|
break
|
|
}
|
|
}
|
|
|
|
if consumerFound {
|
|
s.consumers = append(s.consumers[:consumerID], s.consumers[consumerID+1:]...)
|
|
}
|
|
}
|
|
|
|
func (s *server) addConsumer() consumer {
|
|
s.consumersMutex.Lock()
|
|
defer s.consumersMutex.Unlock()
|
|
|
|
lastConsumerID++
|
|
|
|
c := consumer{
|
|
id: lastConsumerID,
|
|
c: make(chan update),
|
|
}
|
|
|
|
s.consumers = append(s.consumers, c)
|
|
|
|
return c
|
|
}
|
|
|
|
func (s *server) dataFeedHandler(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
lastPing time.Time
|
|
lastPong time.Time
|
|
)
|
|
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
|
|
conn.SetPongHandler(func(s string) error {
|
|
lastPong = time.Now()
|
|
return nil
|
|
})
|
|
|
|
// read and discard all messages
|
|
go func(c *websocket.Conn) {
|
|
for {
|
|
if _, _, err := c.NextReader(); err != nil {
|
|
c.Close()
|
|
break
|
|
}
|
|
}
|
|
}(conn)
|
|
|
|
c := s.addConsumer()
|
|
|
|
defer func() {
|
|
s.removeConsumer(c.id)
|
|
conn.Close()
|
|
}()
|
|
|
|
var i uint
|
|
|
|
for u := range c.c {
|
|
conn.WriteJSON(u)
|
|
i++
|
|
|
|
if i%10 == 0 {
|
|
if diff := lastPing.Sub(lastPong); diff > time.Second*60 {
|
|
return
|
|
}
|
|
now := time.Now()
|
|
if err := conn.WriteControl(websocket.PingMessage, nil, now.Add(time.Second)); err != nil {
|
|
return
|
|
}
|
|
lastPing = now
|
|
}
|
|
}
|
|
}
|
|
|
|
func dataHandler(w http.ResponseWriter, r *http.Request) {
|
|
mutex.RLock()
|
|
defer mutex.RUnlock()
|
|
|
|
if e := r.ParseForm(); e != nil {
|
|
log.Print("error parsing form")
|
|
return
|
|
}
|
|
|
|
callback := r.FormValue("callback")
|
|
|
|
fmt.Fprintf(w, "%v(", callback)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
encoder := json.NewEncoder(w)
|
|
encoder.Encode(data)
|
|
|
|
fmt.Fprint(w, ")")
|
|
}
|