File Coverage

blib/lib/WebService/DataDog.pm
Criterion Covered Total %
statement 32 75 42.6
branch 2 38 5.2
condition 1 15 6.6
subroutine 10 13 76.9
pod 3 3 100.0
total 48 144 33.3


line stmt bran cond sub pod time code
1             package WebService::DataDog;
2              
3 39     39   1709741 use strict;
  39         71  
  39         1162  
4 39     39   154 use warnings;
  39         49  
  39         820  
5              
6 39     39   1942 use Data::Dumper;
  39         14228  
  39         1611  
7 39     39   23481 use LWP::UserAgent qw();
  39         1157849  
  39         902  
8 39     39   305 use HTTP::Request qw();
  39         46  
  39         427  
9 39     39   24077 use JSON qw();
  39         326833  
  39         942  
10 39     39   17464 use Class::Load qw();
  39         799297  
  39         1041  
11 39     39   292 use Carp qw( carp croak );
  39         47  
  39         1714  
12 39     39   6036 use Data::Validate::Type qw();
  39         70157  
  39         25427  
13              
14              
15             our $API_ENDPOINT = "https://app.datadoghq.com/api/v1/";
16              
17              
18             =head1 NAME
19              
20             WebService::DataDog - Interface to DataDog's REST API.
21              
22              
23             =head1 VERSION
24              
25             Version 1.0.3
26              
27             =cut
28              
29             our $VERSION = '1.0.3';
30              
31              
32             =head1 SYNOPSIS
33              
34             This module allows you to interact with DataDog, a service that will "Capture
35             metrics and events, then graph, filter, and search to see what's happening and
36             how systems interact." This module encapsulates all the communications with the
37             REST API provided by DataDog to offer a Perl interface to metrics, dashboards,
38             events, alerts, etc.
39              
40             Requests that write data require reporting access and require an API key.
41             Requests that read data require full access and additionally require an
42             application key.
43              
44             use WebService::DataDog;
45              
46             # Create an object to communicate with DataDog
47             my $datadog = WebService::DataDog->new(
48             api_key => 'your_api_key_here',
49             application_key => 'your_application_key',
50             );
51              
52             # For metrics functions, first build a metrics object
53             my $metric = $datadog->build('Metric');
54              
55             # To post metrics (past or present)
56             # NOTE: only use 'value' OR 'data_points', but not both.
57             $metric->emit(
58             name => $metric_name,
59             type => $metric_type, # Optional - gauge|counter. Default=gauge.
60             value => $metric_value, # For posting a single data point, time 'now'
61             data_points => $data_points, # 1+ data points, with timestamps
62             host => $hostname, # Optional - host that produced the metric
63             tags => $tag_list, # Optional - tags associated with the metric
64             );
65              
66             # For dashboard/timeboard functions, first build a dashboard object
67             my $dashboard = $datadog->build('Dashboard');
68              
69             # Create a new dashboard
70             my $dashboard_id = $dashboard->create(
71             title => $dash_title,
72             description => $dash_description,
73             graphs => $graphs,
74             );
75              
76             # Delete a user-created dashboard that you don't need anymore
77             $dashboard->delete( id => $dash_id );
78              
79             # To make any changes to an existing user-created dashboard:
80             # Specify dash_id and any combination of title, description, graphs
81             $dashboard->update(
82             id => $dash_id,
83             title => $dash_title,
84             description => $dash_description,
85             graphs => $graphs,
86             );
87              
88             # For event functions, first build an event object
89             my $event = $datadog->build('Event');
90              
91             # To search the event stream
92             my $event_list = $event->search(
93             start => $start_time,
94             end => $end_time, # Optional - default 'now'
95             priority => $priority, # Optional - low|normal
96             sources => $sources, # Optional - list of sources. Ex: Datadog, Github, Pingdom, Webmetrics
97             tags => $tag_list, # Optional - list of tags associated with the event
98             );
99              
100             # Find all events in the last 48 hours.
101             my $event_list = $event->search(
102             start => time() - ( 48 * 60 * 60 ),
103             );
104              
105             # To get all details of a specific event
106             my $event_data = $event->retrieve( id => $event_id );
107              
108             # To post a new event to the event stream
109             $event->create(
110             title => $event_title,
111             text => $event_text, # Body/Description of the event.
112             date_happened => $timestamp, # Optional, default "now"
113             priority => $priority, # Optional. normal|low
114             related_event_id => $event_id, # Optional, id of aggregate event
115             tags => $tag_list, # Optional - tags to apply to event (easy to search by)
116             alert_type => $alert_type, # Optional. error|warning|info|success
117             aggregation_key => $agg_key, # Optional. Arbitrary string to use for aggregation.
118             source_type_name => $source_type, # Optional. nagios|hudson|jenkins|user|my apps|feed|chef|puppet|git|bitbucket|fabric|capistrano
119             );
120              
121             # Submit a user event, with timestamp of `now`.
122             $event->create(
123             title => 'Test event',
124             text => 'Testing posting to event stream',
125             source_type_name => 'user',
126             );
127              
128             # For alert functions, first build an alert object
129             my $alert = $datadog->build('Alert');
130              
131             # Get list, with details, of all alerts
132             my $alert_list = $alert->retrieve_all();
133              
134             # Create a new alert
135             my $alert_id = $alert->create(
136             query => $query, # Metric query to alert on
137             name => $alert_name, # Optional. default=dynamic, based on query
138             message => $message, # Optional. default=None
139             silenced => $boolean, # Optional. default=0
140             );
141              
142             # Retrieve details on a specific alert
143             my $alert_data = $alert->retrieve( id => $alert_id );
144              
145             # Update an existing alert
146             $alert->update(
147             id => $alert_id, # ID of alert to modify
148             query => $query, # Metric query to alert on
149             name => $alert_name, # Optional.
150             message => $message, # Optional.
151             silenced => $boolean, # Optional.
152             );
153              
154             # Mute all alerts at once. Example usage: system maintenance.
155             $alert->mute_all();
156              
157             # Unmute all alerts at once. Example usage: completed system maintenance.
158             $alert->unmute_all();
159              
160             # For tag functions, first build a tag object
161             my $tag = $datadog->build('Tag');
162              
163             # Retrieve a mapping of tags to hosts.
164             my $tag_host_list = $tag->retrieve_all();
165              
166             # Return a list of tags for the specified host.
167             my $tag_list = $tag->retrieve( host => $host_name_or_id );
168              
169             # Update tags for specified host.
170             $tag->update(
171             host => $host, # name/ID of host to modify
172             tags => $tag_list, # Updated full list of tags to apply to host
173             );
174              
175             # Add tags to specified host.
176             $tag->add(
177             host => $host, # name/ID of host to modify
178             tags => $tag_list, # Updated full list of tags to apply to host
179             );
180              
181             # Delete all tags from the specified host.
182             $tag->delete( host => $host );
183              
184             # For search, first build a search object
185             my $search = $datadog->build('Search');
186              
187             my $search_results = $search->retrieve(
188             term => $search_term,
189             facet => [ 'hosts', 'metrics' ] #optional
190             );
191            
192             # For graph snapshots, first build a graph object
193             my $graph = $datadog->build('Graph');
194            
195             my $snapshot_url = $graph->snapshot(
196             metric_query => $metric_query,
197             start => $start_timestamp,
198             end => $end_timestamp,
199             event_query => $event_query, # optional -- default=None
200             );
201            
202             =cut
203              
204              
205             =head1 METHODS
206              
207             =head2 new()
208              
209             Create a new DataDog object that will be used as the interface with
210             DataDog's API
211              
212             use WebService::DataDog;
213              
214             # Create an object to communicate with DataDog
215             my $datadog = WebService::DataDog->new(
216             api_key => 'your_api_key_here',
217             application_key => 'your_application_key',
218             verbose => 1,
219             );
220              
221             Creates a new object to communicate with DataDog.
222              
223             Parameters:
224              
225             =over 4
226              
227             =item * api_key
228              
229             DataDog API key. Found at L
230              
231             =item * application_key
232              
233             DataDog application key. Multiple keys can be generated per account. Generate/View existing at
234             L
235              
236             =item * verbose
237              
238             Optional. Set to 1 to see debugging output of request/response interaction with DataDog service.
239              
240             =back
241              
242             =cut
243              
244             sub new
245             {
246 1     1 1 13 my ( $class, %args ) = @_;
247              
248             # Check for mandatory parameters
249 1         2 foreach my $arg ( qw( api_key application_key ) )
250             {
251 2 50 33     12 croak "Argument '$arg' is required to create the WebService::DataDog object"
252             if !defined( $args{$arg} ) || ( $args{$arg} eq '' );
253             }
254              
255             # Create the object
256 1 50       7 my $self = bless(
257             {
258             api_key => $args{'api_key'},
259             application_key => $args{'application_key'},
260             verbose => defined $args{'verbose'} ? $args{'verbose'} : 0,
261             },
262             $class,
263             );
264              
265 1         2 return $self;
266             }
267              
268              
269             =head2 build()
270              
271             Create a WebService::DataDog::* object with the correct connection parameters.
272              
273             # Use the factory to get a WebService::DataDog::* object with
274             # the correct DataDog connection parameters.
275             my $metric = $datadog->build( 'Metric' );
276              
277             Parameters:
278              
279             =over
280              
281             =item *
282              
283             The submodule name, such as Metric for WebService::DataDog::Metric.
284              
285             =back
286              
287             =cut
288              
289             sub build
290             {
291 0     0 1   my ( $self, $module ) = @_;
292              
293             # Check required arguments
294 0 0 0       croak 'Please specify the name of the module to build'
295             if !defined( $module ) || ( $module eq '' );
296              
297             # Load the class corresponding to the submodule requested.
298 0           my $class = __PACKAGE__ . '::' . $module;
299 0 0         Class::Load::load_class( $class ) || croak "Failed to load $class, double-check the class name";
300              
301             # Instantiate a new object of that class. Since it's a subclass
302             # of WebService::DataDog, we pass all the non-hidden properties
303             # of the datadog object to build it.
304 0           my $object = $class->new(
305 0           map { $_ => $self->{$_} }
306 0           grep { substr( $_, 0, 1 ) ne '_' }
307             keys %$self
308             );
309              
310 0           return $object;
311             }
312              
313              
314             =head2 verbose()
315              
316             Get or set the 'verbose' property.
317              
318             my $verbose = $self->verbose();
319             $self->verbose( 1 );
320              
321             =cut
322              
323             sub verbose
324             {
325 0     0 1   my ( $self, $value ) = @_;
326              
327 0 0 0       if ( defined $value && $value =~ /^[01]$/ )
328             {
329 0           $self->{'verbose'} = $value;
330             }
331             else
332             {
333 0           return $self->{'verbose'};
334             }
335              
336 0           return;
337             }
338              
339              
340              
341             =head1 RUNNING TESTS
342              
343             By default, only basic tests that do not require a connection to DataDog's
344             platform are run in t/.
345              
346             To run the developer tests, you will need to do the following:
347              
348             =over 4
349              
350             =item * Sign up to become a DataDog customer ( if you are not already), at
351             L. Free trial accounts are available.
352              
353             =item * Generate an application key at
354             L
355              
356             =back
357              
358             You can now create a file named DataDogConfig.pm in your own directory, with
359             the following content:
360              
361             package DataDogConfig;
362              
363             sub new
364             {
365             return
366             {
367             api_key => 'your_api_key',
368             application_key => 'your_application_key',
369             verbose => 0, # Enable this for debugging output
370             };
371             }
372              
373             1;
374              
375             You will then be able to run all the tests included in this distribution, after
376             adding the path to DataDogConfig.pm to your library paths.
377              
378              
379              
380              
381             =head1 INTERNAL METHODS
382              
383             =head2 _send_request()
384              
385              
386             =cut
387             sub _send_request ## no critic qw( Subroutines::ProhibitUnusedPrivateSubroutines )
388             {
389 0     0     my ( $self, %args ) = @_;
390 0           my $verbose = $self->verbose();
391              
392             # Check for mandatory parameters
393 0           foreach my $arg ( qw( data method url ) )
394             {
395 0 0 0       croak "Argument '$arg' is needed to send a request with the WebService::DataDog object"
396             if !defined( $args{$arg} ) || ( $args{$arg} eq '' );
397             }
398              
399 0           my $url = $args{'url'};
400 0           my $method = $args{'method'};
401              
402             # Add authentication info
403 0 0         if ( $url =~ /\?/ ) # Some endpoints will already have URL params...
404             {
405 0           $url .= '&api_key=' . $self->{'api_key'} . '&application_key=' . $self->{'application_key'};
406             }
407             else
408             {
409 0           $url .= '?api_key=' . $self->{'api_key'} . '&application_key=' . $self->{'application_key'};
410             }
411              
412 0           my $request;
413 0 0         if ( $method =~ /\A(?:GET|POST|DELETE|PUT)\z/x )
414             {
415 0           $request = HTTP::Request->new( $method => $url );
416             }
417             else
418             {
419 0           croak "The method >$method< is not supported. Not sending request.";
420             }
421              
422 0 0         carp "Sending request to URL >" . ( defined( $url ) ? $url : '' ) . "< via method >$method<"
    0          
423             if $verbose;
424              
425              
426 0           my $json_in = JSON::encode_json( $args{'data'} );
427 0 0         carp "Sending JSON request >" . ( defined( $json_in ) ? $json_in : '' ) . "<"
    0          
428             if $verbose;
429              
430 0           $request->content_type('application/json');
431 0           $request->content( $json_in );
432              
433 0 0         carp "Request object: ", Dumper( $request )
434             if $verbose;
435              
436 0           my $user_agent = LWP::UserAgent->new();
437 0           my $response = $user_agent->request($request);
438              
439 0 0         if (! $response->is_success() )
440             {
441 0           my $message = "Request failed:" . $response->status_line();
442 0           my $content = $response->content();
443 0           $message .= "\nResponse errors:" . Dumper($content);
444              
445 0           croak $message;
446             }
447              
448 0 0         carp "Response >" . ( defined( $response ) ? $response->content() : '' ) . "<"
    0          
449             if $verbose;
450              
451             # Try to parse JSON response, only if one was received.
452             # Some functions, such as Dashboard::delete(), Alert::mute_all, Alert::unmute_all()
453             # return nothing when successful, so there won't be anything to parse.
454 0 0 0       my $json_out = defined( $response ) && defined( $response->content() ) && $response->content() ne ''
455             ? JSON::decode_json( $response->content() )
456             : '';
457              
458 0 0         carp "JSON Response >" . ( defined( $json_out ) ? Dumper($json_out) : '' ) . "<"
    0          
459             if $verbose;
460              
461 0           return $json_out;
462             }
463              
464              
465              
466             =head1 AUTHOR
467              
468             Jennifer Pinkham, C<< >>.
469              
470              
471             =head1 BUGS
472              
473             Please report any bugs or feature requests to C,
474             or through the web interface at L.
475             I will be notified, and then you'll automatically be notified of progress on
476             your bug as I make changes.
477              
478              
479             =head1 SUPPORT
480              
481             You can find documentation for this module with the perldoc command.
482              
483             perldoc WebService::DataDog
484              
485              
486             You can also look for information at:
487              
488             =over 4
489              
490             =item * Github Bug/Issue tracker
491              
492             L
493              
494             =item * AnnoCPAN: Annotated CPAN documentation
495              
496             L
497              
498             =item * CPAN Ratings
499              
500             L
501              
502             =item * MetaCPAN
503              
504             L
505              
506             =back
507              
508              
509             =head1 ACKNOWLEDGEMENTS
510              
511             I originally developed this project for ThinkGeek (L).
512             Thanks for allowing me to open-source it!
513              
514             Special thanks for architecture advice, and code contributions, from Guillaume
515             Aubert L.
516              
517             =head1 COPYRIGHT & LICENSE
518              
519             Copyright 2015 Jennifer Pinkham.
520              
521             This program is free software: you can redistribute it and/or modify it under
522             the terms of the GNU General Public License version 3 as published by the Free
523             Software Foundation.
524              
525             This program is distributed in the hope that it will be useful, but WITHOUT ANY
526             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
527             PARTICULAR PURPOSE. See the GNU General Public License for more details.
528              
529             You should have received a copy of the GNU General Public License along with
530             this program. If not, see http://www.gnu.org/licenses/
531              
532             =cut
533              
534             1;