Adjust the width of the timeline in the profiler dynamically when the (browser) window is resized.
This commit is contained in:
parent
1541fe26e4
commit
06cc9ff32d
@ -456,3 +456,19 @@ td.main, td.menu {
|
||||
height: 30px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sf-profiler-timeline .legends {
|
||||
font-size: 12px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.sf-profiler-timeline .legends span {
|
||||
border-left-width: 10px;
|
||||
border-left-style: solid;
|
||||
padding: 0 10px 0 5px;
|
||||
}
|
||||
|
||||
.sf-profiler-timeline canvas {
|
||||
border: 1px solid #999;
|
||||
border-width: 1px 0;
|
||||
}
|
||||
|
@ -37,10 +37,7 @@
|
||||
{% block panel %}
|
||||
<h2>Timeline</h2>
|
||||
|
||||
{% set threshold = app.request.query.get('threshold', 1) %}
|
||||
{% set width = app.request.query.get('width', 680) %}
|
||||
|
||||
<form action="" method="get" style="display: inline">
|
||||
<form id="timeline-control" action="" method="get">
|
||||
<input type="hidden" name="panel" value="time" />
|
||||
<table>
|
||||
<tr>
|
||||
@ -53,15 +50,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Threshold</th>
|
||||
<td><input type="number" size="3" name="threshold" value="{{ threshold }}" min="0" /> ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Width</th>
|
||||
<td><input type="range" name="width" min="0" max="1200" step="10" value="{{ width }}" onChange="drawCanvases(this.value);" /> px</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<td><input type="submit" value="refresh" /></td>
|
||||
<td><input type="number" size="3" name="threshold" value="1" min="0" /> ms</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
@ -71,174 +60,319 @@
|
||||
<small>
|
||||
- {{ collector.events.__section__.totaltime }} ms
|
||||
{% if profile.parent %}
|
||||
- <a href="{{ path('_profiler', { 'token': profile.parent.token, 'panel': 'time', 'threshold': threshold, 'width': width }) }}">parent</a>
|
||||
- <a href="{{ path('_profiler', { 'token': profile.parent.token, 'panel': 'time' }) }}">parent</a>
|
||||
{% endif %}
|
||||
</small>
|
||||
</h3>
|
||||
|
||||
{% set max = collector.events.__section__.endtime %}
|
||||
|
||||
{{ _self.display_timeline('timeline_' ~ token, collector.events, threshold, colors, width) }}
|
||||
{{ _self.display_timeline('timeline_' ~ token, collector.events, colors) }}
|
||||
|
||||
{% if profile.children|length %}
|
||||
{% for child in profile.children %}
|
||||
{% set events = child.getcollector('time').events %}
|
||||
<h3>
|
||||
Sub-request "<a href="{{ path('_profiler', { 'token': child.token, 'panel': 'time', 'threshold': threshold, 'width': width }) }}">{{ child.getcollector('request').requestattributes.get('_controller') }}</a>"
|
||||
Sub-request "<a href="{{ path('_profiler', { 'token': child.token, 'panel': 'time' }) }}">{{ child.getcollector('request').requestattributes.get('_controller') }}</a>"
|
||||
<small> - {{ events.__section__.totaltime }} ms</small>
|
||||
</h3>
|
||||
|
||||
{{ _self.display_timeline('timeline_' ~ child.token, events, threshold, colors, width) }}
|
||||
{{ _self.display_timeline('timeline_' ~ child.token, events, colors) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function drawCanvas(request, max, threshold, width) {
|
||||
/**
|
||||
* In-memory key-value cache manager
|
||||
*/
|
||||
var cache = new function() {
|
||||
"use strict";
|
||||
var text,
|
||||
ms,
|
||||
xc,
|
||||
mainEvents,
|
||||
colors = {{ colors|json_encode|raw }},
|
||||
space = 10.5,
|
||||
ratio = (width - space * 2) / max,
|
||||
height = 0,
|
||||
h = space,
|
||||
x = request.left * ratio + space,
|
||||
canvas = document.getElementById('timeline_' + request.id),
|
||||
ctx = canvas.getContext("2d");
|
||||
var dict = {};
|
||||
|
||||
request.events = request.events.filter(function(event) {
|
||||
return event.totaltime >= threshold;
|
||||
});
|
||||
|
||||
height += 38 * request.events.length;
|
||||
canvas.width = width;
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.lineWidth = 0;
|
||||
|
||||
request.events.forEach(function(event) {
|
||||
event.periods.forEach(function(period) {
|
||||
if ('__section__.child' === event.name) {
|
||||
ctx.fillStyle = colors.child_sections;
|
||||
ctx.fillRect(x + period.begin * ratio, 0, (period.end - period.begin) * ratio, height);
|
||||
} else if ('section' === event.category) {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = "#dfdfdf";
|
||||
ctx.moveTo(x + period.begin * ratio, 0);
|
||||
ctx.lineTo(x + period.begin * ratio, height);
|
||||
ctx.moveTo(x + period.end * ratio, 0);
|
||||
ctx.lineTo(x + period.end * ratio, height);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
mainEvents = request.events.filter(function(event) {
|
||||
return '__section__.child' !== event.name;
|
||||
});
|
||||
|
||||
mainEvents.forEach(function(event) {
|
||||
|
||||
h += 8;
|
||||
|
||||
event.periods.forEach(function(period) {
|
||||
|
||||
if (colors[event.name]) {
|
||||
ctx.fillStyle = colors[event.name];
|
||||
ctx.strokeStyle = colors[event.name];
|
||||
} else if (colors[event.category]) {
|
||||
ctx.fillStyle = colors[event.category];
|
||||
ctx.strokeStyle = colors[event.category];
|
||||
} else {
|
||||
ctx.fillStyle = colors['default'];
|
||||
ctx.strokeStyle = colors['default'];
|
||||
}
|
||||
|
||||
if ('section' !== event.category) {
|
||||
ctx.fillRect(x + period.begin * ratio, h + 3, 2, 6);
|
||||
ctx.fillRect(x + period.begin * ratio, h, (period.end - period.begin) * ratio || 2, 6);
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + period.begin * ratio, h);
|
||||
ctx.lineTo(x + period.begin * ratio, h + 11);
|
||||
ctx.lineTo(x + period.begin * ratio + 8, h);
|
||||
ctx.lineTo(x + period.begin * ratio, h);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + period.end * ratio, h);
|
||||
ctx.lineTo(x + period.end * ratio, h + 11);
|
||||
ctx.lineTo(x + period.end * ratio - 8, h);
|
||||
ctx.lineTo(x + period.end * ratio, h);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + period.begin * ratio, h);
|
||||
ctx.lineTo(x + period.end * ratio, h);
|
||||
ctx.lineTo(x + period.end * ratio, h + 2);
|
||||
ctx.lineTo(x + period.begin * ratio, h + 2);
|
||||
ctx.lineTo(x + period.begin * ratio, h);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
|
||||
h += 30;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = "#dfdfdf";
|
||||
ctx.moveTo(0, h - 10);
|
||||
ctx.lineTo(width, h - 10);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
});
|
||||
|
||||
h = space;
|
||||
|
||||
mainEvents.forEach(function(event) {
|
||||
|
||||
ctx.fillStyle = "#444";
|
||||
ctx.font = "12px sans-serif";
|
||||
text = event.name;
|
||||
ms = " ~ " + (event.totaltime < 1 ? event.totaltime : parseInt(event.totaltime, 10)) + " ms";
|
||||
if (x + event.starttime * ratio + ctx.measureText(text + ms).width > width) {
|
||||
ctx.textAlign = "end";
|
||||
ctx.font = "10px sans-serif";
|
||||
xc = x + event.endtime * ratio - 1;
|
||||
ctx.fillText(ms, xc, h);
|
||||
|
||||
xc -= ctx.measureText(ms).width;
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.fillText(text, xc, h);
|
||||
} else {
|
||||
ctx.textAlign = "start";
|
||||
ctx.font = "12px sans-serif";
|
||||
xc = x + event.starttime * ratio + 1;
|
||||
ctx.fillText(text, xc, h);
|
||||
|
||||
xc += ctx.measureText(text).width;
|
||||
ctx.font = "10px sans-serif";
|
||||
ctx.fillText(ms, xc, h);
|
||||
this.get = function(key) {
|
||||
return dict.hasOwnProperty(key)
|
||||
? dict[key]
|
||||
: null;
|
||||
}
|
||||
|
||||
h += 38;
|
||||
});
|
||||
}
|
||||
this.set = function(key, value) {
|
||||
dict[key] = value;
|
||||
|
||||
function drawCanvases(width)
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Query an element with a CSS selector.
|
||||
*
|
||||
* @param string selector a CSS-selector-compatible query string.
|
||||
*
|
||||
* @return DOMElement|null
|
||||
*/
|
||||
function query(selector)
|
||||
{
|
||||
"use strict";
|
||||
requests_data.requests.forEach(function(request) {
|
||||
drawCanvas(request, requests_data.max, {{ threshold }}, width);
|
||||
});
|
||||
var key = 'SELECTOR: ' + selector;
|
||||
|
||||
return cache.get(key) || cache.set(key, document.querySelector(selector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Canvas Manager
|
||||
*/
|
||||
function CanvasManager(requests, maxRequestTime) {
|
||||
"use strict";
|
||||
|
||||
var _drawingColors = {{ colors|json_encode|raw }},
|
||||
_storagePrefix = 'sf2/profiler/timeline',
|
||||
_threshold = 1,
|
||||
_requests = requests,
|
||||
_maxRequestTime = maxRequestTime;
|
||||
|
||||
/**
|
||||
* Check whether this event is a child event.
|
||||
*
|
||||
* @return true if it is.
|
||||
*/
|
||||
function isChildEvent(event)
|
||||
{
|
||||
return '__section__.child' === event.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this event is categorized in 'section'.
|
||||
*
|
||||
* @return true if it is.
|
||||
*/
|
||||
function isSectionEvent(event)
|
||||
{
|
||||
return 'section' === event.category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of the container.
|
||||
*/
|
||||
function getContainerWidth()
|
||||
{
|
||||
return query('#collector_content h2').clientWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw one canvas.
|
||||
*
|
||||
* @param request the request object
|
||||
* @param max <subjected for removal>
|
||||
* @param threshold the threshold (lower bound) of the length of the timeline (in miliseconds).
|
||||
* @param width the width of the canvas.
|
||||
*/
|
||||
this.drawOne = function(request, max, threshold, width)
|
||||
{
|
||||
"use strict";
|
||||
var text,
|
||||
ms,
|
||||
xc,
|
||||
drawableEvents,
|
||||
mainEvents,
|
||||
elementId = 'timeline_' + request.id,
|
||||
canvasHeight = 0,
|
||||
gapPerEvent = 38,
|
||||
colors = _drawingColors,
|
||||
space = 10.5,
|
||||
ratio = (width - space * 2) / max,
|
||||
h = space,
|
||||
x = request.left * ratio + space, // position
|
||||
canvas = cache.get(elementId) || cache.set(elementId, document.getElementById(elementId)),
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
// Filter events whose total time is below the threshold.
|
||||
drawableEvents = request.events.filter(function(event) {
|
||||
return event.totaltime >= threshold;
|
||||
});
|
||||
|
||||
canvasHeight += gapPerEvent * drawableEvents.length;
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = canvasHeight;
|
||||
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.lineWidth = 0;
|
||||
|
||||
// For each event, draw a line.
|
||||
ctx.strokeStyle = "#dfdfdf";
|
||||
|
||||
drawableEvents.forEach(function(event) {
|
||||
event.periods.forEach(function(period) {
|
||||
var timelineHeadPosition = x + period.begin * ratio;
|
||||
|
||||
if (isChildEvent(event)) {
|
||||
ctx.fillStyle = colors.child_sections;
|
||||
ctx.fillRect(timelineHeadPosition, 0, (period.end - period.begin) * ratio, canvasHeight);
|
||||
} else if (isSectionEvent(event)) {
|
||||
var timelineTailPosition = x + period.end * ratio;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(timelineHeadPosition, 0);
|
||||
ctx.lineTo(timelineHeadPosition, canvasHeight);
|
||||
ctx.moveTo(timelineTailPosition, 0);
|
||||
ctx.lineTo(timelineTailPosition, canvasHeight);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Filter for main events.
|
||||
mainEvents = drawableEvents.filter(function(event) {
|
||||
return ! isChildEvent(event)
|
||||
});
|
||||
|
||||
// For each main event, draw the visual presentation of timelines.
|
||||
mainEvents.forEach(function(event) {
|
||||
|
||||
h += 8;
|
||||
|
||||
// For each sub event, ...
|
||||
event.periods.forEach(function(period) {
|
||||
// Set the drawing style.
|
||||
ctx.fillStyle = colors['default'];
|
||||
ctx.strokeStyle = colors['default'];
|
||||
|
||||
if (colors[event.name]) {
|
||||
ctx.fillStyle = colors[event.name];
|
||||
ctx.strokeStyle = colors[event.name];
|
||||
} else if (colors[event.category]) {
|
||||
ctx.fillStyle = colors[event.category];
|
||||
ctx.strokeStyle = colors[event.category];
|
||||
}
|
||||
|
||||
// Draw the timeline
|
||||
var timelineHeadPosition = x + period.begin * ratio;
|
||||
|
||||
if ( ! isSectionEvent(event)) {
|
||||
ctx.fillRect(timelineHeadPosition, h + 3, 2, 6);
|
||||
ctx.fillRect(timelineHeadPosition, h, (period.end - period.begin) * ratio || 2, 6);
|
||||
} else {
|
||||
var timelineTailPosition = x + period.end * ratio;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(timelineHeadPosition, h);
|
||||
ctx.lineTo(timelineHeadPosition, h + 11);
|
||||
ctx.lineTo(timelineHeadPosition + 8, h);
|
||||
ctx.lineTo(timelineHeadPosition, h);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(timelineTailPosition, h);
|
||||
ctx.lineTo(timelineTailPosition, h + 11);
|
||||
ctx.lineTo(timelineTailPosition - 8, h);
|
||||
ctx.lineTo(timelineTailPosition, h);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(timelineHeadPosition, h);
|
||||
ctx.lineTo(timelineTailPosition, h);
|
||||
ctx.lineTo(timelineTailPosition, h + 2);
|
||||
ctx.lineTo(timelineHeadPosition, h + 2);
|
||||
ctx.lineTo(timelineHeadPosition, h);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
|
||||
h += 30;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = "#dfdfdf";
|
||||
ctx.moveTo(0, h - 10);
|
||||
ctx.lineTo(width, h - 10);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
});
|
||||
|
||||
h = space;
|
||||
|
||||
// For each event, draw the label.
|
||||
mainEvents.forEach(function(event) {
|
||||
|
||||
ctx.fillStyle = "#444";
|
||||
ctx.font = "12px sans-serif";
|
||||
text = event.name;
|
||||
ms = " ~ " + (event.totaltime < 1 ? event.totaltime : parseInt(event.totaltime, 10)) + " ms";
|
||||
if (x + event.starttime * ratio + ctx.measureText(text + ms).width > width) {
|
||||
ctx.textAlign = "end";
|
||||
ctx.font = "10px sans-serif";
|
||||
xc = x + event.endtime * ratio - 1;
|
||||
ctx.fillText(ms, xc, h);
|
||||
|
||||
xc -= ctx.measureText(ms).width;
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.fillText(text, xc, h);
|
||||
} else {
|
||||
ctx.textAlign = "start";
|
||||
ctx.font = "12px sans-serif";
|
||||
xc = x + event.starttime * ratio + 1;
|
||||
ctx.fillText(text, xc, h);
|
||||
|
||||
xc += ctx.measureText(text).width;
|
||||
ctx.font = "10px sans-serif";
|
||||
ctx.fillText(ms, xc, h);
|
||||
}
|
||||
|
||||
h += gapPerEvent;
|
||||
});
|
||||
};
|
||||
|
||||
this.drawAll = function(width, threshold)
|
||||
{
|
||||
"use strict";
|
||||
|
||||
width = width || getContainerWidth();
|
||||
threshold = threshold || this.getThreshold();
|
||||
|
||||
var self = this;
|
||||
|
||||
_requests.forEach(function(request) {
|
||||
self.drawOne(request, maxRequestTime, threshold, width);
|
||||
});
|
||||
};
|
||||
|
||||
this.getThreshold = function() {
|
||||
var threshold = localStorage.getItem(_storagePrefix + '/threshold');
|
||||
|
||||
if (threshold === null) {
|
||||
return _threshold;
|
||||
}
|
||||
|
||||
_threshold = parseInt(threshold);
|
||||
|
||||
return _threshold;
|
||||
};
|
||||
|
||||
this.setThreshold = function(threshold)
|
||||
{
|
||||
_threshold = threshold;
|
||||
|
||||
localStorage.setItem(_storagePrefix + '/threshold', threshold);
|
||||
|
||||
return this;
|
||||
};
|
||||
};
|
||||
|
||||
function canvasAutoUpdateOnResizeAndSubmit(e) {
|
||||
e.preventDefault();
|
||||
canvasManager.drawAll();
|
||||
}
|
||||
|
||||
function canvasAutoUpdateOnThresholdChange(e) {
|
||||
canvasManager
|
||||
.setThreshold(query('input[name="threshold"]').value)
|
||||
.drawAll();
|
||||
}
|
||||
|
||||
var requests_data = {
|
||||
@ -255,7 +389,31 @@
|
||||
]
|
||||
};
|
||||
|
||||
drawCanvases({{ width }});
|
||||
var canvasManager = new CanvasManager(requests_data.requests, requests_data.max);
|
||||
|
||||
query('input[name="threshold"]').value = canvasManager.getThreshold();
|
||||
canvasManager.drawAll();
|
||||
|
||||
// Update the colors of legends.
|
||||
var timelineLegends = document.querySelectorAll('.sf-profiler-timeline > .legends > span[data-color]');
|
||||
|
||||
for (var i = 0; i < timelineLegends.length; ++i) {
|
||||
var timelineLegend = timelineLegends[i];
|
||||
|
||||
timelineLegend.style.borderLeftColor = timelineLegend.getAttribute('data-color');
|
||||
}
|
||||
|
||||
// Bind event handlers
|
||||
var elementTimelineControl = query('#timeline-control'),
|
||||
elementThresholdControl = query('input[name="threshold"]');
|
||||
|
||||
window.onresize = canvasAutoUpdateOnResizeAndSubmit;
|
||||
elementTimelineControl.onsubmit = canvasAutoUpdateOnResizeAndSubmit;
|
||||
|
||||
elementThresholdControl.onclick = canvasAutoUpdateOnThresholdChange;
|
||||
elementThresholdControl.onchange = canvasAutoUpdateOnThresholdChange;
|
||||
elementThresholdControl.onkeyup = canvasAutoUpdateOnThresholdChange;
|
||||
|
||||
//]]></script>
|
||||
{% endblock %}
|
||||
|
||||
@ -289,19 +447,13 @@
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro display_timeline(id, events, threshold, colors, width) %}
|
||||
{% set height = 0 %}
|
||||
{% for name, event in events if '__section__' != name and event.totaltime >= threshold %}
|
||||
{% set height = height + 38 %}
|
||||
{% endfor %}
|
||||
|
||||
<div>
|
||||
<small>
|
||||
{% macro display_timeline(id, events, colors) %}
|
||||
<div class="sf-profiler-timeline">
|
||||
<div class="legends">
|
||||
{% for category, color in colors %}
|
||||
<span style="background-color: {{ color }}"> </span> {{ category }}
|
||||
<span data-color="{{ color }}">{{ category }}</span>
|
||||
{% endfor %}
|
||||
</small>
|
||||
</div>
|
||||
<canvas width="680" height="" id="{{ id }}" class="timeline"></canvas>
|
||||
</div>
|
||||
|
||||
<canvas width="{{ width }}" height="{{ height }}" id="{{ id }}" class="timeline"></canvas>
|
||||
{% endmacro %}
|
||||
|
Reference in New Issue
Block a user