Index: source/tools/profiler2/Profiler2Report.js
===================================================================
--- source/tools/profiler2/Profiler2Report.js
+++ source/tools/profiler2/Profiler2Report.js
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Wildfire Games.
+// Copyright (C) 2019 Wildfire Games.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -20,407 +20,146 @@
// Profiler2Report module
// Create one instance per profiler report you wish to open.
-// This gives you the interface to access the raw and processed data
+// This gives you the interface to access the raw and processed data.
-var Profiler2Report = function(callback, tryLive, file)
-{
-var outInterface = {};
-
-// Item types returned by the engine
-var ITEM_EVENT = 1;
-var ITEM_ENTER = 2;
-var ITEM_LEAVE = 3;
-var ITEM_ATTRIBUTE = 4;
-
-var g_used_colours = {};
-
-var g_raw_data;
-var g_data;
+export default { };
-function refresh(callback, tryLive, file)
+function set_bounds(obj, time)
{
- if (tryLive)
- refresh_live(callback, file);
- else
- refresh_jsonp(callback, file);
+ if (!obj.start || obj.start > time)
+ obj.start = time;
+ if (!obj.end || obj.end < time)
+ obj.end = time;
}
-outInterface.refresh = refresh;
-function refresh_jsonp(callback, source)
+export class Report
{
- if (!source)
- {
- callback(false);
- return
- }
- var reader = new FileReader();
- reader.onload = function(e)
- {
- refresh_from_jsonp(callback, e.target.result);
- }
- reader.onerror = function(e) {
- alert("Failed to load report file");
- callback(false);
- return;
- }
- reader.readAsText(source);
+ constructor(raw_data)
+ {
+ this.raw_data = raw_data;
+ this.thread = {};
+ this.raw_data.threads.forEach(x => this.parse_thread(x));
+ }
+
+ parse_thread(data)
+ {
+ this.thread[data.name] = {
+ "data": [],
+ "blocks_by_name": {},
+ "frameStart": null,
+ "frameEnd": null,
+ "start": null,
+ "end": null,
+ };
+ data.data.events.slice(1).forEach(rows => this.process_rows(rows, this.thread[data.name]));
+ }
+
+ process_rows(data, thread)
+ {
+ let blocks = [];
+ let current_frame = undefined;
+ data.forEach(row => {
+ if (!row)
+ return;
+
+ if (row[0] == 1) // ITEM_EVENT
+ return; // ignored for now
+
+ if (row[0] == 2) // ITEM_ENTER
+ {
+ if (blocks.length == 0)
+ {
+ // initial block, create a Frame around it
+ current_frame = new Frame(thread);
+ thread.data.push(current_frame);
+ }
+
+ let block = new ProfiledBlock(row[2], row[1], current_frame, blocks.length);
+ blocks.push(block);
+ if (!(block.name in thread.blocks_by_name))
+ thread.blocks_by_name[block.name] = [];
+ thread.blocks_by_name[block.name].push(block);
+ }
+
+ if (row[0] == 3) // ITEM_LEAVE
+ {
+ blocks.pop().end_time = row[1];
+ if (blocks.length == 0)
+ current_frame = null;
+ }
+
+ if (row[0] == 4) // ITEM_ATTRIBUTE
+ {
+ if (row[1].startsWith('turn '))
+ current_frame.ID = +row[1].replace('turn ', '');
+ else if (blocks.length)
+ blocks[blocks.length-1].add_attribute(row[1]);
+ }
+ });
+ }
}
-function refresh_from_jsonp(callback, content)
-{
- 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_raw_data = { 'threads': threads };
- compute_data();
- callback(true);
- };
- script.innerHTML = content;
- document.body.appendChild(script);
-}
-
-function refresh_live(callback, file)
+class ProfiledBlock
{
- $.ajax({
- url: 'http://127.0.0.1:8000/overview',
- dataType: 'json',
- success: function (data) {
- var threads = [];
- data.threads.forEach(function(thread) {
- threads.push({'name': thread.name});
- });
- var callback_data = { 'threads': threads, 'completed': 0 };
-
- threads.forEach(function(thread) {
- refresh_thread(callback, thread, callback_data);
- });
- },
- error: function (jqXHR, textStatus, errorThrown)
- {
- console.log('Failed to connect to server ("'+textStatus+'")');
- callback(false);
- }
- });
+ constructor(name, start, frame, depth)
+ {
+ this.start = start;
+ this.name = name;
+ this.frame = frame;
+ this.depth = depth;
+ this.attribute = null;
+
+ this.frame.add(this);
+ }
+
+ set end_time(et)
+ {
+ this.end = et;
+ if (this.end - this.start < 1e-9)
+ this.end = this.start + 1e-9;
+ this.duration = this.end - this.start;
+ set_bounds(this.frame, this.end);
+ set_bounds(this.frame.report, this.end);
+ }
+
+ add_attribute(attr)
+ {
+ this.attribute = attr;
+ }
}
-function refresh_thread(callback, thread, callback_data)
+class Frame
{
- $.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_raw_data = { 'threads': callback_data.threads };
- compute_data();
- callback(true);
- }
- },
- error: function (jqXHR, textStatus, errorThrown) {
- alert('Failed to connect to server ("'+textStatus+'")');
- }
- });
+ constructor(report)
+ {
+ this.report = report;
+ this.frameID = null;
+ this.blocks = [];
+ this.blocksByDepth = {};
+ this.maxDepth = 0;
+ this.start = null;
+ this.end = null;
+ }
+
+ set ID(id)
+ {
+ this.frameID = id;
+ if (this.report.frameStart === null || this.report.frameStart > id)
+ this.report.frameStart = id;
+ if (this.report.frameEnd === null || this.report.frameEnd < id)
+ this.report.frameEnd = id;
+ }
+
+ add(block)
+ {
+ if (block.depth > this.maxDepth)
+ this.maxDepth = block.depth;
+ this.blocks.push(block);
+ set_bounds(this, block.start);
+ set_bounds(this.report, this.start);
+ if (!this.blocksByDepth[block.depth])
+ this.blocksByDepth[block.depth] = [];
+ this.blocksByDepth[block.depth].push(block);
+ }
}
-
-function compute_data(range)
-{
- g_data = { "threads" : [] };
- g_data_by_frame = { "threads" : [] };
- for (let thread = 0; thread < g_raw_data.threads.length; thread++)
- {
- let processed_data = process_raw_data(g_raw_data.threads[thread].data.events, range );
- if (!processed_data.intervals.length && !processed_data.events.length)
- continue;
-
- g_data.threads[thread] = processed_data;
-
- g_data.threads[thread].intervals_by_type_frame = {};
-
- if (!g_data.threads[thread].frames.length)
- continue
- // compute intervals by types and frames if there are frames.
- for (let type in g_data.threads[thread].intervals_by_type)
- {
- let current_frame = 0;
- g_data.threads[thread].intervals_by_type_frame[type] = [[]];
- for (let i = 0; i < g_data.threads[thread].intervals_by_type[type].length;i++)
- {
- let event = g_data.threads[thread].intervals[g_data.threads[thread].intervals_by_type[type][i]];
- while (current_frame < g_data.threads[thread].frames.length && event.t0 > g_data.threads[thread].frames[current_frame].t1)
- {
- g_data.threads[thread].intervals_by_type_frame[type].push([]);
- current_frame++;
- }
- if (current_frame < g_data.threads[thread].frames.length)
- g_data.threads[thread].intervals_by_type_frame[type][current_frame].push(g_data.threads[thread].intervals_by_type[type][i]);
- }
- }
- };
-}
-
-function process_raw_data(data, range)
-{
- if (!data.length)
- return { 'frames': [], 'events': [], 'intervals': [], 'intervals_by_type' : {}, 'tmin': 0, 'tmax': 0 };
-
- var start, end;
- var tmin, tmax;
-
- var frames = [];
- var last_frame_time_start = undefined;
- var last_frame_time_end = undefined;
-
- var stack = [];
- for (var i = 0; i < data.length; ++i)
- {
- if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
- {
- if (last_frame_time_end)
- frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end});
- last_frame_time_start = data[i][1];
- }
- if (data[i][0] == ITEM_ENTER)
- stack.push(data[i][2]);
- if (data[i][0] == ITEM_LEAVE)
- {
- if (stack[stack.length-1] == 'frame')
- last_frame_time_end = data[i][1];
- stack.pop();
- }
- }
- if(!range)
- {
- range = { "tmin" : data[0][1], "tmax" : data[data.length-1][1] };
- }
- 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 = [];
- var intervals_by_type = {};
-
- // Read intervals from the focused data period (start..end)
- 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 - ENTER went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i]));
-
- 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 - LEAVE went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i]));
-
- lastT = data[i][1];
- lastWasEvent = false;
-
- if (!stack.length)
- continue;
-
- var interval = stack.pop();
-
- 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;
- //console.log(JSON.stringify(interval));
- intervals.push(interval);
- if (interval.id in intervals_by_type)
- intervals_by_type[interval.id].push(intervals.length-1);
- else
- intervals_by_type[interval.id] = [intervals.length-1];
-
- if (interval.id == "Script" && interval.attrs && interval.attrs.length)
- {
- let curT = interval.t0;
- for (let subItem in interval.attrs)
- {
- let sub = interval.attrs[subItem];
- if (sub.search("buffer") != -1)
- continue;
- let newInterv = {};
- newInterv.t0 = curT;
- newInterv.duration = +sub.replace(/.+? ([.0-9]+)us/, "$1")/1000000;
- if (!newInterv.duration)
- continue;
- newInterv.t1 = curT + newInterv.duration;
- curT += newInterv.duration;
- newInterv.id = "Script:" + sub.replace(/(.+?) ([.0-9]+)us/, "$1");
- newInterv.colour = g_used_colours[interval.id];
- newInterv.depth = interval.depth+1;
- intervals.push(newInterv);
- if (newInterv.id in intervals_by_type)
- intervals_by_type[newInterv.id].push(intervals.length-1);
- else
- intervals_by_type[newInterv.id] = [intervals.length-1];
- }
- }
- }
- 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, 'intervals_by_type' : intervals_by_type, 'tmin': tmin, 'tmax': tmax };
-}
-
-outInterface.data = function() { return g_data; };
-outInterface.raw_data = function() { return g_raw_data; };
-outInterface.data_by_frame = function() { return g_data_by_frame; };
-
-refresh(callback, tryLive, file);
-
-return outInterface;
-};
Index: source/tools/profiler2/README.md
===================================================================
--- /dev/null
+++ source/tools/profiler2/README.md
@@ -0,0 +1,9 @@
+This is a graphical interface for Profiler2's output.
+
+It provides 3 screens:
+- A timeline of high-level frames, computed from top-level blocks (so it doesn't actually requires frames to function). This screen can be used to zoom on specific frames.
+- A frame flamegraph with all the blocks, which can also be zoomed in.
+- A "CFD" graph which shows the breakdown of times spent for a specific block.
+
+Highcharts v7.0.2 is used under CC BY-NC 3.0 : https://creativecommons.org/licenses/by-nc/3.0/
+The rest of the code is released under MIT.
Index: source/tools/profiler2/ReportAnalytics.js
===================================================================
--- /dev/null
+++ source/tools/profiler2/ReportAnalytics.js
@@ -0,0 +1,155 @@
+// Copyright (C) 2019 Wildfire Games.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+export default {};
+
+import { get_color } from "./ReportColor.js"
+import { filter } from "./profiler2.js"
+import { reports, current_report, current_thread, active_blocks } from "./profiler2.js"
+
+var items;
+export function process_blocks(itemID)
+{
+ items = Object.entries(reports).flatMap(([report_name, report], index) => Object.entries(report.thread[current_thread].blocks_by_name).map(([name, blocks]) => {
+ return {
+ "report_name": report_name,
+ "report": report.thread[current_thread],
+ "block_name": name,
+ "blocks": blocks,
+ "color": get_color(name, index),
+ };
+ })).sort((a,b) => a.block_name.toLowerCase() < b.block_name.toLowerCase() ? -1 : 1);
+
+ let legend = d3.select("#" + itemID).selectAll("p").data(items);
+ legend = legend.enter().append("p").merge(legend)
+ .text(b => b.block_name + ' - ' + b.report_name)
+ .attr("class", item => active_blocks.indexOf(item.block_name + ' - ' + item.report_name) === -1 ? 'inactive' : 'active')
+ .attr("style", item => "--color:" + item.color)
+ .on('click', item => {
+ let index = active_blocks.indexOf(item.block_name + ' - ' + item.report_name);
+ if (index === -1)
+ filter({ "active_blocks": active_blocks.concat([item.block_name + ' - ' + item.report_name]) });
+ else
+ {
+ active_blocks.splice(index, 1);
+ filter({ "active_blocks": active_blocks });
+ }
+ });
+}
+
+export function draw_density(itemID)
+{
+ let accu = 0;
+ let chart = new CanvasJS.Chart(itemID, {
+ animationEnabled: true,
+ zoomEnabled: true,
+ axisY: {
+ minimum: 0,
+ gridColor: "#eaeaea",
+ labelFormatter: () => "",
+ },
+ axisX: {
+ minimum: -0.05,
+ maximum: 1.05,
+ labelFormatter: ()=> "",
+ },
+ data: items.map(item => {
+ accu = 0;
+ return {
+ type: 'stepLine',
+ markerSize: 0,
+ visible: active_blocks.indexOf(item.block_name + ' - ' + item.report_name) !== -1,
+ name: item.block_name + ' - ' + item.report_name,
+ color: item.color,
+ click: (e) => {
+ let start = e.dataPoint.name[0];
+ let end = e.dataPoint.name[1];
+ let dur = Math.max(1e-5, (end-start) * 0.1);
+ start -= dur;
+ end += dur;
+ filter({ 'start': start, 'end': end });
+ },
+ dataPoints: item.blocks.sort((a, b) => a.duration - b.duration).map((block,i) => {
+ accu += block.duration * 1000;
+ return {
+ x: +i/(item.blocks.length-1),
+ y: accu,
+ name: [block.start, block.end],
+ toolTipContent: item.report_name + "
" + item.block_name + "
Frame " + block.frame.frameID + "
" + Math.round(block.duration * 1000000000)/1000000 + "ms
Click to highlight above.",
+ };
+ }),
+ };
+ }),
+ });
+ chart.render();
+}
+
+export function draw_perframe(itemID, reports)
+{
+ let accu = 0;
+ let chart = new CanvasJS.Chart(itemID, {
+ animationEnabled: true,
+ zoomEnabled: true,
+ axisX: {
+ title: "frame",
+ },
+ axisY:{
+ gridColor: "#eaeaea",
+ labelFormatter: x => "" + x.value*1000 + "ms",
+ },
+ data: items.map(item => {
+ let perFrame = {};
+ item.blocks.forEach(b => {
+ if (!b.frame.frameID)
+ return;
+ if (!perFrame[b.frame.frameID])
+ perFrame[b.frame.frameID] = 0;
+ perFrame[b.frame.frameID] += b.duration;
+ });
+ let datapoints = [];
+ for (let i = item.report.frameStart; i <= item.report.frameEnd; i++)
+ {
+ datapoints.push({
+ x: i,
+ y: perFrame[i] || 0,
+ name: item.block_name,
+ click: () => {
+ let frame = item.report.data[i];
+ let dur = (frame.end - frame.start) * 0.1;
+ filter({
+ 'start': frame.start - dur,
+ 'end': frame.end + dur,
+ });
+ }
+ });
+ }
+ return {
+ type: 'stepLine',
+ markerSize: 0,
+ visible: active_blocks.indexOf(item.block_name + ' - ' + item.report_name) !== -1,
+ name: item.name + ' - ' + item.report_name,
+ color: item.color,
+ toolTipContent: item.report_name + "
{name}
{x} : {y}s
Click to highlight this frame above.",
+ dataPoints: datapoints,
+ };
+ }),
+ });
+ chart.render();
+}
Index: source/tools/profiler2/ReportColor.js
===================================================================
--- /dev/null
+++ source/tools/profiler2/ReportColor.js
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 Wildfire Games.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+export default {};
+
+let color_by_name = {};
+let i = 0;
+let colors = [
+ ["#7cb5ec", "#434348", "#90ed7d", "#f7a35c", "#8085e9", "#f15c80", "#e4d354", "#2b908f", "#f45b5b", "#91e8e1"],
+ ["#4c85bc", "#232328", "#70cd5d", "#d7a33c", "#6065c9", "#d13c60", "#c4b334", "#0b706f", "#c43b3b", "#71c8c1"]
+];
+
+
+export function get_color(name, darken = 0)
+{
+ if (!(name in color_by_name))
+ {
+ color_by_name[name] = i;
+ i = ++i % colors[darken % colors.length].length;
+ }
+ return colors[darken % colors.length][color_by_name[name]];
+}
Index: source/tools/profiler2/ReportDraw.js
===================================================================
--- source/tools/profiler2/ReportDraw.js
+++ /dev/null
@@ -1,479 +0,0 @@
-// Copyright (C) 2016 Wildfire Games.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-// Handles the drawing of a report
-
-var g_report_draw = (function()
-{
-var outInterface = {};
-
-var mouse_is_down = null;
-
-function rebuild_canvases(raw_data)
-{
- g_canvas = {};
-
- g_canvas.canvas_frames = $('').get(0);
- g_canvas.threads = {};
-
- for (var thread = 0; thread < raw_data.threads.length; thread++)
- g_canvas.threads[thread] = $('').get(0);
-
- g_canvas.canvas_zoom = $('').get(0);
- g_canvas.text_output = $('
Documentation is available here: https://trac.wildfiregames.com/wiki/Profiler2
+Load JSON profiles using the file loader below. Multiple files may be loaded.
+ Clicking on a profile will switch to it.
+ NB: this requires a local server running, see run.sh in this very folder
Click on any block to zoom on it. Zoom through the timeline graphs above. Reset on the top-right above.
+Drag-click to zoom. Click on a frame to highlight the frame timeline above.
+Use this tool to compare two profile reports. Drag-click to zoom. Click to highlight above.
+