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
#
sub properties {
return {
path => {
- type => 'string', format => 'graphite-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,
+ },
};
}
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, $plugin_config, $node, $data, $ctime) = @_;
+ my ($class, $txn, $node, $data, $ctime) = @_;
- write_graphite_hash($plugin_config, $data, $ctime, "nodes.$node");
+ return assemble($class, $txn, $data, $ctime, "nodes.$node");
}
sub update_qemu_status {
- my ($class, $plugin_config, $vmid, $data, $ctime, $nodename) = @_;
- write_graphite_hash($plugin_config, $data, $ctime, "qemu.$vmid");
+ my ($class, $txn, $vmid, $data, $ctime, $nodename) = @_;
+
+ return assemble($class, $txn, $data, $ctime, "qemu.$vmid");
}
sub update_lxc_status {
- my ($class, $plugin_config, $vmid, $data, $ctime, $nodename) = @_;
+ my ($class, $txn, $vmid, $data, $ctime, $nodename) = @_;
- write_graphite_hash($plugin_config, $data, $ctime, "lxc.$vmid");
+ return assemble($class, $txn, $data, $ctime, "lxc.$vmid");
}
sub update_storage_status {
- my ($class, $plugin_config, $nodename, $storeid, $data, $ctime) = @_;
+ my ($class, $txn, $nodename, $storeid, $data, $ctime) = @_;
+
+ return assemble($class, $txn, $data, $ctime, "storages.$nodename.$storeid");
+}
- write_graphite_hash($plugin_config, $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 write_graphite_hash {
- my ($plugin_config, $d, $ctime, $object) = @_;
+sub _connect {
+ my ($class, $cfg) = @_;
- my $host = $plugin_config->{server};
- my $port = $plugin_config->{port} ? $plugin_config->{port} : 2003;
- my $path = $plugin_config->{path} ? $plugin_config->{path} : 'proxmox';
+ 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 => 'udp',
+ PeerAddr => $host,
+ PeerPort => $port,
+ Proto => $proto,
+ Timeout => $timeout,
) || die "couldn't create carbon socket [$host]:$port - $@\n";
- write_graphite($carbon_socket, $d, $ctime, $path.".$object");
-
- $carbon_socket->close() if $carbon_socket;
+ 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 write_graphite {
- my ($carbon_socket, $d, $ctime, $path) = @_;
+sub assemble {
+ my ($class, $txn, $data, $ctime, $object) = @_;
- for my $key (keys %$d) {
+ my $path = $txn->{cfg}->{path} // 'proxmox';
+ $path .= ".$object";
- my $value = $d->{$key};
- my $oldpath = $path;
- $key =~ s/\./-/g;
- $path .= ".$key";
+ # we do not want boolean/state information to export to graphite
+ my $key_blacklist = {
+ 'template' => 1,
+ 'pid' => 1,
+ 'agent' => 1,
+ 'serial' => 1,
+ };
- if ( defined $value ) {
- if ( ref $value eq 'HASH' ) {
- write_graphite($carbon_socket, $value, $ctime, $path);
- }else {
- $carbon_socket->send( "$path $value $ctime\n" );
- }
- }
- $path = $oldpath;
- }
+ 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);
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 undef if $noerr;
+ die "value does not look like a valid graphite path\n";
}
return $path;