vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

427 lines
16 KiB

10 years ago
  1. 'use strict';
  2. function JobManager(agent) {
  3. this.agent = agent;
  4. this.jobs = {
  5. id:{},
  6. type:{
  7. open:{},
  8. closed:{}
  9. }
  10. };
  11. this.openJobs = {};
  12. }
  13. JobManager.prototype.add = function(id, type, time, prerequisites) {
  14. var me = this;
  15. // create job agent. This agent will keep track of the global job stats. Jobs per type.
  16. this.agent.rpc.request('jobAgentGenerator',{method:'createJob', params:{type:type}}).done();
  17. this.jobs.id[id] = {
  18. type: type,
  19. startTime: time,
  20. prediction: null,
  21. predictionCounter: 0,
  22. elapsedTime: 0,
  23. elapsedTimeWithPause: 0,
  24. pauseCount: 0,
  25. endOfDay: false,
  26. paused: false,
  27. pauseAreaID: ""
  28. };
  29. // assign agent to job.
  30. this.agent.rpc.request(type, {method:'add', params:{
  31. agentId: this.agent.id,
  32. time:time,
  33. jobId: id,
  34. prerequisites: prerequisites
  35. }})
  36. .done(function (prediction) {
  37. if (prediction.duration.mean != 0) {
  38. me.agent.timelineDataset.update({
  39. id: id + "_predMean0",
  40. start: time,
  41. end: new Date(time).getTime() + prediction.duration.mean,
  42. group: me.agent.id,
  43. type: 'range',
  44. content: "",
  45. subgroup: me.agent.usedSubgroups[type],
  46. className: 'prediction'
  47. });
  48. }
  49. me.jobs.id[id].prediction = prediction;
  50. });
  51. if (this.jobs.type.open[type] === undefined) {this.jobs.type.open[type] = {}}
  52. this.jobs.type.open[type][id] = time;
  53. this.openJobs[id] = this.jobs.id[id];
  54. var addQuery = [{id:id, start:time, content:"started: "+ type, group:this.agent.id, subgroup: this.agent.usedSubgroups[type]}];
  55. this.agent.timelineDataset.add(addQuery);
  56. this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}).done();
  57. };
  58. JobManager.prototype.finish = function(id, type, time) {
  59. var me = this;
  60. // finish the job.
  61. this.agent.rpc.request(type, {method:'finish', params:{
  62. agentId: this.agent.id,
  63. time: time,
  64. jobId: id
  65. }})
  66. .done(function (reply) {
  67. var prediction = reply.prediction;
  68. var originalPrediction = me.jobs.id[id].prediction;
  69. me.jobs.id[id].elapsedTime = reply.elapsedTime;
  70. me.jobs.id[id].elapsedTimeWithPause = reply.elapsedTimeWithPause;
  71. me.updateDataSetsFinish(id, type, time, me.jobs.id[id].prediction);
  72. graph2dDataset.push({x: time, y: reply.duration.duration/3600000 ,group: type + '_duration', type: type});
  73. graph2dDataset.push({x: time, y: reply.duration.durationWithPause/3600000 ,group: type + '_durationWithPause', type: type});
  74. graph2dDataset.push({x: time, y: reply.duration.durationWithStartup/3600000 ,group: type + '_durationWithStartup', type: type});
  75. graph2dDataset.push({x: time, y: reply.duration.durationWithBoth/3600000 ,group: type + '_durationWithBoth', type: type});
  76. graph2dDataset.push({x: time, y: prediction.duration.mean/3600000 ,group: type + '_pred_duration', type: type});
  77. graph2dDataset.push({x: time, y: prediction.durationWithPause.mean/3600000 ,group: type + '_pred_durationWithPause', type: type});
  78. graph2dDataset.push({x: time, y: prediction.durationWithStartup.mean/3600000 ,group: type + '_pred_durationWithStartup', type: type});
  79. graph2dDataset.push({x: time, y: prediction.durationWithBoth.mean/3600000 ,group: type + '_pred_durationWithBoth', type: type});
  80. graph2dDataset.push({x: time, y: (prediction.duration.mean + prediction.duration.std)/3600000 ,group: type + '_pred_duration_std_higher', type: type});
  81. graph2dDataset.push({x: time, y: (prediction.durationWithPause.mean + prediction.durationWithPause.std)/3600000 ,group: type + '_pred_durationWithPause_std_higher', type: type});
  82. graph2dDataset.push({x: time, y: (prediction.durationWithStartup.mean + prediction.durationWithStartup.std)/3600000 ,group: type + '_pred_durationWithStartup_std_higher', type: type});
  83. graph2dDataset.push({x: time, y: (prediction.durationWithBoth.mean + prediction.durationWithBoth.std)/3600000 ,group: type + '_pred_durationWithBoth_std_higher', type: type});
  84. graph2dDataset.push({x: time, y: (prediction.duration.mean - prediction.duration.std)/3600000 ,group: type + '_pred_duration_std_lower', type: type});
  85. graph2dDataset.push({x: time, y: (prediction.durationWithPause.mean - prediction.durationWithPause.std)/3600000 ,group: type + '_pred_durationWithPause_std_lower', type: type});
  86. graph2dDataset.push({x: time, y: (prediction.durationWithStartup.mean - prediction.durationWithStartup.std)/3600000 ,group: type + '_pred_durationWithStartup_std_lower', type: type});
  87. graph2dDataset.push({x: time, y: (prediction.durationWithBoth.mean - prediction.durationWithBoth.std)/3600000 ,group: type + '_pred_durationWithBoth_std_lower', type: type});
  88. graph2dDataset.push({x: time, y: originalPrediction.duration.mean/3600000 ,group: type + '_pred_duration_original', type: type});
  89. graph2dDataset.push({x: time, y: originalPrediction.durationWithPause.mean/3600000 ,group: type + '_pred_durationWithPause_original', type: type});
  90. graph2dDataset.push({x: time, y: originalPrediction.durationWithStartup.mean/3600000 ,group: type + '_pred_durationWithStartup_original', type: type});
  91. graph2dDataset.push({x: time, y: originalPrediction.durationWithBoth.mean/3600000 ,group: type + '_pred_durationWithBoth_original', type: type});
  92. });
  93. delete this.jobs.type.open[type][id];
  94. delete this.openJobs[id];
  95. if (this.jobs.type.closed[type] === undefined) {this.jobs.type.closed[type] = {}}
  96. this.jobs.type.closed[type][id] = time;
  97. };
  98. JobManager.prototype.update = function(id, type, time, operation) {
  99. var me = this;
  100. var eventId = uuid();
  101. if (operation == 'endOfDay' || operation == 'startOfDay') {
  102. for (var jobId in this.openJobs) {
  103. if (this.openJobs.hasOwnProperty(jobId)) {
  104. var type = this.openJobs[jobId].type;
  105. this.agent.rpc.request(type, {method:'update', params:{
  106. agentId: this.agent.id,
  107. time: time,
  108. jobId: jobId,
  109. operation: operation
  110. }})
  111. .done(function (reply) {
  112. me.jobs.id[reply.jobId].elapsedTime = reply.elapsedTime;
  113. me.jobs.id[reply.jobId].elapsedTimeWithPause = reply.elapsedTimeWithPause;
  114. me.updateDataSetsOperation(reply.jobId, reply.type, time, operation, eventId);
  115. });
  116. }
  117. }
  118. }
  119. else {
  120. this.agent.rpc.request(type, {method:'update', params:{
  121. agentId: this.agent.id,
  122. time: time,
  123. jobId: id,
  124. operation: operation
  125. }})
  126. .done(function (reply) {
  127. me.jobs.id[id].elapsedTime = reply.elapsedTime;
  128. me.jobs.id[id].elapsedTimeWithPause = reply.elapsedTimeWithPause;
  129. me.updateDataSetsOperation(id, type, time, operation, eventId);
  130. });
  131. }
  132. };
  133. JobManager.prototype.updateDataSetsOperation = function(id, type, time, operation, eventId) {
  134. switch (operation) {
  135. case 'pause':
  136. this.updateDataSetsPause(id, type, time, operation, this.jobs.id[id].prediction, eventId);
  137. break;
  138. case 'endOfDay':
  139. this.updateDataSetsPause(id, type, time, operation, this.jobs.id[id].prediction, eventId);
  140. break;
  141. case 'startOfDay':
  142. this.updateDataSetsResume(id, type, time, operation, this.jobs.id[id].prediction, eventId);
  143. break;
  144. case 'resume':
  145. this.updateDataSetsResume(id, type, time, operation, this.jobs.id[id].prediction, eventId);
  146. break;
  147. }
  148. };
  149. JobManager.prototype.updateDataSetsFinish = function(id, type, time, prediction) {
  150. var updateQuery = [];
  151. var field = 'duration';
  152. var elapsedTime = this.jobs.id[id].elapsedTime;
  153. // gather statistic indicator data
  154. if (this.jobs.id[id].pauseCount > 0) {
  155. field = 'durationWithPause';
  156. elapsedTime = this.jobs.id[id].elapsedTimeWithPause;
  157. }
  158. // generate indicator
  159. var predictedTimeLeft = 0;
  160. if (prediction[field].mean != 0) {
  161. predictedTimeLeft = prediction[field].mean - elapsedTime;
  162. this.updatePredictionOnFinish(id, type, time, prediction[field], elapsedTime);
  163. }
  164. updateQuery.push({
  165. id: id,
  166. end: time,
  167. content: type,
  168. type: 'range',
  169. className: 'finished',
  170. style: 'background-color: ' + this.generateColors(predictedTimeLeft, elapsedTime) + ' !important;'
  171. });
  172. this.agent.freeSubgroup(type);
  173. this.agent.timelineDataset.update(updateQuery);
  174. this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}}).done();
  175. };
  176. JobManager.prototype.updateDataSetsPause = function(id, type, time, operation, prediction, eventId) {
  177. var updateQuery = [];
  178. var image = '<img src="./images/control_pause.png" class="icon"/>';
  179. var flagId = id + "_pauseNotifier" + eventId;
  180. var title = 'Calling RAO for possible NC';
  181. // this causes there to be only one flag for the end of day as well as a moon icon
  182. if (operation == 'endOfDay') {
  183. // don't end-of-day jobs twice
  184. if (this.jobs.id[id].endOfDay == true) {
  185. return;
  186. }
  187. this.jobs.id[id].endOfDay = true;
  188. image = '<img src="./images/moon.png" class="icon"/>';
  189. flagId = id + "endOfDayNotifier" + eventId;
  190. title = "End of working day"
  191. }
  192. else {
  193. this.jobs.id[id].pauseCount += 1;
  194. this.jobs.id[id].pauseAreaID = this.agent.id + "_" + "_pauseArea_" + eventId;
  195. var shadedPauseArea = {
  196. id: this.jobs.id[id].pauseAreaID,
  197. start: time,
  198. end: time,
  199. content: '',
  200. group: this.agent.id,
  201. subgroup: this.agent.usedSubgroups[type],
  202. className: 'pausedArea',
  203. title: 'Job paused.'
  204. };
  205. updateQuery.push(shadedPauseArea);
  206. }
  207. var field = 'duration';
  208. var elapsedTime = this.jobs.id[id].elapsedTime;
  209. if (this.jobs.id[id].pauseCount > 0) {
  210. field = 'durationWithPause';
  211. elapsedTime = this.jobs.id[id].elapsedTimeWithPause;
  212. }
  213. updateQuery.push({id: id, end: time, content: type, type: 'range'});
  214. var imageUpdate = {
  215. id: flagId,
  216. start: time,
  217. end: time,
  218. content: image,
  219. group: this.agent.id,
  220. subgroup: this.agent.usedSubgroups[type],
  221. className: 'pause',
  222. title: title
  223. };
  224. if (this.agent.id == "Paolo") {
  225. imageUpdate.title = "Going to inspect possible NC.";
  226. }
  227. updateQuery.push(imageUpdate);
  228. var predictedTimeLeft = prediction[field].mean - elapsedTime;
  229. var predictionExists = prediction[field].mean != 0;
  230. // update the predicted line if the job is not ALREADY pauseds
  231. if (predictedTimeLeft > 0 && predictionExists == true && this.jobs.id[id].paused != true) {
  232. updateQuery.push({
  233. id: id + "_predMean" + this.jobs.id[id].predictionCounter,
  234. end: time,
  235. group: this.agent.id,
  236. className: 'prediction'
  237. })
  238. }
  239. // set the status to paused if needed
  240. if (operation != 'endOfDay') {
  241. this.jobs.id[id].paused = true;
  242. }
  243. this.agent.timelineDataset.update(updateQuery);
  244. this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}})
  245. };
  246. JobManager.prototype.updateDataSetsResume = function(id, type, time, operation, prediction, eventId) {
  247. var updateQuery = [];
  248. var image = '<img src="./images/control_play.png" class="icon"/>';
  249. var flagId = id + "_resumeNotifier" + eventId;
  250. // this causes there to be only one flag for the start of day as well as a sun icon
  251. if (operation == 'startOfDay') {
  252. // don't start-of-day jobs twice
  253. if (this.jobs.id[id].endOfDay == false) {
  254. return;
  255. }
  256. this.jobs.id[id].endOfDay = false;
  257. image = '<img src="./images/sun.png" class="icon"/>';
  258. flagId = id + "startOfDayNotifier_" + eventId;
  259. }
  260. else {
  261. updateQuery.push({id: this.jobs.id[id].pauseAreaID, end: time, content: '', type: 'range'});
  262. this.jobs.id[id].pauseAreaID = "";
  263. this.jobs.id[id].pauseCount += 1;
  264. }
  265. var field = 'duration';
  266. var elapsedTime = this.jobs.id[id].elapsedTime;
  267. if (this.jobs.id[id].pauseCount > 0) {
  268. field = 'durationWithPause';
  269. elapsedTime = this.jobs.id[id].elapsedTimeWithPause;
  270. }
  271. updateQuery.push({id: id, end: time, content: type, type: 'range'});
  272. updateQuery.push({
  273. id: flagId,
  274. start: time,
  275. end: time,
  276. content: image,
  277. group: this.agent.id,
  278. subgroup: this.agent.usedSubgroups[type],
  279. className: 'pause',
  280. title: 'Resuming job'
  281. });
  282. var predictedTimeLeft = prediction[field].mean - elapsedTime;
  283. // no not increase the prediction line at the start of the day if the job is PAUSED
  284. if (predictedTimeLeft > 0 && !(operation == 'startOfDay' && this.jobs.id[id].paused == true)) {
  285. this.jobs.id[id].predictionCounter += 1;
  286. updateQuery.push({
  287. id: id + "_predMean" + this.jobs.id[id].predictionCounter,
  288. start: time,
  289. end: new Date(time).getTime() + predictedTimeLeft,
  290. group: this.agent.id,
  291. content: "",
  292. subgroup: this.agent.usedSubgroups[type],
  293. type: 'range',
  294. className: 'prediction'
  295. });
  296. }
  297. // resume if needed
  298. if (operation != 'startOfDay'){
  299. this.jobs.id[id].paused = false;
  300. }
  301. this.agent.timelineDataset.update(updateQuery);
  302. this.agent.rpc.request("agentGenerator", {method: 'updateOpenJobs', params:{jobId: id, time: time}})
  303. };
  304. JobManager.prototype.updatePredictionOnFinish = function(id,type,time,prediction,elapsedTime) {
  305. if (prediction.mean != 0) {
  306. var predictedTimeLeft = prediction.mean - elapsedTime;
  307. if (predictedTimeLeft > 0) {
  308. this.agent.timelineDataset.remove({id: id + "_predMean" + this.jobs.id[id].predictionCounter})
  309. }
  310. }
  311. };
  312. JobManager.prototype.updateJobs = function(time, skipId) {
  313. var updateQuery = [];
  314. for (var jobId in this.openJobs) {
  315. if (this.openJobs.hasOwnProperty(jobId) && jobId != skipId) {
  316. var type = this.openJobs[jobId].type;
  317. var prediction = this.openJobs[jobId].prediction;
  318. var predictedTimeLeft = 0;
  319. // never been paused before
  320. if (this.jobs.id[jobId].pauseCount == 0) {
  321. if (prediction !== null && prediction.duration !== null) {
  322. predictedTimeLeft = prediction.duration.mean - this.jobs.id[jobId].elapsedTime;
  323. }
  324. }
  325. else {
  326. if (prediction !== null && prediction.durationWithPause !== null) {
  327. predictedTimeLeft = prediction.durationWithPause.mean - this.jobs.id[jobId].elapsedTimeWithPause;
  328. }
  329. }
  330. updateQuery.push({id: jobId, end: time, content: type, type: 'range'});
  331. if (this.openJobs[jobId].paused == true) {
  332. updateQuery.push({id: this.openJobs[jobId].pauseAreaID, end: time});
  333. }
  334. }
  335. }
  336. this.agent.timelineDataset.update(updateQuery);
  337. };
  338. JobManager.prototype.generateColors = function(predictedTime, elapsedTime) {
  339. var ratio = (elapsedTime + predictedTime) / elapsedTime;
  340. if (ratio > 1) {
  341. ratio = Math.min(2,ratio) - 1; // 2 -- 1
  342. var rgb = HSVToRGB(94/360,ratio*0.6 + 0.2,1);
  343. }
  344. else {
  345. ratio = Math.max(0.5,ratio) - 0.5; // 1 -- 0.5
  346. var rgb = HSVToRGB(40/360,(1-(2*ratio))*0.6 + 0.1 ,1);
  347. }
  348. return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
  349. };
  350. function HSVToRGB(h, s, v) {
  351. var r, g, b;
  352. var i = Math.floor(h * 6);
  353. var f = h * 6 - i;
  354. var p = v * (1 - s);
  355. var q = v * (1 - f * s);
  356. var t = v * (1 - (1 - f) * s);
  357. switch (i % 6) {
  358. case 0: r = v, g = t, b = p; break;
  359. case 1: r = q, g = v, b = p; break;
  360. case 2: r = p, g = v, b = t; break;
  361. case 3: r = p, g = q, b = v; break;
  362. case 4: r = t, g = p, b = v; break;
  363. case 5: r = v, g = p, b = q; break;
  364. }
  365. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  366. }
  367. function RGBToHSV (red,green,blue) {
  368. red=red/255; green=green/255; blue=blue/255;
  369. var minRGB = Math.min(red,Math.min(green,blue));
  370. var maxRGB = Math.max(red,Math.max(green,blue));
  371. // Black-gray-white
  372. if (minRGB == maxRGB) {
  373. return {h:0,s:0,v:minRGB};
  374. }
  375. // Colors other than black-gray-white:
  376. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  377. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  378. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  379. var saturation = (maxRGB - minRGB)/maxRGB;
  380. var value = maxRGB;
  381. return {h:hue,s:saturation,v:value};
  382. }