Projet

Général

Profil

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

root / plugins / network / interfaces_linux_multi @ 17f78427

Historique | Voir | Annoter | Télécharger (15,8 ko)

1 cdf6eeb3 Adrien "ze" Urban
#! /usr/bin/perl
2
########################################################################
3
# Copyright (c) 2012, Adrien Urban
4
# All rights reserved.
5 17f78427 Lars Kruse
#
6 cdf6eeb3 Adrien "ze" Urban
# Redistribution and use in source and binary forms, with or without
7
# modification, are permitted provided that the following conditions are
8 17f78427 Lars Kruse
# met:
9
#
10 cdf6eeb3 Adrien "ze" Urban
# 1. Redistributions of source code must retain the above copyright
11 17f78427 Lars Kruse
#    notice, this list of conditions and the following disclaimer.
12 cdf6eeb3 Adrien "ze" Urban
# 2. Redistributions in binary form must reproduce the above copyright
13
#    notice, this list of conditions and the following disclaimer in the
14
#    documentation and/or other materials provided with the
15 17f78427 Lars Kruse
#    distribution.
16
#
17 cdf6eeb3 Adrien "ze" Urban
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
#
29
########################################################################
30
#                                                                      #
31
#    WARNING    WARNING    WARNING    WARNING    WARNING    WARNING    #
32
#                                                                      #
33
#       This plugin does not work properly with multiple master        #
34
#                                                                      #
35
########################################################################
36
#
37
# multigraph, supersampling, detailed interfaces statistics
38
#
39
# require: ifconfig
40
#   linux only for now. Would need to implement way to gather the same
41
#   data for other ifconfig usage and output.
42
# require: Time::HiRes
43
#
44
# ENV (default):
45
#  MUNIN_PLUGSTATE  - pid and cache files gets there
46
#
47
# ENV (user defined):
48
#  MUNIN_UPDATERATE - rate at which to update (default: 1s)
49
#  MUNIN_CACHEFLUSH_RATE - flush data every N batch (default: 1)
50
#  MUNIN_IFCONFIG - path for ifconfig (default /sbin/ifconfig)
51
#
52
#  MUNIN_IF_INCLUDE - list of interfaces to graph (all by default)
53
#  MUNIN_IF_EXCLUDE - exclude all of those interfaces (none by default)
54
#  MUNIN_GRAPH_BYTES - do graph bytes per seconds (default: yes)
55
#  MUNIN_GRAPH_PACKETS - do graph packets per seconds (default: yes)
56
#  MUNIN_GRAPH_ERRORS - do graph errors (default: yes)
57
#
58
# Parent graphs: none
59
# child graphs: per interface - bytes, packets, errors
60
#	interfaces/XXX/{bw,pkt,err}
61
#
62
# Known bugs:
63
#
64
#   Multi-Master
65
#     If there are many masters, the data is only sent once. Each master will
66
#     only have part of the data.
67
#
68
#   Everlasting
69
#     The daemon is launched on first config/fetch. A touch of the pidfile is
70
#     done on every following config/fetch. The daemon should check if the
71
#     pidfile is recent (configurable) enough, and stop itself if not.
72
#
73
#   Graph Order
74
#     There is currently (2.0.6) noway to order childgraphs.
75
#
76
#   RRD file
77
#     The master currently (2.0.6) generate rrd file for aggregate values, and
78
#     complains that no data is provided for them (but the graph still works
79
#     fine)
80
#
81
82
#%# family=auto
83
#%# capabilities=autoconf
84
85
use strict;
86
use warnings;
87
use Time::HiRes;
88 191ef79a Daniel Forsberg
use IO::Handle;
89 cdf6eeb3 Adrien "ze" Urban
90
my $plugin = $0;
91
$plugin =~ s/.*\///;
92
93
# quick failsafe
94
if (!defined $ENV{MUNIN_PLUGSTATE}) {
95
	die "This plugin should be run via munin. Try munin-run $plugin\n";
96
}
97
98
########################################################################
99
# If you want to change something, it's probably doable here
100
#
101
102
sub pidfile() { "$ENV{MUNIN_PLUGSTATE}/munin.$plugin.pid" }
103
sub cachefile() { "$ENV{MUNIN_PLUGSTATE}/munin.$plugin.cache" }
104
105
sub graph_name() { "interfaces" }
106
#sub graph_title() { "interfaces" }
107
#sub graph_title_all() { "Overall CPU usage" }
108
#sub graph_title_n($) { "CPU#" . shift . " usage" }
109
sub acquire_name() { "<$plugin> collecting information" }
110
111
# Default update rate. Can be changed by configuration.
112
my $update_rate = 1;
113
# default flush interval. Can be changed by configuration.
114
my $flush_interval = 1;
115
# default ifconfig command. Can be changed by configuration
116
my $ifconfig = '/sbin/ifconfig';
117
118
########################################################################
119
# if you need to change something after that line, It should probably be
120
# changed to be configurable above it.
121
#
122
123
if (defined $ENV{MUNIN_UPDATERATE}) {
124
	if ($ENV{MUNIN_UPDATERATE} =~ /^[1-9][0-9]*$/) {
125
		$update_rate = int($ENV{MUNIN_UPDATERATE});
126
	} else {
127
		print STDERR "Invalid update_rate: $ENV{MUNIN_UPDATERATE}";
128
	}
129
}
130
131
if (defined $ENV{MUNIN_CACHEFLUSH_RATE}) {
132
	if ($ENV{MUNIN_CACHEFLUSH_RATE} =~ /^[0-9]+$/) {
133 191ef79a Daniel Forsberg
		$flush_interval = int($ENV{MUNIN_CACHEFLUSH_RATE});
134 cdf6eeb3 Adrien "ze" Urban
	} else {
135
		print STDERR "Invalid flush rate: $ENV{MUNIN_CACHEFLUSH_RATE}";
136
	}
137
}
138
139
if (defined $ENV{MUNIN_IFCONFIG}) {
140
	if (-f $ENV{MUNIN_IFCONFIG}) {
141
		print STDERR "MUNIN_IFCONFIG: file not found: $ENV{MUNIN_IFCONFIG}";
142
	} else {
143
		$ifconfig = defined $ENV{MUNIN_IFCONFIG};
144
	}
145
}
146
147
my $include_list = undef;
148
if (defined $ENV{MUNIN_IF_INCLUDE}) {
149
	$include_list = [ split(/[[:space:]]+/, $ENV{MUNIN_IF_INCLUDE}) ];
150
	if (0 == scalar (@$include_list)) {
151
		$include_list = undef;
152
	} elsif ('' eq $include_list->[0]) {
153
		shift @$include_list;
154
	}
155
}
156
my $exclude_list = undef;
157 191ef79a Daniel Forsberg
if (defined $ENV{MUNIN_IF_EXCLUDE}) {
158
	$exclude_list = [ split(/[[:space:]]+/, $ENV{MUNIN_IF_EXCLUDE}) ];
159 cdf6eeb3 Adrien "ze" Urban
	if (0 == scalar (@$exclude_list)) {
160
		$exclude_list = undef;
161
	} elsif ('' eq $exclude_list->[0]) {
162
		shift @$exclude_list;
163
	}
164
}
165
166
sub configbool($) {
167
	my $str = shift;
168
	if ($str =~ /^(y(es)?|1|t(rue)?)$/i) {
169
		return 1;
170
	}
171
	if ($str =~ /^(no?|0|f(alse)?)$/i) {
172
		return 0;
173
	}
174
	print STDERR "$str: unrecognized bool\n";
175
	return 1;
176
}
177
178
my $should_graph = {
179
	'bytes' => 1,
180
	'packets' => 1,
181
	'errors' => 1,
182
};
183
if (defined $ENV{MUNIN_GRAPH_BYTES}) {
184
	$should_graph->{'bytes'} = configbool($ENV{MUNIN_GRAPH_BYTES});
185
}
186
if (defined $ENV{MUNIN_GRAPH_PACKETS}) {
187
	$should_graph->{'packets'} = configbool($ENV{MUNIN_GRAPH_PACKETS});
188
}
189
if (defined $ENV{MUNIN_GRAPH_ERRORS}) {
190
	$should_graph->{'errors'} = configbool($ENV{MUNIN_GRAPH_ERRORS});
191
}
192
unless ($should_graph->{bytes} or $should_graph->{packets}
193
		or $should_graph->{errors}) {
194
	die "Nothing to graph!";
195
}
196
197
########################################################################
198
# Base functions, specific to what we really try to do here.
199
#
200
sub included_interface($)
201
{
202
	my $if = shift;
203
	if (defined $exclude_list) {
204
		foreach my $ifl (@$exclude_list) {
205
			return 0 if ($if =~ /^($ifl)$/);
206
		}
207
	}
208
	if (defined $include_list) {
209
		foreach my $ifl (@$include_list) {
210
			return 1 if ($if =~ /^($ifl)$/);
211
		}
212
		return 0;
213
	}
214
	return 1;
215
}
216
sub if_to_name($)
217
{
218
	my $if = shift;
219
	$if =~ s/[^A-Za-z0-9]/_/g;
220
	return $if;
221
}
222
223
sub get_data()
224
{
225
	open IFCONFIG, "-|", $ifconfig or
226
		die "open: $ifconfig|: $!\n";
227
	my $data = {};
228
	my $current_if = undef;
229
	while (<IFCONFIG>) {
230
		if (/^([^[:space:]]+)/) {
231
			$current_if = $1;
232
			if (!included_interface($current_if)) {
233
				$current_if = undef;
234
				next
235
			}
236
			$data->{$current_if} = {};
237
			next; # nothing else on that line
238
		}
239
		next if (!defined $current_if);
240
		if (/RX packets:([0-9]+) errors:([0-9]+) dropped:([0-9]+) overruns:([0-9]+) frame:([0-9]+)/) {
241
			$data->{$current_if}{'rx_pkt'} = $1;
242
			$data->{$current_if}{'rx_err'} = $2;
243
			$data->{$current_if}{'rx_drp'} = $3;
244
			$data->{$current_if}{'rx_ovr'} = $4;
245
			$data->{$current_if}{'rx_frm'} = $5;
246
			next;
247
		}
248
		if (/TX packets:([0-9]+) errors:([0-9]+) dropped:([0-9]+) overruns:([0-9]+) carrier:([0-9]+)/) {
249
			$data->{$current_if}{'tx_pkt'} = $1;
250
			$data->{$current_if}{'tx_err'} = $2;
251
			$data->{$current_if}{'tx_drp'} = $3;
252
			$data->{$current_if}{'tx_ovr'} = $4;
253
			$data->{$current_if}{'tx_car'} = $5;
254
			next;
255
		}
256
		if (/RX bytes:([0-9]+) \([^)]*\)  TX bytes:([0-9]+) /) {
257
			$data->{$current_if}{'rx_byt'} = $1;
258
			$data->{$current_if}{'tx_byt'} = $2;
259
		}
260
	}
261
	close IFCONFIG;
262
	return $data;
263
}
264
265
# values names, from a data line
266
sub get_data_names($)
267
{
268
	my $line = shift;
269
	my $name = $line->[0];
270
	my $count = scalar(@$line) - 2; # 2: name, and timestamp
271
	if ($name =~ /\.(bps|pkt)$/ and 2 == $count) {
272
		return [ 'rx', 'tx' ];
273
	}
274
	if ($name =~ /\.err$/ and 8 == $count) {
275
		return [ 'rxerr', 'txerr', 'rxdrp', 'txdrp',
276
			 'rxovr', 'txovr', 'rxfrm', 'txcar', ];
277
	}
278
	# no idea what it is ? corrupted data
279
	return undef;
280
}
281
282
sub collect_info_once($$)
283
{
284
	my $fh = shift;
285
	my $now = shift;
286
	my $data = get_data();
287
	foreach my $if (keys %$data) {
288
		my $name = if_to_name($if);
289
		my $d = $data->{$if};
290
		if ($should_graph->{'bytes'}) {
291
			print $fh <<EOF;
292
$name.bps $now $d->{'rx_byt'} $d->{'tx_byt'}
293
EOF
294
#$name.byt $now rx $d->{'rx_byt'}
295
#$name.byt $now tx $d->{'tx_byt'}
296
		}
297
		if ($should_graph->{'packets'}) {
298
			print $fh <<EOF;
299
$name.pkt $now $d->{'rx_pkt'} $d->{'tx_pkt'}
300
EOF
301
#$name.pkt $now rx $d->{'rx_pkt'}
302
#$name.pkt $now tx $d->{'tx_pkt'}
303
		}
304
		if ($should_graph->{'errors'}) {
305
			print $fh <<EOF;
306
$name.err $now $d->{'rx_err'} $d->{'tx_err'} $d->{'rx_drp'} $d->{'tx_drp'} $d->{'rx_ovr'} $d->{'tx_ovr'} $d->{'rx_frm'} $d->{'tx_car'}
307
EOF
308
#$name.err $now rxerr $d->{'rx_err'}
309
#$name.err $now txerr $d->{'tx_err'}
310
#$name.err $now rxdrp $d->{'rx_drp'}
311
#$name.err $now txdrp $d->{'tx_drp'}
312
#$name.err $now rxovr $d->{'rx_ovr'}
313
#$name.err $now txovr $d->{'tx_ovr'}
314
#$name.err $now rxfrm $d->{'rx_frm'}
315
#$name.err $now txcar $d->{'tx_car'}
316
		}
317
	}
318
}
319
sub show_config()
320
{
321
	my $data = get_data();
322
	my $graph_order = "graph_order";
323
	foreach my $if (sort keys %$data) {
324
		my $name = if_to_name($if);
325
		$graph_order .= " ${name}_bps=${name}.bps.tx";
326
	}
327
	print <<EOF;
328
multigraph @{[ graph_name() ]}
329 34eeebbe Lars Kruse
graph_category network
330 cdf6eeb3 Adrien "ze" Urban
graph_title overall bits per seconds
331
graph_vlabel bits per seconds
332
update_rate 1
333
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
334
graph_args --base 1000
335
$graph_order
336
EOF
337
	my $style = 'AREA';
338
	foreach my $if (keys %$data) {
339
		my $name = if_to_name($if);
340
		print <<EOF;
341
${name}_bps.label $if bps out
342
${name}_bps.draw $style
343
${name}_bps.cdef ${name}_bps,8,*
344
EOF
345
		$style = 'STACK';
346
	}
347
	foreach my $if (keys %$data) {
348
		my $name = if_to_name($if);
349
		print <<EOF;
350
multigraph @{[ graph_name() ]}.@{[ if_to_name($if) ]}
351
graph_title $if traffic
352
graph_vlabel kbits/s and pkt/s
353
graph_order bpsrx=bps.rx bpstx=bps.tx pktrx=pkt.rx pkttx=pkt.tx
354
bpsrx.label $if kbps in
355
bpsrx.draw AREA
356
bpsrx.cdef bpsrx,-125,/
357
bpstx.label $if kbps out
358
bpstx.draw AREA
359
bpstx.cdef bpstx,125,*
360
pktrx.label $if pkt/s in
361
pktrx.draw LINE1
362
pktrx.cdef pktrx,-1,*
363
pkttx.label $if pkt/s out
364
pkttx.draw LINE1
365
EOF
366
#foo.negative bar
367
		if ($should_graph->{'bytes'}) {
368
			print <<EOF;
369
multigraph @{[ graph_name() ]}.@{[ if_to_name($if) ]}.bps
370
graph_title $if - bits per seconds
371
graph_vlabel bits per seconds
372
graph_args --base 1000
373
graph_scale no
374
update_rate 1
375
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
376
rx.label bps received
377
rx.type DERIVE
378
rx.min 0
379
rx.cdef rx,8,*
380
tx.label bps sent
381
tx.type DERIVE
382
tx.min 0
383
tx.cdef tx,8,*
384
EOF
385
		}
386
		if ($should_graph->{'packets'}) {
387
			print <<EOF;
388
multigraph @{[ graph_name() ]}.@{[ if_to_name($if) ]}.pkt
389
graph_title $if - packets per seconds
390
graph_vlabel packets per seconds
391
graph_args --base 1000
392
graph_scale no
393
update_rate 1
394
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
395
rx.label packets per second received
396
rx.type DERIVE
397
rx.min 0
398
tx.label packets per second sent
399
tx.type DERIVE
400
tx.min 0
401
EOF
402
		}
403
		if ($should_graph->{'errors'}) {
404
			print <<EOF;
405
multigraph @{[ graph_name() ]}.@{[ if_to_name($if) ]}.err
406
graph_title $if - errors
407
graph_vlabel errors
408
graph_scale no
409
update_rate 1
410
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
411
graph_order rxerr txerr rxdrp txdrp rxovr txovr rxfrm txcar
412
rxerr.label errors per second (in)
413
rxerr.type DERIVE
414
rxerr.min 0
415
txerr.label errors per second (out)
416
txerr.type DERIVE
417
txerr.min 0
418
rxdrp.label drop per second (in)
419
rxdrp.type DERIVE
420
rxdrp.min 0
421
txdrp.label drop per second (out)
422
txdrp.type DERIVE
423
txdrp.min 0
424
rxovr.label overruns per second (in)
425
rxovr.type DERIVE
426
rxovr.min 0
427
txovr.label overruns per second (out)
428
txovr.type DERIVE
429
txovr.min 0
430
rxfrm.label frame per second (in)
431
rxfrm.type DERIVE
432
rxfrm.min 0
433
txcar.label carrier per second (out)
434
txcar.type DERIVE
435
txcar.min 0
436
EOF
437
		}
438
	}
439
}
440
sub check_req()
441
{
442 17f78427 Lars Kruse
	my $data = get_data();
443 cdf6eeb3 Adrien "ze" Urban
	if (0 != scalar(keys %$data)) {
444
		return 1;
445
	}
446
	return 0;
447
}
448
449
450
########################################################################
451 fba800ae Veres Lajos
# beyond that line, it should be generic stuffs, not dependent on what
452 cdf6eeb3 Adrien "ze" Urban
# you are trying to graph
453
#
454
455
sub check_running() {
456
	if (-f pidfile()) {
457
		my $pid = undef;
458
		if (open FILE, "<", pidfile()) {
459
			$pid = <FILE>;
460
			close FILE;
461
			chomp $pid;
462
		}
463
		if ($pid) {
464
			# does not exist ? kill it
465
			if (kill 0, $pid) {
466
				return 1;
467
			}
468
		}
469
 		unlink(pidfile());
470
	}
471
	return 0;
472
}
473
474
475
# FIXME: should also trap kill sigint and sigterm
476
# FIXME: check pidfile got touched recently
477
sub collect_loop() {
478
	$0 = acquire_name();
479
	$0 = "<$plugin> collecting information";
480
	# write our pid
481
	open PIDFILE, '>', pidfile() or die "open: @{[ pidfile() ]}: $!\n";
482
	print PIDFILE $$, "\n";
483
	close PIDFILE;
484
	# open cache
485
	my $fh_cache;
486
	open $fh_cache, ">>", cachefile() or
487
			die "open: @{[ cachefile() ]}: $!\n";
488
	my @tick = Time::HiRes::gettimeofday();
489
	my $flush_count = 0;
490
	while (1) {
491
		collect_info_once($fh_cache, $tick[0]);
492
		if ($flush_interval) {
493
			if ($flush_interval == ++$flush_count) {
494
				$fh_cache->flush();
495
				$flush_count = 0;
496
			}
497
		}
498
		my @now = Time::HiRes::gettimeofday();
499
		# when should the next tick be ?
500
		$tick[0] += $update_rate;
501
		# how long until next tick ?
502
		my $diff = ($tick[0] - $now[0]) * 1000000
503
				+ $tick[1] - $now[1];
504
		if ($diff <= 0) {
505
			# next tick already passed ? damn!
506
			@tick = @now;
507
		} else {
508
			# sleep what remains
509
			Time::HiRes::usleep($diff);
510
		}
511
	}
512
	unlink(pidfile());
513
	unlink(cachefile());
514
}
515
516
# launch daemon if not running
517
# notify the daemon we still need it (touch its pid)
518
sub daemon_alive() {
519
	if (check_running()) {
520
		my $atime;
521
		my $mtime;
522
		$atime = $mtime = time;
523
		utime $atime, $mtime, pidfile();
524
	} else {
525
		if (0 == fork()) {
526
			close(STDIN);
527
			close(STDOUT);
528
			close(STDERR);
529
			open STDIN, "<", "/dev/null";
530
			open STDOUT, ">", "/dev/null";
531
			open STDERR, ">", "/dev/null";
532
			collect_loop();
533
			exit(0);
534
		}
535
	}
536
}
537
538
539
sub run_autoconf() {
540
	if (check_req()) {
541
		print "yes\n";
542
	} else {
543
		print "no\n";
544
	}
545
}
546
547
sub run_config() {
548
	daemon_alive();
549
	show_config();
550
}
551
552
sub fetch_showline($) {
553
	my $line = shift;
554
	my $names = get_data_names($line);
555
	# don't display anything if we don't like what it is
556
	return unless (defined $names);
557
	my $graph = shift @$line;
558
	my $time = shift @$line;
559
	foreach my $value (@$line) {
560
		my $name = shift @$names;
561
		print <<EOF;
562
$name.value $time:$value
563
EOF
564
	}
565
}
566
sub run_fetch() {
567
	daemon_alive();
568
	if (open CACHE, "+<", cachefile()) {
569
		my $data = {};
570
		while (<CACHE>) {
571
			chomp;
572
			my $field = [];
573
			@$field = split(/ /);
574
			if (not defined $data->{$field->[0]}) {
575
				$data->{$field->[0]} = [];
576
			}
577
			push @{$data->{$field->[0]}}, $field;
578
		}
579 fba800ae Veres Lajos
		# finished reading ? truncate it right away
580 cdf6eeb3 Adrien "ze" Urban
		truncate CACHE, 0;
581
		close CACHE;
582
		foreach my $graph (keys %$data) {
583
			print <<EOF;
584
multigraph @{[ graph_name() ]}.$graph
585
EOF
586
			foreach my $line (@{$data->{$graph}}) {
587
				fetch_showline($line);
588
			}
589
		}
590
	}
591
}
592
593
my $cmd = 'fetch';
594
if (defined $ARGV[0]) {
595
	$cmd = $ARGV[0];
596
}
597
if ('fetch' eq $cmd) {
598
	run_fetch();
599
} elsif ('config' eq $cmd) {
600
	run_config();
601
} elsif ('autoconf' eq $cmd) {
602
	run_autoconf();
603
} elsif ('daemon' eq $cmd) {
604
	run_daemon();
605
} else {
606
	print STDERR <<EOF;
607
$0: unrecognized command
608
609
Usage:
610
	$0 autoconf - check if we have everything we need
611
	$0 config - show plugin configuration
612
	$0 fetch - fetch latest data
613
	$0 daemon - launch daemon
614
EOF
615
	exit(1);
616
}
617
exit(0);