File Coverage

blib/lib/Harbinger/Client.pm
Criterion Covered Total %
statement 32 36 88.8
branch 5 8 62.5
condition 0 6 0.0
subroutine 12 14 85.7
pod 3 3 100.0
total 52 67 77.6


line stmt bran cond sub pod time code
1             package Harbinger::Client;
2             $Harbinger::Client::VERSION = '0.001001';
3             # ABSTRACT: Impend all the doom you could ever want âš”
4              
5 1     1   22102 use v5.16.1;
  1         4  
  1         46  
6 1     1   530 use utf8;
  1         7  
  1         3  
7 1     1   556 use Moo;
  1         10896  
  1         4  
8 1     1   1278 use warnings NONFATAL => 'all';
  1         1  
  1         29  
9 1     1   4 use Try::Tiny;
  1         0  
  1         43  
10              
11 1     1   329 use Harbinger::Client::Doom;
  1         3  
  1         32  
12 1     1   555 use IO::Socket::INET;
  1         17890  
  1         5  
13              
14             has _harbinger_ip => (
15             is => 'ro',
16             default => '127.0.0.1',
17             init_arg => 'harbinger_ip',
18             );
19              
20             has _harbinger_port => (
21             is => 'ro',
22             default => '8001',
23             init_arg => 'harbinger_port',
24             );
25              
26             has _udp_handle => (
27             is => 'ro',
28             lazy => 1,
29             builder => sub {
30 1 50 0 1   396 IO::Socket::INET->new(
31             PeerAddr => $_[0]->_harbinger_ip,
32             PeerPort => $_[0]->_harbinger_port,
33             Proto => 'udp'
34             ) or $ENV{HARBINGER_WARNINGS} && warn "couldn't connect to socket: $@"
35             },
36             );
37              
38             has _default_args => (
39             is => 'ro',
40             default => sub { [] },
41             init_arg => 'default_args',
42             );
43              
44             sub start {
45 0     0 1 0 my $self = shift;
46              
47 0         0 Harbinger::Client::Doom->start(
48 0         0 @{$self->_default_args},
49             @_,
50             )
51             }
52              
53             sub instant {
54 3     3 1 1109 my $self = shift;
55              
56 3         76 $self->send(
57             Harbinger::Client::Doom->new(
58 3         6 @{$self->_default_args},
59             @_,
60             )
61             )
62             }
63              
64             sub send {
65 3     3 1 29 my ($self, $doom) = @_;
66              
67             return unless
68 3 100       10 my $msg = $doom->_as_sereal;
69              
70 1     1   672 no warnings;
  1         2  
  1         126  
71             &try(sub{
72 1 50 0 1   31 send($self->_udp_handle, $msg, 0) == length($msg)
73             or $ENV{HARBINGER_WARNINGS} && warn "cannot send to: $!";
74             },($ENV{HARBINGER_WARNINGS}?(catch {
75 0     0     warn $_;
76 1 50       12 }):()));
77             }
78              
79             1;
80              
81             __END__
82              
83             =pod
84              
85             =encoding UTF-8
86              
87             =head1 NAME
88              
89             Harbinger::Client - Impend all the doom you could ever want ⚔
90              
91             =head1 VERSION
92              
93             version 0.001001
94              
95             =head1 SYNOPSIS
96              
97             my $client = Harbinger::Client->new(
98             harbinger_ip => '10.6.1.6',
99             harbinger_port => 8090,
100             default_args => [
101             server => 'foo.lan.bar.com',
102             port => 1890,
103             ],
104             );
105              
106             my $doom = $client->start(
107             ident => 'process-images',
108             );
109              
110             for (@images) {
111             ...
112             $doom->bode_ill;
113             }
114              
115             $client->send($doom->finish);
116              
117             =head1 DESCRIPTION
118              
119             After reading
120             L<The Mature Optimization Handbook|http://carlos.bueno.org/optimization/mature-optimization.pdf>,
121             in a fever dream of hubris, I wrote C<Harbinger::Client> and
122             L<Harbinger::Server|https://github.com/frioux/Harbinger>. They have both
123             served me surprisingly well with how minimal they are. The goal is to be as
124             lightweight as possible such that the measuring of performance does not degrade
125             performance nor impact reliability. If the client B<ever> throws an exception
126             I have failed in my goals.
127              
128             As should be clear in the L</SYNOPSIS> the grim measurement that the
129             C<Harbinger> records is called L</DOOM 💀>. L</DOOM 💀> currently measures a handful
130             of data points, but the important bits are:
131              
132             =over 2
133              
134             =item * time
135              
136             =item * space
137              
138             =item * queries
139              
140             =back
141              
142             See more in L</DOOM 💀>.
143              
144             =head3 METHODS
145              
146             =head3 C<new>
147              
148             Instantiate client with this method. Note example in L</SYNOPSIS>.
149              
150             Takes a hash of C<harbinger_ip> (default of C<127.0.0.1>), C<harbinger_port>
151             (default of C<8001>), and C<default_args> (default of C<[]>).
152              
153             C<harbinger_ip> and C<harbinger_port> are how to connect to the remote
154             C<HarBinger::Server>.
155              
156             C<default_args> get used in L</start> and L</instant> when forging new L</DOOM 💀>.
157              
158             =head3 C<start>
159              
160             The typical way to start measuring some L</DOOM 💀>. Note example in L</SYNOPSIS>.
161              
162             Actual implementation at L<< /Harbinger::Client::Doom->start >>.
163              
164             =head3 C<instant>
165              
166             $client->instant(
167             ident => 'man overboard',
168             count => 1,
169             );
170              
171             Instead of measuring deltas as L</DOOM 💀> typically does, this method is for measuring
172             instantaneous events, maybe for counting or graphing them later. Sends the
173             event immediately.
174              
175             =head3 C<send>
176              
177             $client->send($completed_doom);
178              
179             Once L</DOOM 💀> is ready to be sent to the server pass it to C<send>.
180              
181             =head1 LIGHTHOUSE ⛯
182              
183             Beware the siren song (👄) of the B<Harbinger>! The API is not stable yet, I already
184             have major changes planned for a plugin (🔌) system. I'm not even going to attempt
185             to keep things working. You've been warned (âš ).
186              
187             =head1 DOOM 💀
188              
189             Measure the crushing weight, the glacial pace, the incredible demand which your
190             application puts upon your database server with C<DOOMâ„¢>
191              
192             =head2 DOOMFUL ATTRIBUTES ☠
193              
194             =head3 C<server>
195              
196             Something unique that identifies the machine that we are measuring the L</DOOM 💀>
197             for. A good idea is the ip address or the hostname. If this is not set L</DOOM 💀>
198             will not be sent or recorded.
199              
200             =head3 C<ident>
201              
202             Something unique that identifies the task that we are measuring the L</DOOM 💀> for.
203             For a web server, C<PATH_INFO> might be a good option, or for some kind of
204             message queue the task type would be a good option.
205              
206             =head3 C<pid>
207              
208             The pid of the process L</DOOM 💀> is being recorded for. Has a sensible default,
209             you probably will never need to set it.
210              
211             =head3 C<port>
212              
213             The port that the service is listening on, if applicable. Leave alone if
214             unknown or not applicable.
215              
216             =head3 C<count>
217              
218             The count of things being done in this unit of L</DOOM 💀>. If it were a web
219             request that returns a list of items, this would reasonably be set as that
220             number. If the operation is not related to things that are countable, leave
221             alone.
222              
223             =head3 C<milliseconds_elapsed>
224              
225             The total milliseconds elapsed during the unit of L</DOOM 💀>. If instant or
226             unknown L</DOOM 💀> leave empty.
227              
228             =head3 C<db_query_count>
229              
230             The total queries executed during the unit of L</DOOM 💀>. If not applicable or
231             unknown L</DOOM 💀> leave empty.
232              
233             =head3 C<memory_growth_in_kb>
234              
235             The total memory growth in kb during the unit of L</DOOM 💀>. If not applicable or
236             unknown L</DOOM 💀> leave empty.
237              
238             =head3 C<query_logger>
239              
240             A tool to measure query count with C<DBIx::Class>. Please only use as
241             documented, underlying implementation may change. See L</QUERYLOG 📜>
242              
243             =head2 DOOMFUL METHODS 🔮
244              
245             =head3 C<< Harbinger::Client::Doom->start >>
246              
247             Normally called via L</start>. Sets up some internal stuff to make automatic
248             measuring of L</memory_growth_in_kb> and L</milliseconds_elapsed> work. Takes a
249             hash and merges hash into the object via accessors.
250              
251             B<NOTE>: to automatically measure memory growth you need either
252             L<Win32::Process::Memory> or L<Proc::ProcessTable> installed.
253              
254             =head3 C<< $doom->bode_ill >>
255              
256             Increment the L</DOOM 💀> L</count>er.
257              
258             =head3 C<< $doom->finish >>
259              
260             $doom->finish( count => 32 );
261              
262             Finalizes L</memory_growth_in_kb> and L</milliseconds_elapsed>. As with
263             L<< /Harbinger::Client::Doom->start >> takes a hash and merges it into the object
264             via accessors. Returns the object to allow chaining.
265              
266             =head1 C<Plack::Middleware::Harbinger>
267              
268             builder {
269             enable Harbinger => {
270             harbinger_ip => '192.168.1.1',
271             harbinger_port => 2250,
272             default_args => [
273             server => '192.168.1.2',
274             port => 80,
275             ],
276             };
277             $my_app
278             };
279              
280             Takes the same args as L</new>. Adds C<query_log> from L</DOOM 💀> to
281             C<harbinger.querylog> in C<psgi ENV>. See L</QUERYLOG 📜>.
282              
283             After the query completes the L</DOOM 💀> will automatically be sent.
284              
285             If C<harbinger.ident> is set it will be used for the L</ident>, otherwise
286             C<PATH_INFO> will be used.
287              
288             C<harbinger.server>, and C<harbinger.count> are passed more or less directly.
289              
290             C<harbinger.port> will be passed if true, otherwise C<SERVER_PORT> will be used.
291              
292             =head1 C<Catalyst::TraitFor::Controller::Harbinger>
293              
294             This page intentionally left blank.
295              
296             =head1 QUERYLOG 📜
297              
298             You are recommended to apply the query log with L<DBIx::Class::QueryLog::Tee>
299             and L<DBIx::Class::QueryLog::Conditional>.
300              
301             First, set up your schema
302             package MyApp::Schema;
303              
304             use base 'DBIx::Class::Schema';
305             use aliased 'DBIx::Class::QueryLog::Tee';
306             use aliased 'DBIx::Class::QueryLog::Conditional';
307              
308             __PACKAGE__->load_namespaces(
309             default_resultset_class => 'ResultSet',
310             );
311              
312             sub connection {
313             my $self = shift;
314              
315             my $ret = $self->next::method(@_);
316              
317             $ret->storage->debugobj(
318             Tee->new(
319             loggers => {
320             original => Conditional->new(
321             logger => $self->storage->debugobj,
322             enabled_method => sub { $ENV{DBIC_TRACE} },
323             ),
324             },
325             )
326             );
327              
328             $ret->storage->debug(1);
329              
330             $ret
331             }
332              
333             1;
334              
335             Note that the L<DBIx::Class::QueryLog::Tee> extension allows you to add more
336             Query loggers as you go, so you can even log inner loops and outer loops at the
337             same time. Also note that L<DBIx::Class::QueryLog::Conditional> allows you to
338             have the C<Harbinger> loggers always on, but the pretty L<DBIx::Class> console
339             logger can still be set via environment variable, as usual.
340              
341             Now to set the logger after whipping up some L</DOOM 💀> this is all that's needed:
342              
343             my $doom = $client->start(
344             ident => 'process-images',
345             );
346              
347             $schema->storage->debugobj
348             ->add_logger('process-images-harbinger', $doom->query_logger);
349              
350             $client->send($doom->finish);
351             $schema->storage->debugobj
352             ->remove_logger('process-images-harbinger');
353              
354             Finally, if you have some legacy code or are using the wrong ORM, you can still
355             use the QueryLogger as follows:
356              
357             $dbh->{Callbacks}{ChildCallbacks}{execute} = sub {
358             $doom->query_log->query_start('', []);
359             $doom->query_log->query_end('', []);
360             return ();
361             }
362              
363             If you can pull it off, doing this dynamically with C<local> is preferred, but
364             that's not always possible.
365              
366             =head1 AUTHOR
367              
368             Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com>
369              
370             =head1 COPYRIGHT AND LICENSE
371              
372             This software is copyright (c) 2014 by Arthur Axel "fREW" Schmidt.
373              
374             This is free software; you can redistribute it and/or modify it under
375             the same terms as the Perl 5 programming language system itself.
376              
377             =cut