2015-08-17 14:18:07 +02:00
|
|
|
#! /usr/bin/env perl
|
2015-06-23 00:14:49 +02:00
|
|
|
|
|
|
|
use strict;
|
2021-08-19 16:36:43 -04:00
|
|
|
use warnings;
|
2015-06-23 00:14:49 +02:00
|
|
|
use utf8;
|
2021-08-16 15:52:14 -04:00
|
|
|
use Getopt::Long;
|
2021-12-21 19:46:19 -05:00
|
|
|
use Time::HiRes qw( gettimeofday tv_interval );
|
2021-08-18 14:08:38 -04:00
|
|
|
use HTTP::Server::PSGI;
|
2021-08-16 15:52:14 -04:00
|
|
|
use Hydra::Event;
|
2021-08-12 12:03:25 -04:00
|
|
|
use Hydra::Event::BuildFinished;
|
|
|
|
use Hydra::Helper::AddBuilds;
|
|
|
|
use Hydra::Helper::Nix;
|
2015-06-23 00:14:49 +02:00
|
|
|
use Hydra::Plugin;
|
2021-06-17 16:27:27 -04:00
|
|
|
use Hydra::PostgresListener;
|
2021-08-26 15:39:29 -04:00
|
|
|
use Hydra::TaskDispatcher;
|
2021-08-18 14:08:38 -04:00
|
|
|
use Parallel::ForkManager;
|
|
|
|
use Prometheus::Tiny::Shared;
|
|
|
|
|
2015-06-23 00:14:49 +02:00
|
|
|
STDERR->autoflush(1);
|
2020-07-27 16:46:16 -04:00
|
|
|
STDOUT->autoflush(1);
|
2015-06-23 00:14:49 +02:00
|
|
|
binmode STDERR, ":encoding(utf8)";
|
|
|
|
|
2021-08-18 15:09:54 -04:00
|
|
|
my $config = getHydraConfig();
|
|
|
|
|
|
|
|
my $prom = Prometheus::Tiny::Shared->new;
|
2021-08-18 15:28:01 -04:00
|
|
|
# Note: It is very important to pre-declare any metrics before using them.
|
2021-08-24 10:57:23 -04:00
|
|
|
# Add a new declaration for any new metrics you create. Metrics which are
|
|
|
|
# not pre-declared disappear when their value is null. See:
|
2021-08-18 15:28:01 -04:00
|
|
|
# https://metacpan.org/pod/Prometheus::Tiny#declare
|
|
|
|
$prom->declare(
|
|
|
|
"event_loop_iterations",
|
|
|
|
type => "counter",
|
|
|
|
help => "Number of iterations through the event loop. Incremented at the start of the event loop."
|
|
|
|
);
|
|
|
|
$prom->declare(
|
|
|
|
"event_received",
|
|
|
|
type => "counter",
|
|
|
|
help => "Timestamp of the last time a new event was received."
|
|
|
|
);
|
|
|
|
$prom->declare(
|
|
|
|
"notify_event",
|
|
|
|
type => "counter",
|
|
|
|
help => "Number of events received on the given channel."
|
|
|
|
);
|
|
|
|
$prom->declare(
|
|
|
|
"notify_event_error",
|
|
|
|
type => "counter",
|
|
|
|
help => "Number of events received that were unprocessable by channel."
|
|
|
|
);
|
2021-12-21 19:46:19 -05:00
|
|
|
$prom->declare(
|
|
|
|
"notify_event_runtime",
|
|
|
|
type => "histogram",
|
|
|
|
help => "Number of seconds spent executing events by channel."
|
|
|
|
);
|
2021-08-18 15:09:54 -04:00
|
|
|
|
|
|
|
my $promCfg = Hydra::Helper::Nix::getHydraNotifyPrometheusConfig($config);
|
|
|
|
if (defined($promCfg)) {
|
2021-08-18 15:30:35 -04:00
|
|
|
print STDERR "Starting the Prometheus exporter, listening on http://${\$promCfg->{'listen_address'}}:${\$promCfg->{'port'}}/metrics.\n";
|
2021-08-18 15:09:54 -04:00
|
|
|
my $fork_manager = Parallel::ForkManager->new(1);
|
|
|
|
$fork_manager->start_child("metrics_exporter", sub {
|
|
|
|
my $server = HTTP::Server::PSGI->new(
|
|
|
|
host => $promCfg->{"listen_address"},
|
|
|
|
port => $promCfg->{"port"},
|
|
|
|
timeout => 1,
|
|
|
|
);
|
|
|
|
|
|
|
|
$server->run($prom->psgi);
|
|
|
|
});
|
2021-08-18 15:30:35 -04:00
|
|
|
} else {
|
|
|
|
print STDERR "Not starting the hydra-notify Prometheus exporter.\n";
|
2021-08-18 15:09:54 -04:00
|
|
|
}
|
|
|
|
|
2021-02-23 16:10:34 -05:00
|
|
|
my $queued_only;
|
|
|
|
|
|
|
|
GetOptions(
|
|
|
|
"queued-only" => \$queued_only
|
|
|
|
) or exit 1;
|
|
|
|
|
2015-06-23 00:14:49 +02:00
|
|
|
|
|
|
|
my $db = Hydra::Model::DB->new();
|
|
|
|
|
|
|
|
my @plugins = Hydra::Plugin->instantiate(db => $db, config => $config);
|
2021-08-26 17:08:22 -04:00
|
|
|
my $task_dispatcher = Hydra::TaskDispatcher->new(
|
|
|
|
$db,
|
|
|
|
$prom,
|
|
|
|
[@plugins],
|
|
|
|
sub {
|
|
|
|
my ($task) = @_;
|
|
|
|
$db->resultset("TaskRetries")->save_task($task);
|
|
|
|
}
|
|
|
|
);
|
2015-06-23 00:14:49 +02:00
|
|
|
|
2019-08-09 19:11:38 +02:00
|
|
|
my $dbh = $db->storage->dbh;
|
2015-06-23 00:14:49 +02:00
|
|
|
|
2021-06-17 16:27:27 -04:00
|
|
|
my $listener = Hydra::PostgresListener->new($dbh);
|
2021-12-20 13:18:32 -05:00
|
|
|
$listener->subscribe("build_queued");
|
2021-06-17 16:27:27 -04:00
|
|
|
$listener->subscribe("build_started");
|
|
|
|
$listener->subscribe("build_finished");
|
|
|
|
$listener->subscribe("step_finished");
|
2021-08-18 15:42:03 -04:00
|
|
|
$listener->subscribe("hydra_notify_dump_metrics");
|
2019-08-09 19:11:38 +02:00
|
|
|
|
|
|
|
# Process builds that finished while hydra-notify wasn't running.
|
|
|
|
for my $build ($db->resultset('Builds')->search(
|
|
|
|
{ notificationpendingsince => { '!=', undef } }))
|
|
|
|
{
|
2021-08-16 16:05:08 -04:00
|
|
|
print STDERR "sending notifications for build ${\$build->id}...\n";
|
2021-08-12 12:03:25 -04:00
|
|
|
|
2021-08-26 15:39:29 -04:00
|
|
|
my $event = Hydra::Event->new_event("build_finished", $build->id);
|
|
|
|
$task_dispatcher->dispatch_event($event);
|
2019-08-09 19:11:38 +02:00
|
|
|
}
|
|
|
|
|
2021-08-26 21:53:51 -04:00
|
|
|
my $taskretries = $db->resultset('TaskRetries');
|
|
|
|
|
2019-08-09 19:11:38 +02:00
|
|
|
# Process incoming notifications.
|
2021-02-23 16:10:34 -05:00
|
|
|
while (!$queued_only) {
|
2021-08-18 14:08:38 -04:00
|
|
|
$prom->inc("event_loop_iterations");
|
2021-08-26 21:53:51 -04:00
|
|
|
my $messages = $listener->block_for_messages($taskretries->get_seconds_to_next_retry());
|
2021-06-17 16:27:27 -04:00
|
|
|
while (my $message = $messages->()) {
|
2021-12-21 19:46:19 -05:00
|
|
|
my $start_time = [gettimeofday()];
|
2021-08-18 14:08:38 -04:00
|
|
|
$prom->set("event_received", time());
|
2021-06-17 16:27:27 -04:00
|
|
|
my $channelName = $message->{"channel"};
|
|
|
|
my $pid = $message->{"pid"};
|
|
|
|
my $payload = $message->{"payload"};
|
2019-08-12 17:28:21 +02:00
|
|
|
|
2021-08-18 14:08:38 -04:00
|
|
|
$prom->inc("notify_event", { channel => $channelName });
|
|
|
|
|
2021-08-18 15:42:03 -04:00
|
|
|
if ($channelName eq "hydra_notify_dump_metrics") {
|
|
|
|
print STDERR "Dumping prometheus metrics:\n${\$prom->format}\n";
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2019-08-12 17:28:21 +02:00
|
|
|
eval {
|
2021-08-16 15:52:25 -04:00
|
|
|
my $event = Hydra::Event->new_event($channelName, $message->{"payload"});
|
2021-08-26 15:39:29 -04:00
|
|
|
$task_dispatcher->dispatch_event($event);
|
2021-08-12 12:03:25 -04:00
|
|
|
|
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 "1":
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 10:56:24 +02:00
|
|
|
1;
|
|
|
|
} or do {
|
2021-08-18 14:08:38 -04:00
|
|
|
$prom->inc("notify_event_error", { channel => $channelName });
|
2019-08-12 17:28:21 +02:00
|
|
|
print STDERR "error processing message '$payload' on channel '$channelName': $@\n";
|
2021-12-21 19:46:19 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
$prom->histogram_observe("notify_event_runtime", tv_interval($start_time), { channel => $channelName });
|
2019-08-09 19:11:38 +02:00
|
|
|
}
|
2021-08-26 21:53:51 -04:00
|
|
|
|
2021-09-07 10:56:52 -04:00
|
|
|
my $task = $taskretries->get_retryable_task();
|
2021-08-26 21:53:51 -04:00
|
|
|
if (defined($task)) {
|
2021-09-07 10:56:52 -04:00
|
|
|
$task_dispatcher->dispatch_task($task);
|
2021-08-26 21:53:51 -04:00
|
|
|
}
|
2015-06-23 00:14:49 +02:00
|
|
|
}
|