Repository: prometheus/procfs
Pull request #270 (2020-02-26 18:57:08)

Add support for fibre channel
Assuming there's no problems and this PR is accepted, I have a matching PR for node_exporter.

This resolves node_exporter feature request: prometheus/node_exporter#570

(I closed my astoundingly messed up previous PR)

deusnefum: I was going to whine about having add a test, but I ended up finding a bug so... point taken.


pgier: @discordianfish @SuperQ any concerns about merging this?
deusnefum: That should do it. Sorry about that. Once this is accepted is their a guide or info on vendoring procfs into node_exporter? Is it -mod=vendor or something else?

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index ffd28ca..ca2082c 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -42,22 +42,22 @@ workflows:
     jobs:
     - test:
         command: default
-        name: linux-1-11
+        name: linux-1-13
         os: linux
-        go_version: "1.11"
+        go_version: "1.13"
     - test:
         command: default
-        name: linux-1-12
+        name: linux-1-14
         os: linux
-        go_version: "1.12"
+        go_version: "1.14"
     - test:
         command: no-test
-        name: windows-1-11
+        name: windows-1-13
         os: windows
-        go_version: "1.11"
+        go_version: "1.13"
     - test:
         command: no-test
-        name: windows-1-12
+        name: windows-1-14
         os: windows
-        go_version: "1.12"
+        go_version: "1.14"
     - codespell
diff --git a/fixtures.ttar b/fixtures.ttar
index 8ea9cff..e75201b 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -2575,6 +2575,140 @@ Mode: 664
 Directory: fixtures/sys/class
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/fc_host
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/fc_host/host0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/dev_loss_tmo
+Lines: 1
+30
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/fabric_name
+Lines: 1
+0x0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/node_name
+Lines: 1
+0x2000e0071bce95f2
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/port_id
+Lines: 1
+0x000002
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/port_name
+Lines: 1
+0x1000e0071bce95f2
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/port_state
+Lines: 1
+Online
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/port_type
+Lines: 1
+Point-To-Point (direct nport connection)
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/speed
+Lines: 1
+16 Gbit
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/fc_host/host0/statistics
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/dumped_frames
+Lines: 1
+0xffffffffffffffff
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/error_frames
+Lines: 1
+0x0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/fcp_packet_aborts
+Lines: 1
+0x13
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/invalid_crc_count
+Lines: 1
+0x2
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/invalid_tx_word_count
+Lines: 1
+0x8
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/link_failure_count
+Lines: 1
+0x9
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/loss_of_signal_count
+Lines: 1
+0x11
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/loss_of_sync_count
+Lines: 1
+0x10
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/nos_count
+Lines: 1
+0x12
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/rx_frames
+Lines: 1
+0x3
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/rx_words
+Lines: 1
+0x4
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/seconds_since_last_reset
+Lines: 1
+0x7
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/tx_frames
+Lines: 1
+0x5
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/statistics/tx_words
+Lines: 1
+0x6
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/supported_classes
+Lines: 1
+Class 3
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/supported_speeds
+Lines: 1
+4 Gbit, 8 Gbit, 16 Gbit
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/fc_host/host0/symbolic_name
+Lines: 1
+Emulex SN1100E2P FV12.4.270.3 DV12.4.0.0. HN:gotest. OS:Linux
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/class/infiniband
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/sysfs/class_fibrechannel.go b/sysfs/class_fibrechannel.go
new file mode 100644
index 0000000..d57ff9c
--- /dev/null
+++ b/sysfs/class_fibrechannel.go
@@ -0,0 +1,249 @@
+// Copyright 2020 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.
+
+// +build !windows
+
+package sysfs
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+const fibrechannelClassPath = "class/fc_host"
+
+type FibreChannelCounters struct {
+	DumpedFrames          uint64 // /sys/class/fc_host/<Name>/statistics/dumped_frames
+	ErrorFrames           uint64 // /sys/class/fc_host/<Name>/statistics/error_frames
+	InvalidCRCCount       uint64 // /sys/class/fc_host/<Name>/statistics/invalid_crc_count
+	RXFrames              uint64 // /sys/class/fc_host/<Name>/statistics/rx_frames
+	RXWords               uint64 // /sys/class/fc_host/<Name>/statistics/rx_words
+	TXFrames              uint64 // /sys/class/fc_host/<Name>/statistics/tx_frames
+	TXWords               uint64 // /sys/class/fc_host/<Name>/statistics/tx_words
+	SecondsSinceLastReset uint64 // /sys/class/fc_host/<Name>/statistics/seconds_since_last_reset
+	InvalidTXWordCount    uint64 // /sys/class/fc_host/<Name>/statistics/invalid_tx_word_count
+	LinkFailureCount      uint64 // /sys/class/fc_host/<Name>/statistics/link_failure_count
+	LossOfSyncCount       uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_sync_count
+	LossOfSignalCount     uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_signal_count
+	NosCount              uint64 // /sys/class/fc_host/<Name>/statistics/nos_count
+	FCPPacketAborts       uint64 // / sys/class/fc_host/<Name>/statistics/fcp_packet_aborts
+}
+
+type FibreChannelHost struct {
+	Name             string               // /sys/class/fc_host/<Name>
+	Speed            string               // /sys/class/fc_host/<Name>/speed
+	PortState        string               // /sys/class/fc_host/<Name>/port_state
+	PortType         string               // /sys/class/fc_host/<Name>/port_type
+	SymbolicName     string               // /sys/class/fc_host/<Name>/symbolic_name
+	NodeName         string               // /sys/class/fc_host/<Name>/node_name
+	PortID           string               // /sys/class/fc_host/<Name>/port_id
+	PortName         string               // /sys/class/fc_host/<Name>/port_name
+	FabricName       string               // /sys/class/fc_host/<Name>/fabric_name
+	DevLossTMO       string               // /sys/class/fc_host/<Name>/dev_loss_tmo
+	SupportedClasses string               // /sys/class/fc_host/<Name>/supported_classes
+	SupportedSpeeds  string               // /sys/class/fc_host/<Name>/supported_speeds
+	Counters         FibreChannelCounters // /sys/class/fc_host/<Name>/statistics/*
+}
+
+type FibreChannelClass map[string]FibreChannelHost
+
+// FibreChannelClass parses everything in /sys/class/fc_host.
+func (fs FS) FibreChannelClass() (FibreChannelClass, error) {
+	path := fs.sys.Path(fibrechannelClassPath)
+
+	dirs, err := ioutil.ReadDir(path)
+	if err != nil {
+		return nil, err
+	}
+
+	fcc := make(FibreChannelClass, len(dirs))
+	for _, d := range dirs {
+		host, err := fs.parseFibreChannelHost(d.Name())
+		if err != nil {
+			return nil, err
+		}
+
+		fcc[host.Name] = *host
+	}
+
+	return fcc, nil
+}
+
+// Parse a single FC host
+func (fs FS) parseFibreChannelHost(name string) (*FibreChannelHost, error) {
+	path := fs.sys.Path(fibrechannelClassPath, name)
+	host := FibreChannelHost{Name: name}
+
+	for _, f := range [...]string{"speed", "port_state", "port_type", "node_name", "port_id", "port_name", "fabric_name", "dev_loss_tmo", "symbolic_name", "supported_classes", "supported_speeds"} {
+		name := filepath.Join(path, f)
+		value, err := util.SysReadFile(name)
+		if err != nil {
+			return nil, fmt.Errorf("failed to read file %q: %v", name, err)
+		}
+
+		switch f {
+		case "speed":
+			host.Speed = value
+		case "port_state":
+			host.PortState = value
+		case "port_type":
+			host.PortType = value
+		case "node_name":
+			if len(value) > 2 {
+				value = value[2:]
+			}
+			host.NodeName = value
+		case "port_id":
+			if len(value) > 2 {
+				value = value[2:]
+			}
+			host.PortID = value
+		case "port_name":
+			if len(value) > 2 {
+				value = value[2:]
+			}
+			host.PortName = value
+		case "fabric_name":
+			if len(value) > 2 {
+				value = value[2:]
+			}
+			host.FabricName = value
+		case "dev_loss_tmo":
+			host.DevLossTMO = value
+		case "supported_classes":
+			host.SupportedClasses = value
+		case "supported_speeds":
+			host.SupportedSpeeds = value
+		case "symbolic_name":
+			host.SymbolicName = value
+		}
+	}
+
+	counters, err := parseFibreChannelStatistics(path)
+	if err != nil {
+		return nil, err
+	}
+	host.Counters = *counters
+
+	return &host, nil
+}
+
+// parseFibreChannelStatistics parses metrics from a single FC host.
+func parseFibreChannelStatistics(hostPath string) (*FibreChannelCounters, error) {
+	var counters FibreChannelCounters
+
+	path := filepath.Join(hostPath, "statistics")
+	files, err := ioutil.ReadDir(path)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, f := range files {
+		if !f.Mode().IsRegular() || f.Name() == "reset_statistics" {
+			continue
+		}
+
+		name := filepath.Join(path, f.Name())
+		value, err := util.SysReadFile(name)
+		if err != nil {
+			// there are some write-only files in this directory; we can safely skip over them
+			if os.IsNotExist(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
+				continue
+			}
+			return nil, fmt.Errorf("failed to read file %q: %v", name, err)
+		}
+
+		vp := util.NewValueParser(value)
+
+		// Below switch was automatically generated. Don't need everything in there yet, so the unwanted bits are commented out.
+		switch f.Name() {
+		case "dumped_frames":
+			counters.DumpedFrames = *vp.PUInt64()
+		case "error_frames":
+			counters.ErrorFrames = *vp.PUInt64()
+		/*
+			case "fc_no_free_exch":
+				counters.FcNoFreeExch = *vp.PUInt64()
+			case "fc_no_free_exch_xid":
+				counters.FcNoFreeExchXid = *vp.PUInt64()
+			case "fc_non_bls_resp":
+				counters.FcNonBlsResp = *vp.PUInt64()
+			case "fc_seq_not_found":
+				counters.FcSeqNotFound = *vp.PUInt64()
+			case "fc_xid_busy":
+				counters.FcXidBusy = *vp.PUInt64()
+			case "fc_xid_not_found":
+				counters.FcXidNotFound = *vp.PUInt64()
+			case "fcp_control_requests":
+				counters.FcpControlRequests = *vp.PUInt64()
+			case "fcp_frame_alloc_failures":
+				counters.FcpFrameAllocFailures = *vp.PUInt64()
+			case "fcp_input_megabytes":
+				counters.FcpInputMegabytes = *vp.PUInt64()
+			case "fcp_input_requests":
+				counters.FcpInputRequests = *vp.PUInt64()
+			case "fcp_output_megabytes":
+				counters.FcpOutputMegabytes = *vp.PUInt64()
+			case "fcp_output_requests":
+				counters.FcpOutputRequests = *vp.PUInt64()
+		*/
+		case "fcp_packet_aborts":
+			counters.FCPPacketAborts = *vp.PUInt64()
+			/*
+				case "fcp_packet_alloc_failures":
+					counters.FcpPacketAllocFailures = *vp.PUInt64()
+			*/
+		case "invalid_tx_word_count":
+			counters.InvalidTXWordCount = *vp.PUInt64()
+		case "invalid_crc_count":
+			counters.InvalidCRCCount = *vp.PUInt64()
+		case "link_failure_count":
+			counters.LinkFailureCount = *vp.PUInt64()
+		/*
+			case "lip_count":
+					counters.LipCount = *vp.PUInt64()
+		*/
+		case "loss_of_signal_count":
+			counters.LossOfSignalCount = *vp.PUInt64()
+		case "loss_of_sync_count":
+			counters.LossOfSyncCount = *vp.PUInt64()
+		case "nos_count":
+			counters.NosCount = *vp.PUInt64()
+		/*
+			case "prim_seq_protocol_err_count":
+				counters.PrimSeqProtocolErrCount = *vp.PUInt64()
+		*/
+		case "rx_frames":
+			counters.RXFrames = *vp.PUInt64()
+		case "rx_words":
+			counters.RXWords = *vp.PUInt64()
+		case "seconds_since_last_reset":
+			counters.SecondsSinceLastReset = *vp.PUInt64()
+		case "tx_frames":
+			counters.TXFrames = *vp.PUInt64()
+		case "tx_words":
+			counters.TXWords = *vp.PUInt64()
+		}
+
+		if err := vp.Err(); err != nil {
+			return nil, err
+		}
+
+	}
+
+	return &counters, nil
+}


Test patch:
diff --git a/sysfs/class_fibrechannel_test.go b/sysfs/class_fibrechannel_test.go
new file mode 100644
index 0000000..09ddc8d
--- /dev/null
+++ b/sysfs/class_fibrechannel_test.go
@@ -0,0 +1,71 @@
+// Copyright 2019 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.
+
+// +build !windows
+
+package sysfs
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestFibreChannelClass(t *testing.T) {
+	fs, err := NewFS(sysTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	got, err := fs.FibreChannelClass()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	want := FibreChannelClass{
+		"host0": FibreChannelHost{
+			Name:             "host0",
+			Speed:            "16 Gbit",
+			PortState:        "Online",
+			PortType:         "Point-To-Point (direct nport connection)",
+			PortName:         "1000e0071bce95f2",
+			SymbolicName:     "Emulex SN1100E2P FV12.4.270.3 DV12.4.0.0. HN:gotest. OS:Linux",
+			NodeName:         "2000e0071bce95f2",
+			PortID:           "000002",
+			FabricName:       "0",
+			DevLossTMO:       "30",
+			SupportedClasses: "Class 3",
+			SupportedSpeeds:  "4 Gbit, 8 Gbit, 16 Gbit",
+			Counters: FibreChannelCounters{
+				DumpedFrames:          ^uint64(0),
+				ErrorFrames:           0,
+				InvalidCRCCount:       0x2,
+				RXFrames:              0x3,
+				RXWords:               0x4,
+				TXFrames:              0x5,
+				TXWords:               0x6,
+				SecondsSinceLastReset: 0x7,
+				InvalidTXWordCount:    0x8,
+				LinkFailureCount:      0x9,
+				LossOfSyncCount:       0x10,
+				LossOfSignalCount:     0x11,
+				NosCount:              0x12,
+				FCPPacketAborts:       0x13,
+			},
+		},
+	}
+
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Fatalf("unexpected FibreChannel class (-want +got):\n%s", diff)
+	}
+}