Skip to content

Commit 2fe94e4

Browse files
committed
noot
1 parent 3c0da71 commit 2fe94e4

File tree

8 files changed

+109
-14
lines changed

8 files changed

+109
-14
lines changed

cmd/volmetd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func main() {
6666
capacity := collector.NewCapacityCollector()
6767

6868
// Create and register volume collector
69-
vc := collector.NewVolumeCollector(multi, diskstats, capacity)
69+
vc := collector.NewVolumeCollector(multi, cfg.HostProcPath, diskstats, capacity)
7070
prometheus.MustRegister(vc)
7171

7272
// HTTP server

pkg/collector/collector.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/prometheus/client_golang/prometheus"
1010

1111
"github.com/gfx-labs/volmetd/pkg/discovery"
12+
"github.com/gfx-labs/volmetd/pkg/diskstats"
1213
)
1314

1415
// Collector collects metrics for discovered volumes
@@ -41,13 +42,18 @@ var (
4142
type VolumeCollector struct {
4243
discoverer *discovery.MultiDiscoverer
4344
collectors []Collector
45+
procPath string
4446
}
4547

4648
// NewVolumeCollector creates a new volume collector
47-
func NewVolumeCollector(discoverer *discovery.MultiDiscoverer, collectors ...Collector) *VolumeCollector {
49+
func NewVolumeCollector(discoverer *discovery.MultiDiscoverer, procPath string, collectors ...Collector) *VolumeCollector {
50+
if procPath == "" {
51+
procPath = "/proc"
52+
}
4853
return &VolumeCollector{
4954
discoverer: discoverer,
5055
collectors: collectors,
56+
procPath: procPath,
5157
}
5258
}
5359

@@ -76,6 +82,9 @@ func (v *VolumeCollector) Collect(ch chan<- prometheus.Metric) {
7682
ch <- prometheus.MustNewConstMetric(scrapeSuccessDesc, prometheus.GaugeValue, 1, "discovery")
7783
ch <- prometheus.MustNewConstMetric(volumesDiscoveredDesc, prometheus.GaugeValue, float64(len(volumes)))
7884

85+
// Resolve device names from diskstats before running collectors
86+
v.resolveDeviceNames(volumes)
87+
7988
// Run collectors in parallel
8089
wg := sync.WaitGroup{}
8190
wg.Add(len(v.collectors))
@@ -104,3 +113,21 @@ func (v *VolumeCollector) execute(c Collector, volumes []*discovery.VolumeInfo,
104113
}
105114
ch <- prometheus.MustNewConstMetric(scrapeSuccessDesc, prometheus.GaugeValue, 1, c.Name())
106115
}
116+
117+
// resolveDeviceNames resolves device names from diskstats using device IDs
118+
func (v *VolumeCollector) resolveDeviceNames(volumes []*discovery.VolumeInfo) {
119+
stats, err := diskstats.Parse(v.procPath + "/diskstats")
120+
if err != nil {
121+
log.Printf("failed to parse diskstats for device name resolution: %v", err)
122+
return
123+
}
124+
125+
for _, vol := range volumes {
126+
// Try to resolve device name from device ID
127+
if vol.DeviceID != "" {
128+
if s, ok := stats.ByDeviceID[vol.DeviceID]; ok {
129+
vol.DeviceName = s.DeviceName
130+
}
131+
}
132+
}
133+
}

pkg/collector/diskstats.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,16 @@ func (d *DiskstatsCollector) Update(volumes []*discovery.VolumeInfo, ch chan<- p
7676

7777
wg := sync.WaitGroup{}
7878
for _, vol := range volumes {
79-
s, ok := stats[vol.DeviceName]
79+
// Device name should already be resolved by VolumeCollector
80+
if vol.DeviceName == "" {
81+
continue
82+
}
83+
84+
s, ok := stats.ByName[vol.DeviceName]
8085
if !ok {
8186
continue
8287
}
88+
8389
wg.Add(1)
8490
go func(vol *discovery.VolumeInfo, s *diskstats.Stats) {
8591
defer wg.Done()

pkg/discovery/csi.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ func (d *CSIDiscoverer) discoverCSIVolumes(ctx context.Context, podUID, csiDir s
115115
// Resolve symlinks to get actual device for diskstats
116116
resolvedPath, deviceName := mounts.ResolveDevice(mount.Device)
117117

118+
// Get device ID from mount point for reliable diskstats lookup
119+
deviceID, _ := mounts.GetDeviceID(mountPath)
120+
118121
vol := &VolumeInfo{
119122
PVName: volData.VolumeName,
120123
PVCName: extractPVCName(volData.VolumeName),
@@ -127,6 +130,7 @@ func (d *CSIDiscoverer) discoverCSIVolumes(ctx context.Context, podUID, csiDir s
127130
CSIDevicePath: mount.Device,
128131
DevicePath: resolvedPath,
129132
DeviceName: deviceName,
133+
DeviceID: deviceID,
130134
MountPath: mountPath,
131135
}
132136

pkg/discovery/k8sapi.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package discovery
33
import (
44
"context"
55
"fmt"
6+
"log"
67
"os"
78
"path/filepath"
89
"strings"
@@ -43,6 +44,7 @@ func NewK8sAPIDiscoverer(kubeletPath, mountsPath string, namespaces []string) (*
4344
}
4445

4546
nodeName := detectNodeName()
47+
log.Printf("k8sapi: detected node name: %q", nodeName)
4648

4749
if kubeletPath == "" {
4850
kubeletPath = "/var/lib/kubelet"
@@ -93,12 +95,21 @@ func (d *K8sAPIDiscoverer) Name() string {
9395
}
9496

9597
func (d *K8sAPIDiscoverer) Available(ctx context.Context) bool {
96-
if d.client == nil || d.nodeName == "" {
98+
if d.client == nil {
99+
log.Printf("k8sapi: client is nil")
100+
return false
101+
}
102+
if d.nodeName == "" {
103+
log.Printf("k8sapi: node name not detected")
97104
return false
98105
}
99106
// Quick check that we can talk to the API
100107
_, err := d.client.CoreV1().Nodes().Get(ctx, d.nodeName, metav1.GetOptions{})
101-
return err == nil
108+
if err != nil {
109+
log.Printf("k8sapi: cannot get node %s: %v", d.nodeName, err)
110+
return false
111+
}
112+
return true
102113
}
103114

104115
func (d *K8sAPIDiscoverer) Discover(ctx context.Context) ([]*VolumeInfo, error) {
@@ -167,6 +178,9 @@ func (d *K8sAPIDiscoverer) Discover(ctx context.Context) ([]*VolumeInfo, error)
167178
// Resolve symlinks to get actual device for diskstats
168179
resolvedPath, deviceName := mounts.ResolveDevice(mount.Device)
169180

181+
// Get device ID from mount point for reliable diskstats lookup
182+
deviceID, _ := mounts.GetDeviceID(mountPath)
183+
170184
// Find container mount path
171185
containerMountPath := findContainerMountPath(&pod, vol.Name)
172186

@@ -182,6 +196,7 @@ func (d *K8sAPIDiscoverer) Discover(ctx context.Context) ([]*VolumeInfo, error)
182196
CSIDevicePath: mount.Device,
183197
DevicePath: resolvedPath,
184198
DeviceName: deviceName,
199+
DeviceID: deviceID,
185200
MountPath: mountPath,
186201
ContainerMountPath: containerMountPath,
187202
}

pkg/discovery/types.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package discovery
22

3-
import "context"
3+
import (
4+
"context"
5+
"log"
6+
)
47

58
// VolumeInfo represents a discovered PVC volume
69
type VolumeInfo struct {
@@ -22,6 +25,7 @@ type VolumeInfo struct {
2225
// Node-local info
2326
DevicePath string // resolved device path, e.g., /dev/sda
2427
DeviceName string // device name for diskstats, e.g., sda
28+
DeviceID string // major:minor device ID for diskstats lookup, e.g., "8:0"
2529
CSIDevicePath string // original CSI device path, e.g., /dev/disk/by-id/scsi-0DO_Volume_...
2630
MountPath string // host path, e.g., /var/lib/kubelet/pods/.../volumes/...
2731
ContainerMountPath string // path inside container, e.g., /data
@@ -51,28 +55,37 @@ func NewMultiDiscoverer(discoverers ...Discoverer) *MultiDiscoverer {
5155

5256
// Discover tries all discoverers and returns merged results
5357
func (m *MultiDiscoverer) Discover(ctx context.Context) ([]*VolumeInfo, error) {
54-
seen := make(map[string]*VolumeInfo) // key by device name
58+
seen := make(map[string]*VolumeInfo) // key by device ID (preferred) or device name
5559

5660
for _, d := range m.discoverers {
5761
if !d.Available(ctx) {
62+
log.Printf("discoverer %s not available", d.Name())
5863
continue
5964
}
6065

6166
volumes, err := d.Discover(ctx)
6267
if err != nil {
63-
// Log but continue with other discoverers
68+
log.Printf("discoverer %s error: %v", d.Name(), err)
6469
continue
6570
}
6671

72+
log.Printf("discoverer %s found %d volumes", d.Name(), len(volumes))
73+
6774
for _, v := range volumes {
68-
if v.DeviceName == "" {
75+
// Use device ID as key if available, otherwise device name
76+
key := v.DeviceID
77+
if key == "" {
78+
key = v.DeviceName
79+
}
80+
if key == "" {
6981
continue
7082
}
71-
if existing, exists := seen[v.DeviceName]; exists {
83+
84+
if existing, exists := seen[key]; exists {
7285
// Merge: fill in empty fields from new discoverer
7386
mergeVolumeInfo(existing, v)
7487
} else {
75-
seen[v.DeviceName] = v
88+
seen[key] = v
7689
}
7790
}
7891
}
@@ -119,6 +132,9 @@ func mergeVolumeInfo(dst, src *VolumeInfo) {
119132
if dst.DevicePath == "" {
120133
dst.DevicePath = src.DevicePath
121134
}
135+
if dst.DeviceID == "" {
136+
dst.DeviceID = src.DeviceID
137+
}
122138
if dst.CSIDevicePath == "" {
123139
dst.CSIDevicePath = src.CSIDevicePath
124140
}

pkg/diskstats/diskstats.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ func (s *Stats) WriteBytesTotal() uint64 {
5353
return s.SectorsWritten * 512
5454
}
5555

56+
// StatsMap holds diskstats indexed by both device name and major:minor
57+
type StatsMap struct {
58+
ByName map[string]*Stats // keyed by device name (e.g., "sda")
59+
ByDeviceID map[string]*Stats // keyed by "major:minor" (e.g., "8:0")
60+
}
61+
5662
// Parse reads /proc/diskstats and returns stats for all devices
57-
func Parse(path string) (map[string]*Stats, error) {
63+
func Parse(path string) (*StatsMap, error) {
5864
if path == "" {
5965
path = "/proc/diskstats"
6066
}
@@ -65,15 +71,20 @@ func Parse(path string) (map[string]*Stats, error) {
6571
}
6672
defer f.Close()
6773

68-
result := make(map[string]*Stats)
74+
result := &StatsMap{
75+
ByName: make(map[string]*Stats),
76+
ByDeviceID: make(map[string]*Stats),
77+
}
6978
scanner := bufio.NewScanner(f)
7079

7180
for scanner.Scan() {
7281
stats, err := parseLine(scanner.Text())
7382
if err != nil {
7483
continue // skip malformed lines
7584
}
76-
result[stats.DeviceName] = stats
85+
result.ByName[stats.DeviceName] = stats
86+
deviceID := fmt.Sprintf("%d:%d", stats.Major, stats.Minor)
87+
result.ByDeviceID[deviceID] = stats
7788
}
7889

7990
if err := scanner.Err(); err != nil {

pkg/mounts/mounts.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,22 @@ func ResolveDevice(devicePath string) (resolvedPath, deviceName string) {
111111
return resolved, name
112112
}
113113

114+
// GetDeviceID returns the major:minor device ID for a mount point
115+
// This works by stat'ing the mount point and extracting the device ID
116+
func GetDeviceID(mountPoint string) (string, error) {
117+
var stat syscall.Stat_t
118+
if err := syscall.Stat(mountPoint, &stat); err != nil {
119+
return "", fmt.Errorf("stat %s: %w", mountPoint, err)
120+
}
121+
122+
// Dev contains major:minor encoded
123+
// On Linux: major = (dev >> 8) & 0xfff, minor = (dev & 0xff) | ((dev >> 12) & 0xfff00)
124+
major := (stat.Dev >> 8) & 0xfff
125+
minor := (stat.Dev & 0xff) | ((stat.Dev >> 12) & 0xfff00)
126+
127+
return fmt.Sprintf("%d:%d", major, minor), nil
128+
}
129+
114130
// evalSymlinks resolves all symlinks in a path
115131
func evalSymlinks(path string) (string, error) {
116132
// Use filepath.EvalSymlinks equivalent

0 commit comments

Comments
 (0)