├── README.md ├── Template_Linux_Collectd_libvirt.xml ├── configs ├── collectd.conf └── zabbix-collectd.conf ├── docs-examples └── example-discovery-result.md └── scripts └── collect-libvirt-handler.pl /README.md: -------------------------------------------------------------------------------- 1 | # Zabbix Template Linux Collectd_libvirt 2 | 3 | A Zabbix templates for libvirt stats 4 | 5 | Tested on: 6 | 7 | > Ubuntu 12.04 x86_64 with KVM (kernel 3.5.0-44), collectd 4.10 8 | > Zabbix 2.0.x 9 | 10 | > CentOS 6.x X86_64, Collectd 4.10 11 | > Zabbix 2.0.x 12 | 13 | ### Authors 14 | * Patrik Majer 15 | 16 | 17 | ### installation - Manual 18 | 19 | ##### on monitored server (where you have kvm/libvirt) 20 | 21 | * install a configure zabbix-agent 22 | 23 | * copy file "zabbix-collectd.conf" into your zabbix include folder 24 | 25 | * install collectd package(s) and perl modules 26 | 27 | ```sh 28 | apt-get install collectd 29 | apt-get install libregexp-common-perl 30 | 31 | yum install collectd collectd-virt perl-Collectd 32 | ``` 33 | 34 | * * if yum installation fails, install epel repo package (extras repo) 35 | 36 | ```sh 37 | yum install epel-release 38 | ``` 39 | 40 | * copy collectd config file (collectd.conf) into zabbix agent config folder (e.g. /etc/zabbix/zabbix_agentd.d) 41 | 42 | * copy script "collect-libvirt-handler.pl" into /etc/zabbix/scripts/collectd-libvirt folder (with 755 perms) 43 | 44 | * reboot collectd service 45 | 46 | * reboot zabbix-agent service 47 | 48 | ##### on zabbix server 49 | 50 | * import template (xml file) 51 | 52 | ### installation - Automated 53 | 54 | * use puppet module/manifest [czhujer/puppet-zabbixagent](https://github.com/czhujer/puppet-zabbixagent#usage---example-manual-run) 55 | 56 | ### Monitored items 57 | 58 | * virt-cpu-total - on all libvirt guests 59 | 60 | * disk operations READ - on all libvirt guests 61 | 62 | * disk operations WRITE - on all libvirt guests 63 | 64 | * disk octets READ - on all libvirt guests 65 | 66 | * disk octets WRITE - on all libvirt guests 67 | 68 | * network interface packets TR/RX - on all libvirt guests 69 | 70 | * network interface octets TR/RX - on all libvirt guests 71 | -------------------------------------------------------------------------------- /Template_Linux_Collectd_libvirt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.0 4 | 2014-07-14T12:33:00Z 5 | 6 | 7 | Templates 8 | 9 | 10 | 11 | 675 | 676 | 677 | 678 | -------------------------------------------------------------------------------- /configs/collectd.conf: -------------------------------------------------------------------------------- 1 | 2 | FQDNLookup true 3 | Interval 60 4 | 5 | LoadPlugin syslog 6 | 7 | 8 | LogLevel info 9 | 10 | 11 | LoadPlugin csv 12 | LoadPlugin libvirt 13 | LoadPlugin unixsock 14 | 15 | 16 | DataDir "/var/lib/collectd/csv" 17 | StoreRates false 18 | 19 | 20 | 21 | SocketFile "/var/run/collectd-unixsock" 22 | SocketGroup "collectd" 23 | SocketPerms "0660" 24 | 25 | 26 | Include "/etc/collectd/filters.conf" 27 | Include "/etc/collectd/thresholds.conf" 28 | -------------------------------------------------------------------------------- /configs/zabbix-collectd.conf: -------------------------------------------------------------------------------- 1 | ## 2 | # collectd - plugin libvirt 3 | ## 4 | # 5 | #CPU 6 | # 7 | UserParameter=collectd-libvirt.cpu.discovery,sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock LISTVAL LIBVIRT-CPU 8 | UserParameter=collectd-libvirt.cpu[*],sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock GETVAL $1 9 | # 10 | #DISK - operations 11 | # 12 | UserParameter=collectd-libvirt.disk.discovery,sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock LISTVAL LIBVIRT-DISK 13 | UserParameter=collectd-libvirt.disk-ops-read[*],sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock GETVAL $1 OPS-READ 14 | UserParameter=collectd-libvirt.disk-ops-write[*],sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock GETVAL $1 OPS-WRITE 15 | # 16 | #DISK - octets 17 | # 18 | UserParameter=collectd-libvirt.disk-oct-read[*],sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock GETVAL $1 OCT-READ 19 | UserParameter=collectd-libvirt.disk-oct-write[*],sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock GETVAL $1 OCT-WRITE 20 | # 21 | # NET 22 | # 23 | UserParameter=collectd-libvirt.net.discovery,sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock LISTVAL LIBVIRT-NET 24 | UserParameter=collectd-libvirt.net-packets-rx[*],sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock GETVAL $1 NET-PACKETS-RX 25 | UserParameter=collectd-libvirt.net-packets-tx[*],sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock GETVAL $1 NET-PACKETS-TX 26 | UserParameter=collectd-libvirt.net-octets-rx[*],sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock GETVAL $1 NET-OCTETS-RX 27 | UserParameter=collectd-libvirt.net-octets-tx[*],sudo /etc/zabbix/scripts/collectd-libvirt/collect-libvirt-handler.pl /var/run/collectd-unixsock GETVAL $1 NET-OCTETS-TX 28 | -------------------------------------------------------------------------------- /docs-examples/example-discovery-result.md: -------------------------------------------------------------------------------- 1 | 2 | [root@test collectd-libvirt]# ./collect-libvirt-handler.pl /var/run/collectd-unixsock LISTVAL LIBVIRT-CPU 3 | 4 | ``` 5 | { 6 | "data":[ 7 | { 8 | "{#NAME}":"instance-00000113-virt_cpu_total"}, 9 | { 10 | "{#NAME}":"instance-00000112-virt_cpu_total"}, 11 | { 12 | "{#NAME}":"instance-00000111-virt_cpu_total"}, 13 | ] 14 | } 15 | ``` 16 | 17 | [root@test collectd-libvirt]# ./collect-libvirt-handler.pl /var/run/collectd-unixsock LISTVAL LIBVIRT-DISK 18 | 19 | ``` 20 | { 21 | "data":[ 22 | { 23 | "{#NAME}":"instance-00000113-disk-vda"}, 24 | { 25 | "{#NAME}":"instance-00000112-disk-vda"}, 26 | { 27 | "{#NAME}":"instance-00000111-disk-vda"}, 28 | ] 29 | } 30 | ``` 31 | 32 | [root@test collectd-libvirt]# ./collect-libvirt-handler.pl /var/run/collectd-unixsock LISTVAL LIBVIRT-NET 33 | 34 | ``` 35 | { 36 | "data":[ 37 | { 38 | "{#NAME}":"instance-00000113-if-tap620bc156-19"}, 39 | { 40 | "{#NAME}":"instance-00000112-if-tap50269e65-06"}, 41 | { 42 | "{#NAME}":"instance-00000111-if-tap869d29b8-08"}, 43 | ] 44 | } 45 | ``` 46 | 47 | 48 | -------------------------------------------------------------------------------- /scripts/collect-libvirt-handler.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Collectd::Unixsock(); 7 | 8 | { # main 9 | my $path = $ARGV[0] || "/var/run/collectd-unixsock"; 10 | my $command = $ARGV[1] || "LISTVAL"; 11 | my @vals; 12 | our $val = $ARGV[2]; 13 | our $val_type = $ARGV[3] || "undef"; 14 | 15 | if( $command eq "LISTVAL" and $val eq ""){ 16 | $val = "ALL" 17 | } 18 | 19 | if( $command eq "GETVAL"){ 20 | 21 | if( $val =~ /^.*-virt_cpu_total/ ){ 22 | @vals = split(/-virt/, $val); 23 | $val = $vals[0] . "/libvirt/" . "virt" . $vals[1] 24 | } 25 | elsif($val =~ /^.*-disk-/ and $val_type =~ /^OPS/){ 26 | @vals = split(/-disk/, $val); 27 | $val = $vals[0] . "/libvirt/disk_ops" .$vals[1] 28 | } 29 | elsif($val =~ /^.*-disk-/ and $val_type =~ /^OCT/){ 30 | @vals = split(/-disk/, $val); 31 | $val = $vals[0] . "/libvirt/disk_octets" .$vals[1] 32 | } 33 | elsif($val =~ /^.*-if-/ and $val_type =~ /^NET-PACKETS/){ 34 | @vals = split(/-if/, $val); 35 | $val = $vals[0] . "/libvirt/if_packets" . $vals[1] 36 | } 37 | elsif($val =~ /^.*-if-/ and $val_type =~ /^NET-OCTETS/){ 38 | @vals = split(/-if/, $val); 39 | $val = $vals[0] . "/libvirt/if_octets" .$vals[1] 40 | } 41 | $command .= " " . $val; 42 | 43 | #debug 44 | #print "DEBUG: command: " . $command . " val: " . $val . " \n"; 45 | } 46 | 47 | my $sock = Collectd::Unixsock->new($path); 48 | 49 | my $cmds = { 50 | HELP => \&cmd_help, 51 | PUTVAL => \&putval, 52 | GETVAL => \&getval, 53 | GETTHRESHOLD => \&getthreshold, 54 | FLUSH => \&flush, 55 | LISTVAL => \&listval, 56 | PUTNOTIF => \&putnotif, 57 | }; 58 | 59 | 60 | if (! $sock) { 61 | print STDERR "Unable to connect to $path!\n"; 62 | exit 1; 63 | } 64 | 65 | my $line = $command; 66 | 67 | last if (! $line); 68 | 69 | chomp $line; 70 | 71 | last if ($line =~ m/^quit$/i); 72 | 73 | my ($cmd) = $line =~ m/^(\w+)\s*/; 74 | $line = $'; 75 | 76 | next if (! $cmd); 77 | $cmd = uc $cmd; 78 | 79 | my $f = undef; 80 | if (defined $cmds->{$cmd}) { 81 | $f = $cmds->{$cmd}; 82 | } 83 | else { 84 | print STDERR "ERROR: Unknown command $cmd!\n"; 85 | next; 86 | } 87 | 88 | if (! $f->($sock, $line)) { 89 | print STDERR "ERROR: Command failed!\n"; 90 | next; 91 | } 92 | 93 | $sock->destroy(); 94 | exit 0; 95 | } 96 | 97 | sub tokenize { 98 | my $line = shift || return; 99 | my $line_ptr = $line; 100 | my @line = (); 101 | 102 | my $token_pattern = qr/[^"\s]+|"[^"]+"/; 103 | 104 | while (my ($token) = $line_ptr =~ m/^($token_pattern)\s+/) { 105 | $line_ptr = $'; 106 | push @line, $token; 107 | } 108 | 109 | if ($line_ptr =~ m/^$token_pattern$/) { 110 | push @line, $line_ptr; 111 | } 112 | else { 113 | my ($token) = split m/ /, $line_ptr, 1; 114 | print STDERR "Failed to parse line: $line\n"; 115 | print STDERR "Parse error near token \"$token\".\n"; 116 | return; 117 | } 118 | 119 | foreach my $l (@line) { 120 | if ($l =~ m/^"(.*)"$/) { 121 | $l = $1; 122 | } 123 | } 124 | return @line; 125 | } 126 | 127 | sub getid { 128 | my $string = shift || return; 129 | 130 | my ($h, $p, $pi, $t, $ti) = 131 | $string =~ m#^([^/]+)/([^/-]+)(?:-([^/]+))?/([^/-]+)(?:-([^/]+))?\s*#; 132 | $string = $'; 133 | 134 | return if ((! $h) || (! $p) || (! $t)); 135 | 136 | my %id = (); 137 | 138 | ($id{'host'}, $id{'plugin'}, $id{'type'}) = ($h, $p, $t); 139 | 140 | $id{'plugin_instance'} = $pi if defined ($pi); 141 | $id{'type_instance'} = $ti if defined ($ti); 142 | return \%id; 143 | } 144 | 145 | sub putid { 146 | my $ident = shift || return; 147 | 148 | my $string; 149 | 150 | our $val; 151 | 152 | $string = $ident->{'host'} . "/" . $ident->{'plugin'}; 153 | 154 | if (defined $ident->{'plugin_instance'}) { 155 | $string .= "-" . $ident->{'plugin_instance'}; 156 | } 157 | 158 | $string .= "/" . $ident->{'type'}; 159 | 160 | if (defined $ident->{'type_instance'}) { 161 | $string .= "-" . $ident->{'type_instance'}; 162 | } 163 | 164 | if( $val eq "ALL"){ 165 | return $string . $/; 166 | } 167 | elsif( $ident->{'type'} eq "virt_cpu_total" and $val eq "CPU"){ 168 | return $string . $/; 169 | } 170 | } 171 | 172 | sub putidjson { 173 | my $ident = shift || return; 174 | my $string; 175 | my $stringjson; 176 | our $val; 177 | 178 | $string = $ident->{'host'}; 179 | 180 | if( $val eq "ALL"){ 181 | $string .= "-" . $ident->{'plugin'}; 182 | } 183 | 184 | if (defined $ident->{'plugin_instance'}) { 185 | $string .= "-" . $ident->{'plugin_instance'}; 186 | } 187 | 188 | if ($ident->{'plugin'} eq "libvirt" and $ident->{'type'} =~ /^disk/ and $val eq "LIBVIRT-DISK"){ 189 | $ident->{'type'} =~ s/_ops//; 190 | $string .= "-" . $ident->{'type'}; 191 | } 192 | elsif ($ident->{'plugin'} eq "libvirt" and $ident->{'type'} =~ /^if/ and $val eq "LIBVIRT-NET") { 193 | $ident->{'type'} =~ s/_packets//; 194 | $string .= "-" . $ident->{'type'}; 195 | } 196 | else{ 197 | $string .= "-" . $ident->{'type'}; 198 | } 199 | 200 | if (defined $ident->{'type_instance'}) { 201 | $string .= "-" . $ident->{'type_instance'}; 202 | } 203 | 204 | $stringjson = "{#NAME}\":\"" . $string . "\""; 205 | 206 | if( $val eq "ALL"){ 207 | return $stringjson; 208 | } 209 | elsif( $ident->{'plugin'} eq "libvirt" and $ident->{'type'} eq "virt_cpu_total" and $val eq "LIBVIRT-CPU"){ 210 | return $stringjson; 211 | } 212 | elsif( $ident->{'plugin'} eq "libvirt" and $ident->{'type'} =~ /^disk$/ and $val eq "LIBVIRT-DISK"){ 213 | return $stringjson; 214 | } 215 | elsif( $ident->{'plugin'} eq "libvirt" and $ident->{'type'} =~ /^if$/ and $val eq "LIBVIRT-NET"){ 216 | return $stringjson; 217 | } 218 | } 219 | 220 | sub listval { 221 | my $sock = shift || return; 222 | my $line = shift; 223 | 224 | my @res; 225 | 226 | if ($line ne "") { 227 | print STDERR "Synopsis: LISTVAL" . $/; 228 | return; 229 | } 230 | 231 | @res = $sock->listval(); 232 | 233 | if (! @res) { 234 | print STDERR "socket error: " . $sock->{'error'} . $/; 235 | return; 236 | } 237 | 238 | # foreach my $ident (@res) { 239 | # print putidB($ident); 240 | # } 241 | 242 | my $firstline = 1; 243 | 244 | print "{\n\t\"data\":[\n\n"; 245 | 246 | foreach my $ident (@res) { 247 | 248 | my $rs = putidjson($ident); 249 | 250 | if(length($rs) > 0){ 251 | 252 | print "\t,\n" if not $firstline; 253 | $firstline = 0; 254 | print "\t{\n"; 255 | 256 | print "\t\t\"" . putidjson($ident) . "\n"; 257 | 258 | print "\t}\n"; 259 | } #end of if 260 | 261 | } #end of foreach 262 | 263 | print "\n\t]\n"; 264 | print "}\n"; 265 | 266 | return 1; 267 | } 268 | 269 | sub getval { 270 | my $sock = shift || return; 271 | my $line = shift || return; 272 | 273 | my @line = tokenize($line); 274 | 275 | my $id; 276 | my $vals; 277 | 278 | my $err_msg; 279 | our $val_type; 280 | 281 | if (! @line) { 282 | return; 283 | } 284 | 285 | if (scalar(@line) < 1) { 286 | print STDERR "Synopsis: GETVAL " . $/; 287 | return; 288 | } 289 | 290 | $id = getid($line[0]); 291 | 292 | if (! $id) { 293 | print STDERR "Invalid id \"$line[0]\"." . $/; 294 | return; 295 | } 296 | 297 | $vals = $sock->getval(%$id); 298 | 299 | if (! $vals) { 300 | 301 | $err_msg = $sock->{'error'}; 302 | 303 | if ("$err_msg" eq "No such value") { 304 | print "0" .$/; 305 | return 1; 306 | } 307 | # else 308 | { 309 | # print STDERR "socket error: " . $sock->{'error'} . $/; 310 | print STDERR "socket error: " . $sock->{'error'} . $/; 311 | return; 312 | } 313 | } 314 | 315 | foreach my $key (keys %$vals) { 316 | 317 | #debug 318 | #print $line[0] . "\n"; 319 | 320 | if( $line[0] =~ /^.*\/libvirt\/virt_cpu_total/ ){ 321 | print "$vals->{$key}\n"; 322 | } 323 | elsif($line[0] =~ /^.*\/libvirt\/disk_ops/){ 324 | 325 | if($val_type eq "OPS-READ" and $key eq "read"){ 326 | print "$vals->{$key}\n"; 327 | } 328 | elsif($val_type eq "OPS-WRITE" and $key eq "write"){ 329 | print "$vals->{$key}\n"; 330 | } 331 | elsif($val_type eq "OPS"){ 332 | print "\t$key: $vals->{$key}\n"; 333 | } 334 | } 335 | elsif($line[0] =~ /^.*\/libvirt\/disk_octets/){ 336 | 337 | #debug 338 | #print "DEBUG: disk_octets options ..." . $/; 339 | 340 | if($val_type eq "OCT-READ" and $key eq "read"){ 341 | print "$vals->{$key}\n"; 342 | } 343 | elsif($val_type eq "OCT-WRITE" and $key eq "write"){ 344 | print "$vals->{$key}\n"; 345 | } 346 | elsif($val_type eq "OCT"){ 347 | print "\t$key: $vals->{$key}\n"; 348 | } 349 | 350 | } 351 | elsif($line[0] =~ /^.*\/libvirt\/if_packets/){ 352 | 353 | #debug 354 | #print "DEBUG: if_packets options ..." . $/; 355 | 356 | if($val_type eq "NET-PACKETS-RX" and $key eq "rx"){ 357 | print "$vals->{$key}\n"; 358 | } 359 | elsif($val_type eq "NET-PACKETS-TX" and $key eq "tx"){ 360 | print "$vals->{$key}\n"; 361 | } 362 | elsif($val_type eq "NET-PACKETS"){ 363 | print "\t$key: $vals->{$key}\n"; 364 | } 365 | 366 | } 367 | elsif($line[0] =~ /^.*\/libvirt\/if_octets/){ 368 | 369 | #debug 370 | #print "DEBUG: if_octets options ..." . $/; 371 | 372 | if($val_type eq "NET-OCTETS-RX" and $key eq "rx"){ 373 | print "$vals->{$key}\n"; 374 | } 375 | elsif($val_type eq "NET-OCTETS-TX" and $key eq "tx"){ 376 | print "$vals->{$key}\n"; 377 | } 378 | elsif($val_type eq "NET-OCTETS"){ 379 | print "\t$key: $vals->{$key}\n"; 380 | } 381 | 382 | } 383 | else{ 384 | print "\t$key: $vals->{$key}\n"; 385 | } 386 | } 387 | return 1; 388 | } 389 | 390 | --------------------------------------------------------------------------------