forked from flakshack/SysAdminBoard
-
Notifications
You must be signed in to change notification settings - Fork 0
/
snmp_interface_1.py
246 lines (201 loc) · 10.3 KB
/
snmp_interface_1.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#!/usr/bin/env python
"""snmp_interface: module called to generate SNMP monitoring data as JSON for display on the dashboard.
# In this monitor, we want to aggregate TX + RX and redundant uplinks into a single value
# How To Calculate Bandwidth Utilization Using SNMP
# http://www.cisco.com/en/US/tech/tk648/tk362/technologies_tech_note09186a008009496e.shtml
"""
from pysnmp.entity.rfc3413.oneliner import cmdgen
import time
import json
import logging.config
from credentials import SNMP_COMMUNITY
__author__ = '[email protected] (Scott Vintinner)'
# =================================SETTINGS======================================
MAX_DATAPOINTS = 30
SAMPLE_INTERVAL = 60
GRAPH_TITLE = "Floor VLAN Bandwidth (Mbps)"
# Standard SNMP OIDs
# sysUpTime 1.3.6.1.2.1.1.3.0 (this is hundreds of a second)
# 64-bit counters because 32-bit defaults rollover too quickly
# ifHCInOctets 1.3.6.1.2.1.31.1.1.1.6.interfacenumber
# ifHCOutOctets 1.3.6.1.2.1.31.1.1.1.10.interfacenumber
# Enter the details for each SNMP counter.
# ip: This is the IP address or resolvable host name
# community: This is the SNMPv1 community that will grant access to read the OID (usually this is "public")
# oid: This is the SNMP OID interface counter we'll be measuring.
# uptime_oid: This is the SNMP OID for the device's uptime (so we know what the time was when we measured the counter)
# name: This is the name of the device as it will appear on the graph
DEVICE_IP = "cisco-clt-core"
DEVICE_SNMP = SNMP_COMMUNITY
DEVICE_UPTIME_OID = "1.3.6.1.2.1.1.3.0"
AGGREGATE_INTERFACES = (
{"oid": ["1.3.6.1.2.1.31.1.1.1.6.875", "1.3.6.1.2.1.31.1.1.1.10.875"], "name": "16"},
{"oid": ["1.3.6.1.2.1.31.1.1.1.6.879", "1.3.6.1.2.1.31.1.1.1.10.879"], "name": "17"},
{"oid": ["1.3.6.1.2.1.31.1.1.1.6.883", "1.3.6.1.2.1.31.1.1.1.10.883"], "name": "18"},
{"oid": ["1.3.6.1.2.1.31.1.1.1.6.887", "1.3.6.1.2.1.31.1.1.1.10.887"], "name": "19"},
{"oid": ["1.3.6.1.2.1.31.1.1.1.6.891", "1.3.6.1.2.1.31.1.1.1.10.891"], "name": "20"}
)
# ================================================================================
class MonitorJSON:
"""This is a simple class passed to Monitor threads so we can access the current JSON data in that thread"""
def __init__(self):
self.json = output_message("Waiting " + str(SAMPLE_INTERVAL) + " seconds for first run", "")
class AggregateInterface:
all_aggr_interfaces = [] # Static array containing all interfaces
def __init__(self, name, oids):
self.name = name
self.interface_oids = oids # List of oids
self.snmp_data = [] # Hold raw data
self.datapoints = [] # Holds pretty data
self.__class__.all_aggr_interfaces.append(self) # Add self to static array
class SNMPDatapoint:
def __init__(self, value, timeticks):
self.value = value
self.timeticks = timeticks
def get_snmp(device, community, snmp_oid1, snmp_oid2, snmp_uptime_oid):
"""Returns the value of the specified snmp OID.
Also gets the uptime (TimeTicks) so we know exactly when the sample was taken."""
# Perform a synchronous SNMP GET
cmd_gen = cmdgen.CommandGenerator()
error_indication, error_status, error_index, var_binds = cmd_gen.getCmd(
cmdgen.CommunityData(community), cmdgen.UdpTransportTarget((device, 161)), snmp_oid1, snmp_oid2, snmp_uptime_oid
)
snmp_value1 = None
snmp_value2 = None
snmp_error = None
snmp_uptime_value = None
if error_indication: # Check for SNMP errors
snmp_error = str(error_indication)
else:
if error_status:
snmp_error = error_status.prettyPrint()
else:
# varBinds are returned as SNMP objects, so convert to integers
snmp_value1 = int(var_binds[0][1])
snmp_value2 = int(var_binds[1][1])
snmp_uptime_value = int(var_binds[2][1])
return snmp_value1, snmp_value2, snmp_uptime_value, snmp_error
def calculate_bps(current_sample_octets, current_sample_time, historical_sample_octets, historical_sample_time):
"""Calculate the bits-per-second based on the octets and timeticks (hundreths of a second)."""
# When the SNMP counter reaches 18446744073709551615, it will rollover and reset to ZERO.
# If this happens, we want to make sure we don't output a negative bps
if current_sample_octets < historical_sample_octets:
# If we reset to 0, add the max value of the octets counter
current_sample_octets += 18446744073709551615
delta = current_sample_octets - historical_sample_octets
# SysUpTime is in TimeTicks (Hundreds of a second), so covert to seconds
seconds_between_samples = (current_sample_time - historical_sample_time) / 100.0
# Multiply octets by 8 to get bits
bps = (delta * 8) / seconds_between_samples
bps /= 1048576 # Convert to Mbps
bps = round(bps, 2)
return bps
def output_message(message, detail):
"""This function will output an error message formatted in JSON to display on the dashboard"""
output = {"graph": {"title": GRAPH_TITLE, "error": {"message": message, "detail": detail}}}
return json.dumps(output)
def generate_json(snmp_monitor):
"""This function will take the device config and raw data (if any) from the snmp_monitor and output JSON data
formatted for the StatusBar iPad App"""
logger = logging.getLogger("snmp_interface_1")
time_x_axis = time.strftime("%H:%M") # Use the same time value for all samples per iteration
datasequences = []
snmp_error = None
# First time through, create a list of InterfaceDevices using the contants provided above
if len(AggregateInterface.all_aggr_interfaces) == 0:
for aggr_interface in AGGREGATE_INTERFACES:
AggregateInterface(aggr_interface["name"], aggr_interface["oid"])
# Loop through each aggregate interface, update the SNMP data for each item
for aggr_interface in AggregateInterface.all_aggr_interfaces:
# Get the SNMP data
try:
snmp_value1, snmp_value2, snmp_uptime_value, \
snmp_error = get_snmp(
DEVICE_IP,
DEVICE_SNMP,
aggr_interface.interface_oids[0],
aggr_interface.interface_oids[1],
DEVICE_UPTIME_OID
)
except Exception as error:
if not snmp_error:
snmp_error = str(error)
if snmp_error:
logger.warning("Error retrieving SNMP data", snmp_error)
break
else:
# Total the value of our 2 interfaces
snmp_value = snmp_value1 + snmp_value2
# Add the raw SNMP data to a list
if len(aggr_interface.snmp_data) == 0: # first time through, initialize the list
aggr_interface.snmp_data = [SNMPDatapoint(snmp_value, snmp_uptime_value)]
else:
aggr_interface.snmp_data.append(SNMPDatapoint(snmp_value, snmp_uptime_value))
# If we already have the max number of datapoints in our list, delete the oldest item
if len(aggr_interface.snmp_data) >= MAX_DATAPOINTS:
del(aggr_interface.snmp_data[0])
# If we have at least 2 samples, calculate bps by comparing the last item with the second to last item
if len(aggr_interface.snmp_data) > 1:
bps = calculate_bps(
aggr_interface.snmp_data[-1].value,
aggr_interface.snmp_data[-1].timeticks,
aggr_interface.snmp_data[-2].value,
aggr_interface.snmp_data[-2].timeticks
)
bps = round(bps, 2)
if len(aggr_interface.datapoints) == 0:
aggr_interface.datapoints = [{"title": time_x_axis, "value": bps}]
else:
aggr_interface.datapoints.append({"title": time_x_axis, "value": bps})
# If we already have the max number of datapoints, delete the oldest item.
if len(aggr_interface.datapoints) >= MAX_DATAPOINTS:
del(aggr_interface.datapoints[0])
# Generate the data sequence
datasequences.append({"title": aggr_interface.name, "datapoints": aggr_interface.datapoints})
# If we ran into an SNMP error, go ahead and write out the JSON file with the error
if snmp_error:
snmp_monitor.json = output_message("Error retrieving SNMP data", snmp_error)
# If this is the first run through, show Initializing on iPad
elif len(AggregateInterface.all_aggr_interfaces[-1].snmp_data) <= 2:
snmp_monitor.json = output_message(
"Initializing bandwidth dataset: " +
str(SAMPLE_INTERVAL * (3 - len(AggregateInterface.all_aggr_interfaces[-1].snmp_data))) +
" seconds...", ""
)
else:
# Generate JSON output and assign to snmp_monitor object (for return back to caller module)
graph = {
"title": GRAPH_TITLE, "type": "line",
"refreshEveryNSeconds": SAMPLE_INTERVAL,
"datasequences": datasequences
}
snmp_monitor.json = json.dumps({"graph": graph})
logger.debug(snmp_monitor.json)
# ======================================================
# __main__
#
# If you run this module by itself, it will instantiate
# the MonitorJSON class and start an infinite loop
# printing data.
# ======================================================
#
if __name__ == '__main__':
# When run by itself, we need to create the logger object (which is normally created in webserver.py)
try:
f = open("log_settings.json", 'rt')
log_config = json.load(f)
f.close()
logging.config.dictConfig(log_config)
except FileNotFoundError as e:
print("Log configuration file not found: " + str(e))
logging.basicConfig(level=logging.DEBUG) # fallback to basic settings
except json.decoder.JSONDecodeError as e:
print("Error parsing logger config file: " + str(e))
raise
monitor = MonitorJSON()
while True:
main_logger = logging.getLogger(__name__)
generate_json(monitor)
# Wait X seconds for the next iteration
main_logger.debug("Waiting for " + str(SAMPLE_INTERVAL) + " seconds")
time.sleep(SAMPLE_INTERVAL)