Compare commits

...

16 Commits

Author SHA1 Message Date
Eelco Dolstra
f92a143c5f Don't use the Switch module
It was removed in Perl 5.14.
2012-03-12 20:41:45 +01:00
Eelco Dolstra
3eeb035d5a Fix the jobset unchanged check
When checking whether the jobset is unchanged, we need to compare with
the previous JobsetEval regardless of whether it had new builds.
Otherwise we'll keep adding new JobsetEval rows.
2012-03-12 20:28:44 +01:00
Eelco Dolstra
2a471ce0a4 Re-enable the "Related Builds" tab
However, there is a "limit 1" on the JobsetEval search to prevent a
sequential scan of the Builds table.
2012-03-12 20:14:42 +01:00
Eelco Dolstra
a4b63db992 Disable prepared statements completely
Because of the way DBIx::Class does prepared statements, even
innocuous queries such

  $c->model('DB::Builds)->search({finished => 0})

can be extremely slow.  This is because DBIx::Class prepares a
PostgreSQL statement

  select ... from Builds where finished = ?

and since Builds is very large and there is a large fraction of rows
with "finished = 1", the PostgreSQL query planner decides to implement
this query with a sequential scan of the Builds table (despite the
existence of an index on "finished"), which is extremely slow.  It
would be nice if we could tell DBIx::Class that constants should be
part of the prepared statement, i.e.

  select ... from Builds where finished = 0

but AFAIK we can't.
2012-03-12 19:42:59 +01:00
Eelco Dolstra
0420232324 Speed up channel processing
In particular the /pkg action is now O(lg n) instead of O(n) in the
number of packages in the channel, and listing the channel contents
no longer requires calling isValidPath() on all packages.

Derivations (and thus build time dependencies) are no longer included
in the channel, because they're not GC roots.  Thus they could
disappear unexpectedly.
2012-03-08 01:17:59 +01:00
Eelco Dolstra
4dfca7f83b Fix a reference to BuildResultInfo 2012-03-08 01:17:11 +01:00
Eelco Dolstra
36bd37d353 Reduce I/O in build listings by only fetching required columns
Columns such as "longDescription" can be large, so fetching them
when they're not needed is wasteful.
2012-03-07 22:20:15 +01:00
Eelco Dolstra
84834881c3 hydra-build: only send email if the status differs from the previous build
This isn't perfect because it doesn't handle the case where a
previous build hasn't finished yet.  But at least it won't send mail
for old builds that fail while a newer build has already succeeded.
2012-03-07 19:37:45 +01:00
Eelco Dolstra
2560e97c1e Evaluator cleanups
* Don't use isCurrent anymore; instead look up builds in the previous
  jobset evaluation.  (The isCurrent field is still maintained because
  it's still used in some other places.)

* To determine whether to perform an evaluation, compare the hash of
  the current inputs with the inputs of the previous jobset
  evaluation, rather than checking if there was ever an evaluation
  with those inputs.  This way, if the inputs of an evaluation change
  back to a previous state, we get a new jobset evaluation in the
  database (and thus the latest jobset evaluation correctly represents
  the latest state of the jobset).

* Improve performance by removing some unnecessary operations and
  adding an index.
2012-03-07 18:48:10 +01:00
Eelco Dolstra
33ba3cf330 Fix a race condition in hydra-update-gc-roots
Since it read the actual roots after determining the set of desired
roots, there was a possibility that it would delete roots added by
hydra-evaluator or hydra-build while hydra-update-gc-roots was
running.  This could cause a derivation to be garbage-collected before
the build was performed, for instance.  Now the actual roots are read
first, so any root added after that time is not deleted.
2012-03-07 15:12:47 +01:00
Eelco Dolstra
5771fb33e0 Speed up hydra-update-gc-roots
The hydra-update-gc-roots script is taking around 95 minutes on our
Hydra instance (though a lot of that is I/O wait).  This patch
significantly reduces the number of database queries.  In particular,
the N most recent successful builds for each job in a jobset are now
determined in a single query.  Also, it removes the calls to
readlink().
2012-03-07 15:02:31 +01:00
Eelco Dolstra
a07a2e6687 Drop the merged tables after conversion 2012-03-06 13:42:12 +01:00
Eelco Dolstra
8781c35de7 Don't use a prepared statement for the active build steps query
Prepared statements are sometimes much slower than unprepared
statements, because the planner doesn't have access to the query
parameters.  This is the case for the active build steps query (in
/status), where a prepared statement is three orders of magnitude
slower.  So disable the use of prepared statements in this case.

(Since the query parameters are constant here, it would be nicer if we
could tell DBIx::Class to prepare a statement with those parameters
fixed.  But I don't know an easy way to do so.)
2012-03-06 00:30:43 +01:00
Eelco Dolstra
b061a8cea9 Merge the BuildResultInfo table into the Builds table 2012-03-06 00:30:43 +01:00
Eelco Dolstra
dd2e91e7cc Add hydra-init to the tarball 2012-03-06 00:30:43 +01:00
Eelco Dolstra
c454ecb21c Merge the BuildSchedulingInfo table into the Builds table
This simplifies the code and improves performance since it reduces
the number of joins.
2012-03-06 00:30:43 +01:00
33 changed files with 670 additions and 903 deletions

View File

@@ -10,14 +10,12 @@ use Hydra::Helper::CatalystUtils;
sub getJobStatus { sub getJobStatus {
my ($self, $c) = @_; my ($self, $c) = @_;
my $latest = joinWithResultInfo($c, $c->stash->{jobStatus});
my $maintainer = $c->request->params->{"maintainer"}; my $maintainer = $c->request->params->{"maintainer"};
$latest = $latest->search( my $latest = $c->stash->{jobStatus}->search(
defined $maintainer ? { maintainers => { like => "%$maintainer%" } } : {}, defined $maintainer ? { maintainers => { like => "%$maintainer%" } } : {},
{ '+select' => ["me.statusChangeId", "me.statusChangeTime", "resultInfo.buildStatus"] { '+select' => ["me.statusChangeId", "me.statusChangeTime"]
, '+as' => ["statusChangeId", "statusChangeTime", "buildStatus"] , '+as' => ["statusChangeId", "statusChangeTime"]
, order_by => "coalesce(statusChangeTime, 0) desc" , order_by => "coalesce(statusChangeTime, 0) desc"
}); });
@@ -43,7 +41,7 @@ sub errors : Chained('get_builds') PathPart Args(0) {
[$c->stash->{allJobs}->search({errormsg => {'!=' => ''}})] [$c->stash->{allJobs}->search({errormsg => {'!=' => ''}})]
if defined $c->stash->{allJobs}; if defined $c->stash->{allJobs};
$c->stash->{brokenBuilds} = $c->stash->{brokenBuilds} =
[getJobStatus($self, $c)->search({'resultInfo.buildstatus' => {'!=' => 0}})]; [getJobStatus($self, $c)->search({buildStatus => {'!=' => 0}})];
} }
@@ -64,11 +62,10 @@ sub all : Chained('get_builds') PathPart {
$c->stash->{resultsPerPage} = $resultsPerPage; $c->stash->{resultsPerPage} = $resultsPerPage;
$c->stash->{totalBuilds} = $nrBuilds; $c->stash->{totalBuilds} = $nrBuilds;
$c->stash->{builds} = [ joinWithResultInfo($c, $c->stash->{allBuilds})->search( $c->stash->{builds} = [ $c->stash->{allBuilds}->search(
{ finished => 1 }, { finished => 1 },
{ '+select' => ["resultInfo.buildStatus"] { order_by => "timestamp DESC"
, '+as' => ["buildStatus"] , columns => [@buildListColumns]
, order_by => "timestamp DESC"
, rows => $resultsPerPage , rows => $resultsPerPage
, page => $page }) ]; , page => $page }) ];
} }
@@ -79,12 +76,10 @@ sub nix : Chained('get_builds') PathPart('channel') CaptureArgs(1) {
eval { eval {
if ($channelName eq "latest") { if ($channelName eq "latest") {
$c->stash->{channelName} = $c->stash->{channelBaseName} . "-latest"; $c->stash->{channelName} = $c->stash->{channelBaseName} . "-latest";
getChannelData($c, scalar($c->stash->{latestSucceeded})); $c->stash->{channelBuilds} = $c->stash->{latestSucceeded}
->search_literal("exists (select 1 from buildproducts where build = me.id and type = 'nix-build')")
->search({}, { columns => [@buildListColumns, 'drvpath', 'outpath', 'description', 'homepage'] });
} }
#elsif ($channelName eq "all") {
# $c->stash->{channelName} = $c->stash->{channelBaseName} . "-all";
# getChannelData($c, scalar($c->stash->{allBuilds}));
#}
else { else {
notFound($c, "Unknown channel `$channelName'."); notFound($c, "Unknown channel `$channelName'.");
} }
@@ -97,8 +92,8 @@ sub nix : Chained('get_builds') PathPart('channel') CaptureArgs(1) {
sub latest : Chained('get_builds') PathPart('latest') { sub latest : Chained('get_builds') PathPart('latest') {
my ($self, $c, @rest) = @_; my ($self, $c, @rest) = @_;
my ($latest) = joinWithResultInfo($c, $c->stash->{allBuilds}) my ($latest) = $c->stash->{allBuilds}->search(
->search({finished => 1, buildstatus => 0}, {order_by => ["isCurrent DESC", "timestamp DESC"]}); {finished => 1, buildstatus => 0}, {order_by => ["isCurrent DESC", "timestamp DESC"]});
notFound($c, "There is no successful build to redirect to.") unless defined $latest; notFound($c, "There is no successful build to redirect to.") unless defined $latest;
@@ -112,8 +107,8 @@ sub latest_for : Chained('get_builds') PathPart('latest-for') {
notFound($c, "You need to specify a platform type in the URL.") unless defined $system; notFound($c, "You need to specify a platform type in the URL.") unless defined $system;
my ($latest) = joinWithResultInfo($c, $c->stash->{allBuilds}) my ($latest) = $c->stash->{allBuilds}->search(
->search({finished => 1, buildstatus => 0, system => $system}, {order_by => ["isCurrent DESC", "timestamp DESC"]}); {finished => 1, buildstatus => 0, system => $system}, {order_by => ["isCurrent DESC", "timestamp DESC"]});
notFound($c, "There is no successful build for platform `$system' to redirect to.") unless defined $latest; notFound($c, "There is no successful build for platform `$system' to redirect to.") unless defined $latest;

View File

@@ -3,14 +3,45 @@ package Hydra::Base::Controller::NixChannel;
use strict; use strict;
use warnings; use warnings;
use base 'Catalyst::Controller'; use base 'Catalyst::Controller';
use Nix::Store;
use Hydra::Helper::Nix; use Hydra::Helper::Nix;
use Hydra::Helper::CatalystUtils; use Hydra::Helper::CatalystUtils;
sub getChannelData {
my ($c, $checkValidity) = @_;
my @storePaths = ();
foreach my $build ($c->stash->{channelBuilds}->all) {
next if $checkValidity && !isValidPath($build->outpath);
#if (isValidPath($build->drvpath)) {
# # Adding `drvpath' implies adding `outpath' because of the
# # `--include-outputs' flag passed to `nix-store'.
# push @storePaths, $build->drvpath;
#} else {
# push @storePaths, $build->outpath;
#}
push @storePaths, $build->outpath;
my $pkgName = $build->nixname . "-" . $build->system . "-" . $build->id;
$c->stash->{nixPkgs}->{"${pkgName}.nixpkg"} = {build => $build, name => $pkgName};
# Put the system type in the manifest (for top-level paths) as
# a hint to the binary patch generator. (It shouldn't try to
# generate patches between builds for different systems.) It
# would be nice if Nix stored this info for every path but it
# doesn't.
$c->stash->{systemForPath}->{$build->outpath} = $build->system;
};
$c->stash->{storePaths} = [@storePaths];
}
sub closure : Chained('nix') PathPart { sub closure : Chained('nix') PathPart {
my ($self, $c) = @_; my ($self, $c) = @_;
$c->stash->{current_view} = 'NixClosure'; $c->stash->{current_view} = 'NixClosure';
getChannelData($c, 1);
# !!! quick hack; this is to make HEAD requests return the right # !!! quick hack; this is to make HEAD requests return the right
# MIME type. This is set in the view as well, but the view isn't # MIME type. This is set in the view as well, but the view isn't
# called for HEAD requests. There should be a cleaner solution... # called for HEAD requests. There should be a cleaner solution...
@@ -22,18 +53,23 @@ sub manifest : Chained('nix') PathPart("MANIFEST") Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
$c->stash->{current_view} = 'NixManifest'; $c->stash->{current_view} = 'NixManifest';
$c->stash->{narBase} = $c->uri_for($c->controller('Root')->action_for("nar")); $c->stash->{narBase} = $c->uri_for($c->controller('Root')->action_for("nar"));
getChannelData($c, 1);
} }
sub pkg : Chained('nix') PathPart Args(1) { sub pkg : Chained('nix') PathPart Args(1) {
my ($self, $c, $pkgName) = @_; my ($self, $c, $pkgName) = @_;
my $pkg = $c->stash->{nixPkgs}->{$pkgName}; if (!$c->stash->{build}) {
$pkgName =~ /-(\d+)\.nixpkg$/ or notFound($c, "Bad package name.");
$c->stash->{build} = $c->stash->{channelBuilds}->find({ id => $1 })
|| notFound($c, "No such package in this channel.");
}
notFound($c, "Unknown Nix package `$pkgName'.") if (!isValidPath($c->stash->{build}->outpath)) {
unless defined $pkg; $c->response->status(410); # "Gone"
error($c, "Build " . $c->stash->{build}->id . " is no longer available.");
$c->stash->{build} = $pkg->{build}; }
$c->stash->{manifestUri} = $c->uri_for($self->action_for("manifest"), $c->req->captures); $c->stash->{manifestUri} = $c->uri_for($self->action_for("manifest"), $c->req->captures);
@@ -46,26 +82,30 @@ sub pkg : Chained('nix') PathPart Args(1) {
sub nixexprs : Chained('nix') PathPart('nixexprs.tar.bz2') Args(0) { sub nixexprs : Chained('nix') PathPart('nixexprs.tar.bz2') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
$c->stash->{current_view} = 'NixExprs'; $c->stash->{current_view} = 'NixExprs';
getChannelData($c, 1);
} }
sub name { sub name {
my ($build) = @_; my ($build) = @_;
return $build->get_column('releasename') || $build->nixname; return $build->releasename || $build->nixname;
} }
sub sortPkgs { sub sortPkgs {
# Sort by name, then timestamp. # Sort by name, then id.
return sort return sort
{ lc(name($a->{build})) cmp lc(name($b->{build})) { lc(name($a->{build})) cmp lc(name($b->{build}))
or $a->{build}->timestamp <=> $b->{build}->timestamp or $a->{build}->id <=> $b->{build}->id } @_;
} @_;
} }
sub channel_contents : Chained('nix') PathPart('') Args(0) { sub channel_contents : Chained('nix') PathPart('') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
# Optimistically assume that none of the packages have been
# garbage-collected. That should be true for the "latest"
# channel.
getChannelData($c, 0);
$c->stash->{template} = 'channel-contents.tt'; $c->stash->{template} = 'channel-contents.tt';
$c->stash->{nixPkgs} = [sortPkgs (values %{$c->stash->{nixPkgs}})]; $c->stash->{nixPkgs} = [sortPkgs (values %{$c->stash->{nixPkgs}})];
} }

View File

@@ -15,11 +15,13 @@ use File::Slurp;
# !!! Rewrite this to use View::JSON. # !!! Rewrite this to use View::JSON.
sub api : Chained('/') PathPart('api') CaptureArgs(0) { sub api : Chained('/') PathPart('api') CaptureArgs(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
$c->response->content_type('application/json'); $c->response->content_type('application/json');
} }
sub projectToHash { sub projectToHash {
my ($project) = @_; my ($project) = @_;
return { return {
@@ -28,14 +30,15 @@ sub projectToHash {
}; };
} }
sub projects : Chained('api') PathPart('projects') Args(0) { sub projects : Chained('api') PathPart('projects') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
my @projects = $c->model('DB::Projects')->search({hidden => 0}, {order_by => 'name'}) ; my @projects = $c->model('DB::Projects')->search({hidden => 0}, {order_by => 'name'});
my @list ; my @list;
foreach my $p (@projects) { foreach my $p (@projects) {
push @list, projectToHash($p) ; push @list, projectToHash($p);
} }
$c->stash->{'plain'} = { $c->stash->{'plain'} = {
@@ -44,6 +47,7 @@ sub projects : Chained('api') PathPart('projects') Args(0) {
$c->forward('Hydra::View::Plain'); $c->forward('Hydra::View::Plain');
} }
sub buildToHash { sub buildToHash {
my ($build) = @_; my ($build) = @_;
my $result = { my $result = {
@@ -58,7 +62,7 @@ sub buildToHash {
}; };
if($build->finished) { if($build->finished) {
$result->{'buildstatus'} = $build->get_column("buildstatus") ; $result->{'buildstatus'} = $build->get_column("buildstatus");
} else { } else {
$result->{'busy'} = $build->get_column("busy"); $result->{'busy'} = $build->get_column("busy");
$result->{'priority'} = $build->get_column("priority"); $result->{'priority'} = $build->get_column("priority");
@@ -67,28 +71,27 @@ sub buildToHash {
return $result; return $result;
}; };
sub latestbuilds : Chained('api') PathPart('latestbuilds') Args(0) { sub latestbuilds : Chained('api') PathPart('latestbuilds') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
my $nr = $c->request->params->{nr} ; my $nr = $c->request->params->{nr};
error($c, "Parameter not defined!") if !defined $nr; error($c, "Parameter not defined!") if !defined $nr;
my $project = $c->request->params->{project} ; my $project = $c->request->params->{project};
my $jobset = $c->request->params->{jobset} ; my $jobset = $c->request->params->{jobset};
my $job = $c->request->params->{job} ; my $job = $c->request->params->{job};
my $system = $c->request->params->{system} ; my $system = $c->request->params->{system};
my $filter = {finished => 1} ; my $filter = {finished => 1};
$filter->{project} = $project if ! $project eq ""; $filter->{project} = $project if ! $project eq "";
$filter->{jobset} = $jobset if ! $jobset eq ""; $filter->{jobset} = $jobset if ! $jobset eq "";
$filter->{job} = $job if !$job eq ""; $filter->{job} = $job if !$job eq "";
$filter->{system} = $system if !$system eq ""; $filter->{system} = $system if !$system eq "";
my @latest = joinWithResultInfo($c, $c->model('DB::Builds'))->search($filter, {rows => $nr, order_by => ["timestamp DESC"] }); my @latest = $c->model('DB::Builds')->search($filter, {rows => $nr, order_by => ["timestamp DESC"] });
my @list ; my @list;
foreach my $b (@latest) { push @list, buildToHash($_) foreach @latest;
push @list, buildToHash($b) ;
}
$c->stash->{'plain'} = { $c->stash->{'plain'} = {
data => scalar (JSON::Any->objToJson(\@list)) data => scalar (JSON::Any->objToJson(\@list))
@@ -96,6 +99,7 @@ sub latestbuilds : Chained('api') PathPart('latestbuilds') Args(0) {
$c->forward('Hydra::View::Plain'); $c->forward('Hydra::View::Plain');
} }
sub jobsetToHash { sub jobsetToHash {
my ($jobset) = @_; my ($jobset) = @_;
return { return {
@@ -108,10 +112,11 @@ sub jobsetToHash {
}; };
} }
sub jobsets : Chained('api') PathPart('jobsets') Args(0) { sub jobsets : Chained('api') PathPart('jobsets') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
my $projectName = $c->request->params->{project} ; my $projectName = $c->request->params->{project};
error($c, "Parameter 'project' not defined!") if !defined $projectName; error($c, "Parameter 'project' not defined!") if !defined $projectName;
my $project = $c->model('DB::Projects')->find($projectName) my $project = $c->model('DB::Projects')->find($projectName)
@@ -119,10 +124,8 @@ sub jobsets : Chained('api') PathPart('jobsets') Args(0) {
my @jobsets = jobsetOverview($c, $project); my @jobsets = jobsetOverview($c, $project);
my @list ; my @list;
foreach my $j (@jobsets) { push @list, jobsetToHash($_) foreach @jobsets;
push @list, jobsetToHash($j) ;
}
$c->stash->{'plain'} = { $c->stash->{'plain'} = {
data => scalar (JSON::Any->objToJson(\@list)) data => scalar (JSON::Any->objToJson(\@list))
@@ -130,57 +133,59 @@ sub jobsets : Chained('api') PathPart('jobsets') Args(0) {
$c->forward('Hydra::View::Plain'); $c->forward('Hydra::View::Plain');
} }
sub queue : Chained('api') PathPart('queue') Args(0) { sub queue : Chained('api') PathPart('queue') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
my $nr = $c->request->params->{nr} ; my $nr = $c->request->params->{nr};
error($c, "Parameter not defined!") if !defined $nr; error($c, "Parameter not defined!") if !defined $nr;
my @builds = $c->model('DB::Builds')->search({finished => 0}, {rows => $nr, join => ['schedulingInfo'] , order_by => ["busy DESC", "priority DESC", "timestamp"], '+select' => ['schedulingInfo.priority', 'schedulingInfo.busy'], '+as' => ['priority', 'busy'] }); my @builds = $c->model('DB::Builds')->search({finished => 0}, {rows => $nr, order_by => ["busy DESC", "priority DESC", "timestamp"]});
my @list ; my @list;
foreach my $b (@builds) { push @list, buildToHash($_) foreach @builds;
push @list, buildToHash($b) ;
}
$c->stash->{'plain'} = { $c->stash->{'plain'} = {
data => scalar (JSON::Any->objToJson(\@list)) data => scalar (JSON::Any->objToJson(\@list))
}; };
$c->forward('Hydra::View::Plain'); $c->forward('Hydra::View::Plain');
} }
sub nrqueue : Chained('api') PathPart('nrqueue') Args(0) { sub nrqueue : Chained('api') PathPart('nrqueue') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
my $nrQueuedBuilds = $c->model('DB::BuildSchedulingInfo')->count(); my $nrQueuedBuilds = $c->model('DB::Builds')->search({finished => 0})->count();
$c->stash->{'plain'} = { $c->stash->{'plain'} = {
data => " $nrQueuedBuilds" data => "$nrQueuedBuilds"
}; };
$c->forward('Hydra::View::Plain'); $c->forward('Hydra::View::Plain');
} }
sub nrrunning : Chained('api') PathPart('nrrunning') Args(0) { sub nrrunning : Chained('api') PathPart('nrrunning') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
my $nrRunningBuilds = $c->model('DB::BuildSchedulingInfo')->search({ busy => 1 }, {})->count(); my $nrRunningBuilds = $c->model('DB::Builds')->search({finished => 0, busy => 1 })->count();
$c->stash->{'plain'} = { $c->stash->{'plain'} = {
data => " $nrRunningBuilds" data => "$nrRunningBuilds"
}; };
$c->forward('Hydra::View::Plain'); $c->forward('Hydra::View::Plain');
} }
sub nrbuilds : Chained('api') PathPart('nrbuilds') Args(0) { sub nrbuilds : Chained('api') PathPart('nrbuilds') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
my $nr = $c->request->params->{nr} ; my $nr = $c->request->params->{nr};
my $period = $c->request->params->{period} ; my $period = $c->request->params->{period};
error($c, "Parameter not defined!") if !defined $nr || !defined $period; error($c, "Parameter not defined!") if !defined $nr || !defined $period;
my $base; my $base;
my $project = $c->request->params->{project} ; my $project = $c->request->params->{project};
my $jobset = $c->request->params->{jobset} ; my $jobset = $c->request->params->{jobset};
my $job = $c->request->params->{job} ; my $job = $c->request->params->{job};
my $system = $c->request->params->{system} ; my $system = $c->request->params->{system};
my $filter = {finished => 1} ; my $filter = {finished => 1};
$filter->{project} = $project if ! $project eq ""; $filter->{project} = $project if ! $project eq "";
$filter->{jobset} = $jobset if ! $jobset eq ""; $filter->{jobset} = $jobset if ! $jobset eq "";
$filter->{job} = $job if !$job eq ""; $filter->{job} = $job if !$job eq "";
@@ -189,11 +194,9 @@ sub nrbuilds : Chained('api') PathPart('nrbuilds') Args(0) {
$base = 60*60 if($period eq "hour"); $base = 60*60 if($period eq "hour");
$base = 24*60*60 if($period eq "day"); $base = 24*60*60 if($period eq "day");
my @stats = $c->model('DB::Builds')->search($filter, {select => [{ count => "*" }], as => ["nr"], group_by => ["timestamp - timestamp % $base"], order_by => "timestamp - timestamp % $base DESC", rows => $nr}) ; my @stats = $c->model('DB::Builds')->search($filter, {select => [{ count => "*" }], as => ["nr"], group_by => ["timestamp - timestamp % $base"], order_by => "timestamp - timestamp % $base DESC", rows => $nr});
my @arr ; my @arr;
foreach my $d (@stats) { push @arr, int($_->get_column("nr")) foreach @stats;
push @arr, int($d->get_column("nr"));
}
@arr = reverse(@arr); @arr = reverse(@arr);
$c->stash->{'plain'} = { $c->stash->{'plain'} = {
@@ -202,38 +205,40 @@ sub nrbuilds : Chained('api') PathPart('nrbuilds') Args(0) {
$c->forward('Hydra::View::Plain'); $c->forward('Hydra::View::Plain');
} }
sub scmdiff : Chained('api') PathPart('scmdiff') Args(0) { sub scmdiff : Chained('api') PathPart('scmdiff') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
my $uri = $c->request->params->{uri} ; my $uri = $c->request->params->{uri};
my $type = $c->request->params->{type} ; my $type = $c->request->params->{type};
my $rev1 = $c->request->params->{rev1} ; my $rev1 = $c->request->params->{rev1};
my $rev2 = $c->request->params->{rev2} ; my $rev2 = $c->request->params->{rev2};
my $branch; my $branch;
die("invalid revisions: [$rev1] [$rev2]") if $rev1 !~ m/^[a-zA-Z0-9_.]+$/ || $rev2 !~ m/^[a-zA-Z0-9_.]+$/ ; die("invalid revisions: [$rev1] [$rev2]") if $rev1 !~ m/^[a-zA-Z0-9_.]+$/ || $rev2 !~ m/^[a-zA-Z0-9_.]+$/;
my $diff = ""; my $diff = "";
if($type eq "hg") { if ($type eq "hg") {
my $clonePath = scmPath . "/" . sha256_hex($uri); my $clonePath = scmPath . "/" . sha256_hex($uri);
die if ! -d $clonePath; die if ! -d $clonePath;
$branch = `(cd $clonePath ; hg log --template '{branch}' -r $rev2)`; $branch = `(cd $clonePath; hg log --template '{branch}' -r $rev2)`;
$diff .= `(cd $clonePath ; hg log -r $rev1 -r $rev2 -b $branch)`; $diff .= `(cd $clonePath; hg log -r $rev1 -r $rev2 -b $branch)`;
$diff .= `(cd $clonePath ; hg diff -r $rev1:$rev2)`; $diff .= `(cd $clonePath; hg diff -r $rev1:$rev2)`;
} elsif ($type eq "git") { } elsif ($type eq "git") {
my $clonePath = scmPath . "/" . sha256_hex($uri); my $clonePath = scmPath . "/" . sha256_hex($uri);
die if ! -d $clonePath; die if ! -d $clonePath;
$diff .= `(cd $clonePath ; git log $rev1..$rev2)`; $diff .= `(cd $clonePath; git log $rev1..$rev2)`;
$diff .= `(cd $clonePath ; git diff $rev1..$rev2)`; $diff .= `(cd $clonePath; git diff $rev1..$rev2)`;
} }
$c->stash->{'plain'} = { data => (scalar $diff) || " " }; $c->stash->{'plain'} = { data => (scalar $diff) || " " };
$c->forward('Hydra::View::Plain'); $c->forward('Hydra::View::Plain');
} }
sub readNormalizedLog { sub readNormalizedLog {
my ($file) = @_; my ($file) = @_;
my $pipe = (-f "$file.bz2" ? "cat $file.bz2 | bzip2 -d" : "cat $file") ; my $pipe = (-f "$file.bz2" ? "cat $file.bz2 | bzip2 -d" : "cat $file");
my $res = `$pipe`; my $res = `$pipe`;
$res =~ s/\/nix\/store\/[a-z0-9]*-/\/nix\/store\/...-/g; $res =~ s/\/nix\/store\/[a-z0-9]*-/\/nix\/store\/...-/g;
@@ -242,6 +247,7 @@ sub readNormalizedLog {
return $res; return $res;
} }
sub logdiff : Chained('api') PathPart('logdiff') Args(2) { sub logdiff : Chained('api') PathPart('logdiff') Args(2) {
my ($self, $c, $buildid1, $buildid2) = @_; my ($self, $c, $buildid1, $buildid2) = @_;
@@ -254,9 +260,9 @@ sub logdiff : Chained('api') PathPart('logdiff') Args(2) {
notFound($c, "Build with ID $buildid2 doesn't exist.") notFound($c, "Build with ID $buildid2 doesn't exist.")
if !defined $build2; if !defined $build2;
if (-f $build1->resultInfo->logfile && -f $build2->resultInfo->logfile) { if (-f $build1->logfile && -f $build2->logfile) {
my $logtext1 = readNormalizedLog($build1->resultInfo->logfile); my $logtext1 = readNormalizedLog($build1->logfile);
my $logtext2 = readNormalizedLog($build2->resultInfo->logfile); my $logtext2 = readNormalizedLog($build2->logfile);
$diff = diff \$logtext1, \$logtext2; $diff = diff \$logtext1, \$logtext2;
} else { } else {
$c->response->status(404); $c->response->status(404);
@@ -267,4 +273,5 @@ sub logdiff : Chained('api') PathPart('logdiff') Args(2) {
$c->forward('Hydra::View::Plain'); $c->forward('Hydra::View::Plain');
} }
1; 1;

View File

@@ -57,9 +57,9 @@ sub index : Chained('admin') PathPart('') Args(0) {
, '+as' => ['idle'] , '+as' => ['idle']
})]; })];
$c->stash->{steps} = [ $c->model('DB::BuildSteps')->search( $c->stash->{steps} = [ $c->model('DB::BuildSteps')->search(
{ 'me.busy' => 1, 'schedulingInfo.busy' => 1 }, { finished => 0, 'me.busy' => 1, 'build.busy' => 1, },
{ join => [ 'schedulingInfo', 'build' ] { join => [ 'build' ]
, order_by => [ 'machine' ] , order_by => [ 'machine', 'stepnr' ]
} ) ]; } ) ];
$c->stash->{template} = 'admin.tt'; $c->stash->{template} = 'admin.tt';
} }
@@ -296,13 +296,15 @@ sub machine_disable : Chained('machine') PathPart('disable') Args(0) {
sub clear_queue_non_current : Chained('admin') Path('clear-queue-non-current') Args(0) { sub clear_queue_non_current : Chained('admin') Path('clear-queue-non-current') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
$c->model('DB::Builds')->search({iscurrent => 0, busy => 0}, { join => 'schedulingInfo' })->delete_all; # !!! Mark the builds as cancelled instead.
$c->model('DB::Builds')->search({finished => 0, iscurrent => 0, busy => 0})->delete_all;
$c->res->redirect("/admin"); $c->res->redirect("/admin");
} }
sub clear_queue : Chained('admin') Path('clear-queue') Args(0) { sub clear_queue : Chained('admin') Path('clear-queue') Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
$c->model('DB::Builds')->search({busy => 0}, { join => 'schedulingInfo' })->delete_all; # !!! Mark the builds as cancelled instead.
$c->model('DB::Builds')->search({finished => 0, busy => 0})->delete_all;
$c->res->redirect("/admin"); $c->res->redirect("/admin");
} }

View File

@@ -41,28 +41,27 @@ sub view_build : Chained('build') PathPart('') Args(0) {
$c->stash->{drvAvailable} = isValidPath $build->drvpath; $c->stash->{drvAvailable} = isValidPath $build->drvpath;
$c->stash->{flashMsg} = $c->flash->{buildMsg}; $c->stash->{flashMsg} = $c->flash->{buildMsg};
my $pathHash = $c->stash->{available} ? queryPathHash($build->outpath) : "Not available"; $c->stash->{pathHash} = $c->stash->{available} ? queryPathHash($build->outpath) : undef;
$c->stash->{pathHash} = $pathHash;
if (!$build->finished && $build->schedulingInfo->busy) { if (!$build->finished && $build->busy) {
my $logfile = $build->schedulingInfo->logfile; my $logfile = $build->logfile;
$c->stash->{logtext} = `cat $logfile` if defined $logfile && -e $logfile; $c->stash->{logtext} = `cat $logfile` if defined $logfile && -e $logfile;
} }
if (defined $build->resultInfo && $build->resultInfo->iscachedbuild) { if ($build->finished && $build->iscachedbuild) {
(my $cachedBuildStep) = $c->model('DB::BuildSteps')->search({ outpath => $build->outpath }, {}) ; (my $cachedBuildStep) = $c->model('DB::BuildSteps')->search({ outpath => $build->outpath }, {}) ;
$c->stash->{cachedBuild} = $cachedBuildStep->build if defined $cachedBuildStep; $c->stash->{cachedBuild} = $cachedBuildStep->build if defined $cachedBuildStep;
} }
(my $lastBuildStep) = $build->buildsteps->search({},{order_by => "stepnr DESC", rows => 1}); (my $lastBuildStep) = $build->buildsteps->search({},{order_by => "stepnr DESC", rows => 1});
my $path = defined $lastBuildStep ? $lastBuildStep->logfile : "" ; my $path = defined $lastBuildStep ? $lastBuildStep->logfile : "" ;
if (defined $build->resultInfo && ($build->resultInfo->buildstatus == 1 || $build->resultInfo->buildstatus == 6) && !($path eq "") && -f $lastBuildStep->logfile) { if ($build->finished && ($build->buildstatus == 1 || $build->buildstatus == 6) && !($path eq "") && -f $lastBuildStep->logfile) {
my $logtext = `tail -n 50 $path`; my $logtext = `tail -n 50 $path`;
$c->stash->{logtext} = removeAsciiEscapes($logtext); $c->stash->{logtext} = removeAsciiEscapes($logtext);
} }
if($build->finished) { if ($build->finished) {
$c->stash->{prevBuilds} = [joinWithResultInfo($c, $c->model('DB::Builds'))->search( $c->stash->{prevBuilds} = [$c->model('DB::Builds')->search(
{ project => $c->stash->{project}->name { project => $c->stash->{project}->name
, jobset => $c->stash->{build}->jobset->name , jobset => $c->stash->{build}->jobset->name
, job => $c->stash->{build}->job->name , job => $c->stash->{build}->job->name
@@ -81,13 +80,13 @@ sub view_build : Chained('build') PathPart('') Args(0) {
]; ];
} }
my $r = joinWithResultInfo( $c, $c->model('DB::Builds'))->search( my $maxRelated = 100;
{ eval => { -in => $build->jobsetevalmembers->get_column('eval')->as_query } } my $r = $c->model('DB::Builds')->search(
, { join => 'jobsetevalmembers', order_by => [ 'project', 'jobset', 'job'], distinct => 1 } { eval => { -in => $build->jobsetevalmembers->search({}, {rows => 1})->get_column('eval')->as_query } },
); { join => 'jobsetevalmembers', order_by => [ 'project', 'jobset', 'job'], distinct => 1, rows => $maxRelated + 1 }
if ($r->count <= 100) { );
$c->stash->{relatedbuilds} = [$r->all]; $c->stash->{relatedbuilds} = [$r->all];
} delete $c->stash->{relatedbuilds} if scalar(@{$c->stash->{relatedbuilds}}) > $maxRelated;
} }
@@ -106,9 +105,9 @@ sub view_nixlog : Chained('build') PathPart('nixlog') {
sub view_log : Chained('build') PathPart('log') { sub view_log : Chained('build') PathPart('log') {
my ($self, $c, $mode) = @_; my ($self, $c, $mode) = @_;
error($c, "Build didn't produce a log.") if !defined $c->stash->{build}->resultInfo->logfile; error($c, "Build didn't produce a log.") if !defined $c->stash->{build}->logfile;
showLog($c, $c->stash->{build}->resultInfo->logfile, $mode); showLog($c, $c->stash->{build}->logfile, $mode);
} }
@@ -141,7 +140,7 @@ sub showLog {
my $url = $c->request->uri->as_string; my $url = $c->request->uri->as_string;
$url =~ s/tail-reload/tail/g; $url =~ s/tail-reload/tail/g;
$c->stash->{url} = $url; $c->stash->{url} = $url;
$c->stash->{reload} = defined $c->stash->{build}->schedulingInfo && $c->stash->{build}->schedulingInfo->busy; $c->stash->{reload} = !$c->stash->{build}->finished && $c->stash->{build}->busy;
$c->stash->{title} = ""; $c->stash->{title} = "";
$c->stash->{contents} = (scalar `$pipestart | tail -n 50`) || " "; $c->stash->{contents} = (scalar `$pipestart | tail -n 50`) || " ";
$c->stash->{template} = 'plain-reload.tt'; $c->stash->{template} = 'plain-reload.tt';
@@ -371,10 +370,7 @@ sub nix : Chained('build') PathPart('nix') CaptureArgs(0) {
notFound($c, "Path " . $build->outpath . " is no longer available.") notFound($c, "Path " . $build->outpath . " is no longer available.")
unless isValidPath($build->outpath); unless isValidPath($build->outpath);
$c->stash->{storePaths} = [$build->outpath]; $c->stash->{channelBuilds} = $c->model('DB::Builds')->search({id => $build->id});
my $pkgName = $build->nixname . "-" . $build->system;
$c->stash->{nixPkgs} = {"${pkgName}.nixpkg" => {build => $build, name => $pkgName}};
} }
@@ -406,21 +402,16 @@ sub cancel : Chained('build') PathPart Args(0) {
txn_do($c->model('DB')->schema, sub { txn_do($c->model('DB')->schema, sub {
error($c, "This build cannot be cancelled.") error($c, "This build cannot be cancelled.")
if $build->finished || $build->schedulingInfo->busy; if $build->finished || $build->busy;
# !!! Actually, it would be nice to be able to cancel busy # !!! Actually, it would be nice to be able to cancel busy
# builds as well, but we would have to send a signal or # builds as well, but we would have to send a signal or
# something to the build process. # something to the build process.
$build->update({finished => 1, timestamp => time}); $build->update(
{ finished => 1, busy => 0, timestamp => time
$c->model('DB::BuildResultInfo')->create( , iscachedbuild => 0, buildstatus => 4 # = cancelled
{ id => $build->id
, iscachedbuild => 0
, buildstatus => 4 # = cancelled
}); });
$build->schedulingInfo->delete;
}); });
$c->flash->{buildMsg} = "Build has been cancelled."; $c->flash->{buildMsg} = "Build has been cancelled.";
@@ -441,7 +432,7 @@ sub keep : Chained('build') PathPart Args(1) {
registerRoot $build->outpath if $newStatus == 1; registerRoot $build->outpath if $newStatus == 1;
txn_do($c->model('DB')->schema, sub { txn_do($c->model('DB')->schema, sub {
$build->resultInfo->update({keep => int $newStatus}); $build->update({keep => int $newStatus});
}); });
$c->flash->{buildMsg} = $c->flash->{buildMsg} =
@@ -541,7 +532,7 @@ sub clone_submit : Chained('build') PathPart('clone/submit') Args(0) {
my %currentBuilds; my %currentBuilds;
my $newBuild = checkBuild( my $newBuild = checkBuild(
$c->model('DB'), $build->project, $build->jobset, $c->model('DB'), $build->project, $build->jobset,
$inputInfo, $nixExprInput, $job, \%currentBuilds); $inputInfo, $nixExprInput, $job, \%currentBuilds, undef);
error($c, "This build has already been performed.") unless $newBuild; error($c, "This build has already been performed.") unless $newBuild;

View File

@@ -25,20 +25,21 @@ sub overview : Chained('job') PathPart('') Args(0) {
#getBuildStats($c, scalar $c->stash->{job}->builds); #getBuildStats($c, scalar $c->stash->{job}->builds);
$c->stash->{currentBuilds} = [$c->stash->{job}->builds->search({iscurrent => 1}, { join => 'resultInfo', '+select' => ["resultInfo.releasename", "resultInfo.buildStatus"] $c->stash->{currentBuilds} = [$c->stash->{job}->builds->search({finished => 1, iscurrent => 1}, { order_by => 'system' })];
, '+as' => ["releasename", "buildStatus"], order_by => 'system' })];
$c->stash->{lastBuilds} = $c->stash->{lastBuilds} =
[ $c->stash->{job}->builds->search({ finished => 1 }, [ $c->stash->{job}->builds->search({ finished => 1 },
{ join => 'resultInfo', { order_by => 'timestamp DESC', rows => 10, columns => [@buildListColumns] }) ];
, '+select' => ["resultInfo.releasename", "resultInfo.buildStatus"]
, '+as' => ["releasename", "buildStatus"]
, order_by => 'timestamp DESC', rows => 10
}) ];
$c->stash->{runningBuilds} = [$c->stash->{job}->builds->search({busy => 1}, { join => ['schedulingInfo', 'project'] , order_by => ["priority DESC", "timestamp"] $c->stash->{runningBuilds} = [
, '+select' => ['project.enabled', 'schedulingInfo.priority', 'schedulingInfo.disabled', 'schedulingInfo.busy'] $c->stash->{job}->builds->search(
, '+as' => ['enabled', 'priority', 'disabled', 'busy'] })]; { busy => 1 },
{ join => ['project']
, order_by => ["priority DESC", "timestamp"]
, '+select' => ['project.enabled']
, '+as' => ['enabled']
}
) ];
$c->stash->{systems} = [$c->stash->{job}->builds->search({iscurrent => 1}, {select => ["system"], distinct => 1})]; $c->stash->{systems} = [$c->stash->{job}->builds->search({iscurrent => 1}, {select => ["system"], distinct => 1})];
} }

View File

@@ -65,10 +65,10 @@ sub jobsetIndex {
my @as = (); my @as = ();
push(@select, "job"); push(@as, "job"); push(@select, "job"); push(@as, "job");
foreach my $system (@systems) { foreach my $system (@systems) {
push(@select, "(SELECT buildstatus FROM BuildResultInfo bri NATURAL JOIN Builds b WHERE b.id = (SELECT MAX(id) FROM Builds t WHERE t.project = me.project AND t.jobset = me.jobset AND t.job = me.job AND t.system = '$system' AND t.iscurrent = 1 ))"); push(@select, "(select buildstatus from Builds b where b.id = (select max(id) from Builds t where t.project = me.project and t.jobset = me.jobset and t.job = me.job and t.system = '$system' and t.iscurrent = 1 ))");
push(@as, $system); push(@as, $system);
push(@select, "(SELECT b.id FROM BuildResultInfo bri NATURAL JOIN Builds b WHERE b.id = (SELECT MAX(id) FROM Builds t WHERE t.project = me.project AND t.jobset = me.jobset AND t.job = me.job AND t.system = '$system' AND t.iscurrent = 1 ))"); push(@select, "(select b.id from Builds b where b.id = (select max(id) from Builds t where t.project = me.project and t.jobset = me.jobset and t.job = me.job and t.system = '$system' and t.iscurrent = 1 ))");
push(@as, $system."-build"); push(@as, "$system-build");
} }
$c->stash->{activeJobsStatus} = $c->stash->{activeJobsStatus} =
[ $c->model('DB')->resultset('ActiveJobsForJobset')->search( [ $c->model('DB')->resultset('ActiveJobsForJobset')->search(
@@ -81,13 +81,9 @@ sub jobsetIndex {
} }
# Last builds for jobset. # Last builds for jobset.
my $tmp = $c->stash->{jobset}->builds;
$c->stash->{lastBuilds} = $c->stash->{lastBuilds} =
[ joinWithResultInfo($c, $tmp)->search({ finished => 1 }, [ $c->stash->{jobset}->builds->search({ finished => 1 },
{ order_by => "timestamp DESC", rows => 5 { order_by => "timestamp DESC", rows => 5, columns => [@buildListColumns] }) ];
, '+select' => ["resultInfo.buildStatus"]
, '+as' => ["buildStatus"]
}) ];
} }

View File

@@ -22,8 +22,8 @@ sub begin :Private {
$c->stash->{tracker} = $ENV{"HYDRA_TRACKER"} ; $c->stash->{tracker} = $ENV{"HYDRA_TRACKER"} ;
if (scalar(@args) == 0 || $args[0] ne "static") { if (scalar(@args) == 0 || $args[0] ne "static") {
$c->stash->{nrRunningBuilds} = $c->model('DB::BuildSchedulingInfo')->search({ busy => 1 }, {})->count(); $c->stash->{nrRunningBuilds} = $c->model('DB::Builds')->search({ finished => 0, busy => 1 }, {})->count();
$c->stash->{nrQueuedBuilds} = $c->model('DB::BuildSchedulingInfo')->count(); $c->stash->{nrQueuedBuilds} = $c->model('DB::Builds')->search({ finished => 0 })->count();
} }
} }
@@ -74,7 +74,7 @@ sub queue :Local {
my ($self, $c) = @_; my ($self, $c) = @_;
$c->stash->{template} = 'queue.tt'; $c->stash->{template} = 'queue.tt';
$c->stash->{queue} = [$c->model('DB::Builds')->search( $c->stash->{queue} = [$c->model('DB::Builds')->search(
{finished => 0}, {join => ['schedulingInfo', 'project'] , order_by => ["priority DESC", "timestamp"], '+select' => ['project.enabled', 'schedulingInfo.priority', 'schedulingInfo.disabled', 'schedulingInfo.busy'], '+as' => ['enabled', 'priority', 'disabled', 'busy'] })]; {finished => 0}, { join => ['project'], order_by => ["priority DESC", "timestamp"], columns => [@buildListColumns], '+select' => ['project.enabled'], '+as' => ['enabled'] })];
$c->stash->{flashMsg} = $c->flash->{buildMsg}; $c->stash->{flashMsg} = $c->flash->{buildMsg};
} }
@@ -86,21 +86,18 @@ sub timeline :Local {
$pit = $pit-(24*60*60)-1; $pit = $pit-(24*60*60)-1;
$c->stash->{template} = 'timeline.tt'; $c->stash->{template} = 'timeline.tt';
$c->stash->{builds} = [$c->model('DB::Builds')->search( $c->stash->{builds} = [ $c->model('DB::Builds')->search
{finished => 1, stoptime => { '>' => $pit } } ( { finished => 1, stoptime => { '>' => $pit } }
, { join => 'resultInfo' , { order_by => ["starttime"] }
, order_by => ["starttime"] ) ];
, '+select' => [ 'resultInfo.starttime', 'resultInfo.stoptime', 'resultInfo.buildstatus' ]
, '+as' => [ 'starttime', 'stoptime', 'buildstatus' ]
})];
} }
sub status :Local { sub status :Local {
my ($self, $c) = @_; my ($self, $c) = @_;
$c->stash->{steps} = [ $c->model('DB::BuildSteps')->search( $c->stash->{steps} = [ $c->model('DB::BuildSteps')->search(
{ 'me.busy' => 1, 'schedulingInfo.busy' => 1 }, { 'me.busy' => 1, 'build.finished' => 0, 'build.busy' => 1 },
{ join => [ 'schedulingInfo', 'build' ] { join => [ 'build' ]
, order_by => [ 'machine' ] , order_by => [ 'machine' ]
} ) ]; } ) ];
} }

View File

@@ -153,10 +153,7 @@ sub result : Chained('view') PathPart('') {
# Note: we don't actually check whether $id is a primary build, # Note: we don't actually check whether $id is a primary build,
# but who cares? # but who cares?
my $primaryBuild = $c->stash->{project}->builds->find($id, my $primaryBuild = $c->stash->{project}->builds->find($id)
{ join => 'resultInfo',
, '+select' => ["resultInfo.releasename", "resultInfo.buildstatus"]
, '+as' => ["releasename", "buildstatus"] })
or error($c, "Build $id doesn't exist."); or error($c, "Build $id doesn't exist.");
my $result = getViewResult($primaryBuild, $c->stash->{jobs}); my $result = getViewResult($primaryBuild, $c->stash->{jobs});

View File

@@ -17,6 +17,7 @@ our @ISA = qw(Exporter);
our @EXPORT = qw( our @EXPORT = qw(
fetchInput evalJobs checkBuild inputsToArgs captureStdoutStderr fetchInput evalJobs checkBuild inputsToArgs captureStdoutStderr
getReleaseName getBuildLog addBuildProducts restartBuild scmPath getReleaseName getBuildLog addBuildProducts restartBuild scmPath
getPrevJobsetEval
); );
@@ -244,7 +245,7 @@ sub fetchInputBuild {
(my $prevBuild) = $db->resultset('Builds')->search( (my $prevBuild) = $db->resultset('Builds')->search(
{ finished => 1, project => $projectName, jobset => $jobsetName { finished => 1, project => $projectName, jobset => $jobsetName
, job => $jobName, buildStatus => 0 }, , job => $jobName, buildStatus => 0 },
{ join => 'resultInfo', order_by => "me.id DESC", rows => 1 { order_by => "me.id DESC", rows => 1
, where => \ attrsToSQL($attrs, "me.id") }); , where => \ attrsToSQL($attrs, "me.id") });
if (!defined $prevBuild || !isValidPath($prevBuild->outpath)) { if (!defined $prevBuild || !isValidPath($prevBuild->outpath)) {
@@ -257,7 +258,7 @@ sub fetchInputBuild {
my $pkgNameRE = "(?:(?:[A-Za-z0-9]|(?:-[^0-9]))+)"; my $pkgNameRE = "(?:(?:[A-Za-z0-9]|(?:-[^0-9]))+)";
my $versionRE = "(?:[A-Za-z0-9\.\-]+)"; my $versionRE = "(?:[A-Za-z0-9\.\-]+)";
my $relName = ($prevBuild->resultInfo->releasename or $prevBuild->nixname); my $relName = ($prevBuild->releasename or $prevBuild->nixname);
my $version = $2 if $relName =~ /^($pkgNameRE)-($versionRE)$/; my $version = $2 if $relName =~ /^($pkgNameRE)-($versionRE)$/;
return return
@@ -294,7 +295,7 @@ sub fetchInputSystemBuild {
my $pkgNameRE = "(?:(?:[A-Za-z0-9]|(?:-[^0-9]))+)"; my $pkgNameRE = "(?:(?:[A-Za-z0-9]|(?:-[^0-9]))+)";
my $versionRE = "(?:[A-Za-z0-9\.\-]+)"; my $versionRE = "(?:[A-Za-z0-9\.\-]+)";
my $relName = ($prevBuild->resultInfo->releasename or $prevBuild->nixname); my $relName = ($prevBuild->releasename or $prevBuild->nixname);
my $version = $2 if $relName =~ /^($pkgNameRE)-($versionRE)$/; my $version = $2 if $relName =~ /^($pkgNameRE)-($versionRE)$/;
my $input = my $input =
@@ -787,9 +788,21 @@ sub addBuildProducts {
} }
# Return the most recent evaluation of the given jobset (that
# optionally had new builds), or undefined if no such evaluation
# exists.
sub getPrevJobsetEval {
my ($db, $jobset, $hasNewBuilds) = @_;
my ($prevEval) = $jobset->jobsetevals(
($hasNewBuilds ? { hasnewbuilds => 1 } : { }),
{ order_by => "id DESC", rows => 1 });
return $prevEval;
}
# Check whether to add the build described by $buildInfo. # Check whether to add the build described by $buildInfo.
sub checkBuild { sub checkBuild {
my ($db, $project, $jobset, $inputInfo, $nixExprInput, $buildInfo, $currentBuilds) = @_; my ($db, $project, $jobset, $inputInfo, $nixExprInput, $buildInfo, $buildIds, $prevEval) = @_;
my $jobName = $buildInfo->{jobName}; my $jobName = $buildInfo->{jobName};
my $drvPath = $buildInfo->{drvPath}; my $drvPath = $buildInfo->{drvPath};
@@ -822,19 +835,36 @@ sub checkBuild {
# !!! Checking $outPath doesn't take meta-attributes into # !!! Checking $outPath doesn't take meta-attributes into
# account. For instance, do we want a new build to be # account. For instance, do we want a new build to be
# scheduled if the meta.maintainers field is changed? # scheduled if the meta.maintainers field is changed?
my @previousBuilds = $job->builds->search({outPath => $outPath, isCurrent => 1}); if (defined $prevEval) {
if (scalar(@previousBuilds) > 0) { my ($prevBuild) = $prevEval->builds->search({ job => $job->name, outPath => $outPath }, { rows => 1, columns => ['id'] });
print STDERR "already scheduled/built\n"; if (defined $prevBuild) {
$currentBuilds->{$_->id} = 0 foreach @previousBuilds; print STDERR " already scheduled/built as build ", $prevBuild->id, "\n";
return; $buildIds->{$prevBuild->id} = 0;
return;
}
} }
my $time = time(); my $time = time();
# Nope, so add it. # Nope, so add it.
my %extraFlags;
if (isValidPath($outPath)) {
%extraFlags =
( finished => 1
, iscachedbuild => 1
, buildstatus => 0
, starttime => $time
, stoptime => $time
, logfile => getBuildLog($drvPath)
, errormsg => ""
, releasename => getReleaseName($outPath)
);
} else {
%extraFlags = ( finished => 0 );
}
$build = $job->builds->create( $build = $job->builds->create(
{ finished => 0 { timestamp => $time
, timestamp => $time
, description => $buildInfo->{description} , description => $buildInfo->{description}
, longdescription => $buildInfo->{longDescription} , longdescription => $buildInfo->{longDescription}
, license => $buildInfo->{license} , license => $buildInfo->{license}
@@ -849,31 +879,19 @@ sub checkBuild {
, iscurrent => 1 , iscurrent => 1
, nixexprinput => $jobset->nixexprinput , nixexprinput => $jobset->nixexprinput
, nixexprpath => $jobset->nixexprpath , nixexprpath => $jobset->nixexprpath
, priority => $priority
, busy => 0
, locker => ""
, %extraFlags
}); });
$buildIds->{$build->id} = 1;
$currentBuilds->{$build->id} = 1; if ($build->iscachedbuild) {
print STDERR " marked as cached build ", $build->id, "\n";
if (isValidPath($outPath)) {
print STDERR "marked as cached build ", $build->id, "\n";
$build->update({ finished => 1 });
$build->create_related('buildresultinfo',
{ iscachedbuild => 1
, buildstatus => 0
, starttime => $time
, stoptime => $time
, logfile => getBuildLog($drvPath)
, errormsg => ""
, releasename => getReleaseName($outPath)
});
addBuildProducts($db, $build); addBuildProducts($db, $build);
} else { } else {
print STDERR "added to queue as build ", $build->id, "\n"; print STDERR " added to queue as build ", $build->id, "\n";
$build->create_related('buildschedulinginfo',
{ priority => $priority
, busy => 0
, locker => ""
});
} }
my %inputs; my %inputs;
@@ -906,24 +924,21 @@ sub restartBuild {
my ($db, $build) = @_; my ($db, $build) = @_;
txn_do($db, sub { txn_do($db, sub {
my $drvpath = $build->drvpath ; my $drvpath = $build->drvpath;
my $outpath = $build->outpath ; my $outpath = $build->outpath;
my $paths = ""; my $paths = "";
foreach my $bs ($build->buildsteps) { foreach my $bs ($build->buildsteps) {
$paths = $paths . " " . $bs->outpath; $paths = $paths . " " . $bs->outpath;
} }
my $r = `nix-store --clear-failed-paths $paths $outpath`; my $r = `nix-store --clear-failed-paths $paths $outpath`;
$build->update({finished => 0, timestamp => time});
$build->resultInfo->delete; $build->update(
{ finished => 0
$db->resultset('BuildSchedulingInfo')->create( , timestamp => time
{ id => $build->id
, priority => 0 # don't know the original priority anymore...
, busy => 0 , busy => 0
, locker => "" , locker => ""
}); });
}); });
} }

View File

@@ -8,20 +8,26 @@ use Hydra::Helper::Nix;
our @ISA = qw(Exporter); our @ISA = qw(Exporter);
our @EXPORT = qw( our @EXPORT = qw(
getBuild getPreviousBuild getNextBuild getPreviousSuccessfulBuild getBuildStats joinWithResultInfo getChannelData getBuild getPreviousBuild getNextBuild getPreviousSuccessfulBuild getBuildStats getChannelData
error notFound error notFound
requireLogin requireProjectOwner requireAdmin requirePost isAdmin isProjectOwner requireLogin requireProjectOwner requireAdmin requirePost isAdmin isProjectOwner
trim trim
$pathCompRE $relPathRE $relNameRE $jobNameRE $systemRE $pathCompRE $relPathRE $relNameRE $jobNameRE $systemRE
@buildListColumns
); );
# Columns from the Builds table needed to render build lists.
Readonly our @buildListColumns => ('id', 'finished', 'timestamp', 'project', 'jobset', 'job', 'nixname', 'system', 'priority', 'busy', 'buildstatus', 'releasename');
sub getBuild { sub getBuild {
my ($c, $id) = @_; my ($c, $id) = @_;
my $build = $c->model('DB::Builds')->find($id); my $build = $c->model('DB::Builds')->find($id);
return $build; return $build;
} }
sub getPreviousBuild { sub getPreviousBuild {
my ($c, $build) = @_; my ($c, $build) = @_;
return undef if !defined $build; return undef if !defined $build;
@@ -38,6 +44,7 @@ sub getPreviousBuild {
return $prevBuild; return $prevBuild;
} }
sub getNextBuild { sub getNextBuild {
my ($c, $build) = @_; my ($c, $build) = @_;
return undef if !defined $build; return undef if !defined $build;
@@ -54,11 +61,12 @@ sub getNextBuild {
return $nextBuild; return $nextBuild;
} }
sub getPreviousSuccessfulBuild { sub getPreviousSuccessfulBuild {
my ($c, $build) = @_; my ($c, $build) = @_;
return undef if !defined $build; return undef if !defined $build;
(my $prevBuild) = joinWithResultInfo($c, $c->model('DB::Builds'))->search( (my $prevBuild) = $c->model('DB::Builds')->search(
{ finished => 1 { finished => 1
, system => $build->system , system => $build->system
, project => $build->project->name , project => $build->project->name
@@ -71,75 +79,26 @@ sub getPreviousSuccessfulBuild {
return $prevBuild; return $prevBuild;
} }
sub getBuildStats { sub getBuildStats {
my ($c, $builds) = @_; my ($c, $builds) = @_;
$c->stash->{finishedBuilds} = $builds->search({finished => 1}) || 0; $c->stash->{finishedBuilds} = $builds->search({finished => 1}) || 0;
$c->stash->{succeededBuilds} = $builds->search( $c->stash->{succeededBuilds} = $builds->search({finished => 1, buildStatus => 0}) || 0;
{finished => 1, buildStatus => 0},
{join => 'resultInfo'}) || 0;
$c->stash->{scheduledBuilds} = $builds->search({finished => 0}) || 0; $c->stash->{scheduledBuilds} = $builds->search({finished => 0}) || 0;
$c->stash->{busyBuilds} = $builds->search( $c->stash->{busyBuilds} = $builds->search({finished => 0, busy => 1}) || 0;
{finished => 0, busy => 1},
{join => 'schedulingInfo'}) || 0;
my $res; my $res;
$res = $builds->search({}, $res = $builds->search({}, {select => {sum => 'stoptime - starttime'}, as => ['sum']})->first;
{join => 'resultInfo', select => {sum => 'stoptime - starttime'}, as => ['sum']})
->first ;
$c->stash->{totalBuildTime} = defined ($res) ? $res->get_column('sum') : 0 ; $c->stash->{totalBuildTime} = defined ($res) ? $res->get_column('sum') : 0 ;
} }
# Add the releaseName and buildStatus attributes from the
# BuildResultInfo table for each build.
sub joinWithResultInfo {
my ($c, $source) = @_;
return $source->search(
{ },
{ join => 'resultInfo'
, '+select' => ["resultInfo.releasename", "resultInfo.buildstatus"]
, '+as' => ["releasename", "buildStatus"]
});
}
sub getChannelData {
my ($c, $builds) = @_;
my @builds2 = joinWithResultInfo($c, $builds)
->search_literal("exists (select 1 from buildproducts where build = resultInfo.id and type = 'nix-build')");
my @storePaths = ();
foreach my $build (@builds2) {
next unless isValidPath($build->outpath);
if (isValidPath($build->drvpath)) {
# Adding `drvpath' implies adding `outpath' because of the
# `--include-outputs' flag passed to `nix-store'.
push @storePaths, $build->drvpath;
} else {
push @storePaths, $build->outpath;
}
my $pkgName = $build->nixname . "-" . $build->system . "-" . $build->id;
$c->stash->{nixPkgs}->{"${pkgName}.nixpkg"} = {build => $build, name => $pkgName};
# Put the system type in the manifest (for top-level paths) as
# a hint to the binary patch generator. (It shouldn't try to
# generate patches between builds for different systems.) It
# would be nice if Nix stored this info for every path but it
# doesn't.
$c->stash->{systemForPath}->{$build->outpath} = $build->system;
};
$c->stash->{storePaths} = [@storePaths];
}
sub error { sub error {
my ($c, $msg) = @_; my ($c, $msg) = @_;
$c->error($msg); $c->error($msg);
@@ -161,12 +120,14 @@ sub requireLogin {
$c->detach; # doesn't return $c->detach; # doesn't return
} }
sub isProjectOwner { sub isProjectOwner {
my ($c, $project) = @_; my ($c, $project) = @_;
return $c->user_exists && ($c->check_user_roles('admin') || $c->user->username eq $project->owner->username || defined $c->model('DB::ProjectMembers')->find({ project => $project, userName => $c->user->username })); return $c->user_exists && ($c->check_user_roles('admin') || $c->user->username eq $project->owner->username || defined $c->model('DB::ProjectMembers')->find({ project => $project, userName => $c->user->username }));
} }
sub requireProjectOwner { sub requireProjectOwner {
my ($c, $project) = @_; my ($c, $project) = @_;
@@ -183,6 +144,7 @@ sub isAdmin {
return $c->user_exists && $c->check_user_roles('admin'); return $c->user_exists && $c->check_user_roles('admin');
} }
sub requireAdmin { sub requireAdmin {
my ($c) = @_; my ($c) = @_;
@@ -207,12 +169,12 @@ sub trim {
# Security checking of filenames. # Security checking of filenames.
Readonly::Scalar our $pathCompRE => "(?:[A-Za-z0-9-\+\._][A-Za-z0-9-\+\._]*)"; Readonly our $pathCompRE => "(?:[A-Za-z0-9-\+\._][A-Za-z0-9-\+\._]*)";
Readonly::Scalar our $relPathRE => "(?:$pathCompRE(?:/$pathCompRE)*)"; Readonly our $relPathRE => "(?:$pathCompRE(?:/$pathCompRE)*)";
Readonly::Scalar our $relNameRE => "(?:[A-Za-z0-9-][A-Za-z0-9-\.]*)"; Readonly our $relNameRE => "(?:[A-Za-z0-9-][A-Za-z0-9-\.]*)";
Readonly::Scalar our $attrNameRE => "(?:[A-Za-z_][A-Za-z0-9_]*)"; Readonly our $attrNameRE => "(?:[A-Za-z_][A-Za-z0-9_]*)";
Readonly::Scalar our $jobNameRE => "(?:$attrNameRE(?:\\.$attrNameRE)*)"; Readonly our $jobNameRE => "(?:$attrNameRE(?:\\.$attrNameRE)*)";
Readonly::Scalar our $systemRE => "(?:[a-z0-9_]+-[a-z0-9_]+)"; Readonly our $systemRE => "(?:[a-z0-9_]+-[a-z0-9_]+)";
1; 1;

View File

@@ -121,9 +121,7 @@ sub allPrimaryBuilds {
my ($project, $primaryJob) = @_; my ($project, $primaryJob) = @_;
my $allPrimaryBuilds = $project->builds->search( my $allPrimaryBuilds = $project->builds->search(
{ jobset => $primaryJob->get_column('jobset'), job => $primaryJob->get_column('job'), finished => 1 }, { jobset => $primaryJob->get_column('jobset'), job => $primaryJob->get_column('job'), finished => 1 },
{ join => 'resultInfo', order_by => "timestamp DESC" { order_by => "timestamp DESC"
, '+select' => ["resultInfo.releasename", "resultInfo.buildstatus"]
, '+as' => ["releasename", "buildstatus"]
, where => \ attrsToSQL($primaryJob->attrs, "me.id") , where => \ attrsToSQL($primaryJob->attrs, "me.id")
}); });
return $allPrimaryBuilds; return $allPrimaryBuilds;
@@ -165,10 +163,9 @@ sub findLastJobForBuilds {
{ {
$thisBuild = $ev->builds->find( $thisBuild = $ev->builds->find(
{ job => $job->get_column('job'), finished => 1 }, { job => $job->get_column('job'), finished => 1 },
{ join => 'resultInfo', rows => 1 { rows => 1
, order_by => ["build.id"] , order_by => ["build.id"]
, where => \ attrsToSQL($job->attrs, "build.id") , where => \ attrsToSQL($job->attrs, "build.id")
, '+select' => ["resultInfo.buildstatus"], '+as' => ["buildstatus"]
}); });
} }
@@ -180,30 +177,31 @@ sub findLastJobForBuilds {
{ project => $project, jobset => $jobset { project => $project, jobset => $jobset
, job => $job->get_column('job'), finished => 1 , job => $job->get_column('job'), finished => 1
}, },
{ join => 'resultInfo', rows => 1 { rows => 1
, order_by => ["buildstatus", "timestamp"] , order_by => ["buildstatus", "timestamp"]
, where => \ attrsToSQL($job->attrs, "build.id") , where => \ attrsToSQL($job->attrs, "build.id")
, '+select' => ["resultInfo.buildstatus"], '+as' => ["buildstatus"]
}) })
unless defined $thisBuild; unless defined $thisBuild;
return $thisBuild; return $thisBuild;
} }
sub jobsetOverview { sub jobsetOverview {
my ($c, $project) = @_; my ($c, $project) = @_;
return $project->jobsets->search( isProjectOwner($c, $project) ? {} : { hidden => 0 }, return $project->jobsets->search( isProjectOwner($c, $project) ? {} : { hidden => 0 },
{ order_by => "name" { order_by => "name"
, "+select" => , "+select" =>
[ "(SELECT COUNT(*) FROM Builds AS a NATURAL JOIN BuildSchedulingInfo WHERE me.project = a.project AND me.name = a.jobset AND a.isCurrent = 1)" [ "(select count(*) from Builds as a where a.finished = 0 and me.project = a.project and me.name = a.jobset and a.isCurrent = 1)"
, "(SELECT COUNT(*) FROM Builds AS a NATURAL JOIN BuildResultInfo WHERE me.project = a.project AND me.name = a.jobset AND buildstatus <> 0 AND a.isCurrent = 1)" , "(select count(*) from Builds as a where a.finished = 1 and me.project = a.project and me.name = a.jobset and buildstatus <> 0 and a.isCurrent = 1)"
, "(SELECT COUNT(*) FROM Builds AS a NATURAL JOIN BuildResultInfo WHERE me.project = a.project AND me.name = a.jobset AND buildstatus = 0 AND a.isCurrent = 1)" , "(select count(*) from Builds as a where a.finished = 1 and me.project = a.project and me.name = a.jobset and buildstatus = 0 and a.isCurrent = 1)"
, "(SELECT COUNT(*) FROM Builds AS a WHERE me.project = a.project AND me.name = a.jobset AND a.isCurrent = 1)" , "(select count(*) from Builds as a where me.project = a.project and me.name = a.jobset and a.isCurrent = 1)"
] ]
, "+as" => ["nrscheduled", "nrfailed", "nrsucceeded", "nrtotal"] , "+as" => ["nrscheduled", "nrfailed", "nrsucceeded", "nrtotal"]
}); });
} }
sub getViewResult { sub getViewResult {
my ($primaryBuild, $jobs) = @_; my ($primaryBuild, $jobs) = @_;
@@ -258,10 +256,12 @@ sub getLatestSuccessfulViewResult {
return undef; return undef;
} }
sub removeAsciiEscapes { sub removeAsciiEscapes {
my ($logtext) = @_; my ($logtext) = @_;
$logtext =~ s/\e\[[0-9]*[A-Za-z]//g; $logtext =~ s/\e\[[0-9]*[A-Za-z]//g;
return $logtext; return $logtext;
} }
1; 1;

View File

@@ -6,7 +6,10 @@ use Hydra::Helper::Nix;
__PACKAGE__->config( __PACKAGE__->config(
schema_class => 'Hydra::Schema', schema_class => 'Hydra::Schema',
connect_info => [getHydraDBPath], connect_info => {
dsn => getHydraDBPath,
pg_server_prepare => 0,
},
); );
=head1 NAME =head1 NAME

View File

@@ -46,7 +46,7 @@ __PACKAGE__->table("BuildProducts");
=head2 filesize =head2 filesize
data_type: 'integer' data_type: 'bigint'
is_nullable: 1 is_nullable: 1
=head2 sha1hash =head2 sha1hash
@@ -91,7 +91,7 @@ __PACKAGE__->add_columns(
"subtype", "subtype",
{ data_type => "text", is_nullable => 0 }, { data_type => "text", is_nullable => 0 },
"filesize", "filesize",
{ data_type => "integer", is_nullable => 1 }, { data_type => "bigint", is_nullable => 1 },
"sha1hash", "sha1hash",
{ data_type => "text", is_nullable => 1 }, { data_type => "text", is_nullable => 1 },
"sha256hash", "sha256hash",
@@ -133,8 +133,8 @@ Related object: L<Hydra::Schema::Builds>
__PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" }, {}); __PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" }, {});
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43 # Created by DBIx::Class::Schema::Loader v0.07014 @ 2012-02-29 00:47:18
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:suSgQkBLXzu0yD4YicRS1A # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:dzTKwZ7bby7kplnSgta3Gw
# You can replace this text with custom content, and it will be preserved on regeneration # You can replace this text with custom content, and it will be preserved on regeneration
1; 1;

View File

@@ -1,174 +0,0 @@
use utf8;
package Hydra::Schema::BuildResultInfo;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Hydra::Schema::BuildResultInfo
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 TABLE: C<BuildResultInfo>
=cut
__PACKAGE__->table("BuildResultInfo");
=head1 ACCESSORS
=head2 id
data_type: 'integer'
is_auto_increment: 1
is_foreign_key: 1
is_nullable: 0
=head2 iscachedbuild
data_type: 'integer'
is_nullable: 0
=head2 buildstatus
data_type: 'integer'
is_nullable: 1
=head2 errormsg
data_type: 'text'
is_nullable: 1
=head2 starttime
data_type: 'integer'
is_nullable: 1
=head2 stoptime
data_type: 'integer'
is_nullable: 1
=head2 logfile
data_type: 'text'
is_nullable: 1
=head2 logsize
data_type: 'bigint'
default_value: 0
is_nullable: 0
=head2 size
data_type: 'bigint'
default_value: 0
is_nullable: 0
=head2 closuresize
data_type: 'bigint'
default_value: 0
is_nullable: 0
=head2 releasename
data_type: 'text'
is_nullable: 1
=head2 keep
data_type: 'integer'
default_value: 0
is_nullable: 0
=head2 faileddepbuild
data_type: 'integer'
is_nullable: 1
=head2 faileddepstepnr
data_type: 'integer'
is_nullable: 1
=cut
__PACKAGE__->add_columns(
"id",
{
data_type => "integer",
is_auto_increment => 1,
is_foreign_key => 1,
is_nullable => 0,
},
"iscachedbuild",
{ data_type => "integer", is_nullable => 0 },
"buildstatus",
{ data_type => "integer", is_nullable => 1 },
"errormsg",
{ data_type => "text", is_nullable => 1 },
"starttime",
{ data_type => "integer", is_nullable => 1 },
"stoptime",
{ data_type => "integer", is_nullable => 1 },
"logfile",
{ data_type => "text", is_nullable => 1 },
"logsize",
{ data_type => "bigint", default_value => 0, is_nullable => 0 },
"size",
{ data_type => "bigint", default_value => 0, is_nullable => 0 },
"closuresize",
{ data_type => "bigint", default_value => 0, is_nullable => 0 },
"releasename",
{ data_type => "text", is_nullable => 1 },
"keep",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
"faileddepbuild",
{ data_type => "integer", is_nullable => 1 },
"faileddepstepnr",
{ data_type => "integer", is_nullable => 1 },
);
=head1 PRIMARY KEY
=over 4
=item * L</id>
=back
=cut
__PACKAGE__->set_primary_key("id");
=head1 RELATIONS
=head2 id
Type: belongs_to
Related object: L<Hydra::Schema::Builds>
=cut
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" }, {});
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hX3+iQYrGslQqY9vKvyw3g
__PACKAGE__->belongs_to(
"failedDep",
"Hydra::Schema::BuildSteps",
{ build => "faileddepbuild", stepnr => "faileddepstepnr" },
);
1;

View File

@@ -1,120 +0,0 @@
use utf8;
package Hydra::Schema::BuildSchedulingInfo;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Hydra::Schema::BuildSchedulingInfo
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 TABLE: C<BuildSchedulingInfo>
=cut
__PACKAGE__->table("BuildSchedulingInfo");
=head1 ACCESSORS
=head2 id
data_type: 'integer'
is_auto_increment: 1
is_foreign_key: 1
is_nullable: 0
=head2 priority
data_type: 'integer'
default_value: 0
is_nullable: 0
=head2 busy
data_type: 'integer'
default_value: 0
is_nullable: 0
=head2 locker
data_type: 'text'
default_value: (empty string)
is_nullable: 0
=head2 logfile
data_type: 'text'
is_nullable: 1
=head2 disabled
data_type: 'integer'
default_value: 0
is_nullable: 0
=head2 starttime
data_type: 'integer'
is_nullable: 1
=cut
__PACKAGE__->add_columns(
"id",
{
data_type => "integer",
is_auto_increment => 1,
is_foreign_key => 1,
is_nullable => 0,
},
"priority",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
"busy",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
"locker",
{ data_type => "text", default_value => "", is_nullable => 0 },
"logfile",
{ data_type => "text", is_nullable => 1 },
"disabled",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
"starttime",
{ data_type => "integer", is_nullable => 1 },
);
=head1 PRIMARY KEY
=over 4
=item * L</id>
=back
=cut
__PACKAGE__->set_primary_key("id");
=head1 RELATIONS
=head2 id
Type: belongs_to
Related object: L<Hydra::Schema::Builds>
=cut
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" }, {});
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Uz7y9Ly+ADRrtrPfEk9lGA
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@@ -151,10 +151,4 @@ __PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" }, {})
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43 # Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:5H+OkGT0zQEWkAjU+OlBdg # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:5H+OkGT0zQEWkAjU+OlBdg
__PACKAGE__->belongs_to(
"schedulingInfo",
"Hydra::Schema::BuildSchedulingInfo",
{ id => "build" },
);
1; 1;

View File

@@ -130,6 +130,85 @@ __PACKAGE__->table("Builds");
data_type: 'text' data_type: 'text'
is_nullable: 1 is_nullable: 1
=head2 priority
data_type: 'integer'
default_value: 0
is_nullable: 0
=head2 busy
data_type: 'integer'
default_value: 0
is_nullable: 0
=head2 locker
data_type: 'text'
is_nullable: 1
=head2 logfile
data_type: 'text'
is_nullable: 1
=head2 disabled
data_type: 'integer'
default_value: 0
is_nullable: 0
=head2 starttime
data_type: 'integer'
is_nullable: 1
=head2 stoptime
data_type: 'integer'
is_nullable: 1
=head2 iscachedbuild
data_type: 'integer'
is_nullable: 1
=head2 buildstatus
data_type: 'integer'
is_nullable: 1
=head2 errormsg
data_type: 'text'
is_nullable: 1
=head2 logsize
data_type: 'bigint'
is_nullable: 1
=head2 size
data_type: 'bigint'
is_nullable: 1
=head2 closuresize
data_type: 'bigint'
is_nullable: 1
=head2 releasename
data_type: 'text'
is_nullable: 1
=head2 keep
data_type: 'integer'
default_value: 0
is_nullable: 0
=cut =cut
__PACKAGE__->add_columns( __PACKAGE__->add_columns(
@@ -173,6 +252,36 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 }, { data_type => "text", is_nullable => 1 },
"nixexprpath", "nixexprpath",
{ data_type => "text", is_nullable => 1 }, { data_type => "text", is_nullable => 1 },
"priority",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
"busy",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
"locker",
{ data_type => "text", is_nullable => 1 },
"logfile",
{ data_type => "text", is_nullable => 1 },
"disabled",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
"starttime",
{ data_type => "integer", is_nullable => 1 },
"stoptime",
{ data_type => "integer", is_nullable => 1 },
"iscachedbuild",
{ data_type => "integer", is_nullable => 1 },
"buildstatus",
{ data_type => "integer", is_nullable => 1 },
"errormsg",
{ data_type => "text", is_nullable => 1 },
"logsize",
{ data_type => "bigint", is_nullable => 1 },
"size",
{ data_type => "bigint", is_nullable => 1 },
"closuresize",
{ data_type => "bigint", is_nullable => 1 },
"releasename",
{ data_type => "text", is_nullable => 1 },
"keep",
{ data_type => "integer", default_value => 0, is_nullable => 0 },
); );
=head1 PRIMARY KEY =head1 PRIMARY KEY
@@ -234,36 +343,6 @@ __PACKAGE__->has_many(
{}, {},
); );
=head2 buildresultinfo
Type: might_have
Related object: L<Hydra::Schema::BuildResultInfo>
=cut
__PACKAGE__->might_have(
"buildresultinfo",
"Hydra::Schema::BuildResultInfo",
{ "foreign.id" => "self.id" },
{},
);
=head2 buildschedulinginfo
Type: might_have
Related object: L<Hydra::Schema::BuildSchedulingInfo>
=cut
__PACKAGE__->might_have(
"buildschedulinginfo",
"Hydra::Schema::BuildSchedulingInfo",
{ "foreign.id" => "self.id" },
{},
);
=head2 buildsteps =head2 buildsteps
Type: has_many Type: has_many
@@ -350,8 +429,8 @@ __PACKAGE__->has_many(
); );
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43 # Created by DBIx::Class::Schema::Loader v0.07014 @ 2012-02-29 18:56:22
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:RRtBPTdD946kA5133+c4kw # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:w16c86FRReLPdA8H0yTIRg
use Hydra::Helper::Nix; use Hydra::Helper::Nix;
@@ -369,18 +448,6 @@ __PACKAGE__->has_many(
{ "foreign.build" => "self.id" }, { "foreign.build" => "self.id" },
); );
__PACKAGE__->belongs_to(
"schedulingInfo",
"Hydra::Schema::BuildSchedulingInfo",
{ id => "id" },
);
__PACKAGE__->belongs_to(
"resultInfo",
"Hydra::Schema::BuildResultInfo",
{ id => "id" },
);
__PACKAGE__->has_one( __PACKAGE__->has_one(
"actualBuildStep", "actualBuildStep",
"Hydra::Schema::BuildSteps", "Hydra::Schema::BuildSteps",
@@ -408,35 +475,16 @@ sub makeSource {
sub makeQueries { sub makeQueries {
my ($name, $constraint) = @_; my ($name, $constraint) = @_;
my $joinWithStatusChange =
<<QUERY;
natural join BuildResultInfo r
left join Builds b on
b.id =
(select max(id)
from builds c natural join buildresultinfo r2
where
x.project = c.project and x.jobset = c.jobset and x.job = c.job and x.system = c.system and
x.id > c.id and
((r.buildstatus = 0 and r2.buildstatus != 0) or
(r.buildstatus != 0 and r2.buildstatus = 0)))
QUERY
my $activeJobs = "(select distinct project, jobset, job, system from Builds where isCurrent = 1 $constraint)"; my $activeJobs = "(select distinct project, jobset, job, system from Builds where isCurrent = 1 $constraint)";
makeSource( makeSource(
"JobStatus$name", "JobStatus$name",
# Urgh, can't use "*" in the "select" here because of the status change join. # Urgh, can't use "*" in the "select" here because of the status change join.
<<QUERY <<QUERY
select select x.*, b.id as statusChangeId, b.timestamp as statusChangeTime
x.id, x.finished, x.timestamp, x.project, x.jobset, x.job, x.nixname,
x.description, x.drvpath, x.outpath, x.system, x.longdescription,
x.license, x.homepage, x.maintainers, x.isCurrent, x.nixExprInput,
x.nixExprPath, x.maxsilent, x.timeout,
b.id as statusChangeId, b.timestamp as statusChangeTime
from from
(select (select
(select max(id) from builds b (select max(b.id) from Builds b
where where
project = activeJobs.project and jobset = activeJobs.jobset project = activeJobs.project and jobset = activeJobs.jobset
and job = activeJobs.job and system = activeJobs.system and job = activeJobs.job and system = activeJobs.system
@@ -445,7 +493,15 @@ QUERY
from $activeJobs as activeJobs from $activeJobs as activeJobs
) as latest ) as latest
join Builds x using (id) join Builds x using (id)
$joinWithStatusChange left join Builds b on
b.id =
(select max(c.id) from Builds c
where
c.finished = 1 and
x.project = c.project and x.jobset = c.jobset and x.job = c.job and x.system = c.system and
x.id > c.id and
((x.buildStatus = 0 and c.buildStatus != 0) or
(x.buildStatus != 0 and c.buildStatus = 0)))
QUERY QUERY
); );
@@ -457,12 +513,11 @@ QUERY
select * select *
from from
(select (select
(select max(id) from builds b (select max(b.id) from builds b
where where
project = activeJobs.project and jobset = activeJobs.jobset project = activeJobs.project and jobset = activeJobs.jobset
and job = activeJobs.job and system = activeJobs.system and job = activeJobs.job and system = activeJobs.system
and finished = 1 and finished = 1 and buildstatus = 0
and exists (select 1 from buildresultinfo where id = b.id and buildstatus = 0)
) as id ) as id
from $activeJobs as activeJobs from $activeJobs as activeJobs
) as latest ) as latest

View File

@@ -161,5 +161,4 @@ __PACKAGE__->has_many(
__PACKAGE__->many_to_many(builds => 'buildIds', 'build'); __PACKAGE__->many_to_many(builds => 'buildIds', 'build');
# You can replace this text with custom content, and it will be preserved on regeneration
1; 1;

View File

@@ -26,31 +26,15 @@ __PACKAGE__->table("SchemaVersion");
=head2 version =head2 version
data_type: 'integer' data_type: 'integer'
is_auto_increment: 1
is_nullable: 0 is_nullable: 0
=cut =cut
__PACKAGE__->add_columns( __PACKAGE__->add_columns("version", { data_type => "integer", is_nullable => 0 });
"version",
{ data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
);
=head1 PRIMARY KEY
=over 4
=item * L</version>
=back
=cut
__PACKAGE__->set_primary_key("version");
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43 # Created by DBIx::Class::Schema::Loader v0.07014 @ 2012-02-29 00:47:18
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:F/jsSRq8pxR4mWq/N4qYGw # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:LFD28W0GvvrOOylCM98SEQ
# You can replace this text with custom code or comments, and it will be preserved on regeneration # You can replace this text with custom code or comments, and it will be preserved on regeneration

View File

@@ -7,7 +7,6 @@
[% project = build.project %] [% project = build.project %]
[% jobset = build.jobset %] [% jobset = build.jobset %]
[% job = build.job %] [% job = build.job %]
[% resultInfo = build.resultInfo %]
[% BLOCK renderBuildSteps %] [% BLOCK renderBuildSteps %]
@@ -35,7 +34,7 @@
[% INCLUDE renderDuration duration = step.stoptime - step.starttime %] [% INCLUDE renderDuration duration = step.stoptime - step.starttime %]
[% ELSE %] [% ELSE %]
[% IF build.finished %] [% IF build.finished %]
[% INCLUDE renderDuration duration = resultInfo.stoptime - step.starttime %] [% INCLUDE renderDuration duration = build.stoptime - step.starttime %]
[% ELSE %] [% ELSE %]
[% INCLUDE renderDuration duration = curTime - step.starttime %] [% INCLUDE renderDuration duration = curTime - step.starttime %]
[% END %] [% END %]
@@ -70,7 +69,7 @@
<h1> <h1>
Job <tt>[% project.name %]:[% jobset.name %]:[% job.name %]</tt> build [% id %] Job <tt>[% project.name %]:[% jobset.name %]:[% job.name %]</tt> build [% id %]
[% IF !build.finished %] [% IF !build.finished %]
[% IF build.schedulingInfo.busy %] [% IF build.busy %]
(currently building) (currently building)
[% ELSE %] [% ELSE %]
(scheduled) (scheduled)
@@ -106,10 +105,10 @@
<th>Build ID:</th> <th>Build ID:</th>
<td>[% build.id %]</td> <td>[% build.id %]</td>
</tr> </tr>
[% IF resultInfo.releasename %] [% IF build.releasename %]
<tr> <tr>
<th>Release name:</th> <th>Release name:</th>
<td><tt>[% HTML.escape(resultInfo.releasename) %]</tt></td> <td><tt>[% HTML.escape(build.releasename) %]</tt></td>
</tr> </tr>
[% ELSE %] [% ELSE %]
<tr> <tr>
@@ -127,19 +126,19 @@
<th>System:</th> <th>System:</th>
<td><tt>[% build.system %]</tt></td> <td><tt>[% build.system %]</tt></td>
</tr> </tr>
[% IF !build.schedulingInfo %] [% IF !build.finished %]
<tr> <tr>
<th>Duration:</th> <th>Duration:</th>
<td> <td>
[% IF resultInfo.iscachedbuild %] [% IF build.iscachedbuild %]
(cached[% IF cachedBuild %] from [% INCLUDE renderFullBuildLink build=cachedBuild %][% END %]) (cached[% IF cachedBuild %] from [% INCLUDE renderFullBuildLink build=cachedBuild %][% END %])
[% ELSE %] [% ELSE %]
[% INCLUDE renderDuration duration = resultInfo.stoptime - resultInfo.starttime %] <tt>finished at [% INCLUDE renderDateTime timestamp = resultInfo.stoptime %]</tt> [% INCLUDE renderDuration duration = build.stoptime - build.starttime %] <tt>finished at [% INCLUDE renderDateTime timestamp = build.stoptime %]</tt>
[% END %] [% END %]
</td> </td>
</tr> </tr>
[% END %] [% END %]
[% IF resultInfo.logfile %] [% IF build.logfile %]
<tr> <tr>
<th>Logfile:</th> <th>Logfile:</th>
<td> <td>
@@ -182,7 +181,7 @@
[% END %] [% END %]
[% IF build.finished %] [% IF build.finished %]
[% IF build.buildsteps && resultInfo.buildstatus != 0 && resultInfo.buildstatus != 6 %] [% IF build.buildsteps && build.buildstatus != 0 && build.buildstatus != 6 %]
[% INCLUDE renderBuildSteps type="Failed" %] [% INCLUDE renderBuildSteps type="Failed" %]
[% END %] [% END %]
@@ -214,11 +213,11 @@
</table> </table>
[% END %] [% END %]
[% IF resultInfo.errormsg && resultInfo.buildstatus != 5 %] [% IF build.errormsg && build.buildstatus != 5 %]
<h2 id="nix-error">Nix error output</h2> <h2 id="nix-error">Nix error output</h2>
<pre class="buildlog">[% HTML.escape(resultInfo.errormsg) -%]</pre> <pre class="buildlog">[% HTML.escape(build.errormsg) -%]</pre>
[% END %] [% END %]
[% END %] [% END %]
[% IF logtext %] [% IF logtext %]
@@ -264,10 +263,10 @@
<th>Nix name:</th> <th>Nix name:</th>
<td><tt>[% build.nixname %]</tt></td> <td><tt>[% build.nixname %]</tt></td>
</tr> </tr>
[% IF resultInfo.releasename %] [% IF build.releasename %]
<tr> <tr>
<th>Release name:</th> <th>Release name:</th>
<td><tt>[% HTML.escape(resultInfo.releasename) %]</tt></td> <td><tt>[% HTML.escape(build.releasename) %]</tt></td>
</tr> </tr>
[% END %] [% END %]
<tr> <tr>
@@ -316,18 +315,20 @@
[% END %] [% END %]
</td> </td>
</tr> </tr>
[% IF pathHash %]
<tr> <tr>
<th>Output store path hash:</th> <th>Output store path hash:</th>
<td> <td>
<tt>[% pathHash %]</tt> <tt>[% pathHash %]</tt>
</td> </td>
</tr> </tr>
[% END %]
<tr> <tr>
<th>Time added:</th> <th>Time added:</th>
<td>[% INCLUDE renderDateTime timestamp = build.timestamp %]</td> <td>[% INCLUDE renderDateTime timestamp = build.timestamp %]</td>
</tr> </tr>
[% IF build.finished && resultInfo.buildstatus != 4 %] [% IF build.finished && build.buildstatus != 4 %]
[% IF resultInfo.iscachedbuild && cachedBuild %] [% IF build.iscachedbuild && cachedBuild %]
<tr> <tr>
<th>Cached build:</th> <th>Cached build:</th>
<td>[% INCLUDE renderFullBuildLink build=cachedBuild %]</td> <td>[% INCLUDE renderFullBuildLink build=cachedBuild %]</td>
@@ -336,23 +337,23 @@
<tr> <tr>
<th>Build started:</th> <th>Build started:</th>
<td>[% IF resultInfo.starttime %][% INCLUDE renderDateTime timestamp = resultInfo.starttime %][% ELSE %]<em>(cached build)</em>[% END %]</td> <td>[% IF build.starttime %][% INCLUDE renderDateTime timestamp = build.starttime %][% ELSE %]<em>(cached build)</em>[% END %]</td>
</tr> </tr>
<tr> <tr>
<th>Build finished:</th> <th>Build finished:</th>
<td>[% IF resultInfo.stoptime %][% INCLUDE renderDateTime timestamp = resultInfo.stoptime %][% ELSE %]<em>(cached build)</em>[% END %]</td> <td>[% IF build.stoptime %][% INCLUDE renderDateTime timestamp = build.stoptime %][% ELSE %]<em>(cached build)</em>[% END %]</td>
</tr> </tr>
<tr> <tr>
<th>Duration:</th> <th>Duration:</th>
<td> <td>
[% IF resultInfo.iscachedbuild %] [% IF build.iscachedbuild %]
<em>(cached build)</em> <em>(cached build)</em>
[% ELSE %] [% ELSE %]
[% INCLUDE renderDuration duration = resultInfo.stoptime - resultInfo.starttime %] [% INCLUDE renderDuration duration = build.stoptime - build.starttime %]
[% END %] [% END %]
</td> </td>
</tr> </tr>
[% IF resultInfo.logfile %] [% IF build.logfile %]
<tr> <tr>
<th>Logfile:</th> <th>Logfile:</th>
<td> <td>
@@ -366,7 +367,7 @@
[% IF !build.finished %] [% IF !build.finished %]
<tr> <tr>
<th>Priority:</th> <th>Priority:</th>
<td>[% build.schedulingInfo.priority %]</td> <td>[% build.priority %]</td>
</tr> </tr>
[% END %] [% END %]
[% IF build.finished && build.buildproducts %] [% IF build.finished && build.buildproducts %]
@@ -375,7 +376,7 @@
<td> <td>
[% IF !available %] [% IF !available %]
<em>Build output is no longer available</em> <em>Build output is no longer available</em>
[% ELSIF resultInfo.keep %] [% ELSIF build.keep %]
<em>Build output will be kept permanently</em> <em>Build output will be kept permanently</em>
[% IF c.user_exists %] [% IF c.user_exists %]
<form action="[% c.uri_for('/build' build.id 'keep' 0) %]" method="post" class="inline"> <form action="[% c.uri_for('/build' build.id 'keep' 0) %]" method="post" class="inline">
@@ -485,10 +486,9 @@
$(function() { $(function() {
var d = []; var d = [];
var ids = []; var ids = [];
[% FOREACH prevbuild IN prevBuilds %][% IF prevbuild.resultInfo.starttime != 0 %] [% FOREACH prevbuild IN prevBuilds %][% IF prevbuild.build.starttime != 0 %]
[% pbResultInfo = prevbuild.resultInfo %] d.push([[% prevbuild.starttime * 1000 %],[% prevbuild.get_column('actualBuildTime') %]]);
d.push([[% pbResultInfo.starttime * 1000 %],[% prevbuild.get_column('actualBuildTime') %]]); ids[[% prevbuild.starttime * 1000 %]] = [% prevbuild.id %] ;
ids[[% pbResultInfo.starttime * 1000 %]] = [% prevbuild.id %] ;
[% END %][% END %] [% END %][% END %]
var options = { var options = {
@@ -558,9 +558,9 @@
$(function() { $(function() {
var d = []; var d = [];
var ids = []; var ids = [];
[% FOREACH prevbuild IN prevBuilds %][% IF prevbuild.resultInfo.size != 0 %] [% FOREACH prevbuild IN prevBuilds %][% IF prevbuild.size != 0 %]
d.push([[% prevbuild.resultInfo.starttime * 1000 %],[% prevbuild.resultInfo.size / (1024*1024.0) %]]); d.push([[% prevbuild.starttime * 1000 %],[% prevbuild.size / (1024*1024.0) %]]);
ids[[% prevbuild.resultInfo.starttime * 1000 %]] = [% prevbuild.id %] ; ids[[% prevbuild.starttime * 1000 %]] = [% prevbuild.id %] ;
[% END %][% END %] [% END %][% END %]
var options = { var options = {

View File

@@ -93,20 +93,20 @@
[%- FOREACH build IN builds -%] [%- FOREACH build IN builds -%]
<tr class="clickable <tr class="clickable
[%- IF showSchedulingInfo -%] [%- IF showSchedulingInfo -%]
[%- IF build.get_column('busy') %]runningBuild[% ELSIF build.get_column('disabled') == 1 || build.get_column('enabled') == 0 %]disabledBuild[% END -%] [%- IF build.busy %]runningBuild[% ELSIF build.disabled == 1 || build.get_column('enabled') == 0 %]disabledBuild[% END -%]
[%- ELSE -%] [%- ELSE -%]
[%- IF odd %] odd [% END; odd = !odd -%] [%- IF odd %] odd [% END; odd = !odd -%]
[%- END %]" [%- END %]"
onclick="if(event.which == 2) return true; window.location = '[% c.uri_for('/build' build.id) %]'"> onclick="if(event.which == 2) return true; window.location = '[% c.uri_for('/build' build.id) %]'">
[%- IF !hideResultInfo -%] [%- IF !hideResultInfo -%]
<td> <td>
[%- INCLUDE renderBuildStatusIcon size=16 busy=(showSchedulingInfo ? 1 : 0) buildstatus=build.get_column('buildStatus') -%] [%- INCLUDE renderBuildStatusIcon size=16 busy=(showSchedulingInfo ? 1 : 0) buildstatus=build.buildstatus -%]
</td> </td>
[%- END -%] [%- END -%]
<td><a href="[% c.uri_for('/build' build.id) %]">[% build.id %]</a></td> <td><a href="[% c.uri_for('/build' build.id) %]">[% build.id %]</a></td>
[%- IF showSchedulingInfo -%] [%- IF showSchedulingInfo -%]
<td>[% IF build.get_column('busy') %]<img src="/static/images/running.gif" alt="Running" />[% ELSIF build.get_column('disabled') == 1 || build.get_column('enabled') == 0 %]Disabled[% END %]</td> <td>[% IF build.busy %]<img src="/static/images/running.gif" alt="Running" />[% ELSIF build.disabled == 1 || build.get_column('enabled') == 0 %]Disabled[% END %]</td>
<td>[% build.get_column('priority') %]</td> <td>[% build.priority %]</td>
[%- END -%] [%- END -%]
<td>[%- INCLUDE renderFullJobNameOfBuild -%]</td> <td>[%- INCLUDE renderFullJobNameOfBuild -%]</td>
<td>[% !showSchedulingInfo and build.get_column('releasename') ? build.get_column('releasename') : build.nixname %]</td> <td>[% !showSchedulingInfo and build.get_column('releasename') ? build.get_column('releasename') : build.nixname %]</td>
@@ -203,8 +203,8 @@
[%- BLOCK renderBuildStatusIcon -%] [%- BLOCK renderBuildStatusIcon -%]
[%- finished = build != undef ? build.finished : 1 -%] [%- finished = build != undef ? build.finished : 1 -%]
[%- busy = busy != undef ? busy : build.schedulingInfo.busy -%] [%- busy = busy != undef ? busy : build.busy -%]
[%- buildstatus = buildstatus != undef ? buildstatus : build.resultInfo.buildstatus -%] [%- buildstatus = buildstatus != undef ? buildstatus : build.buildstatus -%]
[%- IF finished -%] [%- IF finished -%]
[%- IF buildstatus == 0 -%] [%- IF buildstatus == 0 -%]
<img src="/static/images/checkmark_[% size %].png" alt="Succeeded" /> <img src="/static/images/checkmark_[% size %].png" alt="Succeeded" />
@@ -230,7 +230,7 @@
[% BLOCK renderStatus %] [% BLOCK renderStatus %]
[% IF build.finished %] [% IF build.finished %]
[% buildstatus = build.resultInfo.buildstatus %] [% buildstatus = build.buildstatus %]
[% INCLUDE renderBuildStatusIcon size=16 %] [% INCLUDE renderBuildStatusIcon size=16 %]
[% IF buildstatus == 0 %] [% IF buildstatus == 0 %]
<strong>Success</strong> <strong>Success</strong>
@@ -242,8 +242,6 @@
<span class="error">Cancelled by user</span> <span class="error">Cancelled by user</span>
[% ELSIF buildstatus == 5 %] [% ELSIF buildstatus == 5 %]
<span class="error">Build inhibited because a dependency previously failed to build</span> <span class="error">Build inhibited because a dependency previously failed to build</span>
[% failedDep = build.resultInfo.failedDep %]
(namely, <a href="[% c.uri_for('/build' failedDep.build.id 'nixlog' failedDep.stepnr) %]"><tt>[% failedDep.outpath %]</tt></a>)
[% ELSIF buildstatus == 6 %] [% ELSIF buildstatus == 6 %]
<span class="error">Build failed (with result)</span> <span class="error">Build failed (with result)</span>
[% ELSE %] [% ELSE %]
@@ -255,9 +253,9 @@
<button id="restart" type="submit">Restart</button> <button id="restart" type="submit">Restart</button>
</form> </form>
[% END %] [% END %]
[% ELSIF build.schedulingInfo.busy %] [% ELSIF build.busy %]
<strong>Build in progress</strong> <strong>Build in progress</strong>
since [% INCLUDE renderDateTime timestamp = build.schedulingInfo.starttime %] since [% INCLUDE renderDateTime timestamp = build.starttime %]
[% ELSE %] [% ELSE %]
<strong>Scheduled to be built</strong> <strong>Scheduled to be built</strong>
[% IF c.user_exists %] [% IF c.user_exists %]

View File

@@ -19,7 +19,7 @@
[% SWITCH product.type %] [% SWITCH product.type %]
[% CASE "nix-build" %] [% CASE "nix-build" %]
[% IF build.resultInfo.buildstatus == 6 %] [% IF build.buildstatus == 6 %]
[% filename = "${build.nixname}.closure.gz" %] [% filename = "${build.nixname}.closure.gz" %]
[% uri = c.uri_for('/build' build.id 'nix' 'closure' filename ) %] [% uri = c.uri_for('/build' build.id 'nix' 'closure' filename ) %]
<tr class="product"> <tr class="product">

View File

@@ -24,7 +24,7 @@
[% IF j.build %] [% IF j.build %]
[% IF j.build.resultInfo.buildstatus == 0 %] [% IF j.build.buildstatus == 0 %]
[% IF j.build.buildproducts %] [% IF j.build.buildproducts %]
[% p = jobNames.${j.job.job} > 1 ? "-${j.build.system}" : ""; [% p = jobNames.${j.job.job} > 1 ? "-${j.build.system}" : "";

View File

@@ -3,6 +3,7 @@ EXTRA_DIST = \
$(bin_SCRIPTS) $(bin_SCRIPTS)
bin_SCRIPTS = \ bin_SCRIPTS = \
hydra-init \
hydra-build \ hydra-build \
hydra-evaluator \ hydra-evaluator \
hydra-queue-runner \ hydra-queue-runner \

View File

@@ -17,7 +17,7 @@ use Text::Table;
use POSIX qw(strftime); use POSIX qw(strftime);
use Net::Twitter::Lite; use Net::Twitter::Lite;
use Data::Dump qw(dump); use Data::Dump qw(dump);
use Switch; use feature qw/switch/;
STDOUT->autoflush(); STDOUT->autoflush();
@@ -34,9 +34,9 @@ sub sendTwitterNotification {
my $addURL = defined $config{'base_uri'}; my $addURL = defined $config{'base_uri'};
my $jobName = $build->project->name . ":" . $build->jobset->name . ":" . $build->job->name; my $jobName = $build->project->name . ":" . $build->jobset->name . ":" . $build->job->name;
my $status = $build->resultInfo->buildstatus == 0 ? "SUCCEEDED" : "FAILED"; my $status = $build->buildstatus == 0 ? "SUCCEEDED" : "FAILED";
my $system = $build->system; my $system = $build->system;
my $duration = ($build->resultInfo->stoptime - $build->resultInfo->starttime) . " seconds"; my $duration = ($build->stoptime - $build->starttime) . " seconds";
my $url = $config{'base_uri'}."/build/".$build->id ; my $url = $config{'base_uri'}."/build/".$build->id ;
my $nt = Net::Twitter::Lite->new( my $nt = Net::Twitter::Lite->new(
@@ -61,11 +61,11 @@ sub statusDescription {
my ($buildstatus) = @_; my ($buildstatus) = @_;
my $status = "Failed"; my $status = "Failed";
switch ($buildstatus) { given ($buildstatus) {
case 0 { $status = "Success"; } when (0) { $status = "Success"; }
case 1 { $status = "Failed with non-zero exit code"; } when (1) { $status = "Failed with non-zero exit code"; }
case 2 { $status = "Dependency failed"; } when (2) { $status = "Dependency failed"; }
case 4 { $status = "Cancelled"; } when (4) { $status = "Cancelled"; }
} }
return $status; return $status;
@@ -74,7 +74,7 @@ sub statusDescription {
sub sendEmailNotification { sub sendEmailNotification {
my ($build) = @_; my ($build) = @_;
die unless defined $build->resultInfo; die unless $build->finished;
return if ! ( $build->jobset->enableemail && ($build->maintainers ne "" || $build->jobset->emailoverride ne "") ); return if ! ( $build->jobset->enableemail && ($build->maintainers ne "" || $build->jobset->emailoverride ne "") );
@@ -87,19 +87,18 @@ sub sendEmailNotification {
, job => $build->job->name , job => $build->job->name
, system => $build->system , system => $build->system
, finished => 1 , finished => 1
, id => { '!=', $build->id } , id => { '<', $build->id }
}, { order_by => ["timestamp DESC"] } }, { order_by => ["id DESC"] }
); );
# if there is a previous build with same buildstatus, do not send email # if there is a previous build with same buildstatus, do not send email
if (defined $prevBuild && ($build->resultInfo->buildstatus == $prevBuild->resultInfo->buildstatus)) { if (defined $prevBuild && ($build->buildstatus == $prevBuild->buildstatus)) {
return; return;
} }
# if buildstatus of this build or the previous one is aborted, do # if buildstatus of this build or the previous one is aborted, do
# not send email # not send email
if ($build->resultInfo->buildstatus == 3 || (defined $prevBuild && ($prevBuild->resultInfo->buildstatus == 3))) { if ($build->buildstatus == 3 || (defined $prevBuild && ($prevBuild->buildstatus == 3))) {
return; return;
} }
@@ -110,7 +109,7 @@ sub sendEmailNotification {
my $jobName = $build->project->name . ":" . $build->jobset->name . ":" . $build->job->name; my $jobName = $build->project->name . ":" . $build->jobset->name . ":" . $build->job->name;
my $status = statusDescription($build->resultInfo->buildstatus); my $status = statusDescription($build->buildstatus);
my $baseurl = hostname_long ; my $baseurl = hostname_long ;
my $sender = $config{'notification_sender'} || my $sender = $config{'notification_sender'} ||
@@ -132,10 +131,10 @@ sub sendEmailNotification {
[ "Time added:", showTime $build->timestamp ], [ "Time added:", showTime $build->timestamp ],
); );
push @lines, ( push @lines, (
[ "Build started:", showTime $build->resultInfo->starttime ], [ "Build started:", showTime $build->starttime ],
[ "Build finished:", showTime $build->resultInfo->stoptime ], [ "Build finished:", showTime $build->stoptime ],
[ "Duration:", $build->resultInfo->stoptime - $build->resultInfo->starttime . "s" ], [ "Duration:", $build->stoptime - $build->starttime . "s" ],
) if $build->resultInfo->starttime; ) if $build->starttime;
$infoTable->load(@lines); $infoTable->load(@lines);
my $inputsTable = Text::Table->new( my $inputsTable = Text::Table->new(
@@ -157,18 +156,18 @@ sub sendEmailNotification {
$inputsTable->load(@lines); $inputsTable->load(@lines);
my $loglines = 50; my $loglines = 50;
my $logfile = $build->resultInfo->logfile; my $logfile = $build->logfile;
my $logtext = defined $logfile && -e $logfile ? `tail -$loglines $logfile` : "No logfile available.\n"; my $logtext = defined $logfile && -e $logfile ? `tail -$loglines $logfile` : "No logfile available.\n";
$logtext = removeAsciiEscapes($logtext); $logtext = removeAsciiEscapes($logtext);
my $body = "Hi,\n" my $body = "Hi,\n"
. "\n" . "\n"
. "This is to let you know that Hydra build " . $build->id . "This is to let you know that Hydra build " . $build->id
. " of job " . $jobName . " " . (defined $prevBuild ? "has changed from '" . statusDescription($prevBuild->resultInfo->buildstatus) . "' to '$status'" : "is '$status'" ) .".\n" . " of job " . $jobName . " " . (defined $prevBuild ? "has changed from '" . statusDescription($prevBuild->buildstatus) . "' to '$status'" : "is '$status'" ) .".\n"
. "\n" . "\n"
. "Complete build information can be found on this page: " . "Complete build information can be found on this page: "
. "$selfURI/build/" . $build->id . "\n" . "$selfURI/build/" . $build->id . "\n"
. ($build->resultInfo->buildstatus != 0 ? "\nThe last $loglines lines of the build log are shown at the bottom of this email.\n" : "") . ($build->buildstatus != 0 ? "\nThe last $loglines lines of the build log are shown at the bottom of this email.\n" : "")
. "\n" . "\n"
. "A summary of the build information follows:\n" . "A summary of the build information follows:\n"
. "\n" . "\n"
@@ -181,7 +180,7 @@ sub sendEmailNotification {
. $inputsTable->body . $inputsTable->body
. "\n" . "\n"
. "Regards,\n\nThe Hydra build daemon.\n" . "Regards,\n\nThe Hydra build daemon.\n"
. ($build->resultInfo->buildstatus != 0 ? "\n---\n$logtext" : ""); . ($build->buildstatus != 0 ? "\n---\n$logtext" : "");
# stripping trailing spaces from lines # stripping trailing spaces from lines
$body =~ s/[\ ]+$//gm; $body =~ s/[\ ]+$//gm;
@@ -192,7 +191,6 @@ sub sendEmailNotification {
To => $to, To => $to,
From => "Hydra Build Daemon <$sender>", From => "Hydra Build Daemon <$sender>",
Subject => "Hydra job $jobName on " . $build->system . ", build " . $build->id . ": $status", Subject => "Hydra job $jobName on " . $build->system . ", build " . $build->id . ": $status",
'X-Hydra-Instance' => $baseurl, 'X-Hydra-Instance' => $baseurl,
'X-Hydra-Project' => $build->project->name, 'X-Hydra-Project' => $build->project->name,
'X-Hydra-Jobset' => $build->jobset->name, 'X-Hydra-Jobset' => $build->jobset->name,
@@ -398,16 +396,16 @@ sub doBuild {
} }
txn_do($db, sub { txn_do($db, sub {
$build->update({finished => 1, timestamp => time});
my $releaseName = getReleaseName($outPath); my $releaseName = getReleaseName($outPath);
if ($buildStatus == 0 && -f "$outPath/nix-support/failed") { $buildStatus = 6 if $buildStatus == 0 && -f "$outPath/nix-support/failed";
$buildStatus = 6;
}
$db->resultset('BuildResultInfo')->create( $build->update(
{ id => $build->id { finished => 1
, busy => 0
, locker => ''
, logfile => ''
, timestamp => time # !!! Why change the timestamp?
, iscachedbuild => $isCachedBuild , iscachedbuild => $isCachedBuild
, buildstatus => $buildStatus , buildstatus => $buildStatus
, starttime => $startTime , starttime => $startTime
@@ -423,8 +421,6 @@ sub doBuild {
if ($buildStatus == 0 || $buildStatus == 6) { if ($buildStatus == 0 || $buildStatus == 6) {
addBuildProducts($db, $build); addBuildProducts($db, $build);
} }
$build->schedulingInfo->delete;
}); });
sendEmailNotification $build; sendEmailNotification $build;
@@ -452,11 +448,11 @@ my $build;
txn_do($db, sub { txn_do($db, sub {
$build = $db->resultset('Builds')->find($buildId); $build = $db->resultset('Builds')->find($buildId);
die "build $buildId doesn't exist" unless defined $build; die "build $buildId doesn't exist" unless defined $build;
die "build $buildId already done" if defined $build->resultInfo; die "build $buildId already done" if $build->finished;
if ($build->schedulingInfo->busy != 0 && $build->schedulingInfo->locker != getppid) { if ($build->busy != 0 && $build->locker != getppid) {
die "build $buildId is already being built"; die "build $buildId is already being built";
} }
$build->schedulingInfo->update({busy => 1, locker => $$}); $build->update({busy => 1, locker => $$});
$build->buildsteps->search({busy => 1})->delete_all; $build->buildsteps->search({busy => 1})->delete_all;
$build->buildproducts->delete_all; $build->buildproducts->delete_all;
}); });
@@ -472,6 +468,6 @@ eval {
if ($@) { if ($@) {
warn $@; warn $@;
txn_do($db, sub { txn_do($db, sub {
$build->schedulingInfo->update({busy => 0, locker => $$}); $build->update({busy => 0, locker => $$});
}); });
} }

View File

@@ -80,11 +80,12 @@ sub sendJobsetErrorNotification() {
); );
$email->body_set($body); $email->body_set($body);
print $email->as_string if $ENV{'HYDRA_MAIL_TEST'}; print STDERR $email->as_string if $ENV{'HYDRA_MAIL_TEST'};
sendmail($email); sendmail($email);
} }
sub permute { sub permute {
my @list = @_; my @list = @_;
for (my $n = scalar @list - 1; $n > 0; $n--) { for (my $n = scalar @list - 1; $n > 0; $n--) {
@@ -105,13 +106,12 @@ sub checkJobset {
my $checkoutStop = time; my $checkoutStop = time;
# Hash the arguments to hydra-eval-jobs and check the # Hash the arguments to hydra-eval-jobs and check the
# JobsetInputHashes to see if we've already evaluated this set of # JobsetInputHashes to see if the previous evaluation had the same
# inputs. If so, bail out. # inputs. If so, bail out.
my @args = ($jobset->nixexprinput, $jobset->nixexprpath, inputsToArgs($inputInfo)); my @args = ($jobset->nixexprinput, $jobset->nixexprpath, inputsToArgs($inputInfo));
my $argsHash = sha256_hex("@args"); my $argsHash = sha256_hex("@args");
if (getPrevJobsetEval($db, $jobset, 0)->hash eq $argsHash) {
if (scalar($jobset->jobsetevals->search({hash => $argsHash})) > 0) { print STDERR " jobset is unchanged, skipping\n";
print " already evaluated, skipping\n";
txn_do($db, sub { txn_do($db, sub {
$jobset->update({lastcheckedtime => time}); $jobset->update({lastcheckedtime => time});
}); });
@@ -125,12 +125,19 @@ sub checkJobset {
txn_do($db, sub { txn_do($db, sub {
my $prevEval = getPrevJobsetEval($db, $jobset, 1);
# Clear the "current" flag on all builds. Since we're in a
# transaction this will only become visible after the new
# current builds have been added.
$jobset->builds->search({iscurrent => 1})->update({iscurrent => 0});
# Schedule each successfully evaluated job. # Schedule each successfully evaluated job.
my %currentBuilds; my %buildIds;
foreach my $job (permute @{$jobs->{job}}) { foreach my $job (permute @{$jobs->{job}}) {
next if $job->{jobName} eq ""; next if $job->{jobName} eq "";
print "considering job " . $job->{jobName} . "\n"; print STDERR " considering job " . $project->name, ":", $jobset->name, ":", $job->{jobName} . "\n";
checkBuild($db, $project, $jobset, $inputInfo, $nixExprInput, $job, \%currentBuilds); checkBuild($db, $project, $jobset, $inputInfo, $nixExprInput, $job, \%buildIds, $prevEval);
} }
# Update the last checked times and error messages for each # Update the last checked times and error messages for each
@@ -140,22 +147,11 @@ sub checkJobset {
$jobset->update({lastcheckedtime => time}); $jobset->update({lastcheckedtime => time});
foreach my $job ($jobset->jobs->all) { $_->update({ errormsg => $failedJobNames{$_->name} ? join '\n', @{$failedJobNames{$_->name}} : undef })
if ($failedJobNames{$job->name}) { foreach $jobset->jobs->all;
$job->update({errormsg => join '\n', @{$failedJobNames{$job->name}}});
} else {
$job->update({errormsg => undef});
}
}
# Clear the "current" flag on all builds that are no longer
# current.
foreach my $build ($jobset->builds->search({iscurrent => 1})) {
$build->update({iscurrent => 0}) unless defined $currentBuilds{$build->id};
}
my $hasNewBuilds = 0; my $hasNewBuilds = 0;
while (my ($id, $new) = each %currentBuilds) { while (my ($id, $new) = each %buildIds) {
$hasNewBuilds = 1 if $new; $hasNewBuilds = 1 if $new;
} }
@@ -168,13 +164,16 @@ sub checkJobset {
}); });
if ($hasNewBuilds) { if ($hasNewBuilds) {
while (my ($id, $new) = each %currentBuilds) { while (my ($id, $new) = each %buildIds) {
$ev->jobsetevalmembers->create({ build => $id, isnew => $new }); $ev->jobsetevalmembers->create({ build => $id, isnew => $new });
} }
print STDERR " created new eval ", $ev->id, "\n";
} else {
print STDERR " created cached eval ", $ev->id, "\n";
} }
}); });
# Store the errors messages for jobs that failed to evaluate. # Store the error messages for jobs that failed to evaluate.
my $msg = ""; my $msg = "";
foreach my $error (@{$jobs->{error}}) { foreach my $error (@{$jobs->{error}}) {
my $bindings = ""; my $bindings = "";
@@ -197,7 +196,7 @@ sub checkJobset {
sub checkJobsetWrapped { sub checkJobsetWrapped {
my ($project, $jobset) = @_; my ($project, $jobset) = @_;
print "considering jobset ", $jobset->name, " in ", $project->name, "\n"; print STDERR "considering jobset ", $project->name, ":", $jobset->name, "\n";
eval { eval {
checkJobset($project, $jobset); checkJobset($project, $jobset);
@@ -205,7 +204,7 @@ sub checkJobsetWrapped {
if ($@) { if ($@) {
my $msg = $@; my $msg = $@;
print "error evaluating jobset ", $jobset->name, ": $msg"; print STDERR "error evaluating jobset ", $jobset->name, ": $msg";
txn_do($db, sub { txn_do($db, sub {
$jobset->update({lastcheckedtime => time}); $jobset->update({lastcheckedtime => time});
setJobsetError($jobset, $msg); setJobsetError($jobset, $msg);
@@ -216,7 +215,7 @@ sub checkJobsetWrapped {
sub checkProjects { sub checkProjects {
foreach my $project ($db->resultset('Projects')->search({enabled => 1})) { foreach my $project ($db->resultset('Projects')->search({enabled => 1})) {
print "considering project ", $project->name, "\n"; print STDERR "considering project ", $project->name, "\n";
checkJobsetWrapped($project, $_) checkJobsetWrapped($project, $_)
foreach $project->jobsets->search({enabled => 1}); foreach $project->jobsets->search({enabled => 1});
} }
@@ -237,7 +236,7 @@ while (1) {
eval { eval {
checkProjects; checkProjects;
}; };
if ($@) { print "$@"; } if ($@) { print STDERR "$@"; }
print "sleeping...\n"; print STDERR "sleeping...\n";
sleep 30; sleep 30;
} }

View File

@@ -20,10 +20,9 @@ STDOUT->autoflush();
sub unlockDeadBuilds { sub unlockDeadBuilds {
# Unlock builds whose building process has died. # Unlock builds whose building process has died.
txn_do($db, sub { txn_do($db, sub {
my @builds = $db->resultset('Builds')->search( my @builds = $db->resultset('Builds')->search({finished => 0, busy => 1});
{finished => 0, busy => 1}, {join => 'schedulingInfo'});
foreach my $build (@builds) { foreach my $build (@builds) {
my $pid = $build->schedulingInfo->locker; my $pid = $build->locker;
my $unlock = 0; my $unlock = 0;
if ($pid == $$) { if ($pid == $$) {
# Work around sqlite locking timeouts: if the child # Work around sqlite locking timeouts: if the child
@@ -32,7 +31,7 @@ sub unlockDeadBuilds {
# So if after a minute it hasn't been updated, # So if after a minute it hasn't been updated,
# unlock the build. !!! need a better fix for those # unlock the build. !!! need a better fix for those
# locking timeouts. # locking timeouts.
if ($build->schedulingInfo->starttime + 60 < time) { if ($build->starttime + 60 < time) {
$unlock = 1; $unlock = 1;
} }
} elsif (kill(0, $pid) != 1) { # see if we can signal the process } elsif (kill(0, $pid) != 1) { # see if we can signal the process
@@ -40,9 +39,9 @@ sub unlockDeadBuilds {
} }
if ($unlock) { if ($unlock) {
print "build ", $build->id, " pid $pid died, unlocking\n"; print "build ", $build->id, " pid $pid died, unlocking\n";
$build->schedulingInfo->busy(0); $build->busy(0);
$build->schedulingInfo->locker(""); $build->locker("");
$build->schedulingInfo->update; $build->update;
} }
} }
}); });
@@ -64,7 +63,7 @@ sub findBuildDependencyInQueue {
($depBuild) = $db->resultset('Builds')->search( ($depBuild) = $db->resultset('Builds')->search(
{ drvpath => [ @drvs ], finished => 0, busy => 0, enabled => 1, disabled => 0 }, { drvpath => [ @drvs ], finished => 0, busy => 0, enabled => 1, disabled => 0 },
{ join => ['schedulingInfo', 'project'], rows => 1 } ) ; { join => ['project'], rows => 1 } ) ;
return $depBuild; return $depBuild;
} }
@@ -79,7 +78,7 @@ sub checkBuilds {
# Get the system types for the runnable builds. # Get the system types for the runnable builds.
my @systemTypes = $db->resultset('Builds')->search( my @systemTypes = $db->resultset('Builds')->search(
{ finished => 0, busy => 0, enabled => 1, disabled => 0 }, { finished => 0, busy => 0, enabled => 1, disabled => 0 },
{ join => ['schedulingInfo', 'project'], select => ['system'], as => ['system'], distinct => 1 }); { join => ['project'], select => ['system'], as => ['system'], distinct => 1 });
# For each system type, select up to the maximum number of # For each system type, select up to the maximum number of
# concurrent build for that system type. Choose the highest # concurrent build for that system type. Choose the highest
@@ -88,8 +87,7 @@ sub checkBuilds {
# How many builds are already currently executing for this # How many builds are already currently executing for this
# system type? # system type?
my $nrActive = $db->resultset('Builds')->search( my $nrActive = $db->resultset('Builds')->search(
{finished => 0, busy => 1, system => $system->system}, {finished => 0, busy => 1, system => $system->system})->count;
{join => 'schedulingInfo'})->count;
# How many extra builds can we start? # How many extra builds can we start?
(my $systemTypeInfo) = $db->resultset('SystemTypes')->search({system => $system->system}); (my $systemTypeInfo) = $db->resultset('SystemTypes')->search({system => $system->system});
@@ -100,7 +98,7 @@ sub checkBuilds {
# Select the highest-priority builds to start. # Select the highest-priority builds to start.
my @builds = $extraAllowed == 0 ? () : $db->resultset('Builds')->search( my @builds = $extraAllowed == 0 ? () : $db->resultset('Builds')->search(
{ finished => 0, busy => 0, system => $system->system, enabled => 1, disabled => 0 }, { finished => 0, busy => 0, system => $system->system, enabled => 1, disabled => 0 },
{ join => ['schedulingInfo', 'project'], order_by => ["priority DESC", "timestamp"], { join => ['project'], order_by => ["priority DESC", "timestamp"],
rows => $extraAllowed }); rows => $extraAllowed });
print "system type `", $system->system, print "system type `", $system->system,
@@ -114,11 +112,11 @@ sub checkBuilds {
my $logfile = getcwd . "/logs/" . $build->id; my $logfile = getcwd . "/logs/" . $build->id;
mkdir(dirname $logfile); mkdir(dirname $logfile);
unlink($logfile); unlink($logfile);
$build->schedulingInfo->busy(1); $build->busy(1);
$build->schedulingInfo->locker($$); $build->locker($$);
$build->schedulingInfo->logfile($logfile); $build->logfile($logfile);
$build->schedulingInfo->starttime(time); $build->starttime(time);
$build->schedulingInfo->update; $build->update;
push @buildsStarted, $build; push @buildsStarted, $build;
} }
} }
@@ -130,7 +128,7 @@ sub checkBuilds {
my $id = $build->id; my $id = $build->id;
print "starting build $id (", $build->project->name, ":", $build->jobset->name, ':', $build->job->name, ") on ", $build->system, "\n"; print "starting build $id (", $build->project->name, ":", $build->jobset->name, ':', $build->job->name, ") on ", $build->system, "\n";
eval { eval {
my $logfile = $build->schedulingInfo->logfile; my $logfile = $build->logfile;
my $child = fork(); my $child = fork();
die unless defined $child; die unless defined $child;
if ($child == 0) { if ($child == 0) {
@@ -147,9 +145,9 @@ sub checkBuilds {
if ($@) { if ($@) {
warn $@; warn $@;
txn_do($db, sub { txn_do($db, sub {
$build->schedulingInfo->busy(0); $build->busy(0);
$build->schedulingInfo->locker($$); $build->locker($$);
$build->schedulingInfo->update; $build->update;
}); });
} }
} }

View File

@@ -13,32 +13,64 @@ my $db = openHydraDB;
my %roots; my %roots;
sub registerRoot { sub addRoot {
my ($path) = @_; my ($path) = @_;
Hydra::Helper::Nix::registerRoot($path); registerRoot($path);
$roots{$path} = 1; $roots{$path} = 1;
} }
my @columns = ( "id", "project", "jobset", "job", "system", "finished", "outpath", "drvpath", "timestamp" );
sub keepBuild { sub keepBuild {
my ($build) = @_; my ($build) = @_;
print STDERR " keeping build ", $build->id, " (", print STDERR " keeping ", ($build->finished ? "" : "scheduled "), "build ", $build->id, " (",
$build->system, "; ", $build->get_column('project'), ":", $build->get_column('jobset'), ":", $build->get_column('job'), "; ",
$build->system, "; ",
strftime("%Y-%m-%d %H:%M:%S", localtime($build->timestamp)), ")\n"; strftime("%Y-%m-%d %H:%M:%S", localtime($build->timestamp)), ")\n";
if (isValidPath($build->outpath)) { if (isValidPath($build->outpath)) {
registerRoot $build->outpath; addRoot $build->outpath;
} else { } else {
print STDERR "warning: output ", $build->outpath, " has disappeared\n"; print STDERR " warning: output ", $build->outpath, " has disappeared\n" if $build->finished;
}
if (!$build->finished) {
if (isValidPath($build->drvpath)) {
addRoot $build->drvpath;
} else {
print STDERR " warning: derivation ", $build->drvpath, " has disappeared\n";
}
} }
} }
# Go over all projects. # Read the current GC roots. We need to do that here so that we don't
# delete roots that were added while we were determining the desired
# roots.
print STDERR "*** reading current roots...\n";
my $gcRootsDir = getGCRootsDir;
opendir DIR, $gcRootsDir or die;
my @roots = readdir DIR;
closedir DIR;
foreach my $project ($db->resultset('Projects')->all) {
# Keep every build in every release of every project.
print STDERR "*** looking for release members\n";
keepBuild $_ foreach $db->resultset('Builds')->search_literal(
"exists (select 1 from releasemembers where build = me.id)", { order_by => ["project", "jobset", "job", "id"] });
# Keep all builds that have been marked as "keep".
print STDERR "*** looking for kept builds\n";
my @buildsToKeep = $db->resultset('Builds')->search(
{ finished => 1, keep => 1 }, { order_by => ["project", "jobset", "job", "id"], columns => [ @columns ] });
keepBuild $_ foreach @buildsToKeep;
# Go over all projects.
foreach my $project ($db->resultset('Projects')->search({}, { order_by => ["name"] })) {
# Go over all jobsets in this project. # Go over all jobsets in this project.
foreach my $jobset ($project->jobsets->all) { foreach my $jobset ($project->jobsets->search({}, { order_by => ["name" ]})) {
my $keepnr = $jobset->keepnr; my $keepnr = $jobset->keepnr;
# If the jobset has been disabled for more than one week, than # If the jobset has been disabled for more than one week, than
@@ -53,29 +85,19 @@ foreach my $project ($db->resultset('Projects')->all) {
next; next;
} }
# Go over all jobs in this jobset. print STDERR "*** looking for the $keepnr most recent successful builds of each job in jobset ",
foreach my $job ($jobset->jobs->all) { $project->name, ":", $jobset->name, "\n";
print STDERR "*** looking for builds to keep in job ",
$project->name, ":", $job->jobset->name, ":", $job->name, "\n";
# Keep the N most recent successful builds for each job keepBuild $_ foreach $jobset->builds->search(
# and platform. { 'me.id' => { 'in' => \
# !!! Take time into account? E.g. don't delete builds [ "select b2.id from Builds b2 join " .
# that are younger than N days. " (select distinct job, system, coalesce( " .
my @systems = $job->builds->search({ }, { select => ["system"], distinct => 1 })->all; " (select id from builds where project = b.project and jobset = b.jobset and job = b.job and system = b.system and finished = 1 and buildStatus = 0 order by id desc offset ? limit 1)" .
foreach my $system (@systems) { " , 0) nth from builds b where project = ? and jobset = ? and isCurrent = 1) x " .
my @recentBuilds = $job->builds->search( " on b2.project = ? and b2.jobset = ? and b2.job = x.job and b2.system = x.system and (id >= x.nth) where finished = 1 and buildStatus = 0"
{ finished => 1 , [ '', $keepnr - 1 ], [ '', $project->name ], [ '', $jobset->name ], [ '', $project->name ], [ '', $jobset->name ] ] }
, buildStatus => 0 # == success },
, system => $system->system { order_by => ["job", "system", "id"], columns => [ @columns ] });
},
{ join => 'resultInfo'
, order_by => 'me.id DESC'
, rows => $keepnr
});
keepBuild $_ foreach @recentBuilds;
}
}
} }
# Go over all views in this project. # Go over all views in this project.
@@ -88,56 +110,35 @@ foreach my $project ($db->resultset('Projects')->all) {
# Keep all builds belonging to the most recent successful view result. # Keep all builds belonging to the most recent successful view result.
my $latest = getLatestSuccessfulViewResult($project, $primaryJob, $jobs); my $latest = getLatestSuccessfulViewResult($project, $primaryJob, $jobs);
if (defined $latest) { if (defined $latest) {
print STDERR "keeping latest successful view result ", $latest->id, " (", $latest->get_column('releasename'), ")\n"; print STDERR " keeping latest successful view result ", $latest->id, " (", $latest->get_column('releasename'), ")\n";
my $result = getViewResult($latest, $jobs); my $result = getViewResult($latest, $jobs);
keepBuild $_->{build} foreach @{$result->{jobs}}; keepBuild $_->{build} foreach @{$result->{jobs}};
} }
} }
# Keep every build in every release in this project.
print STDERR "*** keeping releases in project ", $project->name, "\n"
if scalar $project->releases > 0;
foreach my $release ($project->releases->all) {
print STDERR "keeping release ", $release->name, "\n";
keepBuild $_->build foreach $release->releasemembers;
}
} }
# Keep all builds that have been marked as "keep".
print STDERR "*** looking for kept builds\n";
my @buildsToKeep = $db->resultset('Builds')->search({finished => 1, keep => 1}, {join => 'resultInfo'});
keepBuild $_ foreach @buildsToKeep;
# For scheduled builds, we register the derivation as a GC root. # For scheduled builds, we register the derivation as a GC root.
print STDERR "*** looking for scheduled builds\n"; print STDERR "*** looking for scheduled builds\n";
foreach my $build ($db->resultset('Builds')->search({finished => 0}, {join => 'schedulingInfo'})) { keepBuild $_ foreach $db->resultset('Builds')->search({ finished => 0 }, { columns => [ @columns ] });
if (isValidPath($build->drvpath)) {
print STDERR "keeping scheduled build ", $build->id, " (",
strftime("%Y-%m-%d %H:%M:%S", localtime($build->timestamp)), ")\n";
registerRoot $build->drvpath;
registerRoot $build->outpath if -e $build->outpath;
} else {
print STDERR "warning: derivation ", $build->drvpath, " has disappeared\n";
}
}
# Remove existing roots that are no longer wanted. !!! racy # Remove existing roots that are no longer wanted.
print STDERR "*** removing unneeded GC roots\n"; print STDERR "*** removing unneeded GC roots\n";
my $gcRootsDir = getGCRootsDir; my $rootsKept = 0;
my $rootsDeleted = 0;
opendir DIR, $gcRootsDir or die; foreach my $link (@roots) {
next if $link eq "." || $link eq "..";
foreach my $link (readdir DIR) { my $path = "/nix/store/$link";
next if !-l "$gcRootsDir/$link";
my $path = readlink "$gcRootsDir/$link" or die;
if (!defined $roots{$path}) { if (!defined $roots{$path}) {
print STDERR "removing root $path\n"; print STDERR "removing root $path\n";
unlink "$gcRootsDir/$link" or die "cannot remove $gcRootsDir/$link"; $rootsDeleted++;
#unlink "$gcRootsDir/$link" or warn "cannot remove $gcRootsDir/$link";
} else {
$rootsKept++;
} }
} }
closedir DIR; print STDERR "kept $rootsKept roots, deleted $rootsDeleted roots\n";

View File

@@ -114,11 +114,6 @@ create table Jobs (
); );
-- This table contains all wbuilds, either scheduled or finished. For
-- scheduled builds, additional info (such as the priority) can be
-- found in the BuildSchedulingInfo table. For finished builds,
-- additional info (such as the logs, build products, etc.) can be
-- found in several tables, such as BuildResultInfo and BuildProducts.
create table Builds ( create table Builds (
#ifdef POSTGRESQL #ifdef POSTGRESQL
id serial primary key not null, id serial primary key not null,
@@ -156,37 +151,22 @@ create table Builds (
-- build. -- build.
nixExprInput text, nixExprInput text,
nixExprPath text, nixExprPath text,
foreign key (project) references Projects(name) on update cascade,
foreign key (project, jobset) references Jobsets(project, name) on update cascade,
foreign key (project, jobset, job) references Jobs(project, jobset, name) on update cascade
);
-- Information about scheduled builds.
-- Info for a scheduled build.
create table BuildSchedulingInfo (
id integer primary key not null,
priority integer not null default 0, priority integer not null default 0,
busy integer not null default 0, -- true means someone is building this job now busy integer not null default 0, -- true means someone is building this job now
locker text not null default '', -- !!! hostname/pid of the process building this job? locker text, -- !!! hostname/pid of the process building this job?
logfile text, -- if busy, the path of the logfile logfile text, -- if busy, the path of the logfile
disabled integer not null default 0, disabled integer not null default 0, -- !!! boolean
startTime integer, -- if busy, time we started startTime integer, -- if busy, time we started
stopTime integer,
foreign key (id) references Builds(id) on delete cascade
);
-- Information about finished builds.
-- Info for a finished build. isCachedBuild integer, -- boolean
create table BuildResultInfo (
id integer primary key not null,
isCachedBuild integer not null, -- boolean
-- Status codes: -- Status codes:
-- 0 = succeeded -- 0 = succeeded
@@ -199,23 +179,17 @@ create table BuildResultInfo (
errorMsg text, -- error message in case of a Nix failure errorMsg text, -- error message in case of a Nix failure
startTime integer, -- in Unix time, 0 = used cached build result logSize bigint,
stopTime integer, size bigint,
closureSize bigint,
logfile text, -- the path of the logfile
logsize bigint not null default 0,
size bigint not null default 0,
closuresize bigint not null default 0,
releaseName text, -- e.g. "patchelf-0.5pre1234" releaseName text, -- e.g. "patchelf-0.5pre1234"
keep integer not null default 0, -- true means never garbage-collect the build output keep integer not null default 0, -- true means never garbage-collect the build output
failedDepBuild integer, -- obsolete foreign key (project) references Projects(name) on update cascade,
failedDepStepNr integer, -- obsolete foreign key (project, jobset) references Jobsets(project, name) on update cascade,
foreign key (project, jobset, job) references Jobs(project, jobset, name) on update cascade
foreign key (id) references Builds(id) on delete cascade
); );
@@ -519,16 +493,18 @@ create table BuildMachineSystemTypes (
-- Some indices. -- Some indices.
create index IndexBuildInputsOnBuild on BuildInputs(build); create index IndexBuildInputsOnBuild on BuildInputs(build);
create index IndexBuildInputsOnDependency on BuildInputs(dependency); create index IndexBuildInputsOnDependency on BuildInputs(dependency);
create index IndexBuildProducstOnBuildAndType on BuildProducts(build, type); create index IndexBuildProducstOnBuildAndType on BuildProducts(build, type);
create index IndexBuildProductsOnBuild on BuildProducts(build); create index IndexBuildProductsOnBuild on BuildProducts(build);
create index IndexBuildSchedulingInfoOnBuild on BuildSchedulingInfo(id); -- idem
create index IndexBuildStepsOnBuild on BuildSteps(build); create index IndexBuildStepsOnBuild on BuildSteps(build);
create index IndexBuildStepsOnBusy on BuildSteps(busy);
create index IndexBuildStepsOnDrvpathTypeBusyStatus on BuildSteps(drvpath, type, busy, status); create index IndexBuildStepsOnDrvpathTypeBusyStatus on BuildSteps(drvpath, type, busy, status);
create index IndexBuildStepsOnOutpath on BuildSteps(outpath); create index IndexBuildStepsOnOutpath on BuildSteps(outpath);
create index IndexBuildStepsOnOutpathBuild on BuildSteps (outpath, build); create index IndexBuildStepsOnOutpathBuild on BuildSteps (outpath, build);
create index IndexBuildsOnFinished on Builds(finished); create index IndexBuildsOnFinished on Builds(finished);
create index IndexBuildsOnFinishedBusy on Builds(finished, busy);
create index IndexBuildsOnIsCurrent on Builds(isCurrent); create index IndexBuildsOnIsCurrent on Builds(isCurrent);
create index IndexBuildsOnJobsetIsCurrent on Builds(project, jobset, isCurrent); create index IndexBuildsOnJobsetIsCurrent on Builds(project, jobset, isCurrent);
create index IndexBuildsOnJobIsCurrent on Builds(project, jobset, job, isCurrent); create index IndexBuildsOnJobIsCurrent on Builds(project, jobset, job, isCurrent);
@@ -551,3 +527,10 @@ create index IndexJobsetInputAltsOnInput on JobsetInputAlts(project, jobset, inp
create index IndexJobsetInputAltsOnJobset on JobsetInputAlts(project, jobset); create index IndexJobsetInputAltsOnJobset on JobsetInputAlts(project, jobset);
create index IndexProjectsOnEnabled on Projects(enabled); create index IndexProjectsOnEnabled on Projects(enabled);
create index IndexReleaseMembersOnBuild on ReleaseMembers(build); create index IndexReleaseMembersOnBuild on ReleaseMembers(build);
-- For hydra-update-gc-roots.
create index IndexBuildsOnKeep on Builds(keep);
create index IndexMostRecentSuccessfulBuilds on Builds(project, jobset, job, system, finished, buildStatus, id desc);
-- To get the most recent eval for a jobset.
create index IndexJobsetEvalsOnJobsetId on JobsetEvals(project, jobset, hasNewBuilds, id desc);

44
src/sql/upgrade-2.sql Normal file
View File

@@ -0,0 +1,44 @@
alter table Builds
add column priority integer not null default 0,
add column busy integer not null default 0,
add column locker text,
add column logfile text,
add column disabled integer not null default 0,
add column startTime integer,
add column stopTime integer,
add column isCachedBuild integer,
add column buildStatus integer,
add column errorMsg text,
add column logSize bigint,
add column size bigint,
add column closureSize bigint,
add column releaseName text,
add column keep integer not null default 0;
update Builds b set
priority = (select priority from BuildSchedulingInfo s where s.id = b.id),
busy = (select busy from BuildSchedulingInfo s where s.id = b.id),
disabled = (select disabled from BuildSchedulingInfo s where s.id = b.id),
locker = (select locker from BuildSchedulingInfo s where s.id = b.id),
logfile = (select logfile from BuildSchedulingInfo s where s.id = b.id)
where exists (select 1 from BuildSchedulingInfo s where s.id = b.id);
update Builds b set
startTime = ((select startTime from BuildSchedulingInfo s where s.id = b.id) union (select startTime from BuildResultInfo r where r.id = b.id));
update Builds b set
isCachedBuild = (select isCachedBuild from BuildResultInfo r where r.id = b.id),
buildStatus = (select buildStatus from BuildResultInfo r where r.id = b.id),
errorMsg = (select errorMsg from BuildResultInfo r where r.id = b.id),
startTime = (select startTime from BuildResultInfo r where r.id = b.id),
stopTime = (select stopTime from BuildResultInfo r where r.id = b.id),
logfile = (select logfile from BuildResultInfo r where r.id = b.id),
logSize = (select logsize from BuildResultInfo r where r.id = b.id),
size = (select size from BuildResultInfo r where r.id = b.id),
closureSize = (select closuresize from BuildResultInfo r where r.id = b.id),
releaseName = (select releaseName from BuildResultInfo r where r.id = b.id),
keep = (select keep from BuildResultInfo r where r.id = b.id)
where exists (select 1 from BuildResultInfo r where r.id = b.id);
drop table BuildSchedulingInfo;
drop table BuildResultInfo;

2
src/sql/upgrade-3.sql Normal file
View File

@@ -0,0 +1,2 @@
create index IndexBuildsOnKeep on Builds(keep); -- used by hydra-update-gc-roots
create index IndexMostRecentSuccessfulBuilds on Builds(project, jobset, job, system, finished, buildStatus, id desc); -- used by hydra-update-gc-roots

1
src/sql/upgrade-4.sql Normal file
View File

@@ -0,0 +1 @@
create index IndexJobsetEvalsOnJobsetId on JobsetEvals(project, jobset, hasNewBuilds, id desc);