Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ RUN go build -ldflags="-s -w" -a -o node_exporter node_exporter.go


FROM alpine:3.21
RUN apk add smartmontools pciutils nvme-cli
RUN apk add smartmontools pciutils nvme-cli util-linux
COPY --from=builder /workspace/node_exporter /bin/node_exporter

EXPOSE 9100
Expand Down
112 changes: 112 additions & 0 deletions collector/lsblk_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//go:build !nolsblk
// +build !nolsblk

package collector

import (
"encoding/json"
"fmt"
"log/slog"
"os"

"github.com/alecthomas/kingpin/v2"
"github.com/prometheus/client_golang/prometheus"
)

const lsblkDiskSubsystem = "disk"

var lsblkPath = kingpin.Flag(
"collector.lsblk.path",
"Path to the lsblk binary.",
).Default("/bin/lsblk").String()

var lsblkTimeout = kingpin.Flag(
"collector.lsblk.timeout",
"Timeout for running lsblk.",
).Default("5s").Duration()

type lsblkCollector struct {
logger *slog.Logger
infoDesc *prometheus.Desc
}

func init() {
registerCollector("lsblk", defaultEnabled, NewLsblkCollector)
}

// NewLsblkCollector returns a collector that exposes lsblk(8) JSON output as metrics.
func NewLsblkCollector(logger *slog.Logger) (Collector, error) {
return &lsblkCollector{
logger: logger,
infoDesc: prometheus.NewDesc(
prometheus.BuildFQName(namespace, lsblkDiskSubsystem, "lsblk_info"),
"Non-numeric block device fields from lsblk --json, value is always 1.",
[]string{"name", "parent", "fstype", "mountpoint", "size", "fsused", "fsuse_percent"},
nil,
),
}, nil
}

type lsblkJSONRoot struct {
Blockdevices []lsblkJSONDevice `json:"blockdevices"`
}

type lsblkJSONDevice struct {
Name string `json:"name"`
Size *string `json:"size"`
Fstype *string `json:"fstype"`
Mountpoint *string `json:"mountpoint"`
Fsused *string `json:"fsused"`
FsusePct *string `json:"fsuse%"`
Children []lsblkJSONDevice `json:"children"`
}

func lsblkStringPtr(s *string) string {
if s == nil {
return ""
}
return *s
}

func (c *lsblkCollector) emitRecursive(ch chan<- prometheus.Metric, devices []lsblkJSONDevice, parent string) {
for _, d := range devices {
ch <- prometheus.MustNewConstMetric(c.infoDesc, prometheus.GaugeValue, 1,
d.Name,
parent,
lsblkStringPtr(d.Fstype),
lsblkStringPtr(d.Mountpoint),
lsblkStringPtr(d.Size),
lsblkStringPtr(d.Fsused),
lsblkStringPtr(d.FsusePct),
)
if len(d.Children) > 0 {
c.emitRecursive(ch, d.Children, d.Name)
}
}
}

func (c *lsblkCollector) Update(ch chan<- prometheus.Metric) error {
path := *lsblkPath
if path == "" {
return fmt.Errorf("collector.lsblk.path is empty")
}
if _, err := os.Stat(path); err != nil {
c.logger.Debug("lsblk binary not available", "path", path, "err", err)
return ErrNoData
}

args := []string{"--json", "-o", "NAME,SIZE,FSTYPE,MOUNTPOINT,FSUSED,FSUSE%"}
cmd := execCommand(path, args...)
out, err := CombinedOutputTimeout(cmd, *lsblkTimeout)
if err != nil {
return fmt.Errorf("lsblk failed: %w", err)
}

var root lsblkJSONRoot
if err := json.Unmarshal(out, &root); err != nil {
return fmt.Errorf("lsblk json parse error: %w", err)
}

c.emitRecursive(ch, root.Blockdevices, "")
return nil
}
1 change: 1 addition & 0 deletions end-to-end-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ supported_enabled_collectors=$(supported_collectors "${enabled_collectors}")
disabled_collectors=$(cat << COLLECTORS
selinux
filesystem
lsblk
timex
uname
COLLECTORS
Expand Down
Loading