Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion collector/fixtures/e2e-64k-page-output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2825,7 +2825,22 @@ node_nfsd_server_rpcs_total 18628
node_nfsd_server_threads 8
# HELP node_nvme_info Non-numeric data from /sys/class/nvme/<device>, value is always 1.
# TYPE node_nvme_info gauge
node_nvme_info{device="nvme0",firmware_revision="1B2QEXP7",model="Samsung SSD 970 PRO 512GB",serial="S680HF8N190894I",state="live"} 1
node_nvme_info{cntlid="1997",device="nvme0",firmware_revision="1B2QEXP7",model="Samsung SSD 970 PRO 512GB",serial="S680HF8N190894I",state="live"} 1
# HELP node_nvme_namespace_capacity_bytes Capacity of the NVMe namespace in bytes. Computed as namespace_size * namespace_logical_block_size
# TYPE node_nvme_namespace_capacity_bytes gauge
node_nvme_namespace_capacity_bytes{device="nvme0",nsid="0"} 1.6e+13
# HELP node_nvme_namespace_info Information about NVMe namespaces. Value is always 1
# TYPE node_nvme_namespace_info gauge
node_nvme_namespace_info{ana_state="optimized",device="nvme0",nsid="0"} 1
# HELP node_nvme_namespace_logical_block_size_bytes Logical block size of the NVMe namespace in bytes. Usually 4Kb. Available in /sys/class/nvme/<device>/<namespace>/queue/logical_block_size
# TYPE node_nvme_namespace_logical_block_size_bytes gauge
node_nvme_namespace_logical_block_size_bytes{device="nvme0",nsid="0"} 4096
# HELP node_nvme_namespace_size_bytes Size of the NVMe namespace in bytes. Available in /sys/class/nvme/<device>/<namespace>/size
# TYPE node_nvme_namespace_size_bytes gauge
node_nvme_namespace_size_bytes{device="nvme0",nsid="0"} 3.90625e+09
# HELP node_nvme_namespace_used_bytes Used space of the NVMe namespace in bytes. Available in /sys/class/nvme/<device>/<namespace>/nuse
# TYPE node_nvme_namespace_used_bytes gauge
node_nvme_namespace_used_bytes{device="nvme0",nsid="0"} 2e+12
# HELP node_os_info A metric with a constant '1' value labeled by build_id, id, id_like, image_id, image_version, name, pretty_name, variant, variant_id, version, version_codename, version_id.
# TYPE node_os_info gauge
node_os_info{build_id="",id="ubuntu",id_like="debian",image_id="",image_version="",name="Ubuntu",pretty_name="Ubuntu 20.04.2 LTS",variant="",variant_id="",version="20.04.2 LTS (Focal Fossa)",version_codename="focal",version_id="20.04"} 1
Expand Down
17 changes: 16 additions & 1 deletion collector/fixtures/e2e-output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2847,7 +2847,22 @@ node_nfsd_server_rpcs_total 18628
node_nfsd_server_threads 8
# HELP node_nvme_info Non-numeric data from /sys/class/nvme/<device>, value is always 1.
# TYPE node_nvme_info gauge
node_nvme_info{device="nvme0",firmware_revision="1B2QEXP7",model="Samsung SSD 970 PRO 512GB",serial="S680HF8N190894I",state="live"} 1
node_nvme_info{cntlid="1997",device="nvme0",firmware_revision="1B2QEXP7",model="Samsung SSD 970 PRO 512GB",serial="S680HF8N190894I",state="live"} 1
# HELP node_nvme_namespace_capacity_bytes Capacity of the NVMe namespace in bytes. Computed as namespace_size * namespace_logical_block_size
# TYPE node_nvme_namespace_capacity_bytes gauge
node_nvme_namespace_capacity_bytes{device="nvme0",nsid="0"} 1.6e+13
# HELP node_nvme_namespace_info Information about NVMe namespaces. Value is always 1
# TYPE node_nvme_namespace_info gauge
node_nvme_namespace_info{ana_state="optimized",device="nvme0",nsid="0"} 1
# HELP node_nvme_namespace_logical_block_size_bytes Logical block size of the NVMe namespace in bytes. Usually 4Kb. Available in /sys/class/nvme/<device>/<namespace>/queue/logical_block_size
# TYPE node_nvme_namespace_logical_block_size_bytes gauge
node_nvme_namespace_logical_block_size_bytes{device="nvme0",nsid="0"} 4096
# HELP node_nvme_namespace_size_bytes Size of the NVMe namespace in bytes. Available in /sys/class/nvme/<device>/<namespace>/size
# TYPE node_nvme_namespace_size_bytes gauge
node_nvme_namespace_size_bytes{device="nvme0",nsid="0"} 3.90625e+09
# HELP node_nvme_namespace_used_bytes Used space of the NVMe namespace in bytes. Available in /sys/class/nvme/<device>/<namespace>/nuse
# TYPE node_nvme_namespace_used_bytes gauge
node_nvme_namespace_used_bytes{device="nvme0",nsid="0"} 2e+12
# HELP node_os_info A metric with a constant '1' value labeled by build_id, id, id_like, image_id, image_version, name, pretty_name, variant, variant_id, version, version_codename, version_id.
# TYPE node_os_info gauge
node_os_info{build_id="",id="ubuntu",id_like="debian",image_id="",image_version="",name="Ubuntu",pretty_name="Ubuntu 20.04.2 LTS",variant="",variant_id="",version="20.04.2 LTS (Focal Fossa)",version_codename="focal",version_id="20.04"} 1
Expand Down
26 changes: 26 additions & 0 deletions collector/fixtures/sys.ttar
Original file line number Diff line number Diff line change
Expand Up @@ -2211,6 +2211,32 @@ Lines: 1
live
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: sys/class/nvme/nvme0/nvme0c0n0
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: sys/class/nvme/nvme0/nvme0c0n0/ana_state
Lines: 1
optimized
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: sys/class/nvme/nvme0/nvme0c0n0/size
Lines: 1
3906250000
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: sys/class/nvme/nvme0/nvme0c0n0/nuse
Lines: 1
488281250
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: sys/class/nvme/nvme0/nvme0c0n0/queue
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: sys/class/nvme/nvme0/nvme0c0n0/queue/logical_block_size
Lines: 1
4096
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: sys/class/power_supply
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Expand Down
147 changes: 136 additions & 11 deletions collector/nvme_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,23 @@ import (
"fmt"
"log/slog"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/procfs/sysfs"
)

type nvmeCollector struct {
fs sysfs.FS
logger *slog.Logger
fs sysfs.FS
logger *slog.Logger
namespaceInfo *prometheus.Desc
namespaceCapacityBytes *prometheus.Desc
namespaceSizeBytes *prometheus.Desc
namespaceUsedBytes *prometheus.Desc
namespaceLogicalBlockSizeBytes *prometheus.Desc
info *prometheus.Desc
}

func init() {
Expand All @@ -42,9 +51,51 @@ func NewNVMeCollector(logger *slog.Logger) (Collector, error) {
return nil, fmt.Errorf("failed to open sysfs: %w", err)
}

info := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "nvme", "info"),
"Non-numeric data from /sys/class/nvme/<device>, value is always 1.",
[]string{"device", "firmware_revision", "model", "serial", "state", "cntlid"},
nil,
)
namespaceInfo := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "nvme", "namespace_info"),
"Information about NVMe namespaces. Value is always 1",
[]string{"device", "nsid", "ana_state"}, nil,
)

namespaceCapacityBytes := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "nvme", "namespace_capacity_bytes"),
"Capacity of the NVMe namespace in bytes. Computed as namespace_size * namespace_logical_block_size",
[]string{"device", "nsid"}, nil,
)

namespaceSizeBytes := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "nvme", "namespace_size_bytes"),
"Size of the NVMe namespace in bytes. Available in /sys/class/nvme/<device>/<namespace>/size",
[]string{"device", "nsid"}, nil,
)

namespaceUsedBytes := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "nvme", "namespace_used_bytes"),
"Used space of the NVMe namespace in bytes. Available in /sys/class/nvme/<device>/<namespace>/nuse",
[]string{"device", "nsid"}, nil,
)

namespaceLogicalBlockSizeBytes := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "nvme", "namespace_logical_block_size_bytes"),
"Logical block size of the NVMe namespace in bytes. Usually 4Kb. Available in /sys/class/nvme/<device>/<namespace>/queue/logical_block_size",
[]string{"device", "nsid"}, nil,
)

return &nvmeCollector{
fs: fs,
logger: logger,
fs: fs,
logger: logger,
namespaceInfo: namespaceInfo,
namespaceCapacityBytes: namespaceCapacityBytes,
namespaceSizeBytes: namespaceSizeBytes,
namespaceUsedBytes: namespaceUsedBytes,
namespaceLogicalBlockSizeBytes: namespaceLogicalBlockSizeBytes,
info: info,
}, nil
}

Expand All @@ -59,14 +110,88 @@ func (c *nvmeCollector) Update(ch chan<- prometheus.Metric) error {
}

for _, device := range devices {
infoDesc := prometheus.NewDesc(
prometheus.BuildFQName(namespace, "nvme", "info"),
"Non-numeric data from /sys/class/nvme/<device>, value is always 1.",
[]string{"device", "firmware_revision", "model", "serial", "state"},
nil,
)
infoValue := 1.0
ch <- prometheus.MustNewConstMetric(infoDesc, prometheus.GaugeValue, infoValue, device.Name, device.FirmwareRevision, device.Model, device.Serial, device.State)

devicePath := filepath.Join(*sysPath, "class/nvme", device.Name)
ch <- prometheus.MustNewConstMetric(c.info, prometheus.GaugeValue, infoValue, device.Name, device.FirmwareRevision, device.Model, device.Serial, device.State, device.ControllerID)
// Find namespace directories.
namespacePaths, err := filepath.Glob(filepath.Join(devicePath, "nvme[0-9]*c[0-9]*n[0-9]*"))
if err != nil {
c.logger.Error("failed to list NVMe namespaces", "device", device.Name, "err", err)
continue
}
re := regexp.MustCompile(`nvme[0-9]+c[0-9]+n([0-9]+)`)

for _, namespacePath := range namespacePaths {

// Read namespace data.
match := re.FindStringSubmatch(filepath.Base(namespacePath))
if len(match) == 0 {
continue
}
nsid := match[1]
nuse, err := readUintFromFile(filepath.Join(namespacePath, "nuse"))
if err != nil {
c.logger.Debug("failed to read nuse", "device", device.Name, "namespace", match[0], "err", err)
}
nsze, err := readUintFromFile(filepath.Join(namespacePath, "size"))
if err != nil {
c.logger.Debug("failed to read size", "device", device.Name, "namespace", match[0], "err", err)
}
lbaSize, err := readUintFromFile(filepath.Join(namespacePath, "queue", "logical_block_size"))
if err != nil {
c.logger.Debug("failed to read queue/logical_block_size", "device", device.Name, "namespace", match[0], "err", err)
}
ncap := nsze * lbaSize
anaState := "unknown"
anaStateSysfs, err := os.ReadFile(filepath.Join(namespacePath, "ana_state"))
if err == nil {
anaState = strings.TrimSpace(string(anaStateSysfs))
} else {
c.logger.Debug("failed to read ana_state", "device", device.Name, "namespace", match[0], "err", err)
}

ch <- prometheus.MustNewConstMetric(
c.namespaceInfo,
prometheus.GaugeValue,
1.0,
device.Name,
nsid,
anaState,
)

ch <- prometheus.MustNewConstMetric(
c.namespaceCapacityBytes,
prometheus.GaugeValue,
float64(ncap),
device.Name,
nsid,
)

ch <- prometheus.MustNewConstMetric(
c.namespaceSizeBytes,
prometheus.GaugeValue,
float64(nsze),
device.Name,
nsid,
)

ch <- prometheus.MustNewConstMetric(
c.namespaceUsedBytes,
prometheus.GaugeValue,
float64(nuse*lbaSize),
device.Name,
nsid,
)

ch <- prometheus.MustNewConstMetric(
c.namespaceLogicalBlockSizeBytes,
prometheus.GaugeValue,
float64(lbaSize),
device.Name,
nsid,
)
}
}

return nil
Expand Down