-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprocess.go
More file actions
135 lines (120 loc) · 2.92 KB
/
Copy pathprocess.go
File metadata and controls
135 lines (120 loc) · 2.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package main
import (
"fmt"
"os/exec"
"strconv"
"strings"
"time"
)
type ProcessInfo struct {
Name string
Status string
PID int
CPU float64
MemoryKB int64
MemoryStr string
Uptime time.Duration
UptimeStr string
Restarts int
Cmd string
Cwd string
Env map[string]string
HasProcess bool
}
func getProcessInfo(unit string) ProcessInfo {
name := unitToAppName(unit)
info := ProcessInfo{
Name: name,
}
// Get status
state := getUnitProperty(unit, "ActiveState")
info.Status = state
// Get PID
pidStr := getUnitProperty(unit, "MainPID")
pid, _ := strconv.Atoi(pidStr)
info.PID = pid
// Get restart count
restartStr := getUnitProperty(unit, "NRestarts")
info.Restarts, _ = strconv.Atoi(restartStr)
// Get uptime from ActiveEnterTimestamp
if state == "active" {
ts := getUnitProperty(unit, "ActiveEnterTimestamp")
if ts != "" && ts != "n/a" {
if t, err := parseSystemdTimestamp(ts); err == nil {
info.Uptime = time.Since(t)
info.UptimeStr = formatDuration(info.Uptime)
}
}
}
// Get CPU and memory from ps if PID is valid
if pid > 0 {
info.HasProcess = true
cpu, mem := getProcessStats(pid)
info.CPU = cpu
info.MemoryKB = mem
info.MemoryStr = formatMemory(mem)
}
// Get command and cwd from service file
info.Cmd, info.Cwd, info.Env = parseServiceFileFields(unitFilePath(name))
return info
}
func getProcessStats(pid int) (cpu float64, memKB int64) {
out, err := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "%cpu=,rss=", "--no-headers").Output()
if err != nil {
return 0, 0
}
fields := strings.Fields(strings.TrimSpace(string(out)))
if len(fields) >= 2 {
cpu, _ = strconv.ParseFloat(fields[0], 64)
mem, _ := strconv.ParseInt(fields[1], 10, 64)
memKB = mem
}
return
}
func parseSystemdTimestamp(ts string) (time.Time, error) {
// systemd timestamps look like: "Mon 2024-01-15 10:30:00 UTC"
// or "Mon 2024-01-15 10:30:00 CET"
// Try multiple formats
formats := []string{
"Mon 2006-01-02 15:04:05 MST",
"Mon 2006-01-02 15:04:05 -0700",
"2006-01-02 15:04:05 MST",
"2006-01-02 15:04:05 -0700",
}
for _, f := range formats {
if t, err := time.Parse(f, ts); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("cannot parse timestamp: %s", ts)
}
func formatDuration(d time.Duration) string {
if d < time.Minute {
return fmt.Sprintf("%ds", int(d.Seconds()))
}
if d < time.Hour {
return fmt.Sprintf("%dm", int(d.Minutes()))
}
if d < 24*time.Hour {
h := int(d.Hours())
m := int(d.Minutes()) % 60
return fmt.Sprintf("%dh %dm", h, m)
}
days := int(d.Hours()) / 24
hours := int(d.Hours()) % 24
return fmt.Sprintf("%dd %dh", days, hours)
}
func formatMemory(kb int64) string {
if kb == 0 {
return "-"
}
if kb < 1024 {
return fmt.Sprintf("%d KB", kb)
}
mb := float64(kb) / 1024.0
if mb < 1024 {
return fmt.Sprintf("%.1f MB", mb)
}
gb := mb / 1024.0
return fmt.Sprintf("%.1f GB", gb)
}