Repository: prometheus/procfs
Pull request #436 (2022-03-01 14:17:52)

Parsing for /proc/softirqs
Parsing of detailed softirq statistics found in /proc/softirqs.

Relating to pull request node_exporter: https://github.com/prometheus/node_exporter/pull/2294

Hopefully this is what you had in mind @discordianfish - once pulled in I'll return to the original PR and replace the parsing.

discordianfish: LGTM beside the print statements and the naming.
pgier: Thanks for fixing the copyright headers from the other commit.  Would you mind squashing the commits?  And then I will merge.
SuperQ: @pgier I usually use the GitHub "Squash and merge" feature.
pgier: @SuperQ ok, I can do that, just didn't want to have the messy commit log comment
SuperQ: GitHub also lets you edit the commit before merge. I usually clean things up.

Patch:
diff --git a/fixtures.ttar b/fixtures.ttar
index 0fe1e08..1eedeef 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -156,6 +156,14 @@ Inter-|   Receive                                                |  Transmit
   eth0:     438       5    0    0    0     0          0         0      648       8    0    0    0     0       0          0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/26231/net/netstat
+Lines: 4
+TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed EmbryonicRsts PruneCalled RcvPruned OfoPruned OutOfWindowIcmps LockDroppedIcmps ArpFilter TW TWRecycled TWKilled PAWSActive PAWSEstab DelayedACKs DelayedACKLocked DelayedACKLost ListenOverflows ListenDrops TCPHPHits TCPPureAcks TCPHPAcks TCPRenoRecovery TCPSackRecovery TCPSACKReneging TCPSACKReorder TCPRenoReorder TCPTSReorder TCPFullUndo TCPPartialUndo TCPDSACKUndo TCPLossUndo TCPLostRetransmit TCPRenoFailures TCPSackFailures TCPLossFailures TCPFastRetrans TCPSlowStartRetrans TCPTimeouts TCPLossProbes TCPLossProbeRecovery TCPRenoRecoveryFail TCPSackRecoveryFail TCPRcvCollapsed TCPDSACKOldSent TCPDSACKOfoSent TCPDSACKRecv TCPDSACKOfoRecv TCPAbortOnData TCPAbortOnClose TCPAbortOnMemory TCPAbortOnTimeout TCPAbortOnLinger TCPAbortFailed TCPMemoryPressures TCPMemoryPressuresChrono TCPSACKDiscard TCPDSACKIgnoredOld TCPDSACKIgnoredNoUndo TCPSpuriousRTOs TCPMD5NotFound TCPMD5Unexpected TCPMD5Failure TCPSackShifted TCPSackMerged TCPSackShiftFallback TCPBacklogDrop PFMemallocDrop TCPMinTTLDrop TCPDeferAcceptDrop IPReversePathFilter TCPTimeWaitOverflow TCPReqQFullDoCookies TCPReqQFullDrop TCPRetransFail TCPRcvCoalesce TCPOFOQueue TCPOFODrop TCPOFOMerge TCPChallengeACK TCPSYNChallenge TCPFastOpenActive TCPFastOpenActiveFail TCPFastOpenPassive TCPFastOpenPassiveFail TCPFastOpenListenOverflow TCPFastOpenCookieReqd TCPFastOpenBlackhole TCPSpuriousRtxHostQueues BusyPollRxPackets TCPAutoCorking TCPFromZeroWindowAdv TCPToZeroWindowAdv TCPWantZeroWindowAdv TCPSynRetrans TCPOrigDataSent TCPHystartTrainDetect TCPHystartTrainCwnd TCPHystartDelayDetect TCPHystartDelayCwnd TCPACKSkippedSynRecv TCPACKSkippedPAWS TCPACKSkippedSeq TCPACKSkippedFinWait2 TCPACKSkippedTimeWait TCPACKSkippedChallenge TCPWinProbe TCPKeepAlive TCPMTUPFail TCPMTUPSuccess TCPWqueueTooBig
+TcpExt: 0 0 0 1 0 0 0 0 0 0 83 0 0 0 3640 287 1 7460 0 0 134193 1335 829 0 4 0 1 0 0 0 0 1 19 0 0 0 0 3 0 32 100 4 0 0 0 7460 2421 49 1 62 6 0 23 0 7 0 0 0 0 19 2 0 0 0 0 0 6 0 0 0 0 3 0 0 0 0 92425 65515 0 2421 4 4 0 0 0 0 0 0 0 0 0 10 0 0 0 16 2221 0 0 2 45 0 0 3 0 0 0 0 456 0 0 0
+IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts InBcastPkts OutBcastPkts InOctets OutOctets InMcastOctets OutMcastOctets InBcastOctets OutBcastOctets InCsumErrors InNoECTPkts InECT1Pkts InECT0Pkts InCEPkts ReasmOverlaps
+IpExt: 0 0 208 214 118 111 190585481 7512674 26093 25903 14546 13628 0 134215 0 0 0 0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/26231/net/snmp
 Lines: 12
 Ip: Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequests OutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKs FragFails FragCreates
@@ -172,14 +180,6 @@ UdpLite: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InC
 UdpLite: 0 0 0 0 0 0 0 0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Path: fixtures/proc/26231/net/netstat
-Lines: 4
-TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed EmbryonicRsts PruneCalled RcvPruned OfoPruned OutOfWindowIcmps LockDroppedIcmps ArpFilter TW TWRecycled TWKilled PAWSActive PAWSEstab DelayedACKs DelayedACKLocked DelayedACKLost ListenOverflows ListenDrops TCPHPHits TCPPureAcks TCPHPAcks TCPRenoRecovery TCPSackRecovery TCPSACKReneging TCPSACKReorder TCPRenoReorder TCPTSReorder TCPFullUndo TCPPartialUndo TCPDSACKUndo TCPLossUndo TCPLostRetransmit TCPRenoFailures TCPSackFailures TCPLossFailures TCPFastRetrans TCPSlowStartRetrans TCPTimeouts TCPLossProbes TCPLossProbeRecovery TCPRenoRecoveryFail TCPSackRecoveryFail TCPRcvCollapsed TCPDSACKOldSent TCPDSACKOfoSent TCPDSACKRecv TCPDSACKOfoRecv TCPAbortOnData TCPAbortOnClose TCPAbortOnMemory TCPAbortOnTimeout TCPAbortOnLinger TCPAbortFailed TCPMemoryPressures TCPMemoryPressuresChrono TCPSACKDiscard TCPDSACKIgnoredOld TCPDSACKIgnoredNoUndo TCPSpuriousRTOs TCPMD5NotFound TCPMD5Unexpected TCPMD5Failure TCPSackShifted TCPSackMerged TCPSackShiftFallback TCPBacklogDrop PFMemallocDrop TCPMinTTLDrop TCPDeferAcceptDrop IPReversePathFilter TCPTimeWaitOverflow TCPReqQFullDoCookies TCPReqQFullDrop TCPRetransFail TCPRcvCoalesce TCPOFOQueue TCPOFODrop TCPOFOMerge TCPChallengeACK TCPSYNChallenge TCPFastOpenActive TCPFastOpenActiveFail TCPFastOpenPassive TCPFastOpenPassiveFail TCPFastOpenListenOverflow TCPFastOpenCookieReqd TCPFastOpenBlackhole TCPSpuriousRtxHostQueues BusyPollRxPackets TCPAutoCorking TCPFromZeroWindowAdv TCPToZeroWindowAdv TCPWantZeroWindowAdv TCPSynRetrans TCPOrigDataSent TCPHystartTrainDetect TCPHystartTrainCwnd TCPHystartDelayDetect TCPHystartDelayCwnd TCPACKSkippedSynRecv TCPACKSkippedPAWS TCPACKSkippedSeq TCPACKSkippedFinWait2 TCPACKSkippedTimeWait TCPACKSkippedChallenge TCPWinProbe TCPKeepAlive TCPMTUPFail TCPMTUPSuccess TCPWqueueTooBig
-TcpExt: 0 0 0 1 0 0 0 0 0 0 83 0 0 0 3640 287 1 7460 0 0 134193 1335 829 0 4 0 1 0 0 0 0 1 19 0 0 0 0 3 0 32 100 4 0 0 0 7460 2421 49 1 62 6 0 23 0 7 0 0 0 0 19 2 0 0 0 0 0 6 0 0 0 0 3 0 0 0 0 92425 65515 0 2421 4 4 0 0 0 0 0 0 0 0 0 10 0 0 0 16 2221 0 0 2 45 0 0 3 0 0 0 0 456 0 0 0
-IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts InBcastPkts OutBcastPkts InOctets OutOctets InMcastOctets OutMcastOctets InBcastOctets OutBcastOctets InCsumErrors InNoECTPkts InECT1Pkts InECT0Pkts InCEPkts ReasmOverlaps
-IpExt: 0 0 208 214 118 111 190585481 7512674 26093 25903 14546 13628 0 134215 0 0 0 0
-Mode: 644
-# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/26231/net/snmp6
 Lines: 92
 Ip6InReceives                   	92166
@@ -274,6 +274,7 @@ UdpLite6RcvbufErrors            	0
 UdpLite6SndbufErrors            	0
 UdpLite6InCsumErrors            	0
 Mode: 644
+Mode: 664
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/proc/26231/ns
 Mode: 755
@@ -2773,6 +2774,21 @@ kmem_cache_node      904    928    512   32    4 : tunables    0    0    0 : sla
 kmem_cache           904    936    832   39    8 : tunables    0    0    0 : slabdata     24     24      0
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/softirqs
+Lines: 11
+                    CPU0       CPU1       
+          HI:          3          0
+       TIMER:    2776180     247490
+      NET_TX:       2419        772
+      NET_RX:      55919      28694
+       BLOCK:     174915     262755
+    IRQ_POLL:          0          0
+     TASKLET:        209         75
+       SCHED:    2278692     815209
+     HRTIMER:       1281        220
+         RCU:     605871     532783
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/stat
 Lines: 16
 cpu  301854 612 111922 8979004 3552 2 3944 0 0 0
diff --git a/proc_netstat.go b/proc_netstat.go
index c974b9b..da40ea1 100644
--- a/proc_netstat.go
+++ b/proc_netstat.go
@@ -183,7 +183,7 @@ func (p Proc) Netstat() (ProcNetstat, error) {
 // and returns a ProcNetstat structure.
 func parseNetstat(r io.Reader, fileName string) (ProcNetstat, error) {
 	var (
-		scanner  = bufio.NewScanner(r)
+		scanner     = bufio.NewScanner(r)
 		procNetstat = ProcNetstat{}
 	)
 
diff --git a/proc_snmp6.go b/proc_snmp6.go
index 9438995..52c4891 100644
--- a/proc_snmp6.go
+++ b/proc_snmp6.go
@@ -160,7 +160,7 @@ func (p Proc) Snmp6() (ProcSnmp6, error) {
 // and returns a map contains those metrics.
 func parseSNMP6Stats(r io.Reader) (ProcSnmp6, error) {
 	var (
-		scanner  = bufio.NewScanner(r)
+		scanner   = bufio.NewScanner(r)
 		procSnmp6 = ProcSnmp6{}
 	)
 
diff --git a/softirqs.go b/softirqs.go
new file mode 100644
index 0000000..d27c35f
--- /dev/null
+++ b/softirqs.go
@@ -0,0 +1,160 @@
+// 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"
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+// Softirqs represents the softirq statistics.
+type Softirqs struct {
+	Hi      []uint64
+	Timer   []uint64
+	NetTx   []uint64
+	NetRx   []uint64
+	Block   []uint64
+	IRQPoll []uint64
+	Tasklet []uint64
+	Sched   []uint64
+	HRTimer []uint64
+	RCU     []uint64
+}
+
+func (fs FS) Softirqs() (Softirqs, error) {
+	fileName := fs.proc.Path("softirqs")
+	data, err := util.ReadFileNoStat(fileName)
+	if err != nil {
+		return Softirqs{}, err
+	}
+
+	reader := bytes.NewReader(data)
+
+	return parseSoftirqs(reader)
+}
+
+func parseSoftirqs(r io.Reader) (Softirqs, error) {
+	var (
+		softirqs = Softirqs{}
+		scanner  = bufio.NewScanner(r)
+	)
+
+	if !scanner.Scan() {
+		return Softirqs{}, fmt.Errorf("softirqs empty")
+	}
+
+	for scanner.Scan() {
+		parts := strings.Fields(scanner.Text())
+		var err error
+
+		// require at least one cpu
+		if len(parts) < 2 {
+			continue
+		}
+		switch {
+		case parts[0] == "HI:":
+			perCpu := parts[1:]
+			softirqs.Hi = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.Hi[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (HI%d): %w", count, i, err)
+				}
+			}
+		case parts[0] == "TIMER:":
+			perCpu := parts[1:]
+			softirqs.Timer = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.Timer[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (TIMER%d): %w", count, i, err)
+				}
+			}
+		case parts[0] == "NET_TX:":
+			perCpu := parts[1:]
+			softirqs.NetTx = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.NetTx[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (NET_TX%d): %w", count, i, err)
+				}
+			}
+		case parts[0] == "NET_RX:":
+			perCpu := parts[1:]
+			softirqs.NetRx = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.NetRx[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (NET_RX%d): %w", count, i, err)
+				}
+			}
+		case parts[0] == "BLOCK:":
+			perCpu := parts[1:]
+			softirqs.Block = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.Block[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (BLOCK%d): %w", count, i, err)
+				}
+			}
+		case parts[0] == "IRQ_POLL:":
+			perCpu := parts[1:]
+			softirqs.IRQPoll = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.IRQPoll[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (IRQ_POLL%d): %w", count, i, err)
+				}
+			}
+		case parts[0] == "TASKLET:":
+			perCpu := parts[1:]
+			softirqs.Tasklet = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.Tasklet[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (TASKLET%d): %w", count, i, err)
+				}
+			}
+		case parts[0] == "SCHED:":
+			perCpu := parts[1:]
+			softirqs.Sched = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.Sched[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (SCHED%d): %w", count, i, err)
+				}
+			}
+		case parts[0] == "HRTIMER:":
+			perCpu := parts[1:]
+			softirqs.HRTimer = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.HRTimer[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (HRTIMER%d): %w", count, i, err)
+				}
+			}
+		case parts[0] == "RCU:":
+			perCpu := parts[1:]
+			softirqs.RCU = make([]uint64, len(perCpu))
+			for i, count := range perCpu {
+				if softirqs.RCU[i], err = strconv.ParseUint(count, 10, 64); err != nil {
+					return Softirqs{}, fmt.Errorf("couldn't parse %q (RCU%d): %w", count, i, err)
+				}
+			}
+		}
+	}
+
+	if err := scanner.Err(); err != nil {
+		return Softirqs{}, fmt.Errorf("couldn't parse softirqs: %w", err)
+	}
+
+	return softirqs, scanner.Err()
+}


Test patch:
diff --git a/proc_netstat_test.go b/proc_netstat_test.go
index 3a0ed31..6cb9af2 100644
--- a/proc_netstat_test.go
+++ b/proc_netstat_test.go
@@ -1,3 +1,16 @@
+// 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 (
diff --git a/proc_snmp6_test.go b/proc_snmp6_test.go
index c3a55bf..aed483e 100644
--- a/proc_snmp6_test.go
+++ b/proc_snmp6_test.go
@@ -1,3 +1,16 @@
+// 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 "testing"
diff --git a/softirqs_test.go b/softirqs_test.go
new file mode 100644
index 0000000..bf32af2
--- /dev/null
+++ b/softirqs_test.go
@@ -0,0 +1,64 @@
+// 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 "testing"
+
+func TestSoftirqs(t *testing.T) {
+	s, err := getProcFixtures(t).Softirqs()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// hi
+	if want, have := uint64(3), s.Hi[0]; want != have {
+		t.Errorf("want softirq HI count %d, have %d", want, have)
+	}
+	// timer
+	if want, have := uint64(247490), s.Timer[1]; want != have {
+		t.Errorf("want softirq TIMER count %d, have %d", want, have)
+	}
+	// net_tx
+	if want, have := uint64(2419), s.NetTx[0]; want != have {
+		t.Errorf("want softirq NET_TX count %d, have %d", want, have)
+	}
+	// net_rx
+	if want, have := uint64(28694), s.NetRx[1]; want != have {
+		t.Errorf("want softirq NET_RX count %d, have %d", want, have)
+	}
+	// block
+	if want, have := uint64(262755), s.Block[1]; want != have {
+		t.Errorf("want softirq BLOCK count %d, have %d", want, have)
+	}
+	// irq_poll
+	if want, have := uint64(0), s.IRQPoll[0]; want != have {
+		t.Errorf("want softirq IRQ_POLL count %d, have %d", want, have)
+	}
+	// tasklet
+	if want, have := uint64(209), s.Tasklet[0]; want != have {
+		t.Errorf("want softirq TASKLET count %d, have %d", want, have)
+	}
+	// sched
+	if want, have := uint64(2278692), s.Sched[0]; want != have {
+		t.Errorf("want softirq SCHED count %d, have %d", want, have)
+	}
+	// hrtimer
+	if want, have := uint64(1281), s.HRTimer[0]; want != have {
+		t.Errorf("want softirq HRTIMER count %d, have %d", want, have)
+	}
+	// rcu
+	if want, have := uint64(532783), s.RCU[1]; want != have {
+		t.Errorf("want softirq RCU count %d, have %d", want, have)
+	}
+}