diff --git a/proc_interrupts.go b/proc_interrupts.go new file mode 100644 index 00000000..9df79c23 --- /dev/null +++ b/proc_interrupts.go @@ -0,0 +1,98 @@ +// Copyright 2022 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" + "bytes" + "errors" + "fmt" + "io" + "strconv" + "strings" + + "github.com/prometheus/procfs/internal/util" +) + +// Interrupt represents a single interrupt line. +type Interrupt struct { + // Info is the type of interrupt. + Info string + // Devices is the name of the device that is located at that IRQ + Devices string + // Values is the number of interrupts per CPU. + Values []string +} + +// Interrupts models the content of /proc/interrupts. Key is the IRQ number. +// - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/s2-proc-interrupts +// - https://raspberrypi.stackexchange.com/questions/105802/explanation-of-proc-interrupts-output +type Interrupts map[string]Interrupt + +// Interrupts creates a new instance from a given Proc instance. +func (p Proc) Interrupts() (Interrupts, error) { + data, err := util.ReadFileNoStat(p.path("interrupts")) + if err != nil { + return nil, err + } + return parseInterrupts(bytes.NewReader(data)) +} + +func parseInterrupts(r io.Reader) (Interrupts, error) { + var ( + interrupts = Interrupts{} + scanner = bufio.NewScanner(r) + ) + + if !scanner.Scan() { + return nil, errors.New("interrupts empty") + } + cpuNum := len(strings.Fields(scanner.Text())) // one header per cpu + + for scanner.Scan() { + parts := strings.Fields(scanner.Text()) + if len(parts) == 0 { // skip empty lines + continue + } + if len(parts) < 2 { + return nil, fmt.Errorf("not enough fields in interrupts (expected at least 2 fields but got %d): %s", len(parts), parts) + } + intName := parts[0][:len(parts[0])-1] // remove trailing : + + if len(parts) == 2 { + interrupts[intName] = Interrupt{ + Info: "", + Devices: "", + Values: []string{ + parts[1], + }, + } + continue + } + + intr := Interrupt{ + Values: parts[1 : cpuNum+1], + } + + if _, err := strconv.Atoi(intName); err == nil { // numeral interrupt + intr.Info = parts[cpuNum+1] + intr.Devices = strings.Join(parts[cpuNum+2:], " ") + } else { + intr.Info = strings.Join(parts[cpuNum+1:], " ") + } + interrupts[intName] = intr + } + + return interrupts, scanner.Err() +} diff --git a/proc_interrupts_test.go b/proc_interrupts_test.go new file mode 100644 index 00000000..34ce70aa --- /dev/null +++ b/proc_interrupts_test.go @@ -0,0 +1,94 @@ +// Copyright 2022 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 ( + "reflect" + "testing" +) + +func TestProcInterrupts(t *testing.T) { + p, err := getProcFixtures(t).Proc(26231) + if err != nil { + t.Fatal(err) + } + + interrupts, err := p.Interrupts() + if err != nil { + t.Fatal(err) + } + + if want, have := 47, len(interrupts); want != have { + t.Errorf("want length %d, have %d", want, have) + } + + for _, test := range []struct { + name string + irq string + want Interrupt + }{ + { + name: "first line", + irq: "0", + want: Interrupt{ + Info: "IO-APIC", + Devices: "2-edge timer", + Values: []string{"49", "0", "0", "0"}, + }, + }, + { + name: "last line", + irq: "PIW", + want: Interrupt{ + Info: "Posted-interrupt wakeup event", + Devices: "", + Values: []string{"0", "0", "0", "0"}, + }, + }, + { + name: "empty devices", + irq: "LOC", + want: Interrupt{ + Info: "Local timer interrupts", + Devices: "", + Values: []string{"10196", "7429", "8542", "8229"}, + }, + }, + { + name: "single value", + irq: "ERR", + want: Interrupt{ + Info: "", + Devices: "", + Values: []string{"0"}, + }, + }, + } { + t.Run(test.name, func(t *testing.T) { + if value, ok := interrupts[test.irq]; ok { + if value.Info != test.want.Info { + t.Errorf("info: want %s, have %s", test.want.Info, value.Info) + } + if value.Devices != test.want.Devices { + t.Errorf("devices: want %s, have %s", test.want.Devices, value.Devices) + } + if !reflect.DeepEqual(value.Values, test.want.Values) { + t.Errorf("values: want %v, have %v", test.want.Values, value.Values) + } + } else { + t.Errorf("IRQ %s not found", test.irq) + } + }) + } +} diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar index 8400ec76..b67bb97c 100644 --- a/testdata/fixtures.ttar +++ b/testdata/fixtures.ttar @@ -89,6 +89,59 @@ flags: 02004002 mnt_id: 9 Mode: 400 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/26231/interrupts +Lines: 49 + CPU0 CPU1 CPU2 CPU3 + 0: 49 0 0 0 IO-APIC 2-edge timer + 1: 0 0 0 9 IO-APIC 1-edge i8042 + 4: 0 1443 0 0 IO-APIC 4-edge ttyS0 + 8: 1 0 0 0 IO-APIC 8-edge rtc0 + 9: 0 0 0 0 IO-APIC 9-fasteoi acpi + 12: 0 0 144 0 IO-APIC 12-edge i8042 + 22: 0 0 0 5 IO-APIC 22-fasteoi virtio1 + 24: 0 0 0 0 PCI-MSI 114688-edge virtio5-config + 25: 1800 0 0 0 PCI-MSI 114689-edge virtio5-req.0 + 26: 0 1469 0 0 PCI-MSI 114690-edge virtio5-req.1 + 27: 0 0 2654 0 PCI-MSI 114691-edge virtio5-req.2 + 28: 0 0 0 1989 PCI-MSI 114692-edge virtio5-req.3 + 29: 1362 0 0 934 PCI-MSI 512000-edge ahci[0000:00:1f.2] + 30: 0 0 0 0 PCI-MSI 98304-edge xhci_hcd + 31: 0 0 0 0 PCI-MSI 98305-edge xhci_hcd + 32: 0 0 0 0 PCI-MSI 98306-edge xhci_hcd + 33: 0 0 0 0 PCI-MSI 98307-edge xhci_hcd + 34: 0 0 0 0 PCI-MSI 98308-edge xhci_hcd + 35: 0 0 0 0 PCI-MSI 16384-edge virtio0-config + 36: 0 335 37 0 PCI-MSI 16385-edge virtio0-input.0 + 37: 0 0 0 318 PCI-MSI 16386-edge virtio0-output.0 + 38: 0 0 0 0 PCI-MSI 49152-edge virtio2-config + 39: 1243 178 0 0 PCI-MSI 49153-edge virtio2-control + 40: 0 0 0 0 PCI-MSI 49154-edge virtio2-cursor + 41: 0 0 0 0 PCI-MSI 65536-edge virtio3-config + 42: 0 0 0 0 PCI-MSI 65537-edge virtio3-virtqueues + 43: 0 0 0 0 PCI-MSI 81920-edge virtio4-config + 44: 0 0 0 0 PCI-MSI 81921-edge virtio4-virtqueues +NMI: 0 0 0 0 Non-maskable interrupts +LOC: 10196 7429 8542 8229 Local timer interrupts +SPU: 0 0 0 0 Spurious interrupts +PMI: 0 0 0 0 Performance monitoring interrupts +IWI: 0 3 11 6 IRQ work interrupts +RTR: 0 0 0 0 APIC ICR read retries +RES: 7997 11147 10898 12675 Rescheduling interrupts +CAL: 2761 2485 1787 2367 Function call interrupts +TLB: 212 137 158 231 TLB shootdowns +TRM: 0 0 0 0 Thermal event interrupts +THR: 0 0 0 0 Threshold APIC interrupts +DFR: 0 0 0 0 Deferred Error APIC interrupts +MCE: 0 0 0 0 Machine check exceptions +MCP: 1 1 1 1 Machine check polls +ERR: 0 +MIS: 0 + +PIN: 0 0 0 0 Posted-interrupt notification event +NPI: 0 0 0 0 Nested posted-interrupt event +PIW: 0 0 0 0 Posted-interrupt wakeup event +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/proc/26231/io Lines: 7 rchar: 750339