File Coverage

blib/lib/Geneos/API.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Geneos::API - Handy Perl interface to ITRS Geneos XML-RPC Instrumentation API
4              
5             =head1 VERSION
6              
7             Version 1.00
8              
9             =head1 SYNOPSIS
10              
11             use Geneos::API;
12              
13             # open API to NetProbe running on host example.com and port 7036
14             my $api = Geneos::API->new("http://example.com:7036/xmlrpc");
15              
16             # get the sampler "Residents" in the managed entity "Zoo"
17             my $sampler = $api->get_sampler("Zoo", "Residents");
18              
19             # create view "Monkeys" in the group "Locals"
20             my $view = $sampler->create_view("Monkeys", "Locals");
21              
22             # prepare some data
23             my $monkeys = [
24             ["Name", "Type" ],
25             ["Funky", "Red-tailed monkey"],
26             ["Cheeky", "Tibetan macaque" ]
27             ];
28              
29             # populate the view
30             $view->update_entire_table($monkeys);
31              
32             # get stream "News" on sampler "Channels" in the managed entity "Zoo"
33             my $stream = $api->get_sampler("Zoo","Channels")->get_stream("News");
34              
35             # add a message to the stream
36             $stream->add_message("Funky beats Cheeky in a chess boxing match!");
37              
38             =head1 DESCRIPTION
39              
40             C is a Perl module that implements ITRS XML-RPC Instrumentation API.
41             It can be used to create clients for both Geneos API and API Steams plug-ins.
42             The plug-in acts as an XML-RPC server.
43              
44             Geneos C, C and C are represented by instances of C, C and C classes.
45             This provides easy to use building blocks for developing monitoring applications.
46              
47             This module comes with its own XML-RPC module based on L as ITRS implementation of XML-RPC does not conform to the XML-RPC standard and therefore most of the available XML-RPC modules cannot be used. The client uses L and gives access to all the available constructor options provided by L.
48              
49             The module also provides customizable error and debug hanlders.
50              
51             =head1 INSTALLATION
52              
53             One of the easiest ways is to run:
54              
55             perl -MCPAN -e'install Geneos::API'
56              
57             This will download and install the latest production version available from CPAN.
58              
59             Alternatively, use any other method that suits you.
60              
61             =head1 METHODS
62              
63             =head2 Constructor
64              
65             =head3 C
66              
67             $api->new($url)
68             $api->new($url, $options)
69              
70             C<$url> is required and must be in the format:
71              
72             C
73              
74             For example:
75              
76             my $api = Geneos::API->new("http://localhost:7036/xmlrpc");
77              
78             XML-RPC Client is initialized upon call to the API constructor
79              
80             =head3 Options
81              
82             The constructor accepts a reference to the options hash as optional second parameter:
83              
84             my $api = Geneos::API->new("http://localhost:7036/xmlrpc", {
85             api => {
86             # XML-RPC API options:
87             raise_error => 1,
88             },
89             ua => {
90             # UserAgent options:
91             keep_alive => 20,
92             timeout => 60,
93             },
94             });
95              
96             =head4 B - XML-RPC options
97              
98             =over
99              
100             =item * C<< raise_error >>
101              
102             Force errors to raise exceptions via C
103              
104             =item * C<< print_error >>
105              
106             Force errors to raise warnings via C
107              
108             =item * C<< error_handler >>
109              
110             Custom error handler. See L section for more details.
111              
112             =item * C<< debug_handler >>
113              
114             Debug handler. See L section for more details.
115              
116             =back
117              
118             The order of precedence for error handling is as follows:
119              
120             =over
121              
122             =item * C<< error_handler >>
123              
124             =item * C<< raise_error >>
125              
126             =item * C<< print_error >>
127              
128             =back
129              
130             If neither is set, the errors won't be reported and L method will need to be called to check if the latest call generated an error or not.
131              
132             Example
133              
134             # force errors to raise exceptions:
135             my $api = Geneos::API->new("http://example.com:7036/xmlrpc", {api=>{raise_error=>1,},});
136              
137             =head4 B - UserAgent options
138              
139             =over
140              
141             =item * C<< any options supported by L >>
142              
143             =back
144              
145             If no LWP::UserAgent options are passed to the constructor, the keep alive will be enabled with the total capacity of 10. In other words, the two calls below are identical:
146              
147             $api = Geneos::API->new("http://localhost:7036/xmlrpc")
148              
149             # is identical to
150             $api = Geneos::API->new("http://localhost:7036/xmlrpc", {
151             ua => {
152             keep_alive => 10,
153             },
154             });
155              
156             # but different to (keep alive disabled):
157             $api = Geneos::API->new("http://localhost:7036/xmlrpc", {
158             ua => {},
159             });
160              
161             Note that if you pass the LWP::UserAgent options, the keep alive default won't be applied:
162              
163             # keep alive is not enabled
164             $api = Geneos::API->new("http://localhost:7036/xmlrpc", {
165             ua => {
166             timeout => 300,
167             },
168             });
169              
170             Examples:
171              
172             # sets http timeout to 30 seconds and implicitly disables keep alive:
173             $api = Geneos::API->new("http://example.com:7036/xmlrpc", {
174             ua => {
175             timeout=>30,
176             },
177             });
178              
179             # sets the agent name to "geneos-client/1.00"
180             $api = Geneos::API->new("http://example.com:7036/xmlrpc", {
181             ua => {
182             agent=>"geneos-client/1.00",
183             },
184             });
185              
186             =head2 API and API Streams Function Calls
187              
188             There are three classes that represent Samplers, Views and Streams.
189              
190             Samplers are represented by the internal C class.
191             First, a sampler object must be created using C method:
192              
193             =head3 C
194              
195             $api->get_sampler($managed_entity, $sampler_name)
196             $api->get_sampler($managed_entity, $sampler_name, $type_name)
197              
198             This method doesn't check whether the sampler exists. Use C<$type_name> parameter only if the sampler is a part of that type
199              
200             Returns sampler object.
201              
202             $sampler = $api->get_sampler($managed_entity, $sampler_name, $type_name)
203              
204             This will create a Sampler object representing a sampler with the name C<$sampler_name> in the managed entity C<$managed_entity>. You can call any method from the section L on this object.
205              
206             To reference samplers which are part of a type, use $type_name parameter:
207              
208             # This will get sampler "Monkeys" in type "Animals" on managed entity "Zoo":
209             $sampler_in_type = $api->get_sampler("Zoo", "Monkeys", "Animals")
210              
211             # If the sampler is assigned directly to managed entity:
212             $sampler = $api->get_sampler("Zoo", "Monkeys")
213              
214             Views are represented by the internal C class.
215             In order to create an instance of this class, you can use:
216              
217             # if the view already exists
218             $view = $sampler->get_view($view_name, $group_heading)
219              
220             # if the view does not exist yet and you want to create it
221             $view = $sampler->create_view($view_name, $group_heading)
222              
223             Once the view object is created, you can call any of the "View methods" on it.
224              
225             Streams are represented by the internal C class. In order to create an instance of this class, you can use:
226              
227             $stream = $sampler->get_stream($stream_name)
228              
229             Once the object is created, you can call any of the L on it.
230              
231             =head2 Sampler methods
232              
233             =head3 C
234              
235             $sampler->get_stream($stream_name)
236              
237             The stream must already exist. This method will NOT check that the stream extists or not.
238              
239             Returns an object representing the stream C<$stream_name>.
240              
241             =head3 C
242              
243             $sampler->create_view($view_name, $group_heading)
244              
245             Creates a new, empty view C<$view_name> in the specified sampler under the specified C<$group_heading>.
246             This method will create a view and returns the object representing it. An error will be produced if the view already exists.
247              
248             Returns C on successful completion.
249              
250             =head3 C
251              
252             $sampler->get_view($view_name, $group_heading)
253              
254             The view must already exist. This method will NOT check that the view extists or not.
255             Use L method for that.
256              
257             Returns an object representing the view C<$view_name>.
258              
259             =head3 C
260              
261             $sampler->view_exists($view_name, $group_heading)
262              
263             Checks whether a particular view exists in this sampler.
264              
265             Returns C<1> if the view exists, C<0> otherwise.
266              
267             =head3 C
268              
269             $sampler->remove_view($view_name)
270              
271             Removes a view that has been created with create_view.
272              
273             Returns C on successful completion.
274              
275             =head3 C
276              
277             $sampler->get_parameter($parameter_name)
278              
279             Retrieves the value of a sampler parameter that has been defined in the gateway configuration.
280              
281             Returns the parameter text written in the gateway configuration.
282              
283             =head3 C
284              
285             $sampler->sign_on($period)
286              
287             $period - The maximum time between updates before samplingStatus becomes FAILED
288              
289             Commits the API client to provide at least one heartbeat or update to the view within the time period specified.
290              
291             Returns C on successful completion.
292              
293             =head3 C
294              
295             $sampler->sign_off()
296              
297             Cancels the commitment to provide updates to a view.
298              
299             Returns C on successful completion.
300              
301             =head3 C
302              
303             $sampler->heartbeat()
304              
305             Prevents the sampling status from becoming failed when no updates are needed to a view and the client is signed on.
306              
307             Returns C on successful completion.
308              
309             =head2 View methods
310              
311             =head3 C
312              
313             $view->add_table_row($row_name,$data)
314              
315             Adds a new, table row to the specified view and populates it with data.
316              
317             Returns C on successful completion.
318              
319             =head3 C
320              
321             $view->remove_table_row($row_name)
322              
323             Removes an existing row from the specified view.
324              
325             Returns C on successful completion.
326              
327             =head3 C
328              
329             $view->add_headline($headline_name)
330              
331             Adds a headline variable to the view.
332              
333             Returns C on successful completion.
334              
335             =head3 C
336              
337             $view->remove_headline($headline_name)
338              
339             Removes a headline variable from the view.
340              
341             Returns C on successful completion.
342              
343             =head3 C
344              
345             $view->update_variable($variable_name, $new_value)
346              
347             Can be used to update either a headline variable or a table cell.
348             If the variable name contains a period (.) then a cell is assumed, otherwise a headline variable is assumed.
349              
350             Returns C on successful completion.
351              
352             =head3 C
353              
354             $view->update_headline($headline_name, $new_value)
355              
356             Updates a headline variable.
357              
358             Returns C on successful completion.
359              
360             =head3 C
361              
362             $view->update_table_cell($cell_name, $new_value)
363              
364             Updates a single cell in a table. The standard C format should be used to reference a cell.
365              
366             Returns C on successful completion.
367              
368             =head3 C
369              
370             $view->update_table_row($row_name, $new_value)
371              
372             Updates an existing row from the specified view with the new values provided.
373              
374             Returns C on successful completion.
375              
376             =head3 C
377              
378             $view->add_table_column($column_name)
379              
380             Adds another column to the table.
381              
382             Returns C on successful completion.
383              
384             =head3 C
385              
386             $view->update_entire_table($new_table)
387              
388             Updates the entire table for a given view. This is useful if the entire table will change at once or the table is being created for the first time.
389             The array passed should be two dimensional. The first row should be the column headings and the first column of each subsequent row should be the name of the row.
390             The array should be at least 2 columns by 2 rows. Once table columns have been defined, they cannot be changed by this method.
391              
392             Returns C on successful completion.
393              
394             =head3 C
395              
396             $view->column_exists($column_name)
397              
398             Check if the headline variable exists.
399              
400             Returns C<1> if the column exists, C<0> otherwise.
401              
402             =head3 C
403              
404             $view->row_exists($row_name)
405              
406             Check if the headline variable exists.
407              
408             Returns C<1> if the row exists, C<0> otherwise.
409              
410             =head3 C
411              
412             $view->headline_exists($headline_name)
413              
414             Check if the headline variable exists.
415              
416             Returns C<1> if the headline variable exists, C<0> otherwise.
417              
418             =head3 C
419              
420             $view->get_column_count()
421              
422             Return the column count of the view.
423              
424             Returns the number of columns in the view. This includes the rowName column.
425              
426             =head3 C
427              
428             $view->get_row_count()
429              
430             Return the headline count of the view.
431              
432             Returns the number of headlines in the view. This includes the C headline.
433              
434             =head3 C
435              
436             $view->get_headline_count()
437              
438             Returns the number of headlines in the view. This includes the C headline.
439              
440             =head3 C
441              
442             $view->get_column_names()
443              
444             Returns the names of existing columns in the view. This includes the rowNames column name.
445              
446             =head3 C
447              
448             $view->get_row_names()
449              
450             Returns the names of existing rows in the view
451              
452             =head3 C
453              
454             $view->get_headline_names()
455              
456             Returns the names of existing headlines in the view.
457             This includes the C headline.
458              
459             =head3 C
460              
461             $view->get_row_names_older_than($timestamp)
462              
463             C<$timestamp> - The timestamp against which to compare row update time.
464             The timestamp should be provided as Unix timestamp, i.e. number of seconds elapsed since UNIX epoch.
465              
466             Returns the names of rows whose update time is older than the time provided.
467              
468             =head2 Stream methods
469              
470             =head3 C
471              
472             $stream->add_message($message)
473              
474             Adds a new message to the end of the stream.
475              
476             Returns C on successful completion.
477              
478             =head2 NetProbe Function Calls
479              
480             =head3 C
481              
482             $api->managed_entity_exists($managed_entity)
483              
484             Checks whether a particular Managed Entity exists on this NetProbe containing any API or API-Streams samplers.
485              
486             Returns C<1> if the Managed Entity exists, C<0> otherwise
487              
488             =head3 C
489              
490             $api->sampler_exists($managed_entity, $sampler_name)
491             $api->sampler_exists($managed_entity, $sampler_name, $type_name)
492              
493             Checks whether a particular API or API-Streams sampler exists on this NetProbe
494              
495             Returns C<1> if sampler exists, C<0> otherwise
496              
497             If the sampler in the question is part of the type - use the $type_name parameter.
498             See examples for L method
499              
500             =head3 C
501              
502             $api->gateway_connected()
503              
504             Checks whether the Gateway is connected to this NetProbe
505              
506             Returns C<1> if the Gateway is connected, C<0> otherwise
507              
508             =head2 Gateway Function Calls
509              
510             =head3 C
511              
512             $api->add_managed_entity($managed_entity, $data_section)
513              
514             Adds the managed entity to the particular data section
515              
516             Returns C<1> on success, C<0> otherwise
517              
518             =head2 Error handling
519              
520             =head3 C
521              
522             $api->raise_error()
523              
524             Get the raise_error attribute value
525              
526             Returns C<1> is the raise_error attribute is set or C<0> otherwise
527              
528             If the raise_error attribute is set, errors generated by API calls will be passed to C
529              
530             =head3 C
531              
532             $api->remove_raise_error()
533              
534             Remove the raise_error attribute
535              
536             =head3 C
537              
538             $api->print_error()
539              
540             Get the print_error attribute value
541              
542             Returns C<1> is the print_error attribute is set or C<0> othersise
543              
544             If the print_error attribute is set, errors generated by API calls will be passed to C
545              
546             print_error attribute is ignored if raise_error is set.
547              
548             =head3 C
549              
550             $api->remove_print_error()
551              
552             Remove the print_error attribute
553              
554             =head3 C
555              
556             $api->status_line()
557              
558             Returns the string C<< >>. Returns C if there is no error.
559              
560             =head3 C
561              
562             $api->error
563              
564             Get the error produced by the last api call.
565              
566             Returns reference to the error hash or undef if the last call produced no error.
567             The hash contains three elements:
568              
569             =over
570              
571             =item * code
572              
573             HTTP or XML-RPC error code.
574              
575             =item * message
576              
577             Error string.
578              
579             =item * class
580              
581             The component that produced the error: C or C.
582              
583             =back
584              
585             Example
586              
587             my $e = $api->error;
588             printf("code: %d\nmessage: %s\n", $e->{code}, $e->{message});
589              
590             # example output:
591             code: 202
592             message: Sampler does not exist
593              
594             =head3 C
595              
596             $api->error_handler()
597              
598             Allows you to provide your own behaviour in case of errors.
599              
600             The handler must be passed as a reference to subroutine and it could be done as a constructor option:
601              
602             my $api = Geneos::API->new("http://localhost:7036/xmlrpc", {
603             api => { error_handler => \&my_error_handler, },
604             });
605              
606             or via a separate method:
607              
608             $api->error_handler(\&my_error_handler)
609              
610             The subroutine is called with two parameters: reference to the error hash and the api object itself.
611              
612             For example, to die with a full stack trace for any error:
613              
614             use Carp;
615             $api->error_handler( sub { confess("$_[0]->{code} $_[0]->{message}") } );
616              
617             Please note that the custom error handler overrides the raise_error and print_error settings.
618              
619             The error handler can be removed by calling:
620              
621             $api->remove_error_handler()
622              
623             =head2 Debugging
624              
625             The module comes with a debug handler. The handler must be passed as a reference to subroutine and it could be done as a constructor option:
626              
627             my $api = Geneos::API->new("http://localhost:7036/xmlrpc", {
628             api => { debug_handler => \&my_debug_handler, },
629             });
630              
631             # or via a separate method:
632             $api->debug_handler(\&my_debug_handler)
633              
634             The subroutine is called with one parameter: C object.
635              
636             The following C methods might be useful for debugging purposes:
637              
638             =over 4
639              
640             =item * C
641              
642             Returns the time at the start of the request. It's captured using Time::HiRes::gettimeofday
643             method: C<$t0 = [gettimeofday]>
644              
645             =item * C
646              
647             Returns the C object.
648              
649             =item * C
650              
651             Returns the C object.
652              
653             =item * C
654              
655             Returns the C object. See L for more details.
656              
657             =item * C
658              
659             Returns the C object. See L for more details.
660              
661             =back
662              
663             The debug handler can be removed by calling:
664              
665             $api->remove_debug_handler()
666              
667             Example.
668              
669             The custom debug handler in this example will output the following stats:
670              
671             =over 4
672              
673             =item * Elapsed time
674              
675             =item * HTTP request headers
676              
677             =item * HTTP response headers
678              
679             =back
680              
681             use Time::HiRes qw(tv_interval);
682              
683             $api->debug_handler(\&custom_debug_handler);
684              
685             sub custom_debug_handler {
686             my $api_obj = shift;
687              
688             printf "# elapsed time: %f\n\n# request header:\n%s\n# response header:\n%s\n",
689             tv_interval($api_obj->t0),
690             $api_obj->http_request->headers_as_string,
691             $api_obj->http_response->headers_as_string;
692             }
693              
694             Upon execution, it will produce output similar to:
695              
696             # elapsed time: 0.002529
697              
698             # request header:
699             User-Agent: libwww-perl/6.04
700             Content-Type: text/xml
701              
702             # response header:
703             Connection: Keep-Alive
704             Server: GENEOS XML-RPC
705             Content-Length: 152
706             Content-Type: text/xml
707             Client-Date: Fri, 26 Dec 2014 16:18:10 GMT
708             Client-Peer: 127.0.0.1:7036
709             Client-Response-Num: 1
710              
711             =head1 EXAMPLE
712              
713             This is a Perl version of the C++/Java example from the ITRS documentation.
714              
715             #!/usr/bin/perl
716              
717             use strict;
718             use warnings;
719              
720             use Geneos::API;
721              
722             unless (@ARGV == 2) {
723             warn "Usage: QueueSamplerClient serverHost serverPort\n";
724             exit -1;
725             }
726              
727             my ($host,$port) = @ARGV;
728              
729             my $api = Geneos::API->new("http://$host:$port/xmlrpc",{api=>{raise_error=>1,},});
730              
731             my $sampler = $api->get_sampler("myManEnt","mySampler");
732             my $view = $sampler->create_view("queues","myGroup");
733              
734             $view->add_headline("totalQueues");
735             $view->add_headline("queuesOffline");
736              
737             my $table = [
738             ["queueName","currentSize","maxSize","currentUtilisation","status"],
739             ["queue1",332,30000,"0.11","online"],
740             ["queue2",0,90000,"0","offline"],
741             ["queue3",7331,45000,"0.16","online"]
742             ];
743              
744             $view->update_entire_table($table);
745             $view->update_headline("totalQueues",3);
746             $view->update_headline("queuesOffline",1);
747              
748             for(1..1000) {
749             $view->update_table_cell("queue2.currentSize",$_);
750             sleep 1;
751             }
752              
753             To run this example: setup the managed entity and the sampler as per the instructions given in the ITRS documentation, save this code as I, make it executable and run:
754              
755             ./QueueSamplerClient localhost 7036
756              
757             This assumes that the NetProbe runs on the localhost and port 7036
758              
759             =head1 ONLINE RESOURCES AND SUPPORT
760              
761             =over 4
762              
763             =item * L
764              
765             =item * L
766              
767             =item * Drop me an email if you have any questions with Geneos::API in the subject
768              
769             =back
770              
771             =head1 KNOWN ISSUES
772              
773             Few issues have been discovered while testing ITRS Instrumentation API.
774             These issues are not caused by Geneos::API Perl module but ITRS implementation of the XML-RPC interface to Geneos.
775              
776             =over 4
777              
778             =item * Memory leak in the netprobe
779              
780             Memory leak occurs when data view is removed via entity.sampler.removeView call
781              
782             One way to reproduce this issue is to perform a serious of calls:
783              
784             ...
785             entity.sampler.removeView
786             entity.sampler.createView
787             entity.sampler.view.updateEntireTable
788             ...
789              
790             The memory usage by the NetProbe process grows almost linear when the data view size is constant.
791              
792             =item * Invalid parameters passed to XML-RPC method can crash netprobe
793              
794             An entity.sampler.UpdateEntireTable call with a scalar parameter instead of 2 dimensional array crashes the NetProbe:
795              
796            
797            
798             entity.sampler.group-view.updateEntireTable
799            
800            
801             scalar instead of array
802            
803            
804            
805              
806             =back
807              
808             Please contact ITRS directly for the latest status.
809              
810             =head1 BUGS
811              
812             Of course. Please raise a ticket via L
813              
814             =head1 AUTHOR
815              
816             Ivan Dmitriev, Etot@cpan.orgE
817              
818             =head1 COPYRIGHT AND LICENSE
819              
820             Copyright (C) 2015 by Ivan Dmitriev
821              
822             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
823              
824             =cut
825              
826             ###############################################################
827             #
828             # package Geneos::API::XMLRPC::Response
829             #
830             # Parses XML-RPC response and converts it into Perl structure
831             #
832             ###############################################################
833              
834             package Geneos::API::XMLRPC::Response;
835              
836 1     1   17155 use strict;
  1         3  
  1         60  
837              
838 1     1   285 use XML::LibXML qw(:libxml);
  0            
  0            
839              
840             # -----------
841             # Constructor
842              
843             sub new {
844             my $this = shift;
845             my $class = ref($this) || $this;
846             my $self = {
847             _response => {},
848             _error => undef,
849             };
850              
851             bless $self, $class;
852             $self->_init(@_);
853             }
854              
855             # ---------------
856             # Private methods
857              
858             sub _init {
859             my ($self, $response) = @_;
860              
861             # Check if the HTTP request succeeded
862             if ($response->is_success) {
863              
864             my $dom = XML::LibXML->load_xml(string => $response->decoded_content);
865             process_node($self->{_response}, $dom->documentElement);
866              
867             if (exists $self->{_response}{fault}) {
868             my $code = exists $self->{_response}{fault}{faultCode}
869             ? $self->{_response}{fault}{faultCode}
870             : -1;
871              
872             my $str = exists $self->{_response}{fault}{faultString}
873             ? $self->{_response}{fault}{faultString}
874             : 'NO_ERROR_STRING';
875              
876             $self->error({class=>"XML-RPC", code=>$code, message=>$str,});
877             }
878              
879             }
880             else {
881             $self->error({class=>"HTTP", code=>$response->code, message=>$response->message,});
882             }
883              
884             return $self;
885             }
886              
887             # --------------
888             # Public methods
889              
890             sub is_success {!shift->error}
891              
892             sub params {shift->{_response}{params}}
893              
894             sub error {
895             my ($self, $error) = @_;
896             $self->{_error} = $error if $error;
897              
898             return $self->{_error};
899             }
900              
901             # ---------------
902             # Response parser
903              
904             sub process_node {
905             my ($r, $node) = @_;
906              
907             for my $child ($node->childNodes) {
908              
909             if ($child->nodeName eq "struct") {
910             process_struct($r, $child);
911             }
912             elsif ($child->nodeName eq "fault") {
913             process_fault($r, $child);
914             }
915             elsif ($child->nodeName eq "params") {
916             process_params($r, $child);
917             }
918             elsif ($child->nodeName eq "array") {
919             process_array($r, $child);
920             }
921             elsif ($child->nodeName =~ m/^i4|int|boolean|string|double|dateTime\.iso8601|base64$/) {
922             $$r = $child->textContent;
923             }
924             elsif ($child->nodeType == 3
925             && $node->nodeName eq "value"
926             && $node->childNodes->size == 1
927             ) {
928             $$r = $child->textContent;
929             }
930             else {
931             process_node($r, $child);
932             }
933             }
934             }
935              
936             sub process_fault {
937             my ($r, $node) = @_;
938              
939             my ($value) = $node->findnodes("./value");
940              
941             process_node(\$r->{fault}, $value);
942             }
943              
944             sub process_struct {
945             my ($r, $node) = @_;
946              
947             foreach my $member ( $node->findnodes("./member") ) {
948             my ($name) = $member->findnodes("./name");
949             my ($value) = $member->findnodes("./value");
950              
951             process_node(\$$r->{$name->textContent}, $value);
952             }
953             }
954              
955             sub process_array {
956             my ($r, $node) = @_;
957              
958             foreach my $value ( $node->findnodes("./data/value") ) {
959             process_node(\$$r->[++$#{$$r}], $value);
960             }
961             }
962              
963             sub process_params {
964             my ($r, $node) = @_;
965              
966             $r->{params} = [];
967              
968             foreach my $param ( $node->findnodes("./param") ) {
969             my ($value) = $param->findnodes("./value");
970             process_node(\$r->{params}[++$#{$r->{params}}], $value);
971             }
972             }
973              
974             ###########################################
975             #
976             # package Geneos::API::XMLRPC::Request
977             #
978             # Converts method and Perl data structure
979             # into an XML-RPC request body
980             #
981             ###########################################
982              
983             package Geneos::API::XMLRPC::Request;
984              
985             use XML::LibXML;
986              
987             # -----------
988             # Constructor
989              
990             sub new {
991             my $this = shift;
992             my $class = ref($this) || $this;
993             my $self = {};
994             bless $self, $class;
995             $self->_init(@_);
996             }
997              
998             # ---------------
999             # Private methods
1000              
1001             sub _init {
1002             my ($self, $method, @params) = @_;
1003              
1004             # remember the method and params
1005             $self->{_method} = $method;
1006             $self->{_params} = \@params;
1007              
1008             $self->{doc} = XML::LibXML::Document->new('1.0', 'utf-8');
1009              
1010             my $root = $self->{doc}->createElement("methodCall");
1011             $self->{doc}->setDocumentElement($root);
1012              
1013             # ------------------
1014             # Add the methodName
1015              
1016             my $methodName = $self->{doc}->createElement("methodName");
1017             $methodName->appendTextNode($method);
1018             $root->appendChild($methodName);
1019              
1020             # --------------
1021             # Add the params
1022             my $params = $self->{doc}->createElement("params");
1023             $root->appendChild($params);
1024              
1025             # ---------------------
1026             # Process the agruments
1027             foreach (@params) {
1028             my $param = $self->{doc}->createElement("param");
1029             $params->addChild($param);
1030             $self->parse($param, $_);
1031             }
1032              
1033             return $self;
1034             }
1035              
1036             # --------------
1037             # Public methods
1038              
1039             # accessor for the method
1040             sub method {shift->{_method}}
1041              
1042             # accessor for the params
1043             sub params {shift->{_params}}
1044              
1045             sub content {shift->{doc}->toString}
1046              
1047             sub parse {
1048             my ($self, $node, $p) = @_;
1049              
1050             my $value = $self->{doc}->createElement("value");
1051             $node->addChild($value);
1052              
1053             if ( ref($p) eq 'HASH' ) {
1054             $self->parse_hash($value,$p);
1055             }
1056             elsif ( ref($p) eq 'ARRAY' ) {
1057             $self->parse_array($value,$p);
1058             }
1059             elsif ( ref($p) eq 'CODE' ) {
1060             $self->parse_code($value,$p);
1061             }
1062             else {
1063             $self->parse_scalar($value,$p);
1064             }
1065             }
1066              
1067             # It seems that Geneos treats everything as a string
1068             # no need for anything sophisticated here
1069              
1070             sub parse_scalar {
1071             my ($self, $node, $scalar) = @_;
1072              
1073             $scalar ||= "";
1074              
1075             if (( $scalar =~ m/^[\-+]?\d+$/) && (abs($scalar) <= (0xffffffff >> 1))) {
1076             my $i = $self->{doc}->createElement("i4");
1077             $i->appendTextNode($scalar);
1078             $node->appendChild($i);
1079             }
1080             elsif ( $scalar =~ m/^[\-+]?\d+\.\d+$/ ) {
1081             my $d = $self->{doc}->createElement("double");
1082             $d->appendTextNode($scalar);
1083             $node->appendChild($d);
1084             }
1085             else {
1086             my $s = $self->{doc}->createElement("string");
1087             $s->appendTextNode($scalar);
1088             $node->appendChild($s);
1089             }
1090             }
1091              
1092             sub parse_hash {
1093             my ($self, $node, $hash) = @_;
1094              
1095             my $struct = $self->{doc}->createElement("struct");
1096             $node->appendChild($struct);
1097              
1098             foreach (keys %$hash) {
1099             my $member = $self->{doc}->createElement("member");
1100             $struct->appendChild($member);
1101              
1102             my $name = $self->{doc}->createElement("name");
1103             $name->appendTextNode($_);
1104             $member->appendChild($name);
1105              
1106             $self->parse($member, $hash->{$_});
1107             }
1108             }
1109              
1110             sub parse_array {
1111             my ($self, $node, $args) = @_;
1112              
1113             my $array = $self->{doc}->createElement("array");
1114             $node->appendChild($array);
1115              
1116             my $data = $self->{doc}->createElement("data");
1117             $array->appendChild($data);
1118              
1119             $self->parse($data, $_) for @$args;
1120             }
1121              
1122             sub parse_code {
1123             my ($self, $node, $code) = @_;
1124              
1125             my ($type, $data) = $code->();
1126              
1127             my $e = $self->{doc}->createElement($type);
1128             $e->appendTextNode($data);
1129             $node->appendChild($e);
1130             }
1131              
1132             ########################################################################
1133             #
1134             # package Geneos::API::XMLRPC
1135             #
1136             # XML-RPC client
1137             # The reason for yet another XML-RPC implementation is that
1138             # because Geneos XML-RPC does not conform to the XML-RPC standard:
1139             #
1140             # * '-', '(' and ')' characters may be used in the method names
1141             # * the values do not default to type 'string'
1142             #
1143             # Among other reasons, ensuring that HTTP1.1 is used to take advantage
1144             # of the keep alive feature supported by Geneos XML-RPC server
1145             #
1146             ########################################################################
1147              
1148             package Geneos::API::XMLRPC;
1149              
1150             use LWP::UserAgent;
1151             use Time::HiRes qw(gettimeofday);
1152              
1153             # -----------
1154             # Constructor
1155              
1156             sub new {
1157             my $this = shift;
1158              
1159             my $class = ref($this) || $this;
1160             my $self = {};
1161             bless $self, $class;
1162              
1163             $self->_init(@_);
1164             }
1165              
1166             # ---------------
1167             # Private methods
1168              
1169             sub _init {
1170             my ($self, $url, $opts) = @_;
1171              
1172             $self->{_url} = $url;
1173              
1174             $opts ||= {};
1175             $opts->{ua} ||= {};
1176              
1177             # set up the UserAgent
1178             $self->{_ua} = LWP::UserAgent->new(%{$opts->{ua}});
1179              
1180             return $self;
1181             }
1182              
1183             # --------------
1184             # Public methods
1185              
1186             sub request {
1187             my ($self, $method, @params) = @_;
1188              
1189             # record the start time
1190             $self->{_t0} = [gettimeofday];
1191              
1192             # prepare the XML-RPC request
1193             $self->{_xmlrpc_request} = Geneos::API::XMLRPC::Request->new($method, @params);
1194              
1195             # create an http request
1196             $self->{_http_request} = HTTP::Request->new("POST",$self->{_url});
1197              
1198             $self->{_http_request}->header('Content-Type' => 'text/xml');
1199             $self->{_http_request}->add_content_utf8($self->{_xmlrpc_request}->content);
1200              
1201             # send the http request
1202             $self->{_http_response} = $self->{_ua}->request($self->{_http_request});
1203              
1204             # parse the http response
1205             $self->{_xmlrpc_response} = Geneos::API::XMLRPC::Response->new($self->{_http_response});
1206             }
1207              
1208             # the LWP::UserAgent object
1209             sub user_agent {shift->{_ua}}
1210              
1211             # --------------------------------------
1212             # These methods are useful for debugging
1213             #
1214              
1215             # Request start time (epoch seconds)
1216             sub t0 {shift->{_t0}}
1217              
1218             # XML-RPC request: instance of Geneos::API::XMLRPC::Request
1219             sub xmlrpc_request {shift->{_xmlrpc_request}}
1220              
1221             # XML-RPC response: instance of Geneos::API::XMLRPC::Response
1222             sub xmlrpc_response {shift->{_xmlrpc_response}}
1223              
1224             # HTTP request: instance of HTTP::Request
1225             sub http_request {shift->{_http_request}}
1226              
1227             # HTTP response: instance of HTTP::Response
1228             sub http_response {shift->{_http_response}}
1229              
1230             ###########################################################################
1231             # package Geneos::API::Base #
1232             # #
1233             # This base class implements error handling and interface to #
1234             # Geneos::API::XMLRPC that is used by Geneos::API, Geneos::API::Sampler, #
1235             # Geneos::API::Sampler::View and Geneos::API::Sampler::Stream classes #
1236             # #
1237             ###########################################################################
1238              
1239             package Geneos::API::Base;
1240              
1241             use Carp;
1242              
1243             our $VERSION = '1.00';
1244              
1245             sub new {bless({_error=>undef,}, shift)->_init(@_)}
1246              
1247             sub status_line {
1248             my $self = shift;
1249              
1250             if ($self->{_error}) {
1251             my $code = $self->{_error}->{code} || '000';
1252             my $message = $self->{_error}->{message} || 'Empty';
1253             return "$code $message";
1254             }
1255             else {
1256             return undef;
1257             }
1258             }
1259              
1260             sub call {
1261             my ($self, $method, @params) = @_;
1262              
1263             $self->_reset_error;
1264              
1265             # send the XMLRPC request to the NetProbe
1266             my $response = $self->api->request($self->_method($method), @params);
1267              
1268             # debug handler is passed the xmlrpc object
1269             $self->api->{_debug_handler}->($self->api->xmlrpc) if $self->api->{_debug_handler};
1270              
1271             # check the response
1272             if ($response->is_success) {
1273             $response->params->[0];
1274             }
1275             else {
1276             $self->_handle_error($response->error);
1277             }
1278             }
1279              
1280             sub error {shift->{_error}}
1281              
1282             sub _error {
1283             my ($self, $error) = @_;
1284             $self->{_error} = $error if $error;
1285              
1286             return $self->{_error};
1287             }
1288              
1289             sub _handle_error {
1290             my ($self, $error) = @_;
1291              
1292             # check if there is an error to handle
1293             unless (ref($error) eq 'HASH') {
1294             $error = {
1295             class => '_INTERNAL',
1296             code => '999',
1297             message => "Expected hashref but received '$error' instead",
1298             };
1299             }
1300              
1301             # record the error
1302             $self->_error($error);
1303              
1304             # execute the error handler code
1305             $self->api->{_error_handler}->($error, $self) if $self->api->{_error_handler};
1306              
1307             # always return undef
1308             return;
1309             }
1310              
1311             sub _reset_error {shift->{_error} = undef}
1312              
1313             #######################################
1314             #
1315             # package Geneos::API::Sampler::Stream
1316             #
1317             # Implements all Steam methods
1318             #
1319             #######################################
1320              
1321             package Geneos::API::Sampler::Stream;
1322              
1323             use base 'Geneos::API::Base';
1324             use Carp;
1325              
1326             # ---------------
1327             # Private methods
1328              
1329             sub _init {
1330             my ($self, $sampler, $stream) = @_;
1331              
1332             croak "Geneos::API::Sampler::Stream->new was called without SAMPLER!" unless $sampler;
1333             croak "Geneos::API::Sampler::Stream->new was called without STREAM!" unless $stream;
1334              
1335             $self->{_sampler} = $sampler;
1336             $self->{_stream} = $stream;
1337              
1338             return $self;
1339             }
1340              
1341             sub _method {
1342             my $self = shift;
1343             join(".", $self->{_sampler}->entity, $self->{_sampler}->sampler, $self->{_stream}, @_);
1344             }
1345              
1346             # --------------
1347             # Public methods
1348              
1349             sub api {shift->{_sampler}->api}
1350              
1351             # API Streams Function Calls
1352              
1353             sub add_message {shift->call("addMessage", @_)}
1354              
1355             ######################################
1356             #
1357             # package Geneos::API::Sampler::View
1358             #
1359             # Implements all View methods
1360             #
1361             ######################################
1362              
1363             package Geneos::API::Sampler::View;
1364              
1365             use base 'Geneos::API::Base';
1366             use Carp;
1367              
1368             # ---------------
1369             # Private methods
1370              
1371             sub _init {
1372             my ($self, $sampler, $view, $group) = @_;
1373              
1374             croak "Geneos::API::Sampler::View->new was called without SAMPLER!" unless $sampler;
1375             croak "Geneos::API::Sampler::View->new was called without VIEW!" unless $view;
1376             croak "Geneos::API::Sampler::View->new was called without GROUP!" unless $group;
1377              
1378             $self->{_sampler} = $sampler;
1379             $self->{_view} = $view;
1380             $self->{_group} = $group;
1381              
1382             return $self;
1383             }
1384              
1385             sub _method {
1386             my $self = shift;
1387             join(".", $self->{_sampler}->entity, $self->{_sampler}->sampler, "$self->{_group}-$self->{_view}", @_);
1388             }
1389              
1390             # --------------
1391             # Public methods
1392              
1393             sub api {shift->{_sampler}->api}
1394              
1395             # API calls
1396              
1397             # ---------------------------------------------
1398             # Combines addTableRow and updateTableRow calls
1399             #
1400             sub add_table_row {
1401             my ($self, $name, $data) = @_;
1402              
1403             return unless $self->_add_table_row($name);
1404              
1405             # if there is data - add it to the row
1406             $data ? $self->update_table_row($name, $data) : 1;
1407             }
1408              
1409             # -----------------------------------------------------
1410             # Each method below is an XML-RPC call to the NetProbe
1411             #
1412             # The first argument passed to the call method is the
1413             # XML-RPC method name. The rest are parameters passed
1414             # with that call to the XML-RPC server:
1415             #
1416             # method->($method_name, @params)
1417             #
1418              
1419             sub _add_table_row {shift->call("addTableRow", @_)}
1420              
1421             sub remove_table_row {shift->call("removeTableRow", @_)}
1422              
1423             sub add_headline {shift->call("addHeadline", @_)}
1424              
1425             sub remove_headline {shift->call("removeHeadline", @_)}
1426              
1427             sub update_variable {shift->call("updateVariable", @_)}
1428              
1429             sub update_headline {shift->call("updateHeadline", @_)}
1430              
1431             sub update_table_cell {shift->call("updateTableCell", @_)}
1432              
1433             sub update_table_row {shift->call("updateTableRow", @_)}
1434              
1435             sub add_table_column {shift->call("addTableColumn", @_)}
1436              
1437             sub update_entire_table {shift->call("updateEntireTable", @_)}
1438              
1439             sub column_exists {shift->call("columnExists", @_)}
1440              
1441             sub row_exists {shift->call("rowExists", @_)}
1442              
1443             sub headline_exists {shift->call("headlineExists", @_)}
1444              
1445             sub get_column_count {shift->call("getColumnCount")}
1446              
1447             sub get_row_count {shift->call("getRowCount")}
1448              
1449             sub get_headline_count {shift->call("getHeadlineCount")}
1450              
1451             sub get_column_names {shift->call("getColumnNames")}
1452              
1453             sub get_row_names {shift->call("getRowNames")}
1454              
1455             sub get_headline_names {shift->call("getHeadlineNames")}
1456              
1457             sub get_row_names_older_than {shift->call("getRowNamesOlderThan", @_)}
1458              
1459             ##################################
1460             #
1461             # package Geneos::API::Sampler
1462             #
1463             # Implements all sampler methods
1464             #
1465             ##################################
1466              
1467             package Geneos::API::Sampler;
1468              
1469             use base 'Geneos::API::Base';
1470             use Carp;
1471              
1472             # ---------------
1473             # Private methods
1474              
1475             sub _init {
1476             my ($self, $api, $entity, $sampler, $type) = @_;
1477              
1478             croak "Geneos::API::Sampler->new was called without ENTITY!" unless $entity;
1479             croak "Geneos::API::Sampler->new was called without SAMPLER!" unless $sampler;
1480              
1481             $self->{_api} = $api;
1482             $self->{_entity} = $entity;
1483             $self->{_sampler} = $type ? "${sampler}($type)" : $sampler;
1484              
1485             return $self;
1486             }
1487              
1488             # ---------------------------------------------------------
1489             # XML-RPC methodName for the sampler calls looks like this:
1490             # entity.sampler.action
1491             #
1492             # in case the sampler is part of a type, the call becomes:
1493             # entity.sampler(type).action
1494             #
1495              
1496             sub _method {
1497             my $self = shift;
1498             join(".", $self->entity, $self->sampler, @_);
1499             }
1500              
1501             # -------------------------------------
1502             # Public methods
1503              
1504             sub api {shift->{_api}}
1505             sub sampler {shift->{_sampler}}
1506             sub entity {shift->{_entity}}
1507              
1508             sub get_stream {
1509             my $self = shift;
1510             $self->_reset_error;
1511             Geneos::API::Sampler::Stream->new($self, @_)
1512             }
1513              
1514             # -------------------------------------
1515             # returns an instance of the view class
1516              
1517             sub get_view {
1518             my $self = shift;
1519             $self->_reset_error;
1520             Geneos::API::Sampler::View->new($self, @_)
1521             }
1522              
1523             #############
1524             # API calls #
1525             #############
1526              
1527             sub create_view {
1528             my $self = shift;
1529             $self->call("createView", @_) ? Geneos::API::Sampler::View->new($self, @_) : undef;
1530             }
1531              
1532             # -------------------------------------------------------
1533             # Checks whether a particular view exists in this sampler
1534             #
1535             # Returns 1 if the view exists, 0 otherwise
1536             #
1537              
1538             sub view_exists {
1539             my ($self, $view, $group) = @_;
1540             $self->call("viewExists", "${group}-${view}");
1541             }
1542              
1543             sub remove_view {shift->call("removeView", @_)}
1544              
1545             # -----------------------------------------------
1546             # Retrieves the value of a sampler parameter that
1547             # has been defined in the gateway configuration
1548             #
1549             # Returns the parameter text written in the gateway configuration
1550             #
1551              
1552             sub get_parameter {shift->call("getParameter", @_)}
1553              
1554             sub sign_on {shift->call("signOn", @_)}
1555              
1556             sub sign_off {shift->call("signOff")}
1557              
1558             sub heartbeat {shift->call("heartbeat")}
1559              
1560             ######################################
1561             #
1562             # package Geneos::API
1563             #
1564             # Implements the Geneos XML-RPC API
1565             #
1566             ######################################
1567              
1568             package Geneos::API;
1569              
1570             our $VERSION = '1.00';
1571              
1572             use base 'Geneos::API::Base';
1573             use Carp;
1574             use Time::HiRes qw(tv_interval);
1575              
1576             use constant DEFAULT_TOTAL_CAPACITY => 10;
1577              
1578             # ---------------
1579             # Private methods
1580              
1581             sub _init {
1582             my ($self, $url, $opts) = @_;
1583              
1584             # the url must be present
1585             croak "Geneos::API->new was called without URL!" unless $url;
1586              
1587             # if options are passed - it must be a hashref
1588             if ($opts) {
1589             croak "Options for Geneos::API->new must be passed as a HASHREF!" unless ref($opts) eq 'HASH';
1590             }
1591             else {
1592             # init the options
1593             $opts ||= {};
1594             }
1595              
1596             # enable keep alive by default
1597             $opts->{ua} ||= {keep_alive=>DEFAULT_TOTAL_CAPACITY,};
1598              
1599             # no api options are set by default
1600             $opts->{api} ||= {};
1601              
1602             $self->{_xmlrpc} = Geneos::API::XMLRPC->new($url, $opts);
1603             $self->{_opts} = $opts;
1604              
1605             # ----------------------
1606             # init the error handler
1607              
1608             if (ref($opts->{api}{error_handler}) eq 'CODE') {
1609             $self->error_handler($opts->{api}{error_handler});
1610             }
1611             elsif ($opts->{api}{raise_error}) {
1612             $self->error_handler(
1613             sub {croak("$_[0]->{code} $_[0]->{message}")}
1614             );
1615             }
1616             elsif ($opts->{api}{print_error}) {
1617             $self->error_handler(
1618             sub {carp("$_[0]->{code} $_[0]->{message}")}
1619             );
1620             }
1621              
1622             # ----------------------
1623             # init the debug handler
1624              
1625             if ($opts->{api}{debug_handler}) {
1626             $self->debug_handler($opts->{api}{debug_handler});
1627             }
1628              
1629             return $self;
1630             }
1631              
1632             sub _method {shift;@_}
1633              
1634             # --------------
1635             # Public methods
1636              
1637             # ---------------------
1638             # get/set error handler
1639              
1640             sub error_handler {
1641             my ($self, $handler) = @_;
1642              
1643             if (ref($handler) eq 'CODE') {
1644             $self->{_error_handler} = $handler;
1645             }
1646             elsif ($handler) {
1647             carp("argument for error_handler must be a coderef but got: ", ref($handler));
1648             }
1649              
1650             return $self->{_error_handler};
1651             }
1652              
1653             # --------------------
1654             # remove error handler
1655              
1656             sub remove_error_handler {shift->{_error_handler}=undef;}
1657              
1658             # ---------------------
1659             # get/set debug handler
1660              
1661             sub debug_handler {
1662             my ($self, $handler) = @_;
1663              
1664             if (ref($handler) eq 'CODE') {
1665             $self->{_debug_handler} = $handler;
1666             }
1667             elsif ($handler) {
1668             carp("argument for debug_handler must be a coderef but got: ", ref($handler));
1669             }
1670              
1671             return $self->{_debug_handler};
1672             }
1673              
1674             # --------------------
1675             # remove debug handler
1676              
1677             sub remove_debug_handler {shift->{_debug_handler}=undef;}
1678              
1679             sub raise_error {shift->{_opts}{api}{raise_error}}
1680              
1681             sub remove_raise_error {shift->{_opts}{api}{raise_error}=undef;}
1682              
1683             sub print_error {shift->{_opts}{api}{print_error}}
1684              
1685             sub remove_print_error {shift->{_opts}{api}{print_error}=undef;}
1686              
1687             sub api{shift}
1688              
1689             # send XMLRPC request
1690             sub request {shift->{_xmlrpc}->request(@_)}
1691              
1692             # LWP::UserAgent object
1693             sub user_agent {shift->{_xmlrpc}->user_agent}
1694              
1695             # Geneos::API::XMLPRC object
1696             sub xmlrpc {shift->{_xmlrpc}}
1697              
1698             #############
1699             # API calls #
1700             #############
1701              
1702             # ------------------------
1703             # Creates a sampler object
1704              
1705             sub get_sampler {
1706             my $self = shift;
1707             $self->_reset_error;
1708             Geneos::API::Sampler->new($self,@_)
1709             }
1710              
1711             # -------------------------------------------------------------
1712             # Checks whether a particular API or API-Streams sampler exists
1713             # on this NetProbe. If the sampler is part of a type, it needs
1714             # to be passed as sampler_name(type_name)
1715             #
1716             # Returns 1 if the sampler exists, 0 otherwise
1717             #
1718              
1719             sub sampler_exists {
1720             my ($self, $me, $sampler, $type) = @_;
1721             $sampler = "${sampler}($type)" if $type;
1722             $self->call("_netprobe.samplerExists", "$me.$sampler");
1723             }
1724              
1725             # ---------------------------------------------------------
1726             # Checks whether the Gateway is connected to this NetProbe
1727             #
1728             # Returns 1 if the Gateway is connected, 0 otherwise
1729             #
1730              
1731             sub gateway_connected {shift->call("_netprobe.gatewayConnected")}
1732              
1733             # ------------------------------------------------------
1734             # Adds the managed entity to the particular data section
1735             #
1736             # Returns 1 on success, 0 otherwise
1737             #
1738              
1739             sub add_managed_entity {shift->call("_gateway.addManagedEntity", @_)}
1740              
1741             # ------------------------------------------------------------------
1742             # Checks whether a particular Managed Entity exists on this NetProbe
1743             # containing any API or API-Streams samplers
1744             #
1745             # Returns 1 if the Managed Entity exists, 0 otherwise
1746             #
1747              
1748             sub managed_entity_exists {shift->call("_netprobe.managedEntityExists", @_)}
1749              
1750             1;
1751              
1752             __END__