|
'use strict';
|
|
|
|
function JobManager(agent) {
|
|
this.agent = agent;
|
|
this.jobs = {
|
|
id:{},
|
|
type:{
|
|
open:{},
|
|
closed:{}
|
|
}
|
|
};
|
|
this.openJobs = {};
|
|
}
|
|
|
|
JobManager.prototype.add = function(id, type, time, prerequisites) {
|
|
var me = this;
|
|
|
|
// create job agent. This agent will keep track of the global job stats. Jobs per type.
|
|
this.agent.rpc.request('jobAgentGenerator',{method:'createJob', params:{type:type}}).done();
|
|
|
|
this.jobs.id[id] = {
|
|
type: type,
|
|
startTime: time,
|
|
prediction: null,
|
|
predictionCounter: 0,
|
|
elapsedTime: 0,
|
|
elapsedTimeWithPause: 0,
|
|
pauseCount: 0,
|
|
endOfDay: false,
|
|
paused: false,
|
|
pauseAreaID: ""
|
|
};
|
|
|
|
// assign agent to job.
|
|
this.agent.rpc.request(type, {method:'add', params:{
|
|
agentId: this.agent.id,
|
|
time:time,
|
|
jobId: id,
|
|
prerequisites: prerequisites
|
|
}})
|
|
.done(function (prediction) {
|
|
if (prediction.duration.mean != 0) {
|
|
me.agent.timelineDataset.update({
|
|
id: id + "_predMean0",
|
|
start: time,
|
|
end: new Date(time).getTime() + prediction.duration.mean,
|
|
group: me.agent.id,
|
|
type: 'range',
|
|
content: "",
|
|
subgroup: me.agent.usedSubgroups[type],
|
|
className: 'prediction'
|
|
});
|
|
}
|
|
me.jobs.id[id].prediction = prediction;
|
|
});
|
|
|
|
if (this.jobs.type.open[type] === undefined) {this.jobs.type.open[type] = {}}
|
|
this.jobs.type.open[type][id] = time;
|
|
this.openJobs[id] = this.jobs.id[id];
|
|
|
|
|
|
var addQuery = [{id:id, start:time, content:"started: "+ type, group:this.agent.id, subgroup: this.agent.usedSubgroups[type]}];
|
|
this.agent.timelineDataset.add(addQuery);
|
|
this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}).done();
|
|
};
|
|
|
|
JobManager.prototype.finish = function(id, type, time) {
|
|
var me = this;
|
|
// finish the job.
|
|
this.agent.rpc.request(type, {method:'finish', params:{
|
|
agentId: this.agent.id,
|
|
time: time,
|
|
jobId: id
|
|
}})
|
|
.done(function (reply) {
|
|
var prediction = reply.prediction;
|
|
var originalPrediction = me.jobs.id[id].prediction;
|
|
me.jobs.id[id].elapsedTime = reply.elapsedTime;
|
|
me.jobs.id[id].elapsedTimeWithPause = reply.elapsedTimeWithPause;
|
|
me.updateDataSetsFinish(id, type, time, me.jobs.id[id].prediction);
|
|
|
|
graph2dDataset.push({x: time, y: reply.duration.duration/3600000 ,group: type + '_duration', type: type});
|
|
graph2dDataset.push({x: time, y: reply.duration.durationWithPause/3600000 ,group: type + '_durationWithPause', type: type});
|
|
graph2dDataset.push({x: time, y: reply.duration.durationWithStartup/3600000 ,group: type + '_durationWithStartup', type: type});
|
|
graph2dDataset.push({x: time, y: reply.duration.durationWithBoth/3600000 ,group: type + '_durationWithBoth', type: type});
|
|
graph2dDataset.push({x: time, y: prediction.duration.mean/3600000 ,group: type + '_pred_duration', type: type});
|
|
graph2dDataset.push({x: time, y: prediction.durationWithPause.mean/3600000 ,group: type + '_pred_durationWithPause', type: type});
|
|
graph2dDataset.push({x: time, y: prediction.durationWithStartup.mean/3600000 ,group: type + '_pred_durationWithStartup', type: type});
|
|
graph2dDataset.push({x: time, y: prediction.durationWithBoth.mean/3600000 ,group: type + '_pred_durationWithBoth', type: type});
|
|
graph2dDataset.push({x: time, y: (prediction.duration.mean + prediction.duration.std)/3600000 ,group: type + '_pred_duration_std_higher', type: type});
|
|
graph2dDataset.push({x: time, y: (prediction.durationWithPause.mean + prediction.durationWithPause.std)/3600000 ,group: type + '_pred_durationWithPause_std_higher', type: type});
|
|
graph2dDataset.push({x: time, y: (prediction.durationWithStartup.mean + prediction.durationWithStartup.std)/3600000 ,group: type + '_pred_durationWithStartup_std_higher', type: type});
|
|
graph2dDataset.push({x: time, y: (prediction.durationWithBoth.mean + prediction.durationWithBoth.std)/3600000 ,group: type + '_pred_durationWithBoth_std_higher', type: type});
|
|
graph2dDataset.push({x: time, y: (prediction.duration.mean - prediction.duration.std)/3600000 ,group: type + '_pred_duration_std_lower', type: type});
|
|
graph2dDataset.push({x: time, y: (prediction.durationWithPause.mean - prediction.durationWithPause.std)/3600000 ,group: type + '_pred_durationWithPause_std_lower', type: type});
|
|
graph2dDataset.push({x: time, y: (prediction.durationWithStartup.mean - prediction.durationWithStartup.std)/3600000 ,group: type + '_pred_durationWithStartup_std_lower', type: type});
|
|
graph2dDataset.push({x: time, y: (prediction.durationWithBoth.mean - prediction.durationWithBoth.std)/3600000 ,group: type + '_pred_durationWithBoth_std_lower', type: type});
|
|
graph2dDataset.push({x: time, y: originalPrediction.duration.mean/3600000 ,group: type + '_pred_duration_original', type: type});
|
|
graph2dDataset.push({x: time, y: originalPrediction.durationWithPause.mean/3600000 ,group: type + '_pred_durationWithPause_original', type: type});
|
|
graph2dDataset.push({x: time, y: originalPrediction.durationWithStartup.mean/3600000 ,group: type + '_pred_durationWithStartup_original', type: type});
|
|
graph2dDataset.push({x: time, y: originalPrediction.durationWithBoth.mean/3600000 ,group: type + '_pred_durationWithBoth_original', type: type});
|
|
});
|
|
|
|
delete this.jobs.type.open[type][id];
|
|
delete this.openJobs[id];
|
|
if (this.jobs.type.closed[type] === undefined) {this.jobs.type.closed[type] = {}}
|
|
this.jobs.type.closed[type][id] = time;
|
|
};
|
|
|
|
JobManager.prototype.update = function(id, type, time, operation) {
|
|
var me = this;
|
|
var eventId = uuid();
|
|
if (operation == 'endOfDay' || operation == 'startOfDay') {
|
|
for (var jobId in this.openJobs) {
|
|
if (this.openJobs.hasOwnProperty(jobId)) {
|
|
var type = this.openJobs[jobId].type;
|
|
this.agent.rpc.request(type, {method:'update', params:{
|
|
agentId: this.agent.id,
|
|
time: time,
|
|
jobId: jobId,
|
|
operation: operation
|
|
}})
|
|
.done(function (reply) {
|
|
me.jobs.id[reply.jobId].elapsedTime = reply.elapsedTime;
|
|
me.jobs.id[reply.jobId].elapsedTimeWithPause = reply.elapsedTimeWithPause;
|
|
me.updateDataSetsOperation(reply.jobId, reply.type, time, operation, eventId);
|
|
});
|
|
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
this.agent.rpc.request(type, {method:'update', params:{
|
|
agentId: this.agent.id,
|
|
time: time,
|
|
jobId: id,
|
|
operation: operation
|
|
}})
|
|
.done(function (reply) {
|
|
me.jobs.id[id].elapsedTime = reply.elapsedTime;
|
|
me.jobs.id[id].elapsedTimeWithPause = reply.elapsedTimeWithPause;
|
|
me.updateDataSetsOperation(id, type, time, operation, eventId);
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
JobManager.prototype.updateDataSetsOperation = function(id, type, time, operation, eventId) {
|
|
switch (operation) {
|
|
case 'pause':
|
|
this.updateDataSetsPause(id, type, time, operation, this.jobs.id[id].prediction, eventId);
|
|
break;
|
|
case 'endOfDay':
|
|
this.updateDataSetsPause(id, type, time, operation, this.jobs.id[id].prediction, eventId);
|
|
break;
|
|
case 'startOfDay':
|
|
this.updateDataSetsResume(id, type, time, operation, this.jobs.id[id].prediction, eventId);
|
|
break;
|
|
case 'resume':
|
|
this.updateDataSetsResume(id, type, time, operation, this.jobs.id[id].prediction, eventId);
|
|
break;
|
|
}
|
|
};
|
|
|
|
JobManager.prototype.updateDataSetsFinish = function(id, type, time, prediction) {
|
|
var updateQuery = [];
|
|
var field = 'duration';
|
|
var elapsedTime = this.jobs.id[id].elapsedTime;
|
|
|
|
// gather statistic indicator data
|
|
if (this.jobs.id[id].pauseCount > 0) {
|
|
field = 'durationWithPause';
|
|
elapsedTime = this.jobs.id[id].elapsedTimeWithPause;
|
|
}
|
|
|
|
// generate indicator
|
|
var predictedTimeLeft = 0;
|
|
if (prediction[field].mean != 0) {
|
|
predictedTimeLeft = prediction[field].mean - elapsedTime;
|
|
this.updatePredictionOnFinish(id, type, time, prediction[field], elapsedTime);
|
|
}
|
|
|
|
updateQuery.push({
|
|
id: id,
|
|
end: time,
|
|
content: type,
|
|
type: 'range',
|
|
className: 'finished',
|
|
style: 'background-color: ' + this.generateColors(predictedTimeLeft, elapsedTime) + ' !important;'
|
|
});
|
|
|
|
this.agent.freeSubgroup(type);
|
|
this.agent.timelineDataset.update(updateQuery);
|
|
this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}).done();
|
|
};
|
|
|
|
JobManager.prototype.updateDataSetsPause = function(id, type, time, operation, prediction, eventId) {
|
|
var updateQuery = [];
|
|
var image = '<img src="./images/control_pause.png" class="icon"/>';
|
|
var flagId = id + "_pauseNotifier" + eventId;
|
|
var title = 'Calling RAO for possible NC';
|
|
|
|
// this causes there to be only one flag for the end of day as well as a moon icon
|
|
if (operation == 'endOfDay') {
|
|
// don't end-of-day jobs twice
|
|
if (this.jobs.id[id].endOfDay == true) {
|
|
return;
|
|
}
|
|
this.jobs.id[id].endOfDay = true;
|
|
image = '<img src="./images/moon.png" class="icon"/>';
|
|
flagId = id + "endOfDayNotifier" + eventId;
|
|
title = "End of working day"
|
|
}
|
|
else {
|
|
this.jobs.id[id].pauseCount += 1;
|
|
this.jobs.id[id].pauseAreaID = this.agent.id + "_" + "_pauseArea_" + eventId;
|
|
var shadedPauseArea = {
|
|
id: this.jobs.id[id].pauseAreaID,
|
|
start: time,
|
|
end: time,
|
|
content: '',
|
|
group: this.agent.id,
|
|
subgroup: this.agent.usedSubgroups[type],
|
|
className: 'pausedArea',
|
|
title: 'Job paused.'
|
|
};
|
|
updateQuery.push(shadedPauseArea);
|
|
}
|
|
|
|
var field = 'duration';
|
|
var elapsedTime = this.jobs.id[id].elapsedTime;
|
|
if (this.jobs.id[id].pauseCount > 0) {
|
|
field = 'durationWithPause';
|
|
elapsedTime = this.jobs.id[id].elapsedTimeWithPause;
|
|
}
|
|
|
|
updateQuery.push({id: id, end: time, content: type, type: 'range'});
|
|
var imageUpdate = {
|
|
id: flagId,
|
|
start: time,
|
|
end: time,
|
|
content: image,
|
|
group: this.agent.id,
|
|
subgroup: this.agent.usedSubgroups[type],
|
|
className: 'pause',
|
|
title: title
|
|
};
|
|
|
|
|
|
if (this.agent.id == "Paolo") {
|
|
imageUpdate.title = "Going to inspect possible NC.";
|
|
}
|
|
updateQuery.push(imageUpdate);
|
|
|
|
var predictedTimeLeft = prediction[field].mean - elapsedTime;
|
|
var predictionExists = prediction[field].mean != 0;
|
|
|
|
// update the predicted line if the job is not ALREADY pauseds
|
|
if (predictedTimeLeft > 0 && predictionExists == true && this.jobs.id[id].paused != true) {
|
|
updateQuery.push({
|
|
id: id + "_predMean" + this.jobs.id[id].predictionCounter,
|
|
end: time,
|
|
group: this.agent.id,
|
|
className: 'prediction'
|
|
})
|
|
}
|
|
|
|
// set the status to paused if needed
|
|
if (operation != 'endOfDay') {
|
|
this.jobs.id[id].paused = true;
|
|
}
|
|
this.agent.timelineDataset.update(updateQuery);
|
|
this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}})
|
|
};
|
|
|
|
JobManager.prototype.updateDataSetsResume = function(id, type, time, operation, prediction, eventId) {
|
|
var updateQuery = [];
|
|
var image = '<img src="./images/control_play.png" class="icon"/>';
|
|
var flagId = id + "_resumeNotifier" + eventId;
|
|
|
|
// this causes there to be only one flag for the start of day as well as a sun icon
|
|
if (operation == 'startOfDay') {
|
|
// don't start-of-day jobs twice
|
|
if (this.jobs.id[id].endOfDay == false) {
|
|
return;
|
|
}
|
|
this.jobs.id[id].endOfDay = false;
|
|
image = '<img src="./images/sun.png" class="icon"/>';
|
|
flagId = id + "startOfDayNotifier_" + eventId;
|
|
}
|
|
else {
|
|
updateQuery.push({id: this.jobs.id[id].pauseAreaID, end: time, content: '', type: 'range'});
|
|
this.jobs.id[id].pauseAreaID = "";
|
|
this.jobs.id[id].pauseCount += 1;
|
|
}
|
|
|
|
var field = 'duration';
|
|
var elapsedTime = this.jobs.id[id].elapsedTime;
|
|
if (this.jobs.id[id].pauseCount > 0) {
|
|
field = 'durationWithPause';
|
|
elapsedTime = this.jobs.id[id].elapsedTimeWithPause;
|
|
}
|
|
|
|
updateQuery.push({id: id, end: time, content: type, type: 'range'});
|
|
updateQuery.push({
|
|
id: flagId,
|
|
start: time,
|
|
end: time,
|
|
content: image,
|
|
group: this.agent.id,
|
|
subgroup: this.agent.usedSubgroups[type],
|
|
className: 'pause',
|
|
title: 'Resuming job'
|
|
});
|
|
|
|
var predictedTimeLeft = prediction[field].mean - elapsedTime;
|
|
// no not increase the prediction line at the start of the day if the job is PAUSED
|
|
if (predictedTimeLeft > 0 && !(operation == 'startOfDay' && this.jobs.id[id].paused == true)) {
|
|
this.jobs.id[id].predictionCounter += 1;
|
|
updateQuery.push({
|
|
id: id + "_predMean" + this.jobs.id[id].predictionCounter,
|
|
start: time,
|
|
end: new Date(time).getTime() + predictedTimeLeft,
|
|
group: this.agent.id,
|
|
content: "",
|
|
subgroup: this.agent.usedSubgroups[type],
|
|
type: 'range',
|
|
className: 'prediction'
|
|
});
|
|
}
|
|
|
|
// resume if needed
|
|
if (operation != 'startOfDay'){
|
|
this.jobs.id[id].paused = false;
|
|
}
|
|
this.agent.timelineDataset.update(updateQuery);
|
|
this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}})
|
|
};
|
|
|
|
JobManager.prototype.updatePredictionOnFinish = function(id,type,time,prediction,elapsedTime) {
|
|
if (prediction.mean != 0) {
|
|
var predictedTimeLeft = prediction.mean - elapsedTime;
|
|
if (predictedTimeLeft > 0) {
|
|
this.agent.timelineDataset.remove({id: id + "_predMean" + this.jobs.id[id].predictionCounter})
|
|
}
|
|
}
|
|
};
|
|
|
|
JobManager.prototype.updateJobs = function(time, skipId) {
|
|
var updateQuery = [];
|
|
for (var jobId in this.openJobs) {
|
|
if (this.openJobs.hasOwnProperty(jobId) && jobId != skipId) {
|
|
var type = this.openJobs[jobId].type;
|
|
var prediction = this.openJobs[jobId].prediction;
|
|
var predictedTimeLeft = 0;
|
|
// never been paused before
|
|
if (this.jobs.id[jobId].pauseCount == 0) {
|
|
if (prediction !== null && prediction.duration !== null) {
|
|
predictedTimeLeft = prediction.duration.mean - this.jobs.id[jobId].elapsedTime;
|
|
}
|
|
}
|
|
else {
|
|
if (prediction !== null && prediction.durationWithPause !== null) {
|
|
predictedTimeLeft = prediction.durationWithPause.mean - this.jobs.id[jobId].elapsedTimeWithPause;
|
|
}
|
|
}
|
|
updateQuery.push({id: jobId, end: time, content: type, type: 'range'});
|
|
if (this.openJobs[jobId].paused == true) {
|
|
updateQuery.push({id: this.openJobs[jobId].pauseAreaID, end: time});
|
|
}
|
|
}
|
|
}
|
|
|
|
this.agent.timelineDataset.update(updateQuery);
|
|
};
|
|
|
|
|
|
JobManager.prototype.generateColors = function(predictedTime, elapsedTime) {
|
|
var ratio = (elapsedTime + predictedTime) / elapsedTime;
|
|
if (ratio > 1) {
|
|
ratio = Math.min(2,ratio) - 1; // 2 -- 1
|
|
var rgb = HSVToRGB(94/360,ratio*0.6 + 0.2,1);
|
|
}
|
|
else {
|
|
ratio = Math.max(0.5,ratio) - 0.5; // 1 -- 0.5
|
|
var rgb = HSVToRGB(40/360,(1-(2*ratio))*0.6 + 0.1 ,1);
|
|
}
|
|
return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
|
|
};
|
|
|
|
function HSVToRGB(h, s, v) {
|
|
var r, g, b;
|
|
|
|
var i = Math.floor(h * 6);
|
|
var f = h * 6 - i;
|
|
var p = v * (1 - s);
|
|
var q = v * (1 - f * s);
|
|
var t = v * (1 - (1 - f) * s);
|
|
|
|
switch (i % 6) {
|
|
case 0: r = v, g = t, b = p; break;
|
|
case 1: r = q, g = v, b = p; break;
|
|
case 2: r = p, g = v, b = t; break;
|
|
case 3: r = p, g = q, b = v; break;
|
|
case 4: r = t, g = p, b = v; break;
|
|
case 5: r = v, g = p, b = q; break;
|
|
}
|
|
|
|
return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
|
|
}
|
|
function RGBToHSV (red,green,blue) {
|
|
red=red/255; green=green/255; blue=blue/255;
|
|
var minRGB = Math.min(red,Math.min(green,blue));
|
|
var maxRGB = Math.max(red,Math.max(green,blue));
|
|
|
|
// Black-gray-white
|
|
if (minRGB == maxRGB) {
|
|
return {h:0,s:0,v:minRGB};
|
|
}
|
|
|
|
// Colors other than black-gray-white:
|
|
var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
|
|
var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
|
|
var hue = 60*(h - d/(maxRGB - minRGB))/360;
|
|
var saturation = (maxRGB - minRGB)/maxRGB;
|
|
var value = maxRGB;
|
|
return {h:hue,s:saturation,v:value};
|
|
}
|