Builds can now emit metrics that Hydra will store in its database and render as time series via flot charts. Typical applications are to keep track of performance indicators, coverage percentages, artifact sizes, and so on. For example, a coverage build can emit the coverage percentage as follows: echo "lineCoverage $pct %" > $out/nix-support/hydra-metrics Graphs of all metrics for a job can be seen at http://.../job/<project>/<jobset>/<job>#tabs-charts Specific metrics are also visible at http://.../job/<project>/<jobset>/<job>/metric/<metric> The latter URL also allows getting the data in JSON format (e.g. via "curl -H 'Accept: application/json'").
160 lines
5.9 KiB
Perl
160 lines
5.9 KiB
Perl
package Hydra::Controller::Job;
|
||
|
||
use strict;
|
||
use warnings;
|
||
use base 'Hydra::Base::Controller::ListBuilds';
|
||
use Hydra::Helper::Nix;
|
||
use Hydra::Helper::CatalystUtils;
|
||
|
||
|
||
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} = $c->stash->{jobset}->jobs->find({ name => $jobName })
|
||
or notFound($c, "Job $projectName:$jobsetName:$jobName doesn't exist.");
|
||
$c->stash->{project} = $c->stash->{job}->project;
|
||
}
|
||
|
||
|
||
sub overview : Chained('job') PathPart('') Args(0) {
|
||
my ($self, $c) = @_;
|
||
my $job = $c->stash->{job};
|
||
|
||
$c->stash->{template} = 'job.tt';
|
||
|
||
$c->stash->{lastBuilds} =
|
||
[ $job->builds->search({ finished => 1 },
|
||
{ order_by => 'id DESC', rows => 10, columns => [@buildListColumns] }) ];
|
||
|
||
$c->stash->{queuedBuilds} = [
|
||
$job->builds->search(
|
||
{ 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 => $job->builds->search({}, { 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 $b (@constituents) {
|
||
my $jobName = $b->get_column('job');
|
||
$aggregates->{$b->get_column('aggregate')}->{constituents}->{$jobName} =
|
||
{ id => $b->id, finished => $b->finished, buildstatus => $b->buildstatus };
|
||
$constituentJobs{$jobName} = 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}->name
|
||
})->count == 1 if $c->user_exists;
|
||
|
||
$c->stash->{metrics} = [ $job->buildmetrics->search(
|
||
{ }, { select => ["name"], distinct => 1, order_by => "timestamp desc", }) ];
|
||
}
|
||
|
||
|
||
sub build_times : Chained('job') PathPart('build-times') Args(0) {
|
||
my ($self, $c) = @_;
|
||
my @res = $c->stash->{job}->builds->search(
|
||
{ 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->{job}->builds->search(
|
||
{ 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->{job}->builds->search(
|
||
{ 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->{job}->buildmetrics->search(
|
||
{ 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->{job}->builds;
|
||
$c->stash->{latestSucceeded} = $c->model('DB')->resultset('LatestSucceededForJob')
|
||
->search({}, {bind => [$c->stash->{project}->name, $c->stash->{jobset}->name, $c->stash->{job}->name]});
|
||
$c->stash->{channelBaseName} =
|
||
$c->stash->{project}->name . "-" . $c->stash->{jobset}->name . "-" . $c->stash->{job}->name;
|
||
}
|
||
|
||
|
||
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}->name
|
||
};
|
||
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;
|