mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 15:51:27 +00:00
P5b: notifier transport registry (webhook/slack/discord/telegram/ gotify/ntfy/smtp via net/smtp), per-monitor+domain alert rules (failsBeforeDown, latencyWarn, certDays, mute). P5c: per-container cpu/mem, optional SMART/ZFS/GPU exec-or-skip collectors, crt.sh subdomain discovery with daily sweep. P5d: opt-in auth (bcrypt, sqlite sessions, 7d rotating cookie, login rate limit, public-route bypass) and multiple boards (boards table, sections.board_id, /api/boards CRUD, slug routing). Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
122 lines
3.3 KiB
Go
122 lines
3.3 KiB
Go
package main
|
|
|
|
// Docker collector: container liveness plus per-container CPU/memory from
|
|
// /containers/{id}/stats?stream=false. All best-effort — no socket, no data.
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const dockerSock = "/var/run/docker.sock"
|
|
|
|
var dockerClient = &http.Client{
|
|
Timeout: 4 * time.Second,
|
|
Transport: &http.Transport{
|
|
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
return (&net.Dialer{Timeout: 2 * time.Second}).DialContext(ctx, "unix", dockerSock)
|
|
},
|
|
},
|
|
}
|
|
|
|
// dockerStatsJSON is the subset of /containers/{id}/stats we consume.
|
|
type dockerStatsJSON struct {
|
|
CPUStats struct {
|
|
CPUUsage struct {
|
|
TotalUsage uint64 `json:"total_usage"`
|
|
} `json:"cpu_usage"`
|
|
SystemCPUUsage uint64 `json:"system_cpu_usage"`
|
|
OnlineCPUs uint32 `json:"online_cpus"`
|
|
} `json:"cpu_stats"`
|
|
PreCPUStats struct {
|
|
CPUUsage struct {
|
|
TotalUsage uint64 `json:"total_usage"`
|
|
} `json:"cpu_usage"`
|
|
SystemCPUUsage uint64 `json:"system_cpu_usage"`
|
|
} `json:"precpu_stats"`
|
|
MemoryStats struct {
|
|
Usage uint64 `json:"usage"`
|
|
Limit uint64 `json:"limit"`
|
|
} `json:"memory_stats"`
|
|
}
|
|
|
|
// containerCPUPct applies the Docker dashboard formula: cpu delta over
|
|
// system delta, scaled by online CPUs. 0 when deltas don't advance.
|
|
func containerCPUPct(s *dockerStatsJSON) float64 {
|
|
cpuDelta := float64(s.CPUStats.CPUUsage.TotalUsage) - float64(s.PreCPUStats.CPUUsage.TotalUsage)
|
|
sysDelta := float64(s.CPUStats.SystemCPUUsage) - float64(s.PreCPUStats.SystemCPUUsage)
|
|
online := s.CPUStats.OnlineCPUs
|
|
if online == 0 {
|
|
online = 1
|
|
}
|
|
if cpuDelta <= 0 || sysDelta <= 0 {
|
|
return 0
|
|
}
|
|
return cpuDelta / sysDelta * float64(online) * 100
|
|
}
|
|
|
|
// readContainers lists containers and fans out to per-container stats.
|
|
// Stats fetches are bounded: 8 concurrent, each under the client timeout.
|
|
func readContainers() []containerStat {
|
|
if _, err := net.DialTimeout("unix", dockerSock, time.Second); err != nil {
|
|
return nil
|
|
}
|
|
res, err := dockerClient.Get("http://d/containers/json?all=1")
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer res.Body.Close()
|
|
var raw []struct {
|
|
ID string `json:"Id"`
|
|
Names []string `json:"Names"`
|
|
State string `json:"State"`
|
|
}
|
|
if json.NewDecoder(io.LimitReader(res.Body, 4<<20)).Decode(&raw) != nil {
|
|
return nil
|
|
}
|
|
out := make([]containerStat, len(raw))
|
|
var wg sync.WaitGroup
|
|
sem := make(chan struct{}, 8)
|
|
for i, r := range raw {
|
|
name := ""
|
|
if len(r.Names) > 0 {
|
|
name = strings.TrimPrefix(r.Names[0], "/")
|
|
}
|
|
out[i] = containerStat{Name: name, State: r.State}
|
|
if r.State != "running" || r.ID == "" {
|
|
continue
|
|
}
|
|
wg.Add(1)
|
|
sem <- struct{}{}
|
|
go func(i int, id string) {
|
|
defer wg.Done()
|
|
defer func() { <-sem }()
|
|
fillContainerStats(&out[i], id)
|
|
}(i, r.ID)
|
|
}
|
|
wg.Wait()
|
|
return out
|
|
}
|
|
|
|
// fillContainerStats fetches one container's one-shot stats into cs.
|
|
func fillContainerStats(cs *containerStat, id string) {
|
|
res, err := dockerClient.Get("http://d/containers/" + id + "/stats?stream=false")
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer res.Body.Close()
|
|
var st dockerStatsJSON
|
|
if json.NewDecoder(io.LimitReader(res.Body, 4<<20)).Decode(&st) != nil {
|
|
return
|
|
}
|
|
cs.CPU = containerCPUPct(&st)
|
|
cs.MemUsed = float64(st.MemoryStats.Usage)
|
|
cs.MemLimit = float64(st.MemoryStats.Limit)
|
|
}
|