mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-06 03:14:12 +08:00
✨ Sync 2026-10-20 03:34:23
This commit is contained in:
1 parent
d210fa85f8
commit
cbd3ce4959
21 files changed
+2002
-90
No files matched your search
@@ -1,9 +1,17 @@
|
||||
config agent 'agent'
|
||||
option enabled '0'
|
||||
option port '45876'
|
||||
option public_key ''
|
||||
option hub_url '' # optional, for outbound mode (websockets)
|
||||
option token '' # optional, for outbound mode (websockets)
|
||||
option extra_filesystems '' # optional, comma-separated, e.g. 'sda1__NAS'
|
||||
option fingerprint '' # auto-populated on first start, do not edit
|
||||
config beszel-agent 'main'
|
||||
option boot_delay '15'
|
||||
option enable '0'
|
||||
option data_dir '/etc/beszel-agent'
|
||||
option docker_host ''
|
||||
option hub_url ''
|
||||
option intel_gpu_device ''
|
||||
option key ''
|
||||
option listen '0.0.0.0:45876'
|
||||
option log_level 'info'
|
||||
option system_name 'openwrt'
|
||||
option token ''
|
||||
|
||||
# Load additional file systems (optional)
|
||||
# Add multiple mount points using 'list' syntax
|
||||
# list extra_filesystems '/mnt/sda1'
|
||||
# list extra_filesystems '/mnt/sdb1'
|
||||
@@ -1,75 +1,124 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
NAME=beszel-agent
|
||||
PROG=/usr/bin/beszel-agent
|
||||
DATA_DIR=/var/lib/beszel-agent
|
||||
FP_FILE="$DATA_DIR/fingerprint"
|
||||
OLD_FP_FILE=/usr/share/beszel-agent/fingerprint
|
||||
|
||||
append_fs() {
|
||||
local value="$1"
|
||||
if [ -z "$FS_LIST" ]; then
|
||||
FS_LIST="$value"
|
||||
else
|
||||
FS_LIST="$FS_LIST,$value"
|
||||
fi
|
||||
}
|
||||
|
||||
append_nic() {
|
||||
local value="$1"
|
||||
if [ -z "$NIC_LIST" ]; then
|
||||
NIC_LIST="$value"
|
||||
else
|
||||
NIC_LIST="$NIC_LIST,$value"
|
||||
fi
|
||||
}
|
||||
|
||||
boot() {
|
||||
local delay
|
||||
delay="$(uci -q get $NAME.@main.boot_delay)"
|
||||
sleep "${delay:-15}"
|
||||
start "$@"
|
||||
}
|
||||
|
||||
start_service() {
|
||||
local enabled port key url token extra_fs fp
|
||||
local enable data_dir docker_host gpu_collector hub_url intel_gpu_device key listen log_level port token skip_gpu skip_systemd system_name
|
||||
|
||||
config_load 'beszel-agent'
|
||||
config_get_bool enabled 'agent' 'enabled' '0'
|
||||
config_get port 'agent' 'port' '45876'
|
||||
config_get key 'agent' 'public_key' ''
|
||||
config_get url 'agent' 'hub_url' ''
|
||||
config_get token 'agent' 'token' ''
|
||||
config_get extra_fs 'agent' 'extra_filesystems' ''
|
||||
config_get fp 'agent' 'fingerprint' ''
|
||||
config_load "$NAME"
|
||||
|
||||
[ "$enabled" -eq 0 ] && return 0
|
||||
config_get_bool enable "main" "enable" 0
|
||||
[ "$enable" -eq 1 ] || return 0
|
||||
|
||||
[ -z "$key" ] && {
|
||||
logger -t beszel-agent "Public key not set!"
|
||||
return 1
|
||||
}
|
||||
config_get data_dir "main" "data_dir" "/etc/beszel-agent"
|
||||
config_get docker_host "main" "docker_host" ""
|
||||
config_get gpu_collector "main" "gpu_collector" ""
|
||||
config_get hub_url "main" "hub_url" ""
|
||||
config_get intel_gpu_device "main" "intel_gpu_device" ""
|
||||
config_get key "main" "key" ""
|
||||
config_get listen "main" "listen" ""
|
||||
config_get log_level "main" "log_level" "info"
|
||||
config_get port "main" "port" "45876"
|
||||
config_get skip_gpu "main" "skip_gpu" "false"
|
||||
case "$(uname -m)" in
|
||||
x86_64|i?86)
|
||||
if ! ls /sys/class/drm/card[0-9]* >/dev/null 2>&1; then
|
||||
skip_gpu="true"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
skip_gpu="true"
|
||||
;;
|
||||
esac
|
||||
config_get skip_systemd "main" "skip_systemd" "false"
|
||||
config_get system_name "main" "system_name" ""
|
||||
config_get token "main" "token" ""
|
||||
|
||||
if [ -n "$url" ] || [ -n "$token" ]; then
|
||||
if [ -z "$url" ] || [ -z "$token" ]; then
|
||||
logger -t beszel-agent "Outbound mode requires both hub_url and token, but only one set!"
|
||||
return 1
|
||||
if [ -n "$data_dir" ]; then
|
||||
mkdir -p "$data_dir"
|
||||
if [ -f /etc/sysupgrade.conf ]; then
|
||||
if ! grep -qFx "$data_dir" /etc/sysupgrade.conf; then
|
||||
echo "$data_dir" >> /etc/sysupgrade.conf
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$DATA_DIR"
|
||||
FS_LIST=""
|
||||
config_list_foreach "main" "extra_filesystems" append_fs
|
||||
|
||||
# migrate from pre-UCI storage on flash
|
||||
[ -z "$fp" ] && [ -s "$OLD_FP_FILE" ] && fp=$(cat "$OLD_FP_FILE")
|
||||
NIC_LIST=""
|
||||
config_list_foreach "main" "nics" append_nic
|
||||
|
||||
if [ -z "$fp" ]; then
|
||||
DATA_DIR="$DATA_DIR" "$PROG" fingerprint > /dev/null 2>&1
|
||||
[ -s "$FP_FILE" ] && fp=$(cat "$FP_FILE")
|
||||
fi
|
||||
procd_open_instance "beszel-agent"
|
||||
procd_set_param command "$PROG"
|
||||
|
||||
if [ -n "$fp" ]; then
|
||||
printf '%s' "$fp" > "$FP_FILE"
|
||||
[ "$(uci -q get beszel-agent.agent.fingerprint)" = "$fp" ] || {
|
||||
uci set beszel-agent.agent.fingerprint="$fp"
|
||||
uci commit beszel-agent
|
||||
logger -t beszel-agent "Fingerprint saved to UCI: $fp"
|
||||
}
|
||||
else
|
||||
logger -t beszel-agent "Failed to obtain fingerprint, agent will generate one"
|
||||
fi
|
||||
procd_set_param env HOST_PROC="/proc" HOST_SYS="/sys"
|
||||
|
||||
procd_open_instance beszel-agent
|
||||
procd_set_param command "$PROG" -listen "$port" -key "$key"
|
||||
[ -n "$url" ] && [ -n "$token" ] && {
|
||||
procd_append_param command -url "$url" -token "$token"
|
||||
}
|
||||
if [ -n "$listen" ]; then
|
||||
procd_append_param env LISTEN="$listen"
|
||||
else
|
||||
procd_append_param env PORT="$port"
|
||||
fi
|
||||
|
||||
procd_set_param env DATA_DIR="$DATA_DIR"
|
||||
[ -n "$extra_fs" ] && procd_append_param env EXTRA_FILESYSTEMS="$extra_fs"
|
||||
procd_set_param respawn 3600 5 5
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
[ -n "$docker_host" ] && procd_append_param env DOCKER_HOST="$docker_host"
|
||||
[ -n "$data_dir" ] && procd_append_param env DATA_DIR="$data_dir"
|
||||
[ -n "$FS_LIST" ] && procd_append_param env EXTRA_FILESYSTEMS="$FS_LIST"
|
||||
[ -n "$hub_url" ] && procd_append_param env HUB_URL="$hub_url"
|
||||
[ -n "$key" ] && procd_append_param env KEY="$key"
|
||||
[ -n "$log_level" ] && procd_append_param env LOG_LEVEL="$log_level"
|
||||
[ -n "$NIC_LIST" ] && procd_append_param env NICS="$NIC_LIST"
|
||||
[ -n "$skip_gpu" ] && procd_append_param env SKIP_GPU="$skip_gpu"
|
||||
[ -n "$skip_systemd" ] && procd_append_param env SKIP_SYSTEMD="$skip_systemd"
|
||||
[ -n "$system_name" ] && procd_append_param env SYSTEM_NAME="$system_name"
|
||||
[ -n "$token" ] && procd_append_param env TOKEN="$token"
|
||||
|
||||
if [ "$skip_gpu" = "false" ]; then
|
||||
[ -n "$gpu_collector" ] && procd_append_param env GPU_COLLECTOR="$gpu_collector"
|
||||
if [ "$gpu_collector" = "intel_gpu_top" ] || [ -z "$gpu_collector" ]; then
|
||||
[ -n "$intel_gpu_device" ] && procd_append_param env INTEL_GPU_DEVICE="$intel_gpu_device"
|
||||
fi
|
||||
fi
|
||||
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger 'beszel-agent'
|
||||
procd_add_reload_trigger "beszel-agent"
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
//go:build linux && openwrt
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/system"
|
||||
)
|
||||
|
||||
const intelGpuStatsCmd = "sh"
|
||||
|
||||
type intelGpuStats struct {
|
||||
PowerGPU float64
|
||||
PowerPkg float64
|
||||
Engines map[string]float64
|
||||
}
|
||||
|
||||
var (
|
||||
gpuStateMu sync.Mutex
|
||||
lastRc6 uint64
|
||||
lastGpuEnergy uint64
|
||||
lastPkgEnergy uint64
|
||||
lastTime time.Time
|
||||
)
|
||||
|
||||
func getEnergyPaths() (gpuPath string, pkgPath string) {
|
||||
pkgBase := "/sys/class/powercap/intel-rapl/intel-rapl:0"
|
||||
gpuBase := "/sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:1"
|
||||
|
||||
if _, err := os.Stat(pkgBase + "/energy_uj"); err == nil {
|
||||
pkgPath = pkgBase + "/energy_uj"
|
||||
}
|
||||
if _, err := os.Stat(gpuBase + "/energy_uj"); err == nil {
|
||||
gpuPath = gpuBase + "/energy_uj"
|
||||
}
|
||||
return gpuPath, pkgPath
|
||||
}
|
||||
|
||||
func readUint64(path string) uint64 {
|
||||
if path == "" {
|
||||
return 0
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
val, _ := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
||||
return val
|
||||
}
|
||||
|
||||
func (gm *GPUManager) updateIntelFromStats(sample *intelGpuStats) bool {
|
||||
gm.Lock()
|
||||
defer gm.Unlock()
|
||||
|
||||
id := "0"
|
||||
gpuData, ok := gm.GpuDataMap[id]
|
||||
if !ok {
|
||||
gpuData = &system.GPUData{Name: "GPU", Engines: make(map[string]float64)}
|
||||
gm.GpuDataMap[id] = gpuData
|
||||
}
|
||||
|
||||
gpuData.Power += sample.PowerGPU
|
||||
gpuData.PowerPkg += sample.PowerPkg
|
||||
|
||||
if gpuData.Engines == nil {
|
||||
gpuData.Engines = make(map[string]float64, len(sample.Engines))
|
||||
}
|
||||
for name, engine := range sample.Engines {
|
||||
gpuData.Engines[name] += engine
|
||||
}
|
||||
|
||||
gpuData.Count++
|
||||
return true
|
||||
}
|
||||
|
||||
func (gm *GPUManager) collectIntelStats() (err error) {
|
||||
gpuStateMu.Lock()
|
||||
defer gpuStateMu.Unlock()
|
||||
|
||||
device := os.Getenv("INTEL_GPU_DEVICE")
|
||||
if device == "" {
|
||||
device = "card0"
|
||||
}
|
||||
device = filepath.Base(device)
|
||||
|
||||
rc6Path := "/sys/class/drm/" + device + "/power/rc6_residency_ms"
|
||||
gpuEnergyPath, pkgEnergyPath := getEnergyPaths()
|
||||
|
||||
currRc6 := readUint64(rc6Path)
|
||||
currGpuEnergy := readUint64(gpuEnergyPath)
|
||||
currPkgEnergy := readUint64(pkgEnergyPath)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if !lastTime.IsZero() {
|
||||
timeDelta := uint64(now.Sub(lastTime).Milliseconds())
|
||||
if timeDelta > 500 {
|
||||
rc6Delta := currRc6 - lastRc6
|
||||
usage := 100.0 - (float64(rc6Delta) / float64(timeDelta) * 100.0)
|
||||
if usage < 0 { usage = 0 }
|
||||
|
||||
var powerGPU float64
|
||||
if currGpuEnergy > 0 && currGpuEnergy >= lastGpuEnergy {
|
||||
energyDelta := currGpuEnergy - lastGpuEnergy
|
||||
powerGPU = float64(energyDelta) / float64(timeDelta) / 1000.0
|
||||
}
|
||||
|
||||
var powerPkg float64
|
||||
if currPkgEnergy > 0 && currPkgEnergy >= lastPkgEnergy {
|
||||
energyDelta := currPkgEnergy - lastPkgEnergy
|
||||
powerPkg = float64(energyDelta) / float64(timeDelta) / 1000.0
|
||||
}
|
||||
|
||||
sample := intelGpuStats{
|
||||
PowerGPU: powerGPU,
|
||||
PowerPkg: powerPkg,
|
||||
Engines: map[string]float64{
|
||||
"Render/3D": usage,
|
||||
},
|
||||
}
|
||||
gm.updateIntelFromStats(&sample)
|
||||
}
|
||||
}
|
||||
|
||||
lastRc6 = currRc6
|
||||
lastGpuEnergy = currGpuEnergy
|
||||
lastPkgEnergy = currPkgEnergy
|
||||
lastTime = now
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
//go:build linux && openwrt
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-systemd/v22/dbus"
|
||||
"github.com/henrygd/beszel/agent/utils"
|
||||
"github.com/henrygd/beszel/internal/entities/systemd"
|
||||
)
|
||||
|
||||
var errNoActiveTime = errors.New("no active time")
|
||||
|
||||
type systemdManager struct {
|
||||
sync.Mutex
|
||||
serviceStatsMap map[string]*systemd.Service
|
||||
isRunning bool
|
||||
hasFreshStats bool
|
||||
patterns []string
|
||||
logsEnabled bool
|
||||
}
|
||||
|
||||
func isSystemdAvailable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func newSystemdManager() (*systemdManager, error) {
|
||||
if skipSystemd, _ := utils.GetEnv("SKIP_SYSTEMD"); skipSystemd == "true" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
manager := &systemdManager{
|
||||
serviceStatsMap: make(map[string]*systemd.Service),
|
||||
patterns: getServicePatterns(),
|
||||
}
|
||||
|
||||
manager.startWorker(nil)
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
func (sm *systemdManager) startWorker(conn *dbus.Conn) {
|
||||
if sm.isRunning {
|
||||
return
|
||||
}
|
||||
sm.isRunning = true
|
||||
_ = sm.getServiceStats(nil, true)
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Minute * 10)
|
||||
_ = sm.getServiceStats(nil, true)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (sm *systemdManager) getServiceStatsCount() int {
|
||||
return len(sm.serviceStatsMap)
|
||||
}
|
||||
|
||||
func (sm *systemdManager) getFailedServiceCount() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (sm *systemdManager) getServiceStats(conn *dbus.Conn, refresh bool) []*systemd.Service {
|
||||
var services []*systemd.Service
|
||||
|
||||
if !refresh {
|
||||
sm.Lock()
|
||||
defer sm.Unlock()
|
||||
for _, service := range sm.serviceStatsMap {
|
||||
services = append(services, service)
|
||||
}
|
||||
sm.hasFreshStats = false
|
||||
return services
|
||||
}
|
||||
|
||||
out, err := exec.Command("ubus", "call", "service", "list").Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UbusInstance struct {
|
||||
Running bool `json:"running"`
|
||||
Pid int `json:"pid"`
|
||||
}
|
||||
type UbusService struct {
|
||||
Instances map[string]UbusInstance `json:"instances"`
|
||||
}
|
||||
|
||||
var ubusData map[string]UbusService
|
||||
if err := json.Unmarshal(out, &ubusData); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
currentUnits := make(map[string]struct{})
|
||||
pageSize := uint64(os.Getpagesize())
|
||||
if pageSize == 0 {
|
||||
pageSize = 4096
|
||||
}
|
||||
|
||||
sm.Lock()
|
||||
defer sm.Unlock()
|
||||
|
||||
for sName, sData := range ubusData {
|
||||
unitName := sName + ".service"
|
||||
currentUnits[unitName] = struct{}{}
|
||||
|
||||
pid := 0
|
||||
isRunning := false
|
||||
for _, inst := range sData.Instances {
|
||||
if inst.Running && inst.Pid > 0 {
|
||||
pid = inst.Pid
|
||||
isRunning = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
service, exists := sm.serviceStatsMap[unitName]
|
||||
if !exists {
|
||||
service = &systemd.Service{Name: sName}
|
||||
sm.serviceStatsMap[unitName] = service
|
||||
}
|
||||
|
||||
if isRunning {
|
||||
service.State = systemd.ParseServiceStatus("active")
|
||||
service.Sub = systemd.ParseServiceSubState("running")
|
||||
|
||||
if data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)); err == nil {
|
||||
binEnd := strings.LastIndex(string(data), ")")
|
||||
if binEnd != -1 && len(string(data)) > binEnd+2 {
|
||||
fields := strings.Fields(string(data)[binEnd+2:])
|
||||
if len(fields) > 21 {
|
||||
utime, _ := strconv.ParseUint(fields[11], 10, 64)
|
||||
stime, _ := strconv.ParseUint(fields[12], 10, 64)
|
||||
rss, _ := strconv.ParseUint(fields[21], 10, 64)
|
||||
|
||||
service.Mem = rss * pageSize
|
||||
if service.Mem > service.MemPeak {
|
||||
service.MemPeak = service.Mem
|
||||
}
|
||||
service.UpdateCPUPercent((utime + stime) * 10000000)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
service.State = systemd.ParseServiceStatus("active")
|
||||
service.Sub = systemd.ParseServiceSubState("exited")
|
||||
service.Mem = 0
|
||||
service.UpdateCPUPercent(0)
|
||||
}
|
||||
services = append(services, service)
|
||||
}
|
||||
|
||||
for unitName := range sm.serviceStatsMap {
|
||||
if _, exists := currentUnits[unitName]; !exists {
|
||||
delete(sm.serviceStatsMap, unitName)
|
||||
}
|
||||
}
|
||||
|
||||
sm.hasFreshStats = true
|
||||
return services
|
||||
}
|
||||
|
||||
func (sm *systemdManager) updateServiceStats(conn *dbus.Conn, unit dbus.UnitStatus) (*systemd.Service, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (sm *systemdManager) getServiceDetails(serviceName string) (systemd.ServiceDetails, error) {
|
||||
details := make(systemd.ServiceDetails)
|
||||
uName := strings.TrimSuffix(serviceName, ".service")
|
||||
initScript := "/etc/init.d/" + uName
|
||||
|
||||
// 1. Get cache data (used to obtain historical memory peaks)
|
||||
sm.Lock()
|
||||
service, exists := sm.serviceStatsMap[serviceName]
|
||||
if !exists {
|
||||
service, exists = sm.serviceStatsMap[uName]
|
||||
}
|
||||
sm.Unlock()
|
||||
|
||||
// Initialize the default value
|
||||
details["ActiveState"] = "inactive"
|
||||
details["SubState"] = "dead"
|
||||
details["Result"] = "success"
|
||||
details["MemoryCurrent"] = uint64(0)
|
||||
details["MemoryPeak"] = uint64(0)
|
||||
details["MainPID"] = uint64(0)
|
||||
details["TasksCurrent"] = uint64(0)
|
||||
|
||||
// 2. Real-time query Ubus to get the status, PID, number of concurrent threads and memory RSS
|
||||
var isLoadedInProcd bool = false
|
||||
var isRunningRealTime bool = false
|
||||
var currentMem uint64 = 0
|
||||
|
||||
out, err := exec.Command("ubus", "call", "service", "list", fmt.Sprintf(`{"name":"%s"}`, uName)).Output()
|
||||
if err == nil {
|
||||
type UbusInstance struct {
|
||||
Running bool `json:"running"`
|
||||
Pid int `json:"pid"`
|
||||
}
|
||||
type UbusService struct {
|
||||
Instances map[string]UbusInstance `json:"instances"`
|
||||
}
|
||||
var ubusData map[string]UbusService
|
||||
if json.Unmarshal(out, &ubusData) == nil {
|
||||
if sData, ok := ubusData[uName]; ok {
|
||||
isLoadedInProcd = true
|
||||
for _, inst := range sData.Instances {
|
||||
if inst.Running {
|
||||
isRunningRealTime = true
|
||||
if inst.Pid > 0 {
|
||||
details["MainPID"] = uint64(inst.Pid)
|
||||
|
||||
// Accurate real-time reading of memory (RSS)
|
||||
if data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", inst.Pid)); err == nil {
|
||||
binEnd := strings.LastIndex(string(data), ")")
|
||||
if binEnd != -1 && len(string(data)) > binEnd+2 {
|
||||
fields := strings.Fields(string(data)[binEnd+2:])
|
||||
if len(fields) > 21 {
|
||||
pageSize := uint64(os.Getpagesize())
|
||||
if pageSize == 0 {
|
||||
pageSize = 4096
|
||||
}
|
||||
rss, _ := strconv.ParseUint(fields[21], 10, 64)
|
||||
currentMem = rss * pageSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Accurately read the number of threads in real time (Tasks)
|
||||
if data, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", inst.Pid)); err == nil {
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "Threads:") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 {
|
||||
tasks, _ := strconv.ParseUint(fields[1], 10, 64)
|
||||
details["TasksCurrent"] = tasks
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Core indicator status calibration and memory peak calculation
|
||||
if isRunningRealTime {
|
||||
details["ActiveState"] = "active"
|
||||
details["SubState"] = "running"
|
||||
details["MemoryCurrent"] = currentMem
|
||||
|
||||
if exists && service.MemPeak > currentMem {
|
||||
details["MemoryPeak"] = service.MemPeak
|
||||
} else {
|
||||
details["MemoryPeak"] = currentMem
|
||||
}
|
||||
} else if isLoadedInProcd {
|
||||
details["ActiveState"] = "active"
|
||||
details["SubState"] = "exited"
|
||||
details["MemoryCurrent"] = uint64(0)
|
||||
if exists { details["MemoryPeak"] = service.MemPeak }
|
||||
} else {
|
||||
details["ActiveState"] = "inactive"
|
||||
details["SubState"] = "dead"
|
||||
details["MemoryCurrent"] = uint64(0)
|
||||
if exists { details["MemoryPeak"] = service.MemPeak }
|
||||
}
|
||||
|
||||
// 4. Complete Systemd-compatible metadata
|
||||
details["Id"] = serviceName
|
||||
details["Description"] = uName + " (OpenWrt Procd)"
|
||||
details["LoadState"] = "loaded"
|
||||
details["FragmentPath"] = initScript
|
||||
details["NRestarts"] = uint64(0)
|
||||
details["StatusText"] = ""
|
||||
|
||||
// Check the self-starting status of the service
|
||||
unitFileState := "disabled"
|
||||
if info, err := os.Stat(initScript); err == nil && !info.Mode().IsDir() {
|
||||
if err := exec.Command(initScript, "enabled").Run(); err == nil {
|
||||
unitFileState = "enabled"
|
||||
}
|
||||
}
|
||||
details["UnitFileState"] = unitFileState
|
||||
|
||||
// Dynamic detection service control capability (Capabilities)
|
||||
canStart, canStop, canReload := false, false, false
|
||||
if info, err := os.Stat(initScript); err == nil && !info.Mode().IsDir() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
out, _ := exec.CommandContext(ctx, initScript).CombinedOutput()
|
||||
outputStr := strings.ToLower(string(out))
|
||||
if strings.Contains(outputStr, "start") { canStart = true }
|
||||
if strings.Contains(outputStr, "stop") { canStop = true }
|
||||
if strings.Contains(outputStr, "reload") { canReload = true }
|
||||
}
|
||||
details["CanStart"] = canStart
|
||||
details["CanStop"] = canStop
|
||||
details["CanReload"] = canReload
|
||||
|
||||
return details, nil
|
||||
}
|
||||
|
||||
func unescapeServiceName(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func getServicePatterns() []string {
|
||||
patterns := []string{}
|
||||
if envPatterns, _ := utils.GetEnv("SERVICE_PATTERNS"); envPatterns != "" {
|
||||
for pattern := range strings.SplitSeq(envPatterns, ",") {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(pattern, "timer") && !strings.HasSuffix(pattern, ".service") {
|
||||
pattern += ".service"
|
||||
}
|
||||
patterns = append(patterns, pattern)
|
||||
}
|
||||
}
|
||||
if len(patterns) == 0 {
|
||||
patterns = []string{"*.service"}
|
||||
}
|
||||
return patterns
|
||||
}
|
||||
|
||||
func (sm *systemdManager) getServiceLogs(serviceName string) ([]byte, error) {
|
||||
/*
|
||||
未来进阶提示:如果你想在前端也看到 OpenWrt 的服务日志,
|
||||
可以将 serviceName 去掉后缀,然后通过执行类似以下的命令返回真实的日志字节流:
|
||||
out, err := exec.Command("logread", "-e", strings.TrimSuffix(serviceName, ".service")).Output()
|
||||
return out, err
|
||||
*/
|
||||
return nil, nil
|
||||
}
|
||||
Reference in new issue
Block a user