]> jfr.im git - munin-plugins.git/commitdiff
asterisk plugins
authorJohn Runyon <redacted>
Mon, 18 Dec 2023 16:18:35 +0000 (10:18 -0600)
committerJohn Runyon <redacted>
Mon, 18 Dec 2023 16:18:35 +0000 (10:18 -0600)
asterisk [new file with mode: 0755]
asterisk_sippeers [new file with mode: 0755]

diff --git a/asterisk b/asterisk
new file mode 100755 (executable)
index 0000000..bca322e
--- /dev/null
+++ b/asterisk
@@ -0,0 +1,444 @@
+#!/usr/bin/perl -w
+# -*- cperl -*-
+
+=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);
+
+ - DEPRECATED: the number of active MeetMe conferences and users connected to
+   them (replace asterisk_meetme and asterisk_meetmeusers, respectively);
+
+ - the number of active ConfBridge conferences (e.g. non-empty ones) and users
+   connected to them
+
+ - 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
+  env.enable_meetme     - Set to 1 to enable graphs for the MeetMe application
+  env.enable_confbridge - Set to 1 to enable graphs for the ConfBridge application
+
+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
+  env.enable_meetme 0
+  env.enable_confbridge 1
+
+=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 Quiédeville <rodolphe@quiedeville.org>
+Copyright (C) 2012 Diego Elio Pettenò <flameeyes@flameeyes.eu>
+
+=head1 LICENSE
+
+GPLv2
+
+=head1 MAGIC MARKERS
+
+ #%# family=auto
+ #%# capabilities=autoconf
+
+=cut
+
+use strict;
+use Munin::Plugin;
+use IO::Socket;
+
+# See the following and its subpages for change history in the AMI protocol:
+# https://wiki.asterisk.org/wiki/display/AST/Asterisk+Manager+Interface+%28AMI%29+Changes
+sub asterisk_command {
+  my ($socket, $command) = @_;
+  my $line, my $reply;
+
+  $socket->print("Action: command\nCommand: $command\n\n");
+
+  # Response: (Error|Follows|Success)
+  $line = $socket->getline;
+  if ($line !~ /^Response: Success\r?\n$/) {
+    while ( $line = $socket->getline and $line !~ /^\r?\n$/ ) {
+      print STDERR "COMMAND: Ignoring unwanted line: $line" if $Munin::Plugin::DEBUG;
+    }
+    return undef;
+  }
+
+  # Message: Command output follows
+  $line = $socket->getline;
+  print STDERR "COMMAND got response: $line" if $Munin::Plugin::DEBUG;
+
+  # Until we get the --END COMMAND-- marker, it's the command's output.
+  while ( $line = $socket->getline and $line =~ /^Output:/ ) {
+    print STDERR "COMMAND: got response: $line" if $Munin::Plugin::DEBUG;
+    # Don't keep the "Output: " part of the response
+    substr($line, 0, 8, '');
+    $reply .= $line;
+  }
+  return $reply;
+}
+
+$0 =~ /asterisk(?:_(.+))$/;
+my $hostname = $1;
+
+my $peeraddr = $ENV{'host'} || $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 $meetme_enabled = $ENV{'enable_meetme'} || '0';
+my $confbridge_enabled = $ENV{'enable_confbridge'} || '1';
+
+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 !~ /^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 !~ /^\r?\n$/ ) {}
+}
+
+if ( $ARGV[0] and $ARGV[0] eq 'autoconf' ) {
+  if ( $error ) {
+    print "no ($error)\n";
+  } else {
+    print "yes\n";
+  }
+
+  exit 0;
+} elsif ( $ARGV[0] and $ARGV[0] eq 'config' ) {
+  print "host_name $hostname" if $hostname;
+
+  print <<END;
+
+multigraph asterisk_channels
+graph_title Asterisk active channels
+graph_args --base 1000 -l 0
+graph_vlabel channels
+graph_category asterisk
+total.label channels
+END
+
+  foreach my $channel (@CHANNELS) {
+    print <<END;
+$channel.draw AREASTACK
+$channel.label $channel channels
+END
+  }
+
+print <<END;
+
+multigraph asterisk_voicemail
+graph_title Asterisk voicemail messages
+graph_args --base 1000 -l 0
+graph_vlabel messages
+graph_category asterisk
+total.label Total messages
+END
+
+if ($meetme_enabled == '1') {
+  print <<END;
+
+multigraph asterisk_meetme
+graph_title Asterisk meetme statistics
+graph_args --base 1000 -l 0
+graph_category asterisk
+users.label Connected users
+conferences.label Active conferences
+END
+}
+
+if ($confbridge_enabled == '1') {
+  print <<END;
+
+multigraph asterisk_confbridge
+graph_title Asterisk ConfBridge statistics
+graph_args --base 1000 -l 0
+graph_category asterisk
+users.label Connected users
+conferences.label Active conferences
+END
+}
+
+print <<END;
+
+multigraph asterisk_codecs
+graph_title Asterisk channels per codec
+graph_args --base 1000 -l 0
+graph_vlabel channels
+graph_category asterisk
+END
+
+  foreach my $codec (@CODECS) {
+    print <<END;
+$codec.draw AREASTACK
+$codec.label $codec channels
+END
+  }
+
+print <<END;
+other.draw AREASTACK
+other.label Other known codecs
+unknown.draw AREASTACK
+unknown.label Unknown codec
+END
+
+  unless ( ($ENV{MUNIN_CAP_DIRTYCONFIG} || 0) == 1 ) {
+    exit 0;
+  }
+}
+
+# if we arrive here and we don't have a socket, it's time to exit.
+die $error 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      ConfBridge(5500)
+#2 active channels
+#1 active call
+
+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.
+
+my $meetme_response;
+if ($meetme_enabled eq '1') {
+  $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
+}
+
+my $confbridge_response;
+if ($confbridge_enabled eq '1') {
+  $confbridge_response = asterisk_command($socket, "confbridge list");
+  #Conference Bridge Name           Users  Marked Locked Muted
+  #================================ ====== ====== ====== =====
+  #3                                     1      0 No     No
+}
+
+my $sipchannels_response = asterisk_command($socket, "sip show channels");
+#Peer             User/ANR         Call ID          Format           Hold     Last Message    Expiry     Peer
+#192.168.1.135    yann             1341929961-161    00101/00002     No       Rx: INVITE                 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  FirstMsg    LastMsg
+#IAX2/rodolphe@rodolp  10.8.53.6        rodolphe    00003/01287  00006/00004  00000ms  0148ms  0000ms  gsm     Rx:NEW      Tx:ACK
+#1 active IAX channel(s)
+
+# 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();
+
+my $active_channels = 'U';
+$active_channels = $1 if $channels_response =~ /\n([0-9]+) active channels?/;
+
+print <<END;
+
+multigraph asterisk_channels
+total.value $active_channels
+END
+
+my @channels_list = split(/\r?\n/, $channels_response) if $channels_response;
+foreach my $channel (@CHANNELS) {
+  print "$channel.value ";
+  print $channels_response ? scalar(grep(/^$channel\//, @channels_list)) : "U";
+  print "\n";
+}
+
+print "\nmultigraph asterisk_voicemail\n";
+if ( !$voicemail_response or $voicemail_response =~ /no voicemail users/ ) {
+  print "total.value U\n";
+} else {
+  my $messages = 0;
+  foreach my $line (split(/\r?\n/, $voicemail_response)) {
+    next unless $line =~ / ([0-9]+)$/;
+    $messages += $1;
+  }
+
+  print "total.value $messages\n";
+}
+
+if ($meetme_enabled == '1') {
+  print "\nmultigraph asterisk_meetme\n";
+  if ( !$meetme_response ) {
+    print <<END;
+users.value U
+conferences.value U
+END
+  } else {
+    if ( $meetme_response =~ /No active/ ) {
+      print <<END;
+users.value 0
+conferences.value 0
+END
+    } else {
+      my @meetme_list = split(/\r?\n/, $meetme_response);
+
+      my $users = pop(@meetme_list);
+      $users =~ s/^Total number of MeetMe users: ([0-9]+)$/$1/;
+
+      print "users.value $users\n";
+      print "conferences.value " . (scalar(@meetme_list)-1) . "\n";
+    }
+  }
+}
+
+if ($confbridge_enabled == '1') {
+  print "\nmultigraph asterisk_confbridge\n";
+  if ( !$confbridge_response ) {
+    print <<END;
+users.value U
+conferences.value U
+END
+  } else {
+    my @confbridge_list = split(/\r?\n/, $confbridge_response);
+    # Remove column headers, then line of =
+    shift(@confbridge_list);
+    shift(@confbridge_list);
+
+    my $users = 0;
+    foreach my $bridge (@confbridge_list) {
+      my @fields = split ' ', $bridge;
+      # yes we ARE parsing a command's output. if we end up getting some
+      # unexpected things, just break out to and avoid computing nonsense.
+      if (scalar(@fields) < 5 or $fields[1] !~ /^[0-9]+$/) {
+        last;
+      }
+      $users += $fields[1];
+    }
+
+    print "users.value $users\n";
+    print "conferences.value " . (scalar(@confbridge_list)) . "\n";
+  }
+}
+
+print "\nmultigraph asterisk_codecs\n";
+if ( !$sipchannels_response and !$iaxchannels_response ) {
+  foreach my $codec (@CODECS) {
+    print "$codec.value U\n";
+  }
+  print <<END;
+other.value U
+unknown.value U
+END
+} else {
+  my %results;
+  my ($start, $unknown, $other)=(0,0,0);
+  foreach my $codec (@CODECS) {
+    $results{$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;
+       $fields[3] =~ s/[()]//g;
+    if ($fields[3] eq 'nothing') {
+      $unknown += 1;
+      next;
+    }
+    foreach my $codec (@CODECS) {
+      if ($fields[3] eq "$codec") {
+        $results{$codec} = $results{$codec} + 1;
+        $found = 1;
+        last;
+      }
+    }
+    if (! $found) {
+      $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') {
+      $unknown += 1;
+      next;
+    }
+    foreach my $codec (@CODECS) {
+      if ($fields[8] eq "$codec") {
+        $results{$codec} = $results{$codec} + 1;
+        $found = 1;
+        last;
+      }
+    }
+    if (! $found) {
+      $other += 1;
+      print STDERR "CODEC: IAX2 other format: $fields[8]\n" if $Munin::Plugin::DEBUG;
+    }
+  }
+
+  foreach my $codec (@CODECS) {
+    print "$codec.value $results{$codec}\n";
+  }
+  print "other.value $other\n";
+  print "unknown.value $unknown\n";
+}
diff --git a/asterisk_sippeers b/asterisk_sippeers
new file mode 100755 (executable)
index 0000000..321251b
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/perl -w
+# -*- perl -*-
+
+=head1 NAME
+
+asterix_sippeers - Plugin to monitor number of sip peers registered
+
+=head1 CONFIGURATION
+
+The following configuration parameters are used by this plugin
+
+ [asterisk_sippeers]
+  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
+
+The "username" and "secret" parameters are mandatory, and have no
+defaults.
+
+=head2 DEFAULT CONFIGURATION
+
+ [asterisk_sippeers]
+  env.host 127.0.0.1
+  env.port 5038
+
+=head1 AUTHOR
+
+Copyright (C) 2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
+
+=head1 LICENSE
+
+Gnu GPLv2
+
+=begin comment
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 dated June, 1991.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+If you improve this script please send your version to my email
+address with the copyright notice upgrade with your name.
+
+=end comment
+
+=head1 MAGIC MARKERS
+
+ #%# family=contrib
+
+=cut
+
+# #################################################################################
+# Following example from current asterisk 1.4
+#> sip show peers
+#Name/username              Host            Dyn Nat ACL Port     Status
+#104-RANDALLBUILT/104-RAND  74.218.176.166   D          5060     Unmonitored
+#...
+#102-ROCKSOLID/102-ROCKSOL  (Unspecified)    D          0        Unmonitored
+#101-ROCKSOLID/101-ROCKSOL  (Unspecified)    D   N      0        UNKNOWN
+#20 sip peers [Monitored: 0 online, 1 offline Unmonitored: 2 online, 17 offline]
+# #################################################################################
+
+use IO::Socket;
+use strict;
+
+if ($ARGV[0] and $ARGV[0] eq "config")
+{
+    print "graph_title Asterisk sip peers\n";
+    print "graph_args --base 1000 -l 0\n";
+    print "graph_order mon moff umon umoff\n";
+    print "graph_vlabel peers\n";
+    print "graph_category asterisk\n";
+    #print "peers.label total\n";
+    print "mon.draw AREA\n";
+    print "mon.label monitored online\n";
+    print "moff.draw STACK\n";
+    print "moff.label monitored offline\n";
+    print "umon.draw STACK\n";
+    print "umon.label unmonitored online\n";
+    print "umoff.draw STACK\n";
+    print "umoff.label unmonitored offline\n";
+    #graph_scale no
+    #load.warning 10
+    #load.critical 120
+    #graph_info The ... describes ....
+    #load.info Average load for the five minutes.
+    exit 0;
+}
+
+my $host = exists $ENV{'host'} ? $ENV{'host'} : "127.0.0.1";
+my $port = exists $ENV{'port'} ? $ENV{'port'} : "5038";
+
+my $username = $ENV{'username'};
+my $secret   = $ENV{'secret'};
+
+my $pop = new IO::Socket::INET (PeerAddr => $host,
+                               PeerPort => $port,
+                               Proto => 'tcp');
+die "Could not create socket: $!\n" unless $pop;
+
+## Read connection message.
+my $line = $pop->getline;
+die $line unless $line =~ /^Asterisk/;
+
+## Send user name.
+$pop->print("Action: login\n");
+$pop->print("Username: $username\n");
+$pop->print("Secret: $secret\n");
+$pop->print("Events: off\n");
+$pop->print("\n");
+
+while ($line = $pop->getline)
+{
+    last if $line eq "\r\n";
+}
+#Response: Success
+#Message: Authentication accepted
+
+## Request status of messages.
+$pop->print("Action: command\n");
+$pop->print("Command: sip show peers\n");
+$pop->print("\n");
+
+my ($peers,$monitor_online,$monitor_offline,$unmonitor_online,$unmonitor_offline)=(0,0,0,0,0);
+
+while (($line = $pop->getline) and ($line ne "\r\n"))
+{
+    $line =~ s/^Output: //;
+    my @fields = split(' ', $line);
+    my $count = @fields;
+    #20 sip peers [Monitored: 0 online, 1 offline Unmonitored: 2 online, 17 offline]
+    if (($count > 10) and ($fields[1] eq 'sip' and $fields[2] eq 'peers')) {
+       $peers = $fields[0];
+       $monitor_online = $fields[4];
+       $monitor_offline = $fields[6];
+       $unmonitor_online = $fields[9];
+       $unmonitor_offline = $fields[11];
+       #print STDERR "$peers $monitor_online $monitor_offline $unmonitor_online $unmonitor_offline\n";
+       last;
+    }
+}
+
+$pop->print("Action: logoff\n");
+$pop->print("\n");
+
+## Exhaust buffer before closing (to avoid polluting Asterisk's logs)
+while ($line = $pop->getline) {}
+
+#print "peers.value $peers\n";
+print "mon.value $monitor_online\n";
+print "moff.value $monitor_offline\n";
+print "umon.value $unmonitor_online\n";
+print "umoff.value $unmonitor_offline\n";
+
+# vim:syntax=perl