Commit 6fc69131 authored by Daniel Smith's avatar Daniel Smith

First pass refactor and file splitting of NodeJS server

This commit breaks out and rewrites many of the main components
of the scheduler and webserver into modules which share
a global instance of variables and event emitters.
parent 10e5d060
......@@ -9,67 +9,80 @@ This framework provides a web interface for three types of benchmark runs:
### How does it work?
The QMLBench Regression Finder framework consists of a two parts:
1. A nodejs server that operates a lightweight web interface, a job scheduler, and email notification subsystyem
1. A nodejs server that operates a lightweight web interface, a job scheduler, and email notification subsystem
2. A python script that builds, tests, and collects results from QMLBench
The framework is platform independent and can be deployed to as many clients as desired. A standalone mode is also available for local benchmarking.
### Installation pre-requisites
#
## Installation
### Pre-requisites
1. [All the usual stuff to build Qt from source](https://wiki.qt.io/Building_Qt_5_from_Git)
2. [NodeJS](https://nodejs.org/en/) - Built on 10.15.3, but any recent version will likely work
2. [NodeJS & npm](https://nodejs.org/en/) - Built on NodeJS 10.15.3, but any recent version will likely work
3. [Python 3](https://www.python.org/downloads/)
- Python3 submodules (installable with pip / pip3):
- Python3 submodules (installable with pip / pip3):
- argparse
- requests
- thriftpy
- packaging
4. (Windows) [MSVC Build Tools 2017](https://aka.ms/AA363al) - Other versions may work, but VS2017 is recommended for building Qt. Build tools are already included in full installations of Microsoft Visual Studio 2017.
4. (Windows) [MSVC Build Tools 2019](https://visualstudio.microsoft.com/downloads/) - Other versions may work, but VS2019 is recommended for building Qt. Build tools are already included in full installations of Microsoft Visual Studio 2019.
### How to install
1. Download or clone this repository
2. In the repository directory, run the following:
1. npm install
- __Note__: Python2 may be required for the "diskspace" module. If you encouter errors regarding this module during the "npm install" step, ensure that your Python2 installation directory is added to the __beginning__ of your "PATH" environment variable for this step.
3. Celebrate! Installation is complete
### How to run
### Configure your installation
- Modify `config.json` as necessary, or set environment variables to override the config file.
1. `BASE_URL` - This url refers to the URL or IP where the scheduler can be reached. `Default: localhost`
2. `HTTP_PORT` - The port on which to run the server. All instances of this system on a network must be configured to use the same port. `Default: 8080`
3. `SMTP_SERVER` - [Optional] A SMTP server that can send emails. Authentication not supported - The mail server must accept anonymous connections. `Default: smtp.intra.qt.io`
4. `SMTP_PORT` [Optional] - Specify the port to connect to the mail server. `Default: 25`
5. `VS_BUILD_TOOLS` [Windows build hosts only] - The full path to VsDevCmd.bat provided by Microsoft Visual Studio `Default: "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/Common7/Tools/VsDevCmd.bat"`
6. `agents` [Optional: [see "How to Run"](./README.md#How_to_Run)] - A list of IP strings which have the agent deployed as a testrunner. It is unnecessary to add localhost to this list.
#
## Running
1. In the repository directory, run one of the following commands:
1. ```npm start standalone```
1. `npm start standalone`
- Start the scheduler in standalone mode. This mode allows for jobs to be run on the local machine, and does not require additional servers to act as hosts.
2. ```npm start 10.9.70.10 10.9.70.11 10.9.70.12```
- Start the server in scheduler mode with local jobs enabled. This will allow scheduling jobs on both the local machine and remote hosts. A space separated list of remote hosts is required.
3. ```npm start nolocal 10.9.70.10 10.9.70.11 10.9.70.12```
- *__(Recommended configuration)__* Start the server in scheduler only mode. This will prevent the local host from being available as a benchmarking host. A space separated list of remote hosts is required.
4. ```npm start```
- Start the server in remote host mode. This disables the scheduler interface and configures the server to listen for connections from a scheduling server. This is the mode that must be used on remote hosts.
### Optional parameters
Optional parameters are set via environment variables. The following options are available.
1. ```HTTP_PORT``` - The port on which to run the server. All instances of this system on a network must be configured to use the same port. ```Default: 8080```
2. ```BUILD_CORES``` - The number of CPU threads to use when building Qt ```Default: [All available cores]```
3. ```VS_DEV_ENV``` - The full path to VsDevCmd.bat provided by Microsoft Visual Studio ```Default: "C:/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/Common7/Tools/VsDevCmd.bat"```
4. ```SMTP_SERVER``` - A SMTP server that can send emails. Authentication not supported - The mail server must accept anonymous connections. ```Default: smtp.intra.qt.io```
5. ```SMTP_PORT``` - Specify the port to connect to the mail server. ```Default: 25```
2. `npm start 10.9.70.10 10.9.70.11 10.9.70.12`
- Start the server in scheduler mode with local jobs enabled. This will allow scheduling jobs on both the local machine and remote hosts. A space separated list of agents can be accepted in addition to agents specified in the config.json file.
3. `npm start nolocal 10.9.70.10 10.9.70.11 10.9.70.12`
- *__(Recommended configuration)__* Start the server in scheduler only mode. This will prevent the local host from being available as a benchmarking host. A space separated list of agents can be accepted in addition to agents specified in the config.json file.
4. `npm start agent`
- Start the server in remote agent mode. This disables the scheduler interface and configures the server to listen for connections from a scheduling server. This is the mode that must be used on remote agents.
#
## Using the interface
### General guidelines
1. If using the bisect or two commit comparison, the commits must be on the same major branch.
2. You need to know which module the commit(s) were made in (i.e. qtdeclarative or qtbase for example)
3. You need to have a rough idea of the expected performance regression. Testing regressions smaller than ~5% is not recommended due to natural test noise.
4. It is **strongly recommended** that a QMLBench Regression Finder running as a slave is hosted on a machine that is performing no other tasks and has been configured for performance stability.
3. If bisecting, you need to have a rough idea of the expected performance regression.
Testing regressions smaller than ~5% is not recommended due to natural test noise.
4. It is **strongly recommended** that a QMLBench Regression Finder running as a agent is hosted
on a machine that is performing no other tasks and has been configured for
performance stability.
4. You need to know a specific QMLBench test that is affected by a given commit's changes.
- You may also write and upload custom QMLBench benchmarks.
- See [QMLBench](https://github.com/qt-labs/qmlbench) for an overview of the QMLBench test framework.
- The benchmark [creation/quick.item/delegates_item.qml](https://github.com/qt-labs/qmlbench/blob/master/benchmarks/auto/creation/quick.item/delegates_item.qml) may provide a good starting point for writing your own QMLBench benchmark.
- The benchmark
[creation/quick.item/delegates_item.qml](https://github.com/qt-labs/qmlbench/blob/master/benchmarks/auto/creation/quick.item/delegates_item.qml)
may provide a good starting point for writing your own QMLBench benchmark.
5. [**Important**] This tool works by cherry-picking a given change to test onto the closest-related qt5.git sha.
If your change to test requires additional patches to build, enter them when scheduling the
test. Note that this also applies to already merged changes. If the tool is having trouble
building a change, check if there are any patches that might apply.
### Automatic Bisect
This mode takes two commits from the same major branch (i.e. 5.12) and emulates a git-bisect like behavior, testing various commits in-between the known good and known bad commits to find a performance regression.
This mode takes two commits from the same major branch (i.e. 5.15) and emulates a git-bisect like behavior, testing various commits in-between the known good and known bad commits to find a performance regression.
Notes:
1. The "known good" commit must be an ancestor (is older and commited to the same branch) of the "known bad" commit.
1. The "known good" commit must be an ancestor (is older and committed to the same branch) of the "known bad" commit.
2. The commits must have been merged into Qt successfully.
### Single commit
......@@ -77,7 +90,7 @@ The framework is platform independent and can be deployed to as many clients as
### Two Commit Comparison
This mode tests two commits and provides results for each, as well as a percentage difference between the two. In this mode, WIP (work-in-progress) commits and commits not yet merged can be tested.
Notes:
1. Though the commits can be WIP or unmerged, they must still be in the same module and of the same branch. Use multiple runs of the single-commit test mode to test commits in different branches or modules.
......@@ -85,9 +98,9 @@ The framework is platform independent and can be deployed to as many clients as
### Common problems
1. **Build failures**
1. Bad checkout of some qt repo. If a test fails to build when you know it should, try simply deleting the QtBuild directory on the slave and re-stage the rest run.
1. Bad checkout of some qt repo. If a test fails to build when you know it should, try simply deleting the QtBuild directory on the agent and re-stage the rest run.
2. In some rare cases, a commit may not be able to be built with this tool if it had unmet dependencies when merged in COIN that weren't caught.
- **Support for hotpatching with other commit cherry picks is roadmapped.**
- If necessary, you can add additional patchrefs or shas to cherry-pick when testing a change.
2. **Failure to produce a test result**
1. There is no validation of custom QML benchmark file. If everything looks good in the build but QMLBench fails to produce a result or crashes, make sure your custom QMLBench benchmark .qml file is formatted correctly.
2. OpenGL is currently required on all platforms. If no OpenGL is installed, or if Angle is selected on windows but unsupported on the host, QMLBench will likely crash.
......
{
"BASE_URL": "localhost",
"HTTP_PORT": 8080,
"SMTP_SERVER": "smtp.intra.qt.io",
"SMTP_PORT": "25",
"VS_BUILD_TOOLS": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/Common7/Tools/VsDevCmd.bat",
"expectedDurations": {
"bisect": 14400000,
"singleCommit": 1350000,
"twoCommit": 2750000
},
"agents": []
}
\ No newline at end of file
exports.id = "jobNotificationHandler";
const events = require("events");
const axios = require("axios");
const tempWrite = require("temp-write");
const Buffer = require("buffer/").Buffer;
const JSum = require("jsum");
const ip = require("ip");
const os = require("os");
// This class contains functions for handing job queue processing logic,
// and status updates
class jobNotificationHandler {
constructor(notifyJob, testHandler, globals) {
this.notifyJob = notifyJob;
this.testHandler = testHandler;
this.globals = globals;
this.addUpdateToQueue = this.addUpdateToQueue.bind(this);
this.updateRunningJobStatus = this.updateRunningJobStatus.bind(this);
this.notifyJob.on("updateRemote", this.updateRemote.bind(this));
this.notifyJob.on("updateRemoteJob", this.updateRemoteJob.bind(this));
this.notifyJob.on("newLocalJob", this.newLocalJob.bind(this));
this.scheduleJobRequest = this.scheduleJobRequest.bind(this);
this.notifyJob.on("cancel", this.cancel.bind(this));
}
// Received an HTTP request to update a job. Queue the request for processing.
addUpdateToQueue(req, res) {
let _this = this;
console.log(`Received Job update "${req.body.type}" ${
req.body.type == "status_msg"
? "'" + req.body.job.status_msg + "'"
: ""
} for job ${req.body.job.jobHash.slice(0, 10)}`);
_this.globals.updateQueue.push({ job: req.body.job, type: req.body.type });
_this.notifyJob.emit("updateRemote"); // try to process the request immediately.
if (res)
res.sendStatus(200); // Respond to the caller that we received the request.
}
updateRunningJobStatus(req, res) {
let _this = this;
_this.globals.runningJob.status_msg = req.body.status_msg;
_this.notifyJob.emit("updateRemoteJob", _this.globals.runningJob, "status_msg");
console.log(`Local running job changed status to: ${_this.globals.runningJob.status_msg}`);
res.sendStatus(200);
}
updateRemote(recursive) {
let _this = this;
// This signal should be emitted when an update from a remote worker is received.
if (!_this.globals.updateLockout) {
_this.globals.updateLockout = true; // lock the work arrays from being modified by anyone else
_this.updateJob(_this.globals.updateQueue.pop()); // process next in line
} else if (recursive) {
// If recursive was passed, we got here directly from processing a previous update.
// In this case, the lock is not removed in order to prevent a race condition in updating.
_this.updateJob(_this.globals.updateQueue.pop());
}
}
updateJob(job) {
let _this = this;
let tempjob;
if (job.type == "status_msg") {
let index = _this.globals.remoteRunningJobs.findIndex((x) => x.jobHash == job.job.jobHash);
if (index >= 0)
_this.globals.remoteRunningJobs[index].status_msg = job.job.status_msg;
} else if (job.type == "running") {
// Search our arrays based on the type of request received. When the job is found,
// splice it out of it's array and place the updated job we got where it needs to go.
let index = _this.globals.remoteQueuedJobs.findIndex((x) => x.jobHash == job.job.jobHash);
if (index >= 0) {
tempjob = _this.globals.remoteQueuedJobs.splice(index, 1);
console.log(`Moved job ${tempjob[0].jobHash} from remote queued to remote running.`);
}
_this.globals.remoteRunningJobs.push(job.job);
} else if (job.type == "finished" || job.type == "cancelled") {
let index = _this.globals.remoteRunningJobs.findIndex((x) => x.jobHash == job.job.jobHash);
if (index >= 0) {
tempjob = _this.globals.remoteRunningJobs.splice(index, 1);
console.log(`Moved job ${tempjob[0].jobHash} from remote running to Finished.`);
} else {
index = _this.globals.remoteQueuedJobs.findIndex((x) => x.jobHash == job.job.jobHash);
if (index >= 0)
tempjob = _this.globals.remoteQueuedJobs.splice(index, 1);
else
console.log(`Received notification that job ${job.job.jobHash} was cancelled or finished, but we can't find it. This probably means that we missed an earlier update...`);
console.log(`Moved job ${tempjob[0].jobHash} from remote queued to Finished.`);
}
_this.globals.completedJobs.push(job.job);
} else if (job.type == "cancelling") {
let index = _this.globals.remoteRunningJobs.findIndex((x) => x.jobHash == job.job.jobHash);
if (index >= 0) {
_this.globals.remoteRunningJobs[index].status = "cancelling";
console.log(`Set remote running job ${
_this.globals.remoteRunningJobs[index].jobHash} to cancelling.`);
} else {
index = _this.globals.remoteQueuedJobs.findIndex((x) => x.jobHash == job.job.jobHash);
if (index >= 0) {
_this.globals.remoteQueuedJobs[index].status = "cancelling";
console.log(`Set remote queued job ${
_this.globals.remoteQueuedJobs[index].jobHash} to cancelling.`);
} else {
console.log(`Received notification that job ${job.job.jobHash} was cancelled, but we can't find it. This probably means that we missed an earlier update...`);
}
}
} else {
console.log(`WARN: Received unknown update type ${job.type} for job ${
job.job.jobHash}\nIf this job exists, it will probably be stuck now.`);
_this.globals.recentErrors.push({
error: `WARN: Received unknown update type ${job.type} for job ${job.job.jobHash
}\nIf this job exists, it will probably be stuck now.`,
job: job.job
});
}
if (_this.globals.updateQueue.length > 0)
// Process the next update in queue if there is one,
// and keep the updater locked out in the meantime.
_this.notifyJob.emit("updateRemote", true);
else
_this.globals.updateLockout = false; // End the lockout
}
updateRemoteJob(job, action) {
let _this = this;
// A remote host will emit this signal when a job's state changes.
// Send the update type and a copy of the job to the remote master.
let tempjob;
if (action == "status_msg") {
tempjob = { jobHash: job.jobHash, status_msg: job.status_msg };
} else {
console.log(`Updating master at ${job.master_host} with job status ${
job.status} for ${job.jobHash}`);
}
axios.post(
`http://${job.master_host}:${_this.globals.webPort}/pushJobUpdate`,
{ job: tempjob != undefined ? tempjob : job, type: action }
).catch((error) => {
// This is only a WARN because the job will continue processing locally,
// even if the master host isn't aware of it.
console.log("WARN: Unable to post job update to master host. It may be offline.");
});
}
// We received a job for this server to run.
// If we're not locked out due to another current process, execute it immediately.
newLocalJob() {
let _this = this;
if (!_this.globals.locked) {
_this.globals.locked = true; // Lock out other jobs from trying to run.
_this.globals.runningJob = _this.globals.jobQueue.pop(); // Grab a job from the queue
_this.globals.runningJob.status = "running";
_this.globals.runningJob.status_msg = "Launching new build process";
_this.globals.runningJob.startTime = Date.now();
if (_this.globals.runningJob.remote_job)
// Notify the scheduler that we've moved this job into running status.
_this.notifyJob.emit("updateRemoteJob", _this.globals.runningJob, "running");
// set the process handle and hand the job over to the test runner
setTimeout(function () {
_this.globals.runningJob.processHandle =
_this.testHandler.runTest(_this.globals.runningJob);
}, 1000);
}
}
// This is triggered in two ways
// 1. When a user presses the 'schedule' button in the web interface
// 2. When a remote server receives a request to schedule a new job.
scheduleJobRequest(req, res) {
let _this = this;
const readyEmitter = new events.EventEmitter();
console.log("Received request to schedule new job:");
// Dump the job request to the console for logging and diagnostic purposes.
console.log(req.body);
if (req.files)
console.log(req.files);
if (!Array.isArray(req.body.test_hosts)) {
// Magic. This allows us to use the same endpoint for scheduling a job
// on both one and multiple hosts.
req.body.test_hosts = [req.body.test_hosts];
}
// Count how many times we need to schedule.
// Used for knowing when to redirect the scheduler browser.
let numJobsToSchedule = req.body.test_hosts.length;
let jobsScheduled = 0;
_this.notifyJob.on("jobScheduled", function (req, res) {
jobsScheduled += 1;
if (jobsScheduled == numJobsToSchedule) {
// Received responses from all job schedule requests.
_this.notifyJob.emit("finishedScheduling", req, res);
}
});
// The sendJobs signal is fired when we've finished polling codereview
// for a job title from one of the commits.
readyEmitter.on("sendJobs", function (title) {
// Create a job for each test host
for (let i in req.body.test_hosts) {
const job = {
timestamp: Date.now(),
testType: req.body.testType,
platform: req.body.platform,
owner: req.body.owner,
title: title,
status: req.body.status,
status_msg: req.body.status_msg,
branch: req.body.branch,
module: req.body.module,
commit: req.body.commit,
patches: req.body.patches,
firstCommitBuildOnHead: req.body.firstCommitBuildOnHead,
secondCommitBuildOnHead: req.body.secondCommitBuildOnHead,
firstCommit: req.body.firstCommit,
secondCommit: req.body.secondCommit,
bad_commit: req.body.bad_commit,
good_date: req.body.good_date,
good_commit: req.body.good_commit,
openGLBackend: req.body.openGLBackend,
test_name: req.body.test_name,
// File location in the request changes depending on if the
// request to schedule is a result of web form submission or
// incoming http request from the scheduler.
custom_benchmark_file: req.files
? req.files.custom_benchmark_file
: req.body.custom_benchmark_file,
expected_regression: req.body.expected_regression,
test_all: req.body.test_all,
// Only tell the agent which will execute the job about itself.
test_hosts: Array.isArray(req.body.test_hosts)
? [req.body.test_hosts[i]]
: [req.body.test_hosts],
environment: req.body.environment,
remote_job: req.body.remote_job,
master_host: req.body.master_host
};
// Dump the data from the custom file to disk as a temp file.
if (job.custom_benchmark_file) {
// Check to see if we have a properly formed data buffer.
// If not, re-create it from the object.
if (!(
job.custom_benchmark_file.data.constructor &&
job.custom_benchmark_file.data.constructor.isBuffer &&
job.custom_benchmark_file.data.constructor(job.custom_benchmark_file.data)
))
job.custom_benchmark_file.data = Buffer.from(job.custom_benchmark_file.data.data);
}
if (job.owner == "")
job.owner = "default"; // Set a display name if the email address field wasn't filled.
if (!job.status_msg)
job.status_msg = "staged"; // Set a starting state if not set. Display purposes only.
// Either we're a remote host and received a job, or we're the scheduler
// and have a worker available.
if (req.body.test_hosts[i] == _this.globals.myname) {
console.log("Executing test locally.");
if (job.custom_benchmark_file) {
job.custom_benchmark_file["tempFilePath"] =
tempWrite.sync(job.custom_benchmark_file.data, job.custom_benchmark_file.name);
}
// Update self if master_host isn't set. This should only happen with the job is running
// locally on the master host, usually in standalone mode.
if (!job["master_host"])
job["master_host"] = ip.address();
const jobHash = JSum.digest(job, "SHA256", "hex"); // Create a unique hash for this job
console.log(`Hash for this job: ${jobHash}`);
job["jobHash"] = jobHash;
// This should never happen.
// It could only really happen if the scheduler sends
// the same exact job twice (including the timestamp on it)
if (
_this.globals.runningJob.jobHash === jobHash ||
_this.globals.jobQueue.findIndex((x) => x.jobHash == jobHash) >= 0
) {
console.log("Returning error. Job already exists.");
if (!_this.globals.isPrimaryHost) {
res.status(500).send({ error: "jobExists", job: job });
continue;
} else {
_this.globals.recentErrors.push({ error: "jobExists", job: job });
_this.notifyJob.emit("jobScheduled", req, res);
continue;
}
}
job.platform = os.platform();
job.status = "staged";
// Each server keeps an expected duration for each type
// of job so we can give more accurate estimates.
job.expectedDuration = _this.globals.expectedDurations[job.testType];
_this.globals.jobQueue.push(job);
if (job.remote_job)
res.send(job); // Send the completed job object to the scheduler.
else
// We're running this on the scheduler itself,
// need to send a different kind of response.
_this.notifyJob.emit("jobScheduled", req, res);
// Notify self that there's a new job and execute if the queue's empty.
_this.notifyJob.emit("newLocalJob");
} else {
// We're processing the meta job request and there's a host that isn't the scheduler.
// Mark it as remote and forward it.
job["remote_job"] = true;
job["master_host"] = ip.address(); // Record which scheduler requested this job.
let targetAgentIP = _this.globals.hosts[_this.globals.hosts
.findIndex((x) => x.hostname == req.body.test_hosts[i])].ip
console.log(`Posting job to http://${
targetAgentIP}:${_this.globals.webPort}/remoteSchedule`);
axios.post(
`http://${targetAgentIP}:${_this.globals.webPort}/remoteSchedule`,
job
).then((response) => {
console.log("Sent the job to remote host and got a response. Looks good.");
_this.globals.remoteQueuedJobs.push(response.data);
_this.notifyJob.emit("jobScheduled", req, res); // +1 to scheduled jobs
})
.catch((error) => {
console.log(error.response.data);
if (error.response.data.error == "jobExists")
_this.globals.recentErrors.push(error.response.data);
else
console.log(error);
_this.notifyJob.emit("jobScheduled", req, res); // +1 to scheduled jobs
});
} // End local/remote if
} // End for loop
}); // End sendJobs event handler
if (!req.body.title) {
// Initialize the job with a title pulled from one of the commits provided.
const sha = req.body.commit
? req.body.commit
: req.body.firstCommit
? req.body.firstCommit
: req.body.bad_commit;
axios
.get(`https://codereview.qt-project.org/changes/?q=commit:${sha}`, { timeout: 2000 })
.then((response) => {
const title = JSON.parse(response.data.slice(4))[0].subject;
// console.log(`Found sha ${sha} on codereview with title '${title}'.`)
readyEmitter.emit("sendJobs", title);
})
.catch((error) => {
const title = "Unknown Job";
// Go ahead and schedule jobs without a title. Hopefully it works, but good luck.
readyEmitter.emit("sendJobs", title);
});
} else {
// We got a title, schedule the jobs using this.
readyEmitter.emit("sendJobs", req.body.title);
}
}
// Cancel a locally running job
cancel(job, req, res) {
let _this = this;
if (job.status == "running") {
// If it's a running job we need to try and kill the process.
job.status = "cancelling";
job.status_msg = "Cancelling...";
if (job.remote_job) {
console.log(`Preparing to send update to scheduler about cancelling ${job.jobHash}`);
_this.notifyJob.emit("updateRemoteJob", job, "cancelling");
}
// Use platform specific method to kill the entire process tree.
// Unfortunately, we just have to trust here.
if (os.platform() === "win32") {
console.log(`Trying to kill windows job with jobHash ${job.jobHash}`);
let spawn = require("child_process").spawn;
spawn("taskkill", ["/pid", job.processHandle.pid, "/F", "/T"]);
} else {
console.log(`Trying to kill linux job with jobHash ${job.jobHash}`);
process.kill(-job.processHandle.pid); // using "-pid" converts the pid to a group of pids.
}
// Clean up the process handle since it's dead anyway.
// This makes it easier to stringify the job when sending updates to the scheduler, too.
delete job.processHandle;
} else {
// Not a running job, must be queued.
console.log(`Moving queued job to completed-cancelled with jobHash ${job.jobHash}`);
// No need to go through the work of sending an update for "cancelling",
// just go straight to "cancelled" state.
job.status = "cancelled";
_this.globals.completedJobs.push(job);
// pop the queued job out of the queue.
_this.globals.jobQueue.splice(_this.globals.jobQueue.findIndex((x) => x.jobHash == job.jobHash));
if (job.remote_job) {
console.log(`Preparing to send update to master host about cancelled jobHash ${job.jobHash}`);
job.status_msg = "Cancelled";
_this.notifyJob.emit("updateRemoteJob", job, "cancelled");
}
}
setTimeout(function () {
res.redirect("/");
}, 500, res);
}
}
module.exports = jobNotificationHandler;
\ No newline at end of file
......@@ -22,9 +22,7 @@ builddir = os.path.join(basedir, "QtBuild")
installdir = os.path.join(builddir, "Install")
isWindowsOS = (platform.system() == 'Windows')
compiler = ["BuildConsole", f"/COMMAND={os.path.join(basedir, 'JOM', 'jom.exe')}"] if isWindowsOS else ["make", f"-j{os.environ.get('BUILD_CORES') or os.cpu_count()}"]
print(compiler)
installer = [compiler[0], compiler[1] + " install"] if isWindowsOS else compiler + ["install"]
print(installer)
exeExt = '.exe' if isWindowsOS else ''
goodBaselineScore = 0
badBaselineScore = 0
......@@ -1063,7 +1061,6 @@ def reportStatusUpdate(status_msg=""):
if __name__ == "__main__":
atexit.register(on_exit)
args = parseArgs()
print(args.patches, type(args.patches))
prepareEnv()
initRepositories()
if not validateCommits() or not validateTest(): # Validate inputs!
......
......@@ -3,1179 +3,298 @@
const { spawn } = require("child_process");
const express = require("express");
const fileUpload = require("express-fileupload");
let events = require("events");
const events = require("events");
const moment = require("moment");
let Mustache = require("mustache");
let momentDurationFormatSetup = require("moment-duration-format");
const Mustache = require("mustache");
const momentDurationFormatSetup = require("moment-duration-format");
momentDurationFormatSetup(moment);
const os = require("os");
const myname = os.hostname();
let isPrimaryHost;
let path = os.platform() === "win32" ? "c:" : "/";
let pathmodule = require("path");
let agents = [];
let bodyParser = require("body-parser");
let ip = require("ip");
const pathmodule = require("path");
const bodyParser = require("body-parser");
const ip = require("ip");
const disk = require("diskusage");
const axios = require("axios");
const prettyBytes = require("pretty-bytes");
const JSum = require("jsum");
let fs = require("fs");
const nodemailer = require("nodemailer");
const readLastLines = require("read-last-lines");
let validator = require("email-validator");
const fs = require("fs");
const serveIndex