1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-17 11:30:54 +00:00
FrankerFaceZ/socketserver/server/stats.go

300 lines
9.6 KiB
Go
Raw Normal View History

2015-11-16 13:07:02 -08:00
package server
2015-11-16 13:25:25 -08:00
import (
"bytes"
"encoding/json"
"fmt"
"io"
2015-11-16 13:25:25 -08:00
"net/http"
"runtime"
2015-11-17 11:11:14 -08:00
"sync"
"time"
linuxproc "github.com/c9s/goprocinfo/linux"
2015-11-16 13:25:25 -08:00
)
2015-11-16 13:07:02 -08:00
type StatsData struct {
updateMu sync.Mutex
2015-11-16 22:56:40 -08:00
StatsDataVersion int
2015-11-16 22:46:19 -08:00
StartTime time.Time
2015-11-16 23:15:38 -08:00
Uptime string
2015-11-16 22:56:40 -08:00
BuildTime string
BuildHash string
2015-11-16 21:57:18 -08:00
CachedStatsLastUpdate time.Time
2015-11-16 20:36:50 -08:00
Health struct {
IRC bool
Backend map[string]time.Time
}
CurrentClientCount uint64
2017-09-15 13:41:18 -07:00
LiveClientCount uint64
2015-11-16 21:57:18 -08:00
PubSubChannelCount int
2017-09-25 15:24:58 -07:00
ResponseCacheItems int
2015-11-16 21:57:18 -08:00
MemPerClientBytes uint64
SysMemTotalKB uint64
SysMemFreeKB uint64
MemoryInUseKB uint64
MemoryRSSKB uint64
CpuUsagePct float64
2015-11-16 21:57:18 -08:00
ClientConnectsTotal uint64
ClientDisconnectsTotal uint64
ClientVersions map[string]uint64
DisconnectCodes map[string]uint64
2015-11-16 20:35:03 -08:00
CommandsIssuedTotal uint64
CommandsIssuedMap map[Command]uint64
2015-11-16 13:07:02 -08:00
MessagesSent uint64
2015-11-16 13:07:02 -08:00
2015-11-16 21:57:18 -08:00
EmotesReportedTotal uint64
2015-11-16 13:25:25 -08:00
BackendVerifyFails uint64
2015-11-16 20:36:50 -08:00
// DisconnectReasons is at the bottom because it has indeterminate size
DisconnectReasons map[string]uint64
2015-11-16 13:07:02 -08:00
}
// Statistics is several variables that get incremented during normal operation of the server.
// Its structure should be versioned as it is exposed via JSON.
2015-11-16 21:57:18 -08:00
//
// Note as to threaded access - this is soft/fun data and not critical to data integrity.
// Fix anything that -race turns up, but otherwise it's not too much of a problem.
var Statistics = newStatsData()
// CommandCounter is a channel for race-free counting of command usage.
2015-12-16 11:58:48 -08:00
var CommandCounter = make(chan Command, 10)
// commandCounter receives from the CommandCounter channel and uses the value to increment the values in Statistics.
// is_init_func
2015-12-16 11:58:48 -08:00
func commandCounter() {
for cmd := range CommandCounter {
Statistics.CommandsIssuedTotal++
Statistics.CommandsIssuedMap[cmd]++
}
}
2016-01-17 18:01:21 -08:00
// StatsDataVersion is the version of the StatsData struct.
const StatsDataVersion = 8
const pageSize = 4096
var cpuUsage struct {
UserTime uint64
2015-11-16 23:15:38 -08:00
SysTime uint64
}
2015-11-16 13:25:25 -08:00
2015-11-16 13:07:02 -08:00
func newStatsData() *StatsData {
return &StatsData{
2015-11-16 22:46:19 -08:00
StartTime: time.Now(),
CommandsIssuedMap: make(map[Command]uint64),
DisconnectCodes: make(map[string]uint64),
DisconnectReasons: make(map[string]uint64),
ClientVersions: make(map[string]uint64),
2015-11-16 22:56:40 -08:00
StatsDataVersion: StatsDataVersion,
Health: struct {
IRC bool
Backend map[string]time.Time
}{
Backend: make(map[string]time.Time),
},
2015-11-16 13:07:02 -08:00
}
}
2015-11-17 11:01:42 -08:00
// SetBuildStamp should be called from the main package to identify the git build hash and build time.
2015-11-16 22:56:40 -08:00
func SetBuildStamp(buildTime, buildHash string) {
Statistics.BuildTime = buildTime
Statistics.BuildHash = buildHash
}
func updateStatsIfNeeded() {
Statistics.updateMu.Lock()
defer Statistics.updateMu.Unlock()
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)
2015-11-17 12:16:46 -08:00
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
2015-11-16 23:15:38 -08:00
Statistics.CpuUsagePct = 100 * float64(userTicks+sysTicks) / (timeDiff.Seconds() * float64(ticksPerSecond))
2015-11-17 12:16:46 -08:00
Statistics.MemoryRSSKB = uint64(pstat.Rss * pageSize / 1024)
Statistics.MemPerClientBytes = (Statistics.MemoryRSSKB * 1024) / (Statistics.CurrentClientCount + 1)
}
2015-11-17 11:01:42 -08:00
updateSysMem()
}
2015-11-16 21:57:18 -08:00
{
ChatSubscriptionLock.RLock()
Statistics.PubSubChannelCount = len(ChatSubscriptionInfo)
ChatSubscriptionLock.RUnlock()
GlobalSubscriptionLock.RLock()
Statistics.LiveClientCount = uint64(len(GlobalSubscriptionInfo))
versions := make(map[string]uint64)
for _, v := range GlobalSubscriptionInfo {
versions[v.VersionString]++
}
Statistics.ClientVersions = versions
GlobalSubscriptionLock.RUnlock()
2015-11-16 21:57:18 -08:00
}
2015-11-16 22:46:19 -08:00
{
2015-11-16 23:15:38 -08:00
Statistics.Uptime = nowUpdate.Sub(Statistics.StartTime).String()
2017-09-25 15:24:58 -07:00
Statistics.ResponseCacheItems = Backend.responseCache.ItemCount()
2015-11-16 22:46:19 -08:00
}
{
Statistics.Health.IRC = authIrcConnection.Connected()
Backend.lastSuccessLock.Lock()
for k, v := range Backend.lastSuccess {
Statistics.Health.Backend[k] = v
}
Backend.lastSuccessLock.Unlock()
}
}
2015-11-16 13:25:25 -08:00
2015-11-17 11:01:42 -08:00
var sysMemLastUpdate time.Time
var sysMemUpdateLock sync.Mutex
// updateSysMem reads the system's available RAM.
2015-11-17 11:01:42 -08:00
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 {
2015-11-17 11:11:14 -08:00
Statistics.SysMemTotalKB = memInfo.MemTotal
Statistics.SysMemFreeKB = memInfo.MemAvailable
}
2015-12-23 21:57:33 -08:00
{
writeHLL()
2015-11-17 11:01:42 -08:00
}
}
// HTTPShowStatistics handles the /stats endpoint. It writes out the Statistics object as indented JSON.
2017-09-15 16:30:11 -07:00
func HTTPShowStatistics(w http.ResponseWriter, _ *http.Request) {
2015-11-16 13:25:25 -08:00
w.Header().Set("Content-Type", "application/json")
updateStatsIfNeeded()
2015-11-16 13:25:25 -08:00
jsonBytes, _ := json.Marshal(Statistics)
outBuf := bytes.NewBuffer(nil)
json.Indent(outBuf, jsonBytes, "", "\t")
outBuf.WriteTo(w)
}
func HTTPShowStatisticsPrometheus(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
updateStatsIfNeeded()
Statistics.RenderPrometheus(w)
}
const (
mGauge = "gauge"
mCounter = "counter"
)
func writeManualMetric(w io.Writer, name string, value float64, help, typ string) {
fmt.Fprintf(w, "# HELP %s %s\n# TYPE %s %s\n%s %v\n", name, help, name, typ, name, value)
}
func writeLabelledStart(w io.Writer, name string, help, typ string) {
fmt.Fprintf(w, "# HELP %s %s\n# TYPE %s %s\n", name, help, name, typ)
}
func writeLabelledValue(w io.Writer, name string, value float64, labelKey, labelValue string) {
fmt.Fprintf(w, "%s{%s=%q} %v\n", name, labelKey, labelValue, value)
}
func boolToFloat64(v bool) float64 {
if v {
return 1
}
return 0
}
func (s *StatsData) RenderPrometheus(w io.Writer) {
writeManualMetric(w, "process_start_time_seconds", float64(s.StartTime.Unix()), "Start time of the process since unix epoch in seconds.", mGauge)
// build stamp
writeLabelledStart(w, "frankerfacez_build_hash", "Compilation git version hash of the socket server.", mGauge)
writeLabelledValue(w, "frankerfacez_build_hash", 1, "hash", s.BuildHash)
writeLabelledStart(w, "frankerfacez_build_time", "Compilation timestamp of the socket server as a string.", mGauge)
writeLabelledValue(w, "frankerfacez_build_time", 1, "time", s.BuildTime)
writeManualMetric(w, "frankerfacez_health_irc", boolToFloat64(s.Health.IRC), "State of the Twitch IRC health checks.", mGauge)
// todo: backend last seen
// connections
writeManualMetric(w, "frankerfacez_clients_current", float64(s.CurrentClientCount), "Number of current websocket connections to the socket server.", mGauge)
writeManualMetric(w, "frankerfacez_clients_live", float64(s.LiveClientCount), "Number of live (completed handshake) websocket connections to the socket server.", mGauge)
writeManualMetric(w, "frankerfacez_clients_connects_total", float64(s.ClientConnectsTotal), "Number of connections initiated to the socket server.", mCounter)
writeManualMetric(w, "frankerfacez_clients_disconnects_total", float64(s.ClientConnectsTotal), "Number of connections to the socket server that have ended.", mCounter)
writeManualMetric(w, "frankerfacez_pubsub_channels", float64(s.PubSubChannelCount), "Number of publish/subscribe channels the socket server knows about.", mGauge)
writeManualMetric(w, "frankerfacez_pubsub_response_cache_items", float64(s.ResponseCacheItems), "Number of entries in the command response cache.", mGauge)
// memory stats
writeManualMetric(w, "frankerfacez_memory_per_client_bytes", float64(s.MemPerClientBytes), "Average number of bytes needed to serve a connection.", mGauge)
writeManualMetric(w, "go_memstats_alloc_bytes", float64(s.MemoryInUseKB*1024), "Number of bytes allocated and still in use.", mGauge)
writeManualMetric(w, "process_resident_memory_bytes", float64(s.MemoryRSSKB*1024), "Resident memory size in bytes.", mGauge)
writeManualMetric(w, "system_total_memory_bytes", float64(s.SysMemTotalKB*1024), "Total amount of memory available on the system.", mGauge)
writeManualMetric(w, "system_free_memory_bytes", float64(s.SysMemFreeKB*1024), "Total amount of free memory on the system.", mGauge)
writeManualMetric(w, "process_cpu_recent_ratio", s.CpuUsagePct / 100, "Percentage of CPU time used since the last measurement.", mGauge)
writeLabelledStart(w, "frankerfacez_client_versions", "Reported version of connected clients.", mGauge)
for version, count := range s.ClientVersions {
writeLabelledValue(w, "frankerfacez_client_versions", float64(count), "version", version)
}
writeLabelledStart(w, "frankerfacez_commands_issued", "Number of times each command has been issued.", mCounter)
for command, count := range s.CommandsIssuedMap {
writeLabelledValue(w, "frankerfacez_commands_issued", float64(count), "command", string(command))
}
writeManualMetric(w, "frankerfacez_commands_issued_total", float64(s.CommandsIssuedTotal), "Number of times each command has been issued.", mCounter)
writeManualMetric(w, "frankerfacez_messages_sent_total", float64(s.MessagesSent), "Number of times the server has sent a message over the socket.", mCounter)
writeManualMetric(w, "frankerfacez_backend_verify_fail_total", float64(s.BackendVerifyFails), "Number of times the server has failed to securely receive a message from the backend.", mCounter)
}