Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add /proc/interrupts support #475

Merged
merged 1 commit into from Dec 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
98 changes: 98 additions & 0 deletions 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
Dentrax marked this conversation as resolved.
Show resolved Hide resolved
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()
}
94 changes: 94 additions & 0 deletions 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)
}
})
}
}
53 changes: 53 additions & 0 deletions testdata/fixtures.ttar
Expand Up @@ -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
Expand Down