File Coverage

blib/lib/Net/Dogstatsd.pm
Criterion Covered Total %
statement 140 142 98.5
branch 90 94 95.7
condition 40 44 90.9
subroutine 20 21 95.2
pod 9 9 100.0
total 299 310 96.4


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