2 # Copyright (C) 2011 Stanislav Sinyagin
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
18 # Stanislav Sinyagin <ssinyagin@yahoo.com>
23 BEGIN { require '@siam_config_pl@'; }
31 use Torrus::ConfigBuilder;
34 my $server = hostname();
36 my $ddx_path = $Torrus::SIAM::ddx_path;
37 my $bundles_path = $Torrus::SIAM::bundles_path;
38 my $aggr_path = $Torrus::SIAM::aggr_xml_path;
39 my $nodes_path = $Torrus::SIAM::nodes_path;
40 my $rrd_path = $Torrus::SIAM::rrd_path;
48 my $ok = GetOptions( 'server=s' => \$server,
49 'pddx=s' => \$ddx_path,
50 'pbundles=s' => \$bundles_path,
51 'paggr=s' => \$aggr_path,
52 'pnodes=s' => \$nodes_path,
53 'prrd=s' => \$rrd_path,
54 'verbose' => \$verbose,
57 if( not $ok or scalar( @ARGV ) > 0 )
60 ("Usage: $0 [options...]\n",
62 " --server=HOSTNAME Torrus server to match in SIAM [" .
64 " --pddx=PATH Path to store DDX files\n",
65 " [" . $ddx_path . "]\n",
66 " --pbundles=PATH Path to store bundles XML\n",
67 " [" . $bundles_path . "]\n",
68 " --paggr=PATH Path to store aggregates XML\n",
69 " [" . $aggr_path . "]\n",
70 " --pnodes=PATH Path to store per-node XML\n",
71 " [" . $nodes_path . "]\n",
72 " --prrd=PATH RRD data storage path\n",
73 " [" . $rrd_path . "]\n",
74 " --verbose print extra information\n",
75 " --debug print debugging information\n",
82 Torrus::Log::setLevel('debug');
86 Torrus::Log::setLevel('verbose');
89 if( not -d $bundles_path )
91 Error('No such directory: ' . $bundles_path);
94 if( not -d $nodes_path )
96 Error('No such directory: ' . $nodes_path);
100 my %tree_for_deviceid;
103 my $siam = Torrus::SIAM->open();
104 exit(1) unless defined($siam);
106 Verbose('Connected to SIAM');
108 # Generate discovery files
110 my $devices = $siam->get_contained_objects
111 ('SIAM::Device', {'match_attribute' => ['torrus.server', [$server]]});
113 Verbose('Retrieved ' . scalar(@{$devices}) . ' devices from SIAM');
116 # Distribute devices into bundles by torrus.tree attribute
118 for my $device (@{$devices})
120 next unless $device->is_complete();
121 next unless $device->attr('snmp.managed');
123 my $tree = $device->attr('torrus.tree');
124 if( not defined($tree) )
126 Error('SIAM::Device "' . $device->id .
127 '" does not define "torrus.tree" attribute. Aborting.');
131 $tree_for_deviceid{$device->id} = $tree;
133 if( not defined($bundles{$tree}) )
135 $bundles{$tree} = [];
138 push(@{$bundles{$tree}}, $device);
141 @tree_names = sort keys %bundles;
143 Verbose('Preparing to generate ' . scalar(keys %bundles) . ' bundles');
145 for my $tree ( @tree_names )
147 Verbose('Bundle ' . $tree . ': ' . scalar(@{$bundles{$tree}}) .
150 my $outfile = $ddx_path . 'siam-' . $tree . '.ddx';
151 Verbose('Preparing to write ' . $outfile);
153 my $doc = XML::LibXML->createDocument( "1.0", "UTF-8" );
154 my $root = $doc->createElement('snmp-discovery');
155 $doc->setDocumentElement( $root );
158 my $fileInfoNode = $doc->createElement('file-info');
159 $root->appendChild( $fileInfoNode );
161 my $formatNode = $doc->createElement('format-version');
162 $formatNode->appendText('1.0');
163 $fileInfoNode->appendChild( $formatNode );
167 my $creatorNode = $doc->createElement('creator-info');
168 $creatorNode->appendText
169 ("This file is generated from SIAM\n" .
170 'for Torrus tree ' . $tree . ' on ' .
171 scalar(localtime(time())));
172 $root->appendChild( $creatorNode );
176 'output-bundle' => $bundles_path . '/siam-' . $tree . '.xml',
177 'data-dir' => $rrd_path,
178 'SIAM::managed' => 'yes',
181 for my $p (keys %{$Torrus::SIAM::ddx_defaults})
183 $globalParams->{$p} = $Torrus::SIAM::ddx_defaults->{$p};
186 createParamsDom( $globalParams, $doc, $root );
188 for my $device (sort {$a->attr('siam.device.name') cmp
189 $b->attr('siam.device.name')}
192 my $name = $device->attr('siam.device.name');
195 'output-file' => $nodes_path . '/' . $name . '.xml',
196 'system-id' => $name,
199 $params->{'SIAM::device-inventory-id'} =
200 $device->attr('siam.device.inventory_id');
202 my $host = $device->attr('snmp.host');
203 if( not defined($host) )
208 $params->{'snmp-host'} = $host;
210 for my $p ('version', 'community', 'timeout', 'retries')
212 my $v = $device->attr('snmp.' . $p);
215 $params->{'snmp-' . $p} = $v;
219 my $subtree = $device->attr('torrus.subtree-path');
220 if( defined($subtree) )
222 $params->{'host-subtree'} = $subtree;
225 my $hostNode = $doc->createElement('host');
226 $root->appendChild( $hostNode );
228 createParamsDom( $params, $doc, $hostNode );
231 if( $doc->toFile( $outfile, 2 ) )
233 Info("Wrote $outfile");
237 Error("Cannot write $outfile: $!");
244 # Generate traffic aggregate graphs
249 my %aggr_description;
254 for my $svctype ( @Torrus::SIAM::aggr_svc_types )
257 $siam->get_objects_by_attribute
258 ('SIAM::Service', 'siam.svc.type', $svctype );
260 for my $service ( @{$services} )
262 next unless $service->is_complete();
264 my $svc_server = $service->attr('torrus.server');
265 if( not defined($svc_server) )
267 Error('Attribute "torrus.server" is undefined for the ' .
268 'traffic aggregate SIAM::Service ' . $service->id);
273 next unless $svc_server eq $server;
275 my $aggr_id = $service->id();
277 $aggr_name{$aggr_id} =
278 $service->attr($Torrus::SIAM::aggr_subtree_name_attr);
279 if( not defined($aggr_name{$aggr_id}) )
281 Error('Cannot determine the subtree path for aggregate ' .
282 'SIAM::Service ' . $service->id);
287 $aggr_nodeid{$aggr_id} =
288 $service->attr('torrus.aggregate.nodeid');
290 my $units = $service->get_service_units();
291 for my $unit (@{$units})
293 next unless $unit->is_complete();
295 my $device_id = $unit->attr('siam.svcunit.device_id');
296 my $tree = $tree_for_deviceid{$device_id};
298 if( not defined($tree) )
300 Error('SIAM::Device "' . $device_id .
301 '" does not define "torrus.tree" attribute. ' .
302 'It will not be a part of ' . $aggr_id .
308 # Make sure that the aggregate is built from elements
310 if( not defined($aggr_tree{$aggr_id}) )
312 $aggr_tree{$aggr_id} = $tree;
314 elsif( $aggr_tree{$aggr_id} ne $tree )
316 Error('Aggregate ' . $aggr_id . ' is defined for ' .
317 'devices from different trees: ' . $tree .
318 ', ' . $aggr_tree{$aggr_id});
323 my $unit_type = $unit->attr('siam.svcunit.type');
324 if( not defined($aggr_unit_type{$aggr_id}) )
326 $aggr_unit_type{$aggr_id} = $unit_type;
328 elsif( $aggr_unit_type{$aggr_id} ne $unit_type )
330 Error('Aggregate ' . $aggr_id . ' is defined for ' .
331 'service units of different types: ' . $unit_type .
332 ', ' . $aggr_unit_type{$aggr_id});
337 my $nodeid = $unit->attr('torrus.port.nodeid');
338 if( not defined($nodeid) )
340 Error('SIAM::ServiceUnit, id="' . $unit->id .
341 '" does not define torrus.port.nodeid');
346 $aggr_elements{$tree}{$aggr_id}{$nodeid} = 1;
349 $service->attr($Torrus::SIAM::aggr_descr_attr);
350 if( defined($descr) )
352 $aggr_description{$aggr_id} = $descr;
355 $unit_name{$nodeid} = $unit->attr('siam.svcunit.name');
362 Error('Skipping generation of aggregate XML ' .
363 'because of previous errors');
368 for my $tree ( @tree_names )
370 my $outfile = $aggr_path . '/siam-aggr-' . $tree . '.xml';
371 Verbose('Preparing to write ' . $outfile);
373 # we write the XML in any case, even if it's empty
374 my $cb = new Torrus::ConfigBuilder;
375 $cb->addCreatorInfo( "This file was generated by:\n" . $0 . "\n");
377 my $aggregates = $aggr_elements{$tree};
378 if( defined($aggregates) )
380 # Build the subtree containing all our aggregates
381 my $path = $Torrus::SIAM::aggr_tree_location;
383 # Chop the first and last slashes
387 # generate subtree path XML
388 my $aggrTopNode = undef;
389 for my $subtreeName ( split( '/', $path ) )
391 $aggrTopNode = $cb->addSubtree( $aggrTopNode, $subtreeName );
394 $cb->addParams($aggrTopNode, {
395 'has-overview-shortcuts' => 'yes',
396 'overview-shortcuts' => 'traffic',
397 'overview-subleave-name-traffic' => 'InOut_bps',
398 'overview-shortcut-text-traffic' => 'All traffic',
399 'overview-shortcut-title-traffic' =>
400 'Traffic for all aggregates',
401 'overview-page-title-traffic' =>
402 'Aggregate Input/Output Graphs',
405 for my $aggr_id (sort keys %{$aggregates})
407 my $unit_type = $aggr_unit_type{$aggr_id};
408 if( $unit_type eq 'IFMIB.Port' )
410 # remove all invalid characters
411 my $name_translated = $aggr_name{$aggr_id};
412 $name_translated =~ s/\W/_/go;
414 Debug('Building aggregate: ' . $path . '/' .
418 'node-display-name' => $aggr_name{$aggr_id},
419 'nodeid' => $aggr_nodeid{$aggr_id},
422 if( defined($aggr_description{$aggr_id}) )
424 $aggrParam->{'comment'} =
425 $aggr_description{$aggr_id};
428 # build the legend from aggregate members
432 ( sort keys %{$aggr_elements{$tree}{$aggr_id}} )
434 my $name = $unit_name{$port_nodeid};
435 $name =~ s/:/{COLON}/og;
436 $name =~ s/;/{SEMICOL}/og;
437 $name =~ s/%/{PERCENT}/og;
439 $legend .= 'member: ' . $name . ';';
441 $aggrParam->{'legend'} = $legend;
444 my $aggrSubtreeNode = $cb->addSubtree
445 ( $aggrTopNode, $name_translated, $aggrParam );
449 # Add the Bytes_In and Bytes_Out leaves. They are needed
450 # for the monitor mostly.
452 for my $dir ('In', 'Out')
454 my $dirsmall = lc($dir);
458 ( sort keys %{$aggr_elements{$tree}{$aggr_id}} )
461 $port_nodeid . '//' . $dirsmall . 'bytes';
463 # if value is unknown, it is treated as zero
465 '{[[' . $nodeid . ']]}';
473 $rpn .= ',' . $memRef . ',ADDNAN';
478 'comment' => $dir . 'put bytes',
479 'graph-legend' => $dir . 'put bytes',
480 'vertical-label' => 'Bps',
481 'ds-type' => 'rrd-file',
482 'leaf-type' => 'rrd-cdef',
484 'graph-lower-limit' => 0,
487 if( defined($aggr_nodeid{$aggr_id}) )
490 $aggr_nodeid{$aggr_id} . '//' .
494 $cb->addLeaf( $aggrSubtreeNode,
495 'Bytes_' . $dir, $param );
497 # store the RPN for the multigraph
498 $rpnexpr{$dir} = $rpn;
501 # Add the InOut_bps multigraph
504 'comment' => 'In/Out bits per second graphs',
505 'vertical-label' => 'bps',
506 'graph-lower-limit' => 0,
507 'ds-type' => 'rrd-multigraph',
508 'ds-names' => 'in,out',
511 if( defined($aggr_nodeid{$aggr_id}) )
514 $aggr_nodeid{$aggr_id} . '//inoutbit';
517 for my $dir ('In', 'Out')
520 $param->{'ds-expr-' . $ds} =
521 $rpnexpr{$dir} . ',8,*';
522 $param->{'graph-legend-' . $ds} =
523 'Bits per second ' . $ds;
524 $param->{'line-style-' . $ds} = '##Bps' . $dir;
525 $param->{'line-color-' . $ds} = '##Bps' . $dir;
526 $param->{'maxline-style-' . $ds} =
527 '##Bps' . $dir . 'Max';
528 $param->{'maxline-color-' . $ds} =
529 '##Bps' . $dir . 'Max';
532 $param->{'line-order-in'} = 1;
533 $param->{'line-order-out'} = 2;
535 $cb->addLeaf( $aggrSubtreeNode, 'InOut_bps', $param );
541 Error('Service unit type ' . $unit_type .
542 ' is not supported for aggregates');
548 if( $cb->toFile( $outfile ) )
550 Info('Wrote ' . $outfile);
554 Error('Cannot write aggregates to ' . $outfile . ': ' . $!);
563 exit($success ? 0:1);
569 my $parentNode = shift;
571 for my $param ( sort keys %{$params} )
573 my $paramNode = $doc->createElement('param');
574 $paramNode->setAttribute( 'name', $param );
575 $paramNode->setAttribute( 'value', $params->{$param} );
576 $parentNode->appendChild( $paramNode );
584 # indent-tabs-mode: nil
585 # perl-indent-level: 4