|
@@ -14,9 +14,11 @@ import (
|
|
|
"time"
|
|
"time"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-var lastCPU struct {
|
|
|
|
|
- idle, total uint64
|
|
|
|
|
- mu sync.Mutex
|
|
|
|
|
|
|
+var cpuStat struct {
|
|
|
|
|
+ sync.Mutex
|
|
|
|
|
+ lastIdle uint64
|
|
|
|
|
+ lastTotal uint64
|
|
|
|
|
+ usage float64
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var systemInfoCache struct {
|
|
var systemInfoCache struct {
|
|
@@ -25,7 +27,7 @@ var systemInfoCache struct {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func initCPUUsage() {
|
|
func initCPUUsage() {
|
|
|
- getCPUUsage()
|
|
|
|
|
|
|
+ updateCPUUsage()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func updateSystemInfoCache() {
|
|
func updateSystemInfoCache() {
|
|
@@ -33,7 +35,7 @@ func updateSystemInfoCache() {
|
|
|
diskPercent, diskUsed, diskTotal := getDiskInfo()
|
|
diskPercent, diskUsed, diskTotal := getDiskInfo()
|
|
|
|
|
|
|
|
data := map[string]any{
|
|
data := map[string]any{
|
|
|
- "cpu_usage": getCPUUsage(),
|
|
|
|
|
|
|
+ "cpu_usage": updateCPUUsage(),
|
|
|
"cpu_cores": getCPUCore(),
|
|
"cpu_cores": getCPUCore(),
|
|
|
"cpu_freq": getCPUFreq(),
|
|
"cpu_freq": getCPUFreq(),
|
|
|
"cpu_temp": getCPUTemperature(),
|
|
"cpu_temp": getCPUTemperature(),
|
|
@@ -74,53 +76,56 @@ func systemInfoHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
json.NewEncoder(w).Encode(data)
|
|
json.NewEncoder(w).Encode(data)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func getCPUUsage() int {
|
|
|
|
|
- lastCPU.mu.Lock()
|
|
|
|
|
- defer lastCPU.mu.Unlock()
|
|
|
|
|
|
|
+func updateCPUUsage() float64 {
|
|
|
|
|
+ cpuStat.Lock()
|
|
|
|
|
+ defer cpuStat.Unlock()
|
|
|
|
|
|
|
|
data, err := os.ReadFile("/proc/stat")
|
|
data, err := os.ReadFile("/proc/stat")
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return 0
|
|
|
|
|
|
|
+ return cpuStat.usage
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- f := strings.Fields(strings.Split(string(data), "\n")[0])
|
|
|
|
|
- if len(f) < 5 {
|
|
|
|
|
- return 0
|
|
|
|
|
|
|
+ fields := strings.Fields(strings.SplitN(string(data), "\n", 2)[0])
|
|
|
|
|
+
|
|
|
|
|
+ if len(fields) < 5 {
|
|
|
|
|
+ return cpuStat.usage
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- var idle, total uint64
|
|
|
|
|
|
|
+ var idle uint64
|
|
|
|
|
+ var total uint64
|
|
|
|
|
|
|
|
- for i := 1; i < len(f); i++ {
|
|
|
|
|
- v, err := strconv.ParseUint(f[i], 10, 64)
|
|
|
|
|
|
|
+ for i := 1; i < len(fields); i++ {
|
|
|
|
|
+ v, err := strconv.ParseUint(fields[i], 10, 64)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
total += v
|
|
total += v
|
|
|
|
|
|
|
|
- // idle + iowait
|
|
|
|
|
if i == 4 || i == 5 {
|
|
if i == 4 || i == 5 {
|
|
|
idle += v
|
|
idle += v
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if lastCPU.total == 0 {
|
|
|
|
|
- lastCPU.idle = idle
|
|
|
|
|
- lastCPU.total = total
|
|
|
|
|
|
|
+ if cpuStat.lastTotal == 0 {
|
|
|
|
|
+ cpuStat.lastTotal = total
|
|
|
|
|
+ cpuStat.lastIdle = idle
|
|
|
return 0
|
|
return 0
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- dt := total - lastCPU.total
|
|
|
|
|
- di := idle - lastCPU.idle
|
|
|
|
|
|
|
+ deltaTotal := total - cpuStat.lastTotal
|
|
|
|
|
+ deltaIdle := idle - cpuStat.lastIdle
|
|
|
|
|
|
|
|
- lastCPU.idle = idle
|
|
|
|
|
- lastCPU.total = total
|
|
|
|
|
|
|
+ cpuStat.lastTotal = total
|
|
|
|
|
+ cpuStat.lastIdle = idle
|
|
|
|
|
|
|
|
- if dt == 0 {
|
|
|
|
|
- return 0
|
|
|
|
|
|
|
+ if deltaTotal == 0 {
|
|
|
|
|
+ return cpuStat.usage
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return int((dt - di) * 100 / dt)
|
|
|
|
|
|
|
+ cpuStat.usage = float64(deltaTotal-deltaIdle) * 100 / float64(deltaTotal)
|
|
|
|
|
+
|
|
|
|
|
+ return cpuStat.usage
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func getCPUCore() int {
|
|
func getCPUCore() int {
|
|
@@ -219,9 +224,7 @@ func getMemInfo() (int, string, string) {
|
|
|
|
|
|
|
|
used := total - avail
|
|
used := total - avail
|
|
|
|
|
|
|
|
- return int(used * 100 / total),
|
|
|
|
|
- formatSize(used * 1024),
|
|
|
|
|
- formatSize(total * 1024)
|
|
|
|
|
|
|
+ return int(used * 100 / total), formatSize(used * 1024), formatSize(total * 1024)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func getDiskInfo() (int, string, string) {
|
|
func getDiskInfo() (int, string, string) {
|
|
@@ -340,10 +343,16 @@ var services = []struct {
|
|
|
{"yfkj-camera-capture.service", "/opt/yfkj/camera-capture.service", "camera-capture"},
|
|
{"yfkj-camera-capture.service", "/opt/yfkj/camera-capture.service", "camera-capture"},
|
|
|
{"yfkj-app-install.service", "/opt/yfkj/app-install.service", "app-install"},
|
|
{"yfkj-app-install.service", "/opt/yfkj/app-install.service", "app-install"},
|
|
|
{"yfkj-web-ui.service", "/opt/yfkj/web-ui.service", "web-ui"},
|
|
{"yfkj-web-ui.service", "/opt/yfkj/web-ui.service", "web-ui"},
|
|
|
|
|
+ {"yfkj-frp-client.service", "/opt/yfkj/frp-client.service", "frpc"},
|
|
|
{"yfkj-upgrade.service", "/opt/yfkj/upgrade.service", "upgrade"},
|
|
{"yfkj-upgrade.service", "/opt/yfkj/upgrade.service", "upgrade"},
|
|
|
{"yfkj-local-startup.service", "/opt/yfkj/local-startup.service", "startup.sh"},
|
|
{"yfkj-local-startup.service", "/opt/yfkj/local-startup.service", "startup.sh"},
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+var serviceStatusActive struct {
|
|
|
|
|
+ sync.Mutex
|
|
|
|
|
+ lastAccess time.Time
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
var serviceStatusCache struct {
|
|
var serviceStatusCache struct {
|
|
|
sync.RWMutex
|
|
sync.RWMutex
|
|
|
data map[string]string
|
|
data map[string]string
|
|
@@ -352,26 +361,69 @@ var serviceStatusCache struct {
|
|
|
func updateServiceStatus() {
|
|
func updateServiceStatus() {
|
|
|
data := make(map[string]string, len(services)*2)
|
|
data := make(map[string]string, len(services)*2)
|
|
|
|
|
|
|
|
- for i, s := range services {
|
|
|
|
|
- n := i + 1
|
|
|
|
|
|
|
+ args := []string{"show",
|
|
|
|
|
+ "-p", "Id",
|
|
|
|
|
+ "-p", "LoadState",
|
|
|
|
|
+ "-p", "ActiveState",
|
|
|
|
|
+ "-p", "SubState",
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, s := range services {
|
|
|
|
|
+ args = append(args, s.name)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ statusMap := make(map[string]string, len(services))
|
|
|
|
|
+
|
|
|
|
|
+ out, _ := exec.Command("systemctl", args...).CombinedOutput()
|
|
|
|
|
+
|
|
|
|
|
+ var name, loadState, activeState, subState string
|
|
|
|
|
+
|
|
|
|
|
+ save := func() {
|
|
|
|
|
+ if name == "" {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
status := noValue
|
|
status := noValue
|
|
|
|
|
|
|
|
- if out, err := exec.Command("systemctl", "show", s.name,
|
|
|
|
|
- "-p", "LoadState",
|
|
|
|
|
- "-p", "ActiveState",
|
|
|
|
|
- "-p", "SubState").Output(); err == nil {
|
|
|
|
|
- v := string(out)
|
|
|
|
|
- switch {
|
|
|
|
|
- case strings.Contains(v, "ActiveState=active") && strings.Contains(v, "SubState=running"):
|
|
|
|
|
- status = "🟢运行中"
|
|
|
|
|
- case strings.Contains(v, "ActiveState=active") && strings.Contains(v, "SubState=exited"):
|
|
|
|
|
- status = "🔵已完成"
|
|
|
|
|
- case strings.Contains(v, "LoadState=loaded") && strings.Contains(v, "ActiveState=inactive"):
|
|
|
|
|
- status = "🟡已停止"
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ switch {
|
|
|
|
|
+ case activeState == "active" && subState == "running":
|
|
|
|
|
+ status = "🟢运行中"
|
|
|
|
|
+ case activeState == "active" && subState == "exited":
|
|
|
|
|
+ status = "🔵已完成"
|
|
|
|
|
+ case loadState == "loaded" && activeState == "inactive":
|
|
|
|
|
+ status = "🟡已停止"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- data[fmt.Sprintf("var%d", n)] = status
|
|
|
|
|
|
|
+ statusMap[name] = status
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for line := range strings.SplitSeq(string(out), "\n") {
|
|
|
|
|
+ if strings.HasPrefix(line, "Id=") {
|
|
|
|
|
+ save() // Save the previous service status before starting a new one
|
|
|
|
|
+
|
|
|
|
|
+ name = strings.TrimPrefix(line, "Id=")
|
|
|
|
|
+ loadState = ""
|
|
|
|
|
+ activeState = ""
|
|
|
|
|
+ subState = ""
|
|
|
|
|
+ } else if v, ok := strings.CutPrefix(line, "LoadState="); ok {
|
|
|
|
|
+ loadState = v
|
|
|
|
|
+ } else if v, ok := strings.CutPrefix(line, "ActiveState="); ok {
|
|
|
|
|
+ activeState = v
|
|
|
|
|
+ } else if v, ok := strings.CutPrefix(line, "SubState="); ok {
|
|
|
|
|
+ subState = v
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ save() // Save the last service status
|
|
|
|
|
+
|
|
|
|
|
+ for i, s := range services {
|
|
|
|
|
+ n := i + 1
|
|
|
|
|
+
|
|
|
|
|
+ if v, ok := statusMap[s.name]; ok {
|
|
|
|
|
+ data[fmt.Sprintf("var%d", n)] = v
|
|
|
|
|
+ } else {
|
|
|
|
|
+ data[fmt.Sprintf("var%d", n)] = noValue
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if v, err := os.ReadFile(filepath.Join(s.dir, "version.txt")); err == nil {
|
|
if v, err := os.ReadFile(filepath.Join(s.dir, "version.txt")); err == nil {
|
|
|
data[fmt.Sprintf("var%d%d", n, n)] = "v" + strings.TrimSpace(string(v))
|
|
data[fmt.Sprintf("var%d%d", n, n)] = "v" + strings.TrimSpace(string(v))
|
|
@@ -386,12 +438,24 @@ func updateServiceStatus() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func startServiceCache() {
|
|
func startServiceCache() {
|
|
|
- updateServiceStatus()
|
|
|
|
|
go func() {
|
|
go func() {
|
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
|
defer ticker.Stop()
|
|
defer ticker.Stop()
|
|
|
|
|
+
|
|
|
for range ticker.C {
|
|
for range ticker.C {
|
|
|
- updateServiceStatus()
|
|
|
|
|
|
|
+ serviceStatusActive.Lock()
|
|
|
|
|
+
|
|
|
|
|
+ if !serviceStatusActive.lastAccess.IsZero() && time.Since(serviceStatusActive.lastAccess) >= 10*time.Second {
|
|
|
|
|
+ serviceStatusActive.lastAccess = time.Time{}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ active := !serviceStatusActive.lastAccess.IsZero()
|
|
|
|
|
+
|
|
|
|
|
+ serviceStatusActive.Unlock()
|
|
|
|
|
+
|
|
|
|
|
+ if active {
|
|
|
|
|
+ updateServiceStatus()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}()
|
|
}()
|
|
|
}
|
|
}
|
|
@@ -402,6 +466,15 @@ func serviceStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ serviceStatusActive.Lock()
|
|
|
|
|
+ first := serviceStatusActive.lastAccess.IsZero() || time.Since(serviceStatusActive.lastAccess) >= 10*time.Second
|
|
|
|
|
+ serviceStatusActive.lastAccess = time.Now()
|
|
|
|
|
+ serviceStatusActive.Unlock()
|
|
|
|
|
+
|
|
|
|
|
+ if first {
|
|
|
|
|
+ updateServiceStatus()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
serviceStatusCache.RLock()
|
|
serviceStatusCache.RLock()
|
|
|
data := serviceStatusCache.data
|
|
data := serviceStatusCache.data
|
|
|
serviceStatusCache.RUnlock()
|
|
serviceStatusCache.RUnlock()
|