#!/usr/bin/perl -w =encoding utf8 =head1 NAME asterisk - Multigraph-capable plugin to monitor Asterisk =head1 NOTES This plugin will produce multiple graphs showing: - total number of active channels (replaces asterisk_channels), together with breakdown of specific channel types (replaces asterisk_channelstypes); - the number of messages in all voicemail boxes (replaces asterisk_voicemail); - the number of active MeetMe conferences and users connected to them (replace asterisk_meetme and asterisk_meetmeusers, respectively); - the number of active channels for a given codec, for both SIP and IAX2 channels (replaces asterisk_sipchannels and asterisk_codecs). =head1 CONFIGURATION The following configuration parameters are used by this plugin [asterisk] env.host - hostname to connect to env.port - port number to connect to env.username - username used for authentication env.secret - secret used for authentication env.channels - The channel types to look for env.codecsx - List of codec IDs (hexadecimal values) env.codecs - List of codecs names, matching codecsx order The "username" and "secret" parameters are mandatory, and have no defaults. =head2 DEFAULT CONFIGURATION [asterisk] env.host 127.0.0.1 env.port 5038 env.channels Zap IAX2 SIP env.codecsx 0x2 0x4 0x8 env.codecs gsm ulaw alaw =head2 WILDCARD CONFIGURATION It's possible to use the plugin in a virtual-node capacity, in which case the host configuration will default to the hostname following the underscore: [asterisk_someserver] env.host someserver env.port 5038 =head1 AUTHOR Copyright (C) 2005-2006 Rodolphe Quiedeville Copyright (C) 2012 Diego Elio Pettenò =head1 LICENSE GPLv2 =head1 MAGIC MARKERS #%# family=auto #%# capabilities=autoconf =cut use strict; use Munin::Plugin; use Munin::Plugin::Framework; use IO::Socket; sub asterisk_command { my ($socket, $command) = @_; my $line, my $reply; $socket->print("Action: command\nCommand: $command\n\n"); # Response: (Error|Follows|???) $line = $socket->getline; if ($line ne "Response: Follows\r\n") { while ( $line = $socket->getline and $line ne "\r\n" ) {} return undef; } # Privilege: Command $line = $socket->getline; # Until we get the --END COMMAND-- marker, it's the command's output. while ( $line = $socket->getline and $line ne "--END COMMAND--\r\n" ) { $reply .= $line; } # And then wait for the empty line that says we're done while ( $line = $socket->getline and $line ne "\r\n" ) {} return $reply; } my $plugin = Munin::Plugin::Framework->new; $0 =~ /asterisk(?:_(.+))$/; $plugin->{hostname} = $1; my $peeraddr = $ENV{'host'} || $plugin->{hostname} || '127.0.0.1'; my $peerport = $ENV{'port'} || '5038'; my $username = $ENV{'username'}; my $secret = $ENV{'secret'}; my @CHANNELS = exists $ENV{'channels'} ? split ' ',$ENV{'channels'} : qw(Zap IAX2 SIP); my @CODECS = exists $ENV{'codecs'} ? split ' ',$ENV{'codecs'} : qw(gsm ulaw alaw); my @CODECSX = exists $ENV{'codecsx'} ? split ' ',$ENV{'codecsx'} : qw(0x2 0x4 0x8); my %CODEC_IDS; @CODEC_IDS{@CODECS} = @CODECSX; my $line, my $error; my $socket = new IO::Socket::INET(PeerAddr => $peeraddr, PeerPort => $peerport, Proto => 'tcp') or $error = "Could not create socket: $!"; if ( $socket ) { # This will consume the "Asterisk Call Manager" welcome line. $socket->getline; $socket->print("Action: login\nUsername: $username\nSecret: $secret\nEvents: off\n\n"); my $response_status = $socket->getline; if ( $response_status ne "Response: Success\r\n" ) { my $response_message = $socket->getline; $response_message =~ s/Message: (.*)\r\n/$1/; $error = "Asterisk authentication error: " . $response_message; } while ( $line = $socket->getline and $line ne "\r\n" ) {} } my ($active_channels, $messages, @channels_list, $users, $conferences, %codecs); if ( !$error ) { my $channels_response = asterisk_command($socket, "core show channels"); #Channel Location State Application(Data) #Zap/pseudo-198641660 s@frompstn:1 Rsrvd (None) #Zap/1-1 4@frompstn:1 Up MeetMe(5500) #2 active channels #1 active call $active_channels = $1 if $channels_response =~ /\n([0-9]+) active channels/; @channels_list = split(/\r\n/, $channels_response); my $voicemail_response = asterisk_command($socket, "voicemail show users"); #Context Mbox User Zone NewMsg #default 1234 Example Mailbox 1 #other 1234 Company2 User 0 #2 voicemail users configured. if ( $voicemail_response and $voicemail_response !~ /no voicemail users/ ) { $messages = 0; foreach my $line (split(/\r\n/, $voicemail_response)) { next unless $line =~ / ([0-9]+)$/; $messages += $1; } } my $meetme_response = asterisk_command($socket, "meetme list"); #Conf Num Parties Marked Activity Creation #5500 0001 N/A 00:00:03 Static #* Total number of MeetMe users: 1 if ( $meetme_response ) { if ( $meetme_response =~ /No active/ ) { $users = 0; $conferences = 0; } else { my @meetme_list = split(/\r\n/, $meetme_response); $users = pop(@meetme_list); $users =~ s/^Total number of MeetMe users: ([0-9]+)$/$1/; $conferences = scalar(@meetme_list) -1; } } my $sipchannels_response = asterisk_command($socket, "sip show channels"); #Peer User/ANR Call ID Seq (Tx/Rx) Format #192.168.1.135 yann 6902112b3e0 00101/00002 g729 #1 active SIP channel(s) my $iaxchannels_response = asterisk_command($socket, "iax2 show channels"); #Channel Peer Username ID (Lo/Rem) Seq (Tx/Rx) Lag Jitter JitBuf Format #IAX2/rodolphe@rodolp 10.8.53.6 rodolphe 00003/01287 00006/00004 00000ms 0148ms 0000ms gsm #1 active IAX channel(s) if ( $sipchannels_response or $iaxchannels_response ) { %codecs = ( other => 0, unknown => 0 ); foreach my $codec (@CODECS) { $codecs{$codec} = 0; } # split the channels' listing and drop header and footnotes my @sipchannels = $sipchannels_response ? split(/\r\n/, $sipchannels_response) : (); pop(@sipchannels); shift(@sipchannels); my @iaxchannels = $iaxchannels_response ? split(/\r\n/, $iaxchannels_response) : (); pop(@iaxchannels); shift(@iaxchannels); foreach my $sipchan (@sipchannels) { my $found = 0; my @fields = split ' ', $sipchan; if ($fields[4] eq '0x0') { $codecs{unknown} += 1; next; } foreach my $codec (@CODECS) { if ($fields[4] eq $CODEC_IDS{$codec}) { $codecs{$codec} += 1; $found = 1; last; } } if (! $found) { $codecs{other} += 1; print STDERR "CODEC: SIP other format $fields[4]\n" if $Munin::Plugin::DEBUG; } } foreach my $iaxchan (@iaxchannels) { my $found = 0; my @fields = split ' ', $iaxchan; if ($fields[8] eq '0x0') { $codecs{unknown} += 1; next; } foreach my $codec (@CODECS) { if ($fields[8] eq $CODEC_IDS{$codec}) { $codecs{$codec} += 1; $found = 1; last; } } if (! $found) { $codecs{other} += 1; print STDERR "CODEC: IAX2 other format: $fields[8]\n" if $Munin::Plugin::DEBUG; } } } } # After all the data is fetched we can proceed to process it, the # connection can be closed as we don't need any more data. $socket->close() if $socket; $plugin->{autoconf} = "no ($error)" if $error; $plugin->add_graphs ( asterisk_channels => { title => "Asterisk active channels", args => "--base 1000 -l 0", vlabel => "channels", category => "voip", order => "total " . join(' ', @CHANNELS), fields => { total => { label => "channels", min => 0, value => $active_channels, }, }, }, asterisk_voicemail => { title => "Asterisk voicemail messages", args => "--base 1000 -l 0", vlabel => "messages", category => "voip", fields => { messages => { label => "Total messages", min => 0, value => $messages, }, }, }, asterisk_meetme => { title => "Asterisk meetme statistics", args => "--base 1000 -l 0", category => "voip", fields => { users => { label => "Connected Users", min => 0, value => $users, }, conferences => { label => "Active conferences", min => 0, value => $conferences, }, }, }, asterisk_codecs => { title => "Asterisk channels per codec", args => "--base 1000 -l 0", vlabel => "channels", category => "voip", order => join(' ', @CODECS) . " other unknown", fields => { other => { label => "Other known codecs", min => 0, draw => "AREASTACK", value => $codecs{other}, }, unknown => { label => "Unknown codec", min => 0, draw => "AREASTACK", value => $codecs{unknown}, }, }, }, ); foreach my $channel (@CHANNELS) { $plugin->{graphs}->{asterisk_channels}->{fields}->{$channel} = { draw => "AREASTACK", label => "$channel channels", min => 0, value => @channels_list ? scalar(grep(/^$channel\//, @channels_list)) : undef, }; } foreach my $codec (@CODECS) { $plugin->{graphs}->{asterisk_codecs}->{fields}->{$codec} = { label => "$codec channels", min => 0, draw => "AREASTACK", value => $codecs{$codec}, }; } $plugin->run;