Projet

Général

Profil

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

root / plugins / virtualization / vmware / esx_ @ 06437f6f

Historique | Voir | Annoter | Télécharger (34 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/^cpu$/i, name => qr/^ready$/i, instance => qr/^$/ },
398
                  config => { groupBy => "group", graphName => "host_cpu_ready", graphTitle => "Amount of time spent in ready state per " }
399
            },
400
            {   selector => { group => qr/^cpu$/i, name => qr/^wait$/i, instance => qr/^$/ },
401
                  config => { groupBy => "group", graphName => "host_cpu_wait", graphTitle => "Amount of time spent in wait state per " }
402
            },
403
            {   selector => { group => qr/^cpu$/i, name => qr/^swapwait$/i, instance => qr/^$/ },
404
                  config => { groupBy => "group", graphName => "host_cpu_swapwait", graphTitle => "Amount of time spent in swapwait state per " }
405
            },
406
            {   selector => { group => qr/^disk$/i, name => qr/^(read|usage|write)$/i, instance => qr/.+/ },
407
                  config => { groupBy => "group", graphName => "host_disk_transfer", graphTitle => "Disk Transfer Rates per " }
408
            },
409
            {   selector => { group => qr/^disk$/i, name => qr/^.+Averaged$/i, instance => qr/.+/ },
410
                  config => { groupBy => "group", graphName => "host_disk_iops", graphTitle => "Disk I/O operations per " }
411
            },
412
            {   selector => { group => qr/^disk$/i, name => qr/^.+Latency$/i, instance => qr/.+/, vm => qr/^$/ },
413
                  config => { groupBy => "vm", graphName => "host_disk_latency", graphTitle => "Disk latency for " }
414
            },
415
            {   selector => { group => qr/^mem$/i, name => qr/^usage$/i, unit => qr/^KB$/i, vm => qr/^$/ },
416
                  config => { groupBy => "vm", graphName => "host_memory_usage", graphTitle => "Memory usage for " }
417
            },
418
            {   selector => { group => qr/^mem$/i, name => qr/^active$/i, unit => qr/^KB$/i, vm => qr/^$/ },
419
                  config => { groupBy => "vm", graphName => "host_memory_active", graphTitle => "Active memory usage for " }
420
            },
421
            {   selector => { group => qr/^mem$/i, name => qr/^consumed$/i, unit => qr/^KB$/i, vm => qr/^$/ },
422
                  config => { groupBy => "vm", graphName => "host_memory_consumed", graphTitle => "Consumed memory usage for " }
423
            },
424
            {   selector => { group => qr/^mem$/i, name => qr/^shared$/i, unit => qr/^KB$/i, vm => qr/^$/ },
425
                  config => { groupBy => "vm", graphName => "host_memory_shared", graphTitle => "Shared memory usage for " }
426
            },
427
            {   selector => { group => qr/^datastore$/i, unit => qr/^Bytes$/i, vm => qr/^$/ },
428
                  config => { groupBy => "vm", graphName => "usage_datastore", graphTitle => "Disk space usage for ", graphArgs => "--lower-limit 10737418240 --logarithmic --alt-autoscale-min --units=si" }
429
            },
430
            {   selector => { group => qr/^net$/i, unit => qr/^KBps$/i, vm => qr/^$/ },
431
                  config => { groupBy => "vm", graphName => "host_traffic_net", graphTitle => "Network traffic for " }
432
            },
433
            {   selector => { group => qr/^net$/i, unit => qr/^Number$/i, vm => qr/^$/ },
434
                  config => { groupBy => "vm", graphName => "host_packets_net", graphTitle => "Network packets for " }
435
            },
436
            {   selector => { group => qr/^power$/i, name => qr/^power$/i },
437
                  config => { groupBy => "group", graphName => "power_usage", graphTitle => "Host System and VM " }
438
            },
439
            {   selector => { group => qr/^sys$/i, name => qr/^diskUsage$/i },
440
                  config => { groupBy => "name", graphName => "host_disk_usage", graphTitle => "Host System " }
441
            },
442
            {   selector => { group => qr/^sys$/i, name => qr/^uptime$/i },
443
                  config => { groupBy => "name", graphName => "uptimes", graphTitle => "Host System and VM ", graphArgs => "--lower-limit 1000 --logarithmic --alt-autoscale-min" }
444
            }
445
        );
446

    
447
        # graphs per VM
448
        foreach (@all_vms) {
449
            my $vmName = clean_fieldname($resolveNames->{vm}->{$_});
450
            push @all_graphs, (
451
                {   selector => { group => qr/^cpu$/i, name => qr/^usagemhz$/i, vm => qr/^$_$/ },
452
                      config => { groupBy => "vm", graphName => "$vmName.vm_cpu", graphTitle => "CPU usage for " }
453
                },
454
                {   selector => { group => qr/^cpu$/i, name => qr/^ready$/i, vm => qr/^$_$/ },
455
                      config => { groupBy => "vm", graphName => "$vmName.vm_cpu_ready", graphTitle => "Amount of time spent in ready state per " }
456
                },
457
                {   selector => { group => qr/^cpu$/i, name => qr/^wait$/i, vm => qr/^$_$/ },
458
                      config => { groupBy => "vm", graphName => "$vmName.vm_cpu_wait", graphTitle => "Amount of time spent in wait state per " }
459
                },
460
                {   selector => { group => qr/^cpu$/i, name => qr/^swapwait$/i, vm => qr/^$_$/ },
461
                      config => { groupBy => "vm", graphName => "$vmName.vm_cpu_swapwait", graphTitle => "Amount of time spent in swapwait state per " }
462
                },
463
                {   selector => { group => qr/^mem$/i, unit => qr/^KB$/i, name => qr/^usage$/i, vm => qr/^$_$/ },
464
                      config => { groupBy => "vm", graphName => "$vmName.vm_memory", graphTitle => "Memory usage for " }
465
                },
466
                {   selector => { group => qr/^mem$/i, unit => qr/^KB$/i, name => qr/^active$/i, vm => qr/^$_$/ },
467
                      config => { groupBy => "vm", graphName => "$vmName.vm_memory_active", graphTitle => "Active memory usage for " }
468
                },
469
                {   selector => { group => qr/^mem$/i, unit => qr/^KB$/i, name => qr/^consumed$/i, vm => qr/^$_$/ },
470
                      config => { groupBy => "vm", graphName => "$vmName.vm_memory_consumed", graphTitle => "Consumed memory usage for " }
471
                },
472
                {   selector => { group => qr/^mem$/i, unit => qr/^KB$/i, name => qr/^shared$/i, vm => qr/^$_$/ },
473
                      config => { groupBy => "vm", graphName => "$vmName.vm_memory_shared", graphTitle => "Shared memory usage for " }
474
                },
475
                {   selector => { group => qr/^mem$/i, unit => qr/^KB$/i, name => qr/^swapped$/i, vm => qr/^$_$/ },
476
                      config => { groupBy => "vm", graphName => "$vmName.vm_memory_swapped", graphTitle => "Swapped memory usage for " }
477
                },
478
                {   selector => { group => qr/^datastore$/i, unit => qr/^Bytes$/i, vm => qr/^$_$/ },
479
                      config => { groupBy => "vm", graphName => "$vmName.vm_datastore", graphTitle => "Disk space usage for ", graphArgs => "--lower-limit 10485760 --logarithmic --alt-autoscale-min --units=si" }
480
                },
481
                {   selector => { group => qr/^virtualDisk$/i, unit => qr/^Millisecond$/i, vm => qr/^$_$/ },
482
                      config => { groupBy => "vm", graphName => "$vmName.vm_disklat", graphTitle => "Disk latency for " }
483
                },
484
                {   selector => { group => qr/^virtualDisk$/i, unit => qr/^Number$/i, vm => qr/^$_$/ },
485
                      config => { groupBy => "vm", graphName => "$vmName.vm_diskiops", graphTitle => "Disk I/O operations for " }
486
                },
487
                {   selector => { group => qr/^virtualDisk$/i, unit => qr/^KBps$/i, vm => qr/^$_$/ },
488
                      config => { groupBy => "vm", graphName => "$vmName.vm_disktrans", graphTitle => "Disk transfer rates for " }
489
                },
490
                {   selector => { group => qr/^net$/i, unit => qr/^KBps$/i, vm => qr/^$_$/ },
491
                      config => { groupBy => "vm", graphName => "$vmName.vm_traffic_net", graphTitle => "Network traffic for " }
492
                },
493
                {   selector => { group => qr/^net$/i, unit => qr/^Number$/i, vm => qr/^$_$/ },
494
                      config => { groupBy => "vm", graphName => "$vmName.vm_packets_net", graphTitle => "Network packets for " }
495
                },
496
                {   selector => { group => qr/^sys$/i, name => qr/^uptime$/i, vm => qr/^$_$/ },
497
                      config => { groupBy => "vm", graphName => "$vmName.vm_uptime", graphTitle => "VM uptime " }
498
                }
499
            );
500
        }
501

    
502
        # sensor graphs
503
        push @all_graphs, (
504
            {   selector => { group => qr/^sensors$/i },
505
                  config => { groupBy => "unit", graphName => "sensor_", graphTitle => "Sensors ", multiGraph => 1 }
506
            });
507

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

    
510
        # actual processing
511
        foreach (@all_graphs) {
512
            if ((defined $ARGV[0]) and ($ARGV[0] eq "config")) {
513
                munin_print("config", \@all_perf_data, $_);
514
                munin_print("values", \@all_perf_data, $_) if $ENV{MUNIN_CAP_DIRTYCONFIG}; # this doesn't seem to work even on Munin 1.4.6
515
            } else {
516
                munin_print("values", \@all_perf_data, $_);
517
            }
518
        }
519

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

    
522
        exit 0;
523
    } else {
524
        # PARENT
525
        push @returns, *FH;
526
    }
527
}
528

    
529
# gather and print the output of the forked processes
530
foreach my $fh (@returns) {
531
    while (<$fh>) {
532
        print $_;
533
    }
534
    close ($fh);
535
}
536

    
537
exit 0;
538

    
539
####################################################################
540

    
541
# calculate sum, max or avg from performance data values
542
sub process_value_array {
543
    my $arr = shift;
544
    my $pd = shift;
545
    my @vs = ();
546
    if ($pd->unitInfo->key eq "percent") {
547
        @vs = map { $_ / 100 } @$arr ;
548
    } else {
549
        @vs = @$arr;
550
    }
551
    return sum(@vs) if $pd->rollupType->val eq "summation";
552
    return max(@vs) if $pd->nameInfo->key =~ /max/i;
553
    return sum(@vs)/@$arr;
554
}
555

    
556
# query performance data for object
557
sub get_perf_data {
558
    my $entity = shift;
559
    my @ret = ();
560
    my $gathstart = time();
561
    # get the current server time
562
    my $curtime = $iso8601->parse_datetime($dtsys->QueryDateTime());
563
    # and subtract 5 minutes to get all values for the last period
564
    my $oldtime = $curtime->clone->add(minutes => -5);
565

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

    
569
    # loop over PerfEntityMetric
570
    foreach (defined $perfQ ? @$perfQ : ()) {
571
        my $vm = ($_->entity->type eq 'VirtualMachine')?$_->entity->value:"";
572
        # loop over PerfMetricIntSeries
573
        foreach (@{$_->{value}}) {
574
            my $perfDesc = $perfCounter{$_->id->counterId};
575
            next unless defined $perfDesc;
576
            push @ret, { rollup => $perfDesc->rollupType->val,
577
                          group => $perfDesc->groupInfo->key,
578
                           name => $perfDesc->nameInfo->key,
579
                          value => process_value_array(\@{$_->{value}}, $perfDesc),
580
                        counter => $perfDesc,
581
                             vm => $vm,
582
                       instance => $_->id->instance,
583
                           unit => $perfDesc->unitInfo->label };
584
        }
585
    }
586
    print "# time to gather info for $entity :", time() - $gathstart, "\n" if $DEBUG;
587
    return @ret;
588
}
589

    
590
# generate a munin-friendly and unique field name
591
sub gen_dp_name {
592
    my $fname = $_[0]->{name};
593
    $fname .= "v".$resolveNames->{vmuuid}->{$_[0]->{vm}} unless $_[1] eq "vm" or $_[0]->{vm} eq "";
594
    $fname .= "i$_[0]->{instance}" unless $_[1] eq "instance" or $_[0]->{instance} eq "";
595
    return clean_fieldname($fname);
596
}
597

    
598
# trim white spaces
599
sub trim {
600
    my $string = shift;
601
    $string =~ s/^\s+//;
602
    $string =~ s/\s+$//;
603
    return $string;
604
}
605

    
606
# print values and configs for graphs
607
sub munin_print {
608
    # action
609
    my $act = shift || "";
610
    # values
611
    my $arr = shift || ();
612
    # parameters
613
    my $par = shift || {};
614
    my $cfg = $par->{config};
615
    $par = $par->{selector};
616
    my $oldGroup = "_-_";
617
    my $factor;
618
    if ($ENV{flatview}) {
619
        $cfg->{graphName} = clean_fieldname("Host_".$host_view->name).".".$cfg->{graphName} unless $cfg->{graphName} =~ m/\./;
620
    }
621

    
622
    # find values according to criteria in $par and sort by grouping parameter
623
    #foreach (sort { $a->{$cfg->{groupBy}} cmp $b->{$cfg->{groupBy}} } grep { my $d = $_; all { (not exists $d->{$_}) || $d->{$_} =~ /$par->{$_}/ } keys %$par; } @$arr) {
624
     foreach (sort { $a->{$cfg->{groupBy}} cmp $b->{$cfg->{groupBy}} } grep { my $d = $_; all { (not exists $d->{$_}) || $d->{$_} =~ /$par->{$_}/ } keys %$par; } @$arr) {
625
        my $groupCrit = $cfg->{groupBy} || "";
626
        my $curGroup = $_->{$groupCrit} || "";
627

    
628
        if (!($curGroup eq $oldGroup)) {
629
            # we're in a new group, meaning a new graph starts
630
            $factor = 0;
631
            # clean up group name for multigraph name
632
            my $ccurGroup = $curGroup;
633
            $ccurGroup =~ s/ |\./_/g;
634
            print "multigraph ",$cfg->{graphName},(exists $cfg->{multiGraph}?$ccurGroup:""),"\n";
635

    
636
            if ("config" eq $act) {
637
                # want configuration
638
                print "graph_title ",$cfg->{graphTitle},$resolveNames->{$groupCrit}->{$curGroup} || $curGroup,"\n";
639
                #print "graph_order xxx yyy\n";
640

    
641
                my $unit = $_->{unit};
642
                my $base = 1000;
643

    
644
                # since the y-axis markers are going to be wrong with source units like
645
                # KB, MB, MHz etc., we define a correction factor via cdef later
646
                # this way, if 1024 MB is reported, the graph shows 1G and not 1k
647
                # (although 1k MB is technically also correct, but confusing)
648
                if ($unit =~ /^Bytes$/i) {
649
                    $base = 1024;
650
                } elsif ($unit =~ /^KBps$/i) {
651
                    $unit = "Bytes/s";
652
                    $factor = 1024;
653
                    $base = 1024;
654
                } elsif ($unit =~ /^KB$/i) {
655
                    $unit = "Bytes";
656
                    $factor = 1024;
657
                    $base = 1024;
658
                } elsif ($unit =~ /^MB$/i) {
659
                    $unit = "Bytes";
660
                    $factor = 1024*1024;
661
                    $base = 1024;
662
                } elsif ($unit =~ /^MHz$/i) {
663
                    $unit = "Hz";
664
                    $factor = 1000000;
665
                } elsif ($unit =~ /^Millisecond$/i) {
666
                    $unit = "Second";
667
                    $factor = 1/1000;
668
                }
669
                print "graph_vlabel $unit\n";
670
                print "graph_category $_->{group}\n";
671
                print "graph_args --base=$base --alt-autoscale-max ",(defined $cfg->{graphArgs})?$cfg->{graphArgs}:"","\n";
672
            }
673

    
674
        }
675
        $oldGroup = $curGroup;
676
        my $dpName = gen_dp_name($_, $groupCrit);
677
        if ("config" eq $act) {
678
            # want configuration
679
            # get instance and VM names and UF names, if applicable
680
            my $iName = $resolveNames->{$_->{group}}->{$_->{instance}} || (("" eq $_->{instance})?"":$_->{group}." ".$_->{instance});
681
            $iName = " $iName" if $iName;
682
            my $vmName = $resolveNames->{vm}->{$_->{vm}};
683
            $vmName = " $vmName" if $vmName;
684
            # all values are drawn as lines for now
685
            print "$dpName.draw LINE2\n";
686
            print "$dpName.label ",$_->{counter}->nameInfo->label,$iName,("vm" eq $groupCrit)?"":$vmName || "","\n";
687
            my $summary = $_->{counter}->nameInfo->summary;
688
            $summary =~ s!\n!\\n!g;
689
            print "$dpName.info ",$summary,$iName?", instance$iName ($_->{instance})":"",$vmName?",$vmName":"","\n";
690
            # declare CDEF if we want to apply a factor
691
            if ($factor > 1) {
692
                print "$dpName.cdef $dpName,$factor,*\n";
693
            } elsif ($factor <= 0) {
694
                if (defined $_->{unitModifier}) {
695
                    # sensor values have a unit modifier M attached to them so that REALVAL=VAL*10^M
696
                    # y,x,LOG,*,EXP is x^y, just in case this is not obvious to the reader
697
                    print "$dpName.cdef $dpName,",$_->{unitModifier},",10,LOG,*,EXP,*\n";
698
                }
699
            } elsif ($factor < 1) {
700
                print "$dpName.cdef $dpName,",1/$factor,",/\n";
701
            }
702
        } else {
703
            # just print value
704
            print "$dpName.value $_->{value}\n";
705
        }
706
    }
707
}