Projet

Général

Profil

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

root / plugins / network / olsrd @ 99542938

Historique | Voir | Annoter | Télécharger (13,2 ko)

1
#!/bin/sh
2
# weird shebang? See below: "interpreter selection"
3
#
4
# Collect basic information about the neighbours of an OLSR node:
5
#   * link quality
6
#   * neighbour link quality
7
#   * number of nodes reachable behind each neighbour
8
#   * ping times of direct neighbours
9
#
10
# This plugin works with the following python interpreters:
11
#   * Python 2
12
#   * Python 3
13
#   * micropython
14
#
15
# Environment variables:
16
#   * OLSRD_HOST: name or IP of the host running the txtinfo plugin (default: localhost)
17
#   * OLSRD_TXTINFO_PORT: the port that the txtinfo plugin is listening to (default: 2006)
18
#   * OLSRD_BIN_PATH: name of the olsrd binary (only used for 'autoconf', defaults to /usr/sbin/olsrd)
19
#   * MICROPYTHON_HEAP: adjust this parameter for micropython if your olsr network contains
20
#     more than a few thousand nodes (default: 512k)
21
#
22
#
23
# Copyright (C) 2015 Lars Kruse <devel@sumpfralle.de>
24
#
25
#    This program is free software: you can redistribute it and/or modify
26
#    it under the terms of the GNU General Public License as published by
27
#    the Free Software Foundation, either version 3 of the License, or
28
#    (at your option) any later version.
29
#
30
#    This program is distributed in the hope that it will be useful,
31
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
32
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33
#    GNU General Public License for more details.
34
#
35
#    You should have received a copy of the GNU General Public License
36
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
37
#
38
# Magic markers
39
#%# capabilities=autoconf
40
#%# family=auto
41

    
42
"""true"
43
# ****************** Interpreter Selection ***************
44
# This unbelievable dirty hack allows to find a suitable python interpreter.
45
# This is specifically useful for OpenWRT where typically only micropython is available.
46
#
47
# Additionally we need to run micropython with additional startup options.
48
# This is necessary due to our demand for more than 128k heap (this default is sufficient for only 400 olsr nodes).
49
#
50
# This "execution hack" works as follows:
51
#   * the script is executed by busybox ash or another shell
52
#   * the above line (three quotes before and one quote after 'true') evaluates differently for shell and python:
53
#    * shell: run "true" (i.e. nothing happens)
54
#    * python: ignore everything up to the next three consecutive quotes
55
# Thus we may place shell code here that will take care for selecting an interpreter.
56

    
57
# prefer micropython if it is available - otherwise fall back to any python (2 or 3)
58
if which micropython >/dev/null; then
59
    /usr/bin/micropython -X "heapsize=${MICROPYTHON_HEAP:-512k}" "$0" "$@"
60
else
61
    python "$0" "$@"
62
fi
63
exit $?
64

    
65
# For shell: ignore everything starting from here until the last line of this file.
66
# This is necessary for syntax checkers that try to complain about invalid shell syntax below.
67
true <<EOF
68
"""
69

    
70

    
71
plugin_version = "0.3"
72

    
73

    
74
import os
75
import socket
76
import sys
77

    
78

    
79
LQ_GRAPH_CONFIG = """
80
graph_title     {title}
81
graph_vlabel    Link Quality (-) / Neighbour Link Quality (+)
82
graph_category  network
83
graph_info      OLSR estimates the quality of a connection by the ratio of successfully received (link quality) and transmitted (neighbour link quality) hello packets.
84
"""
85

    
86
LQ_VALUES_CONFIG = """
87
nlq{suffix}.label none
88
nlq{suffix}.type GAUGE
89
nlq{suffix}.graph no
90
nlq{suffix}.draw {draw_type}
91
nlq{suffix}.min 0
92
lq{suffix}.label {label}
93
lq{suffix}.type GAUGE
94
lq{suffix}.draw {draw_type}
95
lq{suffix}.negative nlq{suffix}
96
lq{suffix}.min 0
97
"""
98

    
99
NEIGHBOUR_COUNT_CONFIG = """
100
graph_title     Reachable nodes via neighbours
101
graph_vlabel    Number of Nodes
102
graph_category  network
103
graph_info      Count the number of locally known routes passing through each direct neighbour. This number is a good approximation of the number of mesh nodes reachable via this specific neighbour. MIDs (alternative addresses of an OLSR node) and HNAs (host network announcements) are ignored.
104
"""
105

    
106
NEIGHBOUR_COUNT_VALUE = """
107
neighbour_{host_fieldname}.label {host}
108
neighbour_{host_fieldname}.type GAUGE
109
neighbour_{host_fieldname}.draw {draw_type}
110
neighbour_{host_fieldname}.min 0
111
"""
112

    
113
NEIGHBOUR_PING_CONFIG = """
114
graph_title     {title}
115
graph_vlabel    roundtrip time (ms)
116
graph_category  network
117
graph_info      This graph shows ping RTT statistics.
118
graph_args      --base 1000 --lower-limit 0
119
graph_scale     no
120
"""
121

    
122
NEIGHBOUR_PING_VALUE = """neighbour_{host_fieldname}.label {host}"""
123

    
124
# micropython (as of 2015) does not contain "os.linesep"
125
LINESEP = getattr(os, "linesep", "\n")
126

    
127

    
128
get_clean_fieldname = lambda name: "".join([(("a" <= char.lower() <= "z") or ((index == 0) or ("0" <= char <= "9"))) and char or "_" for index, char in enumerate(name)])
129

    
130

    
131
def query_olsrd_txtservice(section=""):
132
    host = os.getenv("OLSRD_HOST", "localhost")
133
    port = os.getenv("OLSRD_TXTINFO_PORT", "2006")
134
    conn = socket.create_connection((host, port), 1.0)
135
    try:
136
        # Python3
137
        request = bytes("/%s" % section, "ascii")
138
    except TypeError:
139
        # Python2
140
        request = bytes("/%s" % section)
141
    conn.sendall(request)
142
    fconn = conn.makefile()
143
    # "enumerate" is not suitable since it reads all lines at once (not a generator in micropython)
144
    index = 0
145
    for line in fconn.readlines():
146
        # skip the first five lines - they are headers
147
        if index < 5:
148
            index += 1
149
            continue
150
        line = line.strip()
151
        if line:
152
            yield line
153
    fconn.close()
154
    conn.close()
155

    
156

    
157
def get_address_device_mapping():
158
    mapping = {}
159
    for line in query_olsrd_txtservice("mid"):
160
        # example line content:
161
        #    192.168.2.171   192.168.22.171;192.168.12.171
162
        device_id, mids = line.split()
163
        for mid in mids.split(";"):
164
            mapping[mid] = device_id
165
    return mapping
166

    
167

    
168
def count_routes_by_neighbour(address_mapping, ignore_list):
169
    node_count = {}
170
    for line in query_olsrd_txtservice("routes"):
171
        # example line content:
172
        #    192.168.1.79/32 192.168.12.38   4       4.008   wlan0
173
        tokens = line.split()
174
        target = tokens[0]
175
        via = tokens[1]
176
        # we care only about single-host routes
177
        if target.endswith("/32"):
178
            if target[:-3] in address_mapping:
179
                # we ignore MIDs - we want only real nodes
180
                continue
181
            if target in ignore_list:
182
                continue
183
            # replace the neighbour's IP with its main IP (if it is an MID)
184
            via = address_mapping.get(via, via)
185
            # increase the counter
186
            node_count[via] = node_count.get(via, 0) + 1
187
    return node_count
188

    
189

    
190
def get_olsr_links():
191
    mid_mapping = get_address_device_mapping()
192
    hna_list = [line.split()[0] for line in query_olsrd_txtservice("hna")]
193
    route_count = count_routes_by_neighbour(mid_mapping, hna_list)
194
    result = []
195
    for line in query_olsrd_txtservice("links"):
196
        tokens = line.split()
197
        link = {}
198
        link["local"] = tokens.pop(0)
199
        remote = tokens.pop(0)
200
        # replace the neighbour's IP with its main IP (if it is an MID)
201
        link["remote"] = mid_mapping.get(remote, remote)
202
        for key in ("hysterese", "lq", "nlq", "cost"):
203
            link[key] = float(tokens.pop(0))
204
        # add the route count
205
        link["route_count"] = route_count.get(link["remote"], 0)
206
        result.append(link)
207
    result.sort(key=lambda link: link["remote"])
208
    return result
209

    
210

    
211
def _read_file(filename):
212
    try:
213
        return open(filename, "r").read().split(LINESEP)
214
    except OSError:
215
        return []
216

    
217

    
218
def get_ping_times(hosts):
219
    tempfile = "/tmp/munin-olsrd-{pid}.tmp".format(pid=os.getpid())
220
    command = 'for host in {hosts}; do echo -n "$host "; ping -c 1 -w 1 "$host" | grep /avg/ || true; done >{tempfile}'\
221
              .format(hosts=" ".join(hosts), tempfile=tempfile)
222
    # micropython supports only "os.system" (as of 2015) - thus we need to stick with it for openwrt
223
    returncode = os.system(command)
224
    if returncode != 0:
225
        return {}
226
    lines = _read_file(tempfile)
227
    os.unlink(tempfile)
228
    # example output for one host:
229
    #   192.168.2.41 round-trip min/avg/max = 4.226/4.226/4.226 ms
230
    result = {}
231
    for line in lines:
232
        tokens = line.split(None)
233
        if len(tokens) > 1:
234
            host = tokens[0]
235
            avg_ping = tokens[-2].split("/")[1]
236
            result[host] = float(avg_ping)
237
    return result
238

    
239

    
240
if __name__ == "__main__":
241
    # parse arguments
242
    if len(sys.argv) > 1:
243
        if sys.argv[1]=="config":
244
            links = list(get_olsr_links())
245

    
246
            # link quality with regard to neighbours
247
            print("multigraph olsr_link_quality")
248
            print(LQ_GRAPH_CONFIG.format(title="OLSR Link Quality"))
249
            is_first = True
250
            for link in links:
251
                print(LQ_VALUES_CONFIG.format(label=link["remote"],
252
                                              suffix="_{host}".format(host=get_clean_fieldname(link["remote"])),
253
                                              draw_type=("AREA" if is_first else "STACK")))
254
                is_first = False
255
            is_first = True
256
            for link in links:
257
                print("multigraph olsr_link_quality.host_{remote}".format(remote=get_clean_fieldname(link["remote"])))
258
                print(LQ_GRAPH_CONFIG.format(title="Link Quality towards {host}".format(host=link["remote"])))
259
                print(LQ_VALUES_CONFIG.format(label="Link Quality", suffix="", draw_type="AREA"))
260
                is_first = False
261

    
262
            # link count ("number of nodes behind each neighbour")
263
            print("multigraph olsr_neighbour_link_count")
264
            print(NEIGHBOUR_COUNT_CONFIG)
265
            is_first = True
266
            for link in links:
267
                print(NEIGHBOUR_COUNT_VALUE
268
                      .format(host=link["remote"],
269
                              host_fieldname=get_clean_fieldname(link["remote"]),
270
                              draw_type=("AREA" if is_first else "STACK")))
271
                is_first = False
272

    
273
            # neighbour ping
274
            print("multigraph olsr_neighbour_ping")
275
            print(NEIGHBOUR_PING_CONFIG.format(title="Ping time of neighbours"))
276
            for link in links:
277
                print(NEIGHBOUR_PING_VALUE
278
                      .format(host=link["remote"],
279
                              host_fieldname=get_clean_fieldname(link["remote"])))
280
            # neighbour pings - single subgraphs
281
            for link in links:
282
                remote = get_clean_fieldname(link["remote"])
283
                print("multigraph olsr_neighbour_ping.host_{remote}".format(remote=remote))
284
                print(NEIGHBOUR_PING_CONFIG.format(title="Ping time of {remote}".format(remote=remote)))
285
                print(NEIGHBOUR_PING_VALUE.format(host=link["remote"], host_fieldname=remote))
286

    
287
            sys.exit(0)
288
        elif sys.argv[1] == "autoconf":
289
            if os.path.exists(os.getenv('OLSRD_BIN_PATH', '/usr/sbin/olsrd')):
290
                print('yes')
291
            else:
292
                print('no')
293
            sys.exit(0)
294
        elif sys.argv[1] == "version":
295
            print('olsrd Munin plugin, version %s' % plugin_version)
296
            sys.exit(0)
297
        elif sys.argv[1] == "":
298
            # ignore
299
            pass
300
        else:
301
            # unknown argument
302
            sys.stderr.write("Unknown argument{eol}".format(eol=LINESEP))
303
            sys.exit(1)
304

    
305
    # output values
306
    links = list(get_olsr_links())
307

    
308
    # overview graph for the link quality (ETX) of all neighbours
309
    print("multigraph olsr_link_quality")
310
    for link in links:
311
        print("lq_{remote}.value {lq:f}".format(lq=link["lq"],
312
                                                remote=get_clean_fieldname(link["remote"])))
313
        print("nlq_{remote}.value {nlq:f}".format(nlq=link["nlq"],
314
                                                  remote=get_clean_fieldname(link["remote"])))
315
    # detailed ETX graph for each single neighbour link
316
    for link in links:
317
        print("multigraph olsr_link_quality.host_{remote}"
318
              .format(remote=get_clean_fieldname(link["remote"])))
319
        print("lq.value {lq:f}".format(lq=link["lq"]))
320
        print("nlq.value {nlq:f}".format(nlq=link["nlq"]))
321

    
322
    # count the links/nodes behind each neighbour node
323
    print("multigraph olsr_neighbour_link_count")
324
    for link in links:
325
        print("neighbour_{host_fieldname}.value {value}"
326
              .format(value=link["route_count"],
327
                      host_fieldname=get_clean_fieldname(link["remote"])))
328

    
329
    # overview of ping roundtrip times
330
    print("multigraph olsr_neighbour_ping")
331
    ping_times = get_ping_times([link["remote"] for link in links])
332
    for link in links:
333
        ping_time = ping_times.get(link["remote"], None)
334
        if ping_time is not None:
335
            print("neighbour_{remote}.value {value:.4f}"
336
                  .format(value=ping_time,
337
                          remote=get_clean_fieldname(link["remote"])))
338
    # single detailed graphs for the ping time of each link
339
    for link in links:
340
        ping_time = ping_times.get(link["remote"], None)
341
        if ping_time is not None:
342
            remote = get_clean_fieldname(link["remote"])
343
            print("multigraph olsr_neighbour_ping.host_{remote}".format(remote=remote))
344
            print("neighbour_{remote}.value {value:.4f}".format(remote=remote, value=ping_time))
345

    
346
# final marker for shell / python hybrid script (see "Interpreter Selection")
347
EOF = True
348
EOF