diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php
index a954832b529b..e53a7cfdf14d 100644
--- a/system/Debug/Toolbar.php
+++ b/system/Debug/Toolbar.php
@@ -194,17 +194,47 @@ public function run(float $startTime, float $totalTime, RequestInterface $reques
* @return string
*/
protected function renderTimeline(array $collectors, float $startTime, int $segmentCount, int $segmentDuration, array &$styles): string
+ {
+ $rows = $this->collectTimelineData($collectors);
+ $output = '';
+ $styleCount = 0;
+
+ // Use recursive render function
+ return $this->renderTimelineRecursive($rows, $startTime, $segmentCount, $segmentDuration, $styles, $styleCount);
+ }
+
+ /**
+ * Recursively renders timeline elements and their children.
+ *
+ * @param array $rows
+ * @param float $startTime
+ * @param int $segmentCount
+ * @param int $segmentDuration
+ * @param array $styles
+ * @param int $styleCount
+ * @param int $level
+ * @param bool $isChild
+ *
+ * @return string
+ */
+ protected function renderTimelineRecursive(array $rows, float $startTime, int $segmentCount, int $segmentDuration, array &$styles, int &$styleCount, int $level = 0, bool $isChild = false): string
{
$displayTime = $segmentCount * $segmentDuration;
- $rows = $this->collectTimelineData($collectors);
- $output = '';
- $styleCount = 0;
+
+ $output = '';
foreach ($rows as $row) {
- $output .= '
';
- $output .= "{$row['name']} | ";
- $output .= "{$row['component']} | ";
- $output .= "" . number_format($row['duration'] * 1000, 2) . ' ms | ';
+ $hasChildren = isset($row['children']) && !empty($row['children']);
+ $open = $row['name'] === 'Controller';
+
+ if ($hasChildren) {
+ $output .= '
';
+ } else {
+ $output .= '
';
+ }
+ $output .= '' . ($hasChildren ? '' : '') . $row['name'] . ' | ';
+ $output .= '' . $row['component'] . ' | ';
+ $output .= '' . number_format($row['duration'] * 1000, 2) . ' ms | ';
$output .= "";
$offset = ((((float) $row['start'] - $startTime) * 1000) / $displayTime) * 100;
@@ -217,6 +247,19 @@ protected function renderTimeline(array $collectors, float $startTime, int $segm
$output .= ' |
';
$styleCount++;
+
+ // Add children if any
+ if ($hasChildren) {
+ $output .= '';
+ $output .= '';
+ $output .= '';
+ $output .= '';
+ $output .= $this->renderTimelineRecursive($row['children'], $startTime, $segmentCount, $segmentDuration, $styles, $styleCount, $level + 1, true);
+ $output .= '';
+ $output .= ' ';
+ $output .= ' | ';
+ $output .= '
';
+ }
}
return $output;
@@ -246,15 +289,54 @@ protected function collectTimelineData($collectors): array
// Sort it
$sortArray = [
- array_column($data, 'start'), SORT_NUMERIC, SORT_ASC,
- array_column($data, 'duration'), SORT_NUMERIC, SORT_DESC,
- &$data
+ array_column($data, 'start'), SORT_NUMERIC, SORT_ASC,
+ array_column($data, 'duration'), SORT_NUMERIC, SORT_DESC,
+ &$data,
];
array_multisort(...$sortArray);
+ // Add end time to each element
+ array_walk($data, static function(&$row) {
+ $row['end'] = $row['start'] + $row['duration'];
+ });
+
+ // Group it
+ $data = $this->structureTimelineData($data);
+
return $data;
}
+ /**
+ * Arranges the already sorted timeline data into a parent => child structure.
+ *
+ * @param array $elements
+ *
+ * @return array
+ */
+ protected function structureTimelineData(array $elements): array
+ {
+ // We define ourselves as the first element of the array
+ $element = array_shift($elements);
+
+ // If we have children behind us, collect and attach them to us
+ while (!empty($elements) && $elements[array_key_first($elements)]['end'] <= $element['end']) {
+ $element['children'][] = array_shift($elements);
+ }
+
+ // Make sure our children know whether they have children, too
+ if (isset($element['children'])) {
+ $element['children'] = $this->structureTimelineData($element['children']);
+ }
+
+ // If we have no younger siblings, we can return
+ if (empty($elements)) {
+ return [$element];
+ }
+
+ // Make sure our younger siblings know their relatives, too
+ return array_merge([$element], $this->structureTimelineData($elements));
+ }
+
//--------------------------------------------------------------------
/**
diff --git a/system/Debug/Toolbar/Views/toolbar.css b/system/Debug/Toolbar/Views/toolbar.css
index f881dd18d6ec..bc8b4ac44ef7 100644
--- a/system/Debug/Toolbar/Views/toolbar.css
+++ b/system/Debug/Toolbar/Views/toolbar.css
@@ -186,6 +186,22 @@
#debug-bar .timeline {
margin-left: 0;
width: 100%; }
+ #debug-bar .timeline tr.timeline-parent {
+ cursor: pointer; }
+ #debug-bar .timeline tr.timeline-parent td:first-child nav {
+ background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMCAxNTAiPjxwYXRoIGQ9Ik02IDdoMThsLTkgMTV6bTAgMzBoMThsLTkgMTV6bTAgNDVoMThsLTktMTV6bTAgMzBoMThsLTktMTV6bTAgMTJsMTggMThtLTE4IDBsMTgtMTgiIGZpbGw9IiM1NTUiLz48cGF0aCBkPSJNNiAxMjZsMTggMThtLTE4IDBsMTgtMTgiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSIjNTU1Ii8+PC9zdmc+") no-repeat scroll 0 0/15px 75px transparent;
+ background-position: 0 25%;
+ display: inline-block;
+ height: 15px;
+ width: 15px;
+ margin-right: 3px;
+ vertical-align: middle; }
+ #debug-bar .timeline tr.timeline-parent.timeline-parent-open td:first-child nav {
+ background-position: 0 75%; }
+ #debug-bar .timeline tr.timeline-parent.timeline-parent-open {
+ background-color: #DFDFDF; }
+ #debug-bar .timeline tr.child-row:hover {
+ background: transparent; }
#debug-bar .timeline th {
border-left: 1px solid;
font-size: 12px;
@@ -200,7 +216,14 @@
padding: 5px;
position: relative; }
#debug-bar .timeline td:first-child {
- border-left: 0; }
+ border-left: 0;
+ max-width: none; }
+ #debug-bar .timeline td.child-container {
+ padding: 0px; }
+ #debug-bar .timeline td.child-container .timeline{
+ margin: 0px; }
+ #debug-bar .timeline td.child-container td:first-child:not(.child-container){
+ padding-left: calc(5px + 10px * var(--level)); }
#debug-bar .timeline .timer {
border-radius: 4px;
-moz-border-radius: 4px;
diff --git a/system/Debug/Toolbar/Views/toolbar.js b/system/Debug/Toolbar/Views/toolbar.js
index ca38f9b47bb5..fd7292c96765 100644
--- a/system/Debug/Toolbar/Views/toolbar.js
+++ b/system/Debug/Toolbar/Views/toolbar.js
@@ -153,6 +153,26 @@ var ciDebugBar = {
}
},
+ /**
+ * Toggle display of timeline child elements
+ *
+ * @param obj
+ */
+ toggleChildRows : function (obj) {
+ if (typeof obj == 'string')
+ {
+ par = document.getElementById(obj + '_parent')
+ obj = document.getElementById(obj + '_children');
+ }
+
+ if (par && obj)
+ {
+ obj.style.display = obj.style.display == 'none' ? '' : 'none';
+ par.classList.toggle('timeline-parent-open');
+ }
+ },
+
+
//--------------------------------------------------------------------
/**