// Copyright 2021 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. //go:build linux && !nofibrechannel // +build linux,!nofibrechannel package collector import ( "fmt" "os" "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/procfs/sysfs" ) const maxUint64 = ^uint64(0) type fibrechannelCollector struct { fs sysfs.FS metricDescs map[string]*prometheus.Desc logger log.Logger subsystem string } func init() { registerCollector("fibrechannel", defaultEnabled, NewFibreChannelCollector) } // NewFibreChannelCollector returns a new Collector exposing FibreChannel stats. func NewFibreChannelCollector(logger log.Logger) (Collector, error) { var i fibrechannelCollector var err error i.fs, err = sysfs.NewFS(*sysPath) if err != nil { return nil, fmt.Errorf("failed to open sysfs: %w", err) } i.logger = logger // Detailed description for all metrics. descriptions := map[string]string{ "dumped_frames_total": "Number of dumped frames", "loss_of_signal_total": "Number of times signal has been lost", "loss_of_sync_total": "Number of failures on either bit or transmission word boundaries", "rx_frames_total": "Number of frames received", "error_frames_total": "Number of errors in frames", "invalid_tx_words_total": "Number of invalid words transmitted by host port", "seconds_since_last_reset_total": "Number of seconds since last host port reset", "tx_words_total": "Number of words transmitted by host port", "invalid_crc_total": "Invalid Cyclic Redundancy Check count", "nos_total": "Number Not_Operational Primitive Sequence received by host port", "fcp_packet_aborts_total": "Number of aborted packets", "rx_words_total": "Number of words received by host port", "tx_frames_total": "Number of frames transmitted by host port", "link_failure_total": "Number of times the host port link has failed", "name": "Name of Fibre Channel HBA", "speed": "Current operating speed", "port_state": "Current port state", "port_type": "Port type, what the port is connected to", "symbolic_name": "Symbolic Name", "node_name": "Node Name as hexadecimal string", "port_id": "Port ID as string", "port_name": "Port Name as hexadecimal string", "fabric_name": "Fabric Name; 0 if PTP", "dev_loss_tmo": "Device Loss Timeout in seconds", "supported_classes": "The FC classes supported", "supported_speeds": "The FC speeds supported", } i.metricDescs = make(map[string]*prometheus.Desc) i.subsystem = "fibrechannel" for metricName, description := range descriptions { i.metricDescs[metricName] = prometheus.NewDesc( prometheus.BuildFQName(namespace, i.subsystem, metricName), description, []string{"fc_host"}, nil, ) } return &i, nil } func (c *fibrechannelCollector) pushMetric(ch chan<- prometheus.Metric, name string, value uint64, host string, valueType prometheus.ValueType) { ch <- prometheus.MustNewConstMetric(c.metricDescs[name], valueType, float64(value), host) } func (c *fibrechannelCollector) pushCounter(ch chan<- prometheus.Metric, name string, value uint64, host string) { // Don't push counters that aren't implemented (a counter equal to maxUint64 is unimplemented by the HBA firmware) if value != maxUint64 { c.pushMetric(ch, name, value, host, prometheus.CounterValue) } } func (c *fibrechannelCollector) Update(ch chan<- prometheus.Metric) error { hosts, err := c.fs.FibreChannelClass() if err != nil { if os.IsNotExist(err) { level.Debug(c.logger).Log("msg", "fibrechannel statistics not found, skipping") return ErrNoData } return fmt.Errorf("error obtaining FibreChannel class info: %s", err) } for _, host := range hosts { infoDesc := prometheus.NewDesc( prometheus.BuildFQName(namespace, c.subsystem, "info"), "Non-numeric data from /sys/class/fc_host/, value is always 1.", []string{"fc_host", "speed", "port_state", "port_type", "port_id", "port_name", "fabric_name", "symbolic_name", "supported_classes", "supported_speeds", "dev_loss_tmo"}, nil, ) infoValue := 1.0 // First push the Host values ch <- prometheus.MustNewConstMetric(infoDesc, prometheus.GaugeValue, infoValue, host.Name, host.Speed, host.PortState, host.PortType, host.PortID, host.PortName, host.FabricName, host.SymbolicName, host.SupportedClasses, host.SupportedSpeeds, host.DevLossTMO) // Then the counters c.pushCounter(ch, "dumped_frames_total", host.Counters.DumpedFrames, host.Name) c.pushCounter(ch, "error_frames_total", host.Counters.ErrorFrames, host.Name) c.pushCounter(ch, "invalid_crc_total", host.Counters.InvalidCRCCount, host.Name) c.pushCounter(ch, "rx_frames_total", host.Counters.RXFrames, host.Name) c.pushCounter(ch, "rx_words_total", host.Counters.RXWords, host.Name) c.pushCounter(ch, "tx_frames_total", host.Counters.TXFrames, host.Name) c.pushCounter(ch, "tx_words_total", host.Counters.TXWords, host.Name) c.pushCounter(ch, "seconds_since_last_reset_total", host.Counters.SecondsSinceLastReset, host.Name) c.pushCounter(ch, "invalid_tx_words_total", host.Counters.InvalidTXWordCount, host.Name) c.pushCounter(ch, "link_failure_total", host.Counters.LinkFailureCount, host.Name) c.pushCounter(ch, "loss_of_sync_total", host.Counters.LossOfSyncCount, host.Name) c.pushCounter(ch, "loss_of_signal_total", host.Counters.LossOfSignalCount, host.Name) c.pushCounter(ch, "nos_total", host.Counters.NosCount, host.Name) c.pushCounter(ch, "fcp_packet_aborts_total", host.Counters.FCPPacketAborts, host.Name) } return nil }