Skip to content

Commit 037340e

Browse files
committed
feat(build): improve formatting and linting workflow
- Update make fmt to use golangci-lint --fix for auto-fixable issues - Make make lint run formatting first, then check remaining issues - Add make lint-only for linting without formatting - Reduces manual formatting work and ensures consistent code style - Auto-fixes wsl_v5, gci, and gofumpt issues
1 parent eb696f3 commit 037340e

File tree

9 files changed

+111
-17
lines changed

9 files changed

+111
-17
lines changed

Makefile

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
.PHONY: help build test lint clean fmt
1+
.PHONY: help build test lint clean fmt lint-only
22

33
# Default target
44
help:
55
@echo "Available targets:"
66
@echo " build - Build the application"
77
@echo " test - Run tests"
8-
@echo " lint - Run golangci-lint using official container"
9-
@echo " fmt - Format code using gofmt and goimports"
8+
@echo " lint - Format code and run golangci-lint"
9+
@echo " fmt - Format code using gofmt, goimports, and golangci-lint"
10+
@echo " lint-only - Run golangci-lint without formatting"
1011
@echo " clean - Clean build artifacts"
1112

1213
# Build the application
@@ -17,18 +18,32 @@ build:
1718
test:
1819
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
1920

20-
# Run golangci-lint using official container
21+
# Format code using gofmt, goimports, and golangci-lint formatters
22+
fmt:
23+
go fmt ./...
24+
goimports -w .
25+
docker run --rm \
26+
-v "$(PWD):/app" \
27+
-w /app \
28+
golangci/golangci-lint:latest \
29+
golangci-lint run --fix
30+
31+
# Run golangci-lint using official container (formats first, then lints)
2132
lint:
33+
make fmt
2234
docker run --rm \
2335
-v "$(PWD):/app" \
2436
-w /app \
2537
golangci/golangci-lint:latest \
2638
golangci-lint run
2739

28-
# Format code using gofmt and goimports
29-
fmt:
30-
go fmt ./...
31-
goimports -w .
40+
# Run only linting without formatting
41+
lint-only:
42+
docker run --rm \
43+
-v "$(PWD):/app" \
44+
-w /app \
45+
golangci/golangci-lint:latest \
46+
golangci-lint run
3247

3348
# Clean build artifacts
3449
clean:

internal/collectors/directory_collector.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func (dc *DirectoryCollector) Start(ctx context.Context) {
3636
func (dc *DirectoryCollector) run(ctx context.Context) {
3737
// Create individual tickers for each directory
3838
tickers := make(map[string]*time.Ticker)
39+
3940
defer func() {
4041
for _, ticker := range tickers {
4142
ticker.Stop()
@@ -79,12 +80,13 @@ func (dc *DirectoryCollector) collectSingleDirectory(groupName string, group con
7980
err := dc.retryWithBackoff(func() error {
8081
return dc.collectDirectoryGroup(groupName, group, collectionType)
8182
}, 3, 2*time.Second)
82-
8383
if err != nil {
8484
slog.Error("Failed to collect directory group metrics after retries", "group", groupName, "error", err)
8585
dc.metrics.CollectionFailedCounter().WithLabelValues(collectionType, groupName).Inc()
86+
8687
return
8788
}
89+
8890
dc.metrics.CollectionSuccessCounter().WithLabelValues(collectionType, groupName).Inc()
8991

9092
duration := time.Since(startTime).Seconds()
@@ -97,6 +99,7 @@ func (dc *DirectoryCollector) collectSingleDirectory(groupName string, group con
9799
// retryWithBackoff implements exponential backoff retry logic
98100
func (dc *DirectoryCollector) retryWithBackoff(operation func() error, maxRetries int, initialDelay time.Duration) error {
99101
var lastErr error
102+
100103
delay := initialDelay
101104

102105
for attempt := 0; attempt <= maxRetries; attempt++ {
@@ -142,8 +145,11 @@ func (dc *DirectoryCollector) collectSingleDirectoryFile(groupName, path, collec
142145

143146
// Track lock waiting time
144147
lockWaitStart := time.Now()
148+
145149
dc.duMutex.Lock()
150+
146151
lockWaitDuration := time.Since(lockWaitStart)
152+
147153
defer dc.duMutex.Unlock()
148154

149155
// Record lock wait duration
@@ -155,6 +161,7 @@ func (dc *DirectoryCollector) collectSingleDirectoryFile(groupName, path, collec
155161
// -s: summarize only
156162
// -x: don't cross filesystem boundaries (faster)
157163
cmd := exec.CommandContext(ctx, "du", "-s", "-x", path)
164+
158165
output, err := cmd.Output()
159166
if err != nil {
160167
dc.metrics.DirectoriesFailedCounter().WithLabelValues(groupName, "du").Inc()
@@ -240,6 +247,7 @@ func (dc *DirectoryCollector) collectSubdirectories(groupName string, group conf
240247
} else {
241248
depth = len(pathComponents) // Each component represents one level of depth
242249
}
250+
243251
if !d.IsDir() {
244252
depth++ // Files are at depth + 1
245253
}
@@ -258,7 +266,6 @@ func (dc *DirectoryCollector) collectSubdirectories(groupName string, group conf
258266

259267
return nil
260268
})
261-
262269
if err != nil {
263270
dc.metrics.DirectoriesFailedCounter().WithLabelValues(groupName, "walk").Inc()
264271
return fmt.Errorf("failed to walk directory tree for %s: %w", group.Path, err)

internal/collectors/directory_collector_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ package collectors
22

33
import (
44
"context"
5-
"filesystem-exporter/internal/config"
6-
"filesystem-exporter/internal/metrics"
75
"path/filepath"
86
"strings"
97
"sync"
108
"testing"
119
"time"
10+
11+
"filesystem-exporter/internal/config"
12+
"filesystem-exporter/internal/metrics"
1213
)
1314

1415
func TestDepthCalculation(t *testing.T) {
@@ -75,6 +76,7 @@ func TestDepthCalculation(t *testing.T) {
7576

7677
// Test the NEW method (what we have now)
7778
pathComponents := strings.Split(relPath, string(filepath.Separator))
79+
7880
var newDepth int
7981
if len(pathComponents) == 1 && pathComponents[0] == "." {
8082
newDepth = 0
@@ -142,6 +144,7 @@ func TestDepthCalculationEdgeCases(t *testing.T) {
142144
}
143145

144146
pathComponents := strings.Split(relPath, string(filepath.Separator))
147+
145148
var depth int
146149
if len(pathComponents) == 1 && pathComponents[0] == "." {
147150
depth = 0
@@ -180,11 +183,13 @@ func TestDirectoryCollectorMutex(t *testing.T) {
180183

181184
// Test that mutex prevents concurrent du operations
182185
var wg sync.WaitGroup
186+
183187
startTime := time.Now()
184188

185189
// Start multiple goroutines that would normally run du concurrently
186190
for i := 0; i < 3; i++ {
187191
wg.Add(1)
192+
188193
go func(id int) {
189194
defer wg.Done()
190195
// This will try to acquire the mutex
@@ -196,6 +201,7 @@ func TestDirectoryCollectorMutex(t *testing.T) {
196201
}
197202

198203
wg.Wait()
204+
199205
duration := time.Since(startTime)
200206

201207
// If the mutex is working correctly, operations should be serialized
@@ -255,6 +261,7 @@ func TestDirectoryCollectorConcurrency(t *testing.T) {
255261
if collector.config == nil {
256262
t.Error("Collector config should not be nil")
257263
}
264+
258265
if collector.metrics == nil {
259266
t.Error("Collector metrics should not be nil")
260267
}
@@ -283,6 +290,7 @@ func TestLockWaitDurationMetric(t *testing.T) {
283290
// Start multiple goroutines to create lock contention
284291
for i := 0; i < 3; i++ {
285292
wg.Add(1)
293+
286294
go func(id int) {
287295
defer wg.Done()
288296
// This will try to acquire the mutex and record wait time

internal/collectors/volume_collector.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func (fc *FilesystemCollector) Start(ctx context.Context) {
3434
func (fc *FilesystemCollector) run(ctx context.Context) {
3535
// Create individual tickers for each filesystem
3636
tickers := make(map[string]*time.Ticker)
37+
3738
defer func() {
3839
for _, ticker := range tickers {
3940
ticker.Stop()
@@ -77,12 +78,13 @@ func (fc *FilesystemCollector) collectSingleFilesystem(filesystem config.Filesys
7778
err := fc.retryWithBackoff(func() error {
7879
return fc.collectFilesystemUsage(filesystem)
7980
}, 3, 2*time.Second)
80-
8181
if err != nil {
8282
slog.Error("Failed to collect filesystem metrics after retries", "filesystem", filesystem.Name, "error", err)
8383
fc.metrics.CollectionFailedCounter().WithLabelValues(collectionType, filesystem.Name).Inc()
84+
8485
return
8586
}
87+
8688
fc.metrics.CollectionSuccessCounter().WithLabelValues(collectionType, filesystem.Name).Inc()
8789

8890
duration := time.Since(startTime).Seconds()
@@ -95,6 +97,7 @@ func (fc *FilesystemCollector) collectSingleFilesystem(filesystem config.Filesys
9597
// retryWithBackoff implements exponential backoff retry logic
9698
func (fc *FilesystemCollector) retryWithBackoff(operation func() error, maxRetries int, initialDelay time.Duration) error {
9799
var lastErr error
100+
98101
delay := initialDelay
99102

100103
for attempt := 0; attempt <= maxRetries; attempt++ {
@@ -128,6 +131,7 @@ func (fc *FilesystemCollector) collectFilesystemUsage(filesystem config.Filesyst
128131
defer cancel()
129132

130133
cmd := exec.CommandContext(ctx, "df", sanitizedMountPoint)
134+
131135
output, err := cmd.Output()
132136
if err != nil {
133137
return fmt.Errorf("failed to execute df command: %w", err)
@@ -148,6 +152,7 @@ func (fc *FilesystemCollector) collectFilesystemUsage(filesystem config.Filesyst
148152
// OR: /dev/sdb1 239313084 27770148 211424152 12% /mnt/backup
149153
// The filesystem name might be on a separate line, so we need to find the line with the stats
150154
var statsLine string
155+
151156
for i := 1; i < len(lines); i++ {
152157
line := strings.TrimSpace(lines[i])
153158
if line == "" {
@@ -199,10 +204,13 @@ func (fc *FilesystemCollector) collectFilesystemUsage(filesystem config.Filesyst
199204
// Handle both formats:
200205
// Single-line: /dev/usb1p1 239313084 27770148 211424152 12% /volumeUSB1/usbshare
201206
// Multi-line: 16847009220 14430849176 2416160044 86% /mnt/data
202-
var sizeKB, availableKB int64
203-
var parseErr error
207+
var (
208+
sizeKB, availableKB int64
209+
parseErr error
210+
)
204211

205212
// Try to parse the first field as a number
213+
206214
if sizeKB, parseErr = strconv.ParseInt(parts[0], 10, 64); parseErr == nil {
207215
// Multi-line format: size is in parts[0], available in parts[2]
208216
if len(parts) >= 3 {
@@ -217,6 +225,7 @@ func (fc *FilesystemCollector) collectFilesystemUsage(filesystem config.Filesyst
217225
if parseErr != nil {
218226
return fmt.Errorf("failed to parse size: %w", parseErr)
219227
}
228+
220229
availableKB, parseErr = strconv.ParseInt(parts[3], 10, 64)
221230
if parseErr != nil {
222231
return fmt.Errorf("failed to parse available space in single-line format: %w", parseErr)

internal/config/config.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
2727
if err != nil {
2828
return fmt.Errorf("invalid duration format '%s': %w", v, err)
2929
}
30+
3031
d.Duration = duration
3132
case int:
3233
// Backward compatibility: treat as seconds
@@ -120,15 +121,19 @@ func Load(path string) (*Config, error) {
120121
if config.Server.Host == "" {
121122
config.Server.Host = "0.0.0.0"
122123
}
124+
123125
if config.Server.Port == 0 {
124126
config.Server.Port = 8080
125127
}
128+
126129
if config.Logging.Level == "" {
127130
config.Logging.Level = "info"
128131
}
132+
129133
if config.Logging.Format == "" {
130134
config.Logging.Format = "json"
131135
}
136+
132137
if !config.Metrics.Collection.DefaultIntervalSet {
133138
config.Metrics.Collection.DefaultInterval = Duration{time.Second * 300}
134139
}
@@ -175,6 +180,7 @@ func (c *Config) validateServerConfig() error {
175180
if c.Server.Port < 1 || c.Server.Port > 65535 {
176181
return fmt.Errorf("port must be between 1 and 65535, got %d", c.Server.Port)
177182
}
183+
178184
return nil
179185
}
180186

@@ -204,9 +210,11 @@ func (c *Config) validateMetricsConfig() error {
204210
if c.Metrics.Collection.DefaultInterval.Seconds() < 1 {
205211
return fmt.Errorf("default interval must be at least 1 second, got %d", c.Metrics.Collection.DefaultInterval.Seconds())
206212
}
213+
207214
if c.Metrics.Collection.DefaultInterval.Seconds() > 86400 {
208215
return fmt.Errorf("default interval must be at most 86400 seconds (24 hours), got %d", c.Metrics.Collection.DefaultInterval.Seconds())
209216
}
217+
210218
return nil
211219
}
212220

@@ -223,21 +231,26 @@ func (c *Config) validateFilesystems() error {
223231
if fs.Name == "" {
224232
return fmt.Errorf("filesystem %d: name is required", i)
225233
}
234+
226235
if seenNames[fs.Name] {
227236
return fmt.Errorf("filesystem %d: duplicate name '%s'", i, fs.Name)
228237
}
238+
229239
seenNames[fs.Name] = true
230240

231241
// Validate mount point
232242
if fs.MountPoint == "" {
233243
return fmt.Errorf("filesystem %d: mount_point is required", i)
234244
}
245+
235246
if !filepath.IsAbs(fs.MountPoint) {
236247
return fmt.Errorf("filesystem %d: mount_point must be absolute path, got '%s'", i, fs.MountPoint)
237248
}
249+
238250
if seenMountPoints[fs.MountPoint] {
239251
return fmt.Errorf("filesystem %d: duplicate mount_point '%s'", i, fs.MountPoint)
240252
}
253+
241254
seenMountPoints[fs.MountPoint] = true
242255

243256
// Validate device
@@ -250,6 +263,7 @@ func (c *Config) validateFilesystems() error {
250263
if fs.Interval.Seconds() < 1 {
251264
return fmt.Errorf("filesystem %d: interval must be at least 1 second, got %d", i, fs.Interval.Seconds())
252265
}
266+
253267
if fs.Interval.Seconds() > 86400 {
254268
return fmt.Errorf("filesystem %d: interval must be at most 86400 seconds (24 hours), got %d", i, fs.Interval.Seconds())
255269
}
@@ -281,18 +295,22 @@ func (c *Config) validateDirectories() error {
281295
if dir.Path == "" {
282296
return fmt.Errorf("directory '%s': path is required", name)
283297
}
298+
284299
if !filepath.IsAbs(dir.Path) {
285300
return fmt.Errorf("directory '%s': path must be absolute, got '%s'", name, dir.Path)
286301
}
302+
287303
if seenPaths[dir.Path] {
288304
return fmt.Errorf("directory '%s': duplicate path '%s'", name, dir.Path)
289305
}
306+
290307
seenPaths[dir.Path] = true
291308

292309
// Validate subdirectory levels
293310
if dir.SubdirectoryLevels < 0 {
294311
return fmt.Errorf("directory '%s': subdirectory_levels must be non-negative, got %d", name, dir.SubdirectoryLevels)
295312
}
313+
296314
if dir.SubdirectoryLevels > 10 {
297315
return fmt.Errorf("directory '%s': subdirectory_levels must be at most 10, got %d", name, dir.SubdirectoryLevels)
298316
}
@@ -302,6 +320,7 @@ func (c *Config) validateDirectories() error {
302320
if dir.Interval.Seconds() < 1 {
303321
return fmt.Errorf("directory '%s': interval must be at least 1 second, got %d", name, dir.Interval.Seconds())
304322
}
323+
305324
if dir.Interval.Seconds() > 86400 {
306325
return fmt.Errorf("directory '%s': interval must be at most 86400 seconds (24 hours), got %d", name, dir.Interval.Seconds())
307326
}
@@ -321,6 +340,7 @@ func (c *Config) GetFilesystemInterval(fs FilesystemConfig) int {
321340
if fs.Interval != nil {
322341
return fs.Interval.Seconds()
323342
}
343+
324344
return c.Metrics.Collection.DefaultInterval.Seconds()
325345
}
326346

@@ -329,5 +349,6 @@ func (c *Config) GetDirectoryInterval(dir DirectoryGroup) int {
329349
if dir.Interval != nil {
330350
return dir.Interval.Seconds()
331351
}
352+
332353
return c.Metrics.Collection.DefaultInterval.Seconds()
333354
}

0 commit comments

Comments
 (0)