f9981d98b0135859c934bc3dbe7e5ce700983065
[collectd.git] / bindings / perl / lib / Collectd / Unixsock.pm
1 #
2 # collectd - bindings/buildperl/Collectd/Unixsock.pm
3 # Copyright (C) 2007,2008  Florian octo Forster
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a
6 # copy of this software and associated documentation files (the "Software"),
7 # to deal in the Software without restriction, including without limitation
8 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 # and/or sell copies of the Software, and to permit persons to whom the
10 # Software is furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 # DEALINGS IN THE SOFTWARE.
22 #
23 # Authors:
24 #   Florian Forster <octo at collectd.org>
25 #
26
27 package Collectd::Unixsock;
28
29 =head1 NAME
30
31 Collectd::Unixsock - Abstraction layer for accessing the functionality by
32 collectd's unixsock plugin.
33
34 =head1 SYNOPSIS
35
36   use Collectd::Unixsock;
37
38   my $sock = Collectd::Unixsock->new ($path);
39
40   my $value = $sock->getval (%identifier);
41   $sock->putval (%identifier,
42                  time => time (),
43                  values => [123, 234, 345]);
44
45   $sock->destroy ();
46
47 =head1 DESCRIPTION
48
49 collectd's unixsock plugin allows external programs to access the values it has
50 collected or received and to submit own values. This Perl-module is simply a
51 little abstraction layer over this interface to make it even easier for
52 programmers to interact with the daemon.
53
54 =cut
55
56 use strict;
57 use warnings;
58
59 use Carp qw(cluck confess carp croak);
60 use IO::Socket::UNIX;
61 use Scalar::Util qw( looks_like_number );
62
63 our $Debug = 0;
64
65 sub _debug
66 {
67         print @_ if $Debug;
68 }
69
70 sub _create_socket
71 {
72         my $path = shift;
73         my $sock = IO::Socket::UNIX->new (Type => SOCK_STREAM, Peer => $path);
74         if (!$sock)
75         {
76                 cluck ("Cannot open UNIX-socket $path: $!");
77                 return;
78         }
79         return ($sock);
80 } # _create_socket
81
82 =head1 VALUE IDENTIFIERS
83
84 The values in the collectd are identified using a five-tuple (host, plugin,
85 plugin-instance, type, type-instance) where only plugin instance and type
86 instance may be undef. Many functions expect an I<%identifier> hash that has at
87 least the members B<host>, B<plugin>, and B<type>, possibly completed by
88 B<plugin_instance> and B<type_instance>.
89
90 Usually you can pass this hash as follows:
91
92   $self->method (host => $host, plugin => $plugin, type => $type, %other_args);
93
94 =cut
95
96 sub _create_identifier
97 {
98         my $args = shift;
99         my ($host, $plugin, $type);
100
101         if (!$args->{host} || !$args->{plugin} || !$args->{type})
102         {
103                 cluck ("Need `host', `plugin' and `type'");
104                 return;
105         }
106
107         $host = $args->{host};
108         $plugin = $args->{plugin};
109         $plugin .= '-' . $args->{plugin_instance} if defined $args->{plugin_instance};
110         $type = $args->{type};
111         $type .= '-' . $args->{type_instance} if defined $args->{type_instance};
112
113         return "$host/$plugin/$type";
114 } # _create_identifier
115
116 sub _parse_identifier
117 {
118         my $string = shift;
119         my ($plugin_instance, $type_instance);
120
121         my ($host, $plugin, $type) = split /\//, $string;
122
123         ($plugin, $plugin_instance) = split /-/, $plugin, 2;
124         ($type, $type_instance) = split /-/, $type, 2;
125
126         my $ident =
127         {
128                 host => $host,
129                 plugin => $plugin,
130                 type => $type
131         };
132         $ident->{plugin_instance} = $plugin_instance if defined $plugin_instance;
133         $ident->{type_instance} = $type_instance if defined $type_instance;
134
135         return $ident;
136 } # _parse_identifier
137
138 sub _escape_argument
139 {
140         local $_ = shift;
141
142         return $_ if /^\w+$/;
143
144         s#\\#\\\\#g;
145         s#"#\\"#g;
146         return "\"$_\"";
147 }
148
149 =head1 PUBLIC METHODS
150
151 =over 4
152
153 =item I<$self> = Collectd::Unixsock->B<new> ([I<$path>]);
154
155 Creates a new connection to the daemon. The optional I<$path> argument gives
156 the path to the UNIX socket of the C<unixsock plugin> and defaults to
157 F</var/run/collectd-unixsock>. Returns the newly created object on success and
158 false on error.
159
160 =cut
161
162 sub new
163 {
164         my $class = shift;
165         my $path = shift || '/var/run/collectd-unixsock';
166         my $sock = _create_socket ($path) or return;
167         return bless
168                 {
169                         path => $path,
170                         sock => $sock,
171                         error => 'No error'
172                 }, $class;
173 } # new
174
175 =item I<$res> = I<$self>-E<gt>B<getval> (I<%identifier>);
176
177 Requests a value-list from the daemon. On success a hash-ref is returned with
178 the name of each data-source as the key and the according value as, well, the
179 value. On error false is returned.
180
181 =cut
182
183 sub getval # {{{
184 {
185         my $self = shift;
186         my %args = @_;
187
188         my ($status, $msg, $identifier, $ret);
189         my $fh = $self->{sock} or confess ('object has no filehandle');
190
191         $ret = {};
192
193         $identifier = _create_identifier (\%args) or return;
194
195         $msg = 'GETVAL ' . _escape_argument ($identifier) . "\n";
196         _debug "-> $msg";
197         print $fh $msg;
198
199         $msg = <$fh>;
200         chomp ($msg);
201         _debug "<- $msg\n";
202
203         ($status, $msg) = split / /, $msg, 2;
204         if ($status <= 0)
205         {
206                 $self->{error} = $msg;
207                 return;
208         }
209
210         for (1 .. $status)
211         {
212                 my $entry = <$fh>;
213                 chomp $entry;
214                 _debug "<- $entry\n";
215
216                 if ($entry =~ m/^(\w+)=NaN$/)
217                 {
218                         $ret->{$1} = undef;
219                 }
220                 elsif ($entry =~ m/^(\w+)=(.*)$/ and looks_like_number($2))
221                 {
222                         $ret->{$1} = 0.0 + $2;
223                 }
224         }
225
226         return $ret;
227 } # }}} sub getval
228
229 =item I<$res> = I<$self>-E<gt>B<getthreshold> (I<%identifier>);
230
231 Requests a threshold from the daemon. On success a hash-ref is returned with
232 the threshold data. On error false is returned.
233
234 =cut
235
236 sub getthreshold # {{{
237 {
238         my $self = shift;
239         my %args = @_;
240
241         my ($status, $msg, $identifier, $ret);
242         my $fh = $self->{sock} or confess ('object has no filehandle');
243
244         $ret = {};
245
246         $identifier = _create_identifier (\%args) or return;
247
248         $msg = 'GETTHRESHOLD ' . _escape_argument ($identifier) . "\n";
249         _debug "-> $msg";
250         print $fh $msg;
251
252         $msg = <$fh>;
253         chomp ($msg);
254         _debug "<- $msg\n";
255
256         ($status, $msg) = split (' ', $msg, 2);
257         if ($status <= 0)
258         {
259                 $self->{error} = $msg;
260                 return;
261         }
262
263         for (1 .. $status)
264         {
265                 my $entry = <$fh>;
266                 chomp ($entry);
267                 _debug "<- $entry\n";
268
269                 if ($entry =~ m/^([^:]+):\s*(\S.*)$/)
270                 {
271                         my $key = $1;
272                         my $value = $2;
273
274                         $key =~ s/(?:^\s+|\s$)//;
275                         $ret->{$key} = $value;
276                 }
277         }
278
279         return $ret;
280 } # }}} sub getthreshold
281
282 =item I<$self>-E<gt>B<putval> (I<%identifier>, B<time> =E<gt> I<$time>, B<values> =E<gt> [...]);
283
284 Submits a value-list to the daemon. If the B<time> argument is omitted
285 C<time()> is used. The required argument B<values> is a reference to an array
286 of values that is to be submitted. The number of values must match the number
287 of values expected for the given B<type> (see L<VALUE IDENTIFIERS>), though
288 this is checked by the daemon, not the Perl module. Also, gauge data-sources
289 (e.E<nbsp>g. system-load) may be C<undef>. Returns true upon success and false
290 otherwise.
291
292 =cut
293
294 sub putval
295 {
296         my $self = shift;
297         my %args = @_;
298
299         my ($status, $msg, $identifier, $values);
300         my $fh = $self->{sock} or confess;
301
302         my $interval = defined $args{interval} ?
303     ' interval=' . _escape_argument ($args{interval}) : '';
304
305         $identifier = _create_identifier (\%args) or return;
306         if (!$args{values})
307         {
308                 cluck ("Need argument `values'");
309                 return;
310         }
311
312         if (ref ($args{values}))
313         {
314                 my $time;
315
316                 if ("ARRAY" ne ref ($args{values}))
317                 {
318                         cluck ("Invalid `values' argument (expected an array ref)");
319                         return;
320                 }
321
322                 if (! scalar @{$args{values}})
323                 {
324                         cluck ("Empty `values' array");
325                         return;
326                 }
327
328                 $time = $args{time} || time;
329                 $values = join (':', $time, map { defined $_ ? $_ : 'U' } @{$args{values}});
330         }
331         else
332         {
333                 $values = $args{values};
334         }
335
336         $msg = 'PUTVAL '
337         . _escape_argument ($identifier)
338         . $interval
339         . ' ' . _escape_argument ($values) . "\n";
340         _debug "-> $msg";
341         print $fh $msg;
342
343         $msg = <$fh>;
344         chomp $msg;
345         _debug "<- $msg\n";
346
347         ($status, $msg) = split / /, $msg, 2;
348         return 1 if $status == 0;
349
350         $self->{error} = $msg;
351         return;
352 } # putval
353
354 =item I<$res> = I<$self>-E<gt>B<listval> ()
355
356 Queries a list of values from the daemon. The list is returned as an array of
357 hash references, where each hash reference is a valid identifier. The C<time>
358 member of each hash holds the epoch value of the last update of that value.
359
360 =cut
361
362 sub listval
363 {
364         my $self = shift;
365         my ($msg, $status);
366         my @ret;
367         my $fh = $self->{sock} or confess;
368
369         _debug "LISTVAL\n";
370         print $fh "LISTVAL\n";
371
372         $msg = <$fh>;
373         chomp ($msg);
374         _debug "<- $msg\n";
375         ($status, $msg) = split / /, $msg, 2;
376         if ($status < 0)
377         {
378                 $self->{error} = $msg;
379                 return;
380         }
381
382         for (1 .. $status)
383         {
384                 my $time;
385                 my $ident;
386
387                 $msg = <$fh>;
388                 chomp ($msg);
389                 _debug "<- $msg\n";
390
391                 ($time, $ident) = split / /, $msg, 2;
392
393                 $ident = _parse_identifier ($ident);
394                 $ident->{time} = int $time;
395
396                 push (@ret, $ident);
397         } # for (i = 0 .. $status)
398
399         return @ret;
400 } # listval
401
402 =item I<$res> = I<$self>-E<gt>B<putnotif> (B<severity> =E<gt> I<$severity>, B<message> =E<gt> I<$message>, ...);
403
404 Submits a notification to the daemon.
405
406 Valid options are:
407
408 =over 4
409
410 =item B<severity>
411
412 Sets the severity of the notification. The value must be one of the following
413 strings: C<failure>, C<warning>, or C<okay>. Case does not matter. This option
414 is mandatory.
415
416 =item B<message>
417
418 Sets the message of the notification. This option is mandatory.
419
420 =item B<time>
421
422 Sets the time. If omitted, C<time()> is used.
423
424 =item I<Value identifier>
425
426 All the other fields of the value identifiers, B<host>, B<plugin>,
427 B<plugin_instance>, B<type>, and B<type_instance>, are optional. When given,
428 the notification is associated with the performance data of that identifier.
429 For more details, please see L<collectd-unixsock(5)>.
430
431 =back
432
433 =cut
434
435 sub putnotif
436 {
437         my $self = shift;
438         my %args = @_;
439
440         my $status;
441         my $fh = $self->{sock} or confess;
442
443         my $msg; # message sent to the socket
444         
445     for my $arg (qw( message severity ))
446     {
447         cluck ("Need argument `$arg'"), return unless $args{$arg};
448     }
449         $args{severity} = lc $args{severity};
450         if (($args{severity} ne 'failure')
451                 && ($args{severity} ne 'warning')
452                 && ($args{severity} ne 'okay'))
453         {
454                 cluck ("Invalid `severity: " . $args{severity});
455                 return;
456         }
457
458         $args{time} ||= time;
459         
460         $msg = 'PUTNOTIF '
461         . join (' ', map { $_ . '=' . _escape_argument ($args{$_}) } keys %args)
462         . "\n";
463
464         _debug "-> $msg";
465         print $fh $msg;
466
467         $msg = <$fh>;
468         chomp $msg;
469         _debug "<- $msg\n";
470
471         ($status, $msg) = split / /, $msg, 2;
472         return 1 if $status == 0;
473
474         $self->{error} = $msg;
475         return;
476 } # putnotif
477
478 =item I<$self>-E<gt>B<flush> (B<timeout> =E<gt> I<$timeout>, B<plugins> =E<gt> [...], B<identifier>  =E<gt> [...]);
479
480 Flush cached data.
481
482 Valid options are:
483
484 =over 4
485
486 =item B<timeout>
487
488 If this option is specified, only data older than I<$timeout> seconds is
489 flushed.
490
491 =item B<plugins>
492
493 If this option is specified, only the selected plugins will be flushed. The
494 argument is a reference to an array of strings.
495
496 =item B<identifier>
497
498 If this option is specified, only the given identifier(s) will be flushed. The
499 argument is a reference to an array of identifiers. Identifiers, in this case,
500 are hash references and have the members as outlined in L<VALUE IDENTIFIERS>.
501
502 =back
503
504 =cut
505
506 sub flush
507 {
508         my $self  = shift;
509         my %args = @_;
510
511         my $fh = $self->{sock} or confess;
512
513         my $status = 0;
514         my $msg    = "FLUSH";
515
516     $msg .= " timeout=$args{timeout}" if defined $args{timeout};
517
518         if ($args{plugins})
519         {
520                 foreach my $plugin (@{$args{plugins}})
521                 {
522                         $msg .= " plugin=" . $plugin;
523                 }
524         }
525
526         if ($args{identifier})
527         {
528                 for my $identifier (@{$args{identifier}})
529                 {
530                         my $ident_str;
531
532                         if (ref ($identifier) ne 'HASH')
533                         {
534                                 cluck ("The argument of the `identifier' "
535                                         . "option must be an array of hashrefs.");
536                                 return;
537                         }
538
539                         $ident_str = _create_identifier ($identifier) or return;
540                         $msg .= ' identifier=' . _escape_argument ($ident_str);
541                 }
542         }
543
544         $msg .= "\n";
545
546         _debug "-> $msg";
547         print $fh $msg;
548
549         $msg = <$fh>;
550         chomp ($msg);
551         _debug "<- $msg\n";
552
553         ($status, $msg) = split / /, $msg, 2;
554         return 1 if $status == 0;
555
556         $self->{error} = $msg;
557         return;
558 }
559
560 sub error
561 {
562         return shift->{error};
563 }
564
565 =item I<$self>-E<gt>destroy ();
566
567 Closes the socket before the object is destroyed. This function is also
568 automatically called then the object goes out of scope.
569
570 =back
571
572 =cut
573
574 sub destroy
575 {
576         my $self = shift;
577         if ($self->{sock})
578         {
579                 close $self->{sock};
580                 delete $self->{sock};
581         }
582 }
583
584 sub DESTROY
585 {
586         my $self = shift;
587         $self->destroy ();
588 }
589
590 =head1 SEE ALSO
591
592 L<collectd(1)>,
593 L<collectd.conf(5)>,
594 L<collectd-unixsock(5)>
595
596 =head1 AUTHOR
597
598 Florian octo Forster E<lt>octo@collectd.orgE<gt>
599
600 =cut
601 1;
602 # vim: set fdm=marker :