mirror of
https://github.com/OPSnet/Gazelle.git
synced 2026-01-16 18:04:34 -05:00
streamline debug reporting
This commit is contained in:
@@ -7,11 +7,11 @@ uses Mysql and Sphinx, but work is under way to replace those with
|
|||||||
Postgresql.
|
Postgresql.
|
||||||
|
|
||||||
## Gazelle Runtime Dependencies
|
## Gazelle Runtime Dependencies
|
||||||
* [PHP 8.2.13+](https://www.php.net/) (required)
|
* [PHP 8.4.6+](https://www.php.net/) (required)
|
||||||
* [nginx](http://wiki.nginx.org/Main) (required)
|
* [nginx](http://wiki.nginx.org/Main) (required)
|
||||||
* [nodeJS 20+](https://nodejs.org/en/) (required)
|
* [nodeJS 20+](https://nodejs.org/en/) (required)
|
||||||
* [memcached](http://memcached.org/) (required)
|
* [memcached](http://memcached.org/) (required)
|
||||||
* [sphinx 2.1.1 or newer](http://sphinxsearch.com/) (required)
|
* [sphinx 2.2.1 or newer](http://sphinxsearch.com/) (required)
|
||||||
* [ocelot](https://github.com/OPSnet/Ocelot) (optional)
|
* [ocelot](https://github.com/OPSnet/Ocelot) (optional)
|
||||||
|
|
||||||
## Logchecker
|
## Logchecker
|
||||||
|
|||||||
152
app/Debug.php
152
app/Debug.php
@@ -5,10 +5,6 @@ namespace Gazelle;
|
|||||||
use Gazelle\Util\{Irc, Time};
|
use Gazelle\Util\{Irc, Time};
|
||||||
|
|
||||||
class Debug {
|
class Debug {
|
||||||
protected const MAX_TIME = 20000;
|
|
||||||
protected const MAX_ERRORS = 0; //Maxmimum errors, warnings, notices we will allow in a page
|
|
||||||
protected const MAX_MEMORY = 80 * 1024 * 1024; //Maximum memory used per pageload
|
|
||||||
|
|
||||||
protected static int $caseCount = 0;
|
protected static int $caseCount = 0;
|
||||||
protected static array $Errors = [];
|
protected static array $Errors = [];
|
||||||
protected static array $markList = [];
|
protected static array $markList = [];
|
||||||
@@ -21,10 +17,15 @@ class Debug {
|
|||||||
) {
|
) {
|
||||||
$this->epochStart = microtime(true);
|
$this->epochStart = microtime(true);
|
||||||
$this->cpuStart = $this->cpuElapsed();
|
$this->cpuStart = $this->cpuElapsed();
|
||||||
error_reporting(E_WARNING | E_ERROR | E_PARSE);
|
|
||||||
set_error_handler($this->errorHandler(...));
|
set_error_handler($this->errorHandler(...));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function flush(): int {
|
||||||
|
$cleared = count(self::$Errors);
|
||||||
|
self::$Errors = [];
|
||||||
|
return $cleared;
|
||||||
|
}
|
||||||
|
|
||||||
public function epochStart(): float {
|
public function epochStart(): float {
|
||||||
return $this->epochStart;
|
return $this->epochStart;
|
||||||
}
|
}
|
||||||
@@ -33,42 +34,27 @@ class Debug {
|
|||||||
return microtime(true) - $this->epochStart();
|
return microtime(true) - $this->epochStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function profile(User $user, string $document, string $Automatic = ''): bool {
|
public function profile(User $user, bool $profile): ErrorLog|null {
|
||||||
$Reason = [];
|
$reason = [];
|
||||||
|
|
||||||
if (!empty($Automatic)) {
|
|
||||||
$Reason[] = $Automatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
$Micro = $this->duration() * 1000;
|
|
||||||
if ($Micro > self::MAX_TIME && !in_array($document, IGNORE_PAGE_MAX_TIME)) {
|
|
||||||
$Reason[] = number_format($Micro, 3) . ' ms';
|
|
||||||
}
|
|
||||||
|
|
||||||
$errorTotal = count($this->errorList());
|
$errorTotal = count($this->errorList());
|
||||||
if ($errorTotal > self::MAX_ERRORS) {
|
if ($errorTotal > 0) {
|
||||||
$Reason[] = "$errorTotal PHP errors";
|
$reason[] = "$errorTotal PHP errors";
|
||||||
}
|
|
||||||
$Ram = memory_get_usage(true);
|
|
||||||
if ($Ram > self::MAX_MEMORY && !in_array($document, IGNORE_PAGE_MAX_MEMORY)) {
|
|
||||||
$Reason[] = byte_format($Ram) . ' RAM used';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->db->loadPreviousWarning(); // see comment in MYSQL::query
|
$this->db->loadPreviousWarning(); // see comment in MYSQL::query
|
||||||
|
|
||||||
if (isset($_REQUEST['profile'])) {
|
if ($profile) {
|
||||||
$Reason[] = 'Requested by ' . $user->username();
|
$reason[] = "Requested by {$user->username()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($Reason[0])) {
|
if ($reason !== []) {
|
||||||
$this->analysis(
|
return $this->analysis(
|
||||||
$user->requestContext()->module(),
|
$user->requestContext()->module(),
|
||||||
implode(', ', $Reason)
|
implode(', ', $reason)
|
||||||
);
|
);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveCase(
|
public function saveCase(
|
||||||
@@ -122,14 +108,14 @@ class Debug {
|
|||||||
return $errorLog;
|
return $errorLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function analysis(string $module, string $message, string $report = ''): void {
|
public function analysis(string $module, string $message, string $report = ''): ErrorLog|null {
|
||||||
$uri = empty($_SERVER['REQUEST_URI']) ? '' : substr($_SERVER['REQUEST_URI'], 1);
|
$uri = empty($_SERVER['REQUEST_URI']) ? '' : substr($_SERVER['REQUEST_URI'], 1);
|
||||||
if (
|
if (
|
||||||
PHP_SAPI === 'cli'
|
(PHP_SAPI === 'cli' && !str_ends_with($_SERVER["SCRIPT_NAME"], "vendor/bin/phpunit"))
|
||||||
|| in_array($uri, ['tools.php?action=db_sandbox'])
|
|| in_array($uri, ['tools.php?action=db_sandbox'])
|
||||||
) {
|
) {
|
||||||
// Don't spam IRC from Boris or these pages
|
// Don't spam IRC from Boris or these pages
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
if (empty($report)) {
|
if (empty($report)) {
|
||||||
$report = $message;
|
$report = $message;
|
||||||
@@ -139,6 +125,7 @@ class Debug {
|
|||||||
. SITE_URL . "/tools.php?action=analysis&case={$case->id} "
|
. SITE_URL . "/tools.php?action=analysis&case={$case->id} "
|
||||||
. SITE_URL . "/{$uri}"
|
. SITE_URL . "/{$uri}"
|
||||||
);
|
);
|
||||||
|
return $case;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveError(\Error|\Exception $e): ErrorLog {
|
public function saveError(\Error|\Exception $e): ErrorLog {
|
||||||
@@ -286,4 +273,103 @@ class Debug {
|
|||||||
'Script end' => Time::sqlTime(microtime(true)),
|
'Script end' => Time::sqlTime(microtime(true)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A list of keys to represent durations (50ms, 100ms, 200ms ... 1000ms and then 1s increments up to 60s) */
|
||||||
|
public function durationHistogramKeyList(): array {
|
||||||
|
return array_map(
|
||||||
|
fn ($t) => sprintf('exec_d_%05d', $t),
|
||||||
|
[
|
||||||
|
50,
|
||||||
|
...array_map(
|
||||||
|
fn ($t) => $t * 100,
|
||||||
|
range(1, 10),
|
||||||
|
),
|
||||||
|
...array_map(
|
||||||
|
fn ($t) => $t * 1000,
|
||||||
|
range(2, 60),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A list of keys to represent memory sizes in 1MiB increments */
|
||||||
|
public function memoryHistogramKeyList(): array {
|
||||||
|
return array_map(
|
||||||
|
fn ($t) => sprintf('exec_m_%03d', $t),
|
||||||
|
range(1, 128),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* initialise the duration histogram */
|
||||||
|
public function initDurationHistogram(): bool {
|
||||||
|
return $this->cache->setMulti(
|
||||||
|
array_fill_keys($this->durationHistogramKeyList(), 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* initialise the memory histogram */
|
||||||
|
public function initMemoryHistogram(): bool {
|
||||||
|
return $this->cache->setMulti(
|
||||||
|
array_fill_keys($this->memoryHistogramKeyList(), 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function durationRound(float $duration): int {
|
||||||
|
return match (true) {
|
||||||
|
$duration <= 50000 => 50,
|
||||||
|
$duration < 1000000 => (int)ceil($duration / 100_000) * 100,
|
||||||
|
default => (int)ceil($duration / 1_000_000) * 1000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function memoryRound(float $size): int {
|
||||||
|
return (int)($size / (1024 * 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* increment the appropriate bucket for the given duration */
|
||||||
|
public function storeDuration(float $duration): int {
|
||||||
|
$rounded = $this->durationRound($duration);
|
||||||
|
return $this->cache->increment(sprintf('exec_d_%05d', $rounded)) === false
|
||||||
|
? 0
|
||||||
|
: $rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* increment the appropriate bucket for the given duration */
|
||||||
|
public function storeMemory(float $memory): int {
|
||||||
|
$rounded = $this->memoryRound($memory);
|
||||||
|
return $this->cache->increment(sprintf('exec_m_%03d', $rounded)) === false
|
||||||
|
? 0
|
||||||
|
: $rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the duration histogram, an assocative array of msec => count pairs */
|
||||||
|
public function durationHistogram(): array {
|
||||||
|
$histogram = $this->cache->getMulti($this->durationHistogramKeyList());
|
||||||
|
if ($histogram === false) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$result = [];
|
||||||
|
foreach ($histogram as $key => $value) {
|
||||||
|
$key = (string)$key;
|
||||||
|
$duration = (int)substr($key, (int)strrpos($key, '_') + 1);
|
||||||
|
$result[$duration < 1000 ? "{$duration}ms" : ($duration / 1000) . "s"] = $value;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the duration histogram, an assocative array of msec => count pairs */
|
||||||
|
public function memoryHistogram(): array {
|
||||||
|
$histogram = $this->cache->getMulti($this->memoryHistogramKeyList());
|
||||||
|
if ($histogram === false) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$result = [];
|
||||||
|
foreach ($histogram as $key => $value) {
|
||||||
|
$key = (string)$key;
|
||||||
|
$result[((int)substr($key, (int)strrpos($key, '_') + 1)) . "MiB"] = $value;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
/** @phpstan-var \Gazelle\Debug $Debug */
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
@@ -7,6 +8,9 @@ namespace Gazelle;
|
|||||||
abstract class Error extends Base {
|
abstract class Error extends Base {
|
||||||
public static function error(string $message = ''): never {
|
public static function error(string $message = ''): never {
|
||||||
echo static::payload($message);
|
echo static::payload($message);
|
||||||
|
global $Debug;
|
||||||
|
$Debug->storeMemory(memory_get_usage(true));
|
||||||
|
$Debug->storeDuration($Debug->duration() * 1000000);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,11 @@ class Twig {
|
|||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
$twig->addFilter(new \Twig\TwigFilter(
|
||||||
|
'values',
|
||||||
|
fn ($list) => array_values($list)
|
||||||
|
));
|
||||||
|
|
||||||
$twig->addFunction(new \Twig\TwigFunction('header', fn ($title, $options = []) => new \Twig\Markup(
|
$twig->addFunction(new \Twig\TwigFunction('header', fn ($title, $options = []) => new \Twig\Markup(
|
||||||
View::header($title, $options),
|
View::header($title, $options),
|
||||||
'UTF-8'
|
'UTF-8'
|
||||||
|
|||||||
@@ -103,9 +103,6 @@ if ($Viewer) {
|
|||||||
header('Location: login.php');
|
header('Location: login.php');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
if ($Viewer->hasAttr('admin-error-reporting')) {
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
}
|
|
||||||
if ($Viewer->permitted('site_disable_ip_history')) {
|
if ($Viewer->permitted('site_disable_ip_history')) {
|
||||||
$Viewer->requestContext()->anonymize();
|
$Viewer->requestContext()->anonymize();
|
||||||
}
|
}
|
||||||
@@ -177,6 +174,8 @@ try {
|
|||||||
} finally {
|
} finally {
|
||||||
$Debug->mark('send to user');
|
$Debug->mark('send to user');
|
||||||
if (!is_null($Viewer)) {
|
if (!is_null($Viewer)) {
|
||||||
$Debug->profile($Viewer, $module);
|
$Debug->profile($Viewer, isset($_REQUEST['profile']));
|
||||||
}
|
}
|
||||||
|
$Debug->storeMemory(memory_get_usage(true));
|
||||||
|
$Debug->storeDuration($Debug->duration() * 1000000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
|
|
||||||
/* require this file to have a fully-initialized Gazelle runtime */
|
/* require this file to have a fully-initialized Gazelle runtime */
|
||||||
|
|
||||||
if (PHP_VERSION_ID < 80403) {
|
if (PHP_VERSION_ID < 80406) {
|
||||||
die("Gazelle (Orpheus fork) requires at least PHP version 8.4.3");
|
die("Gazelle (Orpheus fork) requires at least PHP version 8.4.6");
|
||||||
}
|
}
|
||||||
foreach (['memcached', 'mysqli'] as $e) {
|
foreach (['memcached', 'mysqli'] as $e) {
|
||||||
if (!extension_loaded($e)) {
|
if (!extension_loaded($e)) {
|
||||||
die("$e extension not loaded");
|
die("$e extension not loaded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
error_reporting(E_ALL);
|
||||||
date_default_timezone_set('UTC');
|
date_default_timezone_set('UTC');
|
||||||
|
|
||||||
if (!defined('SITE_NAME')) {
|
if (!defined('SITE_NAME')) {
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ if (!$Viewer->permitted('admin_site_debug')) {
|
|||||||
Error403::error();
|
Error403::error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (($_GET['view'] ?? '') === 'histogram') {
|
||||||
|
global $Debug;
|
||||||
|
echo $Twig->render('admin/process-histogram.twig', [
|
||||||
|
'duration' => $Debug->durationHistogram(),
|
||||||
|
'memory' => $Debug->memoryHistogram(),
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$proc = [];
|
$proc = [];
|
||||||
if (preg_match('/.*\/(.*)/', PHP_BINARY, $match, PREG_UNMATCHED_AS_NULL)) {
|
if (preg_match('/.*\/(.*)/', PHP_BINARY, $match, PREG_UNMATCHED_AS_NULL)) {
|
||||||
$binary = $match[1];
|
$binary = $match[1];
|
||||||
|
|||||||
83
templates/admin/process-histogram.twig
Normal file
83
templates/admin/process-histogram.twig
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
{{ header('Process histograms', {'js': 'vendor/highcharts,vendor/highcharts_custom'}) }}
|
||||||
|
<div class="thin">
|
||||||
|
<div class="header">
|
||||||
|
<h3>Process histograms</h3>
|
||||||
|
</div>
|
||||||
|
<div class="linkbox">
|
||||||
|
<a href="?action=process_info" class="brackets">PHP processes</a>
|
||||||
|
</div>
|
||||||
|
<div class="box pad center">
|
||||||
|
<figure class="highcharts-figure"><div id="process-duration"></div></figure>
|
||||||
|
</div>
|
||||||
|
<div class="box pad center">
|
||||||
|
<figure class="highcharts-figure"><div id="process-memory"></div></figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
Highcharts.chart('process-duration', {
|
||||||
|
chart: {
|
||||||
|
type: 'column',
|
||||||
|
plotBackgroundColor: '#051401',
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: 'Historical Process Duration',
|
||||||
|
style: { color: '#c0c0c0', },
|
||||||
|
},
|
||||||
|
credits: { enabled: false },
|
||||||
|
xAxis: {
|
||||||
|
categories: {{ duration|keys|json_encode|raw }},
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
labels: { format: '{value}', style: { color: Highcharts.getOptions().colors[2] }},
|
||||||
|
title: { text: 'Total', style: { color: Highcharts.getOptions().colors[2] }},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
headerFormat: '<b>{point.x}</b><br/>',
|
||||||
|
pointFormat: '{series.name}: {point.y}'
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
column: { stacking: 'normal' }
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{ type: 'column', name: 'Total', data: {{ duration|values|json_encode|raw }} },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
Highcharts.chart('process-memory', {
|
||||||
|
chart: {
|
||||||
|
type: 'column',
|
||||||
|
plotBackgroundColor: '#051401',
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: 'Historical Memory Duration',
|
||||||
|
style: { color: '#c0c0c0', },
|
||||||
|
},
|
||||||
|
credits: { enabled: false },
|
||||||
|
xAxis: {
|
||||||
|
categories: {{ memory|keys|json_encode|raw }},
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
labels: { format: '{value}', style: { color: Highcharts.getOptions().colors[2] }},
|
||||||
|
title: { text: 'Total', style: { color: Highcharts.getOptions().colors[2] }},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
headerFormat: '<b>{point.x}</b><br/>',
|
||||||
|
pointFormat: '{series.name}: {point.y}'
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
column: { stacking: 'normal' }
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{ type: 'column', name: 'Total', data: {{ memory|values|json_encode|raw }} },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{ footer() }}
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
{{ header('PHP Processes') }}
|
{{ header('PHP Processes') }}
|
||||||
<div class="thin">
|
<div class="thin">
|
||||||
|
<div class="header">
|
||||||
|
<h3>PHP processes</h3>
|
||||||
|
<div class="linkbox">
|
||||||
|
<a href="?action=process_info&view=histogram" class="brackets">Performance histograms</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<table class="process_info">
|
<table class="process_info">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col class="process_info_pid" />
|
<col class="process_info_pid" />
|
||||||
@@ -7,7 +13,7 @@
|
|||||||
</colgroup>
|
</colgroup>
|
||||||
<tr class="colhead_dark">
|
<tr class="colhead_dark">
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{{ proc|length }} process{{ proc|length|plural('es') }} as of {{ now }}
|
{{ proc|length }} process{{ proc|length|plural('es') }} as of {{ 'now'|date("Y-m-d H:i:s") }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for pid, info in proc %}
|
{% for pid, info in proc %}
|
||||||
@@ -20,4 +26,5 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
{{ footer() }}
|
{{ footer() }}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace Gazelle;
|
namespace Gazelle;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
use GazelleUnitTest\Helper;
|
use GazelleUnitTest\Helper;
|
||||||
|
|
||||||
class DebugTest extends TestCase {
|
class DebugTest extends TestCase {
|
||||||
@@ -13,6 +14,71 @@ class DebugTest extends TestCase {
|
|||||||
$this->assertGreaterThan(0, $Debug->epochStart(), 'debug-epoch-start');
|
$this->assertGreaterThan(0, $Debug->epochStart(), 'debug-epoch-start');
|
||||||
$this->assertGreaterThan(350, count($Debug->includeList()), 'debug-include-list');
|
$this->assertGreaterThan(350, count($Debug->includeList()), 'debug-include-list');
|
||||||
$this->assertTrue(Helper::recentDate(date('Y-m-d H:i:s', (int)$Debug->epochStart()), 180), 'debug-recent-start');
|
$this->assertTrue(Helper::recentDate(date('Y-m-d H:i:s', (int)$Debug->epochStart()), 180), 'debug-recent-start');
|
||||||
|
|
||||||
|
$this->assertCount(70, $Debug->durationHistogramKeyList(), 'debug-duration-histo-key-list');
|
||||||
|
$this->assertTrue($Debug->initDurationHistogram(), 'debug-duration-init');
|
||||||
|
$initial = $Debug->durationHistogram();
|
||||||
|
$this->assertCount(70, $initial, 'debug-duration-histo-initial');
|
||||||
|
$this->assertEquals(60000, $Debug->storeDuration(59999999.0), 'debug-duration-store');
|
||||||
|
$after = $Debug->durationHistogram();
|
||||||
|
$this->assertEquals($initial['60s'] + 1, $after['60s'], 'debug-duration-inc');
|
||||||
|
|
||||||
|
$this->assertCount(128, $Debug->memoryHistogramKeyList(), 'debug-memory-histo-key-list');
|
||||||
|
$this->assertTrue($Debug->initMemoryHistogram(), 'debug-memory-init');
|
||||||
|
$initial = $Debug->memoryHistogram();
|
||||||
|
$this->assertCount(128, $initial, 'debug-memory-histo-initial');
|
||||||
|
$this->assertEquals(128, $Debug->storeMemory(134217728), 'debug-memory-store');
|
||||||
|
$after = $Debug->memoryHistogram();
|
||||||
|
$this->assertEquals($initial['128MiB'] + 1, $after['128MiB'], 'debug-memory-inc');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function providerDurationRound(): array {
|
||||||
|
return [
|
||||||
|
['debug-neg-dur', 50, -1.0],
|
||||||
|
['debug-0-dur', 50, 0.0],
|
||||||
|
['debug-0.1-dur', 50, 0.1],
|
||||||
|
['debug-1-dur', 50, 1.0],
|
||||||
|
['debug-10-dur', 50, 10.0],
|
||||||
|
['debug-100-dur', 50, 100.0],
|
||||||
|
['debug-1000-dur', 50, 1000.0],
|
||||||
|
['debug-10000-dur', 50, 10000.0],
|
||||||
|
['debug-49999-dur', 50, 49999.99999999],
|
||||||
|
['debug-50000-dur', 50, 50000.0],
|
||||||
|
['debug-50001-dur', 100, 50000.00000000001],
|
||||||
|
['debug-99999-dur', 100, 99999.99999999999],
|
||||||
|
['debug-100000-dur', 100, 100000.0],
|
||||||
|
['debug-100001-dur', 200, 100000.00000000001],
|
||||||
|
['debug-899999-dur', 900, 899999.99999999999],
|
||||||
|
['debug-999999-dur', 1000, 999999.99999999999],
|
||||||
|
['debug-1000000-dur', 1000, 1000000.00000000000],
|
||||||
|
['debug-1000001-dur', 2000, 1000000.0000000001],
|
||||||
|
['debug-1999999-dur', 2000, 1999999.9999999999],
|
||||||
|
['debug-19999999-dur', 20000, 19999999.9999999999],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('providerDurationRound')]
|
||||||
|
public function testDebugDurationRound(string $name, int $rounded, float $input): void {
|
||||||
|
global $Debug;
|
||||||
|
$this->assertEquals($rounded, $Debug->durationRound($input), $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function providerMemoryRound(): array {
|
||||||
|
return [
|
||||||
|
['debug-0-mem', 0, 0],
|
||||||
|
['debug-1M--mem', 0, 1048575],
|
||||||
|
['debug-1M-mem', 1, 1048576],
|
||||||
|
['debug-1M+-mem', 1, 1048577],
|
||||||
|
['debug-64M--mem', 63, 67108863],
|
||||||
|
['debug-64M-mem', 64, 67108864],
|
||||||
|
['debug-64M+-mem', 64, 67108865],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('providerMemoryRound')]
|
||||||
|
public function testDebugMemoryRound(string $name, int $rounded, float $input): void {
|
||||||
|
global $Debug;
|
||||||
|
$this->assertEquals($rounded, $Debug->memoryRound($input), $name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreate(): void {
|
public function testCreate(): void {
|
||||||
@@ -46,7 +112,7 @@ class DebugTest extends TestCase {
|
|||||||
$this->assertEquals(1, $case->remove(), 'errorlog-remove');
|
$this->assertEquals(1, $case->remove(), 'errorlog-remove');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCase(): void {
|
public function testDebugCase(): void {
|
||||||
global $Cache;
|
global $Cache;
|
||||||
$key = 'phpunit_' . randomString();
|
$key = 'phpunit_' . randomString();
|
||||||
$Cache->cache_value($key, 'phpunit', 60);
|
$Cache->cache_value($key, 'phpunit', 60);
|
||||||
@@ -84,6 +150,21 @@ class DebugTest extends TestCase {
|
|||||||
$case->remove();
|
$case->remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDebugError(): void {
|
||||||
|
$this->expectException(\DivisionByZeroError::class);
|
||||||
|
try {
|
||||||
|
$fail = 1 / 0; /** @phpstan-ignore-line binaryOp.invalid */
|
||||||
|
} catch (\DivisionByZeroError $e) {
|
||||||
|
global $Debug;
|
||||||
|
$case = $Debug->saveError($e);
|
||||||
|
$this->assertEquals('cli', $case->uri(), 'debug-error-case-uri');
|
||||||
|
$this->assertEquals('Division by zero', $case->trace()[0], 'debug-error-case-trace');
|
||||||
|
$case->remove();
|
||||||
|
// keep phpunit happy
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testMark(): void {
|
public function testMark(): void {
|
||||||
global $Debug;
|
global $Debug;
|
||||||
$message = 'phpunit-' . randomString();
|
$message = 'phpunit-' . randomString();
|
||||||
|
|||||||
@@ -562,4 +562,14 @@ class UserTest extends TestCase {
|
|||||||
);
|
);
|
||||||
$second->remove();
|
$second->remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDebugProfile(): void {
|
||||||
|
global $Debug;
|
||||||
|
$Debug->flush(); // clear out the accumulated errors
|
||||||
|
$case = $Debug->profile($this->user, false);
|
||||||
|
$this->assertNull($case, 'user-debug-profile-no-reason');
|
||||||
|
$case = $Debug->profile($this->user, true);
|
||||||
|
$this->assertInstanceOf(ErrorLog::class, $case, 'user-debug-profile-a-reason');
|
||||||
|
$case->remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user