// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package procfs import ( "bufio" "fmt" "io" "os" "strconv" "strings" ) // CPUStat shows how much time the cpu spend in various stages. type CPUStat struct { User float64 Nice float64 System float64 Idle float64 Iowait float64 IRQ float64 SoftIRQ float64 Steal float64 Guest float64 GuestNice float64 } // SoftIRQStat represent the softirq statistics as exported in the procfs stat file. // A nice introduction can be found at https://0xax.gitbooks.io/linux-insides/content/interrupts/interrupts-9.html // It is possible to get per-cpu stats by reading /proc/softirqs type SoftIRQStat struct { Hi uint64 Timer uint64 NetTx uint64 NetRx uint64 Block uint64 BlockIoPoll uint64 Tasklet uint64 Sched uint64 Hrtimer uint64 Rcu uint64 } // Stat represents kernel/system statistics. type Stat struct { // Boot time in seconds since the Epoch. BootTime uint64 // Summed up cpu statistics. CPUTotal CPUStat // Per-CPU statistics. CPU []CPUStat // Number of times interrupts were handled, which contains numbered and unnumbered IRQs. IRQTotal uint64 // Number of times a numbered IRQ was triggered. IRQ []uint64 // Number of times a context switch happened. ContextSwitches uint64 // Number of times a process was created. ProcessCreated uint64 // Number of processes currently running. ProcessesRunning uint64 // Number of processes currently blocked (waiting for IO). ProcessesBlocked uint64 // Number of times a softirq was scheduled. SoftIRQTotal uint64 // Detailed softirq statistics. SoftIRQ SoftIRQStat } // NewStat returns kernel/system statistics read from /proc/stat. func NewStat() (Stat, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { return Stat{}, err } return fs.NewStat() } // Parse a cpu statistics line and returns the CPUStat struct plus the cpu id (or -1 for the overall sum). func parseCPUStat(line string) (CPUStat, int64, error) { cpuStat := CPUStat{} var cpu string count, err := fmt.Sscanf(line, "%s %f %f %f %f %f %f %f %f %f %f", &cpu, &cpuStat.User, &cpuStat.Nice, &cpuStat.System, &cpuStat.Idle, &cpuStat.Iowait, &cpuStat.IRQ, &cpuStat.SoftIRQ, &cpuStat.Steal, &cpuStat.Guest, &cpuStat.GuestNice) if err != nil && err != io.EOF { return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu): %s", line, err) } if count == 0 { return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu): 0 elements parsed", line) } cpuStat.User /= userHZ cpuStat.Nice /= userHZ cpuStat.System /= userHZ cpuStat.Idle /= userHZ cpuStat.Iowait /= userHZ cpuStat.IRQ /= userHZ cpuStat.SoftIRQ /= userHZ cpuStat.Steal /= userHZ cpuStat.Guest /= userHZ cpuStat.GuestNice /= userHZ if cpu == "cpu" { return cpuStat, -1, nil } cpuID, err := strconv.ParseInt(cpu[3:], 10, 64) if err != nil { return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu/cpuid): %s", line, err) } return cpuStat, cpuID, nil } // Parse a softirq line. func parseSoftIRQStat(line string) (SoftIRQStat, uint64, error) { softIRQStat := SoftIRQStat{} var total uint64 var prefix string _, err := fmt.Sscanf(line, "%s %d %d %d %d %d %d %d %d %d %d %d", &prefix, &total, &softIRQStat.Hi, &softIRQStat.Timer, &softIRQStat.NetTx, &softIRQStat.NetRx, &softIRQStat.Block, &softIRQStat.BlockIoPoll, &softIRQStat.Tasklet, &softIRQStat.Sched, &softIRQStat.Hrtimer, &softIRQStat.Rcu) if err != nil { return SoftIRQStat{}, 0, fmt.Errorf("couldn't parse %s (softirq): %s", line, err) } return softIRQStat, total, nil } // NewStat returns an information about current kernel/system statistics. func (fs FS) NewStat() (Stat, error) { // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt f, err := os.Open(fs.Path("stat")) if err != nil { return Stat{}, err } defer f.Close() stat := Stat{} scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() parts := strings.Fields(scanner.Text()) // require at least if len(parts) < 2 { continue } switch { case parts[0] == "btime": if stat.BootTime, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (btime): %s", parts[1], err) } case parts[0] == "intr": if stat.IRQTotal, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (intr): %s", parts[1], err) } numberedIRQs := parts[2:] stat.IRQ = make([]uint64, len(numberedIRQs)) for i, count := range numberedIRQs { if stat.IRQ[i], err = strconv.ParseUint(count, 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (intr%d): %s", count, i, err) } } case parts[0] == "ctxt": if stat.ContextSwitches, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (ctxt): %s", parts[1], err) } case parts[0] == "processes": if stat.ProcessCreated, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (processes): %s", parts[1], err) } case parts[0] == "procs_running": if stat.ProcessesRunning, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (procs_running): %s", parts[1], err) } case parts[0] == "procs_blocked": if stat.ProcessesBlocked, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (procs_blocked): %s", parts[1], err) } case parts[0] == "softirq": softIRQStats, total, err := parseSoftIRQStat(line) if err != nil { return Stat{}, err } stat.SoftIRQTotal = total stat.SoftIRQ = softIRQStats case strings.HasPrefix(parts[0], "cpu"): cpuStat, cpuID, err := parseCPUStat(line) if err != nil { return Stat{}, err } if cpuID == -1 { stat.CPUTotal = cpuStat } else { for int64(len(stat.CPU)) <= cpuID { stat.CPU = append(stat.CPU, CPUStat{}) } stat.CPU[cpuID] = cpuStat } } } if err := scanner.Err(); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s: %s", f.Name(), err) } return stat, nil }