Projet

Général

Profil

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

root / plugins / network / interfaces_linux_multi @ 8589c6df

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
# 
6
# Redistribution and use in source and binary forms, with or without
7
# modification, are permitted provided that the following conditions are
8
# met: 
9
# 
10
# 1. Redistributions of source code must retain the above copyright
11
#    notice, this list of conditions and the following disclaimer. 
12
# 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
#    distribution. 
16
# 
17
# 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_section() { "system:network" }
106
sub graph_name() { "interfaces" }
107
#sub graph_title() { "interfaces" }
108
#sub graph_title_all() { "Overall CPU usage" }
109
#sub graph_title_n($) { "CPU#" . shift . " usage" }
110
sub acquire_name() { "<$plugin> collecting information" }
111
112
# Default update rate. Can be changed by configuration.
113
my $update_rate = 1;
114
# default flush interval. Can be changed by configuration.
115
my $flush_interval = 1;
116
# default ifconfig command. Can be changed by configuration
117
my $ifconfig = '/sbin/ifconfig';
118
119
########################################################################
120
# if you need to change something after that line, It should probably be
121
# changed to be configurable above it.
122
#
123
124
if (defined $ENV{MUNIN_UPDATERATE}) {
125
	if ($ENV{MUNIN_UPDATERATE} =~ /^[1-9][0-9]*$/) {
126
		$update_rate = int($ENV{MUNIN_UPDATERATE});
127
	} else {
128
		print STDERR "Invalid update_rate: $ENV{MUNIN_UPDATERATE}";
129
	}
130
}
131
132
if (defined $ENV{MUNIN_CACHEFLUSH_RATE}) {
133
	if ($ENV{MUNIN_CACHEFLUSH_RATE} =~ /^[0-9]+$/) {
134 191ef79a Daniel Forsberg
		$flush_interval = int($ENV{MUNIN_CACHEFLUSH_RATE});
135 cdf6eeb3 Adrien "ze" Urban
	} else {
136
		print STDERR "Invalid flush rate: $ENV{MUNIN_CACHEFLUSH_RATE}";
137
	}
138
}
139
140
if (defined $ENV{MUNIN_IFCONFIG}) {
141
	if (-f $ENV{MUNIN_IFCONFIG}) {
142
		print STDERR "MUNIN_IFCONFIG: file not found: $ENV{MUNIN_IFCONFIG}";
143
	} else {
144
		$ifconfig = defined $ENV{MUNIN_IFCONFIG};
145
	}
146
}
147
148
my $include_list = undef;
149
if (defined $ENV{MUNIN_IF_INCLUDE}) {
150
	$include_list = [ split(/[[:space:]]+/, $ENV{MUNIN_IF_INCLUDE}) ];
151
	if (0 == scalar (@$include_list)) {
152
		$include_list = undef;
153
	} elsif ('' eq $include_list->[0]) {
154
		shift @$include_list;
155
	}
156
}
157
my $exclude_list = undef;
158 191ef79a Daniel Forsberg
if (defined $ENV{MUNIN_IF_EXCLUDE}) {
159
	$exclude_list = [ split(/[[:space:]]+/, $ENV{MUNIN_IF_EXCLUDE}) ];
160 cdf6eeb3 Adrien "ze" Urban
	if (0 == scalar (@$exclude_list)) {
161
		$exclude_list = undef;
162
	} elsif ('' eq $exclude_list->[0]) {
163
		shift @$exclude_list;
164
	}
165
}
166
167
sub configbool($) {
168
	my $str = shift;
169
	if ($str =~ /^(y(es)?|1|t(rue)?)$/i) {
170
		return 1;
171
	}
172
	if ($str =~ /^(no?|0|f(alse)?)$/i) {
173
		return 0;
174
	}
175
	print STDERR "$str: unrecognized bool\n";
176
	return 1;
177
}
178
179
my $should_graph = {
180
	'bytes' => 1,
181
	'packets' => 1,
182
	'errors' => 1,
183
};
184
if (defined $ENV{MUNIN_GRAPH_BYTES}) {
185
	$should_graph->{'bytes'} = configbool($ENV{MUNIN_GRAPH_BYTES});
186
}
187
if (defined $ENV{MUNIN_GRAPH_PACKETS}) {
188
	$should_graph->{'packets'} = configbool($ENV{MUNIN_GRAPH_PACKETS});
189
}
190
if (defined $ENV{MUNIN_GRAPH_ERRORS}) {
191
	$should_graph->{'errors'} = configbool($ENV{MUNIN_GRAPH_ERRORS});
192
}
193
unless ($should_graph->{bytes} or $should_graph->{packets}
194
		or $should_graph->{errors}) {
195
	die "Nothing to graph!";
196
}
197
198
########################################################################
199
# Base functions, specific to what we really try to do here.
200
#
201
sub included_interface($)
202
{
203
	my $if = shift;
204
	if (defined $exclude_list) {
205
		foreach my $ifl (@$exclude_list) {
206
			return 0 if ($if =~ /^($ifl)$/);
207
		}
208
	}
209
	if (defined $include_list) {
210
		foreach my $ifl (@$include_list) {
211
			return 1 if ($if =~ /^($ifl)$/);
212
		}
213
		return 0;
214
	}
215
	return 1;
216
}
217
sub if_to_name($)
218
{
219
	my $if = shift;
220
	$if =~ s/[^A-Za-z0-9]/_/g;
221
	return $if;
222
}
223
224
sub get_data()
225
{
226
	open IFCONFIG, "-|", $ifconfig or
227
		die "open: $ifconfig|: $!\n";
228
	my $data = {};
229
	my $current_if = undef;
230
	while (<IFCONFIG>) {
231
		if (/^([^[:space:]]+)/) {
232
			$current_if = $1;
233
			if (!included_interface($current_if)) {
234
				$current_if = undef;
235
				next
236
			}
237
			$data->{$current_if} = {};
238
			next; # nothing else on that line
239
		}
240
		next if (!defined $current_if);
241
		if (/RX packets:([0-9]+) errors:([0-9]+) dropped:([0-9]+) overruns:([0-9]+) frame:([0-9]+)/) {
242
			$data->{$current_if}{'rx_pkt'} = $1;
243
			$data->{$current_if}{'rx_err'} = $2;
244
			$data->{$current_if}{'rx_drp'} = $3;
245
			$data->{$current_if}{'rx_ovr'} = $4;
246
			$data->{$current_if}{'rx_frm'} = $5;
247
			next;
248
		}
249
		if (/TX packets:([0-9]+) errors:([0-9]+) dropped:([0-9]+) overruns:([0-9]+) carrier:([0-9]+)/) {
250
			$data->{$current_if}{'tx_pkt'} = $1;
251
			$data->{$current_if}{'tx_err'} = $2;
252
			$data->{$current_if}{'tx_drp'} = $3;
253
			$data->{$current_if}{'tx_ovr'} = $4;
254
			$data->{$current_if}{'tx_car'} = $5;
255
			next;
256
		}
257
		if (/RX bytes:([0-9]+) \([^)]*\)  TX bytes:([0-9]+) /) {
258
			$data->{$current_if}{'rx_byt'} = $1;
259
			$data->{$current_if}{'tx_byt'} = $2;
260
		}
261
	}
262
	close IFCONFIG;
263
	return $data;
264
}
265
266
# values names, from a data line
267
sub get_data_names($)
268
{
269
	my $line = shift;
270
	my $name = $line->[0];
271
	my $count = scalar(@$line) - 2; # 2: name, and timestamp
272
	if ($name =~ /\.(bps|pkt)$/ and 2 == $count) {
273
		return [ 'rx', 'tx' ];
274
	}
275
	if ($name =~ /\.err$/ and 8 == $count) {
276
		return [ 'rxerr', 'txerr', 'rxdrp', 'txdrp',
277
			 'rxovr', 'txovr', 'rxfrm', 'txcar', ];
278
	}
279
	# no idea what it is ? corrupted data
280
	return undef;
281
}
282
283
sub collect_info_once($$)
284
{
285
	my $fh = shift;
286
	my $now = shift;
287
	my $data = get_data();
288
	foreach my $if (keys %$data) {
289
		my $name = if_to_name($if);
290
		my $d = $data->{$if};
291
		if ($should_graph->{'bytes'}) {
292
			print $fh <<EOF;
293
$name.bps $now $d->{'rx_byt'} $d->{'tx_byt'}
294
EOF
295
#$name.byt $now rx $d->{'rx_byt'}
296
#$name.byt $now tx $d->{'tx_byt'}
297
		}
298
		if ($should_graph->{'packets'}) {
299
			print $fh <<EOF;
300
$name.pkt $now $d->{'rx_pkt'} $d->{'tx_pkt'}
301
EOF
302
#$name.pkt $now rx $d->{'rx_pkt'}
303
#$name.pkt $now tx $d->{'tx_pkt'}
304
		}
305
		if ($should_graph->{'errors'}) {
306
			print $fh <<EOF;
307
$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'}
308
EOF
309
#$name.err $now rxerr $d->{'rx_err'}
310
#$name.err $now txerr $d->{'tx_err'}
311
#$name.err $now rxdrp $d->{'rx_drp'}
312
#$name.err $now txdrp $d->{'tx_drp'}
313
#$name.err $now rxovr $d->{'rx_ovr'}
314
#$name.err $now txovr $d->{'tx_ovr'}
315
#$name.err $now rxfrm $d->{'rx_frm'}
316
#$name.err $now txcar $d->{'tx_car'}
317
		}
318
	}
319
}
320
sub show_config()
321
{
322
	my $data = get_data();
323
	my $graph_order = "graph_order";
324
	foreach my $if (sort keys %$data) {
325
		my $name = if_to_name($if);
326
		$graph_order .= " ${name}_bps=${name}.bps.tx";
327
	}
328
	print <<EOF;
329
multigraph @{[ graph_name() ]}
330
graph_category @{[ graph_section() ]}
331
graph_title overall bits per seconds
332
graph_vlabel bits per seconds
333
update_rate 1
334
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
335
graph_args --base 1000
336
$graph_order
337
EOF
338
	my $style = 'AREA';
339
	foreach my $if (keys %$data) {
340
		my $name = if_to_name($if);
341
		print <<EOF;
342
${name}_bps.label $if bps out
343
${name}_bps.draw $style
344
${name}_bps.cdef ${name}_bps,8,*
345
EOF
346
		$style = 'STACK';
347
	}
348
	foreach my $if (keys %$data) {
349
		my $name = if_to_name($if);
350
		print <<EOF;
351
multigraph @{[ graph_name() ]}.@{[ if_to_name($if) ]}
352
graph_title $if traffic
353
graph_vlabel kbits/s and pkt/s
354
graph_order bpsrx=bps.rx bpstx=bps.tx pktrx=pkt.rx pkttx=pkt.tx
355
bpsrx.label $if kbps in
356
bpsrx.draw AREA
357
bpsrx.cdef bpsrx,-125,/
358
bpstx.label $if kbps out
359
bpstx.draw AREA
360
bpstx.cdef bpstx,125,*
361
pktrx.label $if pkt/s in
362
pktrx.draw LINE1
363
pktrx.cdef pktrx,-1,*
364
pkttx.label $if pkt/s out
365
pkttx.draw LINE1
366
EOF
367
#foo.negative bar
368
		if ($should_graph->{'bytes'}) {
369
			print <<EOF;
370
multigraph @{[ graph_name() ]}.@{[ if_to_name($if) ]}.bps
371
graph_title $if - bits per seconds
372
graph_vlabel bits per seconds
373
graph_args --base 1000
374
graph_scale no
375
update_rate 1
376
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
377
rx.label bps received
378
rx.type DERIVE
379
rx.min 0
380
rx.cdef rx,8,*
381
tx.label bps sent
382
tx.type DERIVE
383
tx.min 0
384
tx.cdef tx,8,*
385
EOF
386
		}
387
		if ($should_graph->{'packets'}) {
388
			print <<EOF;
389
multigraph @{[ graph_name() ]}.@{[ if_to_name($if) ]}.pkt
390
graph_title $if - packets per seconds
391
graph_vlabel packets per seconds
392
graph_args --base 1000
393
graph_scale no
394
update_rate 1
395
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
396
rx.label packets per second received
397
rx.type DERIVE
398
rx.min 0
399
tx.label packets per second sent
400
tx.type DERIVE
401
tx.min 0
402
EOF
403
		}
404
		if ($should_graph->{'errors'}) {
405
			print <<EOF;
406
multigraph @{[ graph_name() ]}.@{[ if_to_name($if) ]}.err
407
graph_title $if - errors
408
graph_vlabel errors
409
graph_scale no
410
update_rate 1
411
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
412
graph_order rxerr txerr rxdrp txdrp rxovr txovr rxfrm txcar
413
rxerr.label errors per second (in)
414
rxerr.type DERIVE
415
rxerr.min 0
416
txerr.label errors per second (out)
417
txerr.type DERIVE
418
txerr.min 0
419
rxdrp.label drop per second (in)
420
rxdrp.type DERIVE
421
rxdrp.min 0
422
txdrp.label drop per second (out)
423
txdrp.type DERIVE
424
txdrp.min 0
425
rxovr.label overruns per second (in)
426
rxovr.type DERIVE
427
rxovr.min 0
428
txovr.label overruns per second (out)
429
txovr.type DERIVE
430
txovr.min 0
431
rxfrm.label frame per second (in)
432
rxfrm.type DERIVE
433
rxfrm.min 0
434
txcar.label carrier per second (out)
435
txcar.type DERIVE
436
txcar.min 0
437
EOF
438
		}
439
	}
440
}
441
sub check_req()
442
{
443
	my $data = get_data();	
444
	if (0 != scalar(keys %$data)) {
445
		return 1;
446
	}
447
	return 0;
448
}
449
450
451
########################################################################
452 fba800ae Veres Lajos
# beyond that line, it should be generic stuffs, not dependent on what
453 cdf6eeb3 Adrien "ze" Urban
# you are trying to graph
454
#
455
456
sub check_running() {
457
	if (-f pidfile()) {
458
		my $pid = undef;
459
		if (open FILE, "<", pidfile()) {
460
			$pid = <FILE>;
461
			close FILE;
462
			chomp $pid;
463
		}
464
		if ($pid) {
465
			# does not exist ? kill it
466
			if (kill 0, $pid) {
467
				return 1;
468
			}
469
		}
470
 		unlink(pidfile());
471
	}
472
	return 0;
473
}
474
475
476
# FIXME: should also trap kill sigint and sigterm
477
# FIXME: check pidfile got touched recently
478
sub collect_loop() {
479
	$0 = acquire_name();
480
	$0 = "<$plugin> collecting information";
481
	# write our pid
482
	open PIDFILE, '>', pidfile() or die "open: @{[ pidfile() ]}: $!\n";
483
	print PIDFILE $$, "\n";
484
	close PIDFILE;
485
	# open cache
486
	my $fh_cache;
487
	open $fh_cache, ">>", cachefile() or
488
			die "open: @{[ cachefile() ]}: $!\n";
489
	my @tick = Time::HiRes::gettimeofday();
490
	my $flush_count = 0;
491
	while (1) {
492
		collect_info_once($fh_cache, $tick[0]);
493
		if ($flush_interval) {
494
			if ($flush_interval == ++$flush_count) {
495
				$fh_cache->flush();
496
				$flush_count = 0;
497
			}
498
		}
499
		my @now = Time::HiRes::gettimeofday();
500
		# when should the next tick be ?
501
		$tick[0] += $update_rate;
502
		# how long until next tick ?
503
		my $diff = ($tick[0] - $now[0]) * 1000000
504
				+ $tick[1] - $now[1];
505
		if ($diff <= 0) {
506
			# next tick already passed ? damn!
507
			@tick = @now;
508
		} else {
509
			# sleep what remains
510
			Time::HiRes::usleep($diff);
511
		}
512
	}
513
	unlink(pidfile());
514
	unlink(cachefile());
515
}
516
517
# launch daemon if not running
518
# notify the daemon we still need it (touch its pid)
519
sub daemon_alive() {
520
	if (check_running()) {
521
		my $atime;
522
		my $mtime;
523
		$atime = $mtime = time;
524
		utime $atime, $mtime, pidfile();
525
	} else {
526
		if (0 == fork()) {
527
			close(STDIN);
528
			close(STDOUT);
529
			close(STDERR);
530
			open STDIN, "<", "/dev/null";
531
			open STDOUT, ">", "/dev/null";
532
			open STDERR, ">", "/dev/null";
533
			collect_loop();
534
			exit(0);
535
		}
536
	}
537
}
538
539
540
sub run_autoconf() {
541
	if (check_req()) {
542
		print "yes\n";
543
	} else {
544
		print "no\n";
545
	}
546
}
547
548
sub run_config() {
549
	daemon_alive();
550
	show_config();
551
}
552
553
sub fetch_showline($) {
554
	my $line = shift;
555
	my $names = get_data_names($line);
556
	# don't display anything if we don't like what it is
557
	return unless (defined $names);
558
	my $graph = shift @$line;
559
	my $time = shift @$line;
560
	foreach my $value (@$line) {
561
		my $name = shift @$names;
562
		print <<EOF;
563
$name.value $time:$value
564
EOF
565
	}
566
}
567
sub run_fetch() {
568
	daemon_alive();
569
	if (open CACHE, "+<", cachefile()) {
570
		my $data = {};
571
		while (<CACHE>) {
572
			chomp;
573
			my $field = [];
574
			@$field = split(/ /);
575
			if (not defined $data->{$field->[0]}) {
576
				$data->{$field->[0]} = [];
577
			}
578
			push @{$data->{$field->[0]}}, $field;
579
		}
580 fba800ae Veres Lajos
		# finished reading ? truncate it right away
581 cdf6eeb3 Adrien "ze" Urban
		truncate CACHE, 0;
582
		close CACHE;
583
		foreach my $graph (keys %$data) {
584
			print <<EOF;
585
multigraph @{[ graph_name() ]}.$graph
586
EOF
587
			foreach my $line (@{$data->{$graph}}) {
588
				fetch_showline($line);
589
			}
590
		}
591
	}
592
}
593
594
my $cmd = 'fetch';
595
if (defined $ARGV[0]) {
596
	$cmd = $ARGV[0];
597
}
598
if ('fetch' eq $cmd) {
599
	run_fetch();
600
} elsif ('config' eq $cmd) {
601
	run_config();
602
} elsif ('autoconf' eq $cmd) {
603
	run_autoconf();
604
} elsif ('daemon' eq $cmd) {
605
	run_daemon();
606
} else {
607
	print STDERR <<EOF;
608
$0: unrecognized command
609
610
Usage:
611
	$0 autoconf - check if we have everything we need
612
	$0 config - show plugin configuration
613
	$0 fetch - fetch latest data
614
	$0 daemon - launch daemon
615
EOF
616
	exit(1);
617
}
618
exit(0);