]> jfr.im git - munin-plugins.git/blob - asterisk
support for a 2nd unbound plugin
[munin-plugins.git] / asterisk
1 #!/usr/bin/perl -w
2 # -*- cperl -*-
3
4 =head1 NAME
5
6 asterisk - Multigraph-capable plugin to monitor Asterisk
7
8 =head1 NOTES
9
10 This plugin will produce multiple graphs showing:
11
12 - total number of active channels (replaces asterisk_channels),
13 together with breakdown of specific channel types (replaces
14 asterisk_channelstypes);
15
16 - the number of messages in all voicemail boxes (replaces
17 asterisk_voicemail);
18
19 - DEPRECATED: the number of active MeetMe conferences and users connected to
20 them (replace asterisk_meetme and asterisk_meetmeusers, respectively);
21
22 - the number of active ConfBridge conferences (e.g. non-empty ones) and users
23 connected to them
24
25 - the number of active channels for a given codec, for both SIP and
26 IAX2 channels (replaces asterisk_sipchannels and asterisk_codecs).
27
28 =head1 CONFIGURATION
29
30 The following configuration parameters are used by this plugin
31
32 [asterisk]
33 env.host - hostname to connect to
34 env.port - port number to connect to
35 env.username - username used for authentication
36 env.secret - secret used for authentication
37 env.channels - The channel types to look for
38 env.codecsx - List of codec IDs (hexadecimal values)
39 env.codecs - List of codecs names, matching codecsx order
40 env.enable_meetme - Set to 1 to enable graphs for the MeetMe application
41 env.enable_confbridge - Set to 1 to enable graphs for the ConfBridge application
42
43 The "username" and "secret" parameters are mandatory, and have no
44 defaults.
45
46 =head2 DEFAULT CONFIGURATION
47
48 [asterisk]
49 env.host 127.0.0.1
50 env.port 5038
51 env.channels Zap IAX2 SIP
52 env.codecsx 0x2 0x4 0x8
53 env.codecs gsm ulaw alaw
54 env.enable_meetme 0
55 env.enable_confbridge 1
56
57 =head2 WILDCARD CONFIGURATION
58
59 It's possible to use the plugin in a virtual-node capacity, in which
60 case the host configuration will default to the hostname following the
61 underscore:
62
63 [asterisk_someserver]
64 env.host someserver
65 env.port 5038
66
67 =head1 AUTHOR
68
69 Copyright (C) 2005-2006 Rodolphe Quiédeville <rodolphe@quiedeville.org>
70 Copyright (C) 2012 Diego Elio Pettenò <flameeyes@flameeyes.eu>
71
72 =head1 LICENSE
73
74 GPLv2
75
76 =head1 MAGIC MARKERS
77
78 #%# family=auto
79 #%# capabilities=autoconf
80
81 =cut
82
83 use strict;
84 use Munin::Plugin;
85 use IO::Socket;
86
87 # See the following and its subpages for change history in the AMI protocol:
88 # https://wiki.asterisk.org/wiki/display/AST/Asterisk+Manager+Interface+%28AMI%29+Changes
89 sub asterisk_command {
90 my ($socket, $command) = @_;
91 my $line, my $reply;
92
93 $socket->print("Action: command\nCommand: $command\n\n");
94
95 # Response: (Error|Follows|Success)
96 $line = $socket->getline;
97 if ($line !~ /^Response: Success\r?\n$/) {
98 while ( $line = $socket->getline and $line !~ /^\r?\n$/ ) {
99 print STDERR "COMMAND: Ignoring unwanted line: $line" if $Munin::Plugin::DEBUG;
100 }
101 return undef;
102 }
103
104 # Message: Command output follows
105 $line = $socket->getline;
106 print STDERR "COMMAND got response: $line" if $Munin::Plugin::DEBUG;
107
108 # Until we get the --END COMMAND-- marker, it's the command's output.
109 while ( $line = $socket->getline and $line =~ /^Output:/ ) {
110 print STDERR "COMMAND: got response: $line" if $Munin::Plugin::DEBUG;
111 # Don't keep the "Output: " part of the response
112 substr($line, 0, 8, '');
113 $reply .= $line;
114 }
115 return $reply;
116 }
117
118 $0 =~ /asterisk(?:_(.+))$/;
119 my $hostname = $1;
120
121 my $peeraddr = $ENV{'host'} || $hostname || '127.0.0.1';
122 my $peerport = $ENV{'port'} || '5038';
123
124 my $username = $ENV{'username'};
125 my $secret = $ENV{'secret'};
126
127 my @CHANNELS = exists $ENV{'channels'} ? split ' ',$ENV{'channels'} : qw(Zap IAX2 SIP);
128 my @CODECS = exists $ENV{'codecs'} ? split ' ',$ENV{'codecs'} : qw(gsm ulaw alaw);
129 my @CODECSX = exists $ENV{'codecsx'} ? split ' ',$ENV{'codecsx'} : qw(0x2 0x4 0x8);
130
131 my $meetme_enabled = $ENV{'enable_meetme'} || '0';
132 my $confbridge_enabled = $ENV{'enable_confbridge'} || '1';
133
134 my $line, my $error;
135 my $socket = new IO::Socket::INET(PeerAddr => $peeraddr,
136 PeerPort => $peerport,
137 Proto => 'tcp')
138 or $error = "Could not create socket: $!";
139
140 if ( $socket ) {
141 # This will consume the "Asterisk Call Manager" welcome line.
142 $socket->getline;
143
144 $socket->print("Action: login\nUsername: $username\nSecret: $secret\nEvents: off\n\n");
145 my $response_status = $socket->getline;
146
147 if ( $response_status !~ /^Response: Success\r?\n$/ ) {
148 my $response_message = $socket->getline;
149 $response_message =~ s/Message: (.*)\r?\n/$1/;
150 $error = "Asterisk authentication error: " . $response_message;
151 }
152
153 while ( $line = $socket->getline and $line !~ /^\r?\n$/ ) {}
154 }
155
156 if ( $ARGV[0] and $ARGV[0] eq 'autoconf' ) {
157 if ( $error ) {
158 print "no ($error)\n";
159 } else {
160 print "yes\n";
161 }
162
163 exit 0;
164 } elsif ( $ARGV[0] and $ARGV[0] eq 'config' ) {
165 print "host_name $hostname" if $hostname;
166
167 print <<END;
168
169 multigraph asterisk_channels
170 graph_title Asterisk active channels
171 graph_args --base 1000 -l 0
172 graph_vlabel channels
173 graph_category asterisk
174 total.label channels
175 END
176
177 foreach my $channel (@CHANNELS) {
178 print <<END;
179 $channel.draw AREASTACK
180 $channel.label $channel channels
181 END
182 }
183
184 print <<END;
185
186 multigraph asterisk_voicemail
187 graph_title Asterisk voicemail messages
188 graph_args --base 1000 -l 0
189 graph_vlabel messages
190 graph_category asterisk
191 total.label Total messages
192 END
193
194 if ($meetme_enabled == '1') {
195 print <<END;
196
197 multigraph asterisk_meetme
198 graph_title Asterisk meetme statistics
199 graph_args --base 1000 -l 0
200 graph_category asterisk
201 users.label Connected users
202 conferences.label Active conferences
203 END
204 }
205
206 if ($confbridge_enabled == '1') {
207 print <<END;
208
209 multigraph asterisk_confbridge
210 graph_title Asterisk ConfBridge statistics
211 graph_args --base 1000 -l 0
212 graph_category asterisk
213 users.label Connected users
214 conferences.label Active conferences
215 END
216 }
217
218 print <<END;
219
220 multigraph asterisk_codecs
221 graph_title Asterisk channels per codec
222 graph_args --base 1000 -l 0
223 graph_vlabel channels
224 graph_category asterisk
225 END
226
227 foreach my $codec (@CODECS) {
228 print <<END;
229 $codec.draw AREASTACK
230 $codec.label $codec channels
231 END
232 }
233
234 print <<END;
235 other.draw AREASTACK
236 other.label Other known codecs
237 unknown.draw AREASTACK
238 unknown.label Unknown codec
239 END
240
241 unless ( ($ENV{MUNIN_CAP_DIRTYCONFIG} || 0) == 1 ) {
242 exit 0;
243 }
244 }
245
246 # if we arrive here and we don't have a socket, it's time to exit.
247 die $error if $error;
248
249 my $channels_response = asterisk_command($socket, "core show channels");
250 #Channel Location State Application(Data)
251 #Zap/pseudo-198641660 s@frompstn:1 Rsrvd (None)
252 #Zap/1-1 4@frompstn:1 Up ConfBridge(5500)
253 #2 active channels
254 #1 active call
255
256 my $voicemail_response = asterisk_command($socket, "voicemail show users");
257 #Context Mbox User Zone NewMsg
258 #default 1234 Example Mailbox 1
259 #other 1234 Company2 User 0
260 #2 voicemail users configured.
261
262 my $meetme_response;
263 if ($meetme_enabled eq '1') {
264 $meetme_response = asterisk_command($socket, "meetme list");
265 #Conf Num Parties Marked Activity Creation
266 #5500 0001 N/A 00:00:03 Static
267 #* Total number of MeetMe users: 1
268 }
269
270 my $confbridge_response;
271 if ($confbridge_enabled eq '1') {
272 $confbridge_response = asterisk_command($socket, "confbridge list");
273 #Conference Bridge Name Users Marked Locked Muted
274 #================================ ====== ====== ====== =====
275 #3 1 0 No No
276 }
277
278 my $sipchannels_response = asterisk_command($socket, "sip show channels");
279 #Peer User/ANR Call ID Format Hold Last Message Expiry Peer
280 #192.168.1.135 yann 1341929961-161 00101/00002 No Rx: INVITE g729
281 #1 active SIP channel(s)
282
283 my $iaxchannels_response = asterisk_command($socket, "iax2 show channels");
284 #Channel Peer Username ID (Lo/Rem) Seq (Tx/Rx) Lag Jitter JitBuf Format FirstMsg LastMsg
285 #IAX2/rodolphe@rodolp 10.8.53.6 rodolphe 00003/01287 00006/00004 00000ms 0148ms 0000ms gsm Rx:NEW Tx:ACK
286 #1 active IAX channel(s)
287
288 # After all the data is fetched we can proceed to process it, the
289 # connection can be closed as we don't need any more data.
290 $socket->close();
291
292 my $active_channels = 'U';
293 $active_channels = $1 if $channels_response =~ /\n([0-9]+) active channels?/;
294
295 print <<END;
296
297 multigraph asterisk_channels
298 total.value $active_channels
299 END
300
301 my @channels_list = split(/\r?\n/, $channels_response) if $channels_response;
302 foreach my $channel (@CHANNELS) {
303 print "$channel.value ";
304 print $channels_response ? scalar(grep(/^$channel\//, @channels_list)) : "U";
305 print "\n";
306 }
307
308 print "\nmultigraph asterisk_voicemail\n";
309 if ( !$voicemail_response or $voicemail_response =~ /no voicemail users/ ) {
310 print "total.value U\n";
311 } else {
312 my $messages = 0;
313 foreach my $line (split(/\r?\n/, $voicemail_response)) {
314 next unless $line =~ / ([0-9]+)$/;
315 $messages += $1;
316 }
317
318 print "total.value $messages\n";
319 }
320
321 if ($meetme_enabled == '1') {
322 print "\nmultigraph asterisk_meetme\n";
323 if ( !$meetme_response ) {
324 print <<END;
325 users.value U
326 conferences.value U
327 END
328 } else {
329 if ( $meetme_response =~ /No active/ ) {
330 print <<END;
331 users.value 0
332 conferences.value 0
333 END
334 } else {
335 my @meetme_list = split(/\r?\n/, $meetme_response);
336
337 my $users = pop(@meetme_list);
338 $users =~ s/^Total number of MeetMe users: ([0-9]+)$/$1/;
339
340 print "users.value $users\n";
341 print "conferences.value " . (scalar(@meetme_list)-1) . "\n";
342 }
343 }
344 }
345
346 if ($confbridge_enabled == '1') {
347 print "\nmultigraph asterisk_confbridge\n";
348 if ( !$confbridge_response ) {
349 print <<END;
350 users.value U
351 conferences.value U
352 END
353 } else {
354 my @confbridge_list = split(/\r?\n/, $confbridge_response);
355 # Remove column headers, then line of =
356 shift(@confbridge_list);
357 shift(@confbridge_list);
358
359 my $users = 0;
360 foreach my $bridge (@confbridge_list) {
361 my @fields = split ' ', $bridge;
362 # yes we ARE parsing a command's output. if we end up getting some
363 # unexpected things, just break out to and avoid computing nonsense.
364 if (scalar(@fields) < 5 or $fields[1] !~ /^[0-9]+$/) {
365 last;
366 }
367 $users += $fields[1];
368 }
369
370 print "users.value $users\n";
371 print "conferences.value " . (scalar(@confbridge_list)) . "\n";
372 }
373 }
374
375 print "\nmultigraph asterisk_codecs\n";
376 if ( !$sipchannels_response and !$iaxchannels_response ) {
377 foreach my $codec (@CODECS) {
378 print "$codec.value U\n";
379 }
380 print <<END;
381 other.value U
382 unknown.value U
383 END
384 } else {
385 my %results;
386 my ($start, $unknown, $other)=(0,0,0);
387 foreach my $codec (@CODECS) {
388 $results{$codec} = 0;
389 }
390
391 # split the channels' listing and drop header and footnotes
392 my @sipchannels = $sipchannels_response ? split(/\r?\n/, $sipchannels_response) : ();
393 pop(@sipchannels); shift(@sipchannels);
394 my @iaxchannels = $iaxchannels_response ? split(/\r?\n/, $iaxchannels_response) : ();
395 pop(@iaxchannels); shift(@iaxchannels);
396
397 foreach my $sipchan (@sipchannels) {
398 my $found = 0;
399 my @fields = split ' ', $sipchan;
400 $fields[3] =~ s/[()]//g;
401 if ($fields[3] eq 'nothing') {
402 $unknown += 1;
403 next;
404 }
405 foreach my $codec (@CODECS) {
406 if ($fields[3] eq "$codec") {
407 $results{$codec} = $results{$codec} + 1;
408 $found = 1;
409 last;
410 }
411 }
412 if (! $found) {
413 $other += 1;
414 print STDERR "CODEC: SIP other format $fields[4]\n" if $Munin::Plugin::DEBUG;
415 }
416 }
417
418 foreach my $iaxchan (@iaxchannels) {
419 my $found = 0;
420 my @fields = split ' ', $iaxchan;
421
422 if ($fields[8] eq '0x0') {
423 $unknown += 1;
424 next;
425 }
426 foreach my $codec (@CODECS) {
427 if ($fields[8] eq "$codec") {
428 $results{$codec} = $results{$codec} + 1;
429 $found = 1;
430 last;
431 }
432 }
433 if (! $found) {
434 $other += 1;
435 print STDERR "CODEC: IAX2 other format: $fields[8]\n" if $Munin::Plugin::DEBUG;
436 }
437 }
438
439 foreach my $codec (@CODECS) {
440 print "$codec.value $results{$codec}\n";
441 }
442 print "other.value $other\n";
443 print "unknown.value $unknown\n";
444 }