contrib/cussh.pl: Add support for the “GETTHRESHOLD” command.
[collectd.git] / contrib / cussh.pl
1 #!/usr/bin/perl
2 #
3 # collectd - contrib/cussh.pl
4 # Copyright (C) 2007-2009  Sebastian Harl
5 #
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the
8 # Free Software Foundation; only version 2 of the License is applicable.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18 #
19 # Author:
20 #   Sebastian Harl <sh at tokkee.org>
21 #
22
23 =head1 NAME
24
25 cussh - collectd UNIX socket shell
26
27 =head1 SYNOPSIS
28
29 B<cussh> [I<E<lt>pathE<gt>>]
30
31 =head1 DESCRIPTION
32
33 B<collectd>'s unixsock plugin allows external programs to access the values it
34 has collected or received and to submit own values. This is a little
35 interactive frontend for this plugin.
36
37 =head1 OPTIONS
38
39 =over 4
40
41 =item I<E<lt>pathE<gt>>
42
43 The path to the UNIX socket provided by collectd's unixsock plugin. (Default:
44 F</var/run/collectd-unixsock>)
45
46 =back
47
48 =cut
49
50 use strict;
51 use warnings;
52
53 use Collectd::Unixsock();
54
55 { # main
56         my $path = $ARGV[0] || "/var/run/collectd-unixsock";
57         my $sock = Collectd::Unixsock->new($path);
58
59         my $cmds = {
60                 HELP    => \&cmd_help,
61                 PUTVAL  => \&putval,
62                 GETVAL  => \&getval,
63                 GETTHRESHOLD  => \&getthreshold,
64                 FLUSH   => \&flush,
65                 LISTVAL => \&listval,
66                 PUTNOTIF => \&putnotif,
67         };
68
69         if (! $sock) {
70                 print STDERR "Unable to connect to $path!\n";
71                 exit 1;
72         }
73
74         print "cussh version 0.2, Copyright (C) 2007-2008 Sebastian Harl\n"
75                 . "cussh comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
76                 . "and you are welcome to redistribute it under certain conditions.\n"
77                 . "See the GNU General Public License 2 for more details.\n\n";
78
79         while (1) {
80                 print "cussh> ";
81                 my $line = <STDIN>;
82
83                 last if (! $line);
84
85                 chomp $line;
86
87                 last if ($line =~ m/^quit$/i);
88
89                 my ($cmd) = $line =~ m/^(\w+)\s*/;
90                 $line = $';
91
92                 next if (! $cmd);
93                 $cmd = uc $cmd;
94
95                 my $f = undef;
96                 if (defined $cmds->{$cmd}) {
97                         $f = $cmds->{$cmd};
98                 }
99                 else {
100                         print STDERR "ERROR: Unknown command $cmd!\n";
101                         next;
102                 }
103
104                 if (! $f->($sock, $line)) {
105                         print STDERR "ERROR: Command failed!\n";
106                         next;
107                 }
108         }
109
110         $sock->destroy();
111         exit 0;
112 }
113
114 sub tokenize {
115         my $line     = shift || return;
116         my $line_ptr = $line;
117         my @line     = ();
118
119         my $token_pattern = qr/[^"\s]+|"[^"]+"/;
120
121         while (my ($token) = $line_ptr =~ m/^($token_pattern)\s+/) {
122                 $line_ptr = $';
123                 push @line, $token;
124         }
125
126         if ($line_ptr =~ m/^$token_pattern$/) {
127                 push @line, $line_ptr;
128         }
129         else {
130                 my ($token) = split m/ /, $line_ptr, 1;
131                 print STDERR "Failed to parse line: $line\n";
132                 print STDERR "Parse error near token \"$token\".\n";
133                 return;
134         }
135
136         foreach my $l (@line) {
137                 if ($l =~ m/^"(.*)"$/) {
138                         $l = $1;
139                 }
140         }
141         return @line;
142 }
143
144 sub getid {
145         my $string = shift || return;
146
147         my ($h, $p, $pi, $t, $ti) =
148                 $string =~ m#^([^/]+)/([^/-]+)(?:-([^/]+))?/([^/-]+)(?:-([^/]+))?\s*#;
149         $string = $';
150
151         return if ((! $h) || (! $p) || (! $t));
152
153         my %id = ();
154
155         ($id{'host'}, $id{'plugin'}, $id{'type'}) = ($h, $p, $t);
156
157         $id{'plugin_instance'} = $pi if defined ($pi);
158         $id{'type_instance'} = $ti if defined ($ti);
159         return \%id;
160 }
161
162 sub putid {
163         my $ident = shift || return;
164
165         my $string;
166
167         $string = $ident->{'host'} . "/" . $ident->{'plugin'};
168
169         if (defined $ident->{'plugin_instance'}) {
170                 $string .= "-" . $ident->{'plugin_instance'};
171         }
172
173         $string .= "/" . $ident->{'type'};
174
175         if (defined $ident->{'type_instance'}) {
176                 $string .= "-" . $ident->{'type_instance'};
177         }
178         return $string;
179 }
180
181 =head1 COMMANDS
182
183 =over 4
184
185 =item B<HELP>
186
187 =cut
188
189 sub cmd_help {
190         print <<HELP;
191 Available commands:
192   HELP
193   PUTVAL
194   GETVAL
195   GETTHRESHOLD
196   FLUSH
197   LISTVAL
198   PUTNOTIF
199
200 See the embedded Perldoc documentation for details. To do that, run:
201   perldoc $0
202 HELP
203         return 1;
204 } # cmd_help
205
206 =item B<PUTVAL> I<Identifier> I<Valuelist>
207
208 =cut
209
210 sub putval {
211         my $sock = shift || return;
212         my $line = shift || return;
213
214         my @line = tokenize($line);
215
216         my $id;
217         my $ret;
218
219         if (! @line) {
220                 return;
221         }
222
223         if (scalar(@line) < 2) {
224                 print STDERR "Synopsis: PUTVAL <id> <value0> [<value1> ...]" . $/;
225                 return;
226         }
227
228         $id = getid($line[0]);
229
230         if (! $id) {
231                 print STDERR "Invalid id \"$line[0]\"." . $/;
232                 return;
233         }
234
235         my ($time, @values) = split m/:/, $line;
236         $ret = $sock->putval(%$id, time => $time, values => \@values);
237
238         if (! $ret) {
239                 print STDERR "socket error: " . $sock->{'error'} . $/;
240         }
241         return $ret;
242 }
243
244 =item B<GETVAL> I<Identifier>
245
246 =cut
247
248 sub getval {
249         my $sock = shift || return;
250         my $line = shift || return;
251
252         my @line = tokenize($line);
253
254         my $id;
255         my $vals;
256
257         if (! @line) {
258                 return;
259         }
260
261         if (scalar(@line) < 1) {
262                 print STDERR "Synopsis: GETVAL <id>" . $/;
263                 return;
264         }
265
266         $id = getid($line[0]);
267
268         if (! $id) {
269                 print STDERR "Invalid id \"$line[0]\"." . $/;
270                 return;
271         }
272
273         $vals = $sock->getval(%$id);
274
275         if (! $vals) {
276                 print STDERR "socket error: " . $sock->{'error'} . $/;
277                 return;
278         }
279
280         foreach my $key (keys %$vals) {
281                 print "\t$key: $vals->{$key}\n";
282         }
283         return 1;
284 }
285
286 =item B<GETTHRESHOLD> I<Identifier>
287
288 =cut
289
290 sub getthreshold {
291         my $sock = shift || return;
292         my $line = shift || return;
293
294         my @line = tokenize($line);
295
296         my $id;
297         my $vals;
298
299         if (! @line) {
300                 return;
301         }
302
303         if (scalar(@line) < 1) {
304                 print STDERR "Synopsis: GETTHRESHOLD <id>" . $/;
305                 return;
306         }
307
308         $id = getid($line[0]);
309
310         if (! $id) {
311                 print STDERR "Invalid id \"$line[0]\"." . $/;
312                 return;
313         }
314
315         $vals = $sock->getthreshold(%$id);
316
317         if (! $vals) {
318                 print STDERR "socket error: " . $sock->{'error'} . $/;
319                 return;
320         }
321
322         foreach my $key (keys %$vals) {
323                 print "\t$key: $vals->{$key}\n";
324         }
325         return 1;
326 }
327
328 =item B<FLUSH> [B<timeout>=I<$timeout>] [B<plugin>=I<$plugin>[ ...]]
329
330 =cut
331
332 sub flush {
333         my $sock = shift || return;
334         my $line = shift;
335
336         my @line = tokenize($line);
337
338         my $res;
339
340         if (! $line) {
341                 $res = $sock->flush();
342         }
343         else {
344                 my %args = ();
345
346                 foreach my $i (@line) {
347                         my ($option, $value) = $i =~ m/^([^=]+)=(.+)$/;
348                         next if (! ($option && $value));
349
350                         if ($option eq "plugin") {
351                                 push @{$args{"plugins"}}, $value;
352                         }
353                         elsif ($option eq "timeout") {
354                                 $args{"timeout"} = $value;
355                         }
356                         elsif ($option eq "identifier") {
357                                 my $id = getid (\$value);
358                                 if (!$id)
359                                 {
360                                         print STDERR "Not a valid identifier: \"$value\"\n";
361                                         next;
362                                 }
363                                 push @{$args{"identifier"}}, $id;
364                         }
365                         else {
366                                 print STDERR "Invalid option \"$option\".\n";
367                                 return;
368                         }
369                 }
370
371                 $res = $sock->flush(%args);
372         }
373
374         if (! $res) {
375                 print STDERR "socket error: " . $sock->{'error'} . $/;
376         }
377         return $res;
378 }
379
380 =item B<LISTVAL>
381
382 =cut
383
384 sub listval {
385         my $sock = shift || return;
386         my $line = shift;
387
388         my @res;
389
390         if ($line ne "") {
391                 print STDERR "Synopsis: LISTVAL" . $/;
392                 return;
393         }
394
395         @res = $sock->listval();
396
397         if (! @res) {
398                 print STDERR "socket error: " . $sock->{'error'} . $/;
399                 return;
400         }
401
402         foreach my $ident (@res) {
403                 print $ident->{'time'} . " " . putid($ident) . $/;
404         }
405         return 1;
406 }
407
408 =item B<PUTNOTIF> [[B<severity>=I<$severity>] [B<message>=I<$message>] [ ...]]
409
410 =cut
411
412 sub putnotif {
413         my $sock = shift || return;
414         my $line = shift || return;
415
416         my @line = tokenize($line);
417
418         my $ret;
419
420         my (%values) = ();
421         foreach my $i (@line) {
422                 my ($key, $val) = split m/=/, $i, 2;
423                 if ($key && $val) {
424                         $values{$key} = $val;
425                 }
426                 else {
427                         $values{'message'} = defined($values{'message'})
428                                 ? ($values{'message'} . ' ' . $key)
429                                 : $key;
430                 }
431         }
432         $values{'time'} ||= time();
433
434         $ret = $sock->putnotif(%values);
435         if (! $ret) {
436                 print STDERR "socket error: " . $sock->{'error'} . $/;
437         }
438         return $ret;
439 }
440
441 =back
442
443 These commands follow the exact same syntax as described in
444 L<collectd-unixsock(5)>.
445
446 =head1 SEE ALSO
447
448 L<collectd(1)>, L<collectd-unisock(5)>
449
450 =head1 AUTHOR
451
452 Written by Sebastian Harl E<lt>sh@tokkee.orgE<gt>.
453
454 B<collectd> has been written by Florian Forster and others.
455
456 =head1 COPYRIGHT
457
458 Copyright (C) 2007 Sebastian Harl.
459
460 This program is free software; you can redistribute it and/or modify it under
461 the terms of the GNU General Public License as published by the Free Software
462 Foundation; only version 2 of the License is applicable.
463
464 =cut
465
466 # vim: set sw=4 ts=4 tw=78 noexpandtab :