fix: 修复6个边缘侧问题 + 单元测试
1. 删除 ConfigAgent (与 MQTT 配置更新重叠且未启用) 2. OTA 完整安装流程: 解压tar.gz -> 校验ELF -> 替换二进制 -> systemctl重启 3. StreamManager stopAll 添加 Wait() 防止僵尸进程 4. InferClient socket 读写添加 timeout 防止永久阻塞 5. Heartbeat 集成 NPU 监控 (npu-smi + fallback 脚本) 6. 配置更新时动态重启 ffmpeg 以应用新 fps
This commit is contained in:
@@ -7,6 +7,9 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tianyan-edge/internal/config"
|
||||
@@ -24,6 +27,10 @@ func (h *Heartbeat) Run(ctx context.Context) {
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
// Send first heartbeat immediately
|
||||
h.send(client)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -40,6 +47,12 @@ func (h *Heartbeat) send(client *http.Client) {
|
||||
"version": h.cfg.Version,
|
||||
"ts": float64(time.Now().UnixMilli()) / 1000.0,
|
||||
}
|
||||
|
||||
// Collect NPU stats if available
|
||||
if npu := collectNPUStats(); npu != nil {
|
||||
payload["npu"] = npu
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(payload)
|
||||
url := fmt.Sprintf("%s/api/v1/edge/heartbeat", h.cfg.CloudURL)
|
||||
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
||||
@@ -52,3 +65,113 @@ func (h *Heartbeat) send(client *http.Client) {
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
// NPUStats holds parsed NPU telemetry
|
||||
type NPUStats struct {
|
||||
TempC int `json:"temp_c"`
|
||||
UtilPct int `json:"util_pct"`
|
||||
MemUsedMB int `json:"mem_used_mb"`
|
||||
MemTotalMB int `json:"mem_total_mb"`
|
||||
MemPct int `json:"mem_pct"`
|
||||
}
|
||||
|
||||
// collectNPUStats runs npu-smi and parses the output
|
||||
func collectNPUStats() *NPUStats {
|
||||
// Try npu-smi first (Ascend devices), fall back to the helper script
|
||||
var commonOut, memOut []byte
|
||||
var err error
|
||||
|
||||
commonOut, err = exec.Command("/usr/local/sbin/npu-smi", "info", "-t", "common", "-i", "0").CombinedOutput()
|
||||
if err != nil {
|
||||
// Fallback to shell script
|
||||
commonOut, err = exec.Command("bash", "/opt/tianyan-edge/scripts/npu_info.sh").CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("npu: monitoring unavailable: %v", err)
|
||||
return nil
|
||||
}
|
||||
// If script succeeded, it outputs Influx line protocol — parse that
|
||||
line := strings.TrimSpace(string(commonOut))
|
||||
return parseInfluxLine(line)
|
||||
}
|
||||
|
||||
memOut, _ = exec.Command("/usr/local/sbin/npu-smi", "info", "-t", "memory", "-i", "0").CombinedOutput()
|
||||
|
||||
return parseNpuSmiOutput(string(commonOut), string(memOut))
|
||||
}
|
||||
|
||||
func parseNpuSmiOutput(common, memory string) *NPUStats {
|
||||
stats := &NPUStats{}
|
||||
|
||||
for _, line := range strings.Split(common, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.Contains(line, "Temperature") {
|
||||
if v := extractLastNumber(line); v != 0 {
|
||||
stats.TempC = v
|
||||
}
|
||||
}
|
||||
if strings.Contains(line, "Aicore Usage Rate") {
|
||||
if v := extractLastNumber(line); v != 0 {
|
||||
stats.UtilPct = v
|
||||
}
|
||||
}
|
||||
if strings.Contains(line, "Memory Usage Rate") {
|
||||
if v := extractLastNumber(line); v != 0 {
|
||||
stats.MemPct = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(memory, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.Contains(line, "Capacity") {
|
||||
if v := extractLastNumber(line); v != 0 {
|
||||
stats.MemTotalMB = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if stats.MemTotalMB > 0 && stats.MemPct > 0 {
|
||||
stats.MemUsedMB = stats.MemTotalMB * stats.MemPct / 100
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func parseInfluxLine(line string) *NPUStats {
|
||||
// Format: npu_status,device=0 temp=X,utilization=Y,memory_used=Z,memory_total=W
|
||||
stats := &NPUStats{}
|
||||
parts := strings.Split(line, " ")
|
||||
if len(parts) < 2 {
|
||||
return nil
|
||||
}
|
||||
for _, field := range strings.Split(parts[1], ",") {
|
||||
kv := strings.SplitN(field, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
v, _ := strconv.Atoi(kv[1])
|
||||
switch kv[0] {
|
||||
case "temp":
|
||||
stats.TempC = v
|
||||
case "utilization":
|
||||
stats.UtilPct = v
|
||||
case "memory_used":
|
||||
stats.MemUsedMB = v
|
||||
case "memory_total":
|
||||
stats.MemTotalMB = v
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
func extractLastNumber(line string) int {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 0 {
|
||||
return 0
|
||||
}
|
||||
// Last field might have units like "%" or "C"
|
||||
raw := fields[len(fields)-1]
|
||||
raw = strings.TrimRight(raw, "%C ")
|
||||
v, _ := strconv.Atoi(raw)
|
||||
return v
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user