File Coverage

blib/lib/Net/Dogstatsd.pm
Criterion Covered Total %
statement 139 141 98.5
branch 89 92 96.7
condition 40 44 90.9
subroutine 20 21 95.2
pod 9 9 100.0
total 297 307 96.7


line stmt bran cond sub pod time code
1             package Net::Dogstatsd;
2              
3 12     12   630674 use strict;
  12         21  
  12         330  
4 12     12   41 use warnings;
  12         13  
  12         268  
5              
6 12     12   44 use Carp qw( croak carp );
  12         17  
  12         566  
7 12     12   51 use Data::Dumper;
  12         11  
  12         408  
8 12     12   5462 use Data::Validate::Type;
  12         66504  
  12         589  
9 12     12   7373 use IO::Socket::INET;
  12         203623  
  12         68  
10 12     12   10887 use Try::Tiny;
  12         11325  
  12         19094  
11              
12              
13             =head1 NAME
14              
15             Net::Dogstatsd - Perl client to Datadog's dogstatsd metrics collector.
16              
17              
18             =head1 VERSION
19              
20             Version 1.0.3
21              
22             =cut
23              
24             our $VERSION = '1.0.3';
25              
26              
27             =head1 SYNOPSIS
28              
29             This module allows you to send multiple types of metrics to the Datadog service
30             via dogstatsd, a local daemon installed by Datadog agent package.
31              
32             use Net::Dogstatsd;
33              
34             # Create an object to communicate with Dogstatsd, using default server and
35             # port settings.
36             my $dogstatsd = Net::Dogstatsd->new(
37             host => 'localhost', #optional. Default = 127.0.0.1
38             port => '8125', #optional. Default = 8125
39             verbose => 0, #optional. Default = 0
40             );
41              
42             # Set, and print, the 'verbose' option value.
43             $dogstatsd->verbose(1);
44             print "In verbose mode." if $dogstatsd->verbose();
45              
46             # Before sending metrics, we have to get or create a socket to dogstatsd
47             my $socket = $dogstatsd->get_socket();
48              
49             # Counter metrics can be incremented or decremented
50             # By default, they will be incremented or decremented by 1, unless the
51             # optional 'value' parameter is passed
52             $dogstatsd->increment(
53             name => 'test_metric.sample_counter',
54             value => $increment_value, #optional; default = 1
55             tags => [ 'env:production', db ], #optional
56             );
57              
58             $dogstatsd->decrement(
59             name => $metric_name,
60             value => $decrement_value, #optional; default = 1
61             tags => [ 'env:devel', web ], #optional
62             );
63              
64              
65             # Gauge metrics can be used for capturing value of something over time
66             # Example: Gas gauge, inventory level, free memory
67             $dogstatsd->gauge(
68             name => 'test_metric.inventory_level',
69             value => $gauge_value, #required - must be a number
70             tags => [ 'warehouse:us' ], #optional
71             );
72              
73              
74             # Histogram metrics measure the statistical distribution of a set of values.
75             # Provides min/max/avg as well as 75th, 85th, 95th and 99th percentiles.
76             # NOTE: do not use this for timers. Use timer() instead.
77             $dogstatsd->histogram(
78             name => $metric_name,
79             value => $value,
80             tags => [ 'tag1', 'tag2:value', 'tag3' ], #optional
81             );
82              
83              
84             # Timers are a special type of histogram.
85             $dogstatsd->timer(
86             name => $metric_name,
87             value => $metric_value,
88             unit => $metric_unit, # 'ms' (milliseconds) or 's|sec' (seconds)
89             tags => [ 'tag1', 'tag2:value', 'tag3' ], #optional
90             );
91              
92              
93             # Set metrics are special counters that can track unique elements in a group.
94             # Example: the number of unique visitors currently on a website
95             $dogstatsd->sets(
96             name => 'unique.site_visitors',
97             value => $account_id,
98             tags => [ 'referer:Google' ], #optional
99             );
100              
101             =cut
102              
103             =head1 MAIN
104              
105             =cut
106              
107             # Used to build the UDP datagram
108             my $METRIC_TYPES =
109             {
110             'counter' => 'c',
111             'gauge' => 'g',
112             'histogram' => 'h',
113             'timer' => 'ms',
114             'sets' => 's',
115             };
116              
117             =head1 METHODS
118              
119             =head2 new()
120              
121             Create a new Net::Dogstatsd object that will be used to interact with dogstatsd.
122              
123             use Net::Dogstatsd;
124              
125             my $dogstatsd = Net::Dogstatsd->new(
126             host => 'localhost', #optional. Default = 127.0.0.1
127             port => '8125', #optional. Default = 8125
128             verbose => 1, #optional. Default = 0
129             );
130              
131             =cut
132              
133             sub new
134             {
135 12     12 1 466 my ( $class, %args ) = @_;
136              
137             # Defaults
138 12 100       44 my $host = defined( $args{'host'} ) ? $args{'host'} : '127.0.0.1';
139 12 100       36 my $port = defined( $args{'port'} ) ? $args{'port'} : '8125';
140 12 100       30 my $verbose = defined( $args{'verbose'} ) ? $args{'verbose'} : 0;
141              
142 12         48 my $self = {
143             host => $host,
144             port => $port,
145             verbose => $verbose,
146             };
147              
148 12         22 bless( $self, $class );
149              
150 12         30 return $self;
151             }
152              
153              
154             =head2 verbose()
155              
156             Get or set the 'verbose' property.
157              
158             my $verbose = $dogstatsd->verbose();
159             $dogstatsd->verbose( 1 );
160              
161             =cut
162              
163             sub verbose
164             {
165 316     316 1 1409 my ( $self, $value ) = @_;
166              
167 316 100 100     627 if ( defined $value && $value =~ /^[01]$/ )
168             {
169 1         3 $self->{'verbose'} = $value;
170             }
171             else
172             {
173 315         476 return $self->{'verbose'};
174             }
175              
176 1         2 return;
177             }
178              
179              
180             =head2 get_socket()
181              
182             Create a new socket, if one does not already exist.
183              
184             my $socket = $dogstatsd->get_socket();
185              
186             =cut
187              
188             sub get_socket
189             {
190 58     58 1 674 my ( $self ) = @_;
191 58         109 my $verbose = $self->verbose();
192              
193 58 100       122 if ( !defined $self->{'socket'} )
194             {
195             try
196             {
197 8   50 8   533 $self->{'socket'} = IO::Socket::INET->new(
198             PeerAddr => $self->{'host'},
199             PeerPort => $self->{'port'},
200             Proto => 'udp'
201             )
202             || die 'Could not open UDP connection to' . $self->{'host'} . ':' . $self->{'port'} . "\n";
203              
204             }
205             catch
206             {
207             #TODO how to reach this to test it?
208 0     0   0 croak( "Could not open connection to metrics server. Error: >$_<" );
209 8         75 };
210             }
211              
212 58         2921 return $self->{'socket'};
213             }
214              
215              
216              
217             =head2 increment()
218              
219             Increment a counter metric. Include optional 'value' argument to increment by >1.
220             Include optional arrayref of tags/tag-values.
221              
222             $dogstatsd->increment(
223             name => $metric_name,
224             value => $increment_value, #optional; default = 1
225             );
226              
227             $dogstatsd->increment(
228             name => $metric_name,
229             value => $increment_value, #optional; default = 1
230             tags => [ tag1, tag2:value, tag3 ],
231             );
232              
233             =cut
234              
235             sub increment
236             {
237 23     23 1 14184 my ( $self, %args ) = @_;
238              
239 23         57 $self->_counter( action => 'increment', %args );
240 9         34 return;
241             }
242              
243              
244             =head2 decrement()
245              
246             Decrement a counter metric. Include optional 'value' argument to decrement by >1.
247             Include optional arrayref of tags/tag-values.
248              
249             $dogstatsd->decrement(
250             name => $metric_name,
251             value => $decrement_value, #optional; default = 1
252             );
253              
254             $dogstatsd->decrement(
255             name => $metric_name,
256             value => $decrement_value, #optional; default = 1
257             tags => [ tag1, tag2:value, tag3 ],
258             );
259              
260             =cut
261              
262             sub decrement
263             {
264 22     22 1 13121 my ( $self, %args ) = @_;
265              
266 22         50 $self->_counter( action => 'decrement', %args );
267 9         30 return;
268             }
269              
270              
271             =head2 gauge()
272              
273             Send a 'gauge' metric. ex: gas gauge value, inventory stock level
274             Value must be positive number.
275             Include optional arrayref of tags/tag-values.
276              
277             $dogstatsd->gauge(
278             name => $metric_name,
279             value => $gauge_value,
280             );
281              
282             $dogstatsd->gauge(
283             name => $metric_name,
284             value => $gauge_value,
285             tags => [ 'tag1', 'tag2:value', 'tag3' ],
286             );
287              
288             =cut
289              
290             sub gauge
291             {
292 21     21 1 11659 my ( $self, %args ) = @_;
293 21         35 my $verbose = $self->verbose();
294              
295             # Check for mandatory parameters
296 21         25 foreach my $arg ( qw( name value ) )
297             {
298 41 100 100     189 croak "Argument '$arg' is a required argument"
299             if !defined( $args{$arg} ) || ( $args{$arg} eq '' );
300             }
301              
302             # Check that value is a number
303 18 100       43 croak "Value >$args{'value'}< is not a number, which is required for gauge()"
304             unless Data::Validate::Type::is_number( $args{'value'} );
305              
306             # Error checks common to all metric types
307 17         194 $self->_error_checks( %args );
308              
309 8 100       52 $self->_send_metric(
    100          
310             type => 'gauge',
311             value => $args{'value'},
312             name => $args{'name'},
313             tags => defined $args{'tags'} ? $args{'tags'} : [],
314             sample_rate => defined $args{'sample_rate'} ? $args{'sample_rate'} : 1,
315             );
316              
317 8         29 return;
318             }
319              
320              
321             =head2 histogram()
322              
323             Send a 'histogram' metric. Provides min/max/avg as well as 75th, 85th, 95th and
324             99th percentiles.
325             NOTE: do not use this for timers. Use timer() instead.
326             Include optional arrayref of tags/tag-values.
327              
328             $dogstatsd->histogram(
329             name => $metric_name,
330             value => $value,
331             );
332              
333             $dogstatsd->histogram(
334             name => $metric_name,
335             value => $value,
336             tags => [ 'tag1', 'tag2:value', 'tag3' ],
337             );
338              
339             =cut
340              
341             sub histogram
342             {
343 21     21 1 12026 my ( $self, %args ) = @_;
344 21         36 my $verbose = $self->verbose();
345              
346             # Check for mandatory parameters
347 21         35 foreach my $arg ( qw( name value ) )
348             {
349 41 100 100     189 croak "Argument '$arg' is a required argument"
350             if !defined( $args{$arg} ) || ( $args{$arg} eq '' );
351             }
352              
353             # Check that value is a number
354 18 100       48 croak "Value >$args{'value'}< is not a number, which is required for histogram()"
355             unless Data::Validate::Type::is_number( $args{'value'} );
356              
357             # Error checks common to all metric types
358 17         201 $self->_error_checks( %args );
359              
360 8 100       32 $self->_send_metric(
    100          
361             type => 'histogram',
362             value => $args{'value'},
363             name => $args{'name'},
364             tags => defined $args{'tags'} ? $args{'tags'} : [],
365             sample_rate => defined $args{'sample_rate'} ? $args{'sample_rate'} : 1,
366             );
367              
368 8         31 return;
369             }
370              
371              
372             =head2 timer()
373              
374             Send a 'timer' metric. Provides min/max/avg as well as 75th, 85th, 95th
375             and 99th percentiles.
376             Ex: time to run a database query.
377             Include optional arrayref of tags/tag-values.
378              
379             $dogstatsd->timer(
380             name => $metric_name,
381             value => $metric_value,
382             unit => $metric_unit, # 'ms' (milliseconds) or 's|sec' (seconds)
383             );
384              
385             $dogstatsd->timer(
386             name => $metric_name,
387             value => $metric_value,
388             unit => $metric_unit, # 'ms' (milliseconds) or 's|sec' (seconds)
389             tags => [ 'tag1', 'tag2:value', 'tag3' ],
390             );
391              
392             =cut
393              
394             sub timer
395             {
396 25     25 1 13863 my ( $self, %args ) = @_;
397 25         45 my $verbose = $self->verbose();
398              
399             # Check for mandatory parameters
400 25         42 foreach my $arg ( qw( name value unit ) )
401             {
402 71 100 100     278 croak "Argument '$arg' is a required argument for timer()"
403             if !defined( $args{$arg} ) || ( $args{$arg} eq '' );
404             }
405              
406             # Check that value is a positive number
407 21 100       49 croak "Value >$args{'value'}< is not a positive number, which is required for timer()"
408             unless Data::Validate::Type::is_number( $args{'value'}, positive => 1 );
409              
410             # Check that unit is one of the accepted values
411 20 100       307 unless ( $args{'unit'} =~ m/^(s|sec|ms)$/ )
412             {
413 1         10 croak "Argument 'unit' has invalid value >" . $args{'unit'} . "<. Allowed values are 's', 'sec' or 'ms'";
414             }
415              
416 19 100 100     67 if ( $args{'unit'} eq 's' || $args{'unit'} eq 'sec' )
417             {
418             # Convert to milliseconds
419 18         18 $args{'value'} *= 1000;
420 18         146 $args{'value'} = sprintf( "%.2f", $args{'value'} ); #for things that run in microseconds
421             }
422              
423             # Error checks common to all metric types
424 19         44 $self->_error_checks( %args );
425              
426 10 100       35 $self->_send_metric(
    100          
427             type => 'timer',
428             value => $args{'value'},
429             name => $args{'name'},
430             tags => defined $args{'tags'} ? $args{'tags'} : [],
431             sample_rate => defined $args{'sample_rate'} ? $args{'sample_rate'} : 1,
432             );
433              
434 10         42 return;
435             }
436              
437              
438             =head2 sets()
439              
440             Send a 'sets' metric. Used to count the number of unique elements in a group.
441             Ex: unique site visitors.
442             Include optional arrayref of tags/tag-values.
443              
444             $dogstatsd->sets(
445             name => 'unique.site_visitors',
446             value => $account_id,
447             );
448              
449             $dogstatsd->sets(
450             name => 'unique.site_visitors',
451             value => $account_id,
452             tags => [ 'tag1', 'tag2:value', 'tag3' ],
453             );
454              
455             =cut
456              
457             sub sets
458             {
459 20     20 1 11222 my ( $self, %args ) = @_;
460 20         34 my $verbose = $self->verbose();
461              
462             # Check for mandatory parameters
463 20         26 foreach my $arg ( qw( name value ) )
464             {
465 39 100 100     179 croak "Argument '$arg' is a required argument"
466             if !defined( $args{$arg} ) || ( $args{$arg} eq '' );
467             }
468              
469             # Error checks common to all metric types
470 17         40 $self->_error_checks( %args );
471              
472 8 100       32 $self->_send_metric(
    100          
473             type => 'sets',
474             value => $args{'value'},
475             name => $args{'name'},
476             tags => defined $args{'tags'} ? $args{'tags'} : [],
477             sample_rate => defined $args{'sample_rate'} ? $args{'sample_rate'} : 1,
478             );
479              
480 8         31 return;
481             }
482              
483              
484             =head1 INTERNAL FUNCTIONS
485              
486             =head2 _counter
487              
488             $self->_counter(
489             action => [ increment | decrement ],
490             %args
491             );
492              
493             =cut
494              
495             sub _counter
496             {
497 47     47   964 my ( $self, %args ) = @_;
498              
499 47         76 my $action = delete( $args{'action'} );
500 47         80 my $multipliers = {
501             'increment' => 1,
502             'decrement' => -1,
503             };
504              
505 47 100       110 croak "Error - invalid action >$action<" unless exists( $multipliers->{ $action } );
506              
507 46         52 my $multiplier = $multipliers->{ $action };
508              
509             # Check for mandatory parameters
510 46         65 foreach my $arg ( qw( name ) )
511             {
512 46 100 66     250 croak "Argument '$arg' is a required argument"
513             if !defined( $args{$arg} ) || ( $args{$arg} eq '' );
514             }
515              
516             # Check that value, if provided, is a positive integer
517 44 100       83 if ( defined( $args{'value'} ) )
518             {
519 10 100 66     131 croak "Value >$args{'value'}< is not a positive integer, which is required for decrement()"
520             if ( $args{'value'} !~ /^\d+$/ || $args{'value'} <= 0 );
521             }
522              
523             # Error checks common to all metric types
524 36         67 $self->_error_checks( %args );
525              
526 18 100 66     103 $self->_send_metric(
    100          
    100          
527             type => 'counter',
528             name => $args{'name'},
529             value =>
530             ( defined $args{'value'} && $args{'value'} ne '' )
531             ? ( $args{'value'} * $multiplier )
532             : $multiplier,
533             tags => defined $args{'tags'} ? $args{'tags'} : [],
534             sample_rate => defined $args{'sample_rate'} ? $args{'sample_rate'} : 1,
535             );
536              
537 18         38 return;
538             }
539              
540              
541             =head2 _error_checks()
542              
543             $self->_error_checks( %args );
544              
545             Common error checking for all metric types.
546              
547             =cut
548              
549             sub _error_checks
550             {
551 106     106   168 my ( $self, %args ) = @_;
552 106         135 my $verbose = $self->verbose();
553              
554             # Metric name starts with a letter
555 106 100       321 if ( $args{'name'} !~ /^[a-zA-Z]/ )
556             {
557 6         60 croak( 'ERROR - Invalid metric name >' . $args{'name'} . '<. Names must start with a letter, a-z. Not sending.' );
558             }
559              
560             # Tags, if exist...
561 100 100 100     200 if ( defined( $args{'tags'} ) && scalar( @{ $args{'tags'} } ) != 0 )
  42         172  
562             {
563 30 50       77 if ( !Data::Validate::Type::is_arrayref( $args{'tags'} ) )
564             {
565 0         0 croak 'ERROR - Tag list is invalid. Must be an arrayref.';
566             }
567              
568 30         434 foreach my $tag ( @{ $args{'tags'} } )
  30         61  
569             {
570             # Must start with a letter
571 36 100       144 croak( 'ERROR - Invalid tag >' . $tag . '< on metric >' . $args{'name'} . '<. Tags must start with a letter, a-z. Not sending.' )
572             if ( $tag !~ /^[a-zA-Z]/ );
573              
574             # Must be < 200 characters [ discovered this limitation while testing. Datadog stated it should truncate, but I received various errors ]
575 30 100       124 croak( 'ERROR - Invalid tag >' . $tag . '< on metric >' . $args{'name'} . '<. Tags must be 200 chars or less. Not sending.' )
576             if ( length( $tag ) > 200 );
577              
578             # NOTE: This check isn't required by Datadog, they will allow this through.
579             # However, this tag will not behave as expected in the graphs, if we were to allow it.
580 24 100       163 croak( 'ERROR - Invalid tag >' . $tag . '< on metric >' . $args{'name'} . '<. Tags should only contain a single colon (:). Not sending.' )
581             if ( $tag =~ /^\S+:\S+:/ );
582             }
583             }
584              
585             # Check that optional 'sample_rate' argument is valid ( 1, or a float between 0 and 1 )
586 76 100       134 if ( defined $args{'sample_rate'} )
587             {
588 30 100 100     68 if ( !Data::Validate::Type::is_number( $args{'sample_rate'} , strictly_positive => 1 ) || $args{'sample_rate'} > 1 )
589             {
590 24         493 croak 'ERROR - Invalid sample rate >' . $args{'sample_rate'} . '<. Must be 1, or a float between 0 and 1.';
591             }
592             }
593              
594 52         146 return;
595             }
596              
597              
598             =head2 _send_metric()
599              
600             Send metric to stats server.
601              
602             =cut
603              
604             sub _send_metric
605             {
606 62     62   4432 my ( $self, %args ) = @_;
607 62         81 my $verbose = $self->verbose();
608              
609             # Check for mandatory parameters
610 62         86 foreach my $arg ( qw( name type value ) )
611             {
612 181 100 100     679 croak "Argument '$arg' is a required argument"
613             if !defined( $args{$arg} ) || ( $args{$arg} eq '' );
614             }
615              
616 57         73 my $original_name = $args{'name'};
617             # Metric name should only contain alphanumeric, "_", ".". Convert anything else to underscore and warn about substitution
618             # NOTE: Datadog will do this for you anyway, but won't warn you what the actual metric name will become.
619 57         209 $args{'name'} =~ s/[^a-zA-Z0-9_\.]/_/;
620              
621             #TODO change to Log::Any output
622 57 100       262 carp( 'WARNING: converted metric name from >$original_name< to >', $args{'name'}, '<. Names should only contain: a-z, 0-9, underscores, and dots/periods.' )
623             if $args{'name'} ne $original_name;
624              
625             # Default sample rate = 1
626 57 100       9045 if( !defined( $args{'sample_rate'} ) )
627             {
628 4         6 $args{'sample_rate'} = 1;
629             }
630              
631 57         98 my $socket = $self->get_socket();
632 57 50       100 return unless defined $socket;
633              
634             # Datagram format. More info at http://docs.datadoghq.com/guides/dogstatsd/
635             # dashboard.metricname:value|type|@sample_rate|#tag1:value,tag2
636 57         222 my $metric_string = $args{'name'} . ':' . $args{'value'} . '|' . $METRIC_TYPES->{ $args{'type'} } . '|@' . $args{'sample_rate'} ;
637              
638 57 100 100     119 if ( defined $args{'tags'} && scalar ( @{ $args{'tags'} } ) != 0 )
  56         170  
639             {
640 14         14 foreach my $tag ( @{ $args{'tags'} } )
  14         28  
641             {
642 20         17 my $original_tag = $tag;
643              
644 20         31 $tag =~ s/\s+$//; # Strip trailing whitespace
645             # Tags should only contain alphanumeric, "_", "-",".", "/", ":". Convert anything else to underscore and warn about substitution
646 20         40 $tag =~ s/[^a-zA-Z0-9_\-\.\/:]/_/g;
647 20         19 $tag =~ s/\s+/_/g; # Replace remaining whitespace with underscore
648 20 100       105 carp( "WARNING: converted tag from >$original_tag< to >$tag<. Tags should only contain: a-z, 0-9, underscores, dashes, dots/periods, forward slashes, colons." )
649             if $tag ne $original_tag;
650             }
651 14         3132 $metric_string .= '|#' . join( ',', @{ $args{'tags'} } );
  14         35  
652             }
653              
654             # Force to all lower case because Datadog has case sensitive tags and metric
655             # names. We don't want to end up with multiple case variations of the same
656             # metric name/tag
657 56         95 $metric_string = lc( $metric_string );
658              
659 56 50       79 carp( "\nbuilt metric string >$metric_string<" ) if $verbose;
660              
661             # Use of rand() is how the Ruby and Python clients implement sampling, so we will too.
662 56 100 100     365 if ( $args{'sample_rate'} == 1 || ( rand() < $args{'sample_rate'} ) )
663             {
664 52         128 my $response = IO::Socket::send( $socket, $metric_string, 0 );
665 52 100       2200 unless (defined $response)
666             {
667 24         333 carp( "error sending metric [string >$metric_string<]: $!" );
668             }
669             }
670              
671 56         11936 return;
672             }
673              
674              
675             =head1 RUNNING TESTS
676              
677             By default, only basic tests that do not require a connection to Datadog's
678             platform are run in t/.
679              
680             To run the developer tests, you will need to do the following:
681              
682             =over 4
683              
684             =item * Sign up to become a Datadog customer ( if you are not already), at
685             L. Free trial accounts are available.
686              
687             =item * Install and configure Datadog agent software (requires python 2.6)
688             L
689              
690             =back
691              
692              
693             =head1 AUTHOR
694              
695             Jennifer Pinkham, C<< >>.
696              
697              
698             =head1 BUGS
699              
700             Please report any bugs or feature requests to the GitHub Issue Tracker at
701             L.
702             I will be notified, and then you'll automatically be notified of progress on
703             your bug as I make changes.
704              
705              
706             =head1 SUPPORT
707              
708             You can find documentation for this module with the perldoc command.
709              
710             perldoc Net::Dogstatsd
711              
712              
713             You can also look for information at:
714              
715             =over 4
716              
717             =item * Bugs: GitHub Issue Tracker
718              
719             L
720              
721             =item * AnnoCPAN: Annotated CPAN documentation
722              
723             L
724              
725             =item * CPAN Ratings
726              
727             L
728              
729             =item * MetaCPAN
730              
731             L
732              
733             =back
734              
735              
736             =head1 ACKNOWLEDGEMENTS
737              
738             I originally developed this project for ThinkGeek (L).
739             Thanks for allowing me to open-source it!
740              
741             =head1 COPYRIGHT & LICENSE
742              
743             Copyright 2015 Jennifer Pinkham.
744              
745             This program is free software: you can redistribute it and/or modify it under
746             the terms of the GNU General Public License version 3 as published by the Free
747             Software Foundation.
748              
749             This program is distributed in the hope that it will be useful, but WITHOUT ANY
750             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
751             PARTICULAR PURPOSE. See the GNU General Public License for more details.
752              
753             You should have received a copy of the GNU General Public License along with
754             this program. If not, see http://www.gnu.org/licenses/
755             =cut
756              
757             1;