2 * This js establishes a websockets connection with the API Server.
5 /* Subscribe to websockets event log. Do nothing if already connected. */
6 function subscribeToEventLog () {
7 // if websockets are not supported by browser, do not subscribe for events
8 websocketsSupported = ('WebSocket' in window);
9 if (websocketsSupported == false) {
13 // check if websocket connection is already stored on the window
14 event_log_disp = $(window).data("arv-websocket");
15 if (event_log_disp == null) {
16 // need to create new websocket and event log dispatcher
17 websocket_url = $('meta[name=arv-websocket-url]').attr("content");
18 if (websocket_url == null)
21 event_log_disp = new WebSocket(websocket_url);
23 event_log_disp.onopen = onEventLogDispatcherOpen;
24 event_log_disp.onmessage = onEventLogDispatcherMessage;
26 // store websocket in window to allow reuse when multiple divs subscribe for events
27 $(window).data("arv-websocket", event_log_disp);
31 /* Send subscribe message to the websockets server. Without any filters
32 arguments, this subscribes to all events */
33 function onEventLogDispatcherOpen(event) {
34 this.send('{"method":"subscribe"}');
37 /* Trigger event for all applicable elements waiting for this event */
38 function onEventLogDispatcherMessage(event) {
39 parsedData = JSON.parse(event.data);
40 object_uuid = parsedData.object_uuid;
46 // if there are any listeners for this object uuid or "all", trigger the event
47 matches = ".arv-log-event-listener[data-object-uuid=\"" + object_uuid + "\"],.arv-log-event-listener[data-object-uuids~=\"" + object_uuid + "\"],.arv-log-event-listener[data-object-uuid=\"all\"],.arv-log-event-listener[data-object-kind=\"" + parsedData.object_kind + "\"]";
48 $(matches).trigger('arv-log-event', parsedData);
51 /* Automatically connect if there are any elements on the page that want to
52 receive event log events. */
53 $(document).on('ajax:complete ready', function() {
54 var a = $('.arv-log-event-listener');
56 subscribeToEventLog();
60 /* Assumes existence of:
61 window.jobGraphData = [];
62 window.jobGraphSeries = [];
63 window.jobGraphMaxima = {};
65 function processLogLineForChart( logLine ) {
67 var match = logLine.match(/(\S+) (\S+) (\S+) (\S+) stderr crunchstat: (\S+) (.*) -- interval (.*)/);
69 // the timestamp comes first
70 var timestamp = match[1].replace('_','T');
71 // for the series use the first word after 'crunchstat:'
72 var series = match[5];
73 // and append the task number (the 4th term)
74 series += '-' + match[4]
75 if( $.inArray( series, jobGraphSeries) < 0 ) {
76 jobGraphSeries.push(series);
77 jobGraphMaxima[series] = null;
78 window.recreate = true;
80 var intervalData = match[7].trim().split(' ');
81 var dt = parseFloat(intervalData[0]);
83 for(var i=2; i < intervalData.length; i += 2 ) {
84 dsum += parseFloat(intervalData[i]);
87 if( datum !== 0 && ( jobGraphMaxima[series] === null || jobGraphMaxima[series] < datum ) ) {
88 if( isJobSeriesRescalable(series) ) {
89 // use old maximum to get a scale conversion
90 var scaleConversion = jobGraphMaxima[series]/datum;
91 // set new maximum and rescale the series
92 jobGraphMaxima[series] = datum;
93 rescaleJobGraphSeries( series, scaleConversion );
95 // and special calculation for cpus
96 if( /^cpu-/.test(series) ) {
97 // divide the stat by the number of cpus
98 var cpuCountMatch = match[6].match(/(\d+) cpus/);
100 datum = datum / cpuCountMatch[1];
105 var scaledDatum = null;
106 if( isJobSeriesRescalable(series) && jobGraphMaxima[series] !== null && jobGraphMaxima[series] !== 0 ) {
107 scaledDatum = datum/jobGraphMaxima[series]
111 // identify x axis point, searching from the end of the array (most recent)
113 for( var i = jobGraphData.length - 1; i >= 0; i-- ) {
114 if( jobGraphData[i]['t'] === timestamp ) {
116 jobGraphData[i][series] = scaledDatum;
117 jobGraphData[i]['raw-'+series] = match[7];
119 } else if( jobGraphData[i]['t'] < timestamp ) {
120 // we've gone far enough back in time and this data is supposed to be sorted
124 // index counter from previous loop will have gone one too far, so add one
127 // create a new x point for this previously unrecorded timestamp
128 var entry = { 't': timestamp };
129 entry[series] = scaledDatum;
130 entry['raw-'+series] = match[7];
131 jobGraphData.splice( insertAt, 0, entry );
133 // now let's see about "scrolling" the graph, dropping entries that are too old (>10 minutes)
134 while( jobGraphData.length > 0
135 && (Date.parse( jobGraphData[0]['t'] ).valueOf() + 10*60000 < Date.parse( jobGraphData[jobGraphData.length-1]['t'] ).valueOf()) ) {
136 shifted.push(jobGraphData.shift());
138 if( shifted.length > 0 ) {
139 // from those that we dropped, are any of them maxima? if so we need to rescale
140 jobGraphSeries.forEach( function(series) {
141 // test that every shifted entry in this series was either not a number (in which case we don't care)
142 // or else approximately (to 2 decimal places) smaller than the scaled maximum (i.e. 1),
143 // because otherwise we just scrolled off something that was a maximum point
144 // and so we need to recalculate a new maximum point by looking at all remaining displayed points in the series
145 if( isJobSeriesRescalable(series) && jobGraphMaxima[series] !== null
146 && !shifted.every( function(e) { return( !$.isNumeric(e[series]) || e[series].toFixed(2) < 1.0 ) } ) ) {
147 // check the remaining displayed points and find the new (scaled) maximum
148 var seriesMax = null;
149 jobGraphData.forEach( function(entry) {
150 if( $.isNumeric(entry[series]) && (seriesMax === null || entry[series] > seriesMax)) {
151 seriesMax = entry[series];
154 if( seriesMax !== null && seriesMax !== 0 ) {
155 // set new actual maximum using the new maximum as the conversion conversion and rescale the series
156 jobGraphMaxima[series] *= seriesMax;
157 var scaleConversion = 1/seriesMax;
158 rescaleJobGraphSeries( series, scaleConversion );
161 // we no longer have any data points displaying for this series
162 jobGraphMaxima[series] = null;
167 // add a 10 minute old null data point to keep the chart honest if the oldest point is less than 9.5 minutes old
168 if( jobGraphData.length > 0
169 && (Date.parse( jobGraphData[0]['t'] ).valueOf() + 9.5*60000 > Date.parse( jobGraphData[jobGraphData.length-1]['t'] ).valueOf()) ) {
170 var tenMinutesBefore = (new Date(Date.parse( jobGraphData[jobGraphData.length-1]['t'] ).valueOf() - 600*1000)).toISOString().replace('Z','');
171 jobGraphData.unshift( { 't': tenMinutesBefore } );
174 window.redraw = true;
177 console.log( 'Ignoring error trying to process log line: ' + err);
181 function createJobGraph(elementName) {
183 var emptyGraph = false;
184 if( jobGraphData.length === 0 ) {
185 // If there is no data we still want to show an empty graph,
186 // so add an empty datum and placeholder series to fool it into displaying itself.
187 // Note that when finally a new series is added, the graph will be recreated anyway.
188 jobGraphData.push( {} );
189 jobGraphSeries.push( '' );
192 var graphteristics = {
193 element: elementName,
196 yLabelFormat: function () { return ''; },
198 ykeys: jobGraphSeries,
199 labels: jobGraphSeries,
203 hoverCallback: function(index, options, content) {
204 var s = "<div class='morris-hover-row-label'>";
205 s += options.data[index][options.xkey];
207 for( i = 0; i < options.ykeys.length; i++ ) {
208 var series = options.ykeys[i];
209 var datum = options.data[index][series];
210 s += "<div class='morris-hover-point' style='color: ";
211 s += options.lineColors[i];
213 s += options.labels[i];
215 if ( !(typeof datum === 'undefined') ) {
216 if( isJobSeriesRescalable( series ) ) {
217 datum *= jobGraphMaxima[series];
219 if( parseFloat(datum) !== 0 ) {
220 if( /^cpu-/.test(series) ){
221 datum = $.number(datum * 100, 1) + '%';
222 } else if( datum < 10 ) {
223 datum = $.number(datum, 2);
225 datum = $.number(datum);
227 datum += ' (' + options.data[index]['raw-'+series] + ')';
239 graphteristics['axes'] = false;
240 graphteristics['parseTime'] = false;
241 graphteristics['hideHover'] = 'always';
243 window.jobGraph = Morris.Line( graphteristics );
250 function rescaleJobGraphSeries( series, scaleConversion ) {
251 if( isJobSeriesRescalable() ) {
252 $.each( jobGraphData, function( i, entry ) {
253 if( entry[series] !== null && entry[series] !== undefined ) {
254 entry[series] *= scaleConversion;
260 // that's right - we never do this for the 'cpu' series, which will always be between 0 and 1 anyway
261 function isJobSeriesRescalable( series ) {
262 return !/^cpu-/.test(series);
265 $(document).on('arv-log-event', '#log_graph_div', function(event, eventData) {
266 if( eventData.properties.text ) {
267 processLogLineForChart( eventData.properties.text );
271 $(document).on('ready', function(){
272 window.recreate = false;
273 window.redraw = false;
274 setInterval( function() {
276 window.recreate = false;
277 window.redraw = false;
278 // series have changed, draw entirely new graph
279 $('#log_graph_div').html('');
280 createJobGraph('log_graph_div');
281 } else if( redraw ) {
282 window.redraw = false;
283 jobGraph.setData( jobGraphData );