[TikiWiki-commits] [Git][tikiwiki/tiki][master] [ENH] Performance statistics: Improve performance stats, add details link, and add tests
"ushindi bienvenu \(@usbbush\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <6a30e44aa82c6_3819631066013@gitlab-sidekiq-low-urgency-cpu-bound-v2-68f746c689-548lp.mail> |
ushindi bienvenu pushed to branch master at Tiki Wiki CMS Groupware / Tiki
Commits:
f37cd7c4 by Alain Cisirika at 2026-06-16T05:38:51+00:00
[ENH] Performance statistics: Improve performance stats, add details link, and add tests
---
* Fix pipeline
* [ENH] Performance statistics: Improve performance stats, add details link, and add tests
See merge request tikiwiki/tiki!10141
- - - - -
6 changed files:
- db/tiki.sql
- + installer/schema/20260419_add_breakdown_fields_to_tiki_performance_tiki.sql
- lib/performance/performancestatslib.php
- + lib/test/performance/PerformanceStatsLibTest.php
- templates/tiki-performance_stats.tpl
- tiki-performance_stats.php
Changes:
=====================================
db/tiki.sql
=====================================
@@ -4182,6 +4182,8 @@ CREATE TABLE `tiki_performance` (
`id` int(12) NOT NULL AUTO_INCREMENT,
`url` TEXT NOT NULL,
`time_taken` int(12) NOT NULL,
+ `backend_time` int DEFAULT NULL,
+ `frontend_time` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM;
=====================================
installer/schema/20260419_add_breakdown_fields_to_tiki_performance_tiki.sql
=====================================
@@ -0,0 +1,3 @@
+ALTER TABLE `tiki_performance`
+ ADD COLUMN `backend_time` int DEFAULT NULL AFTER `time_taken`,
+ ADD COLUMN `frontend_time` int DEFAULT NULL AFTER `backend_time`;
=====================================
lib/performance/performancestatslib.php
=====================================
@@ -19,13 +19,17 @@ class PerformanceStatsLib extends TikiLib
* Insert a performance record on the table
* @param string $url
* @param int $time_taken
+ * @param int|null $backend_time
+ * @param int|null $frontend_time
* @return array|bool|mixed
*/
- public function addRecord(string $url, int $time_taken)
+ public function addRecord(string $url, int $time_taken, ?int $backend_time = null, ?int $frontend_time = null)
{
return $this->table('tiki_performance')->insert([
'url' => $url,
- 'time_taken' => $time_taken
+ 'time_taken' => $time_taken,
+ 'backend_time' => $backend_time,
+ 'frontend_time' => $frontend_time,
]);
}
@@ -83,6 +87,52 @@ class PerformanceStatsLib extends TikiLib
return $this->getOne('SELECT COUNT(DISTINCT(url)) FROM tiki_performance');
}
+ /**
+ * Get aggregate timings for a specific URL
+ *
+ * @param string $url
+ * @return array|false
+ */
+ public function getRequestDetailsByUrl(string $url): array|false
+ {
+ $result = $this->query(
+ "SELECT
+ url,
+ COUNT(*) AS number_of_requests,
+ ROUND(AVG(time_taken)) AS average_time_taken,
+ MIN(time_taken) AS minimum_time_taken,
+ MAX(time_taken) AS maximum_time_taken,
+ ROUND(AVG(CASE WHEN backend_time IS NOT NULL THEN backend_time END)) AS average_backend_time,
+ ROUND(AVG(CASE WHEN frontend_time IS NOT NULL THEN frontend_time END)) AS average_frontend_time,
+ SUM(CASE WHEN backend_time IS NOT NULL OR frontend_time IS NOT NULL THEN 1 ELSE 0 END) AS breakdown_samples
+ FROM tiki_performance
+ WHERE url = ?
+ GROUP BY url",
+ [$url]
+ );
+
+ return $result ? $result->fetchRow() : false;
+ }
+
+ /**
+ * Get the slowest samples for a specific URL
+ *
+ * @param string $url
+ * @param int $amount
+ * @return array
+ */
+ public function getSlowestSamplesByUrl(string $url, int $amount = 25): array
+ {
+ return $this->fetchAll(
+ "SELECT id, time_taken, backend_time, frontend_time
+ FROM tiki_performance
+ WHERE url = ?
+ ORDER BY time_taken DESC",
+ [$url],
+ $amount
+ );
+ }
+
/**
* Check if a certain performance related call should be logged
* @param $type
=====================================
lib/test/performance/PerformanceStatsLibTest.php
=====================================
@@ -0,0 +1,74 @@
+<?php
+
+// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
+//
+// All Rights Reserved. See copyright.txt for details and a complete list of authors.
+// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
+
+namespace Tiki\Test\Performance;
+
+require_once 'lib/test/TikiTestCase.php';
+require_once 'lib/performance/performancestatslib.php';
+
+class PerformanceStatsLibTest extends \TikiTestCase
+{
+ private $lib;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+ $this->lib = new \PerformanceStatsLib();
+ $db = \TikiDb::get();
+
+ $db->query('DELETE FROM tiki_performance');
+ $db->query(
+ 'INSERT INTO tiki_performance (id, url, time_taken, backend_time, frontend_time) VALUES
+ (1, ?, 1200, 500, 700),
+ (2, ?, 800, 400, 400),
+ (3, ?, 600, NULL, NULL)',
+ [
+ 'https://example.org/wiki/HomePage',
+ 'https://example.org/wiki/HomePage',
+ 'https://example.org/wiki/AnotherPage',
+ ]
+ );
+ }
+
+ public function testAddRecordStoresBackendAndFrontendTimes(): void
+ {
+ $this->lib->addRecord('https://example.org/wiki/NewPage', 1500, 600, 900);
+
+ $row = \TikiDb::get()->table('tiki_performance')->fetchRow(
+ ['url', 'time_taken', 'backend_time', 'frontend_time'],
+ ['url' => 'https://example.org/wiki/NewPage']
+ );
+
+ $this->assertSame('https://example.org/wiki/NewPage', $row['url']);
+ $this->assertSame(1500, (int) $row['time_taken']);
+ $this->assertSame(600, (int) $row['backend_time']);
+ $this->assertSame(900, (int) $row['frontend_time']);
+ }
+
+ public function testGetRequestDetailsByUrlReturnsAggregateValues(): void
+ {
+ $stats = $this->lib->getRequestDetailsByUrl('https://example.org/wiki/HomePage');
+
+ $this->assertSame('https://example.org/wiki/HomePage', $stats['url']);
+ $this->assertSame(2, (int) $stats['number_of_requests']);
+ $this->assertSame(1000, (int) $stats['average_time_taken']);
+ $this->assertSame(800, (int) $stats['minimum_time_taken']);
+ $this->assertSame(1200, (int) $stats['maximum_time_taken']);
+ $this->assertSame(450, (int) $stats['average_backend_time']);
+ $this->assertSame(550, (int) $stats['average_frontend_time']);
+ $this->assertSame(2, (int) $stats['breakdown_samples']);
+ }
+
+ public function testGetSlowestSamplesByUrlReturnsRowsSortedByTimeTakenDesc(): void
+ {
+ $rows = $this->lib->getSlowestSamplesByUrl('https://example.org/wiki/HomePage', 2);
+
+ $this->assertCount(2, $rows);
+ $this->assertSame(1200, (int) $rows[0]['time_taken']);
+ $this->assertSame(800, (int) $rows[1]['time_taken']);
+ }
+}
=====================================
templates/tiki-performance_stats.tpl
=====================================
@@ -19,8 +19,12 @@
{foreach from=$average_load_time_stats item=stat}
<tr>
<td class="text"><a href="{$stat.url}">{$performance_stats_lib->simplifyURL($stat.url)}</a></td>
- <td class="integer">{$stat.number_of_requests}</td>
- <td class="integer">{$stat.average_time_taken / 1000}</td>
+ <td class="integer">
+ <a href="tiki-performance_stats.php?find={$find|escape:url}&average_stat_offset={$average_stat_offset}&maximum_stat_offset={$maximum_stat_offset}&average_stat_order={$average_stat_order}&maximum_stat_order={$maximum_stat_order}&details_url={$stat.url|escape:url}#request-details">{$stat.number_of_requests}</a>
+ </td>
+ <td class="integer">
+ <a href="tiki-performance_stats.php?find={$find|escape:url}&average_stat_offset={$average_stat_offset}&maximum_stat_offset={$maximum_stat_offset}&average_stat_order={$average_stat_order}&maximum_stat_order={$maximum_stat_order}&details_url={$stat.url|escape:url}#request-details">{$stat.average_time_taken / 1000}</a>
+ </td>
</tr>
{/foreach}
</table>
@@ -40,9 +44,117 @@
{foreach from=$maximum_load_time_stats item=stat}
<tr>
<td class="text"><a href="{$stat.url}">{$performance_stats_lib->simplifyURL($stat.url)}</a></td>
- <td class="integer">{$stat.maximum_time_taken / 1000}</td>
+ <td class="integer">
+ <a href="tiki-performance_stats.php?find={$find|escape:url}&average_stat_offset={$average_stat_offset}&maximum_stat_offset={$maximum_stat_offset}&average_stat_order={$average_stat_order}&maximum_stat_order={$maximum_stat_order}&details_url={$stat.url|escape:url}#request-details">{$stat.maximum_time_taken / 1000}</a>
+ </td>
</tr>
{/foreach}
</table>
</div>
{pagination_links count=$pages_count step=25 offset=$maximum_stat_offset offset_arg="maximum_stat_offset"}{/pagination_links}
+
+{if $details_url}
+ <hr>
+ <h5 id="request-details">{tr}Request details{/tr}</h5>
+
+ {if $request_detail_summary}
+ <p>
+ <strong>{tr}URL:{/tr}</strong>
+ <a href="{$details_url}">{$performance_stats_lib->simplifyURL($details_url)}</a>
+ </p>
+
+ <div class="table-responsive">
+ <table class="table">
+ <tr>
+ <th>{tr}Metric{/tr}</th>
+ <th class="text-end">{tr}Value{/tr}</th>
+ </tr>
+ <tr>
+ <td>{tr}Requests collected{/tr}</td>
+ <td class="text-end">{$request_detail_summary.number_of_requests}</td>
+ </tr>
+ <tr>
+ <td>{tr}Average total load time (seconds){/tr}</td>
+ <td class="text-end">{$request_detail_summary.average_time_taken / 1000}</td>
+ </tr>
+ <tr>
+ <td>{tr}Minimum total load time (seconds){/tr}</td>
+ <td class="text-end">{$request_detail_summary.minimum_time_taken / 1000}</td>
+ </tr>
+ <tr>
+ <td>{tr}Maximum total load time (seconds){/tr}</td>
+ <td class="text-end">{$request_detail_summary.maximum_time_taken / 1000}</td>
+ </tr>
+ <tr>
+ <td>{tr}Average backend response (seconds){/tr}</td>
+ <td class="text-end">
+ {if $request_detail_summary.average_backend_time ne null}
+ {$request_detail_summary.average_backend_time / 1000}
+ {else}
+ {tr}n/a{/tr}
+ {/if}
+ </td>
+ </tr>
+ <tr>
+ <td>{tr}Average frontend render (seconds){/tr}</td>
+ <td class="text-end">
+ {if $request_detail_summary.average_frontend_time ne null}
+ {$request_detail_summary.average_frontend_time / 1000}
+ {else}
+ {tr}n/a{/tr}
+ {/if}
+ </td>
+ </tr>
+ <tr>
+ <td>{tr}Samples with timing breakdown{/tr}</td>
+ <td class="text-end">{$request_detail_summary.breakdown_samples}</td>
+ </tr>
+ </table>
+ </div>
+
+ <h6>{tr}Slowest samples{/tr}</h6>
+ <div class="table-responsive">
+ <table class="table">
+ <tr>
+ <th>{tr}Sample ID{/tr}</th>
+ <th class="text-end">{tr}Total (seconds){/tr}</th>
+ <th class="text-end">{tr}Backend (seconds){/tr}</th>
+ <th class="text-end">{tr}Frontend (seconds){/tr}</th>
+ <th class="text-end">{tr}Other (seconds){/tr}</th>
+ </tr>
+ {foreach from=$request_detail_samples item=sample}
+ <tr>
+ <td>{$sample.id}</td>
+ <td class="text-end">{$sample.time_taken / 1000}</td>
+ <td class="text-end">
+ {if $sample.backend_time ne null}
+ {$sample.backend_time / 1000}
+ {else}
+ {tr}n/a{/tr}
+ {/if}
+ </td>
+ <td class="text-end">
+ {if $sample.frontend_time ne null}
+ {$sample.frontend_time / 1000}
+ {else}
+ {tr}n/a{/tr}
+ {/if}
+ </td>
+ <td class="text-end">
+ {if $sample.backend_time ne null && $sample.frontend_time ne null}
+ {($sample.time_taken - $sample.backend_time - $sample.frontend_time) / 1000}
+ {else}
+ {tr}n/a{/tr}
+ {/if}
+ </td>
+ </tr>
+ {/foreach}
+ </table>
+ </div>
+ <p class="help-block">
+ {tr}Use this breakdown to distinguish backend bottlenecks from frontend/network effects. "Other" is the part of total time not explained by backend + frontend timings.{/tr}
+ </p>
+ {else}
+ <div class="alert alert-warning">{tr}No records were found for the selected URL.{/tr}</div>
+ {/if}
+{/if}
=====================================
tiki-performance_stats.php
=====================================
@@ -14,6 +14,7 @@ $inputConfiguration = [
'maximum_stat_offset' => 'digits', //get
'maximum_stat_order' => 'text', //get
'no_of_requests' => 'alpha', //get
+ 'details_url' => 'text', //get
],
],
];
@@ -30,6 +31,7 @@ if (! empty($_REQUEST['clear']) && $access->checkCsrf()) {
$find = $_REQUEST['find'] ?? '';
$averageStatOffset = $_REQUEST['average_stat_offset'] ?? 0;
$maximumStatOffset = $_REQUEST['maximum_stat_offset'] ?? 0;
+$detailsUrl = $_REQUEST['details_url'] ?? '';
/**
* Validates a sort direction ('ASC' or 'DESC').
@@ -79,5 +81,8 @@ $smarty->assign_by_ref('maximum_stat_offset', $maximumStatOffset);
$smarty->assign_by_ref('maximum_stat_order', $maximumStatOrder);
$smarty->assign_by_ref('average_load_time_stats', $performanceLib->getRequestsBasedOnAverageRequestTime(25, $averageStatOffset, $find, $averageStatOrder, $orderType)->result);
$smarty->assign_by_ref('maximum_load_time_stats', $performanceLib->getRequestsBasedOnMaximumProcessingTime(25, $maximumStatOffset, $find, $maximumStatOrder)->result);
+$smarty->assign('details_url', $detailsUrl);
+$smarty->assign('request_detail_summary', $detailsUrl ? $performanceLib->getRequestDetailsByUrl($detailsUrl) : false);
+$smarty->assign('request_detail_samples', $detailsUrl ? $performanceLib->getSlowestSamplesByUrl($detailsUrl, 25) : []);
$smarty->assign('mid', 'tiki-performance_stats.tpl');
$smarty->display("tiki.tpl");
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/f37cd7c4345108a1ca6e5fa786c355f576a98d04
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/f37cd7c4345108a1ca6e5fa786c355f576a98d04
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs