├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── configuration-question.md
│ └── feature_request.md
├── .gitignore
├── BSD
├── mini_ipmi_bsdcpu.py
├── sudoers.d
│ └── zabbix
└── zabbix_agentd.conf.d
│ └── userparameter_mini-ipmi2.conf
├── KEYS.md
├── Linux
├── mini_ipmi_lmsensors.py
├── sudoers.d
│ └── zabbix
└── zabbix_agentd.d
│ └── userparameter_mini-ipmi2.conf
├── README.md
├── Template_mini-IPMI_v2.xml
├── UNLICENSE
├── Win
├── mini_ipmi_ohmr.py
└── zabbix_agentd.d
│ └── userparameter_mini-ipmi2.conf
├── mini_ipmi_smartctl.py
├── screenshots
├── mini-IPMI-graph.png
├── mini-IPMI-triggers-config.png
├── mini-IPMI-triggers-cpu.png
├── mini-IPMI-triggers-disk.png
├── python-installation1.png
└── python-installation2.png
└── sender_wrapper.py
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Report a bug
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Provide all outputs described in [Testing](https://github.com/nobodysu/zabbix-mini-IPMI#testing) step**
23 | Serial numbers should be replaced with X_SERIAL_X.
24 |
25 | **Please complete the following information:**
26 | - OS: [e.g. Debian]
27 | - Zabbix server version: [e.g. 3.0]
28 | - Active or passive check: [e.g. Passive]
29 | - Using zabbix proxy: [e.g. No proxy]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/configuration-question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Configuration question
3 | about: Ask a question about configuration problem
4 | title: ''
5 | labels: question
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the problem**
11 | A clear and concise description of what the problem is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Provide all outputs described in [Testing](https://github.com/nobodysu/zabbix-mini-IPMI#testing) step**
23 | Serial numbers should be replaced with X_SERIAL_X.
24 |
25 | **Please complete the following information:**
26 | - OS: [e.g. Debian]
27 | - Zabbix server version: [e.g. 3.0]
28 | - Active or passive check: [e.g. Passive]
29 | - Using zabbix proxy: [e.g. No proxy]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
--------------------------------------------------------------------------------
/BSD/mini_ipmi_bsdcpu.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | ## Installation instructions: https://github.com/nobodysu/zabbix-mini-IPMI ##
4 |
5 | BIN_PATH = r'/sbin/sysctl'
6 |
7 | # path to second send script
8 | SENDER_WRAPPER_PATH = r'/usr/local/etc/zabbix/scripts/sender_wrapper.py'
9 |
10 | # path to zabbix agent configuration file
11 | AGENT_CONF_PATH = r'/usr/local/etc/zabbix3/zabbix_agentd.conf'
12 |
13 | SENDER_PATH = r'zabbix_sender'
14 | #SENDER_PATH = r'/usr/bin/zabbix_sender'
15 |
16 | DELAY = '50' # how long the script must wait between LLD and sending, increase if data received late (does not affect windows)
17 | # this setting MUST be lower than 'Update interval' in discovery rule
18 | TJMAX = '70'
19 |
20 | ## End of configuration ##
21 |
22 | import sys
23 | import subprocess
24 | import re
25 | from sender_wrapper import (readConfig, processData, fail_ifNot_Py3)
26 |
27 | HOST = sys.argv[2]
28 |
29 |
30 | def getOutput(binPath_):
31 |
32 | p = None
33 | try:
34 | p = subprocess.check_output([binPath_, 'dev.cpu'], universal_newlines=True)
35 | except OSError as e:
36 | if e.args[0] == 2:
37 | error = 'OS_NOCMD'
38 | else:
39 | error = 'OS_ERROR'
40 | if sys.argv[1] == 'getverb':
41 | raise
42 | except Exception as e:
43 | error = 'UNKNOWN_EXC_ERROR'
44 |
45 | if sys.argv[1] == 'getverb':
46 | raise
47 |
48 | try:
49 | p = e.output
50 | except:
51 | pass
52 | else:
53 | error = 'CONFIGURED'
54 |
55 | return error, p
56 |
57 |
58 | def getCpuData(pOut_):
59 | '''Can work unexpectedly with multiple CPUs.'''
60 | sender = []
61 | json = []
62 |
63 | tempRe = re.findall(r'dev\.cpu\.(\d+)\.temperature:\s+(\d+)', pOut_, re.I)
64 | if tempRe:
65 | error = None
66 | json.append({'{#CPU}':'0'})
67 |
68 | allTemps = []
69 | for num, val in tempRe:
70 | allTemps.append(val)
71 | sender.append('"%s" mini.cpu.temp[cpu0,core%s] "%s"' % (HOST, num, val))
72 | json.append({'{#CPUC}':'0', '{#CORE}':num})
73 |
74 | sender.append('"%s" mini.cpu.info[cpu0,TjMax] "%s"' % (HOST, TJMAX))
75 | sender.append('"%s" mini.cpu.temp[cpu0,MAX] "%s"' % (HOST, max(allTemps)))
76 | sender.append('"%s" mini.cpu.temp[MAX] "%s"' % (HOST, max(allTemps)))
77 |
78 | else:
79 | error = 'NOCPUTEMPS'
80 |
81 | return sender, json, error
82 |
83 |
84 | if __name__ == '__main__':
85 |
86 | fail_ifNot_Py3()
87 |
88 | senderData = []
89 | jsonData = []
90 |
91 | p_Output = getOutput(BIN_PATH)
92 | pRunStatus = p_Output[0]
93 | pOut = p_Output[1]
94 |
95 | errors = None
96 | if pOut:
97 | getCpuData_Out = getCpuData(pOut)
98 | cpuErrors = getCpuData_Out[2]
99 | senderData.extend(getCpuData_Out[0])
100 | jsonData.extend(getCpuData_Out[1])
101 | if cpuErrors:
102 | errors = 'cpu_err'
103 | senderData.append('"%s" mini.cpu.info[ConfigStatus] "%s"' % (HOST, cpuErrors))
104 |
105 | if not errors:
106 | senderData.append('"%s" mini.cpu.info[ConfigStatus] "%s"' % (HOST, pRunStatus)) # OS_NOCMD, OS_ERROR, UNKNOWN_EXC_ERROR, CONFIGURED
107 |
108 | link = r'https://github.com/nobodysu/zabbix-mini-IPMI/issues'
109 | sendStatusKey = 'mini.cpu.info[SendStatus]'
110 | processData(senderData, jsonData, AGENT_CONF_PATH, SENDER_WRAPPER_PATH, SENDER_PATH, DELAY, HOST, link, sendStatusKey)
111 |
112 |
--------------------------------------------------------------------------------
/BSD/sudoers.d/zabbix:
--------------------------------------------------------------------------------
1 | #Defaults:zabbix !requiretty # Older sudo
2 |
3 | zabbix ALL=NOPASSWD: /usr/sbin/smartctl, /usr/bin/smartctl, /usr/local/sbin/smartctl, /sbin/smartctl, /bin/smartctl
4 |
5 |
--------------------------------------------------------------------------------
/BSD/zabbix_agentd.conf.d/userparameter_mini-ipmi2.conf:
--------------------------------------------------------------------------------
1 | UserParameter=mini.disktemp.discovery[*], PATH=/usr/local/sbin:/usr/local/bin "/usr/local/etc/zabbix/scripts/mini_ipmi_smartctl.py" "$1" "$2"
2 | UserParameter=mini.cputemp.discovery[*], PATH=/usr/local/sbin:/usr/local/bin "/usr/local/etc/zabbix/scripts/mini_ipmi_bsdcpu.py" "$1" "$2"
3 |
--------------------------------------------------------------------------------
/KEYS.md:
--------------------------------------------------------------------------------
1 | | Key | Supported in |
2 | | ------------------------------------------------------------ | ------------------- |
3 | | mini.brd.info[BIOSvendor] | mini_ipmi_ohmr.py |
4 | | mini.brd.info[BIOSversion] | mini_ipmi_ohmr.py |
5 | |mini.brd.info[MainboardManufacturer]|mini_ipmi_ohmr.py|
6 | |mini.brd.info[MainboardName]|mini_ipmi_ohmr.py|
7 | |mini.brd.info[MainboardVersion]|mini_ipmi_ohmr.py|
8 | |mini.brd.info[SMBIOSversion]|mini_ipmi_ohmr.py|
9 | |mini.brd.info[vcoreMax]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
10 | |mini.brd.info[vttMax]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
11 | |mini.brd.temp[MAX]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
12 | |mini.brd.vlt[_N_]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
13 | |mini.cpu.info[ConfigStatus]| mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py, mini_ipmi_bsdcpu.py|
14 | |mini.cpu.temp[MAX]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py, mini_ipmi_bsdcpu.py|
15 | |mini.gpu.temp[MAX]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
16 | |mini.info[OHMRver]| mini_ipmi_ohmr.py|
17 | |mini.brd.fan[{#BRDFANNUM},rpm]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
18 | |mini.brd.temp[{#BRDTEMPNUM}]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
19 | |mini.brd.vlt[{#P5V}]|mini_ipmi_lmsensors.py|
20 | |mini.brd.vlt[{#P12V}]|mini_ipmi_lmsensors.py|
21 | |mini.brd.vlt[{#P33V}]|mini_ipmi_lmsensors.py|
22 | |mini.brd.vlt[{#VAVCC}]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
23 | |mini.brd.vlt[{#VBAT}]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py| |
24 | |mini.brd.vlt[{#VCC3V}]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
25 | |mini.brd.vlt[{#VCORE}]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
26 | |mini.brd.vlt[{#VSB3V}]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
27 | |mini.brd.vlt[{#VTT}]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
28 | |mini.cpu.info[cpu{#CPU},CPUstatus]|mini_ipmi_ohmr.py|
29 | |mini.cpu.info[cpu{#CPU},ID]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
30 | | mini.cpu.info[cpu{#CPU},TjMax]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
31 | |mini.cpu.temp[cpu{#CPUC},core{#CORE}]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py, mini_ipmi_bsdcpu.py|
32 | |mini.cpu.temp[cpu{#CPU},MAX]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py, mini_ipmi_bsdcpu.py|
33 | | mini.gpu.fan[gpu{#GPUFAN},rpm]|mini_ipmi_ohmr.py|
34 | |mini.gpu.info[gpu{#GPU},GPUstatus]|mini_ipmi_ohmr.py|
35 | |mini.gpu.info[gpu{#GPU},ID]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
36 | |mini.gpu.memory[gpu{#GPUMEM},free]|mini_ipmi_ohmr.py|
37 | |mini.gpu.memory[gpu{#GPUMEM},total]|mini_ipmi_ohmr.py|
38 | |mini.gpu.memory[gpu{#GPUMEM},used]|mini_ipmi_ohmr.py|
39 | |mini.gpu.temp[gpu{#GPUTEMP}]|mini_ipmi_ohmr.py, mini_ipmi_lmsensors.py|
40 | |mini.disk.info[ConfigStatus]|mini_ipmi_smartctl.py|
41 | |mini.disk.info[{#DISK},DriveStatus]|mini_ipmi_smartctl.py|
42 | |mini.disk.temp[{#DISK}]|mini_ipmi_smartctl.py|
43 | |mini.disk.temp[MAX]|mini_ipmi_smartctl.py|
44 |
--------------------------------------------------------------------------------
/Linux/mini_ipmi_lmsensors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | BIN_PATH = r'sensors' # -u
4 | #BIN_PATH = r'/usr/bin/sensors' # if 'sensors' isn't in PATH
5 |
6 | # path to second send script
7 | SENDER_WRAPPER_PATH = r'/etc/zabbix/scripts/sender_wrapper.py'
8 |
9 | # path to zabbix agent configuration file
10 | AGENT_CONF_PATH = r'/etc/zabbix/zabbix_agentd.conf'
11 |
12 | SENDER_PATH = r'zabbix_sender'
13 | #SENDER_PATH = r'/usr/bin/zabbix_sender'
14 |
15 | FALLBACK_TJMAX = '70'
16 |
17 | # Following settings brings (almost) no overhead. True or False
18 | GATHER_VOLTAGES = True
19 | GATHER_BOARD_FANS = True
20 | GATHER_BOARD_TEMPS = True
21 | GATHER_GPU_DATA = True
22 | GATHER_CPU_DATA = True
23 |
24 | VOLTAGE_REGEXPS_KEYS_AND_JSONS = (
25 | ('Vcore', 'cpuVcore', '{#VCORE}'),
26 | ('VBAT', 'VBat', '{#VBAT}'),
27 | ('3VSB|VSB3V|Standby \+3\.3V|3V_SB', 'VSB3V', '{#VSB3V}'),
28 | ('3VCC|VCC3V', 'VCC3V', '{#VCC3V}'),
29 | ('AVCC', 'AVCC', '{#AVCC}'),
30 | ('VTT', 'VTT', '{#VTT}'),
31 | ('\+3\.3 Voltage', 'p3.3V', '{#p3.3V}'),
32 | ('\+5 Voltage', 'p5V', '{#p5V}'),
33 | ('\+12 Voltage', 'p12V', '{#p12V}'),
34 | )
35 |
36 | # re.I | re.M
37 | CORES_REGEXPS = (
38 | ('Core(?:\s+)?(\d+):\n\s+temp\d+_input:\s+(\d+)'),
39 | ('Core(\d+)\s+Temp:\n\s+temp\d+_input:\s+(\d+)'),
40 | ('Tdie:\n\s+temp(\d+)_input:\s+(\d+)'),
41 | ('Tccd(\d+):\n\s+temp\d+_input:\s+(\d+)'),
42 | ('k\d+temp-pci-\w+\nAdapter:\s+PCI\s+adapter\ntemp(\d+):\n\s+temp\d+_input:\s+(\d+)'),
43 | )
44 |
45 | IGNORED_SENSORS = (
46 | ('nct6791-isa-0290', 'AUXTIN3'), # ignore 'AUXTIN3' on 'nct6791-isa-0290'
47 | )
48 |
49 | DELAY = '50' # how long the script must wait between LLD and sending, increase if data received late (does not affect windows)
50 | # this setting MUST be lower than 'Update interval' in discovery rule
51 |
52 | ## End of configuration ##
53 |
54 | import sys
55 | import subprocess
56 | import re
57 | from sender_wrapper import (readConfig, processData, fail_ifNot_Py3, removeQuotes)
58 |
59 | HOST = sys.argv[2]
60 |
61 |
62 | def getOutput(binPath_):
63 | try:
64 | from subprocess import DEVNULL # for python versions greater than 3.3, inclusive
65 | except:
66 | import os
67 | DEVNULL = open(os.devnull, 'w') # for 3.0-3.2, inclusive
68 |
69 | p = None
70 | try:
71 | p = subprocess.check_output([binPath_, '-u'], universal_newlines=True, stderr=DEVNULL)
72 | except OSError as e:
73 | if e.args[0] == 2:
74 | error = 'OS_NOCMD'
75 | else:
76 | error = 'OS_ERROR'
77 | if sys.argv[1] == 'getverb':
78 | raise
79 | except Exception as e:
80 | error = 'UNKNOWN_EXC_ERROR'
81 |
82 | if sys.argv[1] == 'getverb':
83 | raise
84 |
85 | try:
86 | p = e.output
87 | except:
88 | pass
89 | else:
90 | error = 'CONFIGURED'
91 |
92 | if p:
93 | p = p.strip()
94 | p = p.split('\n\n')
95 |
96 | return error, p
97 |
98 |
99 | def getVoltages(pOut_):
100 | sender = []
101 | json = []
102 |
103 | for block in pOut_:
104 | if 'Adapter: PCI adapter' in block: # we dont need GPU voltages
105 | continue
106 |
107 | voltagesRe = re.findall(r'(.+):\n(?:\s+)?in(\d+)_input:\s+(\d+\.\d+)', block, re.I)
108 | if voltagesRe:
109 | for name, num, val in voltagesRe:
110 |
111 | for regexp, key, jsn in VOLTAGE_REGEXPS_KEYS_AND_JSONS:
112 | if re.search(regexp, name, re.I):
113 | sender.append('"%s" mini.brd.vlt[%s] "%s"' % (HOST, key, removeQuotes(val)))
114 | json.append({jsn:key})
115 |
116 | sender.append('"%s" mini.brd.vlt[%s] "%s"' % (HOST, num, removeQuotes(val))) # static items for graph, could be duplicate
117 |
118 | break # as safe as possible
119 |
120 | return sender, json
121 |
122 |
123 | def getBoardFans(pOut_):
124 |
125 | sender = []
126 | json = []
127 |
128 | for i in pOut_:
129 | if 'Adapter: PCI adapter' in i: # we dont need GPU fans
130 | continue
131 |
132 | fans = re.findall(r'(.+):\n(?:\s+)?fan(\d+)_input:\s+(\d+)', i, re.I)
133 | if fans:
134 | for name, num, val in fans:
135 | # only create LLD when speed is not zero, BUT always send values including zero (prevents false triggering)
136 | sender.append('"%s" mini.brd.fan[%s,rpm] "%s"' % (HOST, num, val))
137 | if val != '0':
138 | json.append({'{#BRDFANNAME}':name.strip(), '{#BRDFANNUM}':num})
139 |
140 | break
141 |
142 | return sender, json
143 |
144 |
145 | def getBoardTemps(pOut_):
146 |
147 | sender = []
148 | json = []
149 |
150 | for i in pOut_:
151 | if 'Adapter: PCI adapter' in i: # we dont need GPU temps
152 | continue
153 |
154 | blockIdentRe = re.search(r'^.+', i)
155 | if blockIdentRe:
156 | blockIdent = blockIdentRe.group(0).strip()
157 | else:
158 | blockIdent = None
159 |
160 | temps = re.findall(r'((?:CPU|GPU|MB|M/B|AUX|Ambient|Other|SYS|Processor).+):\n(?:\s+)?temp(\d+)_input:\s+(\d+)', i, re.I)
161 | if temps:
162 | for name, num, val in temps:
163 | if isIgnoredMbSensor(blockIdent, name):
164 | continue
165 |
166 | sender.append('"%s" mini.brd.temp[%s] "%s"' % (HOST, num, val))
167 | json.append({'{#BRDTEMPNAME}':name.strip(), '{#BRDTEMPNUM}':num})
168 |
169 | break # unrelated blocks
170 |
171 | return sender, json
172 |
173 |
174 | def isIgnoredMbSensor(ident_, sensor_):
175 |
176 | result = False
177 | for ident, sensor in IGNORED_SENSORS:
178 | if (ident_ == ident and
179 | sensor_ == sensor):
180 |
181 | result = True
182 | break
183 |
184 | return result
185 |
186 |
187 | def getGpuData(pOut_):
188 |
189 | sender = []
190 | json = []
191 |
192 | gpuBlocks = -1
193 | allTemps = []
194 | for i in pOut_:
195 | temp = re.search(r'(nouveau.+|nvidia.+|radeon.+)\n.+\n.+\n(?:\s+)?temp\d+_input:\s+(\d+)', i, re.I)
196 | if temp:
197 | gpuid = temp.group(1)
198 | val = temp.group(2)
199 |
200 | gpuBlocks += 1
201 | allTemps.append(int(val))
202 |
203 | json.append({'{#GPU}':gpuBlocks})
204 | sender.append('"%s" mini.gpu.info[gpu%s,ID] "%s"' % (HOST, gpuBlocks, removeQuotes(gpuid)))
205 |
206 | json.append({'{#GPUTEMP}':gpuBlocks})
207 | sender.append('"%s" mini.gpu.temp[gpu%s] "%s"' % (HOST, gpuBlocks, val))
208 |
209 | if gpuBlocks != -1:
210 | if allTemps:
211 | error = None
212 | sender.append('"%s" mini.gpu.temp[MAX] "%s"' % (HOST, max(allTemps)))
213 | else:
214 | error = 'NOGPUTEMPS' # unreachable
215 | else:
216 | error = 'NOGPUS'
217 |
218 | return sender, json, error
219 |
220 |
221 | def chooseCpuRegexp(block_):
222 |
223 | result = ''
224 | for regexp in CORES_REGEXPS:
225 | match = re.search(regexp, block_, re.I | re.M)
226 | if match:
227 | result = regexp
228 |
229 | return result
230 |
231 |
232 | def getCpuData(pOut_):
233 | '''Note: certain cores can pose as different blocks making them separate cpus in zabbix.'''
234 | sender = []
235 | json = []
236 |
237 | cpuBlocks = -1 # first cpu will be '0'
238 | allTemps = []
239 | for block in pOut_:
240 | regexp = chooseCpuRegexp(block)
241 |
242 | coreTempsRe = re.findall(regexp, block, re.I | re.M)
243 |
244 | if (coreTempsRe and
245 | regexp):
246 |
247 | cpuBlocks += 1 # you need to be creative to parse lmsensors
248 |
249 | json.append({'{#CPU}':cpuBlocks})
250 | sender.append('"%s" mini.cpu.info[cpu%s,ID] "%s"' % (HOST, cpuBlocks, removeQuotes(block.splitlines()[0])))
251 |
252 | tempCrit = re.search(r'_crit:\s+(\d+)\.\d+', block, re.I)
253 | if tempCrit:
254 | tjMax = tempCrit.group(1)
255 | else:
256 | tjMax = FALLBACK_TJMAX
257 |
258 | sender.append('"%s" mini.cpu.info[cpu%s,TjMax] "%s"' % (HOST, cpuBlocks, tjMax))
259 |
260 | cpuTemps = []
261 | previousCore = None
262 | for num, val in coreTempsRe:
263 | if previousCore == num:
264 | continue # some cores have same number - ignore them
265 | previousCore = num
266 |
267 | cpuTemps.append(int(val))
268 | allTemps.append(int(val))
269 | sender.append('"%s" mini.cpu.temp[cpu%s,core%s] "%s"' % (HOST, cpuBlocks, num, val))
270 | json.append({'{#CPUC}':cpuBlocks, '{#CORE}':num})
271 |
272 | sender.append('"%s" mini.cpu.temp[cpu%s,MAX] "%s"' % (HOST, cpuBlocks, max(cpuTemps)))
273 |
274 | if cpuBlocks != -1:
275 | if allTemps:
276 | error = None
277 | sender.append('"%s" mini.cpu.temp[MAX] "%s"' % (HOST, max(allTemps)))
278 | else:
279 | error = 'NOCPUTEMPS'
280 | else:
281 | error = 'NOCPUS'
282 |
283 | return sender, json, error
284 |
285 |
286 | if __name__ == '__main__':
287 |
288 | fail_ifNot_Py3()
289 |
290 | senderData = []
291 | jsonData = []
292 | statusErrors = []
293 |
294 | p_Output = getOutput(BIN_PATH)
295 | pRunStatus = p_Output[0]
296 | pOut = p_Output[1]
297 |
298 | if pOut:
299 | if GATHER_VOLTAGES:
300 | getVoltages_Out = getVoltages(pOut)
301 | senderData.extend(getVoltages_Out[0])
302 | jsonData.extend(getVoltages_Out[1])
303 |
304 | if GATHER_BOARD_FANS:
305 | getBoardFans_Out = getBoardFans(pOut)
306 | senderData.extend(getBoardFans_Out[0])
307 | jsonData.extend(getBoardFans_Out[1])
308 |
309 | if GATHER_BOARD_TEMPS:
310 | getBoardTemps_Out = getBoardTemps(pOut)
311 | senderData.extend(getBoardTemps_Out[0])
312 | jsonData.extend(getBoardTemps_Out[1])
313 |
314 | if GATHER_GPU_DATA:
315 | getGpuData_Out = getGpuData(pOut)
316 | gpuErrors = getGpuData_Out[2]
317 | senderData.extend(getGpuData_Out[0])
318 | jsonData.extend(getGpuData_Out[1])
319 | if gpuErrors:
320 | statusErrors.append(gpuErrors) # NOGPUS, NOGPUTEMPS
321 |
322 | if GATHER_CPU_DATA:
323 | getCpuData_Out = getCpuData(pOut)
324 | cpuErrors = getCpuData_Out[2]
325 | senderData.extend(getCpuData_Out[0])
326 | jsonData.extend(getCpuData_Out[1])
327 | if cpuErrors:
328 | statusErrors.append(cpuErrors) # NOCPUS, NOCPUTEMPS
329 |
330 | if statusErrors:
331 | errorsString = ', '.join(statusErrors).strip()
332 | senderData.append('"%s" mini.cpu.info[ConfigStatus] "%s"' % (HOST, errorsString))
333 | else:
334 | senderData.append('"%s" mini.cpu.info[ConfigStatus] "%s"' % (HOST, pRunStatus)) # OS_NOCMD, OS_ERROR, UNKNOWN_EXC_ERROR, CONFIGURED
335 |
336 | link = r'https://github.com/nobody43/zabbix-mini-IPMI/issues'
337 | sendStatusKey = 'mini.cpu.info[SendStatus]'
338 | processData(senderData, jsonData, AGENT_CONF_PATH, SENDER_WRAPPER_PATH, SENDER_PATH, DELAY, HOST, link, sendStatusKey)
339 |
340 |
--------------------------------------------------------------------------------
/Linux/sudoers.d/zabbix:
--------------------------------------------------------------------------------
1 | #Defaults:zabbix !requiretty # Older sudo
2 |
3 | zabbix ALL=NOPASSWD: /usr/sbin/smartctl, /usr/bin/smartctl, /usr/local/sbin/smartctl, /sbin/smartctl, /bin/smartctl
4 |
5 |
--------------------------------------------------------------------------------
/Linux/zabbix_agentd.d/userparameter_mini-ipmi2.conf:
--------------------------------------------------------------------------------
1 | UserParameter=mini.disktemp.discovery[*], "/etc/zabbix/scripts/mini_ipmi_smartctl.py" "$1" "$2"
2 | UserParameter=mini.cputemp.discovery[*], "/etc/zabbix/scripts/mini_ipmi_lmsensors.py" "$1" "$2"
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # zabbix-mini-IPMI
2 | CPU and disk temperature monitoring scripts for zabbix. Also support voltage and fan speed monitoring on certain configurations. Uses `lm-sensors`, `smartmontools` and `OpenHardwareMonitorReport`. For Linux, BSD and Windows.
3 |
4 | ## Features
5 |
6 | - Multi-CPU, disk and GPU solution
7 | - Low-Level Discovery
8 | - Bulk item upload with zabbix-sender
9 | - No unnecessary processes are spawned
10 | - Does not spin idle drives
11 | - RAID passthrough (manual)
12 |
13 | 
14 |
15 | [More screenshots.](https://github.com/nobody43/zabbix-mini-IPMI/tree/master/screenshots)
16 |
17 | ### Choosing OHMR version
18 | Only custom param-capable versions are supported on Windows 7+:
19 | #### [0.9.6.0](https://github.com/openhardwaremonitor/openhardwaremonitor/pull/1115#issuecomment-1189362017)
20 | #### [0.9.2.0](https://github.com/openhardwaremonitor/openhardwaremonitor/pull/1115#issuecomment-616230088)
21 | #### [0.8.0.5](https://github.com/openhardwaremonitor/openhardwaremonitor/pull/1115#issuecomment-462141642)
22 | Version for Windows XP:
23 | #### [0.3.2.0](https://github.com/openhardwaremonitor/openhardwaremonitor/issues/230#issue-102662845)
24 |
25 | ## Installation
26 | As prerequisites you need `python3`, `lm-sensors`, `smartmontools`, `sudo` and `zabbix-sender` packages. For testing `zabbix-get` is also required.
27 | Take a look at scripts first lines and provide paths if needed. If you have custom RAID configuration, also provide that manually. Import `Template_mini-IPMI_v2.xml` in zabbix web interface.
28 | ### Debian / Ubuntu
29 | ```bash
30 | client# apt install python3 sudo zabbix-agent zabbix-sender smartmontools lm-sensors
31 | server# apt install zabbix-get
32 | ```
33 | ### Centos
34 | ```bash
35 | client# yum install python3 sudo zabbix-agent zabbix-sender smartmontools lm_sensors
36 | server# yum install zabbix-get
37 | ```
38 | ### First step
39 | > **Note**: Your include directory may be either `zabbix_agentd.d` or `zabbix_agentd.conf.d` dependent on the distribution.
40 | #### Linux
41 | ```bash
42 | client# mv mini_ipmi_smartctl.py Linux/mini_ipmi_lmsensors.py sender_wrapper.py /etc/zabbix/scripts/
43 | client# mv Linux/sudoers.d/zabbix /etc/sudoers.d/ # place sudoers include for smartctl sudo access
44 | client# mv Linux/zabbix_agentd.d/userparameter_mini-ipmi2.conf /etc/zabbix/zabbix_agentd.d/
45 | ```
46 |
47 | #### FreeBSD
48 | ```bash
49 | client# mv mini_ipmi_smartctl.py BSD/mini_ipmi_bsdcpu.py sender_wrapper.py /etc/zabbix/scripts/
50 | client# mv BSD/sudoers.d/zabbix /usr/local/etc/sudoers.d/
51 | client# mv BSD/zabbix_agentd.d/userparameter_mini-ipmi2.conf /usr/local/etc/zabbix/zabbix_agentd.d/
52 | ```
53 | Then, for Intel processor you need to add `coretemp_load="YES"` to `/boot/loader.conf`. For AMD it will be `amdtemp_load="YES"`. Reboot or manual `kldload` is required to take effect.
54 |
55 | #### Windows
56 | ```cmd
57 | client> move mini_ipmi_smartctl.py "C:\Program Files\Zabbix Agent\scripts\"
58 | client> move mini_ipmi_ohmr.py "C:\Program Files\Zabbix Agent\scripts\"
59 | client> move sender_wrapper.py "C:\Program Files\Zabbix Agent\scripts\"
60 | client> move userparameter_mini-ipmi2.conf "C:\Program Files\Zabbix Agent\zabbix_agentd.d\"
61 | ```
62 | Install [python3](https://www.python.org/downloads/windows/), [adding it to](https://github.com/nobody43/zabbix-mini-IPMI/blob/master/screenshots/python-installation1.png) `PATH` during installation for [all users](https://github.com/nobody43/zabbix-mini-IPMI/blob/master/screenshots/python-installation2.png). Install [smartmontools](https://www.smartmontools.org/wiki/Download#InstalltheWindowspackage) and add its bin folder to `PATH` in environment variables. `OpenHardwareMonitorReport` `0.8.0.5+` requires `.NET Framework 4`. `0.3.2.0` requires `.NET Framework 3`.
63 |
64 | ### Second step
65 | Dependent on the distribution, you may need to include your zabbix conf folder in `zabbix_agentd.conf`, like this:
66 | ```conf
67 | Include=/usr/local/etc/zabbix/zabbix_agentd.d/
68 | ```
69 | Its recomended to add at least `Timeout=10` to server and client config files to allow drives spun up and OHMR execution.
70 |
71 | Thats all for Windows. For others run the following to finish configuration:
72 | ```bash
73 | client# chmod 755 scripts/mini_ipmi*.py scripts/sender_wrapper.py # apply necessary permissions
74 | client# chown root:zabbix scripts/mini_ipmi*.py scripts/sender_wrapper.py
75 | client# chmod 644 userparameter_mini-ipmi2.conf
76 | client# chown root:zabbix userparameter_mini-ipmi2.conf
77 | client# chmod 400 sudoers.d/zabbix
78 | client# chown root sudoers.d/zabbix
79 | client# visudo # test sudoers configuration, type :q! to exit
80 | ```
81 |
82 | ## Testing
83 | ```bash
84 | server$ zabbix_get -s 192.0.2.1 -k mini.cputemp.discovery[get,"Example host"]
85 | server$ zabbix_get -s 192.0.2.1 -k mini.disktemp.discovery[get,"Example host"]
86 | ```
87 | or locally:
88 | ```bash
89 | client$ /etc/zabbix/scripts/mini_ipmi_lmsensors.py get "Example host"
90 | client$ /etc/zabbix/scripts/mini_ipmi_smartctl.py get "Example host"
91 | ```
92 | Default operation mode. Displays json that server should get, detaches, then waits and sends data with zabbix-sender. `Example host` is your `Host name` field in zabbix. You might want to use nonexistent name for testing to avoid unnecessary database pollution (client introduces itself with this name and false names will be ignored).
93 |
94 |
95 | ```bash
96 | server$ zabbix_get -s 192.0.2.1 -k mini.cputemp.discovery[getverb,"Example host"]
97 | server$ zabbix_get -s 192.0.2.1 -k mini.disktemp.discovery[getverb,"Example host"]
98 | ```
99 | or locally:
100 | ```mixed
101 | client$ /etc/zabbix/scripts/mini_ipmi_lmsensors.py getverb "Example host"
102 | client_admin!_console> python "C:\Program Files\Zabbix Agent\scripts\mini_ipmi_ohmr.py" getverb "Example host"
103 | ```
104 | Verbose mode. Does not detaches or prints LLD. Lists all items sent to zabbix-sender, also it is possible to see sender output in this mode.
105 |
106 |
107 | These scripts were tested to work with following configurations:
108 | - Debian 11 / Server (5.0, 6.0) / Agent 4.0 / Python 3.9
109 | - Ubuntu 22.04 / Server (5.0, 6.0) / Agent 5.0 / Python 3.10
110 | - Windows Server 2012 / Server 6.0 / Agent 4.0 / Python (3.7, 3.11)
111 | - Windows 10 / Server 6.0 / Agent 4.0 / Python (3.10, 3.11)
112 | - Windows 7 / Server 6.0 / Agent 4.0 / Python (3.4, 3.7, 3.8)
113 | - Centos 7 / Zabbix 3.0 / Python 3.6
114 | - FreeBSD 10.3 / Zabbix 3.0 / Python 3.6
115 | - Windows XP / Zabbix 3.0 / Python 3.4
116 |
117 | ## Updating
118 | Overwrite scripts and UserParameters. If UserParameters were changed - agent restart is required. If template had changed from previous version - update it in zabbix web interface [marking](https://github.com/nobody43/zabbix-smartmontools/blob/main/screenshots/template-updating.png) all `Delete missing` checkboxes.
119 |
120 | > **Note**: low values in php settings `/etc/httpd/conf.d/zabbix.conf` may result in request failure. Especially `php_value memory_limit`.
121 |
122 | ## Known issues
123 | - Zabbix web panel displays an error on json discovery, but everything works fine ([#18](https://github.com/nobody43/zabbix-mini-IPMI/issues/18))
124 | - Windows version does not detaches, and data will only be gathered on second pass
125 |
126 | ## Links
127 | - https://www.smartmontools.org
128 | - https://wiki.archlinux.org/index.php/Lm_sensors
129 | - https://github.com/openhardwaremonitor/openhardwaremonitor
130 | - https://unlicense.org
131 | - [Disk SMART monitoring solution](https://github.com/nobody43/zabbix-smartmontools)
132 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/Win/mini_ipmi_ohmr.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | #BIN_PATH = r'OpenHardwareMonitorReport.exe'
4 | BIN_PATH = r'C:\Program Files\OpenHardwareMonitorReport\OpenHardwareMonitorReport.exe' # if OHMR isn't in PATH
5 |
6 | # path to send script
7 | SENDER_WRAPPER_PATH = r'C:\Program Files\Zabbix Agent\scripts\sender_wrapper.py'
8 |
9 | # path to zabbix agent configuration file
10 | AGENT_CONF_PATH = r'C:\Program Files\Zabbix Agent\zabbix_agentd.conf'
11 |
12 | #SENDER_PATH = r'zabbix_sender'
13 | SENDER_PATH = r'C:\Program Files\Zabbix Agent\zabbix_sender.exe'
14 |
15 | PARAMS = 'reporttoconsole --IgnoreMonitorHDD --IgnoreMonitorRAM'
16 | # Possible params:
17 | # --IgnoreMonitorCPU
18 | # --IgnoreMonitorFanController
19 | # --IgnoreMonitorGPU
20 | # --IgnoreMonitorHDD
21 | # --IgnoreMonitorMainboard
22 | # --IgnoreMonitorRAM
23 |
24 | SKIP_PARAMS_ON_WINXP = True # True or False
25 |
26 | # Advanced configuration
27 |
28 | # Supply absent Tjmax
29 | MANUAL_TJMAXES = (
30 | ('AMD Athlon 64 X2 Dual Core Processor 5200+', '72'),
31 | )
32 |
33 | # Ignore specific temperature by name on specific board
34 | IGNORED_SENSORS = (
35 | ('H110M-R', 'Temperature #6'), # ignore 'Temperature #6' on board 'H110M-R'
36 | # ('EXAMPLE_BOARDNAME', 'EXAMPLE_TEMPERATURENAME'),
37 | # ('Pull requests', 'are welcome'),
38 | )
39 |
40 | # These CPUs will not produce NO_TEMP problem
41 | CPUS_WITHOUT_SENSOR = (
42 | 'Intel Pentium 4 3.00GHz',
43 | )
44 |
45 | GPUS_WITHOUT_SENSOR = (
46 | 'AMD Radeon Vega 8 Graphics',
47 | 'AMD Radeon Vega 10 Graphics',
48 | )
49 |
50 | BOARD_REGEXPS_AND_KEYS = (
51 | ('^SMBIOS\s+Version:\s+(.+)$', 'mini.brd.info[SMBIOSversion]'),
52 | ('^BIOS\s+Vendor:\s+(.+)$', 'mini.brd.info[BIOSvendor]'),
53 | ('^BIOS\s+Version:\s+(.+)$', 'mini.brd.info[BIOSversion]'),
54 | ('^Mainboard\s+Manufacturer:\s+(.+)$', 'mini.brd.info[MainboardManufacturer]'),
55 | ('^Mainboard\s+Name:\s+(.+)$', 'mini.brd.info[MainboardName]'),
56 | ('^Mainboard\s+Version:\s+(.+)$', 'mini.brd.info[MainboardVersion]'),
57 | )
58 |
59 | VOLTAGE_REGEXPS_KEYS_AND_JSONS = (
60 | ('^VCore$', 'cpuVcore', '{#VCORE}'),
61 | ('^VBAT$', 'VBat', '{#VBAT}'),
62 | ('(^3VSB$|^VSB3V$|^Standby \+3\.3V$)', 'VSB3V', '{#VSB3V}'),
63 | ('(^3VCC$|^VCC3V$)', 'VCC3V', '{#VCC3V}'),
64 | ('^AVCC$', 'AVCC', '{#VAVCC}'),
65 | ('^VTT$', 'VTT', '{#VTT}'),
66 | )
67 |
68 | DELAY = '50' # how long the script must wait between LLD and sending, increase if data received late (does not affect windows)
69 | # this setting MUST be lower than 'Update interval' in the discovery rule
70 |
71 | ## End of configuration ##
72 |
73 | import sys
74 | import subprocess
75 | import re
76 | import platform
77 | from sender_wrapper import (readConfig, processData, fail_ifNot_Py3, removeQuotes)
78 |
79 | HOST = sys.argv[2]
80 |
81 |
82 | def chooseCmd(binPath_, params_):
83 |
84 | cmd = '%s %s' % (binPath_, params_)
85 |
86 | if not SKIP_PARAMS_ON_WINXP:
87 | if platform.release() == "XP":
88 | cmd = binPath_
89 |
90 | return cmd
91 |
92 |
93 | def getOutput(cmd_):
94 |
95 | p = None
96 | try:
97 | p = subprocess.check_output(cmd_, universal_newlines=True)
98 | except OSError as e:
99 | if e.args[0] == 2:
100 | status = 'OS_NOCMD'
101 | else:
102 | status = 'OS_ERROR'
103 | if sys.argv[1] == 'getverb':
104 | raise
105 | except Exception as e:
106 | status = 'UNKNOWN_EXC_ERROR'
107 |
108 | if sys.argv[1] == 'getverb':
109 | raise
110 |
111 | try:
112 | p = e.output
113 | except:
114 | pass
115 | else:
116 | status = 'CONFIGURED'
117 |
118 | # Prevent empty results
119 | if p:
120 | m0 = 'Status: Extracting driver failed'
121 | m1 = 'First Exception: OpenSCManager returned zero.'
122 | m2 = 'Second Exception: OpenSCManager returned zero.'
123 | if (m0 in p or
124 | m1 in p or
125 | m2 in p):
126 |
127 | print('OHMR failed. Try again.')
128 | sys.exit(1)
129 |
130 | return status, p
131 |
132 |
133 | def getOHMRversion(pOut_):
134 |
135 | OHMRver = re.search(r'^Version:\s+(.+)$', pOut_, re.I | re.M)
136 | if OHMRver:
137 | version = OHMRver.group(1).strip()
138 | else:
139 | version = None
140 |
141 | sender = ['"%s" mini.info[OHMRversion] "%s"' % (HOST, removeQuotes(version))]
142 |
143 | return sender
144 |
145 |
146 | def getBoardInfo(pOut_):
147 |
148 | sender = []
149 |
150 | for regexp, key in BOARD_REGEXPS_AND_KEYS:
151 | reMatch = re.search(regexp, pOut_, re.I | re.M)
152 | if reMatch:
153 | sender.append('"%s" %s "%s"' % (HOST, key, removeQuotes(reMatch.group(1).strip())))
154 |
155 | return sender
156 |
157 |
158 | def getBoardName(pOut_):
159 |
160 | boardRe = re.search(r'^Mainboard\s+Name:\s+(.+)$', pOut_, re.I | re.M)
161 | if boardRe:
162 | board = boardRe.group(1).strip()
163 | else:
164 | board = None
165 |
166 | return board
167 |
168 |
169 | def getTjmax(pOut_, cpuID_, cpuName_):
170 |
171 | tjMaxRe = re.search(r'\(\/[\w-]+cpu\/%s\/temperature\/\d+\)\s+\|\s+\|\s+\+\-\s+TjMax\s+\[\S+\]\s+:\s+(\d+)' % cpuID_, pOut_, re.I | re.M)
172 | if tjMaxRe:
173 | tjmax = tjMaxRe.group(1)
174 | else:
175 | tjmax = None
176 | for name, val in MANUAL_TJMAXES:
177 | if name == cpuName_:
178 | tjmax = val
179 | break
180 |
181 | return tjmax
182 |
183 |
184 | def isCpuWithoutSensor(cpuname_):
185 |
186 | if cpuname_ in CPUS_WITHOUT_SENSOR:
187 | result = True
188 | else:
189 | result = False
190 |
191 | return result
192 |
193 |
194 | def isCpuSensorPresent(pOut_):
195 |
196 | coreTempsRe = re.search(r'Core.+:\s+\d+.+\(\/[\w-]+cpu\/\d+\/temperature\/\d+\)', pOut_, re.I)
197 | if coreTempsRe:
198 | result = True
199 | else:
200 | result = False
201 |
202 | return result
203 |
204 |
205 | def isGpuWithoutSensor(gpuname_):
206 |
207 | if gpuname_ in GPUS_WITHOUT_SENSOR:
208 | result = True
209 | else:
210 | result = False
211 |
212 | return result
213 |
214 |
215 | def isParamIgnored(param_):
216 |
217 | if param_ in PARAMS:
218 | result = True
219 | else:
220 | result = False
221 |
222 | return result
223 |
224 |
225 | def getCpusData(pOut_):
226 |
227 | sender = []
228 | json = []
229 |
230 | # determine available CPUs
231 | cpusRe = re.findall(r'\+\-\s+(.+)\s+\(\/[\w-]+cpu\/(\d+)\)', pOut_, re.I)
232 | cpus = set(cpusRe)
233 | #print(cpusRe)
234 |
235 | allTemps = []
236 | for name, id in cpus:
237 | # Processor model
238 | sender.append('"%s" mini.cpu.info[cpu%s,ID] "%s"' % (HOST, id, removeQuotes(name.strip())))
239 | json.append({'{#CPU}':id})
240 |
241 | gotTjmax = getTjmax(pOut_, id, name)
242 | if gotTjmax:
243 | sender.append('"%s" mini.cpu.info[cpu%s,TjMax] "%s"' % (HOST, id, gotTjmax))
244 |
245 | # All core temperatures for given CPU
246 | coreTempsRe = re.findall(r'Core.+:\s+(\d+).+\(\/[\w-]+cpu\/%s\/temperature\/(\d+)\)' % id, pOut_, re.I)
247 | if coreTempsRe:
248 | sender.append('"%s" mini.cpu.info[cpu%s,CPUstatus] "PROCESSED"' % (HOST, id))
249 | cpuTemps = []
250 | for coretemp, coreid in coreTempsRe:
251 | cpuTemps.append(int(coretemp))
252 | allTemps.append(int(coretemp))
253 | sender.append('"%s" mini.cpu.temp[cpu%s,core%s] "%s"' % (HOST, id, coreid, coretemp))
254 | json.append({'{#CPUC}':id, '{#CORE}':coreid})
255 |
256 | sender.append('"%s" mini.cpu.temp[cpu%s,MAX] "%s"' % (HOST, id, str(max(cpuTemps))))
257 |
258 | elif isCpuWithoutSensor(name):
259 | sender.append('"%s" mini.cpu.info[cpu%s,CPUstatus] "NO_SENSOR"' % (HOST, id))
260 | else:
261 | packageTempsRe = re.findall(r'Package.+:\s+(\d+).+\(\/[\w-]+cpu\/%s\/temperature\/(\d+)\)' % id, pOut_, re.I)
262 | if packageTempsRe:
263 | sender.append('"%s" mini.cpu.info[cpu%s,CPUstatus] "PROCESSED"' % (HOST, id))
264 | for packagetemp, packageid in packageTempsRe:
265 | allTemps.append(int(packagetemp))
266 |
267 | else:
268 | sender.append('"%s" mini.cpu.info[cpu%s,CPUstatus] "NO_TEMP"' % (HOST, id))
269 |
270 | if cpus:
271 | if allTemps:
272 | error = None
273 | sender.append('"%s" mini.cpu.temp[MAX] "%s"' % (HOST , str(max(allTemps))))
274 | else:
275 | error = 'NOCPUTEMPS'
276 | else:
277 | error = 'NOCPUS'
278 |
279 | return sender, json, error
280 |
281 |
282 | def getVoltages(pOut_):
283 |
284 | sender = []
285 | json = []
286 |
287 | voltagesRe = re.findall(r'\+\-\s+(.+)\s+:\s+(\d+\.\d+|\d+)\s+.+\(\/lpc\/[\w-]+\/voltage\/(\d+)\)', pOut_, re.I)
288 | for name, val, id in voltagesRe:
289 | name = name.strip()
290 |
291 | for regexp, key, jsn in VOLTAGE_REGEXPS_KEYS_AND_JSONS:
292 | if re.search(regexp, name, re.I):
293 | sender.append('"%s" mini.brd.vlt[%s] "%s"' % (HOST, key, removeQuotes(val)))
294 | json.append({jsn:key})
295 |
296 | sender.append('"%s" mini.brd.vlt[%s] "%s"' % (HOST, id, removeQuotes(val))) # static items for graph, could be duplicate
297 |
298 | return sender, json
299 |
300 |
301 | def getBoardFans(pOut_):
302 |
303 | sender = []
304 | json = []
305 |
306 | fansRe = re.findall(r'\+\-\s+(.+)\s+:\s+(\d+).+\(\/lpc\/[\w-]+\/fan\/(\d+)\)', pOut_, re.I)
307 | for name, val, num in fansRe:
308 | name = name.strip()
309 |
310 | # Only create LLD when speed is not zero, BUT always send zero values (hides phantom fans)
311 | sender.append('"%s" mini.brd.fan[%s,rpm] "%s"' % (HOST, num, val))
312 | if val != '0':
313 | json.append({'{#BRDFANNAME}':name, '{#BRDFANNUM}':num})
314 |
315 | return sender, json
316 |
317 |
318 | def getBoardTemps(pOut_):
319 |
320 | sender = []
321 | json = []
322 |
323 | board = getBoardName(pOut_)
324 |
325 | tempsRe = re.findall(r'\+\-\s+(.+)\s+:\s+(\d+).+\(\/lpc\/[\w-]+\/temperature\/(\d+)\)', pOut_, re.I)
326 | #print(tempsRe)
327 |
328 | allTemps = []
329 | for name, val, id in tempsRe:
330 | name = name.strip()
331 |
332 | if (isCpuSensorPresent(pOut_) and
333 | re.match('^CPU Core$|^CPU$', name)):
334 |
335 | continue
336 |
337 | ignoredSensor = False
338 | if board:
339 | for boardReference, ignoredTempName in IGNORED_SENSORS:
340 | if (boardReference == board and
341 | ignoredTempName == name):
342 |
343 | ignoredSensor = True
344 |
345 | if ignoredSensor:
346 | continue
347 |
348 | allTemps.append(int(val))
349 |
350 | sender.append('"%s" mini.brd.temp[%s] "%s"' % (HOST, id, val))
351 | json.append({'{#BRDTEMPNAME}':name, '{#BRDTEMPNUM}':id})
352 |
353 | if allTemps:
354 | sender.append('"%s" mini.brd.temp[MAX] "%s"' % (HOST, str(max(allTemps))))
355 |
356 | return sender, json
357 |
358 |
359 | def getGpusData(pOut_):
360 |
361 | sender = []
362 | json = []
363 |
364 | # Determine available GPUs
365 | gpusRe = re.findall(r'\+\-\s+(.+)\s+\(\/[\w-]+gpu\/(\d+)\)', pOut_, re.I)
366 | gpus = set(gpusRe)
367 |
368 | allTemps = []
369 | for name, num in gpus:
370 | errors = []
371 | sender.append('"%s" mini.gpu.info[gpu%s,ID] "%s"' % (HOST, num, name.strip()))
372 | json.append({'{#GPU}':num})
373 |
374 | temp = re.search(r':\s+(\d+).+\(\/[\w-]+gpu\/%s\/temperature\/0\)' % num, pOut_, re.I)
375 | if temp:
376 | json.append({'{#GPUTEMP}':num})
377 | allTemps.append(int(temp.group(1)))
378 | sender.append('"%s" mini.gpu.temp[gpu%s] "%s"' % (HOST, num, temp.group(1)))
379 | elif isGpuWithoutSensor(name):
380 | errors.append('NO_SENSOR')
381 | else:
382 | errors.append('NO_TEMP')
383 |
384 | fanspeed = re.search(r':\s+(\d+).+\(\/[\w-]+gpu\/%s\/fan\/0\)' % num, pOut_, re.I)
385 | if fanspeed:
386 | sender.append('"%s" mini.gpu.fan[gpu%s,rpm] "%s"' % (HOST, num, fanspeed.group(1)))
387 | if fanspeed.group(1) != '0':
388 | json.append({'{#GPUFAN}':num})
389 | else:
390 | errors.append('NO_FAN')
391 |
392 | memory = re.findall(r'\+\-\s+(GPU\s+Memory\s+Free|GPU\s+Memory\s+Used|GPU\s+Memory\s+Total)\s+:\s+(\d+).+\(\/[\w-]+gpu\/%s\/smalldata\/\d+\)' % num, pOut_, re.I)
393 | if memory:
394 | json.append({'{#GPUMEM}':num})
395 | for memname, memval in memory:
396 | if 'Free' in memname:
397 | sender.append('"%s" mini.gpu.memory[gpu%s,free] "%s"' % (HOST, num, memval))
398 | elif 'Used' in memname:
399 | sender.append('"%s" mini.gpu.memory[gpu%s,used] "%s"' % (HOST, num, memval))
400 | elif 'Total' in memname:
401 | sender.append('"%s" mini.gpu.memory[gpu%s,total] "%s"' % (HOST, num, memval))
402 |
403 | if errors:
404 | for e in errors:
405 | sender.append('"%s" mini.gpu.info[gpu%s,GPUstatus] "%s"' % (HOST, num, e)) # NO_TEMP, NO_FAN, NO_SENSOR
406 | else:
407 | sender.append('"%s" mini.gpu.info[gpu%s,GPUstatus] "PROCESSED"' % (HOST, num))
408 |
409 | if gpus:
410 | statusError = None
411 | if allTemps:
412 | sender.append('"%s" mini.gpu.temp[MAX] "%s"' % (HOST, str(max(allTemps))))
413 | else:
414 | statusError = 'NOGPUS'
415 |
416 | return sender, json, statusError
417 |
418 |
419 | if __name__ == '__main__':
420 |
421 | fail_ifNot_Py3()
422 |
423 | senderData = []
424 | jsonData = []
425 | statusErrors = []
426 |
427 | cmd = chooseCmd(BIN_PATH, PARAMS)
428 |
429 | p_Output = getOutput(cmd)
430 | pRunStatus = p_Output[0]
431 | pOut = p_Output[1]
432 |
433 | if pOut:
434 | senderData.extend(getOHMRversion(pOut))
435 |
436 | senderData.extend(getBoardInfo(pOut))
437 |
438 | if not isParamIgnored('--IgnoreMonitorCPU'):
439 | cpuData_Out = getCpusData(pOut)
440 | if cpuData_Out:
441 | cpuSender = cpuData_Out[0]
442 | cpuJson = cpuData_Out[1]
443 | cpuError = cpuData_Out[2]
444 |
445 | senderData.extend(cpuSender)
446 | jsonData.extend(cpuJson)
447 |
448 | if cpuError:
449 | statusErrors.append(cpuError)
450 |
451 | if not isParamIgnored('--IgnoreMonitorMainboard'):
452 | boardTemps_Out = getBoardTemps(pOut)
453 | if boardTemps_Out:
454 | boardSender = boardTemps_Out[0]
455 | boardJson = boardTemps_Out[1]
456 |
457 | senderData.extend(boardSender)
458 | jsonData.extend(boardJson)
459 |
460 | voltages_Out = getVoltages(pOut)
461 | if voltages_Out:
462 | voltagesSender = voltages_Out[0]
463 | voltagesJson = voltages_Out[1]
464 |
465 | senderData.extend(voltagesSender)
466 | jsonData.extend(voltagesJson)
467 |
468 | if not isParamIgnored('--IgnoreMonitorFanController'):
469 | boardFans_Out = getBoardFans(pOut)
470 | if boardFans_Out:
471 | senderData.extend(boardFans_Out[0])
472 | jsonData.extend(boardFans_Out[1])
473 |
474 | if not isParamIgnored('--IgnoreMonitorGPU'):
475 | gpuData_Out = getGpusData(pOut)
476 | if gpuData_Out:
477 | gpuSender = gpuData_Out[0]
478 | gpuJson = gpuData_Out[1]
479 | gpuError = gpuData_Out[2]
480 |
481 | senderData.extend(gpuSender)
482 | jsonData.extend(gpuJson)
483 |
484 | if gpuError:
485 | statusErrors.append(gpuError)
486 |
487 | if statusErrors:
488 | errorsString = ', '.join(statusErrors).strip()
489 | senderData.append('"%s" mini.cpu.info[ConfigStatus] "%s"' % (HOST, errorsString))
490 | elif pRunStatus:
491 | senderData.append('"%s" mini.cpu.info[ConfigStatus] "%s"' % (HOST, pRunStatus))
492 |
493 | link = r'https://github.com/nobody43/zabbix-mini-IPMI/issues'
494 | sendStatusKey = 'mini.cpu.info[SendStatus]'
495 | processData(senderData, jsonData, AGENT_CONF_PATH, SENDER_WRAPPER_PATH, SENDER_PATH, DELAY, HOST, link, sendStatusKey)
496 |
--------------------------------------------------------------------------------
/Win/zabbix_agentd.d/userparameter_mini-ipmi2.conf:
--------------------------------------------------------------------------------
1 | UserParameter=mini.disktemp.discovery[*], python "C:\Program Files\Zabbix Agent\scripts\mini_ipmi_smartctl.py" "$1" "$2"
2 | UserParameter=mini.cputemp.discovery[*], python "C:\Program Files\Zabbix Agent\scripts\mini_ipmi_ohmr.py" "$1" "$2"
3 |
--------------------------------------------------------------------------------
/mini_ipmi_smartctl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Only one out of three system-specific setting is used, PATH considered.
4 | binPath_LINUX = r'smartctl'
5 | binPath_WIN = r'C:\Program Files\smartmontools\bin\smartctl.exe'
6 | binPath_OTHER = r'/usr/local/sbin/smartctl'
7 |
8 | # path to zabbix agent configuration file
9 | agentConf_LINUX = r'/etc/zabbix/zabbix_agentd.conf'
10 | agentConf_WIN = r'C:\Program Files\Zabbix Agent\zabbix_agentd.conf'
11 | agentConf_OTHER = r'/usr/local/etc/zabbix3/zabbix_agentd.conf'
12 |
13 | senderPath_LINUX = r'zabbix_sender'
14 | senderPath_WIN = r'C:\Program Files\Zabbix Agent\zabbix_sender.exe'
15 | senderPath_OTHER = r'/usr/local/bin/zabbix_sender'
16 |
17 | # path to second send script
18 | senderPyPath_LINUX = r'/etc/zabbix/scripts/sender_wrapper.py'
19 | senderPyPath_WIN = r'C:\Program Files\Zabbix Agent\scripts\sender_wrapper.py'
20 | senderPyPath_OTHER = r'/usr/local/etc/zabbix/scripts/sender_wrapper.py'
21 |
22 |
23 | ## Advanced configuration ##
24 | # 'True' or 'False'
25 | isCheckNVMe = False # Additional overhead. Should be disabled if smartmontools is >= 7 or NVMe is absent.
26 |
27 | isCheckSAS = False # Use '-a' instead of '-A', which may produce ERR_CODE_*. Slight overhead.
28 |
29 | isIgnoreDuplicates = True
30 |
31 | isHeavyDebug = False
32 |
33 | # type, min, max, critical
34 | thresholds = (
35 | ('hdd', 25, 45, 60),
36 | ('ssd', 5, 55, 70),
37 | )
38 |
39 | perDiskTimeout = 3 # Single disk query can not exceed this value. Python33 or above required.
40 |
41 | delay = '50' # How long the script must wait between LLD and sending, increase if data received late (does not affect windows).
42 | # This setting MUST be lower than 'Update interval' in the discovery rule.
43 |
44 | # Manually provide disk list or RAID configuration if needed.
45 | diskDevsManual = []
46 | # like this:
47 | #diskDevsManual = ['/dev/bus/0 -d megaraid,4', '/dev/bus/0 -d megaraid,5']
48 | # more info: https://www.smartmontools.org/wiki/Supported_RAID-Controllers
49 |
50 | # These models will not produce 'NOTEMP' warning. Pull requests are welcome.
51 | noTemperatureSensorModels = (
52 | 'INTEL SSDSC2CW060A3',
53 | 'AXXROMBSASMR',
54 | 'PLEXTOR PX-256M6Pro',
55 | )
56 |
57 | # re.IGNORECASE | re.MULTILINE
58 | modelPatterns = (
59 | '^Device Model:\s+(.+)$',
60 | '^Device:\s+(.+)$',
61 | '^Product:\s+(.+)$',
62 | '^Model Number:\s+(.+)$',
63 | )
64 |
65 | # First match returned right away; re.IGNORECASE | re.MULTILINE
66 | temperaturePatterns = (
67 | '^(?:\s+)?\d+\s+Temperature_Celsius\s+[\w-]+\s+\d{3}\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+(\d+)',
68 | '^(?:\s+)?\d+\s+Temperature_Internal\s+[\w-]+\s+\d{3}\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+(\d+)',
69 | '^(?:\s+)?\d+\s+Temperature_Case\s+[\w-]+\s+\d{3}\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+(\d+)',
70 | '^(?:\s+)?Current\s+Drive\s+Temperature:\s+(\d+)\s+',
71 | '^(?:\s+)?Temperature:\s+(\d+)\s+C',
72 | '^(?:\s+)?\d+\s+Airflow_Temperature_Cel\s+[\w-]+\s+\d{3}\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+[\w-]+\s+(\d+)',
73 | )
74 |
75 | ## End of configuration ##
76 |
77 |
78 | import sys
79 | import subprocess
80 | import re
81 | import shlex
82 | from sender_wrapper import (fail_ifNot_Py3, sanitizeStr, clearDiskTypeStr, processData)
83 |
84 | HOST = sys.argv[2]
85 |
86 |
87 | def scanDisks(mode):
88 | '''Determines available disks. Can be skipped.'''
89 | if mode == 'NOTYPE':
90 | cmd = addSudoIfNix([binPath, '--scan'])
91 | elif mode == 'NVME':
92 | cmd = addSudoIfNix([binPath, '--scan', '-d', 'nvme'])
93 | else:
94 | print('Invalid type %s. Terminating.' % mode)
95 | sys.exit(1)
96 |
97 | try:
98 | p = subprocess.check_output(cmd, universal_newlines=True)
99 | error = ''
100 | except OSError as e:
101 | p = ''
102 |
103 | if e.args[0] == 2:
104 | error = 'SCAN_OS_NOCMD_%s' % mode
105 | else:
106 | error = 'SCAN_OS_ERROR_%s' % mode
107 |
108 | except Exception as e:
109 | try:
110 | p = e.output
111 | except:
112 | p = ''
113 |
114 | error = 'SCAN_UNKNOWN_ERROR_%s' % mode
115 | if sys.argv[1] == 'getverb':
116 | raise
117 |
118 | # TESTING
119 | #if mode == 'NVME': p = '''/dev/nvme0 -d nvme # /dev/nvme0, NVMe device\n/dev/bus/0 -d megaraid,4 # /dev/bus/0 [megaraid_disk_04], SCSI device'''
120 |
121 | # Determine full device names and types
122 | disks = re.findall(r'^(/dev/[^#]+)', p, re.M)
123 |
124 | return error, disks
125 |
126 |
127 | def moveCsmiToBegining(disks):
128 |
129 | csmis = []
130 | others = []
131 |
132 | for i in disks:
133 | if re.search(r'\/csmi\d+\,\d+', i, re.I):
134 | csmis.append(i)
135 | else:
136 | others.append(i)
137 |
138 | result = csmis + others
139 |
140 | return result
141 |
142 |
143 | def listDisks():
144 |
145 | errors = []
146 |
147 | if not diskDevsManual:
148 | scanDisks_Out = scanDisks('NOTYPE')
149 | errors.append(scanDisks_Out[0]) # SCAN_OS_NOCMD_*, SCAN_OS_ERROR_*, SCAN_UNKNOWN_ERROR_*
150 |
151 | disks = scanDisks_Out[1]
152 |
153 | if isCheckNVMe:
154 | scanDisksNVMe_Out = scanDisks('NVME')
155 | errors.append(scanDisksNVMe_Out[0])
156 |
157 | disks.extend(scanDisksNVMe_Out[1])
158 | else:
159 | errors.append('')
160 |
161 | else:
162 | disks = diskDevsManual
163 |
164 | # Remove duplicates preserving order
165 | diskResult = []
166 | for i in disks:
167 | if i not in diskResult:
168 | diskResult.append(i)
169 |
170 | diskResult = moveCsmiToBegining(diskResult)
171 |
172 | return errors, diskResult
173 |
174 |
175 | def findErrorsAndOuts(cD):
176 |
177 | err = None
178 | p = ''
179 |
180 | try:
181 | if isCheckSAS:
182 | cmd = addSudoIfNix([binPath, '-a', '-i', '-n', 'standby']) + shlex.split(cD)
183 | else:
184 | cmd = addSudoIfNix([binPath, '-A', '-i', '-n', 'standby']) + shlex.split(cD)
185 |
186 | if (sys.version_info.major == 3 and
187 | sys.version_info.minor <= 2):
188 |
189 | p = subprocess.check_output(cmd, universal_newlines=True)
190 |
191 | err = 'OLD_PYTHON32_OR_LESS'
192 | else:
193 | p = subprocess.check_output(cmd, universal_newlines=True, timeout=perDiskTimeout)
194 |
195 | except OSError as e:
196 | if e.args[0] == 2:
197 | err = 'D_OS_NOCMD'
198 | else:
199 | err = 'D_OS_ERROR'
200 | if sys.argv[1] == 'getverb': raise
201 |
202 | except subprocess.CalledProcessError as e:
203 | p = e.output
204 |
205 | if 'Device is in STANDBY (OS)' in p:
206 | err = 'STANDBY_OS'
207 | elif 'Device is in STANDBY' in p:
208 | err = 'STANDBY'
209 | elif 'Device is in SLEEP' in p:
210 | err = 'SLEEP'
211 | elif 'Unknown USB bridge' in p:
212 | err = 'UNK_USB_BRIDGE'
213 | elif r"Packet Interface Devices [this device: CD/DVD] don't support ATA SMART" in p:
214 | err = 'CD_DVD_DRIVE'
215 |
216 | elif (sys.version_info.major == 3 and
217 | sys.version_info.minor <= 1):
218 |
219 | err = 'UNK_OLD_PYTHON31_OR_LESS'
220 |
221 | elif e.args:
222 | err = 'ERR_CODE_%s' % str(e.args[0])
223 | else:
224 | err = 'UNKNOWN_RESPONSE'
225 |
226 | except subprocess.TimeoutExpired:
227 | err = 'TIMEOUT'
228 |
229 | except Exception as e:
230 | err = 'UNKNOWN_EXC_ERROR'
231 | if sys.argv[1] == 'getverb': raise
232 |
233 | try:
234 | p = e.output
235 | except:
236 | p = ''
237 |
238 | return (err, p)
239 |
240 |
241 | def findDiskTemp(p):
242 |
243 | result = None
244 | for i in temperaturePatterns:
245 | temperatureRe = re.search(i, p, re.I | re.M)
246 | if temperatureRe:
247 | result = temperatureRe.group(1)
248 | break
249 |
250 | return result
251 |
252 |
253 | def findIdent(p):
254 |
255 | identPatterns = (
256 | '^Serial Number:\s+(.+)$',
257 | '^LU WWN Device Id:\s+(.+)$',
258 | '^Logical Unit id:\s+(.+)$',
259 | '^Product:\s+(.+)$',
260 | '^Device Model:\s+(.+)$',
261 | )
262 |
263 | result = None
264 | for i in identPatterns:
265 | identRe = re.search(i, p, re.I | re.M)
266 | if identRe:
267 | result = identRe.group(1)
268 | break
269 |
270 | return result
271 |
272 |
273 | def chooseSystemSpecificPaths():
274 |
275 | if sys.platform.startswith('linux'):
276 | binPath_ = binPath_LINUX
277 | agentConf_ = agentConf_LINUX
278 | senderPath_ = senderPath_LINUX
279 | senderPyPath_ = senderPyPath_LINUX
280 |
281 | elif sys.platform == 'win32':
282 | binPath_ = binPath_WIN
283 | agentConf_ = agentConf_WIN
284 | senderPath_ = senderPath_WIN
285 | senderPyPath_ = senderPyPath_WIN
286 |
287 | else:
288 | binPath_ = binPath_OTHER
289 | agentConf_ = agentConf_OTHER
290 | senderPath_ = senderPath_OTHER
291 | senderPyPath_ = senderPyPath_OTHER
292 |
293 | if sys.argv[1] == 'getverb':
294 | print(' Path guess: %s\n' % sys.platform)
295 |
296 | return (binPath_, agentConf_, senderPath_, senderPyPath_)
297 |
298 |
299 | def isModelWithoutSensor(p):
300 |
301 | result = False
302 | for i in modelPatterns:
303 | modelRe = re.search(i, p, re.I | re.M)
304 | if modelRe:
305 | model = modelRe.group(1).strip()
306 |
307 | if model in noTemperatureSensorModels:
308 | result = True
309 | break
310 |
311 | return result
312 |
313 |
314 | def isDummyNVMe(p):
315 |
316 | subsystemRe = re.search(r'Subsystem ID:\s+0x0000', p, re.I)
317 | ouiRe = re.search(r'IEEE OUI Identifier:\s+0x000000', p, re.I)
318 |
319 | if (subsystemRe and
320 | ouiRe):
321 |
322 | result = True
323 | else:
324 | result = False
325 |
326 | return result
327 |
328 |
329 | def addSudoIfNix(cmd):
330 |
331 | result = cmd
332 | if not sys.platform == 'win32':
333 | result = ['sudo'] + cmd
334 |
335 | return result
336 |
337 |
338 | def isSSD(p):
339 |
340 | ssdRe = re.search('^Rotation Rate:\s+Solid State Device', p, re.I | re.M)
341 |
342 | if ssdRe:
343 | result = True
344 | else:
345 | result = False
346 |
347 | return result
348 |
349 |
350 | def doHeavyDebug(diskError_, driveStatus_, diskPout_):
351 |
352 | if (diskError_ and
353 | driveStatus_ != 'DUPLICATE_IGNORE'):
354 |
355 | heavyOut = repr(diskPout_.strip())
356 | heavyOut = heavyOut.strip().strip('"').strip("'").strip()
357 | heavyOut = heavyOut.replace("'", r"\'").replace('"', r'\"')
358 |
359 | debugData = '"%s" mini.disk.HeavyDebug "%s"' % (HOST, heavyOut)
360 | senderData.append(debugData)
361 |
362 |
363 | if __name__ == '__main__':
364 |
365 | fail_ifNot_Py3()
366 |
367 | paths_Out = chooseSystemSpecificPaths()
368 | binPath = paths_Out[0]
369 | agentConf = paths_Out[1]
370 | senderPath = paths_Out[2]
371 | senderPyPath = paths_Out[3]
372 |
373 | senderData = []
374 | jsonData = []
375 |
376 | listDisks_Out = listDisks()
377 | scanErrors = listDisks_Out[0]
378 | diskDevs = listDisks_Out[1]
379 |
380 | if scanErrors:
381 | scanErrorNotype = scanErrors[0]
382 | scanErrorNvme = scanErrors[1]
383 | else:
384 | scanErrorNotype = None
385 | scanErrorNvme = None
386 |
387 | sessionSerials = []
388 | allTemps = []
389 | diskError_NOCMD = False
390 | for d in diskDevs:
391 | clearedD = clearDiskTypeStr(d)
392 | sanitizedD = sanitizeStr(clearedD)
393 | jsonData.append({'{#DISK}':sanitizedD}) # always discover to prevent flapping
394 |
395 | disk_Out = findErrorsAndOuts(clearedD)
396 | diskError = disk_Out[0]
397 | diskPout = disk_Out[1]
398 | if diskError:
399 | if 'D_OS_' in diskError:
400 | diskError_NOCMD = diskError
401 | break # [v] fatal; json of other disks is discarded
402 |
403 | isDuplicate = False
404 | ident = findIdent(diskPout)
405 | if ident in sessionSerials:
406 | isDuplicate = True
407 | elif ident:
408 | sessionSerials.append(ident)
409 |
410 | temp = findDiskTemp(diskPout)
411 | if isDuplicate:
412 | if isIgnoreDuplicates:
413 | driveStatus = 'DUPLICATE_IGNORE'
414 | else:
415 | driveStatus = 'DUPLICATE_MENTION'
416 | elif isModelWithoutSensor(diskPout):
417 | driveStatus = 'NOSENSOR'
418 | elif isDummyNVMe(diskPout):
419 | driveStatus = 'DUMMY_NVME'
420 | elif diskError:
421 | driveStatus = diskError
422 | elif not temp: # !!BUG!! needs more complex conditionals
423 | driveStatus = 'NOTEMP'
424 | else:
425 | driveStatus = 'PROCESSED'
426 | senderData.append('"%s" mini.disk.info[%s,DriveStatus] "%s"' % (HOST, sanitizedD, driveStatus))
427 |
428 | if (temp and
429 | not driveStatus == 'NOSENSOR' and
430 | not driveStatus == 'DUPLICATE_IGNORE'):
431 |
432 | senderData.append('"%s" mini.disk.temp[%s] "%s"' % (HOST, sanitizedD, temp))
433 | allTemps.append(temp)
434 |
435 | if isSSD(diskPout):
436 | threshMin = thresholds[1][1]
437 | threshMax = thresholds[1][2]
438 | threshCrit = thresholds[1][3]
439 | else:
440 | threshMin = thresholds[0][1]
441 | threshMax = thresholds[0][2]
442 | threshCrit = thresholds[0][3]
443 |
444 | senderData.append('"%s" mini.disk.tempMin[%s] "%s"' % (HOST, sanitizedD, threshMin))
445 | senderData.append('"%s" mini.disk.tempMax[%s] "%s"' % (HOST, sanitizedD, threshMax))
446 | senderData.append('"%s" mini.disk.tempCrit[%s] "%s"' % (HOST, sanitizedD, threshCrit))
447 |
448 | if isHeavyDebug:
449 | doHeavyDebug(diskError, driveStatus, diskPout)
450 |
451 | if scanErrorNotype:
452 | configStatus = scanErrorNotype
453 | elif diskError_NOCMD:
454 | configStatus = diskError_NOCMD
455 | elif not diskDevs:
456 | configStatus = 'NODISKS'
457 | elif not allTemps:
458 | configStatus = 'NODISKTEMPS'
459 | else:
460 | configStatus = 'CONFIGURED'
461 | senderData.append('"%s" mini.disk.info[ConfigStatus] "%s"' % (HOST, configStatus))
462 |
463 | if allTemps:
464 | senderData.append('"%s" mini.disk.temp[MAX] "%s"' % (HOST, str(max(allTemps))))
465 |
466 | link = r'https://github.com/nobody43/zabbix-mini-IPMI/issues'
467 | sendStatusKey = 'mini.disk.info[SendStatus]'
468 | processData(senderData, jsonData, agentConf, senderPyPath, senderPath, delay, HOST, link, sendStatusKey)
469 |
470 |
--------------------------------------------------------------------------------
/screenshots/mini-IPMI-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobody43/zabbix-mini-IPMI/7fe920de4a9e95f6159fe88839112a832a638281/screenshots/mini-IPMI-graph.png
--------------------------------------------------------------------------------
/screenshots/mini-IPMI-triggers-config.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobody43/zabbix-mini-IPMI/7fe920de4a9e95f6159fe88839112a832a638281/screenshots/mini-IPMI-triggers-config.png
--------------------------------------------------------------------------------
/screenshots/mini-IPMI-triggers-cpu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobody43/zabbix-mini-IPMI/7fe920de4a9e95f6159fe88839112a832a638281/screenshots/mini-IPMI-triggers-cpu.png
--------------------------------------------------------------------------------
/screenshots/mini-IPMI-triggers-disk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobody43/zabbix-mini-IPMI/7fe920de4a9e95f6159fe88839112a832a638281/screenshots/mini-IPMI-triggers-disk.png
--------------------------------------------------------------------------------
/screenshots/python-installation1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobody43/zabbix-mini-IPMI/7fe920de4a9e95f6159fe88839112a832a638281/screenshots/python-installation1.png
--------------------------------------------------------------------------------
/screenshots/python-installation2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobody43/zabbix-mini-IPMI/7fe920de4a9e95f6159fe88839112a832a638281/screenshots/python-installation2.png
--------------------------------------------------------------------------------
/sender_wrapper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import subprocess
5 | import re
6 | from time import sleep
7 | from json import dumps
8 |
9 |
10 | def isWindows():
11 | if sys.platform == 'win32':
12 | return True
13 | else:
14 | return False
15 |
16 |
17 | def send():
18 |
19 | if fetchMode == 'get':
20 | sleep(timeout) # wait for LLD to be processed by server
21 | senderProc = subprocess.Popen([senderPath, '-c', agentConf, '-i', '-'],
22 | stdin=subprocess.PIPE, universal_newlines=True, close_fds=(not isWindows()))
23 |
24 | elif fetchMode == 'getverb':
25 | print('\n Note: the sender will fail if server did not gather LLD previously.')
26 | print('\n Data sent to zabbix sender:')
27 | print('\n')
28 | print(senderDataNStr)
29 | senderProc = subprocess.Popen([senderPath, '-vv', '-c', agentConf, '-i', '-'],
30 | stdin=subprocess.PIPE, universal_newlines=True, close_fds=(not isWindows()))
31 |
32 | else:
33 | print(sys.argv[0] + " : Not supported. Use 'get' or 'getverb'.")
34 | sys.exit(1)
35 |
36 | senderProc.communicate(input=senderDataNStr)
37 |
38 |
39 | if __name__ == '__main__':
40 | fetchMode = sys.argv[1]
41 |
42 | agentConf = sys.argv[2]
43 | senderPath = sys.argv[3]
44 | timeout = int(sys.argv[4])
45 | senderDataNStr = sys.argv[5]
46 |
47 | if isWindows():
48 | timeout = 0
49 |
50 | send()
51 |
52 |
53 | # External
54 | def fail_ifNot_Py3():
55 | '''Terminate if not using python3.'''
56 | if sys.version_info.major != 3:
57 | sys.stdout.write(sys.argv[0] + ': Python3 is required.')
58 | sys.exit(1)
59 |
60 |
61 | def oldPythonMsg():
62 | if (sys.version_info.major == 3 and
63 | sys.version_info.minor <= 2):
64 |
65 | print("python32 or less is detected. It's advisable to use python33 or above for timeout guards support.")
66 |
67 |
68 | def displayVersions(config, senderPath_):
69 | '''Display python and sender versions.'''
70 | print(' Python version:\n', sys.version)
71 |
72 | oldPythonMsg()
73 |
74 | try:
75 | print('\n Sender version:\n', subprocess.check_output([senderPath_, '-V']).decode())
76 | except:
77 | print('Could not run zabbix_sender.')
78 |
79 | print()
80 |
81 |
82 | def readConfig(config):
83 | '''Read and display important config values for debug.'''
84 | try:
85 | f = open(config, 'r')
86 | text = f.read()
87 | f.close()
88 |
89 | print(" Config's main settings:")
90 | server = re.search(r'^(?:\s+)?(Server(?:\s+)?\=(?:\s+)?.+)$', text, re.M)
91 | if server:
92 | print(server.group(1))
93 | else:
94 | print("Could not find 'Server' setting in config!")
95 |
96 | serverActive = re.search(r'^(?:\s+)?(ServerActive(?:\s+)?\=(?:\s+)?.+)$', text, re.M)
97 | if serverActive:
98 | print(serverActive.group(1))
99 | else:
100 | print("Could not find 'ServerActive' setting in config!")
101 |
102 | timeout = re.search(r'^(?:\s+)?(Timeout(?:\s+)?\=(?:\s+)?(\d+))(?:\s+)?$', text, re.M)
103 | if timeout:
104 | print(timeout.group(1))
105 |
106 | if int(timeout.group(2)) < 10:
107 | print("'Timeout' setting is too low for this script!")
108 | else:
109 | print("Could not find 'Timeout' manual setting in config!\nDefault value is too low for this script.")
110 |
111 | except:
112 | print(' Could not process config file:\n' + config)
113 | finally:
114 | print()
115 |
116 |
117 | def chooseDevnull():
118 | try:
119 | from subprocess import DEVNULL # for python versions greater than 3.3, inclusive
120 | except:
121 | import os
122 | DEVNULL = open(os.devnull, 'w') # for 3.0-3.2, inclusive
123 |
124 | return DEVNULL
125 |
126 |
127 | def processData(senderData_, jsonData_, agentConf_, senderPyPath_, senderPath_,
128 | timeout_, host_, issuesLink_, sendStatusKey_='UNKNOWN'):
129 | '''Compose data and try to send it.'''
130 | DEVNULL = chooseDevnull()
131 |
132 | fetchMode_ = sys.argv[1]
133 | senderDataNStr = '\n'.join(senderData_) # items for zabbix sender separated by newlines
134 |
135 | # pass senderDataNStr to sender_wrapper.py:
136 | if fetchMode_ == 'get':
137 | print(dumps({"data": jsonData_}, indent=4)) # print data gathered for LLD
138 |
139 | # spawn new process and regain shell control immediately (on Win 'sender_wrapper.py' will not wait)
140 | try:
141 | cmd = [sys.executable, senderPyPath_, fetchMode_, agentConf_, senderPath_, timeout_, senderDataNStr]
142 |
143 | subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=DEVNULL, stderr=DEVNULL, close_fds=(not isWindows()))
144 |
145 | except OSError as e:
146 | if e.args[0] == 7:
147 | subprocess.call([senderPath_, '-c', agentConf_, '-s', host_, '-k', sendStatusKey_, '-o', 'HUGEDATA'])
148 | else:
149 | subprocess.call([senderPath_, '-c', agentConf_, '-s', host_, '-k', sendStatusKey_, '-o', 'SEND_OS_ERROR'])
150 |
151 | except:
152 | subprocess.call( [senderPath_, '-c', agentConf_, '-s', host_, '-k', sendStatusKey_, '-o', 'UNKNOWN_SEND_ERROR'])
153 |
154 | elif fetchMode_ == 'getverb':
155 | displayVersions(agentConf_, senderPath_)
156 | readConfig(agentConf_)
157 |
158 | #for i in range(135000): senderDataNStr = senderDataNStr + '0' # HUGEDATA testing
159 | try:
160 | # do not detach if in verbose mode, also skips timeout in 'sender_wrapper.py'
161 | cmd = [sys.executable, senderPyPath_, 'getverb', agentConf_, senderPath_, timeout_, senderDataNStr]
162 |
163 | subprocess.Popen(cmd, stdin=subprocess.PIPE, close_fds=(not isWindows()))
164 |
165 | except OSError as e:
166 | if e.args[0] == 7: # almost unreachable in case of this script
167 | print(sys.argv[0] + ': Could not send anything. Argument list or filepath too long. (HUGEDATA)') # FileNotFoundError: [WinError 206]
168 | else:
169 | print(sys.argv[0] + ': Something went wrong. (SEND_OS_ERROR)')
170 |
171 | raise
172 |
173 | except:
174 | print(sys.argv[0] + ': Something went wrong. (UNKNOWN_SEND_ERROR)')
175 | raise
176 |
177 | finally:
178 | print(' Please report any issues or missing features to:\n%s\n' % issuesLink_)
179 |
180 | else:
181 | print(sys.argv[0] + ": Not supported. Use 'get' or 'getverb'.")
182 |
183 |
184 | def clearDiskTypeStr(s):
185 | stopWords = (
186 | (' -d atacam'), (' -d scsi'), (' -d ata'), (' -d sat'), (' -d nvme'),
187 | (' -d sas'), (' -d csmi'), (' -d usb'), (' -d pd'), (' -d auto'),
188 | )
189 |
190 | for i in stopWords:
191 | s = s.replace(i, '')
192 |
193 | s = s.strip()
194 |
195 | return s
196 |
197 |
198 | def removeQuotes(s):
199 | quotes = ('\'', '"')
200 |
201 | for i in quotes:
202 | s = s.replace(i, '')
203 |
204 | return s
205 |
206 |
207 | def sanitizeStr(s):
208 | '''Sanitizes provided string in sequential order.'''
209 | stopChars = (
210 | ('/dev/', ''), (' -d', ''),
211 | ('!', '_'), (',', '_'), ('[', '_'), ('~', '_'), (' ', '_'),
212 | (']', '_'), ('+', '_'), ('/', '_'), ('\\', '_'), ('\'', '_'),
213 | ('`', '_'), ('@', '_'), ('#', '_'), ('$', '_'), ('%', '_'),
214 | ('^', '_'), ('&', '_'), ('*', '_'), ('(', '_'), (')', '_'),
215 | ('{', '_'), ('}', '_'), ('=', '_'), (':', '_'), (';', '_'),
216 | ('"', '_'), ('?', '_'), ('<', '_'), ('>', '_'), (' ', '_'),
217 | )
218 |
219 | for i, j in stopChars:
220 | s = s.replace(i, j)
221 |
222 | s = s.strip()
223 |
224 | return s
225 |
--------------------------------------------------------------------------------