blob: 61eb6b0e3ceaa6bdfa5be778be8a57c4ab13866c [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001// Copyright 2018 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package procfs
15
16import (
17 "bufio"
18 "fmt"
19 "io"
20 "os"
21 "strconv"
22 "strings"
23)
24
25// CPUStat shows how much time the cpu spend in various stages.
26type CPUStat struct {
27 User float64
28 Nice float64
29 System float64
30 Idle float64
31 Iowait float64
32 IRQ float64
33 SoftIRQ float64
34 Steal float64
35 Guest float64
36 GuestNice float64
37}
38
39// SoftIRQStat represent the softirq statistics as exported in the procfs stat file.
40// A nice introduction can be found at https://0xax.gitbooks.io/linux-insides/content/interrupts/interrupts-9.html
41// It is possible to get per-cpu stats by reading /proc/softirqs
42type SoftIRQStat struct {
43 Hi uint64
44 Timer uint64
45 NetTx uint64
46 NetRx uint64
47 Block uint64
48 BlockIoPoll uint64
49 Tasklet uint64
50 Sched uint64
51 Hrtimer uint64
52 Rcu uint64
53}
54
55// Stat represents kernel/system statistics.
56type Stat struct {
57 // Boot time in seconds since the Epoch.
58 BootTime uint64
59 // Summed up cpu statistics.
60 CPUTotal CPUStat
61 // Per-CPU statistics.
62 CPU []CPUStat
63 // Number of times interrupts were handled, which contains numbered and unnumbered IRQs.
64 IRQTotal uint64
65 // Number of times a numbered IRQ was triggered.
66 IRQ []uint64
67 // Number of times a context switch happened.
68 ContextSwitches uint64
69 // Number of times a process was created.
70 ProcessCreated uint64
71 // Number of processes currently running.
72 ProcessesRunning uint64
73 // Number of processes currently blocked (waiting for IO).
74 ProcessesBlocked uint64
75 // Number of times a softirq was scheduled.
76 SoftIRQTotal uint64
77 // Detailed softirq statistics.
78 SoftIRQ SoftIRQStat
79}
80
81// NewStat returns kernel/system statistics read from /proc/stat.
82func NewStat() (Stat, error) {
83 fs, err := NewFS(DefaultMountPoint)
84 if err != nil {
85 return Stat{}, err
86 }
87
88 return fs.NewStat()
89}
90
91// Parse a cpu statistics line and returns the CPUStat struct plus the cpu id (or -1 for the overall sum).
92func parseCPUStat(line string) (CPUStat, int64, error) {
93 cpuStat := CPUStat{}
94 var cpu string
95
96 count, err := fmt.Sscanf(line, "%s %f %f %f %f %f %f %f %f %f %f",
97 &cpu,
98 &cpuStat.User, &cpuStat.Nice, &cpuStat.System, &cpuStat.Idle,
99 &cpuStat.Iowait, &cpuStat.IRQ, &cpuStat.SoftIRQ, &cpuStat.Steal,
100 &cpuStat.Guest, &cpuStat.GuestNice)
101
102 if err != nil && err != io.EOF {
103 return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu): %s", line, err)
104 }
105 if count == 0 {
106 return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu): 0 elements parsed", line)
107 }
108
109 cpuStat.User /= userHZ
110 cpuStat.Nice /= userHZ
111 cpuStat.System /= userHZ
112 cpuStat.Idle /= userHZ
113 cpuStat.Iowait /= userHZ
114 cpuStat.IRQ /= userHZ
115 cpuStat.SoftIRQ /= userHZ
116 cpuStat.Steal /= userHZ
117 cpuStat.Guest /= userHZ
118 cpuStat.GuestNice /= userHZ
119
120 if cpu == "cpu" {
121 return cpuStat, -1, nil
122 }
123
124 cpuID, err := strconv.ParseInt(cpu[3:], 10, 64)
125 if err != nil {
126 return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu/cpuid): %s", line, err)
127 }
128
129 return cpuStat, cpuID, nil
130}
131
132// Parse a softirq line.
133func parseSoftIRQStat(line string) (SoftIRQStat, uint64, error) {
134 softIRQStat := SoftIRQStat{}
135 var total uint64
136 var prefix string
137
138 _, err := fmt.Sscanf(line, "%s %d %d %d %d %d %d %d %d %d %d %d",
139 &prefix, &total,
140 &softIRQStat.Hi, &softIRQStat.Timer, &softIRQStat.NetTx, &softIRQStat.NetRx,
141 &softIRQStat.Block, &softIRQStat.BlockIoPoll,
142 &softIRQStat.Tasklet, &softIRQStat.Sched,
143 &softIRQStat.Hrtimer, &softIRQStat.Rcu)
144
145 if err != nil {
146 return SoftIRQStat{}, 0, fmt.Errorf("couldn't parse %s (softirq): %s", line, err)
147 }
148
149 return softIRQStat, total, nil
150}
151
152// NewStat returns an information about current kernel/system statistics.
153func (fs FS) NewStat() (Stat, error) {
154 // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
155
156 f, err := os.Open(fs.Path("stat"))
157 if err != nil {
158 return Stat{}, err
159 }
160 defer f.Close()
161
162 stat := Stat{}
163
164 scanner := bufio.NewScanner(f)
165 for scanner.Scan() {
166 line := scanner.Text()
167 parts := strings.Fields(scanner.Text())
168 // require at least <key> <value>
169 if len(parts) < 2 {
170 continue
171 }
172 switch {
173 case parts[0] == "btime":
174 if stat.BootTime, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
175 return Stat{}, fmt.Errorf("couldn't parse %s (btime): %s", parts[1], err)
176 }
177 case parts[0] == "intr":
178 if stat.IRQTotal, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
179 return Stat{}, fmt.Errorf("couldn't parse %s (intr): %s", parts[1], err)
180 }
181 numberedIRQs := parts[2:]
182 stat.IRQ = make([]uint64, len(numberedIRQs))
183 for i, count := range numberedIRQs {
184 if stat.IRQ[i], err = strconv.ParseUint(count, 10, 64); err != nil {
185 return Stat{}, fmt.Errorf("couldn't parse %s (intr%d): %s", count, i, err)
186 }
187 }
188 case parts[0] == "ctxt":
189 if stat.ContextSwitches, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
190 return Stat{}, fmt.Errorf("couldn't parse %s (ctxt): %s", parts[1], err)
191 }
192 case parts[0] == "processes":
193 if stat.ProcessCreated, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
194 return Stat{}, fmt.Errorf("couldn't parse %s (processes): %s", parts[1], err)
195 }
196 case parts[0] == "procs_running":
197 if stat.ProcessesRunning, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
198 return Stat{}, fmt.Errorf("couldn't parse %s (procs_running): %s", parts[1], err)
199 }
200 case parts[0] == "procs_blocked":
201 if stat.ProcessesBlocked, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
202 return Stat{}, fmt.Errorf("couldn't parse %s (procs_blocked): %s", parts[1], err)
203 }
204 case parts[0] == "softirq":
205 softIRQStats, total, err := parseSoftIRQStat(line)
206 if err != nil {
207 return Stat{}, err
208 }
209 stat.SoftIRQTotal = total
210 stat.SoftIRQ = softIRQStats
211 case strings.HasPrefix(parts[0], "cpu"):
212 cpuStat, cpuID, err := parseCPUStat(line)
213 if err != nil {
214 return Stat{}, err
215 }
216 if cpuID == -1 {
217 stat.CPUTotal = cpuStat
218 } else {
219 for int64(len(stat.CPU)) <= cpuID {
220 stat.CPU = append(stat.CPU, CPUStat{})
221 }
222 stat.CPU[cpuID] = cpuStat
223 }
224 }
225 }
226
227 if err := scanner.Err(); err != nil {
228 return Stat{}, fmt.Errorf("couldn't parse %s: %s", f.Name(), err)
229 }
230
231 return stat, nil
232}