hydra-evaluator: deal in jobset IDs

This commit is contained in:
Graham Christensen 2021-01-25 16:08:33 -05:00
parent cb01859718
commit 54341cd9f6
No known key found for this signature in database
GPG key ID: FE918C3A98C1030F

View file

@ -13,7 +13,57 @@
using namespace nix; using namespace nix;
typedef std::pair<std::string, std::string> JobsetIdentity; typedef std::pair<std::string, std::string> JobsetSymbolicIdentity;
class JobsetIdentity {
public:
std::string project;
std::string jobset;
int id;
JobsetIdentity(const std::string& project, const std::string& jobset, const int id)
: project{ project }, jobset{ jobset }, id{ id }
{
}
friend bool operator== (const JobsetIdentity &lhs, const JobsetIdentity &rhs);
friend bool operator!= (const JobsetIdentity &lhs, const JobsetIdentity &rhs);
friend bool operator< (const JobsetIdentity &lhs, const JobsetIdentity &rhs);
friend bool operator== (const JobsetIdentity &lhs, const JobsetSymbolicIdentity &rhs);
friend bool operator!= (const JobsetIdentity &lhs, const JobsetSymbolicIdentity &rhs);
std::string display() const {
return str(format("%1%:%2% (jobset#%3%)") % project % jobset % id);
}
};
bool operator==(const JobsetIdentity & lhs, const JobsetIdentity & rhs)
{
return lhs.id == rhs.id;
}
bool operator!=(const JobsetIdentity & lhs, const JobsetIdentity & rhs)
{
return lhs.id != rhs.id;
}
bool operator<(const JobsetIdentity & lhs, const JobsetIdentity & rhs)
{
return lhs.id < rhs.id;
}
bool operator==(const JobsetIdentity & lhs, const JobsetSymbolicIdentity & rhs)
{
return lhs.project == rhs.first && lhs.jobset == rhs.second;
}
bool operator!=(const JobsetIdentity & lhs, const JobsetSymbolicIdentity & rhs)
{
return ! (lhs == rhs);
}
enum class EvaluationStyle enum class EvaluationStyle
{ {
@ -39,7 +89,7 @@ struct Evaluator
typedef std::map<JobsetIdentity, Jobset> Jobsets; typedef std::map<JobsetIdentity, Jobset> Jobsets;
std::optional<JobsetIdentity> evalOne; std::optional<JobsetSymbolicIdentity> evalOne;
const size_t maxEvals; const size_t maxEvals;
@ -68,7 +118,7 @@ struct Evaluator
pqxx::work txn(*conn); pqxx::work txn(*conn);
auto res = txn.exec auto res = txn.exec
("select project, j.name, lastCheckedTime, triggerTime, checkInterval, j.enabled as jobset_enabled " ("select j.id as id, project, j.name, lastCheckedTime, triggerTime, checkInterval, j.enabled as jobset_enabled "
"from Jobsets j " "from Jobsets j "
"join Projects p on j.project = p.name " "join Projects p on j.project = p.name "
"where j.enabled != 0 and p.enabled != 0"); "where j.enabled != 0 and p.enabled != 0");
@ -79,7 +129,7 @@ struct Evaluator
std::set<JobsetIdentity> seen; std::set<JobsetIdentity> seen;
for (auto const & row : res) { for (auto const & row : res) {
auto name = JobsetIdentity{row["project"].as<std::string>(), row["name"].as<std::string>()}; auto name = JobsetIdentity{row["project"].as<std::string>(), row["name"].as<std::string>(), row["id"].as<int>()};
if (evalOne && name != *evalOne) continue; if (evalOne && name != *evalOne) continue;
@ -113,7 +163,7 @@ struct Evaluator
if (seen.count(i->first)) if (seen.count(i->first))
++i; ++i;
else { else {
printInfo("forgetting jobset %s:%s", i->first.first, i->first.second); printInfo("forgetting jobset %s", i->first.display());
i = state->jobsets.erase(i); i = state->jobsets.erase(i);
} }
} }
@ -122,25 +172,24 @@ struct Evaluator
{ {
time_t now = time(0); time_t now = time(0);
printInfo("starting evaluation of jobset %s:%s (last checked %d s ago)", printInfo("starting evaluation of jobset %s (last checked %d s ago)",
jobset.name.first, jobset.name.second, jobset.name.display(),
now - jobset.lastCheckedTime); now - jobset.lastCheckedTime);
{ {
auto conn(dbPool.get()); auto conn(dbPool.get());
pqxx::work txn(*conn); pqxx::work txn(*conn);
txn.exec_params0 txn.exec_params0
("update Jobsets set startTime = $1 where project = $2 and name = $3", ("update Jobsets set startTime = $1 where id = $2",
now, now,
jobset.name.first, jobset.name.id);
jobset.name.second);
txn.commit(); txn.commit();
} }
assert(jobset.pid == -1); assert(jobset.pid == -1);
jobset.pid = startProcess([&]() { jobset.pid = startProcess([&]() {
Strings args = { "hydra-eval-jobset", jobset.name.first, jobset.name.second }; Strings args = { "hydra-eval-jobset", jobset.name.project, jobset.name.jobset };
execvp(args.front().c_str(), stringsToCharPtrs(args).data()); execvp(args.front().c_str(), stringsToCharPtrs(args).data());
throw SysError("executing %1%", args.front()); throw SysError("executing %1%", args.front());
}); });
@ -154,23 +203,23 @@ struct Evaluator
{ {
if (jobset.pid != -1) { if (jobset.pid != -1) {
// Already running. // Already running.
debug("shouldEvaluate %s:%s? no: already running", debug("shouldEvaluate %s? no: already running",
jobset.name.first, jobset.name.second); jobset.name.display());
return false; return false;
} }
if (jobset.triggerTime != std::numeric_limits<time_t>::max()) { if (jobset.triggerTime != std::numeric_limits<time_t>::max()) {
// An evaluation of this Jobset is requested // An evaluation of this Jobset is requested
debug("shouldEvaluate %s:%s? yes: requested", debug("shouldEvaluate %s? yes: requested",
jobset.name.first, jobset.name.second); jobset.name.display());
return true; return true;
} }
if (jobset.checkInterval <= 0) { if (jobset.checkInterval <= 0) {
// Automatic scheduling is disabled. We allow requested // Automatic scheduling is disabled. We allow requested
// evaluations, but never schedule start one. // evaluations, but never schedule start one.
debug("shouldEvaluate %s:%s? no: checkInterval <= 0", debug("shouldEvaluate %s? no: checkInterval <= 0",
jobset.name.first, jobset.name.second); jobset.name.display());
return false; return false;
} }
@ -186,16 +235,15 @@ struct Evaluator
if (jobset.evaluation_style == EvaluationStyle::ONE_AT_A_TIME) { if (jobset.evaluation_style == EvaluationStyle::ONE_AT_A_TIME) {
auto evaluation_res = txn.parameterized auto evaluation_res = txn.parameterized
("select id from JobsetEvals " ("select id from JobsetEvals "
"where project = $1 and jobset = $2 " "where jobset_id = $1 "
"order by id desc limit 1") "order by id desc limit 1")
(jobset.name.first) (jobset.name.id)
(jobset.name.second)
.exec(); .exec();
if (evaluation_res.empty()) { if (evaluation_res.empty()) {
// First evaluation, so allow scheduling. // First evaluation, so allow scheduling.
debug("shouldEvaluate(one-at-a-time) %s:%s? yes: no prior eval", debug("shouldEvaluate(one-at-a-time) %s? yes: no prior eval",
jobset.name.first, jobset.name.second); jobset.name.display());
return true; return true;
} }
@ -214,20 +262,20 @@ struct Evaluator
// If the previous evaluation has no unfinished builds // If the previous evaluation has no unfinished builds
// schedule! // schedule!
if (unfinished_build_res.empty()) { if (unfinished_build_res.empty()) {
debug("shouldEvaluate(one-at-a-time) %s:%s? yes: no unfinished builds", debug("shouldEvaluate(one-at-a-time) %s? yes: no unfinished builds",
jobset.name.first, jobset.name.second); jobset.name.display());
return true; return true;
} else { } else {
debug("shouldEvaluate(one-at-a-time) %s:%s? no: at least one unfinished build", debug("shouldEvaluate(one-at-a-time) %s:%s? no: at least one unfinished build",
jobset.name.first, jobset.name.second); jobset.name.display());
return false; return false;
} }
} else { } else {
// EvaluationStyle::ONESHOT, EvaluationStyle::SCHEDULED // EvaluationStyle::ONESHOT, EvaluationStyle::SCHEDULED
debug("shouldEvaluate(oneshot/scheduled) %s:%s? yes: checkInterval elapsed", debug("shouldEvaluate(oneshot/scheduled) %s? yes: checkInterval elapsed",
jobset.name.first, jobset.name.second); jobset.name.display());
return true; return true;
} }
} }
@ -352,8 +400,8 @@ struct Evaluator
auto & jobset(i.second); auto & jobset(i.second);
if (jobset.pid == pid) { if (jobset.pid == pid) {
printInfo("evaluation of jobset %s:%s %s", printInfo("evaluation of jobset %s %s",
jobset.name.first, jobset.name.second, statusToString(status)); jobset.name.display(), statusToString(status));
auto now = time(0); auto now = time(0);
@ -369,23 +417,20 @@ struct Evaluator
jobset from getting stuck in an endless jobset from getting stuck in an endless
failing eval loop. */ failing eval loop. */
txn.exec_params0 txn.exec_params0
("update Jobsets set triggerTime = null where project = $1 and name = $2 and startTime is not null and triggerTime <= startTime", ("update Jobsets set triggerTime = null where id = $1 and startTime is not null and triggerTime <= startTime",
jobset.name.first, jobset.name.id);
jobset.name.second);
/* Clear the start time. */ /* Clear the start time. */
txn.exec_params0 txn.exec_params0
("update Jobsets set startTime = null where project = $1 and name = $2", ("update Jobsets set startTime = null where id = $1",
jobset.name.first, jobset.name.id);
jobset.name.second);
if (!WIFEXITED(status) || WEXITSTATUS(status) > 1) { if (!WIFEXITED(status) || WEXITSTATUS(status) > 1) {
txn.exec_params0 txn.exec_params0
("update Jobsets set errorMsg = $1, lastCheckedTime = $2, errorTime = $2, fetchErrorMsg = null where project = $3 and name = $4", ("update Jobsets set errorMsg = $1, lastCheckedTime = $2, errorTime = $2, fetchErrorMsg = null where id = $3",
fmt("evaluation %s", statusToString(status)), fmt("evaluation %s", statusToString(status)),
now, now,
jobset.name.first, jobset.name.id);
jobset.name.second);
} }
txn.commit(); txn.commit();
@ -466,7 +511,7 @@ int main(int argc, char * * argv)
else { else {
if (!args.empty()) { if (!args.empty()) {
if (args.size() != 2) throw UsageError("Syntax: hydra-evaluator [<project> <jobset>]"); if (args.size() != 2) throw UsageError("Syntax: hydra-evaluator [<project> <jobset>]");
evaluator.evalOne = JobsetIdentity(args[0], args[1]); evaluator.evalOne = JobsetSymbolicIdentity(args[0], args[1]);
} }
evaluator.run(); evaluator.run();
} }