mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 11:02:54 +00:00
- Remove standard log import from server_metrics.go - Add logrus import for structured logging - Replace log.Printf with logrus.WithError for system metrics storage errors - Replace log.Printf with logrus.WithError for subscriber count errors (total, active, suspended) - Replace log.Printf with logrus.WithError for active sessions count errors - Replace log.Printf with logrus.WithError for /proc/stat, /proc/meminfo, and /proc/net/dev read errors
197 lines
5.3 KiB
Go
197 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nutcas3/telecom-platform/apps/api-server/internal/database"
|
|
"github.com/nutcas3/telecom-platform/apps/api-server/internal/metrics"
|
|
"github.com/nutcas3/telecom-platform/apps/api-server/internal/models"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// startMetricsCollection periodically collects and stores metrics.
|
|
func startMetricsCollection(ctx context.Context, mc *metrics.MetricsCollectorWithTimeSeries, db *database.Database) {
|
|
ticker := time.NewTicker(30 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
collectMetrics(ctx, mc, db)
|
|
}
|
|
}
|
|
}
|
|
|
|
// collectMetrics gathers metrics from various sources.
|
|
func collectMetrics(ctx context.Context, mc *metrics.MetricsCollectorWithTimeSeries, db *database.Database) {
|
|
totalSubs, activeSubs, suspendedSubs := getSubscriberStats(db)
|
|
mc.UpdateSubscriberMetrics(totalSubs, activeSubs, suspendedSubs)
|
|
|
|
systemMetrics := metrics.SystemMetrics{
|
|
ActiveSessions: getActiveSessionsCount(db),
|
|
CPUUsage: getCPUUsage(),
|
|
MemoryUsage: getMemoryUsage(),
|
|
NetworkRX: getNetworkRX(),
|
|
NetworkTX: getNetworkTX(),
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
if ts := mc.GetTimeSeriesStorage(); ts != nil {
|
|
if err := ts.StoreSystemMetrics(ctx, systemMetrics); err != nil {
|
|
logrus.WithError(err).Error("Failed to store system metrics")
|
|
}
|
|
}
|
|
}
|
|
|
|
// getSubscriberStats counts subscribers by status.
|
|
func getSubscriberStats(db *database.Database) (total, active, suspended int) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
var totalCount, activeCount, suspendedCount int64
|
|
if err := db.DB.WithContext(ctx).Model(&models.Subscriber{}).Count(&totalCount).Error; err != nil {
|
|
logrus.WithError(err).Error("Error counting total subscribers")
|
|
return 0, 0, 0
|
|
}
|
|
if err := db.DB.WithContext(ctx).Model(&models.Subscriber{}).Where("status = ?", "active").Count(&activeCount).Error; err != nil {
|
|
logrus.WithError(err).Error("Error counting active subscribers")
|
|
return int(totalCount), 0, 0
|
|
}
|
|
if err := db.DB.WithContext(ctx).Model(&models.Subscriber{}).
|
|
Where("status IN ?", []string{"suspended", "deactivated", "blocked"}).
|
|
Count(&suspendedCount).Error; err != nil {
|
|
logrus.WithError(err).Error("Error counting suspended subscribers")
|
|
return int(totalCount), int(activeCount), 0
|
|
}
|
|
return int(totalCount), int(activeCount), int(suspendedCount)
|
|
}
|
|
|
|
// getActiveSessionsCount returns the number of currently active charging sessions.
|
|
func getActiveSessionsCount(db *database.Database) int {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
var count int64
|
|
if err := db.DB.WithContext(ctx).Model(&models.Session{}).Where(
|
|
"status IN ? AND end_time IS NULL",
|
|
[]string{"active", "connected", "established"},
|
|
).Count(&count).Error; err != nil {
|
|
logrus.WithError(err).Error("Error counting active sessions")
|
|
return 0
|
|
}
|
|
|
|
return int(count)
|
|
}
|
|
|
|
// getCPUUsage reads /proc/stat and returns the non-idle CPU percentage.
|
|
func getCPUUsage() float64 {
|
|
data, err := os.ReadFile("/proc/stat")
|
|
if err != nil {
|
|
logrus.WithError(err).Error("Error reading /proc/stat")
|
|
return 0
|
|
}
|
|
|
|
lines := strings.Split(string(data), "\n")
|
|
if len(lines) == 0 {
|
|
return 0
|
|
}
|
|
|
|
fields := strings.Fields(lines[0])
|
|
if len(fields) < 8 || fields[0] != "cpu" {
|
|
return 0
|
|
}
|
|
|
|
var totalCPU, idleCPU float64
|
|
for i, field := range fields[1:8] {
|
|
val, err := strconv.ParseFloat(field, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
totalCPU += val
|
|
if i == 3 {
|
|
idleCPU = val
|
|
}
|
|
}
|
|
|
|
if totalCPU == 0 {
|
|
return 0
|
|
}
|
|
|
|
return math.Min(((totalCPU-idleCPU)/totalCPU)*100, 100.0)
|
|
}
|
|
|
|
// getMemoryUsage reads /proc/meminfo and returns used-memory percentage.
|
|
func getMemoryUsage() float64 {
|
|
data, err := os.ReadFile("/proc/meminfo")
|
|
if err != nil {
|
|
logrus.WithError(err).Error("Error reading /proc/meminfo")
|
|
return 0
|
|
}
|
|
|
|
lines := strings.Split(string(data), "\n")
|
|
var totalMem, availableMem float64
|
|
|
|
for _, line := range lines {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
switch fields[0] {
|
|
case "MemTotal:":
|
|
if val, err := strconv.ParseFloat(fields[1], 64); err == nil {
|
|
totalMem = val * 1024
|
|
}
|
|
case "MemAvailable:":
|
|
if val, err := strconv.ParseFloat(fields[1], 64); err == nil {
|
|
availableMem = val * 1024
|
|
}
|
|
}
|
|
}
|
|
|
|
if totalMem == 0 {
|
|
return 0
|
|
}
|
|
|
|
return math.Min(((totalMem-availableMem)/totalMem)*100, 100.0)
|
|
}
|
|
|
|
// getNetworkRX returns total RX bytes across non-loopback interfaces.
|
|
func getNetworkRX() int64 { return readNetworkStat(1) }
|
|
|
|
// getNetworkTX returns total TX bytes across non-loopback interfaces.
|
|
func getNetworkTX() int64 { return readNetworkStat(9) }
|
|
|
|
// readNetworkStat parses /proc/net/dev for a given field index.
|
|
func readNetworkStat(fieldIdx int) int64 {
|
|
data, err := os.ReadFile("/proc/net/dev")
|
|
if err != nil {
|
|
logrus.WithError(err).Error("Error reading /proc/net/dev")
|
|
return 0
|
|
}
|
|
|
|
lines := strings.Split(string(data), "\n")
|
|
var total int64
|
|
for i, line := range lines {
|
|
if i < 2 {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 10 {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(fields[0], "lo:") {
|
|
continue
|
|
}
|
|
if v, err := strconv.ParseInt(fields[fieldIdx], 10, 64); err == nil {
|
|
total += v
|
|
}
|
|
}
|
|
return total
|
|
}
|