| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package upsd |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "strings" |
| 8 | |
| 9 | "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi" |
| 10 | ) |
| 11 | |
| 12 | const ( |
| 13 | prioUpsLoad = collectorapi.Priority + iota |
| 14 | prioUpsLoadUsage |
| 15 | prioUpsStatus |
| 16 | prioUpsTemperature |
| 17 | |
| 18 | prioBatteryCharge |
| 19 | prioBatteryEstimatedRuntime |
| 20 | prioBatteryVoltage |
| 21 | prioBatteryVoltageNominal |
| 22 | |
| 23 | prioInputVoltage |
| 24 | prioInputVoltageNominal |
| 25 | prioInputCurrent |
| 26 | prioInputCurrentNominal |
| 27 | prioInputFrequency |
| 28 | prioInputFrequencyNominal |
| 29 | |
| 30 | prioOutputVoltage |
| 31 | prioOutputVoltageNominal |
| 32 | prioOutputCurrent |
| 33 | prioOutputCurrentNominal |
| 34 | prioOutputFrequency |
| 35 | prioOutputFrequencyNominal |
| 36 | ) |
| 37 | |
| 38 | var upsChartsTmpl = collectorapi.Charts{ |
| 39 | upsLoadChartTmpl.Copy(), |
| 40 | upsLoadUsageChartTmpl.Copy(), |
| 41 | upsStatusChartTmpl.Copy(), |
| 42 | upsTemperatureChartTmpl.Copy(), |
| 43 | |
| 44 | upsBatteryChargePercentChartTmpl.Copy(), |
| 45 | upsBatteryEstimatedRuntimeChartTmpl.Copy(), |
| 46 | upsBatteryVoltageChartTmpl.Copy(), |
| 47 | upsBatteryVoltageNominalChartTmpl.Copy(), |
| 48 | |
| 49 | upsInputVoltageChartTmpl.Copy(), |
| 50 | upsInputVoltageNominalChartTmpl.Copy(), |
| 51 | upsInputCurrentChartTmpl.Copy(), |
| 52 | upsInputCurrentNominalChartTmpl.Copy(), |
| 53 | upsInputFrequencyChartTmpl.Copy(), |
| 54 | upsInputFrequencyNominalChartTmpl.Copy(), |
| 55 | |
| 56 | upsOutputVoltageChartTmpl.Copy(), |
| 57 | upsOutputVoltageNominalChartTmpl.Copy(), |
| 58 | upsOutputCurrentChartTmpl.Copy(), |
| 59 | upsOutputCurrentNominalChartTmpl.Copy(), |
| 60 | upsOutputFrequencyChartTmpl.Copy(), |
| 61 | upsOutputFrequencyNominalChartTmpl.Copy(), |
| 62 | } |
| 63 | |
| 64 | var ( |
| 65 | upsLoadChartTmpl = collectorapi.Chart{ |
| 66 | IDSep: true, |
| 67 | ID: "%s.load_percentage", |
| 68 | Title: "UPS load", |
| 69 | Units: "percentage", |
| 70 | Fam: "ups", |
| 71 | Ctx: "upsd.ups_load", |
| 72 | Priority: prioUpsLoad, |
| 73 | Type: collectorapi.Area, |
| 74 | Dims: collectorapi.Dims{ |
| 75 | {ID: "ups_%s_ups.load", Name: "load", Div: varPrecision}, |
| 76 | }, |
| 77 | } |
| 78 | upsLoadUsageChartTmpl = collectorapi.Chart{ |
| 79 | IDSep: true, |
| 80 | ID: "%s.load_usage", |
| 81 | Title: "UPS load usage (power output)", |
| 82 | Units: "Watts", |
| 83 | Fam: "ups", |
| 84 | Ctx: "upsd.ups_load_usage", |
| 85 | Priority: prioUpsLoadUsage, |
| 86 | Dims: collectorapi.Dims{ |
| 87 | {ID: "ups_%s_ups.load.usage", Name: "load_usage", Div: varPrecision}, |
| 88 | }, |
| 89 | } |
| 90 | upsStatusChartTmpl = collectorapi.Chart{ |
| 91 | IDSep: true, |
| 92 | ID: "%s.status", |
| 93 | Title: "UPS status", |
| 94 | Units: "status", |
| 95 | Fam: "ups", |
| 96 | Ctx: "upsd.ups_status", |
| 97 | Priority: prioUpsStatus, |
| 98 | Dims: collectorapi.Dims{ |
| 99 | {ID: "ups_%s_ups.status.OL", Name: "on_line"}, |
| 100 | {ID: "ups_%s_ups.status.OB", Name: "on_battery"}, |
| 101 | {ID: "ups_%s_ups.status.LB", Name: "low_battery"}, |
| 102 | {ID: "ups_%s_ups.status.HB", Name: "high_battery"}, |
| 103 | {ID: "ups_%s_ups.status.RB", Name: "replace_battery"}, |
| 104 | {ID: "ups_%s_ups.status.CHRG", Name: "charging"}, |
| 105 | {ID: "ups_%s_ups.status.DISCHRG", Name: "discharging"}, |
| 106 | {ID: "ups_%s_ups.status.BYPASS", Name: "bypass"}, |
| 107 | {ID: "ups_%s_ups.status.CAL", Name: "calibration"}, |
| 108 | {ID: "ups_%s_ups.status.OFF", Name: "offline"}, |
| 109 | {ID: "ups_%s_ups.status.OVER", Name: "overloaded"}, |
| 110 | {ID: "ups_%s_ups.status.TRIM", Name: "trim_input_voltage"}, |
| 111 | {ID: "ups_%s_ups.status.BOOST", Name: "boost_input_voltage"}, |
| 112 | {ID: "ups_%s_ups.status.FSD", Name: "forced_shutdown"}, |
| 113 | {ID: "ups_%s_ups.status.other", Name: "other"}, |
| 114 | }, |
| 115 | } |
| 116 | upsTemperatureChartTmpl = collectorapi.Chart{ |
| 117 | IDSep: true, |
| 118 | ID: "%s.temperature", |
| 119 | Title: "UPS temperature", |
| 120 | Units: "Celsius", |
| 121 | Fam: "ups", |
| 122 | Ctx: "upsd.ups_temperature", |
| 123 | Priority: prioUpsTemperature, |
| 124 | Dims: collectorapi.Dims{ |
| 125 | {ID: "ups_%s_ups.temperature", Name: "temperature", Div: varPrecision}, |
| 126 | }, |
| 127 | } |
| 128 | ) |
| 129 | |
| 130 | var ( |
| 131 | upsBatteryChargePercentChartTmpl = collectorapi.Chart{ |
| 132 | IDSep: true, |
| 133 | ID: "%s.battery_charge_percentage", |
| 134 | Title: "UPS Battery charge", |
| 135 | Units: "percentage", |
| 136 | Fam: "battery", |
| 137 | Ctx: "upsd.ups_battery_charge", |
| 138 | Priority: prioBatteryCharge, |
| 139 | Type: collectorapi.Area, |
| 140 | Dims: collectorapi.Dims{ |
| 141 | {ID: "ups_%s_battery.charge", Name: "charge", Div: varPrecision}, |
| 142 | }, |
| 143 | } |
| 144 | upsBatteryEstimatedRuntimeChartTmpl = collectorapi.Chart{ |
| 145 | IDSep: true, |
| 146 | ID: "%s.battery_estimated_runtime", |
| 147 | Title: "UPS Battery estimated runtime", |
| 148 | Units: "seconds", |
| 149 | Fam: "battery", |
| 150 | Ctx: "upsd.ups_battery_estimated_runtime", |
| 151 | Priority: prioBatteryEstimatedRuntime, |
| 152 | Dims: collectorapi.Dims{ |
| 153 | {ID: "ups_%s_battery.runtime", Name: "runtime", Div: varPrecision}, |
| 154 | }, |
| 155 | } |
| 156 | upsBatteryVoltageChartTmpl = collectorapi.Chart{ |
| 157 | IDSep: true, |
| 158 | ID: "%s.battery_voltage", |
| 159 | Title: "UPS Battery voltage", |
| 160 | Units: "Volts", |
| 161 | Fam: "battery", |
| 162 | Ctx: "upsd.ups_battery_voltage", |
| 163 | Priority: prioBatteryVoltage, |
| 164 | Dims: collectorapi.Dims{ |
| 165 | {ID: "ups_%s_battery.voltage", Name: "voltage", Div: varPrecision}, |
| 166 | }, |
| 167 | } |
| 168 | upsBatteryVoltageNominalChartTmpl = collectorapi.Chart{ |
| 169 | IDSep: true, |
| 170 | ID: "%s.battery_voltage_nominal", |
| 171 | Title: "UPS Battery voltage nominal", |
| 172 | Units: "Volts", |
| 173 | Fam: "battery", |
| 174 | Ctx: "upsd.ups_battery_voltage_nominal", |
| 175 | Priority: prioBatteryVoltageNominal, |
| 176 | Dims: collectorapi.Dims{ |
| 177 | {ID: "ups_%s_battery.voltage.nominal", Name: "nominal_voltage", Div: varPrecision}, |
| 178 | }, |
| 179 | } |
| 180 | ) |
| 181 | |
| 182 | var ( |
| 183 | upsInputVoltageChartTmpl = collectorapi.Chart{ |
| 184 | IDSep: true, |
| 185 | ID: "%s.input_voltage", |
| 186 | Title: "UPS Input voltage", |
| 187 | Units: "Volts", |
| 188 | Fam: "input", |
| 189 | Ctx: "upsd.ups_input_voltage", |
| 190 | Priority: prioInputVoltage, |
| 191 | Dims: collectorapi.Dims{ |
| 192 | {ID: "ups_%s_input.voltage", Name: "voltage", Div: varPrecision}, |
| 193 | }, |
| 194 | } |
| 195 | upsInputVoltageNominalChartTmpl = collectorapi.Chart{ |
| 196 | IDSep: true, |
| 197 | ID: "%s.input_voltage_nominal", |
| 198 | Title: "UPS Input voltage nominal", |
| 199 | Units: "Volts", |
| 200 | Fam: "input", |
| 201 | Ctx: "upsd.ups_input_voltage_nominal", |
| 202 | Priority: prioInputVoltageNominal, |
| 203 | Dims: collectorapi.Dims{ |
| 204 | {ID: "ups_%s_input.voltage.nominal", Name: "nominal_voltage", Div: varPrecision}, |
| 205 | }, |
| 206 | } |
| 207 | upsInputCurrentChartTmpl = collectorapi.Chart{ |
| 208 | IDSep: true, |
| 209 | ID: "%s.input_current", |
| 210 | Title: "UPS Input current", |
| 211 | Units: "Ampere", |
| 212 | Fam: "input", |
| 213 | Ctx: "upsd.ups_input_current", |
| 214 | Priority: prioInputCurrent, |
| 215 | Dims: collectorapi.Dims{ |
| 216 | {ID: "ups_%s_input.current", Name: "current", Div: varPrecision}, |
| 217 | }, |
| 218 | } |
| 219 | upsInputCurrentNominalChartTmpl = collectorapi.Chart{ |
| 220 | IDSep: true, |
| 221 | ID: "%s.input_current_nominal", |
| 222 | Title: "UPS Input current nominal", |
| 223 | Units: "Ampere", |
| 224 | Fam: "input", |
| 225 | Ctx: "upsd.ups_input_current_nominal", |
| 226 | Priority: prioInputCurrentNominal, |
| 227 | Dims: collectorapi.Dims{ |
| 228 | {ID: "ups_%s_input.current.nominal", Name: "nominal_current", Div: varPrecision}, |
| 229 | }, |
| 230 | } |
| 231 | upsInputFrequencyChartTmpl = collectorapi.Chart{ |
| 232 | IDSep: true, |
| 233 | ID: "%s.input_frequency", |
| 234 | Title: "UPS Input frequency", |
| 235 | Units: "Hz", |
| 236 | Fam: "input", |
| 237 | Ctx: "upsd.ups_input_frequency", |
| 238 | Priority: prioInputFrequency, |
| 239 | Dims: collectorapi.Dims{ |
| 240 | {ID: "ups_%s_input.frequency", Name: "frequency", Div: varPrecision}, |
| 241 | }, |
| 242 | } |
| 243 | upsInputFrequencyNominalChartTmpl = collectorapi.Chart{ |
| 244 | IDSep: true, |
| 245 | ID: "%s.input_frequency_nominal", |
| 246 | Title: "UPS Input frequency nominal", |
| 247 | Units: "Hz", |
| 248 | Fam: "input", |
| 249 | Ctx: "upsd.ups_input_frequency_nominal", |
| 250 | Priority: prioInputFrequencyNominal, |
| 251 | Dims: collectorapi.Dims{ |
| 252 | {ID: "ups_%s_input.frequency.nominal", Name: "nominal_frequency", Div: varPrecision}, |
| 253 | }, |
| 254 | } |
| 255 | ) |
| 256 | |
| 257 | var ( |
| 258 | upsOutputVoltageChartTmpl = collectorapi.Chart{ |
| 259 | IDSep: true, |
| 260 | ID: "%s.output_voltage", |
| 261 | Title: "UPS Output voltage", |
| 262 | Units: "Volts", |
| 263 | Fam: "output", |
| 264 | Ctx: "upsd.ups_output_voltage", |
| 265 | Priority: prioOutputVoltage, |
| 266 | Dims: collectorapi.Dims{ |
| 267 | {ID: "ups_%s_output.voltage", Name: "voltage", Div: varPrecision}, |
| 268 | }, |
| 269 | } |
| 270 | upsOutputVoltageNominalChartTmpl = collectorapi.Chart{ |
| 271 | IDSep: true, |
| 272 | ID: "%s.output_voltage_nominal", |
| 273 | Title: "UPS Output voltage nominal", |
| 274 | Units: "Volts", |
| 275 | Fam: "output", |
| 276 | Ctx: "upsd.ups_output_voltage_nominal", |
| 277 | Priority: prioOutputVoltageNominal, |
| 278 | Dims: collectorapi.Dims{ |
| 279 | {ID: "ups_%s_output.voltage.nominal", Name: "nominal_voltage", Div: varPrecision}, |
| 280 | }, |
| 281 | } |
| 282 | upsOutputCurrentChartTmpl = collectorapi.Chart{ |
| 283 | IDSep: true, |
| 284 | ID: "%s.output_current", |
| 285 | Title: "UPS Output current", |
| 286 | Units: "Ampere", |
| 287 | Fam: "output", |
| 288 | Ctx: "upsd.ups_output_current", |
| 289 | Priority: prioOutputCurrent, |
| 290 | Dims: collectorapi.Dims{ |
| 291 | {ID: "ups_%s_output.current", Name: "current", Div: varPrecision}, |
| 292 | }, |
| 293 | } |
| 294 | upsOutputCurrentNominalChartTmpl = collectorapi.Chart{ |
| 295 | IDSep: true, |
| 296 | ID: "%s.output_current_nominal", |
| 297 | Title: "UPS Output current nominal", |
| 298 | Units: "Ampere", |
| 299 | Fam: "output", |
| 300 | Ctx: "upsd.ups_output_current_nominal", |
| 301 | Priority: prioOutputCurrentNominal, |
| 302 | Dims: collectorapi.Dims{ |
| 303 | {ID: "ups_%s_output.current.nominal", Name: "nominal_current", Div: varPrecision}, |
| 304 | }, |
| 305 | } |
| 306 | upsOutputFrequencyChartTmpl = collectorapi.Chart{ |
| 307 | IDSep: true, |
| 308 | ID: "%s.output_frequency", |
| 309 | Title: "UPS Output frequency", |
| 310 | Units: "Hz", |
| 311 | Fam: "output", |
| 312 | Ctx: "upsd.ups_output_frequency", |
| 313 | Priority: prioOutputFrequency, |
| 314 | Dims: collectorapi.Dims{ |
| 315 | {ID: "ups_%s_output.frequency", Name: "frequency", Div: varPrecision}, |
| 316 | }, |
| 317 | } |
| 318 | upsOutputFrequencyNominalChartTmpl = collectorapi.Chart{ |
| 319 | IDSep: true, |
| 320 | ID: "%s.output_frequency_nominal", |
| 321 | Title: "UPS Output frequency nominal", |
| 322 | Units: "Hz", |
| 323 | Fam: "output", |
| 324 | Ctx: "upsd.ups_output_frequency_nominal", |
| 325 | Priority: prioOutputFrequencyNominal, |
| 326 | Dims: collectorapi.Dims{ |
| 327 | {ID: "ups_%s_output.frequency.nominal", Name: "nominal_frequency", Div: varPrecision}, |
| 328 | }, |
| 329 | } |
| 330 | ) |
| 331 | |
| 332 | func (c *Collector) addUPSCharts(ups upsUnit) { |
| 333 | charts := upsChartsTmpl.Copy() |
| 334 | |
| 335 | var removed []string |
| 336 | for _, v := range []struct{ v, id string }{ |
| 337 | {varUpsLoad, upsLoadChartTmpl.ID}, |
| 338 | {varUpsLoad, upsLoadUsageChartTmpl.ID}, |
| 339 | |
| 340 | {varBatteryVoltage, upsBatteryVoltageChartTmpl.ID}, |
| 341 | {varBatteryVoltageNominal, upsBatteryVoltageNominalChartTmpl.ID}, |
| 342 | |
| 343 | {varUpsTemperature, upsTemperatureChartTmpl.ID}, |
| 344 | |
| 345 | {varInputVoltage, upsInputVoltageChartTmpl.ID}, |
| 346 | {varInputVoltageNominal, upsInputVoltageNominalChartTmpl.ID}, |
| 347 | {varInputCurrent, upsInputCurrentChartTmpl.ID}, |
| 348 | {varInputCurrentNominal, upsInputCurrentNominalChartTmpl.ID}, |
| 349 | {varInputFrequency, upsInputFrequencyChartTmpl.ID}, |
| 350 | {varInputFrequencyNominal, upsInputFrequencyNominalChartTmpl.ID}, |
| 351 | |
| 352 | {varOutputVoltage, upsOutputVoltageChartTmpl.ID}, |
| 353 | {varOutputVoltageNominal, upsOutputVoltageNominalChartTmpl.ID}, |
| 354 | {varOutputCurrent, upsOutputCurrentChartTmpl.ID}, |
| 355 | {varOutputCurrentNominal, upsOutputCurrentNominalChartTmpl.ID}, |
| 356 | {varOutputFrequency, upsOutputFrequencyChartTmpl.ID}, |
| 357 | {varOutputFrequencyNominal, upsOutputFrequencyNominalChartTmpl.ID}, |
| 358 | } { |
| 359 | if !hasVar(ups.vars, v.v) { |
| 360 | removed = append(removed, v.v) |
| 361 | _ = charts.Remove(v.id) |
| 362 | } |
| 363 | } |
| 364 | |
| 365 | c.Debugf("UPS '%s' no metrics: %v", ups.name, removed) |
| 366 | |
| 367 | name := cleanUpsName(ups.name) |
| 368 | |
| 369 | for _, chart := range *charts { |
| 370 | chart.ID = fmt.Sprintf(chart.ID, name) |
| 371 | chart.Labels = []collectorapi.Label{ |
| 372 | {Key: "ups_name", Value: ups.name}, |
| 373 | {Key: "battery_type", Value: ups.vars[varBatteryType]}, |
| 374 | {Key: "device_model", Value: ups.vars[varDeviceModel]}, |
| 375 | {Key: "device_serial", Value: ups.vars[varDeviceSerial]}, |
| 376 | {Key: "device_manufacturer", Value: ups.vars[varDeviceMfr]}, |
| 377 | {Key: "device_type", Value: ups.vars[varDeviceType]}, |
| 378 | } |
| 379 | for _, dim := range chart.Dims { |
| 380 | dim.ID = fmt.Sprintf(dim.ID, ups.name) |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | if err := c.Charts().Add(*charts...); err != nil { |
| 385 | c.Warning(err) |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | func (c *Collector) removeUPSCharts(name string) { |
| 390 | name = cleanUpsName(name) |
| 391 | for _, chart := range *c.Charts() { |
| 392 | if strings.HasPrefix(chart.ID, name) { |
| 393 | chart.MarkRemove() |
| 394 | chart.MarkNotCreated() |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | func cleanUpsName(name string) string { |
| 400 | name = strings.ReplaceAll(name, " ", "_") |
| 401 | name = strings.ReplaceAll(name, ".", "_") |
| 402 | return name |
| 403 | } |