2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
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
):
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)
77 output_state_bitmask
= 0
78 self_test_in_progress
= output_state_bitmask
& 1 << 35 != 0
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
),
92 parsed
.setdefault("status", {})
93 parsed
["status"][key
] = val
96 parsed
["temp"] = float(battery_temp
)
99 parsed
["elphase"] = {"Battery": {"current": float(battery_current
)}}
104 # .--battery status------------------------------------------------------.
106 # | | |__ __ _| |_| |_ ___ _ __ _ _ ___| |_ __ _| |_ _ _ ___ |
107 # | | '_ \ / _` | __| __/ _ \ '__| | | | / __| __/ _` | __| | | / __| |
108 # | | |_) | (_| | |_| || __/ | | |_| | \__ \ || (_| | |_| |_| \__ \ |
109 # | |_.__/ \__,_|\__|\__\___|_| \__, | |___/\__\__,_|\__|\__,_|___/ |
111 # '----------------------------------------------------------------------'
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
):
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
= {
164 "4": (2, "in fault condition"),
165 }.get(battery_status
, (3, "unexpected(%s)" % battery_status
))
166 yield state
, "Battery status: %s" % state_readable
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
)
176 yield state
, state_readable
179 output_status_txts
= {
183 "4": "on smart boost",
184 "5": "timed sleeping",
185 "6": "software bypass",
188 "9": "switched bypass",
189 "10": "hardware failure bypass",
190 "11": "sleeping until power return",
191 "12": "on smart trim",
194 "15": "on battery test",
195 "16": "emergency static bypass",
196 "17": "static bypass standby",
197 "18": "power saving 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
:
205 elif output_status
not in ["2", "4", "12"] and \
206 calib_result
!= "3" and not self_test_in_progress
:
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")
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 "")
224 battery_capacity
= int(battery_capacity
)
227 if alt_crit_capacity
is not None and diff_sec
< allowed_delay_sec
:
228 if battery_capacity
< alt_crit_capacity
:
230 levelstxt
= " (crit below %d%% in delay after calibration)" % alt_crit_capacity
232 if battery_capacity
< crit_cap
:
234 levelstxt
= " (warn/crit below %.1f%%/%.1f%%)" % (warn_cap
, crit_cap
)
235 elif battery_capacity
< warn_cap
:
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
)
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
:
252 elif battery_time_remain
< battery_time_warn
:
254 perfdata
= [("runtime", battery_time_remain
/ 60, battery_time_warn
/ 60,
255 battery_time_crit
/ 60)]
257 perfdata
= [("runtime", battery_time_remain
/ 60)]
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.
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---------------------------------------------------------.
298 # | | |_ ___ _ __ ___ _ __ ___ _ __ __ _| |_ _ _ _ __ ___ |
299 # | | __/ _ \ '_ ` _ \| '_ \ / _ \ '__/ _` | __| | | | '__/ _ \ |
300 # | | || __/ | | | | | |_) | __/ | | (_| | |_| |_| | | | __/ |
301 # | \__\___|_| |_| |_| .__/ \___|_| \__,_|\__|\__,_|_| \___| |
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
):
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------------------------------------------------------------.
331 # | ___| | _ __ | |__ __ _ ___ ___ |
332 # | / _ \ | | '_ \| '_ \ / _` / __|/ _ \ |
333 # | | __/ | | |_) | | | | (_| \__ \ __/ |
334 # | \___|_| | .__/|_| |_|\__,_|___/\___| |
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", {}):
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'],