| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package nvme |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "strings" |
| 8 | |
| 9 | "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi" |
| 10 | ) |
| 11 | |
| 12 | const ( |
| 13 | _ = 2050 + iota // right after Disks section |
| 14 | prioDeviceEstimatedEndurancePerc |
| 15 | prioDeviceAvailableSparePerc |
| 16 | prioDeviceCompositeTemperature |
| 17 | prioDeviceIOTransferredCount |
| 18 | prioDevicePowerCyclesCount |
| 19 | prioDevicePowerOnTime |
| 20 | prioDeviceUnsafeShutdownsCount |
| 21 | prioDeviceCriticalWarningsState |
| 22 | prioDeviceMediaErrorsRate |
| 23 | prioDeviceErrorLogEntriesRate |
| 24 | prioDeviceWarningCompositeTemperatureTime |
| 25 | prioDeviceCriticalCompositeTemperatureTime |
| 26 | prioDeviceThmTemp1TransitionsCount |
| 27 | prioDeviceThmTemp2TransitionsRate |
| 28 | prioDeviceThmTemp1Time |
| 29 | prioDeviceThmTemp2Time |
| 30 | ) |
| 31 | |
| 32 | var deviceChartsTmpl = collectorapi.Charts{ |
| 33 | deviceEstimatedEndurancePercChartTmpl.Copy(), |
| 34 | deviceAvailableSparePercChartTmpl.Copy(), |
| 35 | deviceCompositeTemperatureChartTmpl.Copy(), |
| 36 | deviceIOTransferredCountChartTmpl.Copy(), |
| 37 | devicePowerCyclesCountChartTmpl.Copy(), |
| 38 | devicePowerOnTimeChartTmpl.Copy(), |
| 39 | deviceUnsafeShutdownsCountChartTmpl.Copy(), |
| 40 | deviceCriticalWarningsStateChartTmpl.Copy(), |
| 41 | deviceMediaErrorsRateChartTmpl.Copy(), |
| 42 | deviceErrorLogEntriesRateChartTmpl.Copy(), |
| 43 | deviceWarnCompositeTemperatureTimeChartTmpl.Copy(), |
| 44 | deviceCritCompositeTemperatureTimeChartTmpl.Copy(), |
| 45 | deviceThmTemp1TransitionsRateChartTmpl.Copy(), |
| 46 | deviceThmTemp2TransitionsRateChartTmpl.Copy(), |
| 47 | deviceThmTemp1TimeChartTmpl.Copy(), |
| 48 | deviceThmTemp2TimeChartTmpl.Copy(), |
| 49 | } |
| 50 | |
| 51 | var deviceEstimatedEndurancePercChartTmpl = collectorapi.Chart{ |
| 52 | ID: "device_%s_estimated_endurance_perc", |
| 53 | Title: "Estimated endurance", |
| 54 | Units: "percentage", |
| 55 | Fam: "endurance", |
| 56 | Ctx: "nvme.device_estimated_endurance_perc", |
| 57 | Priority: prioDeviceEstimatedEndurancePerc, |
| 58 | Dims: collectorapi.Dims{ |
| 59 | {ID: "device_%s_percentage_used", Name: "used"}, |
| 60 | }, |
| 61 | } |
| 62 | var deviceAvailableSparePercChartTmpl = collectorapi.Chart{ |
| 63 | ID: "device_%s_available_spare_perc", |
| 64 | Title: "Remaining spare capacity", |
| 65 | Units: "percentage", |
| 66 | Fam: "spare", |
| 67 | Ctx: "nvme.device_available_spare_perc", |
| 68 | Priority: prioDeviceAvailableSparePerc, |
| 69 | Dims: collectorapi.Dims{ |
| 70 | {ID: "device_%s_available_spare", Name: "spare"}, |
| 71 | }, |
| 72 | } |
| 73 | var deviceCompositeTemperatureChartTmpl = collectorapi.Chart{ |
| 74 | ID: "device_%s_temperature", |
| 75 | Title: "Composite temperature", |
| 76 | Units: "celsius", |
| 77 | Fam: "temperature", |
| 78 | Ctx: "nvme.device_composite_temperature", |
| 79 | Priority: prioDeviceCompositeTemperature, |
| 80 | Dims: collectorapi.Dims{ |
| 81 | {ID: "device_%s_temperature", Name: "temperature"}, |
| 82 | }, |
| 83 | } |
| 84 | var deviceIOTransferredCountChartTmpl = collectorapi.Chart{ |
| 85 | ID: "device_%s_io_transferred_count", |
| 86 | Title: "Amount of data transferred to and from device", |
| 87 | Units: "bytes", |
| 88 | Fam: "transferred data", |
| 89 | Ctx: "nvme.device_io_transferred_count", |
| 90 | Priority: prioDeviceIOTransferredCount, |
| 91 | Type: collectorapi.Area, |
| 92 | Dims: collectorapi.Dims{ |
| 93 | {ID: "device_%s_data_units_read", Name: "read"}, |
| 94 | {ID: "device_%s_data_units_written", Name: "written", Mul: -1}, |
| 95 | }, |
| 96 | } |
| 97 | |
| 98 | var devicePowerCyclesCountChartTmpl = collectorapi.Chart{ |
| 99 | ID: "device_%s_power_cycles_count", |
| 100 | Title: "Power cycles", |
| 101 | Units: "cycles", |
| 102 | Fam: "power cycles", |
| 103 | Ctx: "nvme.device_power_cycles_count", |
| 104 | Priority: prioDevicePowerCyclesCount, |
| 105 | Dims: collectorapi.Dims{ |
| 106 | {ID: "device_%s_power_cycles", Name: "power"}, |
| 107 | }, |
| 108 | } |
| 109 | var devicePowerOnTimeChartTmpl = collectorapi.Chart{ |
| 110 | ID: "device_%s_power_on_time", |
| 111 | Title: "Power-on time", |
| 112 | Units: "seconds", |
| 113 | Fam: "power-on time", |
| 114 | Ctx: "nvme.device_power_on_time", |
| 115 | Priority: prioDevicePowerOnTime, |
| 116 | Dims: collectorapi.Dims{ |
| 117 | {ID: "device_%s_power_on_time", Name: "power-on"}, |
| 118 | }, |
| 119 | } |
| 120 | var deviceCriticalWarningsStateChartTmpl = collectorapi.Chart{ |
| 121 | ID: "device_%s_critical_warnings_state", |
| 122 | Title: "Critical warnings state", |
| 123 | Units: "state", |
| 124 | Fam: "critical warnings", |
| 125 | Ctx: "nvme.device_critical_warnings_state", |
| 126 | Priority: prioDeviceCriticalWarningsState, |
| 127 | Dims: collectorapi.Dims{ |
| 128 | {ID: "device_%s_critical_warning_available_spare", Name: "available_spare"}, |
| 129 | {ID: "device_%s_critical_warning_temp_threshold", Name: "temp_threshold"}, |
| 130 | {ID: "device_%s_critical_warning_nvm_subsystem_reliability", Name: "nvm_subsystem_reliability"}, |
| 131 | {ID: "device_%s_critical_warning_read_only", Name: "read_only"}, |
| 132 | {ID: "device_%s_critical_warning_volatile_mem_backup_failed", Name: "volatile_mem_backup_failed"}, |
| 133 | {ID: "device_%s_critical_warning_persistent_memory_read_only", Name: "persistent_memory_read_only"}, |
| 134 | }, |
| 135 | } |
| 136 | var deviceUnsafeShutdownsCountChartTmpl = collectorapi.Chart{ |
| 137 | ID: "device_%s_unsafe_shutdowns_count", |
| 138 | Title: "Unsafe shutdowns", |
| 139 | Units: "shutdowns", |
| 140 | Fam: "shutdowns", |
| 141 | Ctx: "nvme.device_unsafe_shutdowns_count", |
| 142 | Priority: prioDeviceUnsafeShutdownsCount, |
| 143 | Dims: collectorapi.Dims{ |
| 144 | {ID: "device_%s_unsafe_shutdowns", Name: "unsafe"}, |
| 145 | }, |
| 146 | } |
| 147 | var deviceMediaErrorsRateChartTmpl = collectorapi.Chart{ |
| 148 | ID: "device_%s_media_errors_rate", |
| 149 | Title: "Media and data integrity errors", |
| 150 | Units: "errors/s", |
| 151 | Fam: "media errors", |
| 152 | Ctx: "nvme.device_media_errors_rate", |
| 153 | Priority: prioDeviceMediaErrorsRate, |
| 154 | Dims: collectorapi.Dims{ |
| 155 | {ID: "device_%s_media_errors", Name: "media", Algo: collectorapi.Incremental}, |
| 156 | }, |
| 157 | } |
| 158 | var deviceErrorLogEntriesRateChartTmpl = collectorapi.Chart{ |
| 159 | ID: "device_%s_error_log_entries_rate", |
| 160 | Title: "Error log entries", |
| 161 | Units: "entries/s", |
| 162 | Fam: "error log", |
| 163 | Ctx: "nvme.device_error_log_entries_rate", |
| 164 | Priority: prioDeviceErrorLogEntriesRate, |
| 165 | Dims: collectorapi.Dims{ |
| 166 | {ID: "device_%s_num_err_log_entries", Name: "error_log", Algo: collectorapi.Incremental}, |
| 167 | }, |
| 168 | } |
| 169 | var deviceWarnCompositeTemperatureTimeChartTmpl = collectorapi.Chart{ |
| 170 | ID: "device_%s_warning_composite_temperature_time", |
| 171 | Title: "Warning composite temperature time", |
| 172 | Units: "seconds", |
| 173 | Fam: "warn temp time", |
| 174 | Ctx: "nvme.device_warning_composite_temperature_time", |
| 175 | Priority: prioDeviceWarningCompositeTemperatureTime, |
| 176 | Dims: collectorapi.Dims{ |
| 177 | {ID: "device_%s_warning_temp_time", Name: "wctemp"}, |
| 178 | }, |
| 179 | } |
| 180 | var deviceCritCompositeTemperatureTimeChartTmpl = collectorapi.Chart{ |
| 181 | ID: "device_%s_critical_composite_temperature_time", |
| 182 | Title: "Critical composite temperature time", |
| 183 | Units: "seconds", |
| 184 | Fam: "crit temp time", |
| 185 | Ctx: "nvme.device_critical_composite_temperature_time", |
| 186 | Priority: prioDeviceCriticalCompositeTemperatureTime, |
| 187 | Dims: collectorapi.Dims{ |
| 188 | {ID: "device_%s_critical_comp_time", Name: "cctemp"}, |
| 189 | }, |
| 190 | } |
| 191 | var ( |
| 192 | deviceThmTemp1TransitionsRateChartTmpl = collectorapi.Chart{ |
| 193 | ID: "device_%s_thm_temp1_transitions_rate", |
| 194 | Title: "Thermal management temp1 transitions", |
| 195 | Units: "transitions/s", |
| 196 | Fam: "thermal mgmt transitions", |
| 197 | Ctx: "nvme.device_thermal_mgmt_temp1_transitions_rate", |
| 198 | Priority: prioDeviceThmTemp1TransitionsCount, |
| 199 | Dims: collectorapi.Dims{ |
| 200 | {ID: "device_%s_thm_temp1_trans_count", Name: "temp1", Algo: collectorapi.Incremental}, |
| 201 | }, |
| 202 | } |
| 203 | deviceThmTemp2TransitionsRateChartTmpl = collectorapi.Chart{ |
| 204 | ID: "device_%s_thm_temp2_transitions_rate", |
| 205 | Title: "Thermal management temp2 transitions", |
| 206 | Units: "transitions/s", |
| 207 | Fam: "thermal mgmt transitions", |
| 208 | Ctx: "nvme.device_thermal_mgmt_temp2_transitions_rate", |
| 209 | Priority: prioDeviceThmTemp2TransitionsRate, |
| 210 | Dims: collectorapi.Dims{ |
| 211 | {ID: "device_%s_thm_temp2_trans_count", Name: "temp2", Algo: collectorapi.Incremental}, |
| 212 | }, |
| 213 | } |
| 214 | ) |
| 215 | var ( |
| 216 | deviceThmTemp1TimeChartTmpl = collectorapi.Chart{ |
| 217 | ID: "device_%s_thm_temp1_time", |
| 218 | Title: "Thermal management temp1 time", |
| 219 | Units: "seconds", |
| 220 | Fam: "thermal mgmt time", |
| 221 | Ctx: "nvme.device_thermal_mgmt_temp1_time", |
| 222 | Priority: prioDeviceThmTemp1Time, |
| 223 | Dims: collectorapi.Dims{ |
| 224 | {ID: "device_%s_thm_temp1_total_time", Name: "temp1"}, |
| 225 | }, |
| 226 | } |
| 227 | deviceThmTemp2TimeChartTmpl = collectorapi.Chart{ |
| 228 | ID: "device_%s_thm_temp2_time", |
| 229 | Title: "Thermal management temp1 time", |
| 230 | Units: "seconds", |
| 231 | Fam: "thermal mgmt time", |
| 232 | Ctx: "nvme.device_thermal_mgmt_temp2_time", |
| 233 | Priority: prioDeviceThmTemp2Time, |
| 234 | Dims: collectorapi.Dims{ |
| 235 | {ID: "device_%s_thm_temp2_total_time", Name: "temp2"}, |
| 236 | }, |
| 237 | } |
| 238 | ) |
| 239 | |
| 240 | func (c *Collector) addDeviceCharts(devicePath, model string) { |
| 241 | device := extractDeviceFromPath(devicePath) |
| 242 | |
| 243 | charts := deviceChartsTmpl.Copy() |
| 244 | |
| 245 | for _, chart := range *charts { |
| 246 | chart.ID = fmt.Sprintf(chart.ID, device) |
| 247 | chart.Labels = []collectorapi.Label{ |
| 248 | {Key: "device", Value: device}, |
| 249 | {Key: "model_number", Value: model}, |
| 250 | } |
| 251 | for _, dim := range chart.Dims { |
| 252 | dim.ID = fmt.Sprintf(dim.ID, device) |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | if err := c.Charts().Add(*charts...); err != nil { |
| 257 | c.Warning(err) |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | func (c *Collector) removeDeviceCharts(devicePath string) { |
| 262 | device := extractDeviceFromPath(devicePath) |
| 263 | |
| 264 | px := fmt.Sprintf("device_%s", device) |
| 265 | |
| 266 | for _, chart := range *c.Charts() { |
| 267 | if strings.HasPrefix(chart.ID, px) { |
| 268 | chart.MarkRemove() |
| 269 | chart.MarkNotCreated() |
| 270 | } |
| 271 | } |
| 272 | } |