go-duck-cli 1.4.8 → 1.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,198 @@
1
+
2
+ package telemetry
3
+
4
+ import (
5
+ "runtime"
6
+ "time"
7
+ "github.com/shirou/gopsutil/v3/cpu"
8
+ "github.com/shirou/gopsutil/v3/process"
9
+ "github.com/shirou/gopsutil/v3/mem"
10
+ "os"
11
+ "fmt"
12
+ "strconv"
13
+ "strings"
14
+ "sync"
15
+ )
16
+
17
+ var (
18
+ cgroupCPULock sync.Mutex
19
+ lastCgroupTime time.Time
20
+ lastCgroupUsageNs uint64
21
+ )
22
+
23
+ type SystemMetrics struct {
24
+ Uptime string `json:"uptime"`
25
+ StartTime string `json:"start_time"`
26
+ ProcessCPUUsage float64 `json:"process_cpu_usage"`
27
+ SystemCPUUsage float64 `json:"system_cpu_usage"`
28
+ PodCPULimitPct float64 `json:"pod_cpu_limit_pct"`
29
+ IsPodCPULimited bool `json:"is_pod_cpu_limited"`
30
+ CPUThreadsUsage []float64 `json:"cpu_threads_usage"`
31
+ SystemCPUCount int `json:"system_cpu_count"`
32
+ ProcessFilesOpen int32 `json:"process_files_open"`
33
+
34
+ TotalMemMB uint64 `json:"total_mem_mb"`
35
+ HeapAllocMB uint64 `json:"heap_alloc_mb"`
36
+ HeapSysMB uint64 `json:"heap_sys_mb"`
37
+ NumGC uint32 `json:"num_gc"`
38
+ PauseTotalMs uint64 `json:"gc_pause_total_ms"`
39
+ Goroutines int `json:"goroutines"`
40
+ }
41
+
42
+ func getContainerMemoryLimit() uint64 {
43
+ // Check cgroup v2
44
+ if data, err := os.ReadFile("/sys/fs/cgroup/memory.max"); err == nil {
45
+ val := strings.TrimSpace(string(data))
46
+ if val != "max" && val != "" {
47
+ if limit, err := strconv.ParseUint(val, 10, 64); err == nil {
48
+ return limit
49
+ }
50
+ }
51
+ }
52
+ // Check cgroup v1
53
+ if data, err := os.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes"); err == nil {
54
+ val := strings.TrimSpace(string(data))
55
+ if limit, err := strconv.ParseUint(val, 10, 64); err == nil {
56
+ // Some systems report an absurdly high number (e.g. 9223372036854771712) for "no limit"
57
+ if limit < 9000000000000000000 {
58
+ return limit
59
+ }
60
+ }
61
+ }
62
+ return 0
63
+ }
64
+
65
+ func getCgroupCPUUsageNs() (uint64, error) {
66
+ // try v2
67
+ if data, err := os.ReadFile("/sys/fs/cgroup/cpu.stat"); err == nil {
68
+ lines := strings.Split(string(data), "
69
+ ")
70
+ for _, line := range lines {
71
+ if strings.HasPrefix(line, "usage_usec") {
72
+ parts := strings.Fields(line)
73
+ if len(parts) == 2 {
74
+ if usec, err := strconv.ParseUint(parts[1], 10, 64); err == nil {
75
+ return usec * 1000, nil // to nanoseconds
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ // try v1
82
+ if data, err := os.ReadFile("/sys/fs/cgroup/cpuacct/cpuacct.usage"); err == nil {
83
+ val := strings.TrimSpace(string(data))
84
+ if ns, err := strconv.ParseUint(val, 10, 64); err == nil {
85
+ return ns, nil
86
+ }
87
+ }
88
+ return 0, fmt.Errorf("not found")
89
+ }
90
+
91
+ func getCgroupCPULimitCores() float64 {
92
+ // check v2
93
+ if data, err := os.ReadFile("/sys/fs/cgroup/cpu.max"); err == nil {
94
+ parts := strings.Fields(strings.TrimSpace(string(data)))
95
+ if len(parts) == 2 && parts[0] != "max" {
96
+ max, err1 := strconv.ParseFloat(parts[0], 64)
97
+ period, err2 := strconv.ParseFloat(parts[1], 64)
98
+ if err1 == nil && err2 == nil && period > 0 {
99
+ return max / period
100
+ }
101
+ }
102
+ }
103
+ // check v1
104
+ if quotaData, err := os.ReadFile("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"); err == nil {
105
+ quotaStr := strings.TrimSpace(string(quotaData))
106
+ if quotaStr != "-1" {
107
+ if quota, err := strconv.ParseFloat(quotaStr, 64); err == nil {
108
+ if periodData, err := os.ReadFile("/sys/fs/cgroup/cpu/cpu.cfs_period_us"); err == nil {
109
+ if period, err := strconv.ParseFloat(strings.TrimSpace(string(periodData)), 64); err == nil && period > 0 {
110
+ return quota / period
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+ return 0
117
+ }
118
+
119
+ func CollectSystemMetrics() SystemMetrics {
120
+ var m runtime.MemStats
121
+ runtime.ReadMemStats(&m)
122
+
123
+ cpuPercent, _ := cpu.Percent(0, false)
124
+ threadsPercent, _ := cpu.Percent(0, true)
125
+ sysCPU := 0.0
126
+ if len(cpuPercent) > 0 {
127
+ sysCPU = cpuPercent[0]
128
+ }
129
+
130
+ p, err := process.NewProcess(int32(os.Getpid()))
131
+ procCPU := 0.0
132
+ var openFiles int32 = 0
133
+ if err == nil {
134
+ procCPU, _ = p.CPUPercent()
135
+ files, _ := p.OpenFiles()
136
+ openFiles = int32(len(files))
137
+ }
138
+
139
+ vMem, _ := mem.VirtualMemory()
140
+ totalMemMB := uint64(0)
141
+
142
+ // 1. Try Kubernetes / Docker Cgroup Limits first
143
+ cgroupLimitBytes := getContainerMemoryLimit()
144
+ if cgroupLimitBytes > 0 {
145
+ totalMemMB = cgroupLimitBytes / 1024 / 1024
146
+ } else if vMem != nil {
147
+ // 2. Fallback to underlying physical host memory
148
+ totalMemMB = vMem.Total / 1024 / 1024
149
+ }
150
+
151
+ appMetrics := GetGlobalMetrics()
152
+ uptime := time.Since(appMetrics.StartTime)
153
+
154
+ uptimeStr := fmt.Sprintf("%d days, %d hours, %d minutes",
155
+ int(uptime.Hours())/24, int(uptime.Hours())%24, int(uptime.Minutes())%60)
156
+
157
+ podCPULimitPct := 0.0
158
+ isPodCPULimited := false
159
+
160
+ limitCores := getCgroupCPULimitCores()
161
+ if limitCores > 0 {
162
+ isPodCPULimited = true
163
+ cgroupCPULock.Lock()
164
+ usageNs, err := getCgroupCPUUsageNs()
165
+ now := time.Now()
166
+ if err == nil {
167
+ if !lastCgroupTime.IsZero() && usageNs > lastCgroupUsageNs {
168
+ timeDelta := float64(now.Sub(lastCgroupTime).Nanoseconds())
169
+ usageDelta := float64(usageNs - lastCgroupUsageNs)
170
+ if timeDelta > 0 {
171
+ coresUsed := usageDelta / timeDelta
172
+ podCPULimitPct = (coresUsed / limitCores) * 100.0
173
+ }
174
+ }
175
+ lastCgroupUsageNs = usageNs
176
+ lastCgroupTime = now
177
+ }
178
+ cgroupCPULock.Unlock()
179
+ }
180
+
181
+ return SystemMetrics{
182
+ Uptime: uptimeStr,
183
+ StartTime: appMetrics.StartTime.Format(time.RFC1123),
184
+ ProcessCPUUsage: procCPU,
185
+ SystemCPUUsage: sysCPU,
186
+ PodCPULimitPct: podCPULimitPct,
187
+ IsPodCPULimited: isPodCPULimited,
188
+ CPUThreadsUsage: threadsPercent,
189
+ SystemCPUCount: runtime.NumCPU(),
190
+ ProcessFilesOpen: openFiles,
191
+ TotalMemMB: totalMemMB,
192
+ HeapAllocMB: m.HeapAlloc / 1024 / 1024,
193
+ HeapSysMB: m.HeapSys / 1024 / 1024,
194
+ NumGC: m.NumGC,
195
+ PauseTotalMs: m.PauseTotalNs / 1000000,
196
+ Goroutines: runtime.NumGoroutine(),
197
+ }
198
+ }
@@ -0,0 +1,19 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "gorm.io/datatypes"
7
+ )
8
+
9
+ type TestStruct struct {
10
+ D datatypes.Date `json:"d"`
11
+ T datatypes.Time `json:"t"`
12
+ }
13
+
14
+ func main() {
15
+ j := `{"d": "2024-01-01", "t": "12:00:00"}`
16
+ var ts TestStruct
17
+ fmt.Println("Err:", json.Unmarshal([]byte(j), &ts))
18
+ fmt.Println("Result:", ts)
19
+ }
package/test_time.go ADDED
@@ -0,0 +1,22 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "time"
7
+ )
8
+
9
+ type TestStruct struct {
10
+ T time.Time `json:"t"`
11
+ }
12
+
13
+ func main() {
14
+ j1 := `{"t": "2024-01-01"}`
15
+ j2 := `{"t": "12:00:00"}`
16
+ j3 := `{"t": "2024-01-01T12:00:00Z"}`
17
+
18
+ var t1, t2, t3 TestStruct
19
+ fmt.Println("Date:", json.Unmarshal([]byte(j1), &t1))
20
+ fmt.Println("Time:", json.Unmarshal([]byte(j2), &t2))
21
+ fmt.Println("Instant:", json.Unmarshal([]byte(j3), &t3))
22
+ }