diff --git a/src/hydra-queue-runner/build-remote.cc b/src/hydra-queue-runner/build-remote.cc index a0408f55..cf4de781 100644 --- a/src/hydra-queue-runner/build-remote.cc +++ b/src/hydra-queue-runner/build-remote.cc @@ -116,7 +116,7 @@ static void copyClosureTo(ref destStore, void State::buildRemote(ref destStore, Machine::ptr machine, Step::ptr step, - unsigned int maxSilentTime, unsigned int buildTimeout, + unsigned int maxSilentTime, unsigned int buildTimeout, unsigned int repeats, RemoteResult & result, std::shared_ptr activeStep) { assert(BuildResult::TimedOut == 8); @@ -263,9 +263,10 @@ void State::buildRemote(ref destStore, to << maxSilentTime << buildTimeout; if (GET_PROTOCOL_MINOR(remoteVersion) >= 2) to << 64 * 1024 * 1024; // == maxLogSize - if (GET_PROTOCOL_MINOR(remoteVersion) >= 3) - // FIXME: make the number of repeats configurable. - to << (step->isDeterministic ? 1 : 0); + if (GET_PROTOCOL_MINOR(remoteVersion) >= 3) { + to << repeats // == build-repeat + << step->isDeterministic; // == enforce-determinism + } to.flush(); result.startTime = time(0); @@ -295,6 +296,10 @@ void State::buildRemote(ref destStore, result.stepStatus = bsSuccess; } else { result.errorMsg = readString(from); + if (GET_PROTOCOL_MINOR(remoteVersion) >= 3) { + result.timesBuilt = readInt(from); + result.isNonDeterministic = readInt(from); + } switch ((BuildResult::Status) res) { case BuildResult::Built: result.stepStatus = bsSuccess; diff --git a/src/hydra-queue-runner/builder.cc b/src/hydra-queue-runner/builder.cc index 4e8f6cf3..667a5192 100644 --- a/src/hydra-queue-runner/builder.cc +++ b/src/hydra-queue-runner/builder.cc @@ -86,6 +86,7 @@ State::StepResult State::doBuildStep(nix::ref destStore, BuildID buildId; Path buildDrvPath; unsigned int maxSilentTime, buildTimeout; + unsigned int repeats = step->isDeterministic ? 1 : 0; { std::set dependents; @@ -113,6 +114,11 @@ State::StepResult State::doBuildStep(nix::ref destStore, build = build2; enqueueNotificationItem({NotificationItem::Type::BuildStarted, build->id}); } + { + auto i = jobsetRepeats.find(std::make_pair(build2->projectName, build2->jobsetName)); + if (i != jobsetRepeats.end()) + repeats = std::max(repeats, i->second); + } } if (!build) build = *dependents.begin(); @@ -121,8 +127,8 @@ State::StepResult State::doBuildStep(nix::ref destStore, maxSilentTime = build->maxSilentTime; buildTimeout = build->buildTimeout; - printMsg(lvlInfo, format("performing step ‘%1%’ on ‘%2%’ (needed by build %3% and %4% others)") - % step->drvPath % machine->sshName % buildId % (dependents.size() - 1)); + printInfo("performing step ‘%s’ %d times on ‘%s’ (needed by build %d and %d others)", + step->drvPath, repeats + 1, machine->sshName, buildId, (dependents.size() - 1)); } bool quit = buildId == buildOne && step->drvPath == buildDrvPath; @@ -162,7 +168,7 @@ State::StepResult State::doBuildStep(nix::ref destStore, /* Do the build. */ try { /* FIXME: referring builds may have conflicting timeouts. */ - buildRemote(destStore, machine, step, maxSilentTime, buildTimeout, result, activeStep); + buildRemote(destStore, machine, step, maxSilentTime, buildTimeout, repeats, result, activeStep); } catch (NoTokens & e) { result.stepStatus = bsNarSizeLimitExceeded; } catch (Error & e) { @@ -224,8 +230,7 @@ State::StepResult State::doBuildStep(nix::ref destStore, auto mc = startDbUpdate(); { pqxx::work txn(*conn); - finishBuildStep(txn, result.startTime, result.stopTime, result.overhead, buildId, - stepNr, machine->sshName, result.stepStatus, result.errorMsg); + finishBuildStep(txn, result, buildId, stepNr, machine->sshName); txn.commit(); } stepFinished = true; @@ -279,8 +284,7 @@ State::StepResult State::doBuildStep(nix::ref destStore, pqxx::work txn(*conn); - finishBuildStep(txn, result.startTime, result.stopTime, result.overhead, - buildId, stepNr, machine->sshName, bsSuccess); + finishBuildStep(txn, result, buildId, stepNr, machine->sshName); for (auto & b : direct) { printMsg(lvlInfo, format("marking build %1% as succeeded") % b->id); @@ -386,8 +390,7 @@ State::StepResult State::doBuildStep(nix::ref destStore, if (result.stepStatus != bsCachedFailure && !stepFinished) { assert(stepNr); - finishBuildStep(txn, result.startTime, result.stopTime, result.overhead, - buildId, stepNr, machine->sshName, result.stepStatus, result.errorMsg); + finishBuildStep(txn, result, buildId, stepNr, machine->sshName); } /* Mark all builds that depend on this derivation as failed. */ diff --git a/src/hydra-queue-runner/hydra-queue-runner.cc b/src/hydra-queue-runner/hydra-queue-runner.cc index 8099292d..a531714a 100644 --- a/src/hydra-queue-runner/hydra-queue-runner.cc +++ b/src/hydra-queue-runner/hydra-queue-runner.cc @@ -264,20 +264,21 @@ unsigned int State::createBuildStep(pqxx::work & txn, time_t startTime, BuildID } -void State::finishBuildStep(pqxx::work & txn, time_t startTime, time_t stopTime, unsigned int overhead, - BuildID buildId, unsigned int stepNr, const std::string & machine, BuildStatus status, - const std::string & errorMsg, BuildID propagatedFrom) +void State::finishBuildStep(pqxx::work & txn, const RemoteResult & result, + BuildID buildId, unsigned int stepNr, const std::string & machine) { - assert(startTime); - assert(stopTime); + assert(result.startTime); + assert(result.stopTime); txn.parameterized - ("update BuildSteps set busy = 0, status = $1, propagatedFrom = $4, errorMsg = $5, startTime = $6, stopTime = $7, machine = $8, overhead = $9 where build = $2 and stepnr = $3") - ((int) status)(buildId)(stepNr) - (propagatedFrom, propagatedFrom != 0) - (errorMsg, errorMsg != "") - (startTime)(stopTime) + ("update BuildSteps set busy = 0, status = $1, errorMsg = $4, startTime = $5, stopTime = $6, machine = $7, overhead = $8, timesBuilt = $9, isNonDeterministic = $10 where build = $2 and stepnr = $3") + ((int) result.stepStatus)(buildId)(stepNr) + (result.errorMsg, result.errorMsg != "") + (result.startTime)(result.stopTime) (machine, machine != "") - (overhead, overhead != 0).exec(); + (result.overhead, result.overhead != 0) + (result.timesBuilt, result.timesBuilt > 0) + (result.isNonDeterministic, result.timesBuilt > 1) + .exec(); } @@ -809,6 +810,13 @@ void State::run(BuildID buildOne) useSubstitutes = isTrue(hydraConfig["use-substitutes"]); + // FIXME: hacky mechanism for configuring determinism checks. + for (auto & s : tokenizeString(hydraConfig["xxx-jobset-repeats"])) { + auto s2 = tokenizeString>(s, ":"); + if (s2.size() != 3) throw Error("bad value in xxx-jobset-repeats"); + jobsetRepeats.emplace(std::make_pair(s2[0], s2[1]), std::stoi(s2[2])); + } + { auto conn(dbPool.get()); clearBusy(*conn, 0); diff --git a/src/hydra-queue-runner/state.hh b/src/hydra-queue-runner/state.hh index 2994b9fb..4c23471b 100644 --- a/src/hydra-queue-runner/state.hh +++ b/src/hydra-queue-runner/state.hh @@ -48,6 +48,9 @@ struct RemoteResult bool canCache = false; // for bsFailed std::string errorMsg; // for bsAborted + unsigned int timesBuilt = 0; + bool isNonDeterministic = false; + time_t startTime = 0, stopTime = 0; unsigned int overhead = 0; nix::Path logFile; @@ -414,6 +417,10 @@ private: from showing up as busy until the queue runner is restarted. */ nix::Sync>> orphanedSteps; + /* How often the build steps of a jobset should be repeated in + order to detect non-determinism. */ + std::map, unsigned int> jobsetRepeats; + public: State(); @@ -437,10 +444,8 @@ private: const std::string & machine, BuildStatus status, const std::string & errorMsg = "", BuildID propagatedFrom = 0); - void finishBuildStep(pqxx::work & txn, time_t startTime, time_t stopTime, - unsigned int overhead, BuildID buildId, unsigned int stepNr, - const std::string & machine, BuildStatus status, const std::string & errorMsg = "", - BuildID propagatedFrom = 0); + void finishBuildStep(pqxx::work & txn, const RemoteResult & result, BuildID buildId, unsigned int stepNr, + const std::string & machine); int createSubstitutionStep(pqxx::work & txn, time_t startTime, time_t stopTime, Build::ptr build, const nix::Path & drvPath, const std::string & outputName, const nix::Path & storePath); @@ -492,6 +497,7 @@ private: void buildRemote(nix::ref destStore, Machine::ptr machine, Step::ptr step, unsigned int maxSilentTime, unsigned int buildTimeout, + unsigned int repeats, RemoteResult & result, std::shared_ptr activeStep); void markSucceededBuild(pqxx::work & txn, Build::ptr build, diff --git a/src/lib/Hydra/Schema/BuildSteps.pm b/src/lib/Hydra/Schema/BuildSteps.pm index aeb04a8f..bf572fcf 100644 --- a/src/lib/Hydra/Schema/BuildSteps.pm +++ b/src/lib/Hydra/Schema/BuildSteps.pm @@ -103,6 +103,16 @@ __PACKAGE__->table("BuildSteps"); data_type: 'integer' is_nullable: 1 +=head2 timesbuilt + + data_type: 'integer' + is_nullable: 1 + +=head2 isnondeterministic + + data_type: 'boolean' + is_nullable: 1 + =cut __PACKAGE__->add_columns( @@ -132,6 +142,10 @@ __PACKAGE__->add_columns( { data_type => "integer", is_foreign_key => 1, is_nullable => 1 }, "overhead", { data_type => "integer", is_nullable => 1 }, + "timesbuilt", + { data_type => "integer", is_nullable => 1 }, + "isnondeterministic", + { data_type => "boolean", is_nullable => 1 }, ); =head1 PRIMARY KEY @@ -201,8 +215,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07043 @ 2016-02-16 18:04:52 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:TRALbEoaF/OIOyERYCyxkw +# Created by DBIx::Class::Schema::Loader v0.07045 @ 2016-12-07 13:48:19 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3FYkqSUfgWmiqZzmX8J4TA my %hint = ( columns => [ diff --git a/src/root/build.tt b/src/root/build.tt index e78fc883..f9cffc0f 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -50,13 +50,20 @@ FOR step IN steps; IF step.busy; busy = 1; END; END; END %] [% IF step.busy == 1 || ((step.machine || step.starttime) && (step.status == 0 || step.status == 1 || step.status == 3 || step.status == 4 || step.status == 7)); INCLUDE renderMachineName machine=step.machine; ELSE; "n/a"; END %] - + [% IF step.busy == 1 %] Building [% ELSIF step.status == 0 %] - Succeeded + [% IF step.isnondeterministic %] + Succeeded with non-determistic result + [% ELSE %] + Succeeded + [% END %] + [% IF step.timesbuilt > 1 %] + ([% step.timesbuilt %] times) + [% END %] [% ELSIF step.status == 3 %] - Aborted[% IF step.errormsg %]: [% HTML.escape(step.errormsg); END %] + Aborted[% IF step.errormsg %]: [% HTML.escape(step.errormsg) %][% END %] [% ELSIF step.status == 4 %] Cancelled [% ELSIF step.status == 7 %] @@ -70,9 +77,9 @@ FOR step IN steps; IF step.busy; busy = 1; END; END; [% ELSIF step.status == 11 %] Output limit exceeded [% ELSIF step.status == 12 %] - Non-deterministic build + Non-determinism detected [% IF step.timesbuilt %] after [% step.timesbuilt %] times[% END %] [% ELSIF step.errormsg %] - Failed: [% HTML.escape(step.errormsg) %] + Failed: [% HTML.escape(step.errormsg) %] [% ELSE %] Failed [% END %] @@ -137,7 +144,7 @@ FOR step IN steps; IF step.busy; busy = 1; END; END;
- [% INCLUDE renderBuildStatusIcon size=128, build=build %] + [% INCLUDE renderBuildStatusIcon size=128 build=build %] diff --git a/src/root/static/css/hydra.css b/src/root/static/css/hydra.css index b8d9d7b4..f4cf1d8b 100644 --- a/src/root/static/css/hydra.css +++ b/src/root/static/css/hydra.css @@ -132,3 +132,13 @@ div.flot-tooltip { opacity: 0.80; z-index: 100; } + +td.step-status span.error { + color: red; + font-weight: bold; +} + +td.step-status span.warn { + color: #aaaa00; + font-weight: bold; +} diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index fc20e069..e8f958b1 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -289,6 +289,12 @@ create table BuildSteps ( -- Time in milliseconds spend copying stuff from/to build machines. overhead integer, + -- How many times this build step was done (for checking determinism). + timesBuilt integer, + + -- Whether this build step produced different results when repeated. + isNonDeterministic boolean, + primary key (build, stepnr), foreign key (build) references Builds(id) on delete cascade, foreign key (propagatedFrom) references Builds(id) on delete cascade diff --git a/src/sql/upgrade-52.sql b/src/sql/upgrade-52.sql new file mode 100644 index 00000000..ac376df6 --- /dev/null +++ b/src/sql/upgrade-52.sql @@ -0,0 +1,3 @@ +alter table BuildSteps + add column timesBuilt integer, + add column isNonDeterministic boolean;