Skip to content

Commit b53adfc

Browse files
author
Anze
committed
Implement SNMP counters support
1 parent 061e0ab commit b53adfc

File tree

4 files changed

+133
-43
lines changed

4 files changed

+133
-43
lines changed

Pipfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ requests = "*"
1212
python-dotenv = "*"
1313
apscheduler = "*"
1414
ansicolors = "*"
15-
easysnmp = "*"
15+
easysnmp = {editable = true,git = "http://github.com/grafolean/easysnmp"}
1616
mathjspy = "*"
1717
numpy = "*"
1818

Pipfile.lock

Lines changed: 7 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

snmpcollector.py

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import dotenv
33
import logging
44
import json
5+
import time
56
from pytz import utc
67
from colors import color
78
import requests
89

9-
from easysnmp import Session
10+
from easysnmp import Session, SNMPVariable
1011
from mathjspy import MathJS
1112

1213
from collector import Collector
@@ -25,6 +26,43 @@ class NoValueForOid(Exception):
2526
pass
2627

2728

29+
previous_counter_values = {}
30+
31+
32+
def _get_previous_counter_value(counter_ident):
33+
prev_value = previous_counter_values.get(counter_ident)
34+
if prev_value is None:
35+
return None, None
36+
return prev_value
37+
38+
39+
def _save_current_counter_value(new_value, now, counter_ident):
40+
previous_counter_values[counter_ident] = (new_value, now)
41+
42+
43+
def _convert_counters_to_values(results, now, counter_ident_prefix):
44+
new_results = []
45+
for i, v in enumerate(results):
46+
if isinstance(v, list):
47+
new_results.append(_convert_counters_to_values(v, now, counter_ident_prefix + f'/{i}'))
48+
continue
49+
if v.snmp_type not in ['COUNTER', 'COUNTER64']:
50+
new_results.append(v)
51+
continue
52+
# counter - deal with it:
53+
counter_ident = counter_ident_prefix + f'/{i}/{v.oid}/{v.oid_index}'
54+
old_value, t = _get_previous_counter_value(counter_ident)
55+
new_value = float(v.value)
56+
_save_current_counter_value(new_value, now, counter_ident)
57+
if old_value is None:
58+
new_results.append(SNMPVariable(oid=v.oid, oid_index=v.oid_index, value=None, snmp_type='COUNTER_PER_S'))
59+
else:
60+
dt = now - t
61+
dv = (new_value - old_value) / dt
62+
new_results.append(SNMPVariable(oid=v.oid, oid_index=v.oid_index, value=dv, snmp_type='COUNTER_PER_S'))
63+
return new_results
64+
65+
2866
def _apply_expression_to_results(snmp_results, methods, expression, output_path):
2967
if 'walk' in methods:
3068
"""
@@ -48,7 +86,9 @@ def _apply_expression_to_results(snmp_results, methods, expression, output_path)
4886
mjs = MathJS()
4987
for i, r in enumerate(addressable_results):
5088
v = r.get(oid_index)
51-
if v is None:
89+
if v is None: # oid index wasn't present
90+
raise NoValueForOid()
91+
if v.value is None: # no value (probably the first time we're asking for a counter)
5292
raise NoValueForOid()
5393
mjs.set('${}'.format(i + 1), float(v.value))
5494
value = mjs.eval(expression)
@@ -61,13 +101,19 @@ def _apply_expression_to_results(snmp_results, methods, expression, output_path)
61101
return result
62102

63103
else:
64-
mjs = MathJS()
65-
for i, r in enumerate(snmp_results):
66-
mjs.set('${}'.format(i + 1), float(r.value))
67-
value = mjs.eval(expression)
68-
return [
69-
{'p': output_path, 'v': value},
70-
]
104+
try:
105+
mjs = MathJS()
106+
for i, v in enumerate(snmp_results):
107+
if v.value is None: # no value (probably the first time we're asking for a counter)
108+
raise NoValueForOid()
109+
mjs.set('${}'.format(i + 1), float(v.value))
110+
value = mjs.eval(expression)
111+
return [
112+
{'p': output_path, 'v': value},
113+
]
114+
except NoValueForOid:
115+
log.warning(f'Missing OID value (counter?)')
116+
return []
71117

72118

73119
def send_results_to_grafolean(backend_url, bot_token, account_id, values):
@@ -178,15 +224,17 @@ def do_snmp(*args, **job_info):
178224
# while we are at it, save the indexes of the results:
179225
if not walk_indexes:
180226
walk_indexes = [r.oid_index for r in result]
181-
oids_results = list(zip(oids, methods, results))
182-
log.info("Results: {}".format(oids_results))
227+
log.info("Results: {}".format(list(zip(oids, methods, results))))
228+
229+
counter_ident_prefix = f'{job_info["entity_id"]}/{sensor["sensor_details"]["id"]}'
230+
results_no_counters = _convert_counters_to_values(results, time.time(), counter_ident_prefix)
183231

184232
# We have SNMP results and expression - let's calculate value(s). The trick here is that
185233
# if some of the data is fetched via SNMP WALK, we will have many results; if only SNMP
186234
# GET was used, we get one.
187235
expression = sensor["sensor_details"]["expression"]
188236
output_path = f'entity.{job_info["entity_id"]}.snmp.{sensor["sensor_details"]["output_path"]}'
189-
values = _apply_expression_to_results(results, methods, expression, output_path)
237+
values = _apply_expression_to_results(results_no_counters, methods, expression, output_path)
190238
send_results_to_grafolean(job_info['backend_url'], job_info['bot_token'], job_info['account_id'], values)
191239

192240

test_snmpcollector.py

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from easysnmp import SNMPVariable
22

3-
from snmpcollector import _apply_expression_to_results
3+
from snmpcollector import _apply_expression_to_results, _convert_counters_to_values
44

55

6-
def test_snmpget():
6+
def test_apply_expression_snmpget():
77
results = [
8-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='5', value=68000, snmp_type='GAUGE'),
8+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='5', value='68000', snmp_type='GAUGE'),
99
]
1010
methods = ['walk' if isinstance(x, list) else 'get' for x in results]
1111
expression = '$1'
@@ -15,10 +15,10 @@ def test_snmpget():
1515
]
1616
assert _apply_expression_to_results(results, methods, expression, output_path) == expected_result
1717

18-
def test_snmpget_add():
18+
def test_apply_expression_snmpget_add():
1919
results = [
20-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='5', value=68000, snmp_type='GAUGE'),
21-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value=200, snmp_type='GAUGE'),
20+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='5', value='68000', snmp_type='GAUGE'),
21+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value='200', snmp_type='GAUGE'),
2222
]
2323
methods = ['walk' if isinstance(x, list) else 'get' for x in results]
2424
expression = '$1 + $2'
@@ -28,12 +28,12 @@ def test_snmpget_add():
2828
]
2929
assert _apply_expression_to_results(results, methods, expression, output_path) == expected_result
3030

31-
def test_snmpwalk():
31+
def test_apply_expression_snmpwalk():
3232
results = [
3333
[
34-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value=60000, snmp_type='GAUGE'),
35-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='2', value=61000, snmp_type='GAUGE'),
36-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='3', value=62000, snmp_type='GAUGE'),
34+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value='60000', snmp_type='GAUGE'),
35+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='2', value='61000', snmp_type='GAUGE'),
36+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='3', value='62000', snmp_type='GAUGE'),
3737
],
3838
]
3939
methods = ['walk' if isinstance(x, list) else 'get' for x in results]
@@ -46,13 +46,13 @@ def test_snmpwalk():
4646
]
4747
assert _apply_expression_to_results(results, methods, expression, output_path) == expected_result
4848

49-
def test_expression_add():
49+
def test_apply_expression_expression_add():
5050
results = [
51-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.8', oid_index='23', value=500, snmp_type='GAUGE'),
51+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.8', oid_index='23', value='500', snmp_type='GAUGE'),
5252
[
53-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value=60000, snmp_type='GAUGE'),
54-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='2', value=61000, snmp_type='GAUGE'),
55-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='3', value=62000, snmp_type='GAUGE'),
53+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value='60000', snmp_type='GAUGE'),
54+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='2', value='61000', snmp_type='GAUGE'),
55+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='3', value='62000', snmp_type='GAUGE'),
5656
],
5757
]
5858
methods = ['walk' if isinstance(x, list) else 'get' for x in results]
@@ -65,16 +65,16 @@ def test_expression_add():
6565
]
6666
assert _apply_expression_to_results(results, methods, expression, output_path) == expected_result
6767

68-
def test_snmpwalk_missing_value():
68+
def test_apply_expression_snmpwalk_missing_value_walk():
6969
results = [
7070
[
71-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value=60000, snmp_type='GAUGE'),
72-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='2', value=61000, snmp_type='GAUGE'),
73-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='3', value=62000, snmp_type='GAUGE'),
71+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value='60000', snmp_type='GAUGE'),
72+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='2', value='61000', snmp_type='GAUGE'),
73+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='3', value='62000', snmp_type='GAUGE'),
7474
],
7575
[
76-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.2.2', oid_index='1', value=10, snmp_type='GAUGE'),
77-
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.2.2', oid_index='2', value=10, snmp_type='GAUGE'),
76+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.2.2', oid_index='1', value='10', snmp_type='GAUGE'),
77+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.2.2', oid_index='2', value='10', snmp_type='GAUGE'),
7878
],
7979
]
8080
methods = ['walk' if isinstance(x, list) else 'get' for x in results]
@@ -85,3 +85,47 @@ def test_snmpwalk_missing_value():
8585
{ 'p': 'snmp.test123.asdf.2', 'v': 6100.0 },
8686
]
8787
assert _apply_expression_to_results(results, methods, expression, output_path) == expected_result
88+
89+
def test_apply_expression_snmpwalk_missing_value_get():
90+
results = [
91+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value='60000', snmp_type='GAUGE'),
92+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.2.2', oid_index='1', value=None, snmp_type='GAUGE'),
93+
]
94+
methods = ['walk' if isinstance(x, list) else 'get' for x in results]
95+
expression = '$1 / $2'
96+
output_path = 'snmp.test123.asdf'
97+
expected_result = []
98+
assert _apply_expression_to_results(results, methods, expression, output_path) == expected_result
99+
100+
def test_convert_counters_no_counters_no_change():
101+
""" If there are no counters, nothing should change """
102+
now = 1234567890.123456
103+
results = [
104+
[
105+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='1', value='60000', snmp_type='GAUGE'),
106+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='2', value='61000', snmp_type='GAUGE'),
107+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.1.3', oid_index='3', value='62000', snmp_type='GAUGE'),
108+
],
109+
[
110+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.2.2', oid_index='1', value='10', snmp_type='GAUGE'),
111+
SNMPVariable(oid='1.3.6.1.4.1.2021.13.16.2.2.2', oid_index='2', value='10', snmp_type='GAUGE'),
112+
],
113+
]
114+
assert _convert_counters_to_values(results, now, "ASDF/1234") == results
115+
116+
def test_convert_counters_counter():
117+
""" First expression should be empty, next ones should work """
118+
now = 1234567890.123456
119+
120+
results_0 = [SNMPVariable(oid='.1.3.6.1.2.1.2.2.1.16', oid_index='1', value='1000', snmp_type='COUNTER')]
121+
expected_0 = [SNMPVariable(oid='.1.3.6.1.2.1.2.2.1.16', oid_index='1', value=None, snmp_type='COUNTER_PER_S')]
122+
actual = _convert_counters_to_values(results_0, now, "ASDF/1234")
123+
assert actual == expected_0
124+
125+
results_1 = [SNMPVariable(oid='.1.3.6.1.2.1.2.2.1.16', oid_index='1', value='2000.0', snmp_type='COUNTER')]
126+
expected_1 = [SNMPVariable(oid='.1.3.6.1.2.1.2.2.1.16', oid_index='1', value='1000.0', snmp_type='COUNTER_PER_S')]
127+
assert _convert_counters_to_values(results_1, now + 1.0, "ASDF/1234") == expected_1
128+
129+
results_2 = [SNMPVariable(oid='.1.3.6.1.2.1.2.2.1.16', oid_index='1', value='2300.0', snmp_type='COUNTER')]
130+
expected_2 = [SNMPVariable(oid='.1.3.6.1.2.1.2.2.1.16', oid_index='1', value='100.0', snmp_type='COUNTER_PER_S')]
131+
assert _convert_counters_to_values(results_2, now + 1.0 + 3.0, "ASDF/1234") == expected_2

0 commit comments

Comments
 (0)