1// 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 "bytes" 19 "fmt" 20 "io" 21 "strconv" 22 "strings" 23 24 "github.com/prometheus/procfs/internal/fs" 25 "github.com/prometheus/procfs/internal/util" 26) 27 28// CPUStat shows how much time the cpu spend in various stages. 29type CPUStat struct { 30 User float64 31 Nice float64 32 System float64 33 Idle float64 34 Iowait float64 35 IRQ float64 36 SoftIRQ float64 37 Steal float64 38 Guest float64 39 GuestNice float64 40} 41 42// SoftIRQStat represent the softirq statistics as exported in the procfs stat file. 43// A nice introduction can be found at https://0xax.gitbooks.io/linux-insides/content/interrupts/interrupts-9.html 44// It is possible to get per-cpu stats by reading /proc/softirqs 45type SoftIRQStat struct { 46 Hi uint64 47 Timer uint64 48 NetTx uint64 49 NetRx uint64 50 Block uint64 51 BlockIoPoll uint64 52 Tasklet uint64 53 Sched uint64 54 Hrtimer uint64 55 Rcu uint64 56} 57 58// Stat represents kernel/system statistics. 59type Stat struct { 60 // Boot time in seconds since the Epoch. 61 BootTime uint64 62 // Summed up cpu statistics. 63 CPUTotal CPUStat 64 // Per-CPU statistics. 65 CPU []CPUStat 66 // Number of times interrupts were handled, which contains numbered and unnumbered IRQs. 67 IRQTotal uint64 68 // Number of times a numbered IRQ was triggered. 69 IRQ []uint64 70 // Number of times a context switch happened. 71 ContextSwitches uint64 72 // Number of times a process was created. 73 ProcessCreated uint64 74 // Number of processes currently running. 75 ProcessesRunning uint64 76 // Number of processes currently blocked (waiting for IO). 77 ProcessesBlocked uint64 78 // Number of times a softirq was scheduled. 79 SoftIRQTotal uint64 80 // Detailed softirq statistics. 81 SoftIRQ SoftIRQStat 82} 83 84// Parse a cpu statistics line and returns the CPUStat struct plus the cpu id (or -1 for the overall sum). 85func parseCPUStat(line string) (CPUStat, int64, error) { 86 cpuStat := CPUStat{} 87 var cpu string 88 89 count, err := fmt.Sscanf(line, "%s %f %f %f %f %f %f %f %f %f %f", 90 &cpu, 91 &cpuStat.User, &cpuStat.Nice, &cpuStat.System, &cpuStat.Idle, 92 &cpuStat.Iowait, &cpuStat.IRQ, &cpuStat.SoftIRQ, &cpuStat.Steal, 93 &cpuStat.Guest, &cpuStat.GuestNice) 94 95 if err != nil && err != io.EOF { 96 return CPUStat{}, -1, fmt.Errorf("couldn't parse %q (cpu): %w", line, err) 97 } 98 if count == 0 { 99 return CPUStat{}, -1, fmt.Errorf("couldn't parse %q (cpu): 0 elements parsed", line) 100 } 101 102 cpuStat.User /= userHZ 103 cpuStat.Nice /= userHZ 104 cpuStat.System /= userHZ 105 cpuStat.Idle /= userHZ 106 cpuStat.Iowait /= userHZ 107 cpuStat.IRQ /= userHZ 108 cpuStat.SoftIRQ /= userHZ 109 cpuStat.Steal /= userHZ 110 cpuStat.Guest /= userHZ 111 cpuStat.GuestNice /= userHZ 112 113 if cpu == "cpu" { 114 return cpuStat, -1, nil 115 } 116 117 cpuID, err := strconv.ParseInt(cpu[3:], 10, 64) 118 if err != nil { 119 return CPUStat{}, -1, fmt.Errorf("couldn't parse %q (cpu/cpuid): %w", line, err) 120 } 121 122 return cpuStat, cpuID, nil 123} 124 125// Parse a softirq line. 126func parseSoftIRQStat(line string) (SoftIRQStat, uint64, error) { 127 softIRQStat := SoftIRQStat{} 128 var total uint64 129 var prefix string 130 131 _, err := fmt.Sscanf(line, "%s %d %d %d %d %d %d %d %d %d %d %d", 132 &prefix, &total, 133 &softIRQStat.Hi, &softIRQStat.Timer, &softIRQStat.NetTx, &softIRQStat.NetRx, 134 &softIRQStat.Block, &softIRQStat.BlockIoPoll, 135 &softIRQStat.Tasklet, &softIRQStat.Sched, 136 &softIRQStat.Hrtimer, &softIRQStat.Rcu) 137 138 if err != nil { 139 return SoftIRQStat{}, 0, fmt.Errorf("couldn't parse %q (softirq): %w", line, err) 140 } 141 142 return softIRQStat, total, nil 143} 144 145// NewStat returns information about current cpu/process statistics. 146// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt 147// 148// Deprecated: use fs.Stat() instead 149func NewStat() (Stat, error) { 150 fs, err := NewFS(fs.DefaultProcMountPoint) 151 if err != nil { 152 return Stat{}, err 153 } 154 return fs.Stat() 155} 156 157// NewStat returns information about current cpu/process statistics. 158// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt 159// 160// Deprecated: use fs.Stat() instead 161func (fs FS) NewStat() (Stat, error) { 162 return fs.Stat() 163} 164 165// Stat returns information about current cpu/process statistics. 166// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt 167func (fs FS) Stat() (Stat, error) { 168 fileName := fs.proc.Path("stat") 169 data, err := util.ReadFileNoStat(fileName) 170 if err != nil { 171 return Stat{}, err 172 } 173 174 stat := Stat{} 175 176 scanner := bufio.NewScanner(bytes.NewReader(data)) 177 for scanner.Scan() { 178 line := scanner.Text() 179 parts := strings.Fields(scanner.Text()) 180 // require at least <key> <value> 181 if len(parts) < 2 { 182 continue 183 } 184 switch { 185 case parts[0] == "btime": 186 if stat.BootTime, err = strconv.ParseUint(parts[1], 10, 64); err != nil { 187 return Stat{}, fmt.Errorf("couldn't parse %q (btime): %w", parts[1], err) 188 } 189 case parts[0] == "intr": 190 if stat.IRQTotal, err = strconv.ParseUint(parts[1], 10, 64); err != nil { 191 return Stat{}, fmt.Errorf("couldn't parse %q (intr): %w", parts[1], err) 192 } 193 numberedIRQs := parts[2:] 194 stat.IRQ = make([]uint64, len(numberedIRQs)) 195 for i, count := range numberedIRQs { 196 if stat.IRQ[i], err = strconv.ParseUint(count, 10, 64); err != nil { 197 return Stat{}, fmt.Errorf("couldn't parse %q (intr%d): %w", count, i, err) 198 } 199 } 200 case parts[0] == "ctxt": 201 if stat.ContextSwitches, err = strconv.ParseUint(parts[1], 10, 64); err != nil { 202 return Stat{}, fmt.Errorf("couldn't parse %q (ctxt): %w", parts[1], err) 203 } 204 case parts[0] == "processes": 205 if stat.ProcessCreated, err = strconv.ParseUint(parts[1], 10, 64); err != nil { 206 return Stat{}, fmt.Errorf("couldn't parse %q (processes): %w", parts[1], err) 207 } 208 case parts[0] == "procs_running": 209 if stat.ProcessesRunning, err = strconv.ParseUint(parts[1], 10, 64); err != nil { 210 return Stat{}, fmt.Errorf("couldn't parse %q (procs_running): %w", parts[1], err) 211 } 212 case parts[0] == "procs_blocked": 213 if stat.ProcessesBlocked, err = strconv.ParseUint(parts[1], 10, 64); err != nil { 214 return Stat{}, fmt.Errorf("couldn't parse %q (procs_blocked): %w", parts[1], err) 215 } 216 case parts[0] == "softirq": 217 softIRQStats, total, err := parseSoftIRQStat(line) 218 if err != nil { 219 return Stat{}, err 220 } 221 stat.SoftIRQTotal = total 222 stat.SoftIRQ = softIRQStats 223 case strings.HasPrefix(parts[0], "cpu"): 224 cpuStat, cpuID, err := parseCPUStat(line) 225 if err != nil { 226 return Stat{}, err 227 } 228 if cpuID == -1 { 229 stat.CPUTotal = cpuStat 230 } else { 231 for int64(len(stat.CPU)) <= cpuID { 232 stat.CPU = append(stat.CPU, CPUStat{}) 233 } 234 stat.CPU[cpuID] = cpuStat 235 } 236 } 237 } 238 239 if err := scanner.Err(); err != nil { 240 return Stat{}, fmt.Errorf("couldn't parse %q: %w", fileName, err) 241 } 242 243 return stat, nil 244} 245