250 lines
8.8 KiB
Twig
250 lines
8.8 KiB
Twig
|
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||
|
|
||
|
{% block head %}
|
||
|
{{ parent() }}
|
||
|
|
||
|
<style>
|
||
|
#timeline-control {
|
||
|
background: var(--table-background);
|
||
|
box-shadow: var(--shadow);
|
||
|
margin: 1em 0;
|
||
|
padding: 10px;
|
||
|
}
|
||
|
#timeline-control label {
|
||
|
font-weight: bold;
|
||
|
margin-right: 1em;
|
||
|
}
|
||
|
#timeline-control input {
|
||
|
background: var(--metric-value-background);
|
||
|
font-size: 16px;
|
||
|
padding: 4px;
|
||
|
text-align: right;
|
||
|
width: 5em;
|
||
|
}
|
||
|
#timeline-control .help {
|
||
|
margin-left: 1em;
|
||
|
}
|
||
|
|
||
|
.sf-profiler-timeline .legends {
|
||
|
font-size: 12px;
|
||
|
line-height: 1.5em;
|
||
|
}
|
||
|
.sf-profiler-timeline .legends button {
|
||
|
color: var(--color-text);
|
||
|
}
|
||
|
.sf-profiler-timeline + p.help {
|
||
|
margin-top: 0;
|
||
|
}
|
||
|
</style>
|
||
|
{% endblock %}
|
||
|
|
||
|
{% block toolbar %}
|
||
|
{% set has_time_events = collector.events|length > 0 %}
|
||
|
{% set total_time = has_time_events ? '%.0f'|format(collector.duration) : 'n/a' %}
|
||
|
{% set initialization_time = collector.events|length ? '%.0f'|format(collector.inittime) : 'n/a' %}
|
||
|
{% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' %}
|
||
|
|
||
|
{% set icon %}
|
||
|
{{ source('@WebProfiler/Icon/time.svg') }}
|
||
|
<span class="sf-toolbar-value">{{ total_time }}</span>
|
||
|
<span class="sf-toolbar-label">ms</span>
|
||
|
{% endset %}
|
||
|
|
||
|
{% set text %}
|
||
|
<div class="sf-toolbar-info-piece">
|
||
|
<b>Total time</b>
|
||
|
<span>{{ total_time }} ms</span>
|
||
|
</div>
|
||
|
<div class="sf-toolbar-info-piece">
|
||
|
<b>Initialization time</b>
|
||
|
<span>{{ initialization_time }} ms</span>
|
||
|
</div>
|
||
|
{% endset %}
|
||
|
|
||
|
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }}
|
||
|
{% endblock %}
|
||
|
|
||
|
{% block menu %}
|
||
|
<span class="label">
|
||
|
<span class="icon">{{ source('@WebProfiler/Icon/time.svg') }}</span>
|
||
|
<strong>Performance</strong>
|
||
|
</span>
|
||
|
{% endblock %}
|
||
|
|
||
|
{% block panel %}
|
||
|
{% set has_time_events = collector.events|length > 0 %}
|
||
|
<h2>Performance metrics</h2>
|
||
|
|
||
|
<div class="metrics">
|
||
|
<div class="metric-group">
|
||
|
<div class="metric">
|
||
|
<span class="value">{{ '%.0f'|format(collector.duration) }} <span class="unit">ms</span></span>
|
||
|
<span class="label">Total execution time</span>
|
||
|
</div>
|
||
|
|
||
|
<div class="metric">
|
||
|
<span class="value">{{ '%.0f'|format(collector.inittime) }} <span class="unit">ms</span></span>
|
||
|
<span class="label">Symfony initialization</span>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
{% if profile.collectors.memory %}
|
||
|
<div class="metric-divider"></div>
|
||
|
|
||
|
<div class="metric">
|
||
|
<span class="value">{{ '%.2f'|format(profile.collectors.memory.memory / 1024 / 1024) }} <span class="unit">MiB</span></span>
|
||
|
<span class="label">Peak memory usage</span>
|
||
|
</div>
|
||
|
{% endif %}
|
||
|
|
||
|
{% if profile.children|length > 0 %}
|
||
|
<div class="metric-divider"></div>
|
||
|
|
||
|
<div class="metric-group">
|
||
|
<div class="metric">
|
||
|
<span class="value">{{ profile.children|length }}</span>
|
||
|
<span class="label">Sub-{{ profile_type|title }}{{ profile.children|length > 1 ? 's' }}</span>
|
||
|
</div>
|
||
|
|
||
|
{% set subrequests_time = has_time_events
|
||
|
? profile.children|reduce((total, child) => total + child.getcollector('time').events.__section__.duration, 0)
|
||
|
: 'n/a' %}
|
||
|
|
||
|
<div class="metric">
|
||
|
<span class="value">{{ subrequests_time }} <span class="unit">ms</span></span>
|
||
|
<span class="label">Sub-{{ profile_type|title }}{{ profile.children|length > 1 ? 's' }} time</span>
|
||
|
</div>
|
||
|
</div>
|
||
|
{% endif %}
|
||
|
</div>
|
||
|
|
||
|
<h2>Execution timeline</h2>
|
||
|
|
||
|
{% if not collector.isStopwatchInstalled() %}
|
||
|
<div class="empty empty-panel">
|
||
|
<p>The Stopwatch component is not installed. If you want to see timing events, run: <code>composer require symfony/stopwatch</code>.</p>
|
||
|
</div>
|
||
|
{% elseif collector.events is empty %}
|
||
|
<div class="empty">
|
||
|
<p>No timing events have been recorded. Check that symfony/stopwatch is installed and debugging enabled in the kernel.</p>
|
||
|
</div>
|
||
|
{% else %}
|
||
|
{{ block('panelContent') }}
|
||
|
{% endif %}
|
||
|
{% endblock %}
|
||
|
|
||
|
{% block panelContent %}
|
||
|
<form id="timeline-control" action="" method="get">
|
||
|
<input type="hidden" name="panel" value="time">
|
||
|
<label for="threshold">Threshold</label>
|
||
|
<input type="number" name="threshold" id="threshold" value="1" min="0" placeholder="1.1"> ms
|
||
|
<span class="help">(timeline only displays events with a duration longer than this threshold)</span>
|
||
|
</form>
|
||
|
|
||
|
{% if profile.parent %}
|
||
|
<h3 class="dump-inline">
|
||
|
Sub-{{ profile_type|title }} {{ profiler_dump(profile.getcollector('request').requestattributes.get('_controller')) }}
|
||
|
<small>
|
||
|
{{ collector.events.__section__.duration }} ms
|
||
|
<a class="newline" href="{{ path('_profiler', { token: profile.parent.token, panel: 'time' }) }}">Return to parent {{ profile_type }}</a>
|
||
|
</small>
|
||
|
</h3>
|
||
|
{% elseif profile.children|length > 0 %}
|
||
|
<h3>
|
||
|
Main {{ profile_type|title }} <small>{{ collector.events.__section__.duration }} ms</small>
|
||
|
</h3>
|
||
|
{% endif %}
|
||
|
|
||
|
{{ _self.display_timeline(token, collector.events, collector.events.__section__.origin) }}
|
||
|
|
||
|
{% if profile.children|length %}
|
||
|
<p class="help">Note: sections with a striped background correspond to sub-{{ profile_type }}s.</p>
|
||
|
|
||
|
<h3>Sub-{{ profile_type }}s <small>({{ profile.children|length }})</small></h3>
|
||
|
|
||
|
{% for child in profile.children %}
|
||
|
{% set events = child.getcollector('time').events %}
|
||
|
<h4>
|
||
|
<a href="{{ path('_profiler', { token: child.token, panel: 'time' }) }}">{{ child.getcollector('request').identifier }}</a>
|
||
|
<small>{{ events.__section__.duration }} ms</small>
|
||
|
</h4>
|
||
|
|
||
|
{{ _self.display_timeline(child.token, events, collector.events.__section__.origin) }}
|
||
|
{% endfor %}
|
||
|
{% endif %}
|
||
|
|
||
|
<svg id="timeline-template" width="0" height="0">
|
||
|
<defs>
|
||
|
<pattern id="subrequest" class="timeline-subrequest-pattern" patternUnits="userSpaceOnUse" width="20" height="20" viewBox="0 0 40 40">
|
||
|
<path d="M0 40L40 0H20L0 20M40 40V20L20 40"/>
|
||
|
</pattern>
|
||
|
</defs>
|
||
|
</svg>
|
||
|
<style>
|
||
|
{{ include('@WebProfiler/Collector/time.css.twig') }}
|
||
|
</style>
|
||
|
<script>
|
||
|
{{ source('@WebProfiler/Collector/time.js') }}
|
||
|
</script>
|
||
|
{% endblock %}
|
||
|
|
||
|
{% macro dump_request_data(token, events, origin) %}
|
||
|
{% autoescape 'js' %}
|
||
|
{
|
||
|
id: "{{ token }}",
|
||
|
left: {{ "%F"|format(events.__section__.origin - origin) }},
|
||
|
end: "{{ '%F'|format(events.__section__.endtime) }}",
|
||
|
events: [ {{ _self.dump_events(events) }} ],
|
||
|
}
|
||
|
{% endautoescape %}
|
||
|
{% endmacro %}
|
||
|
|
||
|
{% macro dump_events(events) %}
|
||
|
{% autoescape 'js' %}
|
||
|
{% for name, event in events %}
|
||
|
{% if '__section__' != name %}
|
||
|
{
|
||
|
name: "{{ name }}",
|
||
|
category: "{{ event.category }}",
|
||
|
origin: {{ "%F"|format(event.origin) }},
|
||
|
starttime: {{ "%F"|format(event.starttime) }},
|
||
|
endtime: {{ "%F"|format(event.endtime) }},
|
||
|
duration: {{ "%F"|format(event.duration) }},
|
||
|
memory: {{ "%.1F"|format(event.memory / 1024 / 1024) }},
|
||
|
elements: {},
|
||
|
periods: [
|
||
|
{%- for period in event.periods -%}
|
||
|
{
|
||
|
start: {{ "%F"|format(period.starttime) }},
|
||
|
end: {{ "%F"|format(period.endtime) }},
|
||
|
duration: {{ "%F"|format(period.duration) }},
|
||
|
elements: {}
|
||
|
},
|
||
|
{%- endfor -%}
|
||
|
],
|
||
|
},
|
||
|
{% endif %}
|
||
|
{% endfor %}
|
||
|
{% endautoescape %}
|
||
|
{% endmacro %}
|
||
|
|
||
|
{% macro display_timeline(token, events, origin) %}
|
||
|
<div class="sf-profiler-timeline">
|
||
|
<div id="legend-{{ token }}" class="legends"></div>
|
||
|
<svg id="timeline-{{ token }}" class="timeline-graph"></svg>
|
||
|
<script>{% autoescape 'js' %}
|
||
|
window.addEventListener('load', function onLoad() {
|
||
|
const theme = new Theme();
|
||
|
|
||
|
new TimelineEngine(
|
||
|
theme,
|
||
|
new SvgRenderer(document.getElementById('timeline-{{ token }}')),
|
||
|
new Legend(document.getElementById('legend-{{ token }}'), theme),
|
||
|
document.getElementById('threshold'),
|
||
|
{{ _self.dump_request_data(token, events, origin) }}
|
||
|
);
|
||
|
});
|
||
|
{% endautoescape %}</script>
|
||
|
</div>
|
||
|
{% endmacro %}
|