]>
Commit | Line | Data |
---|---|---|
575d86d9 JR |
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 | } |