Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / virtualization / vmware / esx_ @ e5ce7492

Historique | Voir | Annoter | Télécharger (30,7 ko)

1
#!/usr/bin/perl -w
2
=HEADER
3
        -== Munin plugin for VMware ESXi/vSphere monitoring ==-
4

    
5
 Copyright (c) 2012 - Stefan Seidel <munin@stefanseidel.info>
6

    
7
    This program is free software: you can redistribute it and/or modify
8
    it under the terms of the GNU General Public License as published by
9
    the Free Software Foundation, either version 3 of the License, or
10
    (at your option) any later version.
11

    
12
    This program is distributed in the hope that it will be useful,
13
    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
    GNU General Public License for more details.
16

    
17
    You should have received a copy of the GNU General Public License
18
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19

    
20

    
21
 This plugin uses the vSphere SDK for Perl available at
22
 http://www.vmware.com/support/developer/viperltoolkit/
23
 or included in the vSphere CLI available at
24
 http://www.vmware.com/support/developer/vcli/
25
 The use of the SDK is subject to the terms and condition
26
 of VMware, Inc. to which you must agree upon installation.
27
=cut
28

    
29
=USAGE
30
       -== Usage ==-
31
Put this file in /usr/share/munin/plugins, `chmod +x` it and
32
`ln -s` it to /etc/munin/plugins/esx_<hostname of server to monitor>
33
Add a file "esx_" /etc/munin/plugin-conf.d with content like this
34
(omit the "# " at the beginning of each line)
35

    
36
---- snip ----
37
[esx_*]
38
timeout 60
39
env.user <username on ESX server or vCenter>
40
env.password <password of user on ESX server or vCenter>
41
---- snip ----
42

    
43
Then you need to add this host to your munin.conf on the munin server
44
(often this is the same as your munin node, i.e. this host) and restart
45
munin-node, and wait for the data to populate.
46

    
47

    
48
       -== Query all hosts of a vCenter ==-
49
An alternate way of using this plugin is to link it to
50
/etc/munin/plugins/vcenter_<hostname_of_vcenter>
51
In this mode, the vCenter server specified in the filename is queried
52
for a list of hosts it manages, and graphs are created for all these
53
hosts. The option "flatview" is implied in this mode, since one munin
54
plugin can only be assigned to one host. The option "vCenter" is ignored
55
since the vCenter server is given through the file name. You can, however
56
still use the option "flatview" to override the "host_name" under which
57
the results are reported. Make sure to read the section below about
58
this option!
59

    
60

    
61
       -== Graphs don't render ==-
62
Munin 1.4 has a bug with complex multigraphs like this, see
63
http://munin-monitoring.org/ticket/1224 for details and a fix if
64
your graphs don't render!
65

    
66

    
67
       -== Option flatview ==-
68
There is an option to render all VMs and Host Systems in a flat
69
structure, i.e. not rendering VMs as sub-items of their host.
70
This is useful if you frequently move VMs between hosts and want to
71
keep the VM graphs running. To activate this option, add
72

    
73
---- snip ----
74
env.flatview top_level_entry
75
---- snip ----
76

    
77
to the entry in your config file in /etc/munin/plugin-conf.d (see above).
78
Be aware that this has some drawbacks:
79
 - you cannot have the same VM name in two hosts you monitor
80
   (the VM name is the unique identifier for the graphs)
81
 - you will only indirectly be able to see which VM is on which host
82
   (running VMs will appear in the CPU graphs of their hosts)
83
 - it's a flat structure, so it can become quite a long list
84
 - because of the way Munin works, all hosts will be queried serially,
85
   not in parallel as it would be the case without "flat view" - this
86
   MAY lead to timing problems if you have a large number of hosts or VMs
87

    
88

    
89
       -== Option vCenter ==-
90
If you wish to access the host system indirectly through a vCenter, just
91
specify this parameter:
92

    
93
---- snip ----
94
env.vCenter <address or hostname of the vCenter>
95
---- snip ----
96

    
97
This option can be used with or without the "flatview" option. Make sure your
98
password and username are valid on the vCenter. The plugin name will still have
99
to contain the hostname of the host you want to monitor - be aware that you have
100
to use the hostname exactly as it is registered in the vCenter, so IPs and
101
hostnames are NOT interchangeable.
102
=cut
103

    
104
=ACK
105
     -== Ackknowledgements ==-
106
I would like to thank VMware for their SDK and the good documentation.
107

    
108
Special thanks go to MEGABIT Informationstechnik GmbH (www.megabit.net)
109
who graciously sponsored the development of the "flat view" option
110
and the ability to access hosts via vCenter as well as the feature to
111
query all hosts on the vCenter.
112
=cut
113

    
114
use strict;
115
use sort 'stable'; # guarantee stability
116
no warnings; # don't want warnings in output
117

    
118
use VMware::VIRuntime; # need to install VIM SDK (vSphere CLI/SDK 4.1 or newer)
119
use VMware::VILib;
120
use VMware::VIExt;
121
use Data::Dumper;
122
use DateTime::Format::ISO8601; # may need to install "libdatetime-format-iso8601-perl" on Debian-based systems
123
use List::Util qw(sum max);
124
use List::MoreUtils qw(all);
125
use Munin::Plugin;
126
use Time::HiRes qw(time);
127
my $DEBUG = ${Munin::Plugin::DEBUG};
128

    
129
# Important: this is needed if you do not use a "proper" SSL certificate
130
# on your vSphere/vCenter/ESX(i) server (which is the default)
131
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
132

    
133
# for datetime parsing later on
134
my $iso8601 = DateTime::Format::ISO8601->new;
135
my @host_names = ();
136
my $host_name;
137
my $host_view;
138
my $dtsys;
139
my $perfMan;
140
my %perfCounter;
141
# IDs/UUIDs to human readable names
142
my $resolveNames;
143

    
144
if ($0 =~ /vcenter_(.+)$/) {
145
    $ENV{vCenter} = $1;
146
    $ENV{flatview} = $ENV{flatview} || $ENV{vCenter};
147
    my $vpid = open(FH, "-|");
148
    if ($vpid == 0) {
149
        Opts::set_option ('username', $ENV{user} || 'root');
150
        Opts::set_option ('password', $ENV{password} || '');
151
        Opts::set_option ('server', $ENV{vCenter});
152
        Util::connect();
153
        foreach (@{Vim::find_entity_views(view_type => 'HostSystem', properties => ['name'])}) {
154
            print $_->{name}, "\n";
155
        }
156
        Util::disconnect();
157
        exit 0;
158
    } else {
159
        while (<FH>) {
160
            push @host_names, trim($_);
161
            print "# found host ",trim($_)," on vCenter\n" if $DEBUG;
162
        }
163
        close FH;
164
    }
165
} else {
166
    # get hostname from filename and blurt it out immediately
167
    # so that when something goes wrong, at least the plugin
168
    # output is linked with the right host
169
    $0 =~ /esx_(.+)$/;
170
    push @host_names, $1;
171
}
172

    
173
my @returns = ();
174

    
175
foreach $host_name (@host_names) {
176
    # make sure we only print one host_name statement per plugin
177
    if ((@returns == 0) and (defined $ARGV[0]) and ($ARGV[0] eq "config")) {
178
        if ($ENV{flatview}) {
179
            print "host_name $ENV{flatview}\n";
180
            print "# for host $host_name\n" if $DEBUG;
181
        } else {
182
            print "host_name $host_name\n";
183
        }
184
    }
185
    local *FH;
186
    my $pid = open(FH, "-|");
187
    if ($pid == 0) {
188
        # CHILD
189

    
190

    
191
        # env.user and env.password need to be set in plugin-conf.d
192
        Opts::set_option ('username', $ENV{user} || 'root');
193
        Opts::set_option ('password', $ENV{password} || '');
194
        if ($ENV{vCenter}) {
195
            print "# vCenter: $ENV{vCenter} - host $host_name\n" if $DEBUG;
196
            Opts::add_options ( (vihost => { alias => "h", type => "=s", required => 0 }) );
197
            Opts::set_option ('vihost',$host_name);
198
            Opts::set_option ('server',$ENV{vCenter});
199
        } else {
200
            Opts::set_option ('server',$host_name);
201
        }
202

    
203

    
204
        # plugin needs Munin 1.4 or later
205
        need_multigraph();
206

    
207
        my $sstarttime = time();
208

    
209
        # connect to vSphere host
210
        Util::connect();
211

    
212
        # central object host_view holds all relevant items (VMs, network, etc.)
213
        $host_view = VIExt::get_host_view(1, ['summary', 'network', 'datastore', 'vm', 'runtime', 'configManager.networkSystem', 'configManager.dateTimeSystem']);
214
        Opts::assert_usage(defined($host_view), "Invalid host.");
215

    
216
        my $serviceInst = Vim::get_view (mo_ref => ManagedObjectReference->new(type => 'ServiceInstance', value => 'ServiceInstance'));
217
        # Performance Manager for getting the actual values
218
        $perfMan = Vim::get_view (mo_ref => $serviceInst->content->perfManager);
219
        Opts::assert_usage(defined($perfMan), "No PerformanceManager.");
220

    
221
        # may be needed later
222
        #my $netsys = Vim::get_view(mo_ref => ManagedObjectReference->new(type => 'HostNetworkSystem', value => 'networkSystem'));
223
        #Opts::assert_usage(defined($netsys), "No NetworkSystem.");
224

    
225
        # used for getting the current vSphere server time and then
226
        # defining the (now - 5minutes) interval
227
        $dtsys = Vim::get_view(mo_ref => $host_view->{'configManager.dateTimeSystem'});
228
        Opts::assert_usage(defined($dtsys), "No DateTimeSystem.");
229

    
230
        print "# time to connect and get objects: ", time() - $sstarttime, "\n" if $DEBUG;
231

    
232
        # enumerate all performance counters by their IDs
233
        %perfCounter = map { $_->key => $_ } @{$perfMan->perfCounter};
234
        # holds all performance data
235
        my @all_perf_data = ();
236
        # store VM ids for iteration later on
237
        my @all_vms = ();
238

    
239
        $host_view->update_view_data();
240
        # retrieve performance counters for host
241
        push @all_perf_data, get_perf_data($host_view);
242
        # manually set UF name for host system
243
        $resolveNames->{vm}->{""} = "Host System";
244

    
245
        # only purpose of this loop is getting the UF network names
246
        # network ManagedObjects do not have performance counters
247
        for ($host_view->network) {
248
            for (@$_) {
249
                my $network = Vim::get_view (mo_ref => $_);
250
                $resolveNames->{net}->{$_->{value}} = $_->{value}." (".$network->summary->name.")";
251
            }
252
        }
253

    
254
        # purpose of this loop is getting the UF datastore names
255
        # and retrieving capacity and free/uncommitted space
256
        # datastore ManagedObjects do not have performance counters
257
        for ($host_view->datastore) {
258
            for (@$_) {
259
                my $datastore = Vim::get_view (mo_ref => $_);
260
                # update freeSpace values (doesn't work on free ESXi)
261
                eval { $datastore->RefreshDatastore(); };
262
                $datastore->update_view_data();
263
                my $uuid =$datastore->summary->url;
264
                $uuid =~ s!.+/!!;
265
                $resolveNames->{datastore}->{$uuid} = $datastore->name;
266
                push (@all_perf_data,
267
                            { rollup => "latest",
268
                               group => "datastore",
269
                                name => "capacity",
270
                               value => $datastore->summary->capacity,
271
                             counter => PerfCounterInfo->new(nameInfo => ElementDescription->new(label => "Capacity", summary => "Maximum amount of storage space on this datastore")),
272
                                  vm => "",
273
                            instance => $uuid,
274
                                unit => "Bytes" });
275
                push (@all_perf_data,
276
                            { rollup => "latest",
277
                               group => "datastore",
278
                                name => "freeSpace",
279
                               value => $datastore->summary->freeSpace,
280
                             counter => PerfCounterInfo->new(nameInfo => ElementDescription->new(label => "Free", summary => "Total amount of unused, available storage space on this datastore")),
281
                                  vm => "",
282
                            instance => $uuid,
283
                                unit => "Bytes" });
284
                push (@all_perf_data,
285
                            { rollup => "latest",
286
                               group => "datastore",
287
                                name => "uncommitted",
288
                               value => $datastore->summary->uncommitted,
289
                             counter => PerfCounterInfo->new(nameInfo => ElementDescription->new(label => "Uncommitted", summary => "Total additional storage space, potentially used by all virtual machines on this datastore")),
290
                                  vm => "",
291
                            instance => $uuid,
292
                                unit => "Bytes" });
293
            }
294
        }
295

    
296
        # iterate over all vms
297
        for ($host_view->vm) {
298
            for (@$_) {
299
                my $vm = Vim::get_view (mo_ref => $_);
300
                $vm->update_view_data();
301
                # store VM id for later iteration
302
                my $vmId = $_->{value};
303
                push @all_vms, $vmId;
304
                # ID to VM name
305
                $resolveNames->{vm}->{$vmId} = "VM ".$vm->summary->config->name;
306
                $resolveNames->{vmuuid}->{$vmId} = $vm->summary->config->uuid;
307
                # fetch disk space usage per datastore
308
                for (@{$vm->storage->perDatastoreUsage}) {
309
                    my $uuid = Vim::get_view(mo_ref => $_->datastore)->summary->url;
310
                    $uuid =~ s!.+/!!;
311
                    push (@all_perf_data,
312
                                { rollup => "latest",
313
                                   group => "datastore",
314
                                    name => "committed",
315
                                   value => $_->committed,
316
                                 counter => PerfCounterInfo->new(nameInfo => ElementDescription->new(label => "Comitted", summary => "Storage space, in bytes, on this datastore that is actually being used by the virtual machine.\n\nIt includes space actually occupied by disks, logs, snapshots, configuration files etc. Files of the virtual machine which are present on a different datastore (e.g. a virtual disk on another datastore) are not included here.\n\n")),
317
                                      vm => $vmId,
318
                                instance => $uuid,
319
                                    unit => "Bytes" });
320
                    push (@all_perf_data,
321
                                { rollup => "latest",
322
                                   group => "datastore",
323
                                    name => "uncommitted",
324
                                   value => $_->uncommitted,
325
                                 counter => PerfCounterInfo->new(nameInfo => ElementDescription->new(label => "Uncomitted", summary => "Additional storage space, in bytes, potentially used by the virtual machine on this datastore.\n\nAdditional space may be needed for example when lazily allocated disks grow, or storage for swap is allocated when powering on the virtual machine.\n\nIf the virtual machine is running off delta disks (for example because a snapshot was taken), then only the potential growth of the currently used delta-disks is considered.\n\n")),
326
                                      vm => $vmId,
327
                                instance => $uuid,
328
                                    unit => "Bytes" });
329
                    push (@all_perf_data,
330
                                { rollup => "latest",
331
                                   group => "datastore",
332
                                    name => "unshared",
333
                                   value => $_->unshared,
334
                                 counter => PerfCounterInfo->new(nameInfo => ElementDescription->new(label => "Unshared", summary => "Storage space, in bytes, occupied by the virtual machine on this datastore that is not shared with any other virtual machine.\n\n")),
335
                                      vm => $vmId,
336
                                instance => $uuid,
337
                                    unit => "Bytes" });
338
                }
339
                # retrieve performance counters for this VM
340
                push @all_perf_data, get_perf_data ($_);
341
            }
342
        }
343

    
344
        # keep track of how many sensors are in which state
345
        my %sensorCount = ( green => 0, red => 0, unknown => 0, yellow => 0 );
346

    
347
        # iterate over all sensor data
348
        my $index = 0;
349
        for (@{$host_view->runtime->healthSystemRuntime->systemHealthInfo->numericSensorInfo}) {
350
            # update counters
351
            $sensorCount{$_->healthState->key}++;
352
            # do not create entries for unmonitorable things like software components
353
            next unless ($_->baseUnits =~ /.+/);
354
            # create entry with sensor data
355
            push (@all_perf_data,
356
                        { rollup => "latest",
357
                           group => "sensors",
358
                            name => "sensor_".($index++),
359
                           value => $_->currentReading,
360
                         counter => PerfCounterInfo->new(nameInfo => ElementDescription->new(label => $_->name, summary => "Sensor data for the ".$_->sensorType." sensor ".$_->name.". ".$_->healthState->summary." (".$_->healthState->label.")")),
361
                              vm => "",
362
                        instance => "",
363
                    unitModifier => $_->unitModifier,
364
                            unit => $_->baseUnits });
365
        }
366

    
367
        # we're finished querying the server, so we can disconnect now
368
        Util::disconnect();
369

    
370
        # create entries for the green/red/yellow/unknown counters
371
        for (keys %sensorCount) {
372
            push (@all_perf_data,
373
                        { rollup => "latest",
374
                           group => "sensors",
375
                            name => $_."_sensors",
376
                           value => $sensorCount{$_},
377
                         counter => PerfCounterInfo->new(nameInfo => ElementDescription->new(label => ucfirst($_), summary => "Count of sensors in the $_ state")),
378
                              vm => "",
379
                        instance => "",
380
                            unit => "Numbers" });
381
        }
382

    
383
        if ($DEBUG) {
384
            foreach (sort { $a->{group} cmp $b->{group} || $a->{instance} cmp $b->{instance} || $a->{name} cmp $b->{name} || $a->{rollup} cmp $b->{rollup} || $a->{vm} cmp $b->{vm} } @all_perf_data) {
385
                print "# $_->{vm}\t$_->{rollup}\t$_->{group}\t$_->{instance}\t$_->{name}\t$_->{value}\t$_->{unit}\n";
386
            }
387
        }
388

    
389
        # which graphs to draw
390
        my @all_graphs = ();
391

    
392
        # host system
393
        push @all_graphs, (
394
            {   selector => { group => qr/^cpu$/i, name => qr/^usagemhz$/i, instance => qr/^$/ },
395
                  config => { groupBy => "group", graphName => "host_cpu", graphTitle => "CPU usage per " }
396
            },
397
            {   selector => { group => qr/^disk$/i, name => qr/^(read|usage|write)$/i, instance => qr/.+/ },
398
                  config => { groupBy => "group", graphName => "host_disk_transfer", graphTitle => "Disk Transfer Rates per " }
399
            },
400
            {   selector => { group => qr/^disk$/i, name => qr/^.+Averaged$/i, instance => qr/.+/ },
401
                  config => { groupBy => "group", graphName => "host_disk_iops", graphTitle => "Disk I/O operations per " }
402
            },
403
            {   selector => { group => qr/^disk$/i, name => qr/^.+Latency$/i, instance => qr/.+/, vm => qr/^$/ },
404
                  config => { groupBy => "vm", graphName => "host_disk_latency", graphTitle => "Disk latency for " }
405
            },
406
            {   selector => { group => qr/^mem$/i, unit => qr/^KB$/i, rollup => qr/^none$/, vm => qr/^$/ },
407
                  config => { groupBy => "vm", graphName => "host_memory", graphTitle => "Memory usage for " }
408
            },
409
            {   selector => { group => qr/^datastore$/i, unit => qr/^Bytes$/i, vm => qr/^$/ },
410
                  config => { groupBy => "vm", graphName => "usage_datastore", graphTitle => "Disk space usage for ", graphArgs => "--lower-limit 10737418240 --logarithmic --alt-autoscale-min --units=si" }
411
            },
412
            {   selector => { group => qr/^net$/i, unit => qr/^KBps$/i, vm => qr/^$/ },
413
                  config => { groupBy => "vm", graphName => "host_traffic_net", graphTitle => "Network traffic for " }
414
            },
415
            {   selector => { group => qr/^net$/i, unit => qr/^Number$/i, vm => qr/^$/ },
416
                  config => { groupBy => "vm", graphName => "host_packets_net", graphTitle => "Network packets for " }
417
            },
418
            {   selector => { group => qr/^power$/i, name => qr/^power$/i },
419
                  config => { groupBy => "group", graphName => "power_usage", graphTitle => "Host System and VM " }
420
            },
421
            {   selector => { group => qr/^sys$/i, name => qr/^diskUsage$/i },
422
                  config => { groupBy => "name", graphName => "host_disk_usage", graphTitle => "Host System " }
423
            },
424
            {   selector => { group => qr/^sys$/i, name => qr/^uptime$/i },
425
                  config => { groupBy => "name", graphName => "uptimes", graphTitle => "Host System and VM ", graphArgs => "--lower-limit 1000 --logarithmic --alt-autoscale-min" }
426
            }
427
        );
428

    
429
        # graphs per VM
430
        foreach (@all_vms) {
431
            my $vmName = clean_fieldname($resolveNames->{vm}->{$_});
432
            push @all_graphs, (
433
                {   selector => { group => qr/^cpu$/i, name => qr/^usagemhz$/i, vm => qr/^$_$/ },
434
                      config => { groupBy => "vm", graphName => "$vmName.vm_cpu", graphTitle => "CPU usage for " }
435
                },
436
                {   selector => { group => qr/^mem$/i, unit => qr/^KB$/i, rollup => qr/^none$/, vm => qr/^$_$/ },
437
                      config => { groupBy => "vm", graphName => "$vmName.vm_memory", graphTitle => "Memory usage for " }
438
                },
439
                {   selector => { group => qr/^datastore$/i, unit => qr/^Bytes$/i, vm => qr/^$_$/ },
440
                      config => { groupBy => "vm", graphName => "$vmName.vm_datastore", graphTitle => "Disk space usage for ", graphArgs => "--lower-limit 10485760 --logarithmic --alt-autoscale-min --units=si" }
441
                },
442
                {   selector => { group => qr/^virtualDisk$/i, unit => qr/^Millisecond$/i, vm => qr/^$_$/ },
443
                      config => { groupBy => "vm", graphName => "$vmName.vm_disklat", graphTitle => "Disk latency for " }
444
                },
445
                {   selector => { group => qr/^virtualDisk$/i, unit => qr/^Number$/i, vm => qr/^$_$/ },
446
                      config => { groupBy => "vm", graphName => "$vmName.vm_diskiops", graphTitle => "Disk I/O operations for " }
447
                },
448
                {   selector => { group => qr/^virtualDisk$/i, unit => qr/^KBps$/i, vm => qr/^$_$/ },
449
                      config => { groupBy => "vm", graphName => "$vmName.vm_disktrans", graphTitle => "Disk transfer rates for " }
450
                },
451
                {   selector => { group => qr/^net$/i, unit => qr/^KBps$/i, vm => qr/^$_$/ },
452
                      config => { groupBy => "vm", graphName => "$vmName.vm_traffic_net", graphTitle => "Network traffic for " }
453
                },
454
                {   selector => { group => qr/^net$/i, unit => qr/^Number$/i, vm => qr/^$_$/ },
455
                      config => { groupBy => "vm", graphName => "$vmName.vm_packets_net", graphTitle => "Network packets for " }
456
                },
457
                {   selector => { group => qr/^sys$/i, name => qr/^uptime$/i, vm => qr/^$_$/ },
458
                      config => { groupBy => "vm", graphName => "$vmName.vm_uptime", graphTitle => "VM uptime " }
459
                }
460
            );
461
        }
462

    
463
        # sensor graphs
464
        push @all_graphs, (
465
            {   selector => { group => qr/^sensors$/i },
466
                  config => { groupBy => "unit", graphName => "sensor_", graphTitle => "Sensors ", multiGraph => 1 }
467
            });
468

    
469
        print "# time to collect all data: ", time() - $sstarttime, "\n" if $DEBUG;
470

    
471
        # actual processing
472
        foreach (@all_graphs) {
473
            if ((defined $ARGV[0]) and ($ARGV[0] eq "config")) {
474
                munin_print("config", \@all_perf_data, $_);
475
                munin_print("values", \@all_perf_data, $_) if $ENV{MUNIN_CAP_DIRTYCONFIG}; # this doesn't seem to work even on Munin 1.4.6
476
            } else {
477
                munin_print("values", \@all_perf_data, $_);
478
            }
479
        }
480

    
481
        print "# time of the script: ", time() - $sstarttime, "\n" if $DEBUG;
482

    
483
        exit 0;
484
    } else {
485
        # PARENT
486
        push @returns, *FH;
487
    }
488
}
489

    
490
# gather and print the output of the forked processes
491
foreach my $fh (@returns) {
492
    while (<$fh>) {
493
        print $_;
494
    }
495
    close ($fh);
496
}
497

    
498
exit 0;
499

    
500
####################################################################
501

    
502
# calculate sum, max or avg from performance data values
503
sub process_value_array {
504
    my $arr = shift;
505
    my $pd = shift;
506
    my @vs = ();
507
    if ($pd->unitInfo->key eq "percent") {
508
        @vs = map { $_ / 100 } @$arr ;
509
    } else {
510
        @vs = @$arr;
511
    }
512
    return sum(@vs) if $pd->rollupType->val eq "summation";
513
    return max(@vs) if $pd->nameInfo->key =~ /max/i;
514
    return sum(@vs)/@$arr;
515
}
516

    
517
# query performance data for object
518
sub get_perf_data {
519
    my $entity = shift;
520
    my @ret = ();
521
    my $gathstart = time();
522
    # get the current server time
523
    my $curtime = $iso8601->parse_datetime($dtsys->QueryDateTime());
524
    # and subtract 5 minutes to get all values for the last period
525
    my $oldtime = $curtime->clone->add(minutes => -5);
526

    
527
    # actual query, intervalId is 20 because that's the default
528
    my $perfQ = $perfMan->QueryPerf(querySpec => PerfQuerySpec->new(entity => $entity, intervalId => 20, startTime => $oldtime));
529

    
530
    # loop over PerfEntityMetric
531
    foreach (defined $perfQ ? @$perfQ : ()) {
532
        my $vm = ($_->entity->type eq 'VirtualMachine')?$_->entity->value:"";
533
        # loop over PerfMetricIntSeries
534
        foreach (@{$_->{value}}) {
535
            my $perfDesc = $perfCounter{$_->id->counterId};
536
            next unless defined $perfDesc;
537
            push @ret, { rollup => $perfDesc->rollupType->val,
538
                          group => $perfDesc->groupInfo->key,
539
                           name => $perfDesc->nameInfo->key,
540
                          value => process_value_array(\@{$_->{value}}, $perfDesc),
541
                        counter => $perfDesc,
542
                             vm => $vm,
543
                       instance => $_->id->instance,
544
                           unit => $perfDesc->unitInfo->label };
545
        }
546
    }
547
    print "# time to gather info for $entity :", time() - $gathstart, "\n" if $DEBUG;
548
    return @ret;
549
}
550

    
551
# generate a munin-friendly and unique field name
552
sub gen_dp_name {
553
    my $fname = $_[0]->{name};
554
    $fname .= "v".$resolveNames->{vmuuid}->{$_[0]->{vm}} unless $_[1] eq "vm" or $_[0]->{vm} eq "";
555
    $fname .= "i$_[0]->{instance}" unless $_[1] eq "instance" or $_[0]->{instance} eq "";
556
    return clean_fieldname($fname);
557
}
558

    
559
# trim white spaces
560
sub trim {
561
    my $string = shift;
562
    $string =~ s/^\s+//;
563
    $string =~ s/\s+$//;
564
    return $string;
565
}
566

    
567
# print values and configs for graphs
568
sub munin_print {
569
    # action
570
    my $act = shift || "";
571
    # values
572
    my $arr = shift || ();
573
    # parameters
574
    my $par = shift || {};
575
    my $cfg = $par->{config};
576
    $par = $par->{selector};
577
    my $oldGroup = "_-_";
578
    my $factor;
579
    if ($ENV{flatview}) {
580
        $cfg->{graphName} = clean_fieldname("Host_".$host_view->name).".".$cfg->{graphName} unless $cfg->{graphName} =~ m/\./;
581
    }
582

    
583
    # find values according to criteria in $par and sort by grouping parameter
584
    #foreach (sort { $a->{$cfg->{groupBy}} cmp $b->{$cfg->{groupBy}} } grep { my $d = $_; all { (not exists $d->{$_}) || $d->{$_} =~ /$par->{$_}/ } keys %$par; } @$arr) {
585
     foreach (sort { $a->{$cfg->{groupBy}} cmp $b->{$cfg->{groupBy}} } grep { my $d = $_; all { (not exists $d->{$_}) || $d->{$_} =~ /$par->{$_}/ } keys %$par; } @$arr) {
586
        my $groupCrit = $cfg->{groupBy} || "";
587
        my $curGroup = $_->{$groupCrit} || "";
588

    
589
        if (!($curGroup eq $oldGroup)) {
590
            # we're in a new group, meaning a new graph starts
591
            $factor = 0;
592
            # clean up group name for multigraph name
593
            my $ccurGroup = $curGroup;
594
            $ccurGroup =~ s/ |\./_/g;
595
            print "multigraph ",$cfg->{graphName},(exists $cfg->{multiGraph}?$ccurGroup:""),"\n";
596

    
597
            if ("config" eq $act) {
598
                # want configuration
599
                print "graph_title ",$cfg->{graphTitle},$resolveNames->{$groupCrit}->{$curGroup} || $curGroup,"\n";
600
                #print "graph_order xxx yyy\n";
601

    
602
                my $unit = $_->{unit};
603
                my $base = 1000;
604

    
605
                # since the y-axis markers are going to be wrong with source units like
606
                # KB, MB, MHz etc., we define a correction factor via cdef later
607
                # this way, if 1024 MB is reported, the graph shows 1G and not 1k
608
                # (although 1k MB is technically also correct, but confusing)
609
                if ($unit =~ /^Bytes$/i) {
610
                    $base = 1024;
611
                } elsif ($unit =~ /^KBps$/i) {
612
                    $unit = "Bytes/s";
613
                    $factor = 1024;
614
                    $base = 1024;
615
                } elsif ($unit =~ /^KB$/i) {
616
                    $unit = "Bytes";
617
                    $factor = 1024;
618
                    $base = 1024;
619
                } elsif ($unit =~ /^MB$/i) {
620
                    $unit = "Bytes";
621
                    $factor = 1024*1024;
622
                    $base = 1024;
623
                } elsif ($unit =~ /^MHz$/i) {
624
                    $unit = "Hz";
625
                    $factor = 1000000;
626
                } elsif ($unit =~ /^Millisecond$/i) {
627
                    $unit = "Second";
628
                    $factor = 1/1000;
629
                }
630
                print "graph_vlabel $unit\n";
631
                print "graph_category $_->{group}\n";
632
                print "graph_args --base=$base --alt-autoscale-max ",(defined $cfg->{graphArgs})?$cfg->{graphArgs}:"","\n";
633
            }
634

    
635
        }
636
        $oldGroup = $curGroup;
637
        my $dpName = gen_dp_name($_, $groupCrit);
638
        if ("config" eq $act) {
639
            # want configuration
640
            # get instance and VM names and UF names, if applicable
641
            my $iName = $resolveNames->{$_->{group}}->{$_->{instance}} || (("" eq $_->{instance})?"":$_->{group}." ".$_->{instance});
642
            $iName = " $iName" if $iName;
643
            my $vmName = $resolveNames->{vm}->{$_->{vm}};
644
            $vmName = " $vmName" if $vmName;
645
            # all values are drawn as lines for now
646
            print "$dpName.draw LINE2\n";
647
            print "$dpName.label ",$_->{counter}->nameInfo->label,$iName,("vm" eq $groupCrit)?"":$vmName || "","\n";
648
            my $summary = $_->{counter}->nameInfo->summary;
649
            $summary =~ s!\n!\\n!g;
650
            print "$dpName.info ",$summary,$iName?", instance$iName ($_->{instance})":"",$vmName?",$vmName":"","\n";
651
            # declare CDEF if we want to apply a factor
652
            if ($factor > 1) {
653
                print "$dpName.cdef $dpName,$factor,*\n";
654
            } elsif ($factor <= 0) {
655
                if (defined $_->{unitModifier}) {
656
                    # sensor values have a unit modifier M attached to them so that REALVAL=VAL*10^M
657
                    # y,x,LOG,*,EXP is x^y, just in case this is not obvious to the reader
658
                    print "$dpName.cdef $dpName,",$_->{unitModifier},",10,LOG,*,EXP,*\n";
659
                }
660
            } elsif ($factor < 1) {
661
                print "$dpName.cdef $dpName,",1/$factor,",/\n";
662
            }
663
        } else {
664
            # just print value
665
            print "$dpName.value $_->{value}\n";
666
        }
667
    }
668
}