hydra/src/script/hydra-send-stats

108 lines
3.2 KiB
Plaintext
Raw Normal View History

#! /usr/bin/env perl
use strict;
2021-08-19 20:36:43 +00:00
use warnings;
use utf8;
2021-03-20 05:57:37 +00:00
use Hydra::Helper::Nix;
use Net::Statsd;
2015-06-24 11:19:16 +00:00
use File::Slurp;
use JSON;
use Getopt::Long qw(:config gnu_getopt);
STDERR->autoflush(1);
binmode STDERR, ":encoding(utf8)";
my $config = getHydraConfig();
2021-03-20 05:57:37 +00:00
my $statsdConfig = getStatsdConfig($config);
$Net::Statsd::HOST = $statsdConfig->{'host'};
$Net::Statsd::PORT = $statsdConfig->{'port'};
sub gauge {
my ($name, $val) = @_;
die unless defined $val;
Net::Statsd::gauge($name, $val);
}
sub sendQueueRunnerStats {
my $s = `hydra-queue-runner --status`;
die "cannot get queue runner stats\n" if $? != 0;
my $json = decode_json($s) or die "cannot decode queue runner status";
2015-06-24 11:19:16 +00:00
gauge("hydra.queue.up", $json->{status} eq "up" ? 1 : 0);
return if $json->{status} ne "up";
gauge("hydra.queue.steps.active", $json->{nrActiveSteps});
gauge("hydra.queue.steps.building", $json->{nrStepsBuilding});
2015-06-24 11:19:16 +00:00
gauge("hydra.queue.steps.copying_to", $json->{nrStepsCopyingTo});
gauge("hydra.queue.steps.copying_from", $json->{nrStepsCopyingFrom});
gauge("hydra.queue.steps.waiting", $json->{nrStepsWaiting});
gauge("hydra.queue.steps.runnable", $json->{nrRunnableSteps});
gauge("hydra.queue.steps.unfinished", $json->{nrUnfinishedSteps});
gauge("hydra.queue.steps.finished", $json->{nrStepsDone});
gauge("hydra.queue.steps.retries", $json->{nrRetries});
gauge("hydra.queue.steps.unsupported", $json->{nrUnsupportedSteps});
gauge("hydra.queue.steps.max_retries", $json->{maxNrRetries});
if ($json->{nrStepsDone}) {
gauge("hydra.queue.steps.avg_total_time", $json->{avgStepTime});
gauge("hydra.queue.steps.avg_build_time", $json->{avgStepBuildTime});
}
foreach my $machine (keys %{$json->{machineTypes}}) {
my $machineType = $machine =~ s/:|,/_/r;
gauge("hydra.queue.$machineType.runnable", $json->{machineTypes}->{$machine}->{runnable});
gauge("hydra.queue.$machineType.running", $json->{machineTypes}->{$machine}->{running});
}
gauge("hydra.queue.builds.read", $json->{nrBuildsRead});
gauge("hydra.queue.builds.unfinished", $json->{nrQueuedBuilds});
gauge("hydra.queue.builds.finished", $json->{nrBuildsDone});
gauge("hydra.queue.checks", $json->{nrQueueWakeups});
2015-06-25 14:46:59 +00:00
gauge("hydra.queue.bytes_sent", $json->{bytesSent});
gauge("hydra.queue.bytes_received", $json->{bytesReceived});
gauge("hydra.queue.machines.total", scalar(grep { $_->{enabled} } (values %{$json->{machines}})));
gauge("hydra.queue.machines.in_use", scalar(grep { $_->{currentJobs} > 0 } (values %{$json->{machines}})));
}
sub showHelp {
print <<EOF;
Usage: $0 [--once]
Send stats to statsd. The --once flag can be used to send one round
of stats and then exit.
Example:
\$ $0 --once
EOF
exit 0;
}
my $once = 0;
GetOptions("once" => \$once,
"help" => sub { showHelp() }
) or exit 1;
while (1) {
Improve handling of Perl's block eval errors Taken from `Perl::Critic`: A common idiom in perl for dealing with possible errors is to use `eval` followed by a check of `$@`/`$EVAL_ERROR`: eval { ... }; if ($EVAL_ERROR) { ... } There's a problem with this: the value of `$EVAL_ERROR` (`$@`) can change between the end of the `eval` and the `if` statement. The issue are object destructors: package Foo; ... sub DESTROY { ... eval { ... }; ... } package main; eval { my $foo = Foo->new(); ... }; if ($EVAL_ERROR) { ... } Assuming there are no other references to `$foo` created, when the `eval` block in `main` is exited, `Foo::DESTROY()` will be invoked, regardless of whether the `eval` finished normally or not. If the `eval` in `main` fails, but the `eval` in `Foo::DESTROY()` succeeds, then `$EVAL_ERROR` will be empty by the time that the `if` is executed. Additional issues arise if you depend upon the exact contents of `$EVAL_ERROR` and both `eval`s fail, because the messages from both will be concatenated. Even if there isn't an `eval` directly in the `DESTROY()` method code, it may invoke code that does use `eval` or otherwise affects `$EVAL_ERROR`. The solution is to ensure that, upon normal exit, an `eval` returns a true value and to test that value: # Constructors are no problem. my $object = eval { Class->new() }; # To cover the possiblity that an operation may correctly return a # false value, end the block with &quot;1&quot;: if ( eval { something(); 1 } ) { ... } eval { ... 1; } or do { # Error handling here }; Unfortunately, you can't use the `defined` function to test the result; `eval` returns an empty string on failure. Various modules have been written to take some of the pain out of properly localizing and checking `$@`/`$EVAL_ERROR`. For example: use Try::Tiny; try { ... } catch { # Error handling here; # The exception is in $_/$ARG, not $@/$EVAL_ERROR. }; # Note semicolon. "But we don't use DESTROY() anywhere in our code!" you say. That may be the case, but do any of the third-party modules you use have them? What about any you may use in the future or updated versions of the ones you already use?
2020-05-26 08:56:24 +00:00
eval {
sendQueueRunnerStats();
1;
2020-06-02 13:30:42 +00:00
} or do { warn "$@"; };
2015-06-24 11:19:16 +00:00
my $meminfo = read_file("/proc/meminfo", err_mode => 'quiet') // "";
$meminfo =~ m/Dirty:\s*(\d+) kB/;
if (defined $1) {
my $dirty = $1 / (1024.0 * 1024.0);
gauge("hydra.mem.dirty", $dirty);
}
if ($once) {
last;
}
sleep(30);
}