[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}&amp;average_stat_offset={$average_stat_offset}&amp;maximum_stat_offset={$maximum_stat_offset}&amp;average_stat_order={$average_stat_order}&amp;maximum_stat_order={$maximum_stat_order}&amp;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}&amp;average_stat_offset={$average_stat_offset}&amp;maximum_stat_offset={$maximum_stat_offset}&amp;average_stat_order={$average_stat_order}&amp;maximum_stat_order={$maximum_stat_order}&amp;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}&amp;average_stat_offset={$average_stat_offset}&amp;maximum_stat_offset={$maximum_stat_offset}&amp;average_stat_order={$average_stat_order}&amp;maximum_stat_order={$maximum_stat_order}&amp;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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.