1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-05 18:48:31 +00:00
FrankerFaceZ/socketserver/server/stats.go

165 lines
3.8 KiB
Go

package server
import (
"bytes"
"encoding/json"
"net/http"
"runtime"
"sync"
"time"
linuxproc "github.com/c9s/goprocinfo/linux"
)
type StatsData struct {
StatsDataVersion int
StartTime time.Time
Uptime string
BuildTime string
BuildHash string
CachedStatsLastUpdate time.Time
CurrentClientCount uint64
PubSubChannelCount int
SysMemTotalKB uint64
SysMemFreeKB uint64
MemoryInUseKB uint64
MemoryRSSKB uint64
MemPerClientBytes uint64
CpuUsagePct float64
ClientConnectsTotal uint64
ClientDisconnectsTotal uint64
DisconnectCodes map[string]uint64
CommandsIssuedTotal uint64
CommandsIssuedMap map[Command]uint64
MessagesSent uint64
EmotesReportedTotal uint64
BackendVerifyFails uint64
// DisconnectReasons is at the bottom because it has indeterminate size
DisconnectReasons map[string]uint64
}
// Statistics is several variables that get incremented during normal operation of the server.
// Its structure should be versioned as it is exposed via JSON.
//
// Note as to threaded access - this is soft/fun data and not critical to data integrity.
// I don't really care.
var Statistics = newStatsData()
const StatsDataVersion = 5
const pageSize = 4096
var cpuUsage struct {
UserTime uint64
SysTime uint64
}
func newStatsData() *StatsData {
return &StatsData{
StartTime: time.Now(),
CommandsIssuedMap: make(map[Command]uint64),
DisconnectCodes: make(map[string]uint64),
DisconnectReasons: make(map[string]uint64),
StatsDataVersion: StatsDataVersion,
}
}
// SetBuildStamp should be called from the main package to identify the git build hash and build time.
func SetBuildStamp(buildTime, buildHash string) {
Statistics.BuildTime = buildTime
Statistics.BuildHash = buildHash
}
func updateStatsIfNeeded() {
if time.Now().Add(-2 * time.Second).After(Statistics.CachedStatsLastUpdate) {
updatePeriodicStats()
}
}
func updatePeriodicStats() {
nowUpdate := time.Now()
timeDiff := nowUpdate.Sub(Statistics.CachedStatsLastUpdate)
Statistics.CachedStatsLastUpdate = nowUpdate
{
m := runtime.MemStats{}
runtime.ReadMemStats(&m)
Statistics.MemoryInUseKB = m.Alloc / 1024
}
{
pstat, err := linuxproc.ReadProcessStat("/proc/self/stat")
if err == nil {
userTicks := pstat.Utime - cpuUsage.UserTime
sysTicks := pstat.Stime - cpuUsage.SysTime
cpuUsage.UserTime = pstat.Utime
cpuUsage.SysTime = pstat.Stime
Statistics.CpuUsagePct = 100 * float64(userTicks+sysTicks) / (timeDiff.Seconds() * float64(ticksPerSecond))
Statistics.MemoryRSSKB = uint64(pstat.Rss * pageSize / 1024)
Statistics.MemPerClientBytes = (Statistics.MemoryRSSKB * 1024) / Statistics.CurrentClientCount
}
updateSysMem()
}
{
ChatSubscriptionLock.RLock()
Statistics.PubSubChannelCount = len(ChatSubscriptionInfo)
ChatSubscriptionLock.RUnlock()
GlobalSubscriptionLock.RLock()
Statistics.CurrentClientCount = uint64(len(GlobalSubscriptionInfo))
GlobalSubscriptionLock.RUnlock()
}
{
Statistics.Uptime = nowUpdate.Sub(Statistics.StartTime).String()
}
}
var sysMemLastUpdate time.Time
var sysMemUpdateLock sync.Mutex
func updateSysMem() {
if time.Now().Add(-2 * time.Second).After(sysMemLastUpdate) {
sysMemUpdateLock.Lock()
defer sysMemUpdateLock.Unlock()
if !time.Now().Add(-2 * time.Second).After(sysMemLastUpdate) {
return
}
} else {
return
}
sysMemLastUpdate = time.Now()
memInfo, err := linuxproc.ReadMemInfo("/proc/meminfo")
if err == nil {
Statistics.SysMemTotalKB = memInfo.MemTotal
Statistics.SysMemFreeKB = memInfo.MemAvailable
}
}
func HTTPShowStatistics(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
updateStatsIfNeeded()
jsonBytes, _ := json.Marshal(Statistics)
outBuf := bytes.NewBuffer(nil)
json.Indent(outBuf, jsonBytes, "", "\t")
outBuf.WriteTo(w)
}