feat(instance): метрики всей машины вторым рядом дашборда
Дашборд показывал только контейнер (лимиты профиля), поэтому не было видно, сколько ресурсов у хоста всего. - `sysinfo.NewHostSampler` и `ReadHostMemory` всегда читают `/proc/stat` и `/proc/meminfo`, игнорируя cgroup; - в ответе метрик появились `host_cpu` и `host_memory` (`source: host`); - недоступные источники попадают в проверку `metrics`, а не ломают ручку.
This commit is contained in:
@@ -67,6 +67,8 @@ type metricsCheck struct {
|
||||
type metricsPayload struct {
|
||||
CPU metricsCPU `json:"cpu"`
|
||||
Memory metricsMemory `json:"memory"`
|
||||
HostCPU metricsCPU `json:"host_cpu"`
|
||||
HostMemory metricsMemory `json:"host_memory"`
|
||||
Disk metricsDisk `json:"disk"`
|
||||
Database metricsDatabase `json:"database"`
|
||||
Checks []metricsCheck `json:"checks"`
|
||||
@@ -145,6 +147,25 @@ func (s *Server) collectMetrics(ctx context.Context) metricsPayload {
|
||||
}
|
||||
}
|
||||
|
||||
// Второй ряд дашборда — вся машина: видно, сколько ресурсов у хоста всего.
|
||||
if s.hostCPUSampler == nil {
|
||||
problems = append(problems, "host cpu: sampler is not configured")
|
||||
} else if percent, cores, source, err := s.hostCPUSampler.CPUPercent(); err != nil {
|
||||
problems = append(problems, "host cpu: "+err.Error())
|
||||
} else {
|
||||
payload.HostCPU = metricsCPU{Percent: round1(percent), Cores: cores, Source: source}
|
||||
}
|
||||
if memory, err := sysinfo.ReadHostMemory(s.sysinfoPaths); err != nil {
|
||||
problems = append(problems, "host memory: "+err.Error())
|
||||
} else {
|
||||
payload.HostMemory = metricsMemory{
|
||||
UsedBytes: memory.UsedBytes,
|
||||
TotalBytes: memory.TotalBytes,
|
||||
Percent: round1(memory.Percent()),
|
||||
Source: memory.Source,
|
||||
}
|
||||
}
|
||||
|
||||
if disk, err := sysinfo.ReadDisk(s.cfg.DataDir); err != nil {
|
||||
problems = append(problems, "disk: "+err.Error())
|
||||
} else {
|
||||
|
||||
@@ -24,6 +24,17 @@ type metricsBody struct {
|
||||
Percent float64 `json:"percent"`
|
||||
Source string `json:"source"`
|
||||
} `json:"memory"`
|
||||
HostCPU struct {
|
||||
Percent float64 `json:"percent"`
|
||||
Cores float64 `json:"cores"`
|
||||
Source string `json:"source"`
|
||||
} `json:"host_cpu"`
|
||||
HostMemory struct {
|
||||
UsedBytes int64 `json:"used_bytes"`
|
||||
TotalBytes int64 `json:"total_bytes"`
|
||||
Percent float64 `json:"percent"`
|
||||
Source string `json:"source"`
|
||||
} `json:"host_memory"`
|
||||
Disk struct {
|
||||
UsedBytes int64 `json:"used_bytes"`
|
||||
TotalBytes int64 `json:"total_bytes"`
|
||||
@@ -105,6 +116,18 @@ func TestInstanceMetricsPayload(t *testing.T) {
|
||||
} else if payload.Memory.UsedBytes <= 0 || payload.Memory.Percent <= 0 || payload.Memory.Percent > 100 {
|
||||
t.Fatalf("память = %+v", payload.Memory)
|
||||
}
|
||||
|
||||
// Второй ряд дашборда — вся машина.
|
||||
if payload.HostCPU.Source == "" {
|
||||
t.Log("источники процессора хоста недоступны (не Linux) — проверка пропущена")
|
||||
} else if payload.HostCPU.Source != "host" || payload.HostCPU.Cores <= 0 {
|
||||
t.Fatalf("процессор хоста = %+v", payload.HostCPU)
|
||||
}
|
||||
if payload.HostMemory.TotalBytes == 0 {
|
||||
t.Log("память хоста недоступна (не Linux) — проверка пропущена")
|
||||
} else if payload.HostMemory.Source != "host" || payload.HostMemory.Percent <= 0 {
|
||||
t.Fatalf("память хоста = %+v", payload.HostMemory)
|
||||
}
|
||||
if payload.Disk.TotalBytes <= 0 || payload.Disk.UsedBytes <= 0 || payload.Disk.FreeBytes <= 0 {
|
||||
t.Fatalf("диск = %+v", payload.Disk)
|
||||
}
|
||||
|
||||
@@ -67,9 +67,11 @@ type Server struct {
|
||||
voice *voice.TokenIssuer
|
||||
// voiceAdmin — RoomService для модерации на стороне SFU.
|
||||
voiceAdmin *voice.AdminClient
|
||||
// sysinfoPaths и cpuSampler — источники живых метрик дашборда инстанса.
|
||||
sysinfoPaths sysinfo.Paths
|
||||
cpuSampler *sysinfo.Sampler
|
||||
// sysinfoPaths, cpuSampler и hostCPUSampler — источники живых метрик
|
||||
// дашборда инстанса: контейнер и вся машина.
|
||||
sysinfoPaths sysinfo.Paths
|
||||
cpuSampler *sysinfo.Sampler
|
||||
hostCPUSampler *sysinfo.Sampler
|
||||
// metricsLimiter — отдельный лимит для ручки метрик (опрос раз в секунду).
|
||||
metricsLimiter *httpx.RateLimiter
|
||||
// health — кэш тяжёлых проверок инстанса.
|
||||
@@ -116,6 +118,7 @@ func New(cfg config.Config, db *database.DB, logger *slog.Logger, deps Deps) *Se
|
||||
voiceAdmin: voice.NewAdminClient(voice.NewIssuer(cfg.LiveKitAPIKey, cfg.LiveKitAPISecret, cfg.LiveKitTokenTTL), cfg.LiveKitAPIURL),
|
||||
}
|
||||
s.cpuSampler = sysinfo.NewSampler(s.sysinfoPaths)
|
||||
s.hostCPUSampler = sysinfo.NewHostSampler(s.sysinfoPaths)
|
||||
switch {
|
||||
case deps.Permissions != nil:
|
||||
s.perms = deps.Permissions
|
||||
|
||||
Reference in New Issue
Block a user