Repository: prometheus/procfs (go)

Issue:
Add support for `/proc/net/wireless` parsing
To collect wireless RSSI values using procfs. We can also add this support to node_exporter once we implement this.

Example:
```
$ cat /proc/net/wireless

Inter-| sta-|   Quality        |   Discarded packets               | Missed | WE
 face | tus | link level noise |  nwid  crypt   frag  retry   misc | beacon | 22

wlo1: 0000   70.  -31.  -256        0      0      0      0     25        0
```

/assign

Pull request description:
Add /proc/net/wireless parsing
Fixes #479

cc @discordianfish

Signed-off-by: Furkan <furkan.turkal@trendyol.com>

Patch:
diff --git a/net_wireless.go b/net_wireless.go
new file mode 100644
index 0000000..c80fb15
--- /dev/null
+++ b/net_wireless.go
@@ -0,0 +1,182 @@
+// Copyright 2023 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"
+)
+
+// Wireless models the content of /proc/net/wireless.
+type Wireless struct {
+	Name string
+
+	// Status is the current 4-digit hex value status of the interface.
+	Status uint64
+
+	// QualityLink is the link quality.
+	QualityLink int
+
+	// QualityLevel is the signal gain (dBm).
+	QualityLevel int
+
+	// QualityNoise is the signal noise baseline (dBm).
+	QualityNoise int
+
+	// DiscardedNwid is the number of discarded packets with wrong nwid/essid.
+	DiscardedNwid int
+
+	// DiscardedCrypt is the number of discarded packets with wrong code/decode (WEP).
+	DiscardedCrypt int
+
+	// DiscardedFrag is the number of discarded packets that can't perform MAC reassembly.
+	DiscardedFrag int
+
+	// DiscardedRetry is the number of discarded packets that reached max MAC retries.
+	DiscardedRetry int
+
+	// DiscardedMisc is the number of discarded packets for other reasons.
+	DiscardedMisc int
+
+	// MissedBeacon is the number of missed beacons/superframe.
+	MissedBeacon int
+}
+
+// Wireless returns kernel wireless statistics.
+func (fs FS) Wireless() ([]*Wireless, error) {
+	b, err := util.ReadFileNoStat(fs.proc.Path("net/wireless"))
+	if err != nil {
+		return nil, err
+	}
+
+	m, err := parseWireless(bytes.NewReader(b))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse wireless: %w", err)
+	}
+
+	return m, nil
+}
+
+// parseWireless parses the contents of /proc/net/wireless.
+/*
+Inter-| sta-|   Quality        |   Discarded packets               | Missed | WE
+face | tus | link level noise |  nwid  crypt   frag  retry   misc | beacon | 22
+ eth1: 0000    5.  -256.  -10.       0      1      0     3      0        0
+ eth2: 0000    5.  -256.  -20.       0      2      0     4      0        0
+*/
+func parseWireless(r io.Reader) ([]*Wireless, error) {
+	var (
+		interfaces []*Wireless
+		scanner    = bufio.NewScanner(r)
+	)
+
+	for n := 0; scanner.Scan(); n++ {
+		// Skip the 2 header lines.
+		if n < 2 {
+			continue
+		}
+
+		line := scanner.Text()
+
+		parts := strings.Split(line, ":")
+		if len(parts) != 2 {
+			return nil, fmt.Errorf("expected 2 parts after splitting line by ':', got %d for line %q", len(parts), line)
+		}
+
+		name := strings.TrimSpace(parts[0])
+		stats := strings.Fields(parts[1])
+
+		if len(stats) < 10 {
+			return nil, fmt.Errorf("invalid number of fields in line %d, expected at least 10, got %d: %q", n, len(stats), line)
+		}
+
+		status, err := strconv.ParseUint(stats[0], 16, 16)
+		if err != nil {
+			return nil, fmt.Errorf("invalid status in line %d: %q", n, line)
+		}
+
+		qlink, err := strconv.Atoi(strings.TrimSuffix(stats[1], "."))
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse Quality:link as integer %q: %w", qlink, err)
+		}
+
+		qlevel, err := strconv.Atoi(strings.TrimSuffix(stats[2], "."))
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse Quality:level as integer %q: %w", qlevel, err)
+		}
+
+		qnoise, err := strconv.Atoi(strings.TrimSuffix(stats[3], "."))
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse Quality:noise as integer %q: %w", qnoise, err)
+		}
+
+		dnwid, err := strconv.Atoi(stats[4])
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse Discarded:nwid as integer %q: %w", dnwid, err)
+		}
+
+		dcrypt, err := strconv.Atoi(stats[5])
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse Discarded:crypt as integer %q: %w", dcrypt, err)
+		}
+
+		dfrag, err := strconv.Atoi(stats[6])
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse Discarded:frag as integer %q: %w", dfrag, err)
+		}
+
+		dretry, err := strconv.Atoi(stats[7])
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse Discarded:retry as integer %q: %w", dretry, err)
+		}
+
+		dmisc, err := strconv.Atoi(stats[8])
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse Discarded:misc as integer %q: %w", dmisc, err)
+		}
+
+		mbeacon, err := strconv.Atoi(stats[9])
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse Missed:beacon as integer %q: %w", mbeacon, err)
+		}
+
+		w := &Wireless{
+			Name:           name,
+			Status:         status,
+			QualityLink:    qlink,
+			QualityLevel:   qlevel,
+			QualityNoise:   qnoise,
+			DiscardedNwid:  dnwid,
+			DiscardedCrypt: dcrypt,
+			DiscardedFrag:  dfrag,
+			DiscardedRetry: dretry,
+			DiscardedMisc:  dmisc,
+			MissedBeacon:   mbeacon,
+		}
+
+		interfaces = append(interfaces, w)
+	}
+
+	if err := scanner.Err(); err != nil {
+		return nil, fmt.Errorf("failed to scan /proc/net/wireless: %w", err)
+	}
+
+	return interfaces, nil
+}


Test patch:
diff --git a/net_wireless_test.go b/net_wireless_test.go
new file mode 100644
index 0000000..25d6a65
--- /dev/null
+++ b/net_wireless_test.go
@@ -0,0 +1,70 @@
+// Copyright 2023 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 TestWireless(t *testing.T) {
+	fs, err := NewFS(procTestFixtures)
+	if err != nil {
+		t.Fatalf("failed to open procfs: %v", err)
+	}
+
+	got, err := fs.Wireless()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := []*Wireless{
+		{
+			Name:           "wlan0",
+			Status:         1,
+			QualityLink:    2,
+			QualityLevel:   3,
+			QualityNoise:   4,
+			DiscardedNwid:  5,
+			DiscardedCrypt: 6,
+			DiscardedFrag:  7,
+			DiscardedRetry: 8,
+			DiscardedMisc:  9,
+			MissedBeacon:   10,
+		},
+		{
+			Name:           "wlan1",
+			Status:         16,
+			QualityLink:    9,
+			QualityLevel:   8,
+			QualityNoise:   7,
+			DiscardedNwid:  6,
+			DiscardedCrypt: 5,
+			DiscardedFrag:  4,
+			DiscardedRetry: 3,
+			DiscardedMisc:  2,
+			MissedBeacon:   1,
+		},
+	}
+
+	if len(got) != len(expected) {
+		t.Fatalf("unexpected number of interfaces parsed %d, expected %d", len(got), len(expected))
+	}
+
+	for i, iface := range got {
+		if !reflect.DeepEqual(iface, expected[i]) {
+			t.Errorf("unexpected interface got %+v, expected %+v", iface, expected[i])
+		}
+	}
+}
diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar
index a965b11..e9a8cd0 100644
--- a/testdata/fixtures.ttar
+++ b/testdata/fixtures.ttar
@@ -2568,6 +2568,14 @@ Num       RefCount Protocol Flags    Type St Path
 0000000000000000: 00000003 00000000 00000000 0001 03
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/net/wireless
+Lines: 4
+Inter-| sta-|   Quality        |   Discarded packets               | Missed
+ face | tus | link level noise |  nwid  crypt   frag  retry   misc | beacon
+ wlan0: 0001    2.    3.    4.       5      6      7      8      9       10
+ wlan1: 0010    9     8.    7.       6      5      4      3      2        1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/net/xfrm_stat
 Lines: 28
 XfrmInError                     1