Replaced timings in the footer with a visual for the Server-Timing header.
This commit is contained in:
parent
ceb6bece09
commit
83068a4183
17 changed files with 246 additions and 65 deletions
assets
composer.lockmisuzu.phppublic
src
templates
tools
|
@ -22,3 +22,4 @@ html, body {
|
|||
}
|
||||
|
||||
@include loading.css;
|
||||
@include perf.css;
|
||||
|
|
100
assets/common.css/perf.css
Normal file
100
assets/common.css/perf.css
Normal file
|
@ -0,0 +1,100 @@
|
|||
.msz-perfs {
|
||||
position: fixed;
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
flex-direction: column-reverse;
|
||||
align-items: flex-start;
|
||||
opacity: .5;
|
||||
}
|
||||
.msz-perfs-right {
|
||||
left: initial;
|
||||
right: 4px;
|
||||
}
|
||||
.msz-perfs:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.msz-perf {
|
||||
background-color: #111d;
|
||||
color: #fff;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.msz-perfs:hover .msz-perf {
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.msz-perf-number {
|
||||
color: #fff;
|
||||
}
|
||||
.msz-perf-unit {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.msz-perf-header {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
gap: 4px;
|
||||
}
|
||||
.msz-perf:hover .msz-perf-header {
|
||||
border-bottom: 1px solid #888;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.msz-perf-type {
|
||||
flex: 0 0 auto;
|
||||
font-weight: 700;
|
||||
min-width: 60px;
|
||||
}
|
||||
.msz-perf-type-navigation {
|
||||
color: #f0f;
|
||||
}
|
||||
.msz-perf-type-other {
|
||||
color: #0ff;
|
||||
}
|
||||
|
||||
.msz-perf-target {
|
||||
flex: 1 0 auto;
|
||||
min-width: 200px;
|
||||
}
|
||||
.msz-perf-target-host,
|
||||
.msz-perf-target-path,
|
||||
.msz-perf-target-query {
|
||||
display: inline-block;
|
||||
}
|
||||
.msz-perf-target-host,
|
||||
.msz-perf-target-query {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.msz-perf-total {
|
||||
flex: 0 0 auto;
|
||||
min-width: 80px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.msz-perf-timings {
|
||||
display: none;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
.msz-perf:hover .msz-perf-timings {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.msz-perf-timing-name {
|
||||
font-weight: 700;
|
||||
min-width: 60px;
|
||||
}
|
||||
.msz-perf-timing-comment {
|
||||
color: #888;
|
||||
}
|
||||
.msz-perf-timing-duration {
|
||||
min-width: 80px;
|
||||
text-align: right;
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
#include csrf.js
|
||||
#include html.js
|
||||
#include meta.js
|
||||
#include perf.jsx
|
||||
#include uniqstr.js
|
||||
#include xhr.js
|
||||
|
||||
|
|
72
assets/common.js/perf.jsx
Normal file
72
assets/common.js/perf.jsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
#include html.js
|
||||
|
||||
(() => {
|
||||
const perfs = <div class="msz-perfs"/>;
|
||||
perfs.ondblclick = () => {
|
||||
perfs.classList.toggle('msz-perfs-right');
|
||||
};
|
||||
|
||||
const appendReal = elem => {
|
||||
$appendChild(perfs, elem);
|
||||
};
|
||||
|
||||
let append = elem => {
|
||||
append = appendReal;
|
||||
$appendChild(document.body, perfs);
|
||||
appendReal(elem);
|
||||
};
|
||||
|
||||
(new PerformanceObserver(list => {
|
||||
for(const entry of list.getEntries()) {
|
||||
if(entry.serverTiming.length < 1)
|
||||
break;
|
||||
|
||||
const url = new URL(entry.name);
|
||||
|
||||
let total = 0;
|
||||
let queries = -1;
|
||||
const timings = <table class="msz-perf-timings"/>;
|
||||
for(const timing of entry.serverTiming) {
|
||||
if(timing.name === 'msz-queries') {
|
||||
queries = Math.ceil(timing.duration);
|
||||
continue;
|
||||
}
|
||||
|
||||
total += timing.duration;
|
||||
$appendChild(timings, <tr class="msz-perf-timing">
|
||||
<td class="msz-perf-timing-name">{timing.name}</td>
|
||||
<td class="msz-perf-timing-comment">{decodeURIComponent(timing.description)}</td>
|
||||
<td class="msz-perf-timing-duration">
|
||||
<span class="msz-perf-number">{timing.duration}</span>
|
||||
<span class="msz-perf-unit">ms</span>
|
||||
</td>
|
||||
</tr>);
|
||||
}
|
||||
|
||||
append(<div class="msz-perf">
|
||||
<div class="msz-perf-header">
|
||||
<div class={`msz-perf-type msz-perf-type-${entry instanceof PerformanceNavigationTiming ? 'navigation' : 'other'}`}>
|
||||
{entry instanceof PerformanceNavigationTiming ? entry.type : (
|
||||
entry.initiatorType === 'xmlhttprequest' ? 'xhr' : entry.initiatorType
|
||||
)}
|
||||
</div>
|
||||
<div class="msz-perf-target">
|
||||
{url.host !== location.host ? <div class="msz-perf-target-host">{url.host}</div> : null}
|
||||
<div class="msz-perf-target-path">{url.pathname}</div>
|
||||
{url.search !== '' ? <div class="msz-perf-target-query">{url.search}</div> : null}
|
||||
</div>
|
||||
{queries > 0 ? <div class="msz-perf-total">
|
||||
<span class="msz-perf-number">{queries}</span>
|
||||
{' '}
|
||||
<span class="msz-perf-unit">{queries === 1 ? 'query' : 'queries'}</span>
|
||||
</div> : null}
|
||||
<div class="msz-perf-total">
|
||||
<span class="msz-perf-number">{total.toFixed(5)}</span>
|
||||
<span class="msz-perf-unit">ms</span>
|
||||
</div>
|
||||
</div>
|
||||
{timings}
|
||||
</div>);
|
||||
}
|
||||
})).observe({ entryTypes: ['navigation', 'resource'] });
|
||||
})();
|
6
composer.lock
generated
6
composer.lock
generated
|
@ -501,11 +501,11 @@
|
|||
},
|
||||
{
|
||||
"name": "flashwave/index",
|
||||
"version": "v0.2504.21040",
|
||||
"version": "v0.2504.31541",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://patchii.net/flash/index.git",
|
||||
"reference": "42adbe5da8325d66a996e5d69dbb01def80743fc"
|
||||
"reference": "c2bcb0611bee08a9a21930b92c96cc85304562b6"
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
|
@ -554,7 +554,7 @@
|
|||
],
|
||||
"description": "Composer package for the common library for my projects.",
|
||||
"homepage": "https://railgun.sh/index",
|
||||
"time": "2025-04-02T10:41:10+00:00"
|
||||
"time": "2025-04-03T15:42:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
|
|
50
misuzu.php
50
misuzu.php
|
@ -1,31 +1,41 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
define('MSZ_STARTUP', microtime(true));
|
||||
define('MSZ_STARTUP', hrtime(true));
|
||||
define('MSZ_ROOT', __DIR__);
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
\Dotenv\Dotenv::createImmutable(Misuzu::PATH_ROOT)->load();
|
||||
$msz = (function() {
|
||||
$timings = new \Index\Performance\Timings(
|
||||
new \Index\Performance\Stopwatch(MSZ_STARTUP),
|
||||
);
|
||||
|
||||
if(!empty($_ENV['SENTRY_DSN']))
|
||||
\Sentry\init(['dsn' => $_ENV['SENTRY_DSN']]);
|
||||
try {
|
||||
\Dotenv\Dotenv::createImmutable(Misuzu::PATH_ROOT)->load();
|
||||
|
||||
(function(\Whoops\RunInterface $whoops) {
|
||||
if(Misuzu::cli())
|
||||
$whoops->pushHandler(new Whoops\SentryPlainTextHandler);
|
||||
elseif(Misuzu::debug())
|
||||
$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
|
||||
else
|
||||
$whoops->pushHandler(new Whoops\SentryPageHandler);
|
||||
if(!empty($_ENV['SENTRY_DSN']))
|
||||
\Sentry\init(['dsn' => $_ENV['SENTRY_DSN']]);
|
||||
|
||||
$whoops->register();
|
||||
})(new \Whoops\Run);
|
||||
$whoops = new \Whoops\Run;
|
||||
if(Misuzu::cli())
|
||||
$whoops->pushHandler(new Whoops\SentryPlainTextHandler);
|
||||
elseif(Misuzu::debug())
|
||||
$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
|
||||
else
|
||||
$whoops->pushHandler(new Whoops\SentryPageHandler);
|
||||
|
||||
$msz = new MisuzuContext(
|
||||
$_ENV['DATABASE_DSN'] ?? 'null:',
|
||||
$_ENV['DOMAIN_ROLES'] ?? 'localhost=main,redirect,storage',
|
||||
$_ENV['STORAGE_PATH_LOCAL'] ?? Misuzu::PATH_STORAGE,
|
||||
$_ENV['STORAGE_PATH_REMOTE'] ?? '/_storage',
|
||||
$_ENV['TEMPLATE_CACHE'] ?? '',
|
||||
);
|
||||
$whoops->register();
|
||||
|
||||
return new MisuzuContext(
|
||||
$timings,
|
||||
$_ENV['DATABASE_DSN'] ?? 'null:',
|
||||
$_ENV['DOMAIN_ROLES'] ?? 'localhost=main,redirect,storage',
|
||||
$_ENV['STORAGE_PATH_LOCAL'] ?? Misuzu::PATH_STORAGE,
|
||||
$_ENV['STORAGE_PATH_REMOTE'] ?? '/_storage',
|
||||
$_ENV['TEMPLATE_CACHE'] ?? '',
|
||||
);
|
||||
} finally {
|
||||
$timings->lap('startup');
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -27,6 +27,7 @@ if(is_file($msz->dbCtx->getMigrateLockPath())) {
|
|||
}
|
||||
|
||||
$request = \Index\Http\HttpRequest::fromRequest();
|
||||
$msz->timings->lap('request');
|
||||
$msz->registerRequestRoutes($request);
|
||||
|
||||
if($msz->routingCtx->domainRoles->hasRole($request->getHeaderLine('Host'), 'main')) {
|
||||
|
|
|
@ -169,10 +169,6 @@ class AuthInfo implements LastModifiedExtensionInterface {
|
|||
return $this->usersCtx->tryGetActiveBan($this->userInfo);
|
||||
}
|
||||
|
||||
public function getDisplayTimings(): bool {
|
||||
return Misuzu::debug() || $this->getPerms('global')->check(Perm::G_TIMINGS_VIEW);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getFunctions() {
|
||||
return [
|
||||
|
@ -181,7 +177,6 @@ class AuthInfo implements LastModifiedExtensionInterface {
|
|||
new TwigFunction('auth_impersonating', $this->getImpersonating(...)),
|
||||
new TwigFunction('auth_get_real_user_info', $this->getRealUserInfo(...)),
|
||||
new TwigFunction('auth_get_ban_info', $this->getBanInfo(...)),
|
||||
new TwigFunction('auth_display_timings', $this->getDisplayTimings(...)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,9 @@ use Index\Db\{DbBackends,DbConnection};
|
|||
use Index\Db\Migration\{DbMigrationManager,DbMigrationRepo,FsDbMigrationRepo};
|
||||
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
|
||||
use Index\Http\Routing\Filters\PrefixFilter;
|
||||
use Index\Templating\Extension\TplExtensionCommon;
|
||||
use Twig\TwigFunction;
|
||||
use Twig\Extension\LastModifiedExtensionInterface;
|
||||
|
||||
class DatabaseContext implements RouteHandler, LastModifiedExtensionInterface {
|
||||
use RouteHandlerCommon, TplExtensionCommon;
|
||||
class DatabaseContext implements RouteHandler {
|
||||
use RouteHandlerCommon;
|
||||
|
||||
public private(set) DbConnection $conn;
|
||||
|
||||
|
@ -49,11 +46,4 @@ class DatabaseContext implements RouteHandler, LastModifiedExtensionInterface {
|
|||
if(is_file($this->getMigrateLockPath()))
|
||||
return 503;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getFunctions() {
|
||||
return [
|
||||
new TwigFunction('sql_query_count', $this->getQueryCount(...)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use Index\Config\Db\DbConfig;
|
|||
use Index\Http\{HttpRequest,HttpResponseBuilder};
|
||||
use Index\Http\Routing\{Router,RouteHandler,RouteHandlerCommon};
|
||||
use Index\Http\Routing\Filters\PrefixFilter;
|
||||
use Index\Performance\Timings;
|
||||
use Index\Snowflake\{BinarySnowflake,RandomSnowflake,SnowflakeGenerator};
|
||||
use Index\Urls\{ArrayUrlRegistry,UrlRegistry,UrlSource};
|
||||
|
||||
|
@ -52,6 +53,7 @@ class MisuzuContext implements RouteHandler {
|
|||
public private(set) Users\UsersContext $usersCtx;
|
||||
|
||||
public function __construct(
|
||||
public private(set) Timings $timings,
|
||||
#[\SensitiveParameter] string $dsn,
|
||||
string $domainRoles,
|
||||
string $storageLocalPath,
|
||||
|
@ -61,6 +63,7 @@ class MisuzuContext implements RouteHandler {
|
|||
$this->deps = new Dependencies;
|
||||
$this->deps->register($this);
|
||||
$this->deps->register($this->deps);
|
||||
$this->deps->register($timings);
|
||||
|
||||
$this->deps->register($this->dbCtx = new DatabaseContext($dsn));
|
||||
$this->deps->register($this->dbCtx->conn);
|
||||
|
|
|
@ -6,8 +6,11 @@ use Index\Config\Config;
|
|||
use Index\Http\{HttpRequest,HttpResponseBuilder};
|
||||
use Index\Http\Routing\{Router,RouteHandler as IndexRouteHandler};
|
||||
use Index\Http\Routing\Filters\FilterInfo;
|
||||
use Index\Performance\Timings;
|
||||
use Index\Templating\Extension\TplExtensionCommon;
|
||||
use Index\Urls\{ArrayUrlRegistry,UrlRegistry,UrlSource};
|
||||
use Misuzu\{DatabaseContext,Misuzu,Perm};
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Twig\TwigFunction;
|
||||
use Twig\Extension\LastModifiedExtensionInterface;
|
||||
|
||||
|
@ -20,6 +23,9 @@ class RoutingContext implements LastModifiedExtensionInterface {
|
|||
public private(set) DomainRoles $domainRoles;
|
||||
|
||||
public function __construct(
|
||||
private Timings $timings,
|
||||
private AuthInfo $authInfo,
|
||||
private DatabaseContext $dbCtx,
|
||||
Dependencies $deps,
|
||||
string $domainRoles,
|
||||
) {
|
||||
|
@ -47,7 +53,21 @@ class RoutingContext implements LastModifiedExtensionInterface {
|
|||
}
|
||||
|
||||
public function dispatch(?HttpRequest $request = null): void {
|
||||
$this->router->dispatch($request);
|
||||
if($request === null) {
|
||||
$request = HttpRequest::fromRequest();
|
||||
$this->timings->lap('request');
|
||||
}
|
||||
|
||||
$response = $this->router->handle($request);
|
||||
$this->timings->lap('response');
|
||||
|
||||
if(Misuzu::debug() || $this->authInfo->getPerms('global')->check(Perm::G_TIMINGS_VIEW))
|
||||
$response = $response->withHeader(
|
||||
'Server-Timing',
|
||||
sprintf('%s, msz-queries;dur=%d', $this->timings, $this->dbCtx->getQueryCount())
|
||||
);
|
||||
|
||||
Router::output($response);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
|
|
|
@ -2,17 +2,20 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Index\Performance\Timings;
|
||||
use Index\Templating\TplEnvironment;
|
||||
|
||||
final class Template {
|
||||
private const FILE_EXT = '.twig';
|
||||
|
||||
private static Timings $timings;
|
||||
private static TplEnvironment $env;
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
private static array $vars = [];
|
||||
|
||||
public static function init(MisuzuContext $env): void {
|
||||
self::$timings = $env->timings;
|
||||
self::$env = $env->tplCtx->env;
|
||||
}
|
||||
|
||||
|
@ -22,13 +25,15 @@ final class Template {
|
|||
|
||||
/** @param array<string, mixed> $vars */
|
||||
public static function renderRaw(string $file, array $vars = []): string {
|
||||
if(!defined('MSZ_TPL_RENDER'))
|
||||
define('MSZ_TPL_RENDER', microtime(true));
|
||||
self::$timings->lap('load');
|
||||
try {
|
||||
if(!str_ends_with($file, self::FILE_EXT))
|
||||
$file = str_replace('.', DIRECTORY_SEPARATOR, $file) . self::FILE_EXT;
|
||||
|
||||
if(!str_ends_with($file, self::FILE_EXT))
|
||||
$file = str_replace('.', DIRECTORY_SEPARATOR, $file) . self::FILE_EXT;
|
||||
|
||||
return self::$env->render($file, array_merge(self::$vars, $vars));
|
||||
return self::$env->render($file, array_merge(self::$vars, $vars));
|
||||
} finally {
|
||||
self::$timings->lap('render');
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $vars */
|
||||
|
|
|
@ -44,7 +44,6 @@ final class TemplatingExtension implements LastModifiedExtensionInterface {
|
|||
new TwigFunction('git_commit_hash', GitInfo::hash(...)),
|
||||
new TwigFunction('git_tag', GitInfo::tag(...)),
|
||||
new TwigFunction('git_branch', GitInfo::branch(...)),
|
||||
new TwigFunction('startup_time', fn(float $time = MSZ_STARTUP) => microtime(true) - $time),
|
||||
new TwigFunction('msz_header_menu', $this->getHeaderMenu(...)),
|
||||
new TwigFunction('msz_user_menu', $this->getUserMenu(...)),
|
||||
new TwigFunction('msz_manage_menu', $this->getManageMenu(...)),
|
||||
|
|
|
@ -12,13 +12,6 @@
|
|||
<a href="https://patchii.net/flashii/misuzu/src/tag/{{ git_tag }}" target="_blank" rel="noopener" class="footer__link">{{ git_tag }}</a>
|
||||
{% endif %}
|
||||
# <a href="https://patchii.net/flashii/misuzu/commit/{{ git_commit_hash(true) }}" target="_blank" rel="noopener" class="footer__link">{{ git_commit_hash() }}</a>
|
||||
{% if auth_display_timings() %}
|
||||
/ Index {{ ndx_version() }}
|
||||
/ {{ sql_query_count()|number_format }} queries
|
||||
/ {{ (startup_time() - startup_time(constant('MSZ_TPL_RENDER')))|number_format(5) }} load
|
||||
/ {{ startup_time(constant('MSZ_TPL_RENDER'))|number_format(5) }} template
|
||||
/ {{ startup_time()|number_format(5) }} total
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
@ -89,11 +89,6 @@
|
|||
{% endif %}
|
||||
# <a href="https://github.com/flashwave/misuzu/commit/{{ git_commit_hash(true) }}" target="_blank" rel="noreferrer noopener">{{ git_commit_hash() }}</a>
|
||||
</div>
|
||||
{% if auth_display_timings() %}
|
||||
<div class="landingv2-footer-copyright-line">
|
||||
{{ sql_query_count()|number_format }} queries / {{ (startup_time() - startup_time(constant('MSZ_TPL_RENDER')))|number_format(5) }} load / {{ startup_time(constant('MSZ_TPL_RENDER'))|number_format(5) }} template / {{ startup_time()|number_format(5) }} total
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
@ -407,5 +407,5 @@ try {
|
|||
}
|
||||
} finally {
|
||||
unlink($cronPath);
|
||||
echo 'Took ' . number_format(microtime(true) - MSZ_STARTUP, 5) . 'ms.' . PHP_EOL;
|
||||
printf('Took %.5fms.%s', ((float)hrtime(true) - MSZ_STARTUP) / 1000000, PHP_EOL);
|
||||
}
|
||||
|
|
|
@ -86,8 +86,4 @@ $ctx = $msz->tplCtx->loadTemplate(implode(' ', array_slice($argv, $pathIndex)));
|
|||
foreach($tplArgs as $name => $value)
|
||||
$ctx->setVar($name, $value);
|
||||
|
||||
// things expect this to be defined which is also a bit yucky
|
||||
if(!defined('MSZ_TPL_RENDER'))
|
||||
define('MSZ_TPL_RENDER', microtime(true));
|
||||
|
||||
echo $ctx->render();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue