-
Notifications
You must be signed in to change notification settings - Fork 28
/
sigar_stats.go
188 lines (153 loc) · 4.99 KB
/
sigar_stats.go
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright 2022-Present Couchbase, Inc.
//
// Use of this software is governed by the Business Source License included
// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified
// in that file, in accordance with the Business Source License, use of this
// software will be governed by the Apache License, Version 2.0, included in
// the file licenses/APL2.txt.
//go:build server
// +build server
package cbft
//#cgo LDFLAGS: -lsigar
//#include <sigar.h>
//#include <sigar_control_group.h>
import "C"
import (
"fmt"
"os"
"runtime"
"strconv"
"time"
)
var stats *systemStats
// InitSystemStats should be called at process inception to
// initialize stats^.
func InitSystemStats() error {
var err error
stats, err = newSystemStats()
if err != nil {
return fmt.Errorf("InitSystemStats, err: %v", err)
}
return nil
}
// -----------------------------------------------------------------------------
func GetNumCPUs() string {
cgroupInfo := stats.getControlGroupInfo()
if cgroupInfo.Supported == sigarCgroupSupported {
return strconv.FormatFloat(float64(cgroupInfo.NumCpuPrc)/100.0, 'f', 3, 64)
}
gomaxprocs := os.Getenv("GOMAXPROCS")
if gomaxprocs == "" {
return strconv.Itoa(runtime.NumCPU())
}
return gomaxprocs
}
// GetMemoryLimit returns total memory based on cgroup limits, if possible.
func GetMemoryLimit() (uint64, error) {
memTotal, err := stats.systemTotalMem()
if err != nil {
return 0, fmt.Errorf("GetMemoryLimit: failed to get total mem, err: %v", err)
}
cgroupInfo := stats.getControlGroupInfo()
if cgroupInfo.Supported == sigarCgroupSupported {
cGroupTotal := cgroupInfo.MemoryMax
// cGroupTotal is with-in valid system limits
if cGroupTotal > 0 && cGroupTotal <= memTotal {
return cGroupTotal, nil
}
}
return memTotal, nil
}
// currentCPUPercent returns current CPU percent used by process.
func currentCPUPercent() (float64, error) {
cpu, err := stats.processCPUPercent()
if err != nil {
return 0,
fmt.Errorf("CurrentCPUPercent: failed to get current cpu, err: %v", err)
}
return cpu, nil
}
// -----------------------------------------------------------------------------
var (
sigarCgroupSupported uint8 = 1
)
type systemStats struct {
handle *C.sigar_t
pid C.sigar_pid_t
lastCPU float64
lastCPUTime time.Time
}
// newSystemStats returns a new systemStats after populating handler and PID.
func newSystemStats() (*systemStats, error) {
var handle *C.sigar_t
if err := C.sigar_open(&handle); err != C.SIGAR_OK {
return nil, fmt.Errorf("failed to open sigar, err: %v", err)
}
s := &systemStats{}
s.handle = handle
s.pid = C.sigar_pid_get(handle)
var cpu C.sigar_proc_cpu_t
if err := C.sigar_proc_cpu_get(s.handle, s.pid, &cpu); err != C.SIGAR_OK {
return nil, fmt.Errorf("failed to get CPU, err: %w",
C.sigar_strerror(s.handle, err))
}
s.lastCPU = float64(cpu.user + cpu.sys)
s.lastCPUTime = time.Now()
return s, nil
}
// close systemStats handler.
func (s *systemStats) close() {
C.sigar_close(s.handle)
}
// systemTotalMem returns the hosts-level memory limit in bytes.
func (s *systemStats) systemTotalMem() (uint64, error) {
var mem C.sigar_mem_t
if err := C.sigar_mem_get(s.handle, &mem); err != C.SIGAR_OK {
return uint64(0), fmt.Errorf("failed to get total memory, err: %v",
C.sigar_strerror(s.handle, err))
}
return uint64(mem.total), nil
}
// processCPUPercent gets the percent CPU and is in range
// of [0, GOMAXPROCS] * 100.
// So a value of 123.4 means it is consuming 1.234 CPU cores.
func (s *systemStats) processCPUPercent() (float64, error) {
var cpu C.sigar_proc_cpu_t
if err := C.sigar_proc_cpu_get(s.handle, s.pid, &cpu); err != C.SIGAR_OK {
return float64(0), fmt.Errorf("failed to get CPU, err: %w",
C.sigar_strerror(s.handle, err))
}
totalCPU := float64(cpu.user + cpu.sys)
currentTime := time.Now()
timeDiffInMilliseconds := float64(currentTime.Sub(s.lastCPUTime).Milliseconds())
if timeDiffInMilliseconds <= 0 {
// Avoid divide by zero.
timeDiffInMilliseconds = 1
}
cpuPercent := (totalCPU - s.lastCPU) / timeDiffInMilliseconds
s.lastCPU = totalCPU
s.lastCPUTime = currentTime
return cpuPercent * 100, nil
}
// sigarControlGroupInfo represents the subset of the cgroup info statistics
// relevant to FTS.
type sigarControlGroupInfo struct {
Supported uint8 // "1" if cgroup info is supprted, "0" otherwise
Version uint8 // "1" for cgroup v1, "2" for cgroup v2
// Maximum memory available in the group. Derived from memory.max
MemoryMax uint64
NumCpuPrc uint16
}
// getControlGroupInfo returns the fields of C.sigar_control_group_info_t FTS uses.
// These reflect Linux control group settings, which are used by Kubernetes to set
// pod, memory and CPU limits.
func (h *systemStats) getControlGroupInfo() *sigarControlGroupInfo {
var info C.sigar_control_group_info_t
C.sigar_get_control_group_info(&info)
return &sigarControlGroupInfo{
Supported: uint8(info.supported),
Version: uint8(info.version),
MemoryMax: uint64(info.memory_max),
NumCpuPrc: uint16(info.num_cpu_prc),
}
}