'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 = ''; 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 = ''; 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 = ''; 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 = ''; 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}; }