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 {
my ($self, $c) = @_;
my $latest = joinWithResultInfo($c, $c->stash->{jobStatus});
my $maintainer = $c->request->params->{"maintainer"};
$latest = $latest->search(
my $latest = $c->stash->{jobStatus}->search(
defined $maintainer ? { maintainers => { like => "%$maintainer%" } } : {},
{ '+select' => ["me.statusChangeId", "me.statusChangeTime", "resultInfo.buildStatus"]
, '+as' => ["statusChangeId", "statusChangeTime", "buildStatus"]
{ '+select' => ["me.statusChangeId", "me.statusChangeTime"]
, '+as' => ["statusChangeId", "statusChangeTime"]
, order_by => "coalesce(statusChangeTime, 0) desc"
});
@@ -43,7 +41,7 @@ sub errors : Chained('get_builds') PathPart Args(0) {
[$c->stash->{allJobs}->search({errormsg => {'!=' => ''}})]
if defined $c->stash->{allJobs};
$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->{totalBuilds} = $nrBuilds;
$c->stash->{builds} = [ joinWithResultInfo($c, $c->stash->{allBuilds})->search(
$c->stash->{builds} = [ $c->stash->{allBuilds}->search(
{ finished => 1 },
{ '+select' => ["resultInfo.buildStatus"]
, '+as' => ["buildStatus"]
, order_by => "timestamp DESC"
{ order_by => "timestamp DESC"
, columns => [@buildListColumns]
, rows => $resultsPerPage
, page => $page }) ];
}
@@ -79,12 +76,10 @@ sub nix : Chained('get_builds') PathPart('channel') CaptureArgs(1) {
eval {
if ($channelName eq "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 {
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') {
my ($self, $c, @rest) = @_;
my ($latest) = joinWithResultInfo($c, $c->stash->{allBuilds})
->search({finished => 1, buildstatus => 0}, {order_by => ["isCurrent DESC", "timestamp DESC"]});
my ($latest) = $c->stash->{allBuilds}->search(
{finished => 1, buildstatus => 0}, {order_by => ["isCurrent DESC", "timestamp DESC"]});
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;
my ($latest) = joinWithResultInfo($c, $c->stash->{allBuilds})
->search({finished => 1, buildstatus => 0, system => $system}, {order_by => ["isCurrent DESC", "timestamp DESC"]});
my ($latest) = $c->stash->{allBuilds}->search(
{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;

View File

@@ -3,14 +3,45 @@ package Hydra::Base::Controller::NixChannel;
use strict;
use warnings;
use base 'Catalyst::Controller';
use Nix::Store;
use Hydra::Helper::Nix;
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 {
my ($self, $c) = @_;
$c->stash->{current_view} = 'NixClosure';
getChannelData($c, 1);
# !!! 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
# 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) = @_;
$c->stash->{current_view} = 'NixManifest';
$c->stash->{narBase} = $c->uri_for($c->controller('Root')->action_for("nar"));
getChannelData($c, 1);
}
sub pkg : Chained('nix') PathPart Args(1) {
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'.")
unless defined $pkg;
$c->stash->{build} = $pkg->{build};
if (!isValidPath($c->stash->{build}->outpath)) {
$c->response->status(410); # "Gone"
error($c, "Build " . $c->stash->{build}->id . " is no longer available.");
}
$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) {
my ($self, $c) = @_;
$c->stash->{current_view} = 'NixExprs';
getChannelData($c, 1);
}
sub name {
my ($build) = @_;
return $build->get_column('releasename') || $build->nixname;
return $build->releasename || $build->nixname;
}
sub sortPkgs {
# Sort by name, then timestamp.
# Sort by name, then id.
return sort
{ 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) {
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->{nixPkgs} = [sortPkgs (values %{$c->stash->{nixPkgs}})];
}

View File

@@ -15,11 +15,13 @@ use File::Slurp;
# !!! Rewrite this to use View::JSON.
sub api : Chained('/') PathPart('api') CaptureArgs(0) {
my ($self, $c) = @_;
$c->response->content_type('application/json');
}
sub projectToHash {
my ($project) = @_;
return {
@@ -28,14 +30,15 @@ sub projectToHash {
};
}
sub projects : Chained('api') PathPart('projects') Args(0) {
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) {
push @list, projectToHash($p) ;
push @list, projectToHash($p);
}
$c->stash->{'plain'} = {
@@ -44,6 +47,7 @@ sub projects : Chained('api') PathPart('projects') Args(0) {
$c->forward('Hydra::View::Plain');
}
sub buildToHash {
my ($build) = @_;
my $result = {
@@ -58,7 +62,7 @@ sub buildToHash {
};
if($build->finished) {
$result->{'buildstatus'} = $build->get_column("buildstatus") ;
$result->{'buildstatus'} = $build->get_column("buildstatus");
} else {
$result->{'busy'} = $build->get_column("busy");
$result->{'priority'} = $build->get_column("priority");
@@ -67,28 +71,27 @@ sub buildToHash {
return $result;
};
sub latestbuilds : Chained('api') PathPart('latestbuilds') Args(0) {
my ($self, $c) = @_;
my $nr = $c->request->params->{nr} ;
my $nr = $c->request->params->{nr};
error($c, "Parameter not defined!") if !defined $nr;
my $project = $c->request->params->{project} ;
my $jobset = $c->request->params->{jobset} ;
my $job = $c->request->params->{job} ;
my $system = $c->request->params->{system} ;
my $project = $c->request->params->{project};
my $jobset = $c->request->params->{jobset};
my $job = $c->request->params->{job};
my $system = $c->request->params->{system};
my $filter = {finished => 1} ;
my $filter = {finished => 1};
$filter->{project} = $project if ! $project eq "";
$filter->{jobset} = $jobset if ! $jobset eq "";
$filter->{job} = $job if !$job 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 ;
foreach my $b (@latest) {
push @list, buildToHash($b) ;
}
my @list;
push @list, buildToHash($_) foreach @latest;
$c->stash->{'plain'} = {
data => scalar (JSON::Any->objToJson(\@list))
@@ -96,6 +99,7 @@ sub latestbuilds : Chained('api') PathPart('latestbuilds') Args(0) {
$c->forward('Hydra::View::Plain');
}
sub jobsetToHash {
my ($jobset) = @_;
return {
@@ -108,10 +112,11 @@ sub jobsetToHash {
};
}
sub jobsets : Chained('api') PathPart('jobsets') Args(0) {
my ($self, $c) = @_;
my $projectName = $c->request->params->{project} ;
my $projectName = $c->request->params->{project};
error($c, "Parameter 'project' not defined!") if !defined $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 @list ;
foreach my $j (@jobsets) {
push @list, jobsetToHash($j) ;
}
my @list;
push @list, jobsetToHash($_) foreach @jobsets;
$c->stash->{'plain'} = {
data => scalar (JSON::Any->objToJson(\@list))
@@ -130,57 +133,59 @@ sub jobsets : Chained('api') PathPart('jobsets') Args(0) {
$c->forward('Hydra::View::Plain');
}
sub queue : Chained('api') PathPart('queue') Args(0) {
my ($self, $c) = @_;
my $nr = $c->request->params->{nr} ;
my $nr = $c->request->params->{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 ;
foreach my $b (@builds) {
push @list, buildToHash($b) ;
}
my @list;
push @list, buildToHash($_) foreach @builds;
$c->stash->{'plain'} = {
data => scalar (JSON::Any->objToJson(\@list))
};
$c->forward('Hydra::View::Plain');
}
sub nrqueue : Chained('api') PathPart('nrqueue') Args(0) {
my ($self, $c) = @_;
my $nrQueuedBuilds = $c->model('DB::BuildSchedulingInfo')->count();
my $nrQueuedBuilds = $c->model('DB::Builds')->search({finished => 0})->count();
$c->stash->{'plain'} = {
data => " $nrQueuedBuilds"
data => "$nrQueuedBuilds"
};
$c->forward('Hydra::View::Plain');
}
sub nrrunning : Chained('api') PathPart('nrrunning') Args(0) {
my ($self, $c) = @_;
my $nrRunningBuilds = $c->model('DB::BuildSchedulingInfo')->search({ busy => 1 }, {})->count();
$c->stash->{'plain'} = {
data => " $nrRunningBuilds"
my $nrRunningBuilds = $c->model('DB::Builds')->search({finished => 0, busy => 1 })->count();
$c->stash->{'plain'} = {
data => "$nrRunningBuilds"
};
$c->forward('Hydra::View::Plain');
}
sub nrbuilds : Chained('api') PathPart('nrbuilds') Args(0) {
my ($self, $c) = @_;
my $nr = $c->request->params->{nr} ;
my $period = $c->request->params->{period} ;
my $nr = $c->request->params->{nr};
my $period = $c->request->params->{period};
error($c, "Parameter not defined!") if !defined $nr || !defined $period;
my $base;
my $project = $c->request->params->{project} ;
my $jobset = $c->request->params->{jobset} ;
my $job = $c->request->params->{job} ;
my $system = $c->request->params->{system} ;
my $project = $c->request->params->{project};
my $jobset = $c->request->params->{jobset};
my $job = $c->request->params->{job};
my $system = $c->request->params->{system};
my $filter = {finished => 1} ;
my $filter = {finished => 1};
$filter->{project} = $project if ! $project eq "";
$filter->{jobset} = $jobset if ! $jobset 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 = 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 @arr ;
foreach my $d (@stats) {
push @arr, int($d->get_column("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;
push @arr, int($_->get_column("nr")) foreach @stats;
@arr = reverse(@arr);
$c->stash->{'plain'} = {
@@ -202,38 +205,40 @@ sub nrbuilds : Chained('api') PathPart('nrbuilds') Args(0) {
$c->forward('Hydra::View::Plain');
}
sub scmdiff : Chained('api') PathPart('scmdiff') Args(0) {
my ($self, $c) = @_;
my $uri = $c->request->params->{uri} ;
my $type = $c->request->params->{type} ;
my $rev1 = $c->request->params->{rev1} ;
my $rev2 = $c->request->params->{rev2} ;
my $uri = $c->request->params->{uri};
my $type = $c->request->params->{type};
my $rev1 = $c->request->params->{rev1};
my $rev2 = $c->request->params->{rev2};
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 = "";
if($type eq "hg") {
if ($type eq "hg") {
my $clonePath = scmPath . "/" . sha256_hex($uri);
die if ! -d $clonePath;
$branch = `(cd $clonePath ; hg log --template '{branch}' -r $rev2)`;
$diff .= `(cd $clonePath ; hg log -r $rev1 -r $rev2 -b $branch)`;
$diff .= `(cd $clonePath ; hg diff -r $rev1:$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 diff -r $rev1:$rev2)`;
} elsif ($type eq "git") {
my $clonePath = scmPath . "/" . sha256_hex($uri);
die if ! -d $clonePath;
$diff .= `(cd $clonePath ; git log $rev1..$rev2)`;
$diff .= `(cd $clonePath ; git diff $rev1..$rev2)`;
$diff .= `(cd $clonePath; git log $rev1..$rev2)`;
$diff .= `(cd $clonePath; git diff $rev1..$rev2)`;
}
$c->stash->{'plain'} = { data => (scalar $diff) || " " };
$c->forward('Hydra::View::Plain');
}
sub readNormalizedLog {
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`;
$res =~ s/\/nix\/store\/[a-z0-9]*-/\/nix\/store\/...-/g;
@@ -242,6 +247,7 @@ sub readNormalizedLog {
return $res;
}
sub logdiff : Chained('api') PathPart('logdiff') Args(2) {
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.")
if !defined $build2;
if (-f $build1->resultInfo->logfile && -f $build2->resultInfo->logfile) {
my $logtext1 = readNormalizedLog($build1->resultInfo->logfile);
my $logtext2 = readNormalizedLog($build2->resultInfo->logfile);
if (-f $build1->logfile && -f $build2->logfile) {
my $logtext1 = readNormalizedLog($build1->logfile);
my $logtext2 = readNormalizedLog($build2->logfile);
$diff = diff \$logtext1, \$logtext2;
} else {
$c->response->status(404);
@@ -267,4 +273,5 @@ sub logdiff : Chained('api') PathPart('logdiff') Args(2) {
$c->forward('Hydra::View::Plain');
}
1;

View File

@@ -57,9 +57,9 @@ sub index : Chained('admin') PathPart('') Args(0) {
, '+as' => ['idle']
})];
$c->stash->{steps} = [ $c->model('DB::BuildSteps')->search(
{ 'me.busy' => 1, 'schedulingInfo.busy' => 1 },
{ join => [ 'schedulingInfo', 'build' ]
, order_by => [ 'machine' ]
{ finished => 0, 'me.busy' => 1, 'build.busy' => 1, },
{ join => [ 'build' ]
, order_by => [ 'machine', 'stepnr' ]
} ) ];
$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) {
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");
}
sub clear_queue : Chained('admin') Path('clear-queue') Args(0) {
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");
}

View File

@@ -41,28 +41,27 @@ sub view_build : Chained('build') PathPart('') Args(0) {
$c->stash->{drvAvailable} = isValidPath $build->drvpath;
$c->stash->{flashMsg} = $c->flash->{buildMsg};
my $pathHash = $c->stash->{available} ? queryPathHash($build->outpath) : "Not available";
$c->stash->{pathHash} = $pathHash;
$c->stash->{pathHash} = $c->stash->{available} ? queryPathHash($build->outpath) : undef;
if (!$build->finished && $build->schedulingInfo->busy) {
my $logfile = $build->schedulingInfo->logfile;
if (!$build->finished && $build->busy) {
my $logfile = $build->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 }, {}) ;
$c->stash->{cachedBuild} = $cachedBuildStep->build if defined $cachedBuildStep;
}
(my $lastBuildStep) = $build->buildsteps->search({},{order_by => "stepnr DESC", rows => 1});
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`;
$c->stash->{logtext} = removeAsciiEscapes($logtext);
}
if($build->finished) {
$c->stash->{prevBuilds} = [joinWithResultInfo($c, $c->model('DB::Builds'))->search(
if ($build->finished) {
$c->stash->{prevBuilds} = [$c->model('DB::Builds')->search(
{ project => $c->stash->{project}->name
, jobset => $c->stash->{build}->jobset->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(
{ eval => { -in => $build->jobsetevalmembers->get_column('eval')->as_query } }
, { join => 'jobsetevalmembers', order_by => [ 'project', 'jobset', 'job'], distinct => 1 }
);
if ($r->count <= 100) {
$c->stash->{relatedbuilds} = [$r->all];
}
my $maxRelated = 100;
my $r = $c->model('DB::Builds')->search(
{ eval => { -in => $build->jobsetevalmembers->search({}, {rows => 1})->get_column('eval')->as_query } },
{ join => 'jobsetevalmembers', order_by => [ 'project', 'jobset', 'job'], distinct => 1, rows => $maxRelated + 1 }
);
$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') {
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;
$url =~ s/tail-reload/tail/g;
$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->{contents} = (scalar `$pipestart | tail -n 50`) || " ";
$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.")
unless isValidPath($build->outpath);
$c->stash->{storePaths} = [$build->outpath];
my $pkgName = $build->nixname . "-" . $build->system;
$c->stash->{nixPkgs} = {"${pkgName}.nixpkg" => {build => $build, name => $pkgName}};
$c->stash->{channelBuilds} = $c->model('DB::Builds')->search({id => $build->id});
}
@@ -406,21 +402,16 @@ sub cancel : Chained('build') PathPart Args(0) {
txn_do($c->model('DB')->schema, sub {
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
# builds as well, but we would have to send a signal or
# something to the build process.
$build->update({finished => 1, timestamp => time});
$c->model('DB::BuildResultInfo')->create(
{ id => $build->id
, iscachedbuild => 0
, buildstatus => 4 # = cancelled
$build->update(
{ finished => 1, busy => 0, timestamp => time
, iscachedbuild => 0, buildstatus => 4 # = cancelled
});
$build->schedulingInfo->delete;
});
$c->flash->{buildMsg} = "Build has been cancelled.";
@@ -441,7 +432,7 @@ sub keep : Chained('build') PathPart Args(1) {
registerRoot $build->outpath if $newStatus == 1;
txn_do($c->model('DB')->schema, sub {
$build->resultInfo->update({keep => int $newStatus});
$build->update({keep => int $newStatus});
});
$c->flash->{buildMsg} =
@@ -541,7 +532,7 @@ sub clone_submit : Chained('build') PathPart('clone/submit') Args(0) {
my %currentBuilds;
my $newBuild = checkBuild(
$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;

View File

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

View File

@@ -65,10 +65,10 @@ sub jobsetIndex {
my @as = ();
push(@select, "job"); push(@as, "job");
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(@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(@as, $system."-build");
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");
}
$c->stash->{activeJobsStatus} =
[ $c->model('DB')->resultset('ActiveJobsForJobset')->search(
@@ -81,13 +81,9 @@ sub jobsetIndex {
}
# Last builds for jobset.
my $tmp = $c->stash->{jobset}->builds;
$c->stash->{lastBuilds} =
[ joinWithResultInfo($c, $tmp)->search({ finished => 1 },
{ order_by => "timestamp DESC", rows => 5
, '+select' => ["resultInfo.buildStatus"]
, '+as' => ["buildStatus"]
}) ];
[ $c->stash->{jobset}->builds->search({ finished => 1 },
{ order_by => "timestamp DESC", rows => 5, columns => [@buildListColumns] }) ];
}

View File

@@ -22,8 +22,8 @@ sub begin :Private {
$c->stash->{tracker} = $ENV{"HYDRA_TRACKER"} ;
if (scalar(@args) == 0 || $args[0] ne "static") {
$c->stash->{nrRunningBuilds} = $c->model('DB::BuildSchedulingInfo')->search({ busy => 1 }, {})->count();
$c->stash->{nrQueuedBuilds} = $c->model('DB::BuildSchedulingInfo')->count();
$c->stash->{nrRunningBuilds} = $c->model('DB::Builds')->search({ finished => 0, busy => 1 }, {})->count();
$c->stash->{nrQueuedBuilds} = $c->model('DB::Builds')->search({ finished => 0 })->count();
}
}
@@ -74,7 +74,7 @@ sub queue :Local {
my ($self, $c) = @_;
$c->stash->{template} = 'queue.tt';
$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};
}
@@ -86,21 +86,18 @@ sub timeline :Local {
$pit = $pit-(24*60*60)-1;
$c->stash->{template} = 'timeline.tt';
$c->stash->{builds} = [$c->model('DB::Builds')->search(
{finished => 1, stoptime => { '>' => $pit } }
, { join => 'resultInfo'
, order_by => ["starttime"]
, '+select' => [ 'resultInfo.starttime', 'resultInfo.stoptime', 'resultInfo.buildstatus' ]
, '+as' => [ 'starttime', 'stoptime', 'buildstatus' ]
})];
$c->stash->{builds} = [ $c->model('DB::Builds')->search
( { finished => 1, stoptime => { '>' => $pit } }
, { order_by => ["starttime"] }
) ];
}
sub status :Local {
my ($self, $c) = @_;
$c->stash->{steps} = [ $c->model('DB::BuildSteps')->search(
{ 'me.busy' => 1, 'schedulingInfo.busy' => 1 },
{ join => [ 'schedulingInfo', 'build' ]
{ 'me.busy' => 1, 'build.finished' => 0, 'build.busy' => 1 },
{ join => [ 'build' ]
, 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,
# but who cares?
my $primaryBuild = $c->stash->{project}->builds->find($id,
{ join => 'resultInfo',
, '+select' => ["resultInfo.releasename", "resultInfo.buildstatus"]
, '+as' => ["releasename", "buildstatus"] })
my $primaryBuild = $c->stash->{project}->builds->find($id)
or error($c, "Build $id doesn't exist.");
my $result = getViewResult($primaryBuild, $c->stash->{jobs});

View File

@@ -17,6 +17,7 @@ our @ISA = qw(Exporter);
our @EXPORT = qw(
fetchInput evalJobs checkBuild inputsToArgs captureStdoutStderr
getReleaseName getBuildLog addBuildProducts restartBuild scmPath
getPrevJobsetEval
);
@@ -244,7 +245,7 @@ sub fetchInputBuild {
(my $prevBuild) = $db->resultset('Builds')->search(
{ finished => 1, project => $projectName, jobset => $jobsetName
, 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") });
if (!defined $prevBuild || !isValidPath($prevBuild->outpath)) {
@@ -257,7 +258,7 @@ sub fetchInputBuild {
my $pkgNameRE = "(?:(?:[A-Za-z0-9]|(?:-[^0-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)$/;
return
@@ -294,7 +295,7 @@ sub fetchInputSystemBuild {
my $pkgNameRE = "(?:(?:[A-Za-z0-9]|(?:-[^0-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 $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.
sub checkBuild {
my ($db, $project, $jobset, $inputInfo, $nixExprInput, $buildInfo, $currentBuilds) = @_;
my ($db, $project, $jobset, $inputInfo, $nixExprInput, $buildInfo, $buildIds, $prevEval) = @_;
my $jobName = $buildInfo->{jobName};
my $drvPath = $buildInfo->{drvPath};
@@ -822,19 +835,36 @@ sub checkBuild {
# !!! Checking $outPath doesn't take meta-attributes into
# account. For instance, do we want a new build to be
# scheduled if the meta.maintainers field is changed?
my @previousBuilds = $job->builds->search({outPath => $outPath, isCurrent => 1});
if (scalar(@previousBuilds) > 0) {
print STDERR "already scheduled/built\n";
$currentBuilds->{$_->id} = 0 foreach @previousBuilds;
return;
if (defined $prevEval) {
my ($prevBuild) = $prevEval->builds->search({ job => $job->name, outPath => $outPath }, { rows => 1, columns => ['id'] });
if (defined $prevBuild) {
print STDERR " already scheduled/built as build ", $prevBuild->id, "\n";
$buildIds->{$prevBuild->id} = 0;
return;
}
}
my $time = time();
# 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(
{ finished => 0
, timestamp => $time
{ timestamp => $time
, description => $buildInfo->{description}
, longdescription => $buildInfo->{longDescription}
, license => $buildInfo->{license}
@@ -849,31 +879,19 @@ sub checkBuild {
, iscurrent => 1
, nixexprinput => $jobset->nixexprinput
, nixexprpath => $jobset->nixexprpath
, priority => $priority
, busy => 0
, locker => ""
, %extraFlags
});
$buildIds->{$build->id} = 1;
$currentBuilds->{$build->id} = 1;
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)
});
if ($build->iscachedbuild) {
print STDERR " marked as cached build ", $build->id, "\n";
addBuildProducts($db, $build);
} else {
print STDERR "added to queue as build ", $build->id, "\n";
$build->create_related('buildschedulinginfo',
{ priority => $priority
, busy => 0
, locker => ""
});
print STDERR " added to queue as build ", $build->id, "\n";
}
my %inputs;
@@ -906,24 +924,21 @@ sub restartBuild {
my ($db, $build) = @_;
txn_do($db, sub {
my $drvpath = $build->drvpath ;
my $outpath = $build->outpath ;
my $drvpath = $build->drvpath;
my $outpath = $build->outpath;
my $paths = "";
foreach my $bs ($build->buildsteps) {
$paths = $paths . " " . $bs->outpath;
$paths = $paths . " " . $bs->outpath;
}
my $r = `nix-store --clear-failed-paths $paths $outpath`;
$build->update({finished => 0, timestamp => time});
$build->resultInfo->delete;
$db->resultset('BuildSchedulingInfo')->create(
{ id => $build->id
, priority => 0 # don't know the original priority anymore...
$build->update(
{ finished => 0
, timestamp => time
, busy => 0
, locker => ""
});
});
});
}

View File

@@ -8,20 +8,26 @@ use Hydra::Helper::Nix;
our @ISA = qw(Exporter);
our @EXPORT = qw(
getBuild getPreviousBuild getNextBuild getPreviousSuccessfulBuild getBuildStats joinWithResultInfo getChannelData
getBuild getPreviousBuild getNextBuild getPreviousSuccessfulBuild getBuildStats getChannelData
error notFound
requireLogin requireProjectOwner requireAdmin requirePost isAdmin isProjectOwner
trim
$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 {
my ($c, $id) = @_;
my $build = $c->model('DB::Builds')->find($id);
return $build;
}
sub getPreviousBuild {
my ($c, $build) = @_;
return undef if !defined $build;
@@ -38,6 +44,7 @@ sub getPreviousBuild {
return $prevBuild;
}
sub getNextBuild {
my ($c, $build) = @_;
return undef if !defined $build;
@@ -54,11 +61,12 @@ sub getNextBuild {
return $nextBuild;
}
sub getPreviousSuccessfulBuild {
my ($c, $build) = @_;
return undef if !defined $build;
(my $prevBuild) = joinWithResultInfo($c, $c->model('DB::Builds'))->search(
(my $prevBuild) = $c->model('DB::Builds')->search(
{ finished => 1
, system => $build->system
, project => $build->project->name
@@ -71,75 +79,26 @@ sub getPreviousSuccessfulBuild {
return $prevBuild;
}
sub getBuildStats {
my ($c, $builds) = @_;
$c->stash->{finishedBuilds} = $builds->search({finished => 1}) || 0;
$c->stash->{succeededBuilds} = $builds->search(
{finished => 1, buildStatus => 0},
{join => 'resultInfo'}) || 0;
$c->stash->{succeededBuilds} = $builds->search({finished => 1, buildStatus => 0}) || 0;
$c->stash->{scheduledBuilds} = $builds->search({finished => 0}) || 0;
$c->stash->{busyBuilds} = $builds->search(
{finished => 0, busy => 1},
{join => 'schedulingInfo'}) || 0;
$c->stash->{busyBuilds} = $builds->search({finished => 0, busy => 1}) || 0;
my $res;
$res = $builds->search({},
{join => 'resultInfo', select => {sum => 'stoptime - starttime'}, as => ['sum']})
->first ;
$res = $builds->search({}, {select => {sum => 'stoptime - starttime'}, as => ['sum']})->first;
$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 {
my ($c, $msg) = @_;
$c->error($msg);
@@ -161,12 +120,14 @@ sub requireLogin {
$c->detach; # doesn't return
}
sub isProjectOwner {
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 }));
}
sub requireProjectOwner {
my ($c, $project) = @_;
@@ -183,6 +144,7 @@ sub isAdmin {
return $c->user_exists && $c->check_user_roles('admin');
}
sub requireAdmin {
my ($c) = @_;
@@ -207,12 +169,12 @@ sub trim {
# Security checking of filenames.
Readonly::Scalar our $pathCompRE => "(?:[A-Za-z0-9-\+\._][A-Za-z0-9-\+\._]*)";
Readonly::Scalar our $relPathRE => "(?:$pathCompRE(?:/$pathCompRE)*)";
Readonly::Scalar our $relNameRE => "(?:[A-Za-z0-9-][A-Za-z0-9-\.]*)";
Readonly::Scalar our $attrNameRE => "(?:[A-Za-z_][A-Za-z0-9_]*)";
Readonly::Scalar our $jobNameRE => "(?:$attrNameRE(?:\\.$attrNameRE)*)";
Readonly::Scalar our $systemRE => "(?:[a-z0-9_]+-[a-z0-9_]+)";
Readonly our $pathCompRE => "(?:[A-Za-z0-9-\+\._][A-Za-z0-9-\+\._]*)";
Readonly our $relPathRE => "(?:$pathCompRE(?:/$pathCompRE)*)";
Readonly our $relNameRE => "(?:[A-Za-z0-9-][A-Za-z0-9-\.]*)";
Readonly our $attrNameRE => "(?:[A-Za-z_][A-Za-z0-9_]*)";
Readonly our $jobNameRE => "(?:$attrNameRE(?:\\.$attrNameRE)*)";
Readonly our $systemRE => "(?:[a-z0-9_]+-[a-z0-9_]+)";
1;

View File

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

View File

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

View File

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

View File

@@ -130,6 +130,85 @@ __PACKAGE__->table("Builds");
data_type: 'text'
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
__PACKAGE__->add_columns(
@@ -173,6 +252,36 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 },
"nixexprpath",
{ 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
@@ -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
Type: has_many
@@ -350,8 +429,8 @@ __PACKAGE__->has_many(
);
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:RRtBPTdD946kA5133+c4kw
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2012-02-29 18:56:22
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:w16c86FRReLPdA8H0yTIRg
use Hydra::Helper::Nix;
@@ -369,18 +448,6 @@ __PACKAGE__->has_many(
{ "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(
"actualBuildStep",
"Hydra::Schema::BuildSteps",
@@ -408,35 +475,16 @@ sub makeSource {
sub makeQueries {
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)";
makeSource(
"JobStatus$name",
# Urgh, can't use "*" in the "select" here because of the status change join.
<<QUERY
select
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
select x.*, b.id as statusChangeId, b.timestamp as statusChangeTime
from
(select
(select max(id) from builds b
(select max(b.id) from Builds b
where
project = activeJobs.project and jobset = activeJobs.jobset
and job = activeJobs.job and system = activeJobs.system
@@ -445,7 +493,15 @@ QUERY
from $activeJobs as activeJobs
) as latest
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
);
@@ -457,12 +513,11 @@ QUERY
select *
from
(select
(select max(id) from builds b
(select max(b.id) from builds b
where
project = activeJobs.project and jobset = activeJobs.jobset
and job = activeJobs.job and system = activeJobs.system
and finished = 1
and exists (select 1 from buildresultinfo where id = b.id and buildstatus = 0)
and finished = 1 and buildstatus = 0
) as id
from $activeJobs as activeJobs
) as latest

View File

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

View File

@@ -26,31 +26,15 @@ __PACKAGE__->table("SchemaVersion");
=head2 version
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
=cut
__PACKAGE__->add_columns(
"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");
__PACKAGE__->add_columns("version", { data_type => "integer", is_nullable => 0 });
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:F/jsSRq8pxR4mWq/N4qYGw
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2012-02-29 00:47:18
# 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ use Text::Table;
use POSIX qw(strftime);
use Net::Twitter::Lite;
use Data::Dump qw(dump);
use Switch;
use feature qw/switch/;
STDOUT->autoflush();
@@ -34,9 +34,9 @@ sub sendTwitterNotification {
my $addURL = defined $config{'base_uri'};
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 $duration = ($build->resultInfo->stoptime - $build->resultInfo->starttime) . " seconds";
my $duration = ($build->stoptime - $build->starttime) . " seconds";
my $url = $config{'base_uri'}."/build/".$build->id ;
my $nt = Net::Twitter::Lite->new(
@@ -61,11 +61,11 @@ sub statusDescription {
my ($buildstatus) = @_;
my $status = "Failed";
switch ($buildstatus) {
case 0 { $status = "Success"; }
case 1 { $status = "Failed with non-zero exit code"; }
case 2 { $status = "Dependency failed"; }
case 4 { $status = "Cancelled"; }
given ($buildstatus) {
when (0) { $status = "Success"; }
when (1) { $status = "Failed with non-zero exit code"; }
when (2) { $status = "Dependency failed"; }
when (4) { $status = "Cancelled"; }
}
return $status;
@@ -74,7 +74,7 @@ sub statusDescription {
sub sendEmailNotification {
my ($build) = @_;
die unless defined $build->resultInfo;
die unless $build->finished;
return if ! ( $build->jobset->enableemail && ($build->maintainers ne "" || $build->jobset->emailoverride ne "") );
@@ -87,19 +87,18 @@ sub sendEmailNotification {
, job => $build->job->name
, system => $build->system
, finished => 1
, id => { '!=', $build->id }
}, { order_by => ["timestamp DESC"] }
, id => { '<', $build->id }
}, { order_by => ["id DESC"] }
);
# 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;
}
# if buildstatus of this build or the previous one is aborted, do
# not send email
if ($build->resultInfo->buildstatus == 3 || (defined $prevBuild && ($prevBuild->resultInfo->buildstatus == 3))) {
if ($build->buildstatus == 3 || (defined $prevBuild && ($prevBuild->buildstatus == 3))) {
return;
}
@@ -110,7 +109,7 @@ sub sendEmailNotification {
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 $sender = $config{'notification_sender'} ||
@@ -132,10 +131,10 @@ sub sendEmailNotification {
[ "Time added:", showTime $build->timestamp ],
);
push @lines, (
[ "Build started:", showTime $build->resultInfo->starttime ],
[ "Build finished:", showTime $build->resultInfo->stoptime ],
[ "Duration:", $build->resultInfo->stoptime - $build->resultInfo->starttime . "s" ],
) if $build->resultInfo->starttime;
[ "Build started:", showTime $build->starttime ],
[ "Build finished:", showTime $build->stoptime ],
[ "Duration:", $build->stoptime - $build->starttime . "s" ],
) if $build->starttime;
$infoTable->load(@lines);
my $inputsTable = Text::Table->new(
@@ -157,18 +156,18 @@ sub sendEmailNotification {
$inputsTable->load(@lines);
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";
$logtext = removeAsciiEscapes($logtext);
my $body = "Hi,\n"
. "\n"
. "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"
. "Complete build information can be found on this page: "
. "$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"
. "A summary of the build information follows:\n"
. "\n"
@@ -181,7 +180,7 @@ sub sendEmailNotification {
. $inputsTable->body
. "\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
$body =~ s/[\ ]+$//gm;
@@ -192,7 +191,6 @@ sub sendEmailNotification {
To => $to,
From => "Hydra Build Daemon <$sender>",
Subject => "Hydra job $jobName on " . $build->system . ", build " . $build->id . ": $status",
'X-Hydra-Instance' => $baseurl,
'X-Hydra-Project' => $build->project->name,
'X-Hydra-Jobset' => $build->jobset->name,
@@ -398,16 +396,16 @@ sub doBuild {
}
txn_do($db, sub {
$build->update({finished => 1, timestamp => time});
my $releaseName = getReleaseName($outPath);
if ($buildStatus == 0 && -f "$outPath/nix-support/failed") {
$buildStatus = 6;
}
$buildStatus = 6 if $buildStatus == 0 && -f "$outPath/nix-support/failed";
$db->resultset('BuildResultInfo')->create(
{ id => $build->id
$build->update(
{ finished => 1
, busy => 0
, locker => ''
, logfile => ''
, timestamp => time # !!! Why change the timestamp?
, iscachedbuild => $isCachedBuild
, buildstatus => $buildStatus
, starttime => $startTime
@@ -423,8 +421,6 @@ sub doBuild {
if ($buildStatus == 0 || $buildStatus == 6) {
addBuildProducts($db, $build);
}
$build->schedulingInfo->delete;
});
sendEmailNotification $build;
@@ -452,11 +448,11 @@ my $build;
txn_do($db, sub {
$build = $db->resultset('Builds')->find($buildId);
die "build $buildId doesn't exist" unless defined $build;
die "build $buildId already done" if defined $build->resultInfo;
if ($build->schedulingInfo->busy != 0 && $build->schedulingInfo->locker != getppid) {
die "build $buildId already done" if $build->finished;
if ($build->busy != 0 && $build->locker != getppid) {
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->buildproducts->delete_all;
});
@@ -472,6 +468,6 @@ eval {
if ($@) {
warn $@;
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);
print $email->as_string if $ENV{'HYDRA_MAIL_TEST'};
print STDERR $email->as_string if $ENV{'HYDRA_MAIL_TEST'};
sendmail($email);
}
sub permute {
my @list = @_;
for (my $n = scalar @list - 1; $n > 0; $n--) {
@@ -105,13 +106,12 @@ sub checkJobset {
my $checkoutStop = time;
# 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.
my @args = ($jobset->nixexprinput, $jobset->nixexprpath, inputsToArgs($inputInfo));
my $argsHash = sha256_hex("@args");
if (scalar($jobset->jobsetevals->search({hash => $argsHash})) > 0) {
print " already evaluated, skipping\n";
if (getPrevJobsetEval($db, $jobset, 0)->hash eq $argsHash) {
print STDERR " jobset is unchanged, skipping\n";
txn_do($db, sub {
$jobset->update({lastcheckedtime => time});
});
@@ -125,12 +125,19 @@ sub checkJobset {
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.
my %currentBuilds;
my %buildIds;
foreach my $job (permute @{$jobs->{job}}) {
next if $job->{jobName} eq "";
print "considering job " . $job->{jobName} . "\n";
checkBuild($db, $project, $jobset, $inputInfo, $nixExprInput, $job, \%currentBuilds);
print STDERR " considering job " . $project->name, ":", $jobset->name, ":", $job->{jobName} . "\n";
checkBuild($db, $project, $jobset, $inputInfo, $nixExprInput, $job, \%buildIds, $prevEval);
}
# Update the last checked times and error messages for each
@@ -140,22 +147,11 @@ sub checkJobset {
$jobset->update({lastcheckedtime => time});
foreach my $job ($jobset->jobs->all) {
if ($failedJobNames{$job->name}) {
$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};
}
$_->update({ errormsg => $failedJobNames{$_->name} ? join '\n', @{$failedJobNames{$_->name}} : undef })
foreach $jobset->jobs->all;
my $hasNewBuilds = 0;
while (my ($id, $new) = each %currentBuilds) {
while (my ($id, $new) = each %buildIds) {
$hasNewBuilds = 1 if $new;
}
@@ -168,13 +164,16 @@ sub checkJobset {
});
if ($hasNewBuilds) {
while (my ($id, $new) = each %currentBuilds) {
while (my ($id, $new) = each %buildIds) {
$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 = "";
foreach my $error (@{$jobs->{error}}) {
my $bindings = "";
@@ -197,7 +196,7 @@ sub checkJobset {
sub checkJobsetWrapped {
my ($project, $jobset) = @_;
print "considering jobset ", $jobset->name, " in ", $project->name, "\n";
print STDERR "considering jobset ", $project->name, ":", $jobset->name, "\n";
eval {
checkJobset($project, $jobset);
@@ -205,7 +204,7 @@ sub checkJobsetWrapped {
if ($@) {
my $msg = $@;
print "error evaluating jobset ", $jobset->name, ": $msg";
print STDERR "error evaluating jobset ", $jobset->name, ": $msg";
txn_do($db, sub {
$jobset->update({lastcheckedtime => time});
setJobsetError($jobset, $msg);
@@ -216,7 +215,7 @@ sub checkJobsetWrapped {
sub checkProjects {
foreach my $project ($db->resultset('Projects')->search({enabled => 1})) {
print "considering project ", $project->name, "\n";
print STDERR "considering project ", $project->name, "\n";
checkJobsetWrapped($project, $_)
foreach $project->jobsets->search({enabled => 1});
}
@@ -237,7 +236,7 @@ while (1) {
eval {
checkProjects;
};
if ($@) { print "$@"; }
print "sleeping...\n";
if ($@) { print STDERR "$@"; }
print STDERR "sleeping...\n";
sleep 30;
}

View File

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

View File

@@ -13,32 +13,64 @@ my $db = openHydraDB;
my %roots;
sub registerRoot {
sub addRoot {
my ($path) = @_;
Hydra::Helper::Nix::registerRoot($path);
registerRoot($path);
$roots{$path} = 1;
}
my @columns = ( "id", "project", "jobset", "job", "system", "finished", "outpath", "drvpath", "timestamp" );
sub keepBuild {
my ($build) = @_;
print STDERR " keeping build ", $build->id, " (",
$build->system, "; ",
print STDERR " keeping ", ($build->finished ? "" : "scheduled "), "build ", $build->id, " (",
$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";
if (isValidPath($build->outpath)) {
registerRoot $build->outpath;
addRoot $build->outpath;
} 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.
foreach my $jobset ($project->jobsets->all) {
foreach my $jobset ($project->jobsets->search({}, { order_by => ["name" ]})) {
my $keepnr = $jobset->keepnr;
# If the jobset has been disabled for more than one week, than
@@ -53,29 +85,19 @@ foreach my $project ($db->resultset('Projects')->all) {
next;
}
# Go over all jobs in this jobset.
foreach my $job ($jobset->jobs->all) {
print STDERR "*** looking for builds to keep in job ",
$project->name, ":", $job->jobset->name, ":", $job->name, "\n";
print STDERR "*** looking for the $keepnr most recent successful builds of each job in jobset ",
$project->name, ":", $jobset->name, "\n";
# Keep the N most recent successful builds for each job
# and platform.
# !!! Take time into account? E.g. don't delete builds
# that are younger than N days.
my @systems = $job->builds->search({ }, { select => ["system"], distinct => 1 })->all;
foreach my $system (@systems) {
my @recentBuilds = $job->builds->search(
{ finished => 1
, buildStatus => 0 # == success
, system => $system->system
},
{ join => 'resultInfo'
, order_by => 'me.id DESC'
, rows => $keepnr
});
keepBuild $_ foreach @recentBuilds;
}
}
keepBuild $_ foreach $jobset->builds->search(
{ 'me.id' => { 'in' => \
[ "select b2.id from Builds b2 join " .
" (select distinct job, system, coalesce( " .
" (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)" .
" , 0) nth from builds b where project = ? and jobset = ? and isCurrent = 1) x " .
" 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"
, [ '', $keepnr - 1 ], [ '', $project->name ], [ '', $jobset->name ], [ '', $project->name ], [ '', $jobset->name ] ] }
},
{ order_by => ["job", "system", "id"], columns => [ @columns ] });
}
# 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.
my $latest = getLatestSuccessfulViewResult($project, $primaryJob, $jobs);
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);
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.
print STDERR "*** looking for scheduled builds\n";
foreach my $build ($db->resultset('Builds')->search({finished => 0}, {join => 'schedulingInfo'})) {
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";
}
}
keepBuild $_ foreach $db->resultset('Builds')->search({ finished => 0 }, { columns => [ @columns ] });
# Remove existing roots that are no longer wanted. !!! racy
# Remove existing roots that are no longer wanted.
print STDERR "*** removing unneeded GC roots\n";
my $gcRootsDir = getGCRootsDir;
my $rootsKept = 0;
my $rootsDeleted = 0;
opendir DIR, $gcRootsDir or die;
foreach my $link (readdir DIR) {
next if !-l "$gcRootsDir/$link";
my $path = readlink "$gcRootsDir/$link" or die;
foreach my $link (@roots) {
next if $link eq "." || $link eq "..";
my $path = "/nix/store/$link";
if (!defined $roots{$path}) {
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 (
#ifdef POSTGRESQL
id serial primary key not null,
@@ -156,37 +151,22 @@ create table Builds (
-- build.
nixExprInput 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
);
-- Info for a scheduled build.
create table BuildSchedulingInfo (
id integer primary key not null,
-- Information about scheduled builds.
priority integer not null default 0,
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
disabled integer not null default 0,
disabled integer not null default 0, -- !!! boolean
startTime integer, -- if busy, time we started
foreign key (id) references Builds(id) on delete cascade
);
stopTime integer,
-- Info for a finished build.
create table BuildResultInfo (
id integer primary key not null,
isCachedBuild integer not null, -- boolean
-- Information about finished builds.
isCachedBuild integer, -- boolean
-- Status codes:
-- 0 = succeeded
@@ -199,23 +179,17 @@ create table BuildResultInfo (
errorMsg text, -- error message in case of a Nix failure
startTime integer, -- in Unix time, 0 = used cached build result
stopTime integer,
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,
logSize bigint,
size bigint,
closureSize bigint,
releaseName text, -- e.g. "patchelf-0.5pre1234"
keep integer not null default 0, -- true means never garbage-collect the build output
failedDepBuild integer, -- obsolete
failedDepStepNr integer, -- obsolete
foreign key (id) references Builds(id) on delete cascade
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
);
@@ -519,16 +493,18 @@ create table BuildMachineSystemTypes (
-- Some indices.
create index IndexBuildInputsOnBuild on BuildInputs(build);
create index IndexBuildInputsOnDependency on BuildInputs(dependency);
create index IndexBuildProducstOnBuildAndType on BuildProducts(build, type);
create index IndexBuildProductsOnBuild on BuildProducts(build);
create index IndexBuildSchedulingInfoOnBuild on BuildSchedulingInfo(id); -- idem
create index IndexBuildStepsOnBuild on BuildSteps(build);
create index IndexBuildStepsOnBusy on BuildSteps(busy);
create index IndexBuildStepsOnDrvpathTypeBusyStatus on BuildSteps(drvpath, type, busy, status);
create index IndexBuildStepsOnOutpath on BuildSteps(outpath);
create index IndexBuildStepsOnOutpathBuild on BuildSteps (outpath, build);
create index IndexBuildsOnFinished on Builds(finished);
create index IndexBuildsOnFinishedBusy on Builds(finished, busy);
create index IndexBuildsOnIsCurrent on Builds(isCurrent);
create index IndexBuildsOnJobsetIsCurrent on Builds(project, jobset, 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 IndexProjectsOnEnabled on Projects(enabled);
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);