File Coverage

blib/lib/Zabbix/Sender.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Zabbix::Sender;
2             {
3             $Zabbix::Sender::VERSION = '0.03';
4             }
5             # ABSTRACT: A pure-perl implementation of zabbix-sender.
6              
7 1     1   25030 use Moose;
  0            
  0            
8             use namespace::autoclean;
9              
10             use JSON;
11             use IO::Socket;
12             use IO::Select;
13             use Net::Domain;
14              
15              
16             has 'server' => (
17             'is' => 'rw',
18             'isa' => 'Str',
19             'required' => 1,
20             );
21              
22             has 'port' => (
23             'is' => 'rw',
24             'isa' => 'Int',
25             'default' => 10051,
26             );
27              
28             has 'timeout' => (
29             'is' => 'rw',
30             'isa' => 'Int',
31             'default' => 30,
32             );
33              
34             has 'hostname' => (
35             'is' => 'rw',
36             'isa' => 'Str',
37             'lazy' => 1,
38             'builder' => '_init_hostname',
39             );
40              
41             has 'interval' => (
42             'is' => 'rw',
43             'isa' => 'Int',
44             'default' => 1,
45             );
46              
47             has 'retries' => (
48             'is' => 'rw',
49             'isa' => 'Int',
50             'default' => 3,
51             );
52              
53             has 'keepalive' => (
54             'is' => 'rw',
55             'isa' => 'Bool',
56             'default' => 0,
57             );
58              
59             has '_json' => (
60             'is' => 'rw',
61             'isa' => 'JSON',
62             'lazy' => 1,
63             'builder' => '_init_json',
64             );
65              
66             has '_last_sent' => (
67             'is' => 'rw',
68             'isa' => 'Int',
69             'default' => 0,
70             );
71              
72             has '_socket' => (
73             'is' => 'rw',
74             'isa' => 'Maybe[IO::Socket]',
75             );
76              
77              
78             sub _init_json {
79             my $self = shift;
80              
81             my $JSON = JSON::->new->utf8();
82              
83             return $JSON;
84             }
85              
86              
87             sub _init_hostname {
88             my $self = shift;
89              
90             return Net::Domain::hostname() . '.' . Net::Domain::hostdomain();
91             }
92              
93              
94             has 'zabbix_template_1_8' => (
95             'is' => 'ro',
96             'isa' => 'Str',
97             'default' => "a4 b c4 c4 a*",
98             );
99              
100              
101             sub _encode_request {
102             my $self = shift;
103             my $item = shift;
104             my $value = shift;
105             my $clock = shift;
106              
107             my $data_ref = {
108             'host' => $self->hostname(),
109             'key' => $item,
110             'value' => $value,
111             };
112             $data_ref->{'clock'} = $clock if defined($clock);
113              
114             my $data = {
115             'request' => 'sender data',
116             'data' => [$data_ref],
117             };
118              
119             my $output = '';
120             my $json = $self->_json()->encode($data);
121              
122             # turn on byte semantics to get the real length of the string
123             use bytes;
124             my $length = length($json);
125             no bytes;
126              
127             ## no critic (ProhibitBitwiseOperators)
128             $output = pack(
129             $self->zabbix_template_1_8(),
130             "ZBXD", 0x01,
131             ( $length & 0xFF ),
132             ( $length & 0x00FF ) >> 8,
133             ( $length & 0x0000FF ) >> 16,
134             ( $length & 0x000000FF ) >> 24,
135             0x00, 0x00, 0x00, 0x00, $json
136             );
137             ## use critic
138              
139             return $output;
140             }
141              
142              
143             sub _decode_answer {
144             my $self = shift;
145             my $data = shift;
146              
147             my ( $ident, $answer );
148             $ident = substr( $data, 0, 4 ) if length($data) > 3;
149             $answer = substr( $data, 13 ) if length($data) > 12;
150              
151             if ( $ident && $answer ) {
152             if ( $ident eq 'ZBXD' ) {
153             my $ref = $self->_json()->decode($answer);
154             if ( $ref->{'response'} eq 'success' ) {
155             return 1;
156             }
157             }
158             }
159             return;
160             }
161              
162              
163             # DGR: Anything but send just doesn't makes sense here. And since this is a pure-OO module
164             # and if the implementor avoids indirect object notation you should be fine.
165             ## no critic (ProhibitBuiltinHomonyms)
166             sub send {
167             ## use critic
168             my $self = shift;
169             my $item = shift;
170             my $value = shift;
171             my $clock = shift;
172              
173             my $status = 0;
174             foreach my $i ( 1 .. $self->retries() ) {
175             if ( $self->_send( $item, $value, $clock ) ) {
176             $status = 1;
177             last;
178             }
179             }
180              
181             if ($status) {
182             return 1;
183             }
184             else {
185             return;
186             }
187              
188             }
189              
190             sub _send {
191             my $self = shift;
192             my $item = shift;
193             my $value = shift;
194             my $clock = shift;
195              
196             if ( time() - $self->_last_sent() < $self->interval() ) {
197             my $sleep = $self->interval() - ( time() - $self->_last_sent() );
198             $sleep ||= 0;
199             sleep $sleep;
200             }
201              
202             $self->_connect() unless $self->_socket();
203             $self->_socket()->send( $self->_encode_request( $item, $value, $clock ) );
204             my $Select = IO::Select::->new($self->_socket());
205             my @Handles = $Select->can_read( $self->timeout() );
206              
207             my $status = 0;
208             if ( scalar(@Handles) > 0 ) {
209             my $result;
210             $self->_socket()->recv( $result, 1024 );
211             if ( $self->_decode_answer($result) ) {
212             $status = 1;
213             }
214             }
215             $self->_disconnect() unless $self->keepalive();
216             if ($status) {
217             return $status;
218             }
219             else {
220             return;
221             }
222             }
223              
224             sub _connect {
225             my $self = shift;
226              
227             my $Socket = IO::Socket::INET::->new(
228             PeerAddr => $self->server(),
229             PeerPort => $self->port(),
230             Proto => 'tcp',
231             Timeout => $self->timeout(),
232             ) or die("Could not create socket: $!");
233              
234             $self->_socket($Socket);
235              
236             return 1;
237             }
238              
239             sub _disconnect {
240             my $self = shift;
241              
242             if(!$self->_socket()) {
243             return;
244             }
245              
246             $self->_socket()->close();
247             $self->_socket(undef);
248              
249             return 1;
250             }
251              
252              
253             sub DEMOLISH {
254             my $self = shift;
255              
256             $self->_disconnect();
257              
258             return 1;
259             }
260              
261             no Moose;
262             __PACKAGE__->meta->make_immutable;
263              
264              
265             1; # End of Zabbix::Sender
266              
267             __END__
268              
269             =pod
270              
271             =head1 NAME
272              
273             Zabbix::Sender - A pure-perl implementation of zabbix-sender.
274              
275             =head1 VERSION
276              
277             version 0.03
278              
279             =head1 SYNOPSIS
280              
281             This code snippet shows how to send the value "OK" for the item "my.zabbix.item"
282             to the zabbix server/proxy at "my.zabbix.server.example" on port "10055".
283              
284             use Zabbix::Sender;
285              
286             my $Sender = Zabbix::Sender->new({
287             'server' => 'my.zabbix.server.example',
288             'port' => 10055,
289             });
290             $Sender->send('my.zabbix.item','OK');
291              
292             =head1 NAME
293              
294             Zabbix::Sender - A pure-perl implementation of zabbix-sender.
295              
296             =head1 SUBROUTINES/METHODS
297              
298             =head2 _init_json
299              
300             Zabbix 1.8 uses a JSON encoded payload after a custom Zabbix header.
301             So this initializes the JSON object.
302              
303             =head2 _init_hostname
304              
305             The hostname of the sending instance may be given in the constructor.
306              
307             If not it is detected here.
308              
309             =head2 zabbix_template_1_8
310              
311             ZABBIX 1.8 TEMPLATE
312              
313             a4 - ZBXD
314             b - 0x01
315             c4 - Length of Request in Bytes (64-bit integer), aligned left, padded with 0x00
316             c4 - dito
317             a* - JSON encoded request
318              
319             This may be changed to a HashRef if future version of zabbix change the header template.
320              
321             =head2 _encode_request
322              
323             This method encodes the item and value as a json string and creates
324             the required header acording to the template defined above.
325              
326             =head2 _decode_answer
327              
328             This method tries to decode the answer received from the server.
329              
330             =head2 send
331              
332             Send the given item with the given value to the server.
333              
334             Takes two arguments: item and value. Both should be scalars.
335              
336             =head2 DEMOLISH
337              
338             Disconnects any open sockets on destruction.
339              
340             =head1 AUTHOR
341              
342             "Dominik Schulz", C<< <"lkml at ds.gauner.org"> >>
343              
344             =head1 BUGS
345              
346             Please report any bugs or feature requests to C<bug-zabbix-sender at rt.cpan.org>, or through
347             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Zabbix-Sender>. I will be notified, and then you'll
348             automatically be notified of progress on your bug as I make changes.
349              
350             =head1 SUPPORT
351              
352             You can find documentation for this module with the perldoc command.
353              
354             perldoc Zabbix::Sender
355              
356             You can also look for information at:
357              
358             =over 4
359              
360             =item * RT: CPAN's request tracker
361              
362             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Zabbix-Sender>
363              
364             =item * AnnoCPAN: Annotated CPAN documentation
365              
366             L<http://annocpan.org/dist/Zabbix-Sender>
367              
368             =item * CPAN Ratings
369              
370             L<http://cpanratings.perl.org/d/Zabbix-Sender>
371              
372             =item * Search CPAN
373              
374             L<http://search.cpan.org/dist/Zabbix-Sender/>
375              
376             =back
377              
378             =head1 ACKNOWLEDGEMENTS
379              
380             This code is based on the documentation and sample code found at:
381              
382             =over 4
383              
384             =item http://www.zabbix.com/wiki/doc/tech/proto/zabbixsenderprotocol
385              
386             =item http://www.zabbix.com/documentation/1.8/protocols
387              
388             =back
389              
390             =head1 LICENSE AND COPYRIGHT
391              
392             Copyright 2011 Dominik Schulz.
393              
394             This program is free software; you can redistribute it and/or modify it
395             under the terms of either: the GNU General Public License as published
396             by the Free Software Foundation; or the Artistic License.
397              
398             See http://dev.perl.org/licenses/ for more information.
399              
400             =head1 AUTHOR
401              
402             Dominik Schulz <dominik.schulz@gauner.org>
403              
404             =head1 COPYRIGHT AND LICENSE
405              
406             This software is copyright (c) 2012 by Dominik Schulz.
407              
408             This is free software; you can redistribute it and/or modify it under
409             the same terms as the Perl 5 programming language system itself.
410              
411             =cut