Repository: prometheus/procfs
Pull request #475 (2022-11-02 10:03:48)

Add `/proc/interrupts` support
Signed-off-by: Furkan <furkan.turkal@trendyol.com>

I have some use-cases for this implementation:
* want to parse my `/proc/interrupts` file using procfs package (with go:linkname)
* move interrupt parsing logic [from node_exporter](https://github.com/prometheus/node_exporter/blob/68a6c78c0d1fd7359fa15d30b607b322eb512a8d/collector/interrupts_linux.go#L68-L99) to here
* also supports single values

All tests passes.

Dentrax: I was manually update the fixture so it fails. Now just followed [Updating Test Fixtures](https://github.com/prometheus/procfs#updating-test-fixtures) instructions but I couldn't get exactly:

```
$ make fixtures/.unpacked
make: *** No rule to make target `fixtures/.unpacked'.  Stop.
```

```
$ make test
>> extracting fixtures testdata/fixtures
./ttar -C testdata/ -x -f testdata/fixtures.ttar
WARNING sed unable to handle null bytes, using Python (slow).
```

I almost waited ~5-10m but nothing happens, the process hangs, I guess. (macOS)

Feel free to commit my branch to update fixture!
Dentrax: > Just need to update the dates.

Done!

Patch:
diff --git a/proc_interrupts.go b/proc_interrupts.go
new file mode 100644
index 0000000..9df79c2
--- /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()
+}


Test patch:
diff --git a/proc_interrupts_test.go b/proc_interrupts_test.go
new file mode 100644
index 0000000..34ce70a
--- /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 9caaa08..53c45d7 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