功能:扩展云管资源运维、账号授权及监控管理
This commit is contained in:
1 parent
eef485306f
commit
6134d49c16
87 files changed
+29498
-126
No files matched your search
@@ -0,0 +1,97 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// Values are optional: unavailable sensors are null, never fabricated zeroes.
|
||||
type terminalSystemMetrics struct {
|
||||
CPUPercent *float64 `json:"cpuPercent" db:"cpu_percent" binding:"omitempty,min=0,max=100"`
|
||||
MemoryPercent *float64 `json:"memoryPercent" db:"memory_percent" binding:"omitempty,min=0,max=100"`
|
||||
SoCTemperatureC *float64 `json:"socTemperatureC" db:"soc_temperature_c" binding:"omitempty,min=-40,max=150"`
|
||||
UptimeSeconds *float64 `json:"uptimeSeconds" db:"uptime_seconds" binding:"omitempty,min=0,max=3155760000"`
|
||||
}
|
||||
|
||||
func saveTerminalMetrics(ctx context.Context, tx *sqlx.Tx, id string, input *terminalSystemMetrics) error {
|
||||
metrics := terminalSystemMetrics{}
|
||||
if input != nil {
|
||||
metrics = *input
|
||||
}
|
||||
// Device auth already holds the terminal row lock, serializing concurrent heartbeats.
|
||||
_, err := tx.ExecContext(ctx, `INSERT INTO terminal_metrics_current(terminal_id,sampled_at,supported,cpu_percent,memory_percent,soc_temperature_c,uptime_seconds)
|
||||
VALUES($1,now(),$2,$3,$4,$5,$6) ON CONFLICT(terminal_id) DO UPDATE SET
|
||||
sampled_at=EXCLUDED.sampled_at,supported=EXCLUDED.supported,cpu_percent=EXCLUDED.cpu_percent,
|
||||
memory_percent=EXCLUDED.memory_percent,soc_temperature_c=EXCLUDED.soc_temperature_c,uptime_seconds=EXCLUDED.uptime_seconds`,
|
||||
id, input != nil, metrics.CPUPercent, metrics.MemoryPercent, metrics.SoCTemperatureC, metrics.UptimeSeconds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Bound history growth even if an agent retries aggressively. Preserve null samples on downgrade.
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO terminal_metric_samples(terminal_id,sampled_at,cpu_percent,memory_percent,soc_temperature_c,uptime_seconds)
|
||||
SELECT $1,now(),$2,$3,$4,$5 WHERE NOT EXISTS(SELECT 1 FROM terminal_metric_samples
|
||||
WHERE terminal_id=$1 AND sampled_at>now()-interval '10 seconds')`,
|
||||
id, metrics.CPUPercent, metrics.MemoryPercent, metrics.SoCTemperatureC, metrics.UptimeSeconds)
|
||||
return err
|
||||
}
|
||||
|
||||
type terminalMetricPoint struct {
|
||||
terminalSystemMetrics
|
||||
Time time.Time `json:"time" db:"time"`
|
||||
Samples int `json:"samples" db:"samples"`
|
||||
}
|
||||
|
||||
func (s *Server) terminalMetrics(c *gin.Context) {
|
||||
from, to, ok := timeRange(c, 7)
|
||||
if !ok {
|
||||
s.fail(c, 400, "invalid_range", "请选择有效时间范围,最长 7 天")
|
||||
return
|
||||
}
|
||||
var latest struct {
|
||||
terminalSystemMetrics
|
||||
SampledAt *time.Time `json:"sampledAt" db:"sampled_at"`
|
||||
Supported bool `json:"supported" db:"supported"`
|
||||
Online bool `json:"online" db:"online"`
|
||||
Fresh bool `json:"fresh" db:"fresh"`
|
||||
}
|
||||
// Query through the terminal identity so unknown devices return 404. A stopped,
|
||||
// revoked or stale device keeps history but cannot present an old value as live.
|
||||
err := s.DB.GetContext(c.Request.Context(), &latest, `SELECT m.sampled_at,COALESCE(m.supported,false) supported,
|
||||
COALESCE(t.enabled AND t.last_seen>now()-interval '90 seconds',false) online,
|
||||
COALESCE(t.enabled AND t.last_seen>now()-interval '90 seconds' AND m.sampled_at>now()-interval '90 seconds',false) fresh,
|
||||
CASE WHEN t.enabled AND t.last_seen>now()-interval '90 seconds' AND m.sampled_at>now()-interval '90 seconds' THEN m.cpu_percent END cpu_percent,
|
||||
CASE WHEN t.enabled AND t.last_seen>now()-interval '90 seconds' AND m.sampled_at>now()-interval '90 seconds' THEN m.memory_percent END memory_percent,
|
||||
CASE WHEN t.enabled AND t.last_seen>now()-interval '90 seconds' AND m.sampled_at>now()-interval '90 seconds' THEN m.soc_temperature_c END soc_temperature_c,
|
||||
CASE WHEN t.enabled AND t.last_seen>now()-interval '90 seconds' AND m.sampled_at>now()-interval '90 seconds' THEN m.uptime_seconds END uptime_seconds
|
||||
FROM terminals t LEFT JOIN terminal_metrics_current m ON m.terminal_id=t.id WHERE t.id=$1`, c.Param("id"))
|
||||
if err != nil {
|
||||
s.dbError(c, err)
|
||||
return
|
||||
}
|
||||
bucket := 60
|
||||
if to.Sub(from) > 6*time.Hour {
|
||||
bucket = 300
|
||||
}
|
||||
if to.Sub(from) > 24*time.Hour {
|
||||
bucket = 1800
|
||||
}
|
||||
points := []terminalMetricPoint{}
|
||||
// Explicit empty buckets break the chart across missing heartbeats.
|
||||
err = s.DB.SelectContext(c.Request.Context(), &points, `WITH data AS (
|
||||
SELECT date_bin(make_interval(secs=>$4),sampled_at,'2000-01-01'::timestamptz) time,
|
||||
avg(cpu_percent) cpu_percent,avg(memory_percent) memory_percent,avg(soc_temperature_c) soc_temperature_c,
|
||||
max(uptime_seconds) uptime_seconds,count(*) samples FROM terminal_metric_samples
|
||||
WHERE terminal_id=$1 AND sampled_at>=$2 AND sampled_at<=$3 GROUP BY 1)
|
||||
SELECT b.time,d.cpu_percent,d.memory_percent,d.soc_temperature_c,d.uptime_seconds,COALESCE(d.samples,0) samples
|
||||
FROM generate_series(date_bin(make_interval(secs=>$4),$2::timestamptz,'2000-01-01'::timestamptz),
|
||||
date_bin(make_interval(secs=>$4),$3::timestamptz,'2000-01-01'::timestamptz),make_interval(secs=>$4)) b(time)
|
||||
LEFT JOIN data d ON d.time=b.time ORDER BY b.time`, c.Param("id"), from, to, bucket)
|
||||
if err != nil {
|
||||
s.dbError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"latest": latest, "items": points, "from": from, "to": to, "bucketSeconds": bucket, "retentionDays": 30})
|
||||
}
|
||||
Reference in new issue
Block a user