#!/usr/bin/perl
#
# collectd - contrib/cussh.pl
# Copyright (C) 2007-2009 Sebastian Harl
#
# 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; only version 2 of the License is applicable.
#
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
#
# Author:
# Sebastian Harl <sh at tokkee.org>
#
=head1 NAME
cussh - collectd UNIX socket shell
=head1 SYNOPSIS
B<cussh> [I<E<lt>pathE<gt>>]
=head1 DESCRIPTION
B<collectd>'s unixsock plugin allows external programs to access the values it
has collected or received and to submit own values. This is a little
interactive frontend for this plugin.
=head1 OPTIONS
=over 4
=item I<E<lt>pathE<gt>>
The path to the UNIX socket provided by collectd's unixsock plugin. (Default:
F</var/run/collectd-unixsock>)
=back
=cut
use strict;
use warnings;
use Collectd::Unixsock();
{ # main
my $path = $ARGV[0] || "/var/run/collectd-unixsock";
my $sock = Collectd::Unixsock->new($path);
my $cmds = {
HELP => \&cmd_help,
PUTVAL => \&putval,
GETVAL => \&getval,
GETTHRESHOLD => \&getthreshold,
FLUSH => \&flush,
LISTVAL => \&listval,
PUTNOTIF => \&putnotif,
};
if (! $sock) {
print STDERR "Unable to connect to $path!\n";
exit 1;
}
print "cussh version 0.2, Copyright (C) 2007-2008 Sebastian Harl\n"
. "cussh comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
. "and you are welcome to redistribute it under certain conditions.\n"
. "See the GNU General Public License 2 for more details.\n\n";
while (1) {
print "cussh> ";
my $line = <STDIN>;
last if (! $line);
chomp $line;
last if ($line =~ m/^quit$/i);
my ($cmd) = $line =~ m/^(\w+)\s*/;
$line = $';
next if (! $cmd);
$cmd = uc $cmd;
my $f = undef;
if (defined $cmds->{$cmd}) {
$f = $cmds->{$cmd};
}
else {
print STDERR "ERROR: Unknown command $cmd!\n";
next;
}
if (! $f->($sock, $line)) {
print STDERR "ERROR: Command failed!\n";
next;
}
}
$sock->destroy();
exit 0;
}
sub tokenize {
my $line = shift || return;
my $line_ptr = $line;
my @line = ();
my $token_pattern = qr/[^"\s]+|"[^"]+"/;
while (my ($token) = $line_ptr =~ m/^($token_pattern)\s+/) {
$line_ptr = $';
push @line, $token;
}
if ($line_ptr =~ m/^$token_pattern$/) {
push @line, $line_ptr;
}
else {
my ($token) = split m/ /, $line_ptr, 1;
print STDERR "Failed to parse line: $line\n";
print STDERR "Parse error near token \"$token\".\n";
return;
}
foreach my $l (@line) {
if ($l =~ m/^"(.*)"$/) {
$l = $1;
}
}
return @line;
}
sub getid {
my $string = shift || return;
my ($h, $p, $pi, $t, $ti) =
$string =~ m#^([^/]+)/([^/-]+)(?:-([^/]+))?/([^/-]+)(?:-([^/]+))?\s*#;
$string = $';
return if ((! $h) || (! $p) || (! $t));
my %id = ();
($id{'host'}, $id{'plugin'}, $id{'type'}) = ($h, $p, $t);
$id{'plugin_instance'} = $pi if defined ($pi);
$id{'type_instance'} = $ti if defined ($ti);
return \%id;
}
sub putid {
my $ident = shift || return;
my $string;
$string = $ident->{'host'} . "/" . $ident->{'plugin'};
if (defined $ident->{'plugin_instance'}) {
$string .= "-" . $ident->{'plugin_instance'};
}
$string .= "/" . $ident->{'type'};
if (defined $ident->{'type_instance'}) {
$string .= "-" . $ident->{'type_instance'};
}
return $string;
}
=head1 COMMANDS
=over 4
=item B<HELP>
=cut
sub cmd_help {
my $sock = shift;
my $line = shift || '';
my @line = tokenize($line);
my $cmd = shift (@line);
my %text = (
help => <<HELP,
Available commands:
HELP
PUTVAL
GETVAL
GETTHRESHOLD
FLUSH
LISTVAL
PUTNOTIF
See the embedded Perldoc documentation for details. To do that, run:
perldoc $0
HELP
putval => <<HELP,
PUTVAL <id> <value0> [<value1> ...]
Submits a value to the daemon.
HELP
getval => <<HELP,
GETVAL <id>
Retrieves the current value or values from the daemon.
HELP
flush => <<HELP,
FLUSH [plugin=<plugin>] [timeout=<timeout>] [identifier=<id>] [...]
Sends a FLUSH command to the daemon.
HELP
listval => <<HELP,
LISTVAL
Prints a list of available values.
HELP
putnotif => <<HELP
PUTNOTIF severity=<severity> [...] message=<message>
Sends a notifications message to the daemon.
HELP
);
if (!$cmd)
{
$cmd = 'help';
}
if (!exists ($text{$cmd}))
{
print STDOUT "Unknown command: " . uc ($cmd) . "\n\n";
$cmd = 'help';
}
print STDOUT $text{$cmd};
return 1;
} # cmd_help
=item B<PUTVAL> I<Identifier> I<Valuelist>
=cut
sub putval {
my $sock = shift || return;
my $line = shift || return;
my @line = tokenize($line);
my $id;
my $ret;
if (! @line) {
return;
}
if (scalar(@line) < 2) {
print STDERR "Synopsis: PUTVAL <id> <value0> [<value1> ...]" . $/;
return;
}
$id = getid($line[0]);
if (! $id) {
print STDERR "Invalid id \"$line[0]\"." . $/;
return;
}
my ($time, @values) = split m/:/, $line;
$ret = $sock->putval(%$id, time => $time, values => \@values);
if (! $ret) {
print STDERR "socket error: " . $sock->{'error'} . $/;
}
return $ret;
}
=item B<GETVAL> I<Identifier>
=cut
sub getval {
my $sock = shift || return;
my $line = shift || return;
my @line = tokenize($line);
my $id;
my $vals;
if (! @line) {
return;
}
if (scalar(@line) < 1) {
print STDERR "Synopsis: GETVAL <id>" . $/;
return;
}
$id = getid($line[0]);
if (! $id) {
print STDERR "Invalid id \"$line[0]\"." . $/;
return;
}
$vals = $sock->getval(%$id);
if (! $vals) {
print STDERR "socket error: " . $sock->{'error'} . $/;
return;
}
foreach my $key (keys %$vals) {
print "\t$key: $vals->{$key}\n";
}
return 1;
}
=item B<GETTHRESHOLD> I<Identifier>
=cut
sub getthreshold {
my $sock = shift || return;
my $line = shift || return;
my @line = tokenize($line);
my $id;
my $vals;
if (! @line) {
return;
}
if (scalar(@line) < 1) {
print STDERR "Synopsis: GETTHRESHOLD <id>" . $/;
return;
}
$id = getid($line[0]);
if (! $id) {
print STDERR "Invalid id \"$line[0]\"." . $/;
return;
}
$vals = $sock->getthreshold(%$id);
if (! $vals) {
print STDERR "socket error: " . $sock->{'error'} . $/;
return;
}
foreach my $key (keys %$vals) {
print "\t$key: $vals->{$key}\n";
}
return 1;
}
=item B<FLUSH> [B<timeout>=I<$timeout>] [B<plugin>=I<$plugin>[ ...]]
=cut
sub flush {
my $sock = shift || return;
my $line = shift;
my @line = tokenize($line);
my $res;
if (! $line) {
$res = $sock->flush();
}
else {
my %args = ();
foreach my $i (@line) {
my ($option, $value) = $i =~ m/^([^=]+)=(.+)$/;
next if (! ($option && $value));
if ($option eq "plugin") {
push @{$args{"plugins"}}, $value;
}
elsif ($option eq "timeout") {
$args{"timeout"} = $value;
}
elsif ($option eq "identifier") {
my $id = getid ($value);
if (!$id)
{
print STDERR "Not a valid identifier: \"$value\"\n";
next;
}
push @{$args{"identifier"}}, $id;
}
else {
print STDERR "Invalid option \"$option\".\n";
return;
}
}
$res = $sock->flush(%args);
}
if (! $res) {
print STDERR "socket error: " . $sock->{'error'} . $/;
}
return $res;
}
=item B<LISTVAL>
=cut
sub listval {
my $sock = shift || return;
my $line = shift;
my @res;
if ($line ne "") {
print STDERR "Synopsis: LISTVAL" . $/;
return;
}
@res = $sock->listval();
if (! @res) {
print STDERR "socket error: " . $sock->{'error'} . $/;
return;
}
foreach my $ident (@res) {
print $ident->{'time'} . " " . putid($ident) . $/;
}
return 1;
}
=item B<PUTNOTIF> [[B<severity>=I<$severity>] [B<message>=I<$message>] [ ...]]
=cut
sub putnotif {
my $sock = shift || return;
my $line = shift || return;
my @line = tokenize($line);
my $ret;
my (%values) = ();
foreach my $i (@line) {
my ($key, $val) = split m/=/, $i, 2;
if ($key && $val) {
$values{$key} = $val;
}
else {
$values{'message'} = defined($values{'message'})
? ($values{'message'} . ' ' . $key)
: $key;
}
}
$values{'time'} ||= time();
$ret = $sock->putnotif(%values);
if (! $ret) {
print STDERR "socket error: " . $sock->{'error'} . $/;
}
return $ret;
}
=back
These commands follow the exact same syntax as described in
L<collectd-unixsock(5)>.
=head1 SEE ALSO
L<collectd(1)>, L<collectd-unisock(5)>
=head1 AUTHOR
Written by Sebastian Harl E<lt>sh@tokkee.orgE<gt>.
B<collectd> has been written by Florian Forster and others.
=head1 COPYRIGHT
Copyright (C) 2007 Sebastian Harl.
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; only version 2 of the License is applicable.
=cut
# vim: set sw=4 ts=4 tw=78 noexpandtab :