Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / snmp / snmp__airport @ 8589c6df

Historique | Voir | Annoter | Télécharger (12,3 ko)

1 23a5a6f4 Chris Jones
#!/usr/bin/python
2
"""
3
Munin plugin to monitor various items of data from an Apple Airport
4
Express/Extreme or a Time Capsule.
5
6
v1.0 by Chris Jones <cmsj@tenshu.net>
7
Copyright (C) 2011 Chris Jones
8
This script is released under the GNU GPL v2 license.
9
10
To use this plugin, use specially named symlinks:
11
12
cd /etc/munin/plugins
13
ln -s /path/to/snmp__airport snmp_myairport_airport_clients
14
ln -s /path/to/snmp__airport snmp_myairport_airport_dhcpclients
15
ln -s /path/to/snmp__airport snmp_myairport_airport_rate
16
ln -s /path/to/snmp__airport snmp_myairport_airport_signal
17
ln -s /path/to/snmp__airport snmp_myairport_airport_noise
18
19
NOTE: the name 'myairport' should be a valid hostname or IP address for your
20
      Airport. It can be any value, but it must not include the character '_'.
21
22
Now add a virtual host entry to your munin server's munin.conf:
23
24
[myairport]
25
    address 123.123.123.123
26
    user_node_name no
27
28
(with the correct IP address, obviously)
29
30
this will create a virtual host in munin for the airport named 'myairport' and
31
produce graphs for:
32
    * number of connected wireless clients
33
    * number of active DHCP leases
34
    * rate at which clients are connected (in Mb/s)
35
    * signal quality of connected clients (in dB)
36
    * noise level of connected clients (in dB)
37
38
# Magic markers
39
#%# capabilities=
40
#%# family=contrib manual
41
"""
42
import sys
43
import os
44
try:
45
    import netsnmp
46
except ImportError:
47
    print """ERROR: Unable to import netsnmp.
48
Please install the Python bindings for libsnmp.
49
On Debian/Ubuntu machines this package is named 'libsnmp-python'"""
50
    sys.exit(-3)
51
52
DEBUG=None
53
CMDS=['type', 'rates', 'time', 'lastrefresh', 'signal', 'noise', 'rate', 'rx',
54
      'tx', 'rxerr', 'txerr']
55
CMD=None
56
DESTHOST=None
57
NUMCLIENTS=None
58
NUMDHCPCLIENTS=None
59
WANIFINDEX=None
60
61
def dbg(text):
62
    """Print some debugging text if DEBUG=1 is in our environment"""
63
    if DEBUG is not None:
64
        print "DEBUG: %s" % text
65
66
def usage():
67
    """Print some usage information about ourselves"""
68
    print __doc__
69
70
def parseName(name):
71
    """Examing argv[0] (i.e. the name of this script) for the hostname we should
72
    be talking to and the type of check we want to run. The hostname should be
73
    a valid, resolvable hostname, or an IP address. The command can be any of:
74
        * clients - number of connected wireless clients
75
        * signal  - dB reported by the wireless clients for signal strength
76
        * noise   - dB reported by the wireless clients for noise level
77
        * rate    - Mb/s rate the wireless clients are connected at
78
79
    The name should take the form snmp_HOSTORIP_airport_COMMAND
80
    """
81
    bits = name.split('_')
82
    if len(bits) >= 4:
83
        destHost = bits[1]
84
        cmd = bits[3]
85
        dbg("parseName split '%s' into '%s'/'%s'" % (name, destHost, cmd))
86
        return (destHost, cmd)
87
    else:
88
        dbg("parseName found an inconsistent name: '%s'" % name)
89
        return None
90
91
def tableToDict(table, num):
92
    """The netsnmp library returns a tuple with all of the data, it is not in any
93
    way formatted into rows. This function converts the data into a structured
94
    dictionary, with each key being the MAC address of a wireless client. The
95
    associated value will be a dictionary containing the information available
96
    about the client:
97
        * type        - 1 = sta, 2 = wds
98
        * rates       - the wireless rates available to the client
99
        * time        - length of time the client has been connected
100
        * lastrefresh - time since the client last refreshed
101
        * signal      - dB signal strength reported by the client (or -1)
102
        * noise       - dB noise level reported by the client (or -1)
103
        * rate        - Mb/s rate the client is connected at
104
        * rx          - number of packets received by the client
105
        * tx          - number of packets transmitted by the client
106
        * rxerr       - number of error packets received by the client
107
        * txerr       - number of error packets transmitted by the client
108
    """
109
    table = list(table)
110
    clients = []
111
    clientTable = {}
112
113
    # First get the MACs
114
    i = num
115
    while i > 0:
116
        data = table.pop(0)
117
        clients.append(data)
118
        clientTable[data] = {}
119
        dbg("tableToDict: found client '%s'" % data)
120
        i = i - 1
121
122
    for cmd in CMDS:
123
        i = 0
124
        while i < num:
125
            data = table.pop(0)
126
            clientTable[clients[i]][cmd] = data
127
            dbg("tableToDict: %s['%s'] = %s" % (clients[i], cmd, data))
128
            i = i + 1
129
130
    return clientTable
131
132
def getNumClients():
133
    """Returns the number of wireless clients connected to the Airport we are
134
    examining. This will only ever be polled via SNMP once per invocation. If
135
    called a second time, it will just return the first value it found. This is
136
    intended to be an optimisation to reduce SNMP roundtrips because this script
137
    should not be long-running"""
138
    global NUMCLIENTS
139
    wirelessNumberOID = '.1.3.6.1.4.1.63.501.3.2.1.0'
140
141
    # Dumbly cache this so we only look it up once.
142
    if NUMCLIENTS is None:
143
        NUMCLIENTS = int(netsnmp.snmpget(netsnmp.Varbind(wirelessNumberOID),
144
                                         Version=2, DestHost=DESTHOST,
145
                                         Community='public')[0])
146
        dbg("getNumClients: polled SNMP for client number")
147
148
    dbg("getNumClients: found %d clients" % NUMCLIENTS)
149
    return NUMCLIENTS
150
151
def getNumDHCPClients():
152
    """Returns the number of DHCP clients with currently active leases. This
153
    will only ever be polled via SNMP once per invocation. If called a second
154
    time, it will just return the first value it found. This is intended to be
155 fba800ae Veres Lajos
    an optimisation to reduce SNMP roundtrips because this script should not be
156 23a5a6f4 Chris Jones
    long-running"""
157
    global NUMDHCPCLIENTS
158
    dhcpNumberOID = '.1.3.6.1.4.1.63.501.3.3.1.0'
159
160
    # Dumbly cache this so we only look it up once.
161
    if NUMDHCPCLIENTS is None:
162
        NUMDHCPCLIENTS = int(netsnmp.snmpget(netsnmp.Varbind(dhcpNumberOID),
163
                                             Version=2, DestHost=DESTHOST,
164
                                             Community='public')[0])
165
        dbg("getNumDHCPClients: polled SNMP for dhcp client number")
166
167
    dbg("getNumDHCPClients: found %d clients" % NUMDHCPCLIENTS)
168
    return NUMDHCPCLIENTS
169
170
def getExternalInterface():
171
    """Returns the index of the WAN interface of the Airport. This will only
172
    ever be polled via SNMP once per invocation, per getNum*Clients(). See
173
    above."""
174
    global WANIFINDEX
175
    iFaceNames = '.1.3.6.1.2.1.2.2.1.2'
176
177
    if WANIFINDEX is None:
178
        interfaces = list(netsnmp.snmpwalk(netsnmp.Varbind(iFaceNames),
179
                                          Version=2, DestHost=DESTHOST,
180
                                          Community='public'))
181
        dbg("getExternalInterface: found interfaces: %s" % interfaces)
182
        try:
183
            WANIFINDEX = interfaces.index('mgi1') + 1
184
        except ValueError:
185
            print "ERROR: Unable to find WAN interface mgi1"
186
            print interfaces
187
            sys.exit(-3)
188
189
    dbg("getExternalInterface: found mgi1 at index: %d" % WANIFINDEX)
190
    return WANIFINDEX
191
192
def getExternalInOctets():
193
    """Returns the number of octets of inbound traffic on the WAN interface"""
194
    return getOctets('In')
195
196
def getExternalOutOctets():
197
    """Returns the number of octets of outbound traffic on the WAN interface"""
198
    return getOctets('Out')
199
200
def getOctets(direction):
201
    """Returns the number of octets of traffic on the WAN interface in the
202
    requested direction"""
203
    index = getExternalInterface()
204
205
    if direction == 'In':
206
        iFaceOctets = '.1.3.6.1.2.1.2.2.1.10.%s' % index
207
    else:
208
        iFaceOctets = '.1.3.6.1.2.1.2.2.1.16.%s' % index
209
210
    return int(netsnmp.snmpget(netsnmp.Varbind(iFaceOctets),
211
                               Version=2, DestHost=DESTHOST,
212
                               Community='public')[0])
213
214
def getWanSpeed():
215
    """Returns the speed of the WAN interface"""
216
    ifSpeed = "1.3.6.1.2.1.2.2.1.5.%s" % getExternalInterface()
217
    dbg("getWanSpeed: OID for WAN interface speed: %s" % ifSpeed)
218
    try:
219
        wanSpeed = int(netsnmp.snmpget(netsnmp.Varbind(ifSpeed),
220
                                       Version=2, DestHost=DESTHOST,
221
                                       Community='public')[0])
222
    except:
223
        dbg("getWanSpeed: Unable to probe for data, defaultint to 10000000")
224
        wanSpeed = 10000000
225
226
    return wanSpeed
227
228
def getData():
229
    """Returns a dictionary populated with all of the wireless clients and their
230
    metadata"""
231
    wirelessClientTableOID = '.1.3.6.1.4.1.63.501.3.2.2.1'
232
233
    numClients = getNumClients()
234
235
    if numClients == 0:
236
        # FIXME: what's actually the correct munin plugin behaviour if there is no
237
        # data to be presented?
238
        dbg("getData: 0 clients found, exiting")
239
        sys.exit(0)
240
241
    dbg("getData: polling SNMP for client table")
242
    clientTable = netsnmp.snmpwalk(netsnmp.Varbind(wirelessClientTableOID),
243
                                   Version=2, DestHost=DESTHOST,
244
                                   Community='public')
245
    clients = tableToDict(clientTable, numClients)
246
247
    return clients
248
249
def main(clients=None):
250
    """This function fetches metadata about wireless clients if needed, then
251
    displays whatever values have been requested"""
252
    if clients is None and CMD not in ['clients', 'dhcpclients', 'wanTraffic']:
253
        clients = getData()
254
255
    if CMD == 'clients':
256
        print "clients.value %s" % getNumClients()
257
    elif CMD == 'dhcpclients':
258
        print "dhcpclients.value %s" % getNumDHCPClients()
259
    elif CMD == 'wanTraffic':
260
        print "recv.value %s" % getExternalInOctets()
261
        print "send.value %s" % getExternalOutOctets()
262
    else:
263
        for client in clients:
264
            print "MAC_%s.value %s" % (client, clients[client][CMD])
265
266
if __name__ == '__main__':
267
    clients = None
268
    if os.getenv('DEBUG') == '1':
269
        DEBUG = True
270
        netsnmp.verbose = 1
271
    else:
272
        netsnmp.verbose = 0
273
274
    BITS = parseName(sys.argv[0])
275
    if BITS is None:
276
        usage()
277
        sys.exit(0)
278
    else:
279
        DESTHOST = BITS[0]
280
        CMD = BITS[1]
281
282
    if len(sys.argv) > 1:
283
        if sys.argv[1] == 'config':
284
            print """
285
graph_category network
286
host_name %s""" % DESTHOST
287
288
            if CMD == 'signal':
289
                print """graph_args -l 0 --lower-limit -100 --upper-limit 0
290
graph_title Wireless client signal
291
graph_scale no
292
graph_vlabel dBm Signal"""
293
            elif CMD == 'noise':
294
                print """graph_args -l 0 --lower-limit -100 --upper-limit 0
295
graph_title Wireless client noise
296
graph_scale no
297
graph_vlabel dBm Noise"""
298
            elif CMD == 'rate':
299
                print """graph_args -l 0 --lower-limit 0 --upper-limit 500
300
graph_title Wireless client WiFi rate
301
graph_scale no
302
graph_vlabel WiFi Rate"""
303
            elif CMD == 'clients':
304
                print """graph_title Number of connected clients
305
graph_args --base 1000 -l 0
306
graph_vlabel number of wireless clients
307
graph_info This graph shows the number of wireless clients connected
308
clients.label clients
309
clients.draw LINE2
310
clients.info The number of clients."""
311
            elif CMD == 'dhcpclients':
312
                print """graph_title Number of active DHCP leases
313
graph_args --base 1000 -l 0
314
graph_vlabel number of DHCP clients
315
graph_info This graph shows the number of active DHCP leases
316
dhcpclients.label leases
317
dhcpclients.draw LINE2
318
dhcpclients.info The number of leases."""
319
            elif CMD == 'wanTraffic':
320
                speed = getWanSpeed()
321
                print """graph_title WAN interface traffic
322
graph_order recv send
323
graph_args --base 1000
324
graph_vlabel bits in (-) / out (+) per ${graph_period}
325
graph_category network
326
graph_info This graph shows traffic for the mgi1 network interface
327
send.info Bits sent/received by this interface.
328
recv.label recv
329
recv.type DERIVE
330
recv.graph no
331
recv.cdef recv,8,*
332
recv.max %s
333
recv.min 0
334
send.label bps
335
send.type DERIVE
336
send.negative recv
337
send.cdef send,8,*
338
send.max %s
339
send.min 0""" % (speed, speed)
340
            else:
341
                print "Unknown command: %s" % CMD
342
                sys.exit(-2)
343
344
            if CMD in ['clients', 'dhcpclients', 'wanTraffic']:
345
                # This is static, so we sent the .label data above
346
                pass
347
            else:
348
                clients = getData()
349
                for client in clients:
350
                    print "MAC_%s.label %s" % (client, client)
351
352
            sys.exit(0)
353
    else:
354
        main(clients)