time.html.twig 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. {% extends '@WebProfiler/Profiler/layout.html.twig' %}
  2. {% import _self as helper %}
  3. {% block toolbar %}
  4. {% set has_time_events = collector.events|length > 0 %}
  5. {% set total_time = has_time_events ? '%.0f'|format(collector.duration) : 'n/a' %}
  6. {% set initialization_time = collector.events|length ? '%.0f'|format(collector.inittime) : 'n/a' %}
  7. {% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' : '' %}
  8. {% set icon %}
  9. {{ include('@WebProfiler/Icon/time.svg') }}
  10. <span class="sf-toolbar-value">{{ total_time }}</span>
  11. <span class="sf-toolbar-label">ms</span>
  12. {% endset %}
  13. {% set text %}
  14. <div class="sf-toolbar-info-piece">
  15. <b>Total time</b>
  16. <span>{{ total_time }} ms</span>
  17. </div>
  18. <div class="sf-toolbar-info-piece">
  19. <b>Initialization time</b>
  20. <span>{{ initialization_time }} ms</span>
  21. </div>
  22. {% endset %}
  23. {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }}
  24. {% endblock %}
  25. {% block menu %}
  26. <span class="label">
  27. <span class="icon">{{ include('@WebProfiler/Icon/time.svg') }}</span>
  28. <strong>Performance</strong>
  29. </span>
  30. {% endblock %}
  31. {% block panel %}
  32. {% set has_time_events = collector.events|length > 0 %}
  33. <h2>Performance metrics</h2>
  34. <div class="metrics">
  35. <div class="metric">
  36. <span class="value">{{ '%.0f'|format(collector.duration) }} <span class="unit">ms</span></span>
  37. <span class="label">Total execution time</span>
  38. </div>
  39. <div class="metric">
  40. <span class="value">{{ '%.0f'|format(collector.inittime) }} <span class="unit">ms</span></span>
  41. <span class="label">Symfony initialization</span>
  42. </div>
  43. {% if profile.collectors.memory %}
  44. <div class="metric">
  45. <span class="value">{{ '%.2f'|format(profile.collectors.memory.memory / 1024 / 1024) }} <span class="unit">MiB</span></span>
  46. <span class="label">Peak memory usage</span>
  47. </div>
  48. {% endif %}
  49. {% if profile.children|length > 0 %}
  50. <div class="metric-divider"></div>
  51. <div class="metric">
  52. <span class="value">{{ profile.children|length }}</span>
  53. <span class="label">Sub-Request{{ profile.children|length > 1 ? 's' }}</span>
  54. </div>
  55. {% if has_time_events %}
  56. {% set subrequests_time = 0 %}
  57. {% for child in profile.children %}
  58. {% set subrequests_time = subrequests_time + child.getcollector('time').events.__section__.duration %}
  59. {% endfor %}
  60. {% else %}
  61. {% set subrequests_time = 'n/a' %}
  62. {% endif %}
  63. <div class="metric">
  64. <span class="value">{{ subrequests_time }} <span class="unit">ms</span></span>
  65. <span class="label">Sub-Request{{ profile.children|length > 1 ? 's' }} time</span>
  66. </div>
  67. {% endif %}
  68. </div>
  69. <h2>Execution timeline</h2>
  70. {% if not collector.isStopwatchInstalled() %}
  71. <div class="empty">
  72. <p>The Stopwatch component is not installed. If you want to see timing events, run: <code>composer require symfony/stopwatch</code>.</p>
  73. </div>
  74. {% elseif collector.events is empty %}
  75. <div class="empty">
  76. <p>No timing events have been recorded. Check that symfony/stopwatch is installed and debugging enabled in the kernel.</p>
  77. </div>
  78. {% else %}
  79. {{ block('panelContent') }}
  80. {% endif %}
  81. {% endblock %}
  82. {% block panelContent %}
  83. <form id="timeline-control" action="" method="get">
  84. <input type="hidden" name="panel" value="time">
  85. <label for="threshold">Threshold</label>
  86. <input type="number" name="threshold" id="threshold" value="1" min="0" placeholder="1.1"> ms
  87. <span class="help">(timeline only displays events with a duration longer than this threshold)</span>
  88. </form>
  89. {% if profile.parent %}
  90. <h3 class="dump-inline">
  91. Sub-Request {{ profiler_dump(profile.getcollector('request').requestattributes.get('_controller')) }}
  92. <small>
  93. {{ collector.events.__section__.duration }} ms
  94. <a class="newline" href="{{ path('_profiler', { token: profile.parent.token, panel: 'time' }) }}">Return to parent request</a>
  95. </small>
  96. </h3>
  97. {% elseif profile.children|length > 0 %}
  98. <h3>
  99. Main Request <small>{{ collector.events.__section__.duration }} ms</small>
  100. </h3>
  101. {% endif %}
  102. {{ helper.display_timeline(token, collector.events, collector.events.__section__.origin) }}
  103. {% if profile.children|length %}
  104. <p class="help">Note: sections with a striped background correspond to sub-requests.</p>
  105. <h3>Sub-requests <small>({{ profile.children|length }})</small></h3>
  106. {% for child in profile.children %}
  107. {% set events = child.getcollector('time').events %}
  108. <h4>
  109. <a href="{{ path('_profiler', { token: child.token, panel: 'time' }) }}">{{ child.getcollector('request').identifier }}</a>
  110. <small>{{ events.__section__.duration }} ms</small>
  111. </h4>
  112. {{ helper.display_timeline(child.token, events, collector.events.__section__.origin) }}
  113. {% endfor %}
  114. {% endif %}
  115. <svg id="timeline-template" width="0" height="0">
  116. <defs>
  117. <pattern id="subrequest" class="timeline-subrequest-pattern" patternUnits="userSpaceOnUse" width="20" height="20" viewBox="0 0 40 40">
  118. <path d="M0 40L40 0H20L0 20M40 40V20L20 40"/>
  119. </pattern>
  120. </defs>
  121. </svg>
  122. <style type="text/css">
  123. {% include '@WebProfiler/Collector/time.css.twig' %}
  124. </style>
  125. <script>
  126. {% include '@WebProfiler/Collector/time.js' %}
  127. </script>
  128. {% endblock %}
  129. {% macro dump_request_data(token, events, origin) %}
  130. {% autoescape 'js' %}
  131. {% from _self import dump_events %}
  132. {
  133. id: "{{ token }}",
  134. left: {{ "%F"|format(events.__section__.origin - origin) }},
  135. end: "{{ '%F'|format(events.__section__.endtime) }}",
  136. events: [ {{ dump_events(events) }} ],
  137. }
  138. {% endautoescape %}
  139. {% endmacro %}
  140. {% macro dump_events(events) %}
  141. {% autoescape 'js' %}
  142. {% for name, event in events %}
  143. {% if '__section__' != name %}
  144. {
  145. name: "{{ name }}",
  146. category: "{{ event.category }}",
  147. origin: {{ "%F"|format(event.origin) }},
  148. starttime: {{ "%F"|format(event.starttime) }},
  149. endtime: {{ "%F"|format(event.endtime) }},
  150. duration: {{ "%F"|format(event.duration) }},
  151. memory: {{ "%.1F"|format(event.memory / 1024 / 1024) }},
  152. elements: {},
  153. periods: [
  154. {%- for period in event.periods -%}
  155. {
  156. start: {{ "%F"|format(period.starttime) }},
  157. end: {{ "%F"|format(period.endtime) }},
  158. duration: {{ "%F"|format(period.duration) }},
  159. elements: {}
  160. },
  161. {%- endfor -%}
  162. ],
  163. },
  164. {% endif %}
  165. {% endfor %}
  166. {% endautoescape %}
  167. {% endmacro %}
  168. {% macro display_timeline(token, events, origin) %}
  169. {% import _self as helper %}
  170. <div class="sf-profiler-timeline">
  171. <div id="legend-{{ token }}" class="legends"></div>
  172. <svg id="timeline-{{ token }}" class="timeline-graph"></svg>
  173. <script>{% autoescape 'js' %}
  174. window.addEventListener('load', function onLoad() {
  175. const theme = new Theme();
  176. new TimelineEngine(
  177. theme,
  178. new SvgRenderer(document.getElementById('timeline-{{ token }}')),
  179. new Legend(document.getElementById('legend-{{ token }}'), theme),
  180. document.getElementById('threshold'),
  181. {{ helper.dump_request_data(token, events, origin) }}
  182. );
  183. });
  184. {% endautoescape %}</script>
  185. </div>
  186. {% endmacro %}