1
+ package metric
2
+
3
+ import (
4
+ "fmt"
5
+ "log"
6
+ "os/exec"
7
+ "regexp"
8
+ "strings"
9
+ )
10
+
11
+ // scanDevices returns a list of disks using `smartctl --scan`
12
+ func scanDevices () ([]string , error ) {
13
+ out , err := exec .Command ("smartctl" , "--scan" ).Output ()
14
+ if err != nil {
15
+ return nil , fmt .Errorf ("failed to scan devices: %v" , err )
16
+ }
17
+
18
+ var devices []string
19
+ lines := strings .Split (string (out ), "\n " )
20
+ for _ , line := range lines {
21
+ fields := strings .Fields (line )
22
+ if len (fields ) > 0 {
23
+ devices = append (devices , fields [0 ])
24
+ }
25
+ }
26
+ return devices , nil
27
+ }
28
+
29
+ func parseSmartctlOutput (output string ) * SmartMetric {
30
+ startMarker := "=== START OF SMART DATA SECTION ==="
31
+ startIdx := strings .Index (output , startMarker )
32
+ if startIdx == - 1 {
33
+ return & SmartMetric {Data : make (map [string ]string )}
34
+ }
35
+
36
+ section := output [startIdx :]
37
+ endIdx := strings .Index (section , "===" )
38
+ if endIdx > len (startMarker ) {
39
+ section = section [:endIdx ]
40
+ }
41
+
42
+ data := make (map [string ]string )
43
+ lines := strings .Split (section , "\n " )
44
+
45
+ for _ , line := range lines {
46
+ if strings .TrimSpace (line ) == "" || strings .HasPrefix (line , "===" ) {
47
+ continue
48
+ }
49
+
50
+ // Split into key/value pairs
51
+ parts := strings .SplitN (line , ":" , 2 )
52
+ if len (parts ) != 2 {
53
+ continue
54
+ }
55
+
56
+ key := strings .TrimSpace (parts [0 ])
57
+ value := strings .TrimSpace (parts [1 ])
58
+
59
+ // Clean up value: remove extra spaces and brackets
60
+ value = regexp .MustCompile (`\s+` ).ReplaceAllString (value , " " )
61
+ value = strings .Trim (value , "[]" )
62
+
63
+ data [key ] = value
64
+ }
65
+
66
+ return & SmartMetric {
67
+ Data : data ,
68
+ }
69
+ }
70
+
71
+ func getMetrics (device string ) (* SmartMetric , error ) {
72
+ cmd := exec .Command ("smartctl" , "-d" , "nvme" , "--xall" , "--nocheck" , "standby" , device )
73
+ out , err := cmd .CombinedOutput ()
74
+
75
+ if exitErr , ok := err .(* exec.ExitError ); ok && exitErr .ExitCode () == 4 {
76
+ err = nil
77
+ }
78
+
79
+ if err != nil {
80
+ return nil , fmt .Errorf ("smartctl failed: %v" , err )
81
+ }
82
+
83
+ return parseSmartctlOutput (string (out )), nil
84
+ }
85
+
86
+ func GetSmartMetrics () (MetricsSlice , []CustomErr ) {
87
+ var smartCtlrErrs []CustomErr
88
+ devices , err := scanDevices ()
89
+ if err != nil {
90
+ smartCtlrErrs = append (smartCtlrErrs ,CustomErr {
91
+ Metric : []string {"smart" },
92
+ Error : err .Error (),
93
+ })
94
+ }
95
+
96
+ var metrics MetricsSlice // Use MetricsSlice instead of []*SmartMetric
97
+ for _ , device := range devices {
98
+ metric , err := getMetrics (device )
99
+ if err != nil {
100
+ log .Printf ("Skipping %s: %v" , device , err )
101
+ continue
102
+ }
103
+ metrics = append (metrics , metric )
104
+ }
105
+
106
+ return metrics , smartCtlrErrs
107
+ }
0 commit comments