// TODO: this code needs a load of cleaning up and documenting, // and feature additions and general improvement and unrubbishing // Item types returned by the engine var ITEM_EVENT = 1; var ITEM_ENTER = 2; var ITEM_LEAVE = 3; var ITEM_ATTRIBUTE = 4; function hslToRgb(h, s, l, a) { var r, g, b; if (s == 0) { r = g = b = l; } else { function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return 'rgba(' + Math.floor(r * 255) + ',' + Math.floor(g * 255) + ',' + Math.floor(b * 255) + ',' + a + ')'; } function new_colour(id) { var hs = [0, 1/3, 2/3, 1/4, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5]; var ss = [1, 0.5]; var ls = [0.8, 0.6, 0.9, 0.7]; return hslToRgb(hs[id % hs.length], ss[Math.floor(id / hs.length) % ss.length], ls[Math.floor(id / (hs.length*ss.length)) % ls.length], 1); } var g_used_colours = {}; var g_data; function refresh() { if (1) refresh_live(); else refresh_jsonp('../../../binaries/system/profile2.jsonp'); } function concat_events(data) { var events = []; data.events.forEach(function(ev) { ev.pop(); // remove the dummy null markers Array.prototype.push.apply(events, ev); }); return events; } function refresh_jsonp(url) { var script = document.createElement('script'); window.profileDataCB = function(data) { script.parentNode.removeChild(script); var threads = []; data.threads.forEach(function(thread) { var canvas = $(''); threads.push({'name': thread.name, 'data': { 'events': concat_events(thread.data) }, 'canvas': canvas.get(0)}); }); g_data = { 'threads': threads }; var range = {'seconds': 0.05}; rebuild_canvases(); update_display(range); }; script.src = url; document.body.appendChild(script); } function refresh_live() { $.ajax({ url: 'http://127.0.0.1:8000/overview', dataType: 'json', success: function (data) { var threads = []; data.threads.forEach(function(thread) { var canvas = $(''); threads.push({'name': thread.name, 'canvas': canvas.get(0)}); }); var callback_data = { 'threads': threads, 'completed': 0 }; threads.forEach(function(thread) { refresh_thread(thread, callback_data); }); }, error: function (jqXHR, textStatus, errorThrown) { alert('Failed to connect to server ("'+textStatus+'")'); } }); } function refresh_thread(thread, callback_data) { $.ajax({ url: 'http://127.0.0.1:8000/query', dataType: 'json', data: { 'thread': thread.name }, success: function (data) { data.events = concat_events(data); thread.data = data; if (++callback_data.completed == callback_data.threads.length) { g_data = { 'threads': callback_data.threads }; //var range = {'numframes': 5}; var range = {'seconds': 0.05}; rebuild_canvases(); update_display(range); } }, error: function (jqXHR, textStatus, errorThrown) { alert('Failed to connect to server ("'+textStatus+'")'); } }); } function rebuild_canvases() { g_data.canvas_frames = $('').get(0); g_data.canvas_zoom = $('').get(0); g_data.text_output = $('
').get(0); set_frames_zoom_handlers(g_data.canvas_frames); set_tooltip_handlers(g_data.canvas_frames); $('#timelines').empty(); $('#timelines').append(g_data.canvas_frames); g_data.threads.forEach(function(thread) { $('#timelines').append($(thread.canvas)); }); $('#timelines').append(g_data.canvas_zoom); $('#timelines').append(g_data.text_output); } function update_display(range) { $(g_data.text_output).empty(); var main_events = g_data.threads[0].data.events; var processed_main = g_data.threads[0].processed_events = compute_intervals(main_events, range); // display_top_items(main_events, g_data.text_output); display_frames(processed_main, g_data.canvas_frames); display_events(processed_main, g_data.canvas_frames); $(g_data.threads[0].canvas).unbind(); $(g_data.canvas_zoom).unbind(); display_hierarchy(processed_main, processed_main, g_data.threads[0].canvas, {}, undefined); set_zoom_handlers(processed_main, processed_main, g_data.threads[0].canvas, g_data.canvas_zoom); set_tooltip_handlers(g_data.threads[0].canvas); set_tooltip_handlers(g_data.canvas_zoom); g_data.threads.slice(1).forEach(function(thread) { var processed_data = compute_intervals(thread.data.events, {'tmin': processed_main.tmin, 'tmax': processed_main.tmax}); $(thread.canvas).unbind(); display_hierarchy(processed_main, processed_data, thread.canvas, {}, undefined); set_zoom_handlers(processed_main, processed_data, thread.canvas, g_data.canvas_zoom); set_tooltip_handlers(thread.canvas); }); } function display_top_items(data, output) { var items = {}; for (var i = 0; i < data.length; ++i) { var type = data[i][0]; if (!(type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE)) continue; var id = data[i][2]; if (!items[id]) items[id] = { 'count': 0 }; items[id].count++; } var topitems = []; for (var k in items) topitems.push([k, items[k].count]); topitems.sort(function(a, b) { return b[1] - a[1]; }); topitems.splice(16); topitems.forEach(function(item) { output.appendChild(document.createTextNode(item[1] + 'x -- ' + item[0] + '\n')); }); output.appendChild(document.createTextNode('(' + data.length + ' items)')); } function compute_intervals(data, range) { var start, end; var tmin, tmax; var frames = []; var last_frame_time = undefined; for (var i = 0; i < data.length; ++i) { if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') { var t = data[i][1]; if (last_frame_time) frames.push({'t0': last_frame_time, 't1': t}); last_frame_time = t; } } if (range.numframes) { for (var i = data.length - 1; i > 0; --i) { if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') { end = i; break; } } var framesfound = 0; for (var i = end - 1; i > 0; --i) { if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart') { start = i; if (++framesfound == range.numframes) break; } } tmin = data[start][1]; tmax = data[end][1]; } else if (range.seconds) { var end = data.length - 1; for (var i = end; i > 0; --i) { var type = data[i][0]; if (type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) { tmax = data[i][1]; break; } } tmin = tmax - range.seconds; for (var i = end; i > 0; --i) { var type = data[i][0]; if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin) break; start = i; } } else { start = 0; end = data.length - 1; tmin = range.tmin; tmax = range.tmax; for (var i = data.length-1; i > 0; --i) { var type = data[i][0]; if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmax) { end = i; break; } } for (var i = end; i > 0; --i) { var type = data[i][0]; if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin) break; start = i; } // Move the start/end outwards by another frame, so we don't lose data at the edges while (start > 0) { --start; if (data[start][0] == ITEM_EVENT && data[start][2] == '__framestart') break; } while (end < data.length-1) { ++end; if (data[end][0] == ITEM_EVENT && data[end][2] == '__framestart') break; } } var num_colours = 0; var events = []; // Read events for the entire data period (not just start..end) var lastWasEvent = false; for (var i = 0; i < data.length; ++i) { if (data[i][0] == ITEM_EVENT) { events.push({'t': data[i][1], 'id': data[i][2]}); lastWasEvent = true; } else if (data[i][0] == ITEM_ATTRIBUTE) { if (lastWasEvent) { if (!events[events.length-1].attrs) events[events.length-1].attrs = []; events[events.length-1].attrs.push(data[i][1]); } } else { lastWasEvent = false; } } var intervals = []; // Read intervals from the focused data period (start..end) var stack = []; var lastT = 0; var lastWasEvent = false; for (var i = start; i <= end; ++i) { if (data[i][0] == ITEM_EVENT) { // if (data[i][1] < lastT) // console.log('Time went backwards: ' + (data[i][1] - lastT)); lastT = data[i][1]; lastWasEvent = true; } else if (data[i][0] == ITEM_ENTER) { // if (data[i][1] < lastT) // console.log('Time went backwards: ' + (data[i][1] - lastT)); stack.push({'t0': data[i][1], 'id': data[i][2]}); lastT = data[i][1]; lastWasEvent = false; } else if (data[i][0] == ITEM_LEAVE) { // if (data[i][1] < lastT) // console.log('Time went backwards: ' + (data[i][1] - lastT)); lastT = data[i][1]; lastWasEvent = false; if (!stack.length) continue; var interval = stack.pop(); if (data[i][2] != interval.id && data[i][2] != '(ProfileStop)') alert('inconsistent interval ids ('+interval.id+' / '+data[i][2]+')'); if (!g_used_colours[interval.id]) g_used_colours[interval.id] = new_colour(num_colours++); interval.colour = g_used_colours[interval.id]; interval.t1 = data[i][1]; interval.duration = interval.t1 - interval.t0; interval.depth = stack.length; intervals.push(interval); } else if (data[i][0] == ITEM_ATTRIBUTE) { if (!lastWasEvent && stack.length) { if (!stack[stack.length-1].attrs) stack[stack.length-1].attrs = []; stack[stack.length-1].attrs.push(data[i][1]); } } } return { 'frames': frames, 'events': events, 'intervals': intervals, 'tmin': tmin, 'tmax': tmax }; } function time_label(t) { if (t > 1e-3) return (t * 1e3).toFixed(2) + 'ms'; else return (t * 1e6).toFixed(2) + 'us'; } function display_frames(data, canvas) { canvas._tooltips = []; var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); var xpadding = 8; var padding_top = 40; var width = canvas.width - xpadding*2; var height = canvas.height - padding_top - 4; var tmin = data.frames[0].t0; var tmax = data.frames[data.frames.length-1].t1; var dx = width / (tmax-tmin); canvas._zoomData = { 'x_to_t': function(x) { return tmin + (x - xpadding) / dx; }, 't_to_x': function(t) { return (t - tmin) * dx + xpadding; } }; // var y_per_second = 1000; var y_per_second = 100; [16, 33, 200, 500].forEach(function(t) { var y1 = canvas.height; var y0 = y1 - t/1000*y_per_second; var y = Math.floor(y0) + 0.5; ctx.beginPath(); ctx.moveTo(xpadding, y); ctx.lineTo(canvas.width - xpadding, y); ctx.strokeStyle = 'rgb(255, 0, 0)'; ctx.stroke(); ctx.fillStyle = 'rgb(255, 0, 0)'; ctx.fillText(t+'ms', 0, y-2); }); ctx.strokeStyle = 'rgb(0, 0, 0)'; ctx.fillStyle = 'rgb(255, 255, 255)'; for (var i = 0; i < data.frames.length; ++i) { var frame = data.frames[i]; var duration = frame.t1 - frame.t0; var x0 = xpadding + dx*(frame.t0 - tmin); var x1 = x0 + dx*duration; var y1 = canvas.height; var y0 = y1 - duration*y_per_second; ctx.beginPath(); ctx.rect(x0, y0, x1-x0, y1-y0); ctx.stroke(); canvas._tooltips.push({ 'x0': x0, 'x1': x1, 'y0': y0, 'y1': y1, 'text': function(frame, duration) { return function() { var t = 'Frame