...
1 package linux
2
3 import (
4 "bufio"
5 "context"
6 "fmt"
7 "golang.conradwood.net/go-easyops/prometheus"
8 "golang.conradwood.net/apis/common"
9 "os"
10 "runtime"
11 "strconv"
12 "strings"
13 "sync"
14 "time"
15 )
16
17 const (
18 READ_MILLIS = 5000
19 )
20
21 var (
22 loadGauge = prometheus.NewGaugeVec(
23 prometheus.GaugeOpts{
24 Name: "goeasyops_cpu_percent",
25 Help: "ticks spent per type in a cpu",
26 },
27 []string{"type"},
28 )
29 ctr = 0
30 proclock sync.Mutex
31 procstat = make([]uint64, 10)
32 procstat_diff = make([]uint64, 10)
33 total uint64
34 )
35
36 func init() {
37 prometheus.MustRegister(loadGauge)
38 go load_calc_loop()
39 }
40
41 type Loadavg struct {
42 }
43
44 func (g *Loadavg) GetCPULoad(ctx context.Context, req *common.Void) (*common.CPULoad, error) {
45 if ctr < 3 {
46 return nil, fmt.Errorf("not ready yet - initialising. try later")
47 }
48 res := &common.CPULoad{}
49 file, err := os.Open("/proc/loadavg")
50 if err != nil {
51 return nil, err
52 }
53 defer file.Close()
54
55 var line string
56 scanner := bufio.NewScanner(file)
57 for scanner.Scan() {
58 line = scanner.Text()
59 break
60 }
61
62 if err := scanner.Err(); err != nil {
63 return nil, err
64 }
65 avgs := strings.Split(line, " ")
66 if len(avgs) < 3 {
67 return nil, fmt.Errorf("Invalid line read from loadavg: \"%s\" (splits into %d parts)", line, len(avgs))
68 }
69 res.Avg1, err = strconv.ParseFloat(avgs[0], 32)
70 if err != nil {
71 return nil, err
72 }
73
74 res.Avg5, err = strconv.ParseFloat(avgs[1], 32)
75 if err != nil {
76 return nil, err
77 }
78
79 res.Avg15, err = strconv.ParseFloat(avgs[2], 32)
80 if err != nil {
81 return nil, err
82 }
83
84
85 res.CPUCount = uint32(runtime.NumCPU())
86 res.PerCPU = res.Avg1 / float64(res.CPUCount)
87 proclock.Lock()
88 res.Sum = 0
89 for _, r := range procstat_diff {
90 res.Sum = res.Sum + r
91 }
92 res.RawSum = total
93 res.User = procstat_diff[0]
94 res.Nice = procstat_diff[1]
95 res.System = procstat_diff[2]
96 res.Idle = procstat_diff[3]
97 res.IOWait = procstat_diff[4]
98 res.IRQ = procstat_diff[5]
99 res.SoftIRQ = procstat_diff[6]
100 proclock.Unlock()
101 res.IdleTime = float64(res.Idle) / float64(res.Sum) * 100
102 return res, nil
103 }
104
105 func load_calc_loop() {
106 def := time.Duration(READ_MILLIS) * time.Millisecond
107 for {
108 if ctr < 3 {
109 time.Sleep(time.Duration(100) * time.Millisecond)
110 ctr++
111 } else {
112 time.Sleep(def)
113 }
114 err := load_calc()
115 if err != nil {
116 fmt.Printf("Failed to process /proc/stat: %s\n", err)
117 continue
118 }
119 }
120 }
121
122
123 func load_calc() error {
124
125
126 file, err := os.Open("/proc/stat")
127 if err != nil {
128 return err
129 }
130 defer file.Close()
131
132 scanner := bufio.NewScanner(file)
133 var line string
134 for scanner.Scan() {
135 line = scanner.Text()
136 break
137 }
138
139 if err := scanner.Err(); err != nil {
140 return err
141 }
142 ns := strings.Fields(line)
143 if len(ns) < 8 {
144 return fmt.Errorf("invalid /proc/stat: %s (%d nums)\n", line, len(ns))
145 }
146 var nums []uint64
147 for i, n := range ns {
148 if i == 0 {
149 continue
150 }
151 num, err := strconv.ParseUint(n, 10, 64)
152 if err != nil {
153 return err
154 }
155 nums = append(nums, num)
156 }
157 proclock.Lock()
158 total = 0
159 for i, n := range nums {
160 total = total + n
161 procstat_diff[i] = n - procstat[i]
162 procstat[i] = n
163 }
164 proclock.Unlock()
165 dt := uint64(0)
166 for _, r := range procstat_diff {
167 dt = dt + r
168 }
169 gauge("idle", float64(procstat_diff[3])/float64(dt)*100)
170 gauge("busy", 100.0-(float64(procstat_diff[3])/float64(dt)*100))
171 return nil
172 }
173
174 func gauge(name string, perc float64) {
175 loadGauge.With(prometheus.Labels{"type": name}).Set(perc)
176 }
177
View as plain text