package PVE::Status::Graphite; use strict; use warnings; use IO::Socket::IP; use Socket qw(SOL_SOCKET SO_SNDTIMEO SO_RCVTIMEO); use PVE::Status::Plugin; use PVE::JSONSchema; # example config (/etc/pve/status.cfg) #graphite: # server test # port 2003 # proto udp # path proxmox.mycluster # disable 0 # use base('PVE::Status::Plugin'); sub type { return 'graphite'; } sub properties { return { path => { type => 'string', format => 'graphite-path', description => "root graphite path (ex: proxmox.mycluster.mykey)", }, timeout => { type => 'integer', description => "graphite TCP socket timeout (default=1)", minimum => 0, default => 1, optional => 1 }, proto => { type => 'string', enum => ['udp', 'tcp'], description => "Protocol to send graphite data. TCP or UDP (default)", optional => 1, }, }; } sub options { return { server => {}, port => { optional => 1 }, mtu => { optional => 1 }, proto => { optional => 1 }, timeout => { optional => 1 }, path => { optional => 1 }, disable => { optional => 1 }, }; } # Plugin implementation sub update_node_status { my ($class, $txn, $node, $data, $ctime) = @_; return assemble($class, $txn, $data, $ctime, "nodes.$node"); } sub update_qemu_status { my ($class, $txn, $vmid, $data, $ctime, $nodename) = @_; return assemble($class, $txn, $data, $ctime, "qemu.$vmid"); } sub update_lxc_status { my ($class, $txn, $vmid, $data, $ctime, $nodename) = @_; return assemble($class, $txn, $data, $ctime, "lxc.$vmid"); } sub update_storage_status { my ($class, $txn, $nodename, $storeid, $data, $ctime) = @_; return assemble($class, $txn, $data, $ctime, "storages.$nodename.$storeid"); } sub _send_batch_size { my ($class, $cfg) = @_; my $proto = $cfg->{proto} || 'udp'; if ($proto eq 'tcp') { return 56000; } return $class->SUPER::_send_batch_size($cfg); } sub _connect { my ($class, $cfg) = @_; my $host = $cfg->{server}; my $port = $cfg->{port} || 2003; my $proto = $cfg->{proto} || 'udp'; my $timeout = $cfg->{timeout} // 1; my $carbon_socket = IO::Socket::IP->new( PeerAddr => $host, PeerPort => $port, Proto => $proto, Timeout => $timeout, ) || die "couldn't create carbon socket [$host]:$port - $@\n"; if ($proto eq 'tcp') { # seconds and µs my $timeout_struct = pack( 'l!l!', $timeout, 0); setsockopt($carbon_socket, SOL_SOCKET, SO_SNDTIMEO, $timeout_struct); setsockopt($carbon_socket, SOL_SOCKET, SO_RCVTIMEO, $timeout_struct); } return $carbon_socket; } sub assemble { my ($class, $txn, $data, $ctime, $object) = @_; my $path = $txn->{cfg}->{path} // 'proxmox'; $path .= ".$object"; # we do not want boolean/state information to export to graphite my $key_blacklist = { 'template' => 1, 'pid' => 1, 'agent' => 1, 'serial' => 1, }; my $assemble_graphite_data; $assemble_graphite_data = sub { my ($metric, $path) = @_; for my $key (sort keys %$metric) { my $value = $metric->{$key}; next if !defined($value); $key =~ s/\./-/g; my $metricpath = $path . ".$key"; if (ref($value) eq 'HASH') { $assemble_graphite_data->($value, $metricpath); } elsif ($value =~ m/^[+-]?[0-9]*\.?[0-9]+$/ && !$key_blacklist->{$key}) { $class->add_metric_data($txn, "$metricpath $value $ctime\n"); } } }; $assemble_graphite_data->($data, $path); $assemble_graphite_data = undef; # avoid cyclic reference! } PVE::JSONSchema::register_format('graphite-path', \&pve_verify_graphite_path); sub pve_verify_graphite_path { my ($path, $noerr) = @_; my $regex = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)"; if ($path !~ /^(${regex}\.)*${regex}$/) { return undef if $noerr; die "value does not look like a valid graphite path\n"; } return $path; } 1;