master
go 212 lines 5.69 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package nvme
4
5 import (
6 "bytes"
7 "encoding/json"
8 "strconv"
9 "strings"
10 "time"
11
12 "github.com/netdata/netdata/go/plugins/logger"
13 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/ndexec"
14 )
15
16 type nvmeDeviceList struct {
17 Devices []struct {
18 DevicePath string `json:"DevicePath"`
19 Firmware string `json:"Firmware"`
20 ModelNumber string `json:"ModelNumber"`
21 SerialNumber string `json:"SerialNumber"`
22 }
23 }
24
25 func (n *nvmeDeviceList) UnmarshalJSON(b []byte) error {
26 type plain nvmeDeviceList
27
28 if err := json.Unmarshal(b, (*plain)(n)); err != nil {
29 return err
30 }
31
32 if len(n.Devices) > 0 && n.Devices[0].DevicePath != "" {
33 return nil
34 }
35
36 n.Devices = n.Devices[:0]
37
38 var v211Format struct {
39 Devices []struct {
40 Subsystems []struct {
41 Controllers []struct {
42 SerialNumber string `json:"SerialNumber"`
43 ModelNumber string `json:"ModelNumber"`
44 Firmware string `json:"Firmware"`
45 Namespaces []struct {
46 NameSpace string `json:"NameSpace"`
47 } `json:"Namespaces"`
48 } `json:"Controllers"`
49 } `json:"Subsystems"`
50 } `json:"Devices"`
51 }
52
53 if err := json.Unmarshal(b, &v211Format); err != nil {
54 return err
55 }
56
57 for _, device := range v211Format.Devices {
58 for _, subsystem := range device.Subsystems {
59 for _, controller := range subsystem.Controllers {
60 for _, namespace := range controller.Namespaces {
61 devPath := namespace.NameSpace
62 if !strings.HasPrefix(devPath, "/dev/") {
63 devPath = "/dev/" + devPath
64 }
65 n.Devices = append(n.Devices, struct {
66 DevicePath string `json:"DevicePath"`
67 Firmware string `json:"Firmware"`
68 ModelNumber string `json:"ModelNumber"`
69 SerialNumber string `json:"SerialNumber"`
70 }{
71 DevicePath: devPath,
72 Firmware: controller.Firmware,
73 ModelNumber: controller.ModelNumber,
74 SerialNumber: controller.SerialNumber,
75 })
76 }
77 }
78 }
79 }
80
81 return nil
82 }
83
84 // See "Health Information Log Page" in the Current Specification Version
85 // https://nvmexpress.org/developers/nvme-specification/
86 type nvmeDeviceSmartLog struct {
87 CriticalWarningValue nvmeNumber
88 CriticalWarning json.RawMessage `json:"critical_warning"`
89 Temperature nvmeNumber `json:"temperature"`
90 AvailSpare nvmeNumber `json:"avail_spare"`
91 SpareThresh nvmeNumber `json:"spare_thresh"`
92 PercentUsed nvmeNumber `json:"percent_used"`
93 DataUnitsRead nvmeNumber `json:"data_units_read"`
94 DataUnitsWritten nvmeNumber `json:"data_units_written"`
95 HostReadCommands nvmeNumber `json:"host_read_commands"`
96 HostWriteCommands nvmeNumber `json:"host_write_commands"`
97 ControllerBusyTime nvmeNumber `json:"controller_busy_time"`
98 PowerCycles nvmeNumber `json:"power_cycles"`
99 PowerOnHours nvmeNumber `json:"power_on_hours"`
100 UnsafeShutdowns nvmeNumber `json:"unsafe_shutdowns"`
101 MediaErrors nvmeNumber `json:"media_errors"`
102 NumErrLogEntries nvmeNumber `json:"num_err_log_entries"`
103 WarningTempTime nvmeNumber `json:"warning_temp_time"`
104 CriticalCompTime nvmeNumber `json:"critical_comp_time"`
105 ThmTemp1TransCount nvmeNumber `json:"thm_temp1_trans_count"`
106 ThmTemp2TransCount nvmeNumber `json:"thm_temp2_trans_count"`
107 ThmTemp1TotalTime nvmeNumber `json:"thm_temp1_total_time"`
108 ThmTemp2TotalTime nvmeNumber `json:"thm_temp2_total_time"`
109 }
110
111 func (n *nvmeDeviceSmartLog) UnmarshalJSON(b []byte) error {
112 type plain nvmeDeviceSmartLog
113
114 if err := json.Unmarshal(b, (*plain)(n)); err != nil {
115 return err
116 }
117
118 if n.CriticalWarning != nil {
119 var v211Format struct {
120 Value int `json:"value"`
121 }
122 if err := json.Unmarshal(n.CriticalWarning, &v211Format); err == nil {
123 n.CriticalWarningValue = nvmeNumber(strconv.Itoa(v211Format.Value))
124 } else {
125 n.CriticalWarningValue = nvmeNumber(n.CriticalWarning)
126 }
127 }
128
129 return nil
130 }
131
132 // nvme-cli 2.1.1 exposes some values as strings
133 type nvmeNumber string
134
135 func (n *nvmeNumber) UnmarshalJSON(b []byte) error {
136 *n = nvmeNumber(bytes.Trim(b, "\""))
137 return nil
138 }
139
140 type nvmeCli interface {
141 list() (*nvmeDeviceList, error)
142 smartLog(devicePath string) (*nvmeDeviceSmartLog, error)
143 }
144
145 // ndsudoNvmeCliExec executes nvme via ndsudo (Linux/BSD)
146 type ndsudoNvmeCliExec struct {
147 timeout time.Duration
148 }
149
150 func (n *ndsudoNvmeCliExec) list() (*nvmeDeviceList, error) {
151 bs, err := ndexec.RunNDSudo(nil, n.timeout, "nvme-list")
152 if err != nil {
153 return nil, err
154 }
155
156 var v nvmeDeviceList
157 if err := json.Unmarshal(bs, &v); err != nil {
158 return nil, err
159 }
160
161 return &v, nil
162 }
163
164 func (n *ndsudoNvmeCliExec) smartLog(devicePath string) (*nvmeDeviceSmartLog, error) {
165 bs, err := ndexec.RunNDSudo(nil, n.timeout, "nvme-smart-log", "--device", devicePath)
166 if err != nil {
167 return nil, err
168 }
169
170 var v nvmeDeviceSmartLog
171 if err := json.Unmarshal(bs, &v); err != nil {
172 return nil, err
173 }
174
175 return &v, nil
176 }
177
178 // directNvmeCliExec executes nvme directly (Windows)
179 type directNvmeCliExec struct {
180 *logger.Logger
181
182 nvmePath string
183 timeout time.Duration
184 }
185
186 func (n *directNvmeCliExec) list() (*nvmeDeviceList, error) {
187 bs, err := ndexec.RunDirect(n.Logger, n.timeout, n.nvmePath, "list", "--output-format=json")
188 if err != nil {
189 return nil, err
190 }
191
192 var v nvmeDeviceList
193 if err := json.Unmarshal(bs, &v); err != nil {
194 return nil, err
195 }
196
197 return &v, nil
198 }
199
200 func (n *directNvmeCliExec) smartLog(devicePath string) (*nvmeDeviceSmartLog, error) {
201 bs, err := ndexec.RunDirect(n.Logger, n.timeout, n.nvmePath, "smart-log", devicePath, "--output-format=json")
202 if err != nil {
203 return nil, err
204 }
205
206 var v nvmeDeviceSmartLog
207 if err := json.Unmarshal(bs, &v); err != nil {
208 return nil, err
209 }
210
211 return &v, nil
212 }