munin_ipmi_plugins
view ipmi_sensor_ @ 0:f2e7c91f5ea7
initial release - code written by Ludovic Aubry <ludovic.aubry@logilab.fr>
| author | Arthur Lutz <arthur.lutz@logilab.fr> |
|---|---|
| date | Wed, 10 Jun 2009 17:06:33 +0200 |
| parents | |
| children |
line source
1 #!/usr/bin/python
2 #
3 # munin plugin for the sensors data provided by ipmi
4 #
5 # Copyright (c) 2006 Logilab
6 #
7 # Inspired by code writtent by Peter Palfrader
8 #
9 # This program is free software; you can redistribute it and/or modify it under
10 # the terms of the GNU General Public License as published by the Free Software
11 # Foundation; either version 2 of the License, or (at your option) any later
12 # version.
13 #
14 # This program is distributed in the hope that it will be useful, but WITHOUT
15 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
17 #
18 # You should have received a copy of the GNU General Public License along with
19 # this program; if not, write to the Free Software Foundation, Inc.,
20 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 #
22 #
23 # ipmitool probably needs to be run as root, and it may take more than 10 seconds on some hosts.
24 #
25 # Add the following to your /etc/munin/plugin-conf.d/munin-node:
26 # [ipmi_sensor_*]
27 # user root
28 # timeout 20
29 #
30 # Magic markers
31 #%# capabilities=autoconf suggest
32 #%# family=contrib
34 from subprocess import Popen, PIPE
35 from os import stat, access, R_OK, F_OK
36 from os.path import join
37 from stat import ST_MTIME
38 from time import time
39 import sys
40 import re
42 CACHEDIR = "/var/lib/munin/plugin-state"
43 CACHEFILE = "plugin-ipmi_sensor.cache"
44 CACHEAGE = 120
45 CONFIG = '/etc/munin/plugin-conf.d/ipmi'
49 def normalize_sensor(name):
50 name = name.lower().replace("-","M").replace("+","P")
51 name = re.sub("[^a-z0-9A-Z]","_", name)
52 return name
54 def parse_data( data ):
55 """
56 Parse the data returned by ipmitool get which should be of the
57 following form:
59 Sensor ID : FAN 1 RPM (0x30)
60 Entity ID : 7.1
61 Sensor Type (Analog) : Fan
62 Sensor Reading : 6150 (+/- 75) RPM
63 Status : ok
64 Lower Non-Recoverable : na
65 Lower Critical : 2025.000
66 Lower Non-Critical : na
67 Upper Non-Critical : na
68 Upper Critical : na
69 Upper Non-Recoverable : na
70 Assertion Events :
71 Assertions Enabled : lcr-
72 Deassertions Enabled : lcr-
74 """
75 sensors = {}
76 cur_sensor = None
77 for line in data.splitlines()[1:]:
78 if not line.strip():
79 cur_sensor = None
80 continue
81 if line.startswith("Sensor ID"):
82 label, data = line.split(":", 1)
83 idm = re.match("(.*) \((0x.*)\)", data)
84 if not idm:
85 continue
86 id = idm.group(1).strip()
87 cur_sensor = { "id" : idm.group(2) }
88 sensors[id] = cur_sensor
89 if not cur_sensor:
90 continue
91 label, data = line.split(":", 1)
92 cur_sensor[label.strip()] = data.strip()
93 return sensors
95 def get_sensor_names():
96 p = Popen(["ipmitool","-I","open","sensor"], shell=False, stdout=PIPE)
97 data = p.stdout.readlines()
99 units = {}
100 for k,u in UNITS_TO_SENSORS.items():
101 units[u['vlabel'].lower()] = k
102 sensors = {}
103 for line in data:
104 columns = [ s.strip() for s in line.split('|') ]
105 key = units.get(columns[2].lower(), None)
106 if key:
107 lst = sensors.setdefault(key, [])
108 lst.append( columns[0] )
109 return sensors
111 def get_sensors():
112 cache_filename = join(CACHEDIR,CACHEFILE)
113 try:
114 mtime = stat(cache_filename)[ST_MTIME]
115 except OSError:
116 mtime = 0
117 curtime = time()
119 if curtime-mtime>CACHEAGE:
120 if not SENSORS:
121 p = Popen(["ipmitool","-I","open","sensor"], shell=False, stdout=PIPE)
122 else:
123 p = Popen(["ipmitool","-I","open","sensor", "get"] + SENSORS, shell=False, stdout=PIPE)
124 data = p.stdout.read()
125 try:
126 f = file(cache_filename,"w")
127 f.write(data)
128 except OSError:
129 pass
130 else:
131 data = file(cache_filename).read()
132 return parse_data(data)
134 def query_unit(arg):
135 m = re.search( '_u_(.*)$', arg)
136 if not m:
137 raise RuntimeError("Could not figure which unit you want based on executable name")
138 return m.group(1)
141 UNITS_TO_SENSORS = {
142 'volts' : { 'title' : "Voltages",
143 'args' : '--base 1000',
144 'vlabel' : 'Volts',
145 'info' : "This graph shows the voltages as reported by IPMI",
146 'sensors' : [ 'Voltage 2', ],
147 },
148 'degrees_c' : { 'title' : "Temperature",
149 'args' : '--base 1000 -l 0',
150 'vlabel' : 'Degrees C',
151 'info' : "This graph shows the temperatures as reported by IPMI",
152 'sensors' : [ 'Ambient Temp', ],
153 },
154 'rpm' : { 'title' : "RPMs",
155 'args' : '--base 1000 -l 0',
156 'vlabel' : 'RPM',
157 'info' : "This graph shows the RPMs as reported by IPMI",
158 'sensors' : ['FAN 1 RPM', 'FAN 2 RPM', 'FAN 3 RPM', 'FAN 4 RPM',],
159 },
160 'amps' : { 'title' : "Amperes",
161 'args' : '--base 1000',
162 'vlabel' : 'Amperes',
163 'info' : "This graph shows the amperes as reported by IPMI",
164 'sensors' : ['Current 2'],
165 },
166 'watts' : { 'title' : "Watts",
167 'args' : '--base 1000',
168 'vlabel' : 'Watts',
169 'info' : "This graph shows the watts as reported by IPMI",
170 'sensors' : ['System Level',],
171 },
172 }
175 if access(CONFIG, R_OK):
176 for line in file(CONFIG):
177 if line.strip().startswith('#'):
178 continue
179 data = line.split('=',1)
180 if len(data)!=2:
181 continue
182 unit,sensors = [ d.strip() for d in data ]
183 if unit not in UNITS_TO_SENSORS:
184 continue
185 sensor_list = [ s.strip() for s in sensors.split(',') if s.strip() ]
186 UNITS_TO_SENSORS[unit]['sensors'] = sensor_list
188 SENSORS = []
189 for v in UNITS_TO_SENSORS.values():
190 SENSORS += v['sensors']
193 def config_unit(unit):
194 info = UNITS_TO_SENSORS[unit]
195 data = get_sensors()
196 print "graph_title IPMI Sensors:", info['title']
197 print "graph_args", info['args']
198 print "graph_vlabel", info['vlabel']
199 print "graph_category sensors"
200 print "graph_info", info['info']
201 for lbl in info['sensors']:
202 values = data[lbl]
203 nname = normalize_sensor(lbl)
205 print "%s.label %s" % (nname, lbl)
206 assertions = values['Assertions Enabled'].split()
207 warn_l = warn_u = crit_l = crit_u = ""
208 if 'lcr-' in assertions:
209 crit_l = values['Lower Critical'].replace("na","")
210 if 'lnc-' in assertions:
211 warn_l = values['Lower Non-Critical'].replace("na","")
212 if 'ucr+' in assertions:
213 crit_u = values['Upper Critical'].replace("na","")
214 if 'unc+' in assertions:
215 warn_u = values['Upper Non-Critical'].replace("na","")
216 warn = "%s:%s" % (warn_l,warn_u)
217 crit = "%s:%s" % (crit_l,crit_u)
218 if warn!=":":
219 print "%s.warning %s" % (nname, warn)
220 if crit!=":":
221 print "%s.critical %s" % (nname, crit)
225 def config():
226 unit = query_unit(sys.argv[0])
227 config_unit(unit)
229 def report_unit(unit):
230 info = UNITS_TO_SENSORS[unit]
231 data = get_sensors()
232 for lbl in info['sensors']:
233 nname = normalize_sensor(lbl)
234 value = data[lbl]["Sensor Reading"].split()[0]
235 print "%s.value %s" % (nname, value)
238 def report():
239 unit = query_unit(sys.argv[0])
240 report_unit(unit)
242 def autoconf():
243 data = get_sensors()
244 if data:
245 print "yes"
246 else:
247 print "no (no ipmitool output)"
249 def suggest():
250 names = get_sensor_names()
251 if not os.access(CONFIG, F_OK):
252 f = file(CONFIG, "w")
253 for key, sensors in names.items():
254 f.write("%s = %s\n" % (key, ",".join(sensors)))
255 for key in names.keys():
256 print "u_%s" % key
259 def debug():
260 print SENSORS
261 data = get_sensors()
262 for key, value in data.items():
263 print "%s : %s (%s - %s) [%s - %s] %s" % (key, value['Sensor Reading'],
264 value['Lower Non-Critical'], value['Upper Non-Critical'],
265 value['Lower Critical'], value['Upper Critical'],
266 value['Assertions Enabled'],)
268 def main():
269 if len(sys.argv)>1:
270 command = sys.argv[1]
271 else:
272 command = ""
273 if command=="autoconf":
274 autoconf()
275 elif command=="suggest":
276 suggest()
277 elif command=='config':
278 config()
279 elif command=='debug':
280 debug()
281 else:
282 report()
284 if __name__ == "__main__":
285 main()
