Projet

Général

Profil

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

root / plugins / system / cpu_linux_multi @ ff1c67d5

Historique | Voir | Annoter | Télécharger (10,7 ko)

1
#! /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, extended cpu informations
38
#
39
# require: mpstat (to actually collect the data)
40
# require linux /proc
41
#   (sorry, quick/dirty retrieve the number of cpu from /proc/cpuinfo)
42
#
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_MPSTAT - binary to use as mpstat
51
#
52
#  increase cache flush rate if you have i/o performance issues
53
#  warning: increasing flushrate too much might cause partial write, and
54
#  loss of data. 0 to disable flush
55
#
56
#
57
# Parent graph: cpu usage per core/thread
58
# child graph(1): detailed cpu usage overall
59
# child graph(n): detailed cpu usage per thread
60
#
61
# Known bugs:
62
#
63
#   Multi-Master
64
#     If there are many masters, the data is only sent once. Each master will
65
#     only have part of the data.
66
#
67
#   Everlasting
68
#     The daemon is launched on first config/fetch. A touch of the pidfile is
69
#     done on every following config/fetch. The daemon should check if the
70
#     pidfile is recent (configurable) enough, and stop itself if not.
71
#
72
#   Graph Order
73
#     There is currently (2.0.6) noway to order childgraphs.
74
#
75
#   RRD file
76
#     The master currently (2.0.6) generate rrd file for aggregate values, and
77
#     complains that no data is provided for them (but the graph still works
78
#     fine)
79
#
80

    
81
#%# family=auto
82
#%# capabilities=autoconf
83

    
84
use strict;
85
use warnings;
86

    
87
my $plugin = $0;
88
$plugin =~ s/.*\///;
89

    
90
# quick failsafe
91
if (!defined $ENV{MUNIN_PLUGSTATE}) {
92
	die "This plugin should be run via munin. Try munin-run $plugin\n";
93
}
94

    
95
########################################################################
96
# If you want to change something, it's probably doable here
97
#
98

    
99
# order to display
100
my $fields_order = [
101
	'sys', 'usr', 'nice', 'idle', 'iowait', 'irq', 'soft', 'steal', 'guest',
102
];
103
# order is the order given by mpstat
104
my $fields_info = [
105
	{
106
		name => 'usr',
107
		label => 'usr',
108
		info => "%s time spent in normal programs and daemons",
109
	}, {
110
		name => 'nice',
111
		label => 'nice',
112
		info => "%s time spent in nice(1)d programs and daemons",
113
	}, {
114
		name => 'sys',
115
		label => 'sys',
116
		info => "%s time spent in kernel system activity",
117
	}, {
118
		name => 'iowait',
119
		label => 'iowait',
120
		info => "%s time spent waiting for blocking I/O operations",
121
	}, {
122
		name => 'irq',
123
		label => 'irq',
124
		info => "%s time spent handling interrupts",
125
	}, {
126
		name => 'soft',
127
		label => 'soft',
128
		info => "%s time spent handling software interrupts",
129
	}, {
130
		name => 'steal',
131
		label => 'steal',
132
		info => "%s time spent elsewhere (stolen from us)",
133
	}, {
134
		name => 'guest',
135
		label => 'guest',
136
		info => "%s time spent in a guest operating system",
137
	}, {
138
		name => 'idle',
139
		label => 'idle',
140
		info => "%s time spent idling (waiting to get something to do)",
141
	}
142
];
143

    
144
sub pidfile() { "$ENV{MUNIN_PLUGSTATE}/munin.$plugin.pid" }
145
sub cachefile() { "$ENV{MUNIN_PLUGSTATE}/munin.$plugin.cache" }
146

    
147
sub graph_section() { "system:cpu" };
148
sub graph_name() { "cpu_extended_multi_1s" };
149
sub graph_title() { "CPU usage" };
150
sub graph_title_all() { "Overall CPU usage" };
151
sub graph_title_n($) { "CPU#" . shift . " usage" };
152
sub acquire_name() { "<$plugin> collecting information" }
153

    
154
########################################################################
155
# if you need to change something after that line, It should probably be
156
# changed to be configurable above it.
157
#
158

    
159
# mpstat sampling interval
160
my $update_rate = 1;
161
if (defined $ENV{MUNIN_UPDATERATE}) {
162
	if ($ENV{MUNIN_UPDATERATE} =~ /^[1-9][0-9]*$/) {
163
		$update_rate = int($ENV{MUNIN_UPDATERATE});
164
	} else {
165
		print STDERR "Invalid update_rate: $ENV{MUNIN_UPDATERATE}";
166
	}
167
}
168

    
169
my $flush_interval = 1;
170
if (defined $ENV{MUNIN_CACHEFLUSH_RATE}) {
171
	if ($ENV{MUNIN_CACHEFLUSH_RATE} =~ /^[0-9]+$/) {
172
		$update_rate = int($ENV{MUNIN_CACHEFLUSH_RATE});
173
	} else {
174
		print STDERR "Invalid flush rate: $ENV{MUNIN_CACHEFLUSH_RATE}";
175
	}
176
}
177

    
178
my $mpstat = "mpstat";
179
if (defined $ENV{MUNIN_MPSTAT}) {
180
	if (-f $ENV{MUNIN_MPSTAT}) {
181
		print STDERR "MUNIN_STAT: file not found: $ENV{MUNIN_MPSTAT}";
182
	} else {
183
		$mpstat = defined $ENV{MUNIN_MPSTAT};
184
	}
185
}
186

    
187
my $cpu_count_cache = undef;
188
sub cpu_count() {
189
	# XXX: is there any way to do that cleanly ?
190
	if (not defined $cpu_count_cache) {
191
		$cpu_count_cache = `grep -c ^processor /proc/cpuinfo`;
192
		chomp $cpu_count_cache;
193
	}
194
	return $cpu_count_cache;
195
}
196

    
197
sub is_running() {
198
	if (-f pidfile()) {
199
		my $pid = undef;
200
		if (open FILE, "<", pidfile()) {
201
			$pid = <FILE>;
202
			close FILE;
203
			chomp $pid;
204
		}
205
		if ($pid) {
206
			# does not exist ? kill it
207
			if (kill 0, $pid) {
208
				return 1;
209
			}
210
		}
211
 		unlink(pidfile());
212
	}
213
	return 0;
214
}
215

    
216

    
217
# FIXME: should also trap kill sigint and sigterm
218
# FIXME: check pidfile got touched recently
219
sub acquire() {
220
	$0 = acquire_name();
221
	$ARGV = [ '<daemon>' ];
222
	$0 = "<$plugin> collecting information";
223
	open PIDFILE, '>', pidfile() or die "open: @{[ pidfile() ]}: $!\n";
224
	print PIDFILE $$, "\n";
225
	close PIDFILE;
226
	open CACHE, ">>", cachefile() or die "open: @{[ cachefile() ]}: $!\n";
227
	open MPSTAT, "-|", "$mpstat -P ALL $update_rate" or
228
		die "open mpstat|: $!\n";
229
	my $flush_count = 0;
230
	while (<MPSTAT>) {
231
		chomp;
232
		my @field = split();
233
		if (!($field[1] =~ /^(all|[0-9]+)$/)) {
234
			next;
235
		}
236
		$field[0] = $field[1];
237
		$field[1] = time();
238
		print CACHE join(" ", @field), "\n";
239
		if ($flush_interval) {
240
			if ($flush_interval == ++$flush_count) {
241
				CACHE->flush();
242
				$flush_count = 0;
243
			}
244
		}
245
	}
246
	unlink(pidfile());
247
	unlink(cachefile());
248
}
249

    
250
sub run_daemon() {
251
	if (is_running()) {
252
		my $atime;
253
		my $mtime;
254
		$atime = $mtime = time;
255
		utime $atime, $mtime, pidfile();
256
	} else {
257
		if (0 == fork()) {
258
			close(STDIN);
259
			close(STDOUT);
260
			close(STDERR);
261
			open STDIN, "<", "/dev/null";
262
			open STDOUT, ">", "/dev/null";
263
			open STDERR, ">", "/dev/null";
264
			acquire();
265
			exit(0);
266
		}
267
	}
268
}
269

    
270

    
271
sub run_autoconf() {
272
	# in case we have specified args, check the file before that
273
	my $file = $mpstat;
274
	$file =~ s/ .*//;
275
	my $path = `which "$file"`;
276
	if ($path) {
277
		print "yes\n";
278
	} else {
279
		print "no\n";
280
	}
281
}
282

    
283
sub show_config($$$) {
284
	my $i = shift;
285
	my $name = shift;
286
	my $title = shift;
287
	my $graph_order = "graph_order";
288
	for my $field (@$fields_order) {
289
		$graph_order .= " $field";
290
	}
291
	print <<EOF;
292
multigraph @{[ graph_name() ]}.cpu$i
293
graph_title $title
294
graph_vlabel cpu use %
295
graph_scale no
296
update_rate 1
297
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
298
$graph_order
299
EOF
300
	for my $field (@$fields_info) {
301
		my $style = "STACK";
302
		if ($field->{name} eq $fields_order->[0]) {
303
			$style = "AREA";
304
		}
305
		print <<EOF;
306
$field->{name}.label $field->{label}
307
$field->{name}.draw $style
308
$field->{name}.info @{[ sprintf($field->{info}, $name) ]}
309
$field->{name}.min 0
310
$field->{name}.cdef $field->{name}
311
EOF
312
	}
313
}
314

    
315
sub run_config() {
316
	run_daemon();
317
	my $cpus = cpu_count();
318
	my $graph_order = "graph_order";
319
	my $sub_order = "order cpuall";
320
	for (my $i = 0; $i < $cpus; ++$i) {
321
		$graph_order .= " use$i=@{[ graph_name() ]}.cpu$i.idle";
322
		$sub_order .= " cpu$i";
323
	}
324
# none of those seems to have any effect
325
#domain_$sub_order
326
#node_$sub_order
327
#graph_$sub_order
328
#service_$sub_order
329
#category_$sub_order
330
#group_$sub_order
331

    
332
	print <<EOF;
333
multigraph @{[ graph_name() ]}
334
graph_category @{[ graph_section() ]}
335
graph_title @{[ graph_title() ]}
336
graph_vlabel cpu use %
337
graph_scale no
338
graph_total All CPUs
339
update_rate 1
340
graph_data_size custom 1d, 10s for 1w, 1m for 1t, 5m for 1y
341
$graph_order
342
EOF
343
	my $style="AREA";
344
	for (my $i = 0; $i < $cpus; ++$i) {
345
		print <<EOF;
346
use$i.label CPU#$i
347
use$i.draw $style
348
use$i.cdef 100,use$i,-,${cpus},/
349
EOF
350
		$style = 'STACK';
351
	}
352
	# detailed sub graphs - 1 for all, and 1 per cpu
353
	show_config("all", "all CPU", graph_title_all());
354
	for (my $i = 0; $i < $cpus; ++$i) {
355
		show_config($i, "CPU$i", graph_title_n($i));
356
	}
357
}
358

    
359
sub fetch_showline($) {
360
	my $line = shift;
361
	my $n = 2;
362
	for my $field (@$fields_info) {
363
		print <<EOF;
364
$field->{name}.value $line->[1]:$line->[$n]
365
EOF
366
		++$n;
367
	}
368
}
369
sub run_fetch() {
370
	run_daemon();
371
	if (open CACHE, "+<", cachefile()) {
372
		my $cpus = {};
373
		while (<CACHE>) {
374
			chomp;
375
			my $field = [];
376
			@$field = split(/ /);
377
			if (not defined $cpus->{$field->[0]}) {
378
				$cpus->{$field->[0]} = [];
379
			}
380
			push @{$cpus->{$field->[0]}}, $field;
381
		}
382
		# finished reading ? trucate it right away
383
		truncate CACHE, 0;
384
		close CACHE;
385
		foreach my $cpu (keys %$cpus) {
386
			print <<EOF;
387
multigraph @{[ graph_name() ]}.cpu$cpu
388
EOF
389
			foreach my $line (@{$cpus->{$cpu}}) {
390
				fetch_showline($line);
391
			}
392
		}
393
	}
394
}
395

    
396
my $cmd = 'fetch';
397
if (defined $ARGV[0]) {
398
	$cmd = $ARGV[0];
399
}
400
if ('fetch' eq $cmd) {
401
	run_fetch();
402
} elsif ('config' eq $cmd) {
403
	run_config();
404
} elsif ('autoconf' eq $cmd) {
405
	run_autoconf();
406
} elsif ('daemon' eq $cmd) {
407
	run_daemon();
408
} else {
409
	print STDERR <<EOF;
410
$0: unrecognized command
411

    
412
Usage:
413
	$0 autoconf - check if we have everything we need
414
	$0 config - show plugin configuration
415
	$0 fetch - fetch latest data
416
	$0 daemon - launch daemon
417
EOF
418
	exit(1);
419
}
420
exit(0);