250 lines
8.9 KiB
Perl
250 lines
8.9 KiB
Perl
package Hydra::Controller::Job;
|
||
|
||
use utf8;
|
||
use strict;
|
||
use warnings;
|
||
use base 'Hydra::Base::Controller::ListBuilds';
|
||
use Hydra::Helper::Nix;
|
||
use Hydra::Helper::CatalystUtils;
|
||
use JSON::MaybeXS;
|
||
use Net::Prometheus;
|
||
|
||
sub job : Chained('/') PathPart('job') CaptureArgs(3) {
|
||
my ($self, $c, $projectName, $jobsetName, $jobName) = @_;
|
||
|
||
$c->stash->{jobset} = $c->model('DB::Jobsets')->find({ project => $projectName, name => $jobsetName });
|
||
|
||
if (!$c->stash->{jobset}) {
|
||
my $rename = $c->model('DB::JobsetRenames')->find({ project => $projectName, from_ => $jobsetName });
|
||
notFound($c, "Jobset ‘$jobsetName’ doesn't exist.") unless defined $rename;
|
||
|
||
# Return a permanent redirect to the new jobset name.
|
||
my @captures = @{$c->req->captures};
|
||
$captures[1] = $rename->to_;
|
||
$c->res->redirect($c->uri_for($c->action, \@captures, $c->req->params), 301);
|
||
$c->detach;
|
||
}
|
||
|
||
$c->stash->{job} = $jobName;
|
||
$c->stash->{project} = $c->stash->{jobset}->project;
|
||
}
|
||
|
||
sub shield :Chained('job') PathPart('shield') Args(0) {
|
||
my ($self, $c) = @_;
|
||
|
||
my $job = $c->stash->{job};
|
||
|
||
my $lastBuild = $c->stash->{jobset}->builds->find(
|
||
{ job => $job, finished => 1 },
|
||
{ order_by => 'id DESC', rows => 1, columns => [@buildListColumns] }
|
||
);
|
||
notFound($c, "No latest build for job ‘$job’.") unless defined $lastBuild;
|
||
|
||
my $color =
|
||
$lastBuild->buildstatus == 0 ? "green" :
|
||
$lastBuild->buildstatus == 4 ? "yellow" :
|
||
"red";
|
||
my $message =
|
||
$lastBuild->buildstatus == 0 ? "passing" :
|
||
$lastBuild->buildstatus == 4 ? "cancelled" :
|
||
"failing";
|
||
|
||
$c->response->content_type('application/json');
|
||
$c->stash->{'plain'} = {
|
||
data => scalar (encode_json(
|
||
{
|
||
schemaVersion => 1,
|
||
label => "hydra build",
|
||
color => $color,
|
||
message => $message,
|
||
}))
|
||
};
|
||
$c->forward('Hydra::View::Plain');
|
||
}
|
||
|
||
|
||
sub prometheus : Chained('job') PathPart('prometheus') Args(0) {
|
||
my ($self, $c) = @_;
|
||
my $prometheus = Net::Prometheus->new;
|
||
|
||
my $lastBuild = $c->stash->{jobset}->builds->find(
|
||
{ job => $c->stash->{job}, finished => 1 },
|
||
{ order_by => 'id DESC', rows => 1, columns => ["stoptime", "buildstatus", "closuresize", "size"] }
|
||
);
|
||
|
||
$prometheus->new_counter(
|
||
name => "hydra_job_completion_time",
|
||
help => "The most recent job's completion time",
|
||
labels => [ "project", "jobset", "job" ]
|
||
)->labels(
|
||
$c->stash->{project}->name,
|
||
$c->stash->{jobset}->name,
|
||
$c->stash->{job},
|
||
)->inc($lastBuild->stoptime);
|
||
|
||
$prometheus->new_gauge(
|
||
name => "hydra_job_failed",
|
||
help => "Record if the most recent version of this job failed (1 means failed)",
|
||
labels => [ "project", "jobset", "job" ]
|
||
)->labels(
|
||
$c->stash->{project}->name,
|
||
$c->stash->{jobset}->name,
|
||
$c->stash->{job},
|
||
)->inc($lastBuild->buildstatus > 0);
|
||
|
||
$prometheus->new_gauge(
|
||
name => "hydra_build_closure_size",
|
||
help => "Closure size of the last job's build in bytes",
|
||
labels => [ "project", "jobset", "job" ]
|
||
)->labels(
|
||
$c->stash->{project}->name,
|
||
$c->stash->{jobset}->name,
|
||
$c->stash->{job},
|
||
)->inc($lastBuild->closuresize);
|
||
|
||
$prometheus->new_gauge(
|
||
name => "hydra_build_output_size",
|
||
help => "Output size of the last job's build in bytes",
|
||
labels => [ "project", "jobset", "job" ]
|
||
)->labels(
|
||
$c->stash->{project}->name,
|
||
$c->stash->{jobset}->name,
|
||
$c->stash->{job},
|
||
)->inc($lastBuild->size);
|
||
|
||
$c->stash->{'plain'} = { data => $prometheus->render };
|
||
$c->forward('Hydra::View::Plain');
|
||
}
|
||
|
||
sub overview : Chained('job') PathPart('') Args(0) {
|
||
my ($self, $c) = @_;
|
||
|
||
$c->stash->{template} = 'job.tt';
|
||
|
||
$c->stash->{lastBuilds} =
|
||
[ $c->stash->{jobset}->builds->search({ job => $c->stash->{job}, finished => 1 },
|
||
{ order_by => 'id DESC', rows => 10, columns => [@buildListColumns] }) ];
|
||
|
||
$c->stash->{queuedBuilds} = [
|
||
$c->stash->{jobset}->builds->search(
|
||
{ job => $c->stash->{job}, finished => 0 },
|
||
{ order_by => ["priority DESC", "id"] }
|
||
) ];
|
||
|
||
# If this is an aggregate job, then get its constituents.
|
||
my @constituents = $c->model('DB::Builds')->search(
|
||
{ aggregate => { -in => $c->stash->{jobset}->builds->search({ job => $c->stash->{job} }, { columns => ["id"], order_by => "id desc", rows => 15 })->as_query } },
|
||
{ join => 'aggregateconstituents_constituents',
|
||
columns => ['id', 'job', 'finished', 'buildstatus'],
|
||
+select => ['aggregateconstituents_constituents.aggregate'],
|
||
+as => ['aggregate']
|
||
});
|
||
|
||
my $aggregates = {};
|
||
my %constituentJobs;
|
||
foreach my $build (@constituents) {
|
||
$aggregates->{$build->get_column('aggregate')}->{constituents}->{$build->job} =
|
||
{ id => $build->id, finished => $build->finished, buildstatus => $build->buildstatus };
|
||
$constituentJobs{$build->job} = 1;
|
||
}
|
||
|
||
foreach my $agg (keys %$aggregates) {
|
||
# FIXME: could be done in one query.
|
||
$aggregates->{$agg}->{build} =
|
||
$c->model('DB::Builds')->find({id => $agg}, {columns => [@buildListColumns]}) or die;
|
||
}
|
||
|
||
$c->stash->{aggregates} = $aggregates;
|
||
$c->stash->{constituentJobs} = [sort (keys %constituentJobs)];
|
||
|
||
$c->stash->{starred} = $c->user->starredjobs(
|
||
{ project => $c->stash->{project}->name
|
||
, jobset => $c->stash->{jobset}->name
|
||
, job => $c->stash->{job}
|
||
})->count == 1 if $c->user_exists;
|
||
}
|
||
|
||
|
||
sub metrics_tab : Chained('job') PathPart('metric-tab') Args(0) {
|
||
my ($self, $c) = @_;
|
||
$c->stash->{template} = 'job-metrics-tab.tt';
|
||
$c->stash->{metrics} = [ $c->stash->{jobset}->buildmetrics->search(
|
||
{ job => $c->stash->{job} }, { select => ["name"], distinct => 1, order_by => "name", }) ];
|
||
}
|
||
|
||
|
||
sub build_times : Chained('job') PathPart('build-times') Args(0) {
|
||
my ($self, $c) = @_;
|
||
my @res = $c->stash->{jobset}->builds->search(
|
||
{ job => $c->stash->{job}, finished => 1, buildstatus => 0, closuresize => { '!=', 0 } },
|
||
{ join => "actualBuildStep"
|
||
, "+select" => ["actualBuildStep.stoptime - actualBuildStep.starttime"]
|
||
, "+as" => ["actualBuildTime"],
|
||
, order_by => "id" });
|
||
$self->status_ok($c, entity => [ map { { id => $_->id, timestamp => $_ ->timestamp, value => $_->get_column('actualBuildTime') } } @res ]);
|
||
}
|
||
|
||
|
||
sub closure_sizes : Chained('job') PathPart('closure-sizes') Args(0) {
|
||
my ($self, $c) = @_;
|
||
my @res = $c->stash->{jobset}->builds->search(
|
||
{ job => $c->stash->{job}, finished => 1, buildstatus => 0, closuresize => { '!=', 0 } },
|
||
{ order_by => "id", columns => [ "id", "timestamp", "closuresize" ] });
|
||
$self->status_ok($c, entity => [ map { { id => $_->id, timestamp => $_ ->timestamp, value => $_->closuresize } } @res ]);
|
||
}
|
||
|
||
|
||
sub output_sizes : Chained('job') PathPart('output-sizes') Args(0) {
|
||
my ($self, $c) = @_;
|
||
my @res = $c->stash->{jobset}->builds->search(
|
||
{ job => $c->stash->{job}, finished => 1, buildstatus => 0, size => { '!=', 0 } },
|
||
{ order_by => "id", columns => [ "id", "timestamp", "size" ] });
|
||
$self->status_ok($c, entity => [ map { { id => $_->id, timestamp => $_ ->timestamp, value => $_->size } } @res ]);
|
||
}
|
||
|
||
|
||
sub metric : Chained('job') PathPart('metric') Args(1) {
|
||
my ($self, $c, $metricName) = @_;
|
||
|
||
$c->stash->{template} = 'metric.tt';
|
||
$c->stash->{metricName} = $metricName;
|
||
|
||
my @res = $c->stash->{jobset}->buildmetrics->search(
|
||
{ job => $c->stash->{job}, name => $metricName },
|
||
{ order_by => "timestamp", columns => [ "build", "name", "timestamp", "value", "unit" ] });
|
||
|
||
$self->status_ok($c, entity => [ map { { id => $_->get_column("build"), timestamp => $_ ->timestamp, value => $_->value, unit => $_->unit } } @res ]);
|
||
}
|
||
|
||
|
||
# Hydra::Base::Controller::ListBuilds needs this.
|
||
sub get_builds : Chained('job') PathPart('') CaptureArgs(0) {
|
||
my ($self, $c) = @_;
|
||
$c->stash->{allBuilds} = $c->stash->{jobset}->builds->search({ job => $c->stash->{job} });
|
||
$c->stash->{latestSucceeded} = $c->model('DB')->resultset('LatestSucceededForJob')
|
||
->search({}, {bind => [$c->stash->{jobset}->id, $c->stash->{job}]});
|
||
$c->stash->{channelBaseName} =
|
||
$c->stash->{project}->name . "-" . $c->stash->{jobset}->name . "-" . $c->stash->{job};
|
||
}
|
||
|
||
|
||
sub star : Chained('job') PathPart('star') Args(0) {
|
||
my ($self, $c) = @_;
|
||
requirePost($c);
|
||
requireUser($c);
|
||
my $args =
|
||
{ project => $c->stash->{project}->name
|
||
, jobset => $c->stash->{jobset}->name
|
||
, job => $c->stash->{job}
|
||
};
|
||
if ($c->request->params->{star} eq "1") {
|
||
$c->user->starredjobs->update_or_create($args);
|
||
} else {
|
||
$c->user->starredjobs->find($args)->delete;
|
||
}
|
||
$c->stash->{resource}->{success} = 1;
|
||
}
|
||
|
||
|
||
1;
|