Cleanup config.nodes_of
[check_mk.git] / checks / apc_symmetra
blobdab8d61c984b3d67a64a35ef465fc796aa902351
1 #!/usr/bin/python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 # .1.3.6.1.4.1.318.1.1.1.2.1.1.0 2
28 # .1.3.6.1.4.1.318.1.1.1.4.1.1.0 2
29 # .1.3.6.1.4.1.318.1.1.1.11.1.1.0 0001010000000000001000000000000000000000000000000000000000000000
30 # .1.3.6.1.4.1.318.1.1.1.2.2.1.0 100
31 # .1.3.6.1.4.1.318.1.1.1.2.2.4.0 1
32 # .1.3.6.1.4.1.318.1.1.1.2.2.6.0 0
33 # .1.3.6.1.4.1.318.1.1.1.2.2.3.0 360000
34 # .1.3.6.1.4.1.318.1.1.1.7.2.6.0 2
35 # .1.3.6.1.4.1.318.1.1.1.7.2.4.0 0
36 # .1.3.6.1.4.1.318.1.1.1.2.2.2.0 25
37 # .1.3.6.1.4.1.318.1.1.1.2.2.9.0 0
39 # upsBasicStateOutputState:
40 # The flags are numbered 1 to 64, read from left to right. The flags are defined as follows:
41 # 1: Abnormal Condition Present, 2: On Battery, 3: Low Battery, 4: On Line
42 # 5: Replace Battery, 6: Serial Communication Established, 7: AVR Boost Active
43 # 8: AVR Trim Active, 9: Overload, 10: Runtime Calibration, 11: Batteries Discharged
44 # 12: Manual Bypass, 13: Software Bypass, 14: In Bypass due to Internal Fault
45 # 15: In Bypass due to Supply Failure, 16: In Bypass due to Fan Failure
46 # 17: Sleeping on a Timer, 18: Sleeping until Utility Power Returns
47 # 19: On, 20: Rebooting, 21: Battery Communication Lost, 22: Graceful Shutdown Initiated
48 # 23: Smart Boost or Smart Trim Fault, 24: Bad Output Voltage, 25: Battery Charger Failure
49 # 26: High Battery Temperature, 27: Warning Battery Temperature, 28: Critical Battery Temperature
50 # 29: Self Test In Progress, 30: Low Battery / On Battery, 31: Graceful Shutdown Issued by Upstream Device
51 # 32: Graceful Shutdown Issued by Downstream Device, 33: No Batteries Attached
52 # 34: Synchronized Command is in Progress, 35: Synchronized Sleeping Command is in Progress
53 # 36: Synchronized Rebooting Command is in Progress, 37: Inverter DC Imbalance
54 # 38: Transfer Relay Failure, 39: Shutdown or Unable to Transfer, 40: Low Battery Shutdown
55 # 41: Electronic Unit Fan Failure, 42: Main Relay Failure, 43: Bypass Relay Failure
56 # 44: Temporary Bypass, 45: High Internal Temperature, 46: Battery Temperature Sensor Fault
57 # 47: Input Out of Range for Bypass, 48: DC Bus Overvoltage, 49: PFC Failure
58 # 50: Critical Hardware Fault, 51: Green Mode/ECO Mode, 52: Hot Standby
59 # 53: Emergency Power Off (EPO) Activated, 54: Load Alarm Violation, 55: Bypass Phase Fault
60 # 56: UPS Internal Communication Failure, 57-64: <Not Used>
63 def parse_apc_symmetra(info):
64 parsed = {}
65 if not info:
66 return parsed
68 # some numeric fields may be empty
69 battery_status, output_status, battery_capacity, \
70 battery_replace, battery_num_batt_packs, battery_time_remain, calib_result, \
71 last_diag_date, battery_temp, battery_current, state_output_state = info[0]
73 if state_output_state != '':
74 # string contains a bitmask, convert to int
75 output_state_bitmask = int(state_output_state, 2)
76 else:
77 output_state_bitmask = 0
78 self_test_in_progress = output_state_bitmask & 1 << 35 != 0
80 for key, val in [
81 ("status", battery_status),
82 ("output", output_status),
83 ("self_test", self_test_in_progress),
84 ("capacity", battery_capacity),
85 ("replace", battery_replace),
86 ("num_packs", battery_num_batt_packs),
87 ("time_remain", battery_time_remain),
88 ("calib", calib_result),
89 ("diag_date", last_diag_date),
91 if val:
92 parsed.setdefault("status", {})
93 parsed["status"][key] = val
95 if battery_temp:
96 parsed["temp"] = float(battery_temp)
98 if battery_current:
99 parsed["elphase"] = {"Battery": {"current": float(battery_current)}}
101 return parsed
104 # .--battery status------------------------------------------------------.
105 # | _ _ _ _ _ |
106 # | | |__ __ _| |_| |_ ___ _ __ _ _ ___| |_ __ _| |_ _ _ ___ |
107 # | | '_ \ / _` | __| __/ _ \ '__| | | | / __| __/ _` | __| | | / __| |
108 # | | |_) | (_| | |_| || __/ | | |_| | \__ \ || (_| | |_| |_| \__ \ |
109 # | |_.__/ \__,_|\__|\__\___|_| \__, | |___/\__\__,_|\__|\__,_|___/ |
110 # | |___/ |
111 # '----------------------------------------------------------------------'
113 # old format:
114 # apc_default_levels = ( 95, 40, 1, 220 ) or { "levels" : ( 95, 40, 1, 220 ) }
115 # crit_capacity, crit_sys_temp, crit_batt_curr, crit_voltage = levels
116 # Temperature default now 60C: regadring to a apc technician a temperature up tp 70C is possible
117 factory_settings["apc_default_levels"] = {
118 "capacity": (95, 80),
119 "calibration_state": 0,
120 "battery_replace_state": 1,
124 def inventory_apc_symmetra(parsed):
125 return [(None, {})]
128 def check_apc_symmetra(_no_item, params, parsed):
129 data = parsed["status"]
130 battery_status = data["status"]
131 output_status = data.get("output")
132 self_test_in_progress = data.get("self_test")
133 battery_capacity = data.get("capacity")
134 battery_replace = data.get("replace")
135 battery_num_batt_packs = data.get("num_packs")
136 battery_time_remain = data.get("time_remain")
137 calib_result = data.get("calib")
138 last_diag_date = data.get("diag_date")
140 # convert old format tuple to dict, new format with up to 6 params in dict
141 if isinstance(params, tuple):
142 params = {"levels": params}
144 if "levels" in params:
145 crit_cap = params["levels"][0]
146 params["capacity"] = (crit_cap, crit_cap)
148 alt_crit_capacity = None
149 warn_cap, crit_cap = params['capacity']
150 # the last_diag_date is reported as %m/%d/%Y or %y
151 if params.get("post_calibration_levels") and \
152 last_diag_date not in [ None, 'Unknown' ] and \
153 len(last_diag_date) in [8, 10]:
154 year_format = '%y' if len(last_diag_date) == 8 else '%Y'
155 last_ts = time.mktime(time.strptime(last_diag_date, '%m/%d/' + year_format))
156 diff_sec = time.time() - last_ts
157 allowed_delay_sec = 86400 + params['post_calibration_levels']['additional_time_span']
158 alt_crit_capacity = params['post_calibration_levels']['altcapacity']
160 state, state_readable = {
161 "1": (3, "unknown"),
162 "2": (0, "normal"),
163 "3": (2, "low"),
164 "4": (2, "in fault condition"),
165 }.get(battery_status, (3, "unexpected(%s)" % battery_status))
166 yield state, "Battery status: %s" % state_readable
168 if battery_replace:
169 state, state_readable = {
170 "1": (0, "no battery needs replacing"),
171 "2": (params.get("battery_replace_state", 1), "battery needs replacing"),
172 }.get(battery_replace, (3, "battery needs replacing: unknown"))
173 if battery_num_batt_packs and int(battery_num_batt_packs) > 1:
174 yield 2, "%i batteries need replacement" % int(battery_num_batt_packs)
175 elif state:
176 yield state, state_readable
178 if output_status:
179 output_status_txts = {
180 "1": "unknown",
181 "2": "on line",
182 "3": "on battery",
183 "4": "on smart boost",
184 "5": "timed sleeping",
185 "6": "software bypass",
186 "7": "off",
187 "8": "rebooting",
188 "9": "switched bypass",
189 "10": "hardware failure bypass",
190 "11": "sleeping until power return",
191 "12": "on smart trim",
192 "13": "eco mode",
193 "14": "hot standby",
194 "15": "on battery test",
195 "16": "emergency static bypass",
196 "17": "static bypass standby",
197 "18": "power saving mode",
198 "19": "spot mode",
199 "20": "e conversion",
201 state_readable = output_status_txts.get(output_status, "unexpected(%s)" % output_status)
203 if output_status not in output_status_txts:
204 state = 3
205 elif output_status not in ["2", "4", "12"] and \
206 calib_result != "3" and not self_test_in_progress:
207 state = 2
208 elif output_status in ["2", "4", "12"] and \
209 calib_result == "2" and not self_test_in_progress:
210 state = params.get("calibration_state")
211 else:
212 state = 0
214 calib_text = {
215 "1": "",
216 "2": " (calibration invalid)",
217 "3": " (calibration in progress)",
218 }.get(calib_result, " (calibration unexpected(%s))" % calib_result)
220 yield state, "Output status: %s%s%s" % (
221 state_readable, calib_text, self_test_in_progress and " (self-test running)" or "")
223 if battery_capacity:
224 battery_capacity = int(battery_capacity)
225 state = 0
226 levelstxt = ""
227 if alt_crit_capacity is not None and diff_sec < allowed_delay_sec:
228 if battery_capacity < alt_crit_capacity:
229 state = 2
230 levelstxt = " (crit below %d%% in delay after calibration)" % alt_crit_capacity
231 else:
232 if battery_capacity < crit_cap:
233 state = 2
234 levelstxt = " (warn/crit below %.1f%%/%.1f%%)" % (warn_cap, crit_cap)
235 elif battery_capacity < warn_cap:
236 state = 1
237 levelstxt = " (warn/crit below %.1f%%/%.1f%%)" % (warn_cap, crit_cap)
239 yield state, "Capacity: %d%%%s" % (battery_capacity, levelstxt), \
240 [("capacity", battery_capacity, warn_cap, crit_cap, 0, 100)]
242 if battery_time_remain:
243 battery_time_remain = float(battery_time_remain) / 100
244 battery_time_remain_readable = get_age_human_readable(battery_time_remain)
245 state = 0
246 levelstxt = ""
247 battery_time_warn, battery_time_crit = None, None
248 if params.get('battime'):
249 battery_time_warn, battery_time_crit = params['battime']
250 if battery_time_remain < battery_time_crit:
251 state = 2
252 elif battery_time_remain < battery_time_warn:
253 state = 1
254 perfdata = [("runtime", battery_time_remain / 60, battery_time_warn / 60,
255 battery_time_crit / 60)]
256 else:
257 perfdata = [("runtime", battery_time_remain / 60)]
259 if state:
260 levelstxt = " (warn/crit below %s/%s)" % \
261 (get_age_human_readable(battery_time_warn),
262 get_age_human_readable(battery_time_crit))
264 yield state, "Time remaining: %s%s" % (battery_time_remain_readable, levelstxt), perfdata
267 check_info['apc_symmetra'] = {
268 "parse_function": parse_apc_symmetra,
269 "inventory_function": inventory_apc_symmetra,
270 "check_function": check_apc_symmetra,
271 "service_description": "APC Symmetra status",
272 # A Note on the order of OIDs: If the 11.1.1.0 is not the last to be polled,
273 # this leads to bogus values for some other OIDs on some devices.
274 "snmp_info": (
275 ".1.3.6.1.4.1.318.1.1.1",
277 "2.1.1.0", # PowerNet-MIB::upsBasicBatteryStatus,
278 "4.1.1.0", # PowerNet-MIB::upsBasicOutputStatus,
279 "2.2.1.0", # PowerNet-MIB::upsAdvBatteryCapacity,
280 "2.2.4.0", # PowerNet-MIB::upsAdvBatteryReplaceIndicator,
281 "2.2.6.0", # PowerNet-MIB::upsAdvBatteryNumOfBadBattPacks,
282 "2.2.3.0", # PowerNet-MIB::upsAdvBatteryRunTimeRemaining,
283 "7.2.6.0", # PowerNet-MIB::upsAdvTestCalibrationResults
284 "7.2.4.0", # PowerNet-MIB::upsLastDiagnosticsDate
285 "2.2.2.0", # PowerNet-MIB::upsAdvBatteryTemperature,
286 "2.2.9.0", # PowerNet-MIB::upsAdvBatteryCurrent,
287 "11.1.1.0", # PowerNet-MIB::upsBasicStateOutputState
289 "snmp_scan_function": lambda oid: oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.318.1.3"),
290 "has_perfdata": True,
291 "group": "apc_symentra",
292 "default_levels_variable": "apc_default_levels",
296 # .--temperature---------------------------------------------------------.
297 # | _ _ |
298 # | | |_ ___ _ __ ___ _ __ ___ _ __ __ _| |_ _ _ _ __ ___ |
299 # | | __/ _ \ '_ ` _ \| '_ \ / _ \ '__/ _` | __| | | | '__/ _ \ |
300 # | | || __/ | | | | | |_) | __/ | | (_| | |_| |_| | | | __/ |
301 # | \__\___|_| |_| |_| .__/ \___|_| \__,_|\__|\__,_|_| \___| |
302 # | |_| |
303 # '----------------------------------------------------------------------'
305 # Temperature default now 60C: regadring to a apc technician a temperature up tp 70C is possible
306 factory_settings["apc_symmetra_temp_default_levels"] = {"levels": (50, 60)}
309 def inventory_apc_symmetra_temp(parsed):
310 if "temp" in parsed:
311 return [("Battery", {})]
314 def check_apc_symmetra_temp(item, params, parsed):
315 return check_temperature(parsed["temp"], params, "check_apc_symmetra_temp.%s" % item)
318 check_info['apc_symmetra.temp'] = {
319 'inventory_function': inventory_apc_symmetra_temp,
320 'check_function': check_apc_symmetra_temp,
321 'service_description': "Temperature %s",
322 'default_levels_variable': 'apc_symmetra_temp_default_levels',
323 'has_perfdata': True,
324 'group': 'temperature',
325 'includes': ['temperature.include'],
329 # .--el phase------------------------------------------------------------.
330 # | _ _ |
331 # | ___| | _ __ | |__ __ _ ___ ___ |
332 # | / _ \ | | '_ \| '_ \ / _` / __|/ _ \ |
333 # | | __/ | | |_) | | | | (_| \__ \ __/ |
334 # | \___|_| | .__/|_| |_|\__,_|___/\___| |
335 # | |_| |
336 # '----------------------------------------------------------------------'
338 factory_settings["apc_symmetra_elphase_default_levels"] = {"current": (1, 1)}
341 def inventory_apc_symmetra_elphase(parsed):
342 for phase in parsed.get("elphase", {}):
343 yield phase, {}
346 def check_apc_symmetra_elphase(item, params, parsed):
347 return check_elphase(item, params, parsed.get("elphase", {}))
350 check_info['apc_symmetra.elphase'] = {
351 'inventory_function': inventory_apc_symmetra_elphase,
352 'check_function': check_apc_symmetra_elphase,
353 'service_description': "Phase %s",
354 'has_perfdata': True,
355 'default_levels_variable': 'apc_symmetra_elphase_default_levels',
356 'group': 'ups_outphase',
357 'includes': ['elphase.include'],