Skip to content

Commit 63e6b2c

Browse files
authored
Merge pull request #6 from beclab/feat/lsblk
feat: add lsblk info collector
2 parents 3a05ee0 + 407fdc1 commit 63e6b2c

3 files changed

Lines changed: 114 additions & 1 deletion

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ RUN go build -ldflags="-s -w" -a -o node_exporter node_exporter.go
1010

1111

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

1616
EXPOSE 9100

collector/lsblk_linux.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//go:build !nolsblk
2+
// +build !nolsblk
3+
4+
package collector
5+
6+
import (
7+
"encoding/json"
8+
"fmt"
9+
"log/slog"
10+
"os"
11+
12+
"github.com/alecthomas/kingpin/v2"
13+
"github.com/prometheus/client_golang/prometheus"
14+
)
15+
16+
const lsblkDiskSubsystem = "disk"
17+
18+
var lsblkPath = kingpin.Flag(
19+
"collector.lsblk.path",
20+
"Path to the lsblk binary.",
21+
).Default("/bin/lsblk").String()
22+
23+
var lsblkTimeout = kingpin.Flag(
24+
"collector.lsblk.timeout",
25+
"Timeout for running lsblk.",
26+
).Default("5s").Duration()
27+
28+
type lsblkCollector struct {
29+
logger *slog.Logger
30+
infoDesc *prometheus.Desc
31+
}
32+
33+
func init() {
34+
registerCollector("lsblk", defaultEnabled, NewLsblkCollector)
35+
}
36+
37+
// NewLsblkCollector returns a collector that exposes lsblk(8) JSON output as metrics.
38+
func NewLsblkCollector(logger *slog.Logger) (Collector, error) {
39+
return &lsblkCollector{
40+
logger: logger,
41+
infoDesc: prometheus.NewDesc(
42+
prometheus.BuildFQName(namespace, lsblkDiskSubsystem, "lsblk_info"),
43+
"Non-numeric block device fields from lsblk --json, value is always 1.",
44+
[]string{"name", "parent", "fstype", "mountpoint", "size", "fsused", "fsuse_percent"},
45+
nil,
46+
),
47+
}, nil
48+
}
49+
50+
type lsblkJSONRoot struct {
51+
Blockdevices []lsblkJSONDevice `json:"blockdevices"`
52+
}
53+
54+
type lsblkJSONDevice struct {
55+
Name string `json:"name"`
56+
Size *string `json:"size"`
57+
Fstype *string `json:"fstype"`
58+
Mountpoint *string `json:"mountpoint"`
59+
Fsused *string `json:"fsused"`
60+
FsusePct *string `json:"fsuse%"`
61+
Children []lsblkJSONDevice `json:"children"`
62+
}
63+
64+
func lsblkStringPtr(s *string) string {
65+
if s == nil {
66+
return ""
67+
}
68+
return *s
69+
}
70+
71+
func (c *lsblkCollector) emitRecursive(ch chan<- prometheus.Metric, devices []lsblkJSONDevice, parent string) {
72+
for _, d := range devices {
73+
ch <- prometheus.MustNewConstMetric(c.infoDesc, prometheus.GaugeValue, 1,
74+
d.Name,
75+
parent,
76+
lsblkStringPtr(d.Fstype),
77+
lsblkStringPtr(d.Mountpoint),
78+
lsblkStringPtr(d.Size),
79+
lsblkStringPtr(d.Fsused),
80+
lsblkStringPtr(d.FsusePct),
81+
)
82+
if len(d.Children) > 0 {
83+
c.emitRecursive(ch, d.Children, d.Name)
84+
}
85+
}
86+
}
87+
88+
func (c *lsblkCollector) Update(ch chan<- prometheus.Metric) error {
89+
path := *lsblkPath
90+
if path == "" {
91+
return fmt.Errorf("collector.lsblk.path is empty")
92+
}
93+
if _, err := os.Stat(path); err != nil {
94+
c.logger.Debug("lsblk binary not available", "path", path, "err", err)
95+
return ErrNoData
96+
}
97+
98+
args := []string{"--json", "-o", "NAME,SIZE,FSTYPE,MOUNTPOINT,FSUSED,FSUSE%"}
99+
cmd := execCommand(path, args...)
100+
out, err := CombinedOutputTimeout(cmd, *lsblkTimeout)
101+
if err != nil {
102+
return fmt.Errorf("lsblk failed: %w", err)
103+
}
104+
105+
var root lsblkJSONRoot
106+
if err := json.Unmarshal(out, &root); err != nil {
107+
return fmt.Errorf("lsblk json parse error: %w", err)
108+
}
109+
110+
c.emitRecursive(ch, root.Blockdevices, "")
111+
return nil
112+
}

end-to-end-test.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ supported_enabled_collectors=$(supported_collectors "${enabled_collectors}")
9494
disabled_collectors=$(cat << COLLECTORS
9595
selinux
9696
filesystem
97+
lsblk
9798
timex
9899
uname
99100
COLLECTORS

0 commit comments

Comments
 (0)