nignoggobot/metrics/metrics.go
2025-05-29 00:52:44 +02:00

149 lines
3.1 KiB
Go

package metrics
import (
"encoding/json"
"fmt"
"os"
"sync"
"time"
)
type CommandStats struct {
AllTime int `json:"all_time"`
Last30 [30]int `json:"last_30"`
Last30Day int64 `json:"last_30_day"` // Unix day of the first slot
}
type ChatInfo struct {
IsGroup bool `json:"is_group"`
LastActive int64 `json:"last_active"`
Blocked bool `json:"blocked"`
LastError string `json:"last_error,omitempty"`
}
type Metrics struct {
Commands map[string]*CommandStats `json:"commands"`
Chats map[int64]*ChatInfo `json:"chats"`
}
var (
metricsFile = "metrics/metrics.json"
metrics *Metrics
mu sync.Mutex
)
func loadMetrics() {
if metrics != nil {
return
}
metrics = &Metrics{
Commands: make(map[string]*CommandStats),
Chats: make(map[int64]*ChatInfo),
}
f, err := os.Open(metricsFile)
if err != nil {
return
}
defer f.Close()
json.NewDecoder(f).Decode(metrics)
}
func saveMetrics() {
// Do not lock here; caller must hold the lock else it will shitty deadlock
loadMetrics()
f, err := os.Create(metricsFile)
if err != nil {
return
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
enc.Encode(metrics)
}
func IncrementCommandUsage(cmd string) {
fmt.Printf("[metrics] IncrementCommandUsage called with cmd=%s\n", cmd)
mu.Lock()
defer mu.Unlock()
loadMetrics()
stat, ok := metrics.Commands[cmd]
if !ok {
stat = &CommandStats{Last30Day: time.Now().Unix() / 86400}
metrics.Commands[cmd] = stat
}
today := time.Now().Unix() / 86400
shift := int(today - stat.Last30Day)
if shift > 0 && shift < 30 {
copy(stat.Last30[shift:], stat.Last30[:30-shift])
for i := 0; i < shift; i++ {
stat.Last30[i] = 0
}
stat.Last30Day = today
} else if shift >= 30 {
for i := 0; i < 30; i++ {
stat.Last30[i] = 0
}
stat.Last30Day = today
}
stat.AllTime++
stat.Last30[0]++
saveMetrics()
}
func UpdateChat(chatID int64, isGroup bool) {
fmt.Printf("[metrics] UpdateChat called with chatID=%d isGroup=%v\n", chatID, isGroup)
mu.Lock()
defer mu.Unlock()
loadMetrics()
info, ok := metrics.Chats[chatID]
if !ok {
info = &ChatInfo{IsGroup: isGroup}
metrics.Chats[chatID] = info
}
info.LastActive = time.Now().Unix()
info.Blocked = false
info.LastError = ""
saveMetrics()
}
func MarkChatBlocked(chatID int64, errMsg string) {
fmt.Printf("[metrics] MarkChatBlocked called with chatID=%d errMsg=%s\n", chatID, errMsg)
mu.Lock()
defer mu.Unlock()
loadMetrics()
info, ok := metrics.Chats[chatID]
if !ok {
info = &ChatInfo{}
metrics.Chats[chatID] = info
}
info.Blocked = true
info.LastError = errMsg
saveMetrics()
}
func GetStats() *Metrics {
fmt.Printf("[metrics] GetStats called\n")
mu.Lock()
defer mu.Unlock()
loadMetrics()
copy := *metrics
return &copy
}
func BroadcastMessage(sendFunc func(chatID int64) error) {
fmt.Printf("[metrics] BroadcastMessage called\n")
mu.Lock()
loadMetrics()
ids := make([]int64, 0, len(metrics.Chats))
for id := range metrics.Chats {
ids = append(ids, id)
}
mu.Unlock()
for _, id := range ids {
err := sendFunc(id)
if err != nil {
MarkChatBlocked(id, err.Error())
}
}
}