From 80b8880585f547a81231628d2d262a91e635ad6c Mon Sep 17 00:00:00 2001 From: mchain0 Date: Thu, 14 May 2026 14:12:41 +0200 Subject: [PATCH 1/7] cre-4340: common diskmonitor --- pkg/diskmonitor/diskmonitor.go | 80 +++++++++++++++++++++++++++++ pkg/diskmonitor/diskmonitor_test.go | 55 ++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 pkg/diskmonitor/diskmonitor.go create mode 100644 pkg/diskmonitor/diskmonitor_test.go diff --git a/pkg/diskmonitor/diskmonitor.go b/pkg/diskmonitor/diskmonitor.go new file mode 100644 index 0000000000..b6b16aca50 --- /dev/null +++ b/pkg/diskmonitor/diskmonitor.go @@ -0,0 +1,80 @@ +package diskmonitor + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "go.opentelemetry.io/otel/metric" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" +) + +type int64Gauge interface { + Record(ctx context.Context, value int64, options ...metric.RecordOption) +} + +// DiskMonitor measures dirPath on a fixed interval and records total file bytes to [GaugeVaultDiskUsageBytes]. +type DiskMonitor struct { + services.Service + + eng *services.Engine + tickInterval time.Duration + lggr logger.Logger + sizeOfDir func() (int64, error) + gauge int64Gauge +} + +// NewDiskMonitor returns a [DiskMonitor] for dirPath using gaugeName at tickInterval. +func NewDiskMonitor(lggr logger.Logger, dirPath string, gaugeName string, tickInterval time.Duration) (*DiskMonitor, error) { + g, err := beholder.GetMeter().Int64Gauge(gaugeName) + if err != nil { + return nil, fmt.Errorf("int64 gauge %q: %w", gaugeName, err) + } + + dm := &DiskMonitor{ + gauge: g, + tickInterval: tickInterval, + lggr: logger.With( + logger.Named(lggr, "DiskMonitor"), + "dirPath", dirPath, + ), + sizeOfDir: func() (int64, error) { + var totalSize int64 + walkErr := filepath.Walk(dirPath, func(_ string, info os.FileInfo, ierr error) error { + if ierr == nil && !info.IsDir() { + totalSize += info.Size() + } + return nil + }) + return totalSize, walkErr + }, + } + + dm.Service, dm.eng = services.Config{ + Name: "DiskMonitor", + Start: dm.start, + }.NewServiceEngine(lggr) + return dm, nil +} + +func (dm *DiskMonitor) start(ctx context.Context) error { + ticker := services.TickerConfig{}.NewTicker(dm.tickInterval) + dm.eng.GoTick(ticker, dm.emitDirSizeMetric) + return nil +} + +func (dm *DiskMonitor) emitDirSizeMetric(ctx context.Context) { + totalSize, err := dm.sizeOfDir() + if err != nil { + dm.lggr.Errorw("Failed to measure vault directory size", "error", err) + return + } + + dm.lggr.Debugw("Emitting vault directory size metric", "sizeBytes", totalSize) + dm.gauge.Record(ctx, totalSize) +} diff --git a/pkg/diskmonitor/diskmonitor_test.go b/pkg/diskmonitor/diskmonitor_test.go new file mode 100644 index 0000000000..8800611b24 --- /dev/null +++ b/pkg/diskmonitor/diskmonitor_test.go @@ -0,0 +1,55 @@ +package diskmonitor + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/metric" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" +) + +type mockGauge struct { + gotValue int64 +} + +func (m *mockGauge) Record(ctx context.Context, value int64, options ...metric.RecordOption) { + m.gotValue = value +} + +func TestDiskMonitor_emitDirSizeMetric(t *testing.T) { + lggr, observed := logger.TestObserved(t, zapcore.DebugLevel) + + dm := &DiskMonitor{ + sizeOfDir: func() (int64, error) { + return 42, nil + }, + gauge: &mockGauge{}, + lggr: lggr, + } + + dm.emitDirSizeMetric(t.Context()) + assert.Equal(t, int64(42), dm.gauge.(*mockGauge).gotValue) + + assert.Len(t, observed.FilterMessage("Emitting vault directory size metric").All(), 1) +} + +func TestDiskMonitor_emitDirSizeMetric_error(t *testing.T) { + lggr, observed := logger.TestObserved(t, zapcore.DebugLevel) + + dm := &DiskMonitor{ + sizeOfDir: func() (int64, error) { + return 0, errors.New("disk read error") + }, + gauge: &mockGauge{}, + lggr: lggr, + } + + dm.emitDirSizeMetric(t.Context()) + assert.Equal(t, int64(0), dm.gauge.(*mockGauge).gotValue) + + assert.Len(t, observed.FilterMessage("Failed to measure vault directory size").All(), 1) +} From d1bbbe33f95f546dc61eba86ac412d1f4557d4ae Mon Sep 17 00:00:00 2001 From: mchain0 Date: Thu, 14 May 2026 14:35:24 +0200 Subject: [PATCH 2/7] cre-4340: minor tests improvement --- pkg/diskmonitor/diskmonitor.go | 1 + pkg/diskmonitor/diskmonitor_test.go | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/pkg/diskmonitor/diskmonitor.go b/pkg/diskmonitor/diskmonitor.go index b6b16aca50..659aca914e 100644 --- a/pkg/diskmonitor/diskmonitor.go +++ b/pkg/diskmonitor/diskmonitor.go @@ -42,6 +42,7 @@ func NewDiskMonitor(lggr logger.Logger, dirPath string, gaugeName string, tickIn lggr: logger.With( logger.Named(lggr, "DiskMonitor"), "dirPath", dirPath, + "gaugeName", gaugeName, ), sizeOfDir: func() (int64, error) { var totalSize int64 diff --git a/pkg/diskmonitor/diskmonitor_test.go b/pkg/diskmonitor/diskmonitor_test.go index 8800611b24..541cbc70d1 100644 --- a/pkg/diskmonitor/diskmonitor_test.go +++ b/pkg/diskmonitor/diskmonitor_test.go @@ -4,14 +4,23 @@ import ( "context" "errors" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/metric" "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink-common/pkg/logger" ) +func TestNewDiskMonitor(t *testing.T) { + dm, err := NewDiskMonitor(logger.Test(t), t.TempDir(), "tmp_disk_usage_bytes", time.Second) + require.NoError(t, err) + assert.NotNil(t, dm) + assert.Equal(t, time.Second, dm.tickInterval) +} + type mockGauge struct { gotValue int64 } From 6640128fa93bc2e70bc1f05da4d63a61dc0a14ce Mon Sep 17 00:00:00 2001 From: mchain0 Date: Thu, 14 May 2026 15:41:21 +0200 Subject: [PATCH 3/7] cre-4340: logger improvement Co-authored-by: Jordan Krage --- pkg/diskmonitor/diskmonitor.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/diskmonitor/diskmonitor.go b/pkg/diskmonitor/diskmonitor.go index 659aca914e..4a1f849138 100644 --- a/pkg/diskmonitor/diskmonitor.go +++ b/pkg/diskmonitor/diskmonitor.go @@ -39,11 +39,6 @@ func NewDiskMonitor(lggr logger.Logger, dirPath string, gaugeName string, tickIn dm := &DiskMonitor{ gauge: g, tickInterval: tickInterval, - lggr: logger.With( - logger.Named(lggr, "DiskMonitor"), - "dirPath", dirPath, - "gaugeName", gaugeName, - ), sizeOfDir: func() (int64, error) { var totalSize int64 walkErr := filepath.Walk(dirPath, func(_ string, info os.FileInfo, ierr error) error { @@ -59,7 +54,11 @@ func NewDiskMonitor(lggr logger.Logger, dirPath string, gaugeName string, tickIn dm.Service, dm.eng = services.Config{ Name: "DiskMonitor", Start: dm.start, - }.NewServiceEngine(lggr) + }.NewServiceEngine(logger.With( + "dirPath", dirPath, + "gaugeName", gaugeName, + )) + dm.lggr = dm.eng.SugaredLogger return dm, nil } From 484c78981c4a79a107aa88c1e07a9983ff7588c2 Mon Sep 17 00:00:00 2001 From: mchain0 Date: Thu, 14 May 2026 15:51:13 +0200 Subject: [PATCH 4/7] cre-4340: naming and logs cleanup --- pkg/diskmonitor/diskmonitor.go | 6 +++--- pkg/diskmonitor/diskmonitor_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/diskmonitor/diskmonitor.go b/pkg/diskmonitor/diskmonitor.go index 4a1f849138..2f506c3cbf 100644 --- a/pkg/diskmonitor/diskmonitor.go +++ b/pkg/diskmonitor/diskmonitor.go @@ -18,7 +18,7 @@ type int64Gauge interface { Record(ctx context.Context, value int64, options ...metric.RecordOption) } -// DiskMonitor measures dirPath on a fixed interval and records total file bytes to [GaugeVaultDiskUsageBytes]. +// DiskMonitor measures dirPath on a fixed interval and records total file bytes to gaugeName. type DiskMonitor struct { services.Service @@ -71,10 +71,10 @@ func (dm *DiskMonitor) start(ctx context.Context) error { func (dm *DiskMonitor) emitDirSizeMetric(ctx context.Context) { totalSize, err := dm.sizeOfDir() if err != nil { - dm.lggr.Errorw("Failed to measure vault directory size", "error", err) + dm.lggr.Errorw("Failed to measure directory size", "error", err) return } - dm.lggr.Debugw("Emitting vault directory size metric", "sizeBytes", totalSize) + dm.lggr.Debugw("Emitting directory size metric", "sizeBytes", totalSize) dm.gauge.Record(ctx, totalSize) } diff --git a/pkg/diskmonitor/diskmonitor_test.go b/pkg/diskmonitor/diskmonitor_test.go index 541cbc70d1..3b4fc05839 100644 --- a/pkg/diskmonitor/diskmonitor_test.go +++ b/pkg/diskmonitor/diskmonitor_test.go @@ -43,7 +43,7 @@ func TestDiskMonitor_emitDirSizeMetric(t *testing.T) { dm.emitDirSizeMetric(t.Context()) assert.Equal(t, int64(42), dm.gauge.(*mockGauge).gotValue) - assert.Len(t, observed.FilterMessage("Emitting vault directory size metric").All(), 1) + assert.Len(t, observed.FilterMessage("Emitting directory size metric").All(), 1) } func TestDiskMonitor_emitDirSizeMetric_error(t *testing.T) { @@ -60,5 +60,5 @@ func TestDiskMonitor_emitDirSizeMetric_error(t *testing.T) { dm.emitDirSizeMetric(t.Context()) assert.Equal(t, int64(0), dm.gauge.(*mockGauge).gotValue) - assert.Len(t, observed.FilterMessage("Failed to measure vault directory size").All(), 1) + assert.Len(t, observed.FilterMessage("Failed to measure directory size").All(), 1) } From 44c0f8df677e5ded236d84271905171d49623e59 Mon Sep 17 00:00:00 2001 From: mchain0 Date: Thu, 14 May 2026 15:57:48 +0200 Subject: [PATCH 5/7] cre-4340: minor improvement --- pkg/diskmonitor/diskmonitor.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/diskmonitor/diskmonitor.go b/pkg/diskmonitor/diskmonitor.go index 2f506c3cbf..304bfd027d 100644 --- a/pkg/diskmonitor/diskmonitor.go +++ b/pkg/diskmonitor/diskmonitor.go @@ -55,9 +55,10 @@ func NewDiskMonitor(lggr logger.Logger, dirPath string, gaugeName string, tickIn Name: "DiskMonitor", Start: dm.start, }.NewServiceEngine(logger.With( - "dirPath", dirPath, - "gaugeName", gaugeName, - )) + lggr, + "dirPath", dirPath, + "gaugeName", gaugeName, + )) dm.lggr = dm.eng.SugaredLogger return dm, nil } From cc195c7717118ca9491dd5744e12c75bccd28826 Mon Sep 17 00:00:00 2001 From: mchain0 Date: Thu, 14 May 2026 16:12:30 +0200 Subject: [PATCH 6/7] cre-4340: walkdir --- pkg/diskmonitor/diskmonitor.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/diskmonitor/diskmonitor.go b/pkg/diskmonitor/diskmonitor.go index 304bfd027d..d77bb364a6 100644 --- a/pkg/diskmonitor/diskmonitor.go +++ b/pkg/diskmonitor/diskmonitor.go @@ -3,7 +3,7 @@ package diskmonitor import ( "context" "fmt" - "os" + "io/fs" "path/filepath" "time" @@ -41,10 +41,15 @@ func NewDiskMonitor(lggr logger.Logger, dirPath string, gaugeName string, tickIn tickInterval: tickInterval, sizeOfDir: func() (int64, error) { var totalSize int64 - walkErr := filepath.Walk(dirPath, func(_ string, info os.FileInfo, ierr error) error { - if ierr == nil && !info.IsDir() { - totalSize += info.Size() + walkErr := filepath.WalkDir(dirPath, func(_ string, d fs.DirEntry, ierr error) error { + if ierr != nil || d.IsDir() { + return nil } + fi, err := d.Info() + if err != nil { + return nil + } + totalSize += fi.Size() return nil }) return totalSize, walkErr From 3197d9df8b38c94c0ac9e7e43e7129c403008212 Mon Sep 17 00:00:00 2001 From: mchain0 Date: Thu, 14 May 2026 16:18:31 +0200 Subject: [PATCH 7/7] cre-4340: refactor --- pkg/diskmonitor/diskmonitor.go | 31 +++++++++++++++++------------ pkg/diskmonitor/diskmonitor_test.go | 22 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/pkg/diskmonitor/diskmonitor.go b/pkg/diskmonitor/diskmonitor.go index d77bb364a6..ac78de6345 100644 --- a/pkg/diskmonitor/diskmonitor.go +++ b/pkg/diskmonitor/diskmonitor.go @@ -18,6 +18,23 @@ type int64Gauge interface { Record(ctx context.Context, value int64, options ...metric.RecordOption) } +// totalRegularFileSizeBytes sums on-disk file sizes for every non-directory under dirPath (recursive), using filepath.WalkDir. +func totalRegularFileSizeBytes(dirPath string) (int64, error) { + var totalSize int64 + walkErr := filepath.WalkDir(dirPath, func(_ string, d fs.DirEntry, ierr error) error { + if ierr != nil || d.IsDir() { + return nil + } + fi, err := d.Info() + if err != nil { + return nil + } + totalSize += fi.Size() + return nil + }) + return totalSize, walkErr +} + // DiskMonitor measures dirPath on a fixed interval and records total file bytes to gaugeName. type DiskMonitor struct { services.Service @@ -40,19 +57,7 @@ func NewDiskMonitor(lggr logger.Logger, dirPath string, gaugeName string, tickIn gauge: g, tickInterval: tickInterval, sizeOfDir: func() (int64, error) { - var totalSize int64 - walkErr := filepath.WalkDir(dirPath, func(_ string, d fs.DirEntry, ierr error) error { - if ierr != nil || d.IsDir() { - return nil - } - fi, err := d.Info() - if err != nil { - return nil - } - totalSize += fi.Size() - return nil - }) - return totalSize, walkErr + return totalRegularFileSizeBytes(dirPath) }, } diff --git a/pkg/diskmonitor/diskmonitor_test.go b/pkg/diskmonitor/diskmonitor_test.go index 3b4fc05839..7325267823 100644 --- a/pkg/diskmonitor/diskmonitor_test.go +++ b/pkg/diskmonitor/diskmonitor_test.go @@ -3,6 +3,8 @@ package diskmonitor import ( "context" "errors" + "os" + "path/filepath" "testing" "time" @@ -21,6 +23,26 @@ func TestNewDiskMonitor(t *testing.T) { assert.Equal(t, time.Second, dm.tickInterval) } +func TestDiskMonitor_emitDirSizeMetric_realDir(t *testing.T) { + root := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(root, "a.txt"), []byte("hello"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(root, "b.txt"), []byte("xy"), 0o644)) + sub := filepath.Join(root, "sub") + require.NoError(t, os.Mkdir(sub, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(sub, "c.txt"), []byte("z"), 0o644)) + + dm := &DiskMonitor{ + sizeOfDir: func() (int64, error) { + return totalRegularFileSizeBytes(root) + }, + gauge: &mockGauge{}, + lggr: logger.Test(t), + } + + dm.emitDirSizeMetric(t.Context()) + assert.Equal(t, int64(8), dm.gauge.(*mockGauge).gotValue) +} + type mockGauge struct { gotValue int64 }