Files
OpenFlare/backend/plugins/domain/admin/service/status.go
T
ryan 4f50f6a8f9 feat(core): bind request services and implement registered tasks
Wire plugin services through Bind/InjectFrom and AppContext so HTTP and
workers resolve dependencies after Apply. Register TaskHandler objects
with persisted results, and implement send_email_code, mail:send,
cleanup_inactive_users, and dispatch_bot_msg.
2026-09-02 16:59:00 +08:00

164 lines
4.4 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package service
import (
"Wavelet/pkg/logger"
"Wavelet/plugins/domain/admin/model"
"Wavelet/plugins/domain/admin/repository"
"context"
"fmt"
"math"
"runtime"
"time"
)
var startTime = time.Now()
const (
hoursInDay = 24
minutesInHour = 60
secondsInMinute = 60
nanosPerSecond = 1e9
logDBNamePostgres = "postgres"
logDBNameSQLite = "sqlite"
logDBNameClickHouse = "clickhouse"
defaultLogRetentionDays = 30
logMigrationIdle = "idle"
logMigrationInProgress = "migrating"
unknownGCLabel = "未知"
noGCLabel = "无"
)
// CollectSystemStatus samples the Go runtime counters for the console status page.
func CollectSystemStatus() model.SystemStatusResponse {
var m runtime.MemStats
runtime.ReadMemStats(&m)
uptime := formatDuration(time.Since(startTime))
numGoroutine := runtime.NumGoroutine()
var lastGCTime string
switch {
case m.LastGC > 0 && m.LastGC <= math.MaxInt64:
lastGCTime = formatDuration(time.Since(time.Unix(0, int64(m.LastGC))))
case m.LastGC > 0:
lastGCTime = unknownGCLabel
default:
lastGCTime = noGCLabel
}
var lastPause string
if m.NumGC > 0 {
lastPause = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC-1)%256])/nanosPerSecond)
} else {
lastPause = "0.000s"
}
return model.SystemStatusResponse{
Uptime: uptime,
NumGoroutine: numGoroutine,
Alloc: model.FormatBytes(m.Alloc),
TotalAlloc: model.FormatBytes(m.TotalAlloc),
Sys: model.FormatBytes(m.Sys),
Lookups: m.Lookups,
Mallocs: m.Mallocs,
Frees: m.Frees,
HeapAlloc: model.FormatBytes(m.HeapAlloc),
HeapSys: model.FormatBytes(m.HeapSys),
HeapIdle: model.FormatBytes(m.HeapIdle),
HeapInuse: model.FormatBytes(m.HeapInuse),
HeapReleased: model.FormatBytes(m.HeapReleased),
HeapObjects: m.HeapObjects,
StackInuse: model.FormatBytes(m.StackInuse),
StackSys: model.FormatBytes(m.StackSys),
MSpanInuse: model.FormatBytes(m.MSpanInuse),
MSpanSys: model.FormatBytes(m.MSpanSys),
MCacheInuse: model.FormatBytes(m.MCacheInuse),
MCacheSys: model.FormatBytes(m.MCacheSys),
BuckHashSys: model.FormatBytes(m.BuckHashSys),
GCSys: model.FormatBytes(m.GCSys),
OtherSys: model.FormatBytes(m.OtherSys),
NextGC: model.FormatBytes(m.NextGC),
LastGCTime: lastGCTime,
PauseTotalNs: fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/nanosPerSecond),
LastPause: lastPause,
NumGC: m.NumGC,
}
}
func formatDuration(d time.Duration) string {
days := int(d.Hours()) / hoursInDay
hours := int(d.Hours()) % hoursInDay
minutes := int(d.Minutes()) % minutesInHour
seconds := int(d.Seconds()) % secondsInMinute
var res string
if days > 0 {
res += fmt.Sprintf("%d天", days)
}
if hours > 0 {
res += fmt.Sprintf("%d小时", hours)
}
if minutes > 0 {
res += fmt.Sprintf("%d分钟", minutes)
}
if seconds > 0 || res == "" {
res += fmt.Sprintf("%d秒钟", seconds)
}
return res
}
// LogDatabaseStatus reports the active log engine, migration freeze state and retention.
func LogDatabaseStatus(ctx context.Context) model.LogDatabaseStatus {
activeDB := logDBNameSQLite
migration := logMigrationIdle
if rc := GetRiskControlService(ctx); rc != nil {
activeDB = rc.ActiveLogEngine(ctx)
if rc.IsLogEngineMigrating(ctx) {
migration = logMigrationInProgress
}
}
return model.LogDatabaseStatus{
ActiveDatabase: activeDB,
Migration: migration,
RetentionDays: map[string]int{
logDBNamePostgres: retentionOr(ctx, model.ConfigKeyLogRetentionDaysPostgres),
logDBNameSQLite: retentionOr(ctx, model.ConfigKeyLogRetentionDaysSQLite),
logDBNameClickHouse: retentionOr(ctx, model.ConfigKeyLogRetentionDaysClickHouse),
},
AvailableTargets: availableLogTargets(activeDB),
}
}
func retentionOr(ctx context.Context, key string) int {
v, err := repository.GetIntByKey(ctx, key)
if err != nil {
if !isRecordMissing(err) {
logger.ErrorF(ctx, "读取日志保留天数配置失败 key=%s: %v", key, err)
}
return defaultLogRetentionDays
}
if v < 1 {
return defaultLogRetentionDays
}
return v
}
func availableLogTargets(active string) []string {
if active == logDBNameClickHouse {
if GetDBConfig().Enabled {
return []string{logDBNamePostgres}
}
return []string{logDBNameSQLite}
}
if GetClickHouseConfig().Enabled {
return []string{logDBNameClickHouse}
}
return []string{}
}