Skip to content

Commit

Permalink
feat: standalone stress tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nunomaduro committed Nov 4, 2023
1 parent 73637ad commit 446ab49
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 220 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"license": "MIT",
"require": {
"php": "^8.2",
"pestphp/pest": "^2.24.1",
"pestphp/pest": "^2.24.2",
"pestphp/pest-plugin": "^2.1.1",
"ext-curl": "*"
},
Expand Down
12 changes: 6 additions & 6 deletions src/Blocks/ResponseDuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ public function value(): string
{
$array = $this->result->toArray();

$duration = $array['metrics']['req_connecting']['values']['avg']
+ $array['metrics']['req_tls_handshaking']['values']['avg']
+ $array['metrics']['req_duration']['values']['avg'];
$duration = $array['metrics']['http_req_connecting']['values']['avg']
+ $array['metrics']['http_req_tls_handshaking']['values']['avg']
+ $array['metrics']['http_req_duration']['values']['avg'];

return sprintf('%4.2f ms', $duration);
}
Expand All @@ -42,9 +42,9 @@ public function color(): string
{
$array = $this->result->toArray();

$duration = $array['metrics']['req_connecting']['values']['avg']
+ $array['metrics']['req_tls_handshaking']['values']['avg']
+ $array['metrics']['req_duration']['values']['avg'];
$duration = $array['metrics']['http_req_connecting']['values']['avg']
+ $array['metrics']['http_req_tls_handshaking']['values']['avg']
+ $array['metrics']['http_req_duration']['values']['avg'];

return match (true) {
$duration < 200 => 'green',
Expand Down
10 changes: 9 additions & 1 deletion src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Pest\Stressless\Fluent\WithOptions;
use Pest\Stressless\ValueObjects\Result;
use Pest\Stressless\ValueObjects\Url;
use Pest\TestSuite;

/**
* @internal
Expand Down Expand Up @@ -91,7 +92,7 @@ public function dd(): never
{
$this->dump();

exit(1);
exit(0);
}

/**
Expand Down Expand Up @@ -137,4 +138,11 @@ public function __get(string $name): mixed
{
return $this->{$name}(); // @phpstan-ignore-line
}

public function __destruct()
{
if (! $this->result instanceof Result && TestSuite::getInstance()->test === null) {
$this->dd();
}
}
}
128 changes: 128 additions & 0 deletions src/ResultPrinters/Progress.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

declare(strict_types=1);

namespace Pest\Stressless\ResultPrinters;

use Pest\Stressless\Session;
use Pest\Stressless\ValueObjects\Url;
use Symfony\Component\Process\Process;

use function Termwind\render;
use function Termwind\terminal;

/**
* @internal
*/
final readonly class Progress
{
/**
* Creates a new progress instance.
*/
public function __construct(private Process $process, private Session $session, private Url $url)
{
//
}

/**
* Tails the progress file.
*/
public function tail(): void
{
$date = date('H:i:s');
$domain = $this->url->domain();

render(<<<HTML
<div class="flex mx-2">
<span class="text-gray">$date</span>
<span class="flex-1"></span>
<span class="text-gray">Running stress test on <span class="text-blue">$domain</span></span>
</div>
HTML);

sleep(1);

$tail = new Process(['tail', '-f', $this->session->progressPath()]);

$tail
->setTty(false)
->setTimeout(null)
->start();

/** @var array<int, array{data: array{time: string, value: float}}> $points */
$points = [];

$buffer = '';
$lastTime = null;
while ($this->process->isRunning()) {
$output = $tail->getIncrementalOutput();

$output = $buffer.$output;
$buffer = '';

$lines = explode("\n", $output);

foreach ($lines as $line) {
if (str_starts_with($line, '{"metric":"http_req_duration","type":"Point"')) {
/** @var array{data: array{time: string, value: float}}|null $point */
$point = json_decode($line, true, 512, JSON_THROW_ON_ERROR);

if (is_array($point)) {
$currentTime = substr($point['data']['time'], 0, 19);
if ($lastTime !== $currentTime) {
$this->printCurrentPoints($points);
$points = [];

$lastTime = $currentTime;
}

$points[] = $point;
} else {
$buffer .= $line;
}
}
}

usleep(100000); // 100ms
}
}

/**
* Prints the current points.
*
* @param array<array{data: array{time: string, value: float}}> $points
*/
private function printCurrentPoints(array $points): void
{
static $maxResponseTime;

if ($points !== []) {
$average = array_sum(array_map(fn ($point): float => $point['data']['value'], $points)) / count($points);
$average = round($average, 2);

$time = substr($points[0]['data']['time'], 11, 8);

$width = max(0, terminal()->width());
$width = $width - 4 - strlen($time);

if ($maxResponseTime === null) {
$maxResponseTime = max($average * 3, 1000);
}

$greenDots = (int) (($average * $width) / $maxResponseTime);

$greenDots = str_repeat('', $greenDots);

render(<<<HTML
<div class="flex mx-2">
<span class="text-gray">
<span>{$time}│</span>
<span class="">$greenDots</span>
</span>
<span class="flex-1"></span>
<span class="text-gray ml-1">{$average}ms</span>
</div>
HTML);
}
}
}
114 changes: 3 additions & 111 deletions src/Run.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@

use Pest\Exceptions\ShouldNotHappen;
use Pest\Stressless\ResultPrinters\Blocks;
use Pest\Stressless\ResultPrinters\Progress;
use Pest\Stressless\ValueObjects\Binary;
use Pest\Stressless\ValueObjects\Result;
use Pest\Stressless\ValueObjects\Url;
use RuntimeException;
use Symfony\Component\Process\Process;

use function Termwind\render;
use function Termwind\terminal;

/**
* @internal
*/
Expand Down Expand Up @@ -51,7 +49,7 @@ public function start(): Result
$process->start();

if ($this->verbose) {
$this->tailProgress($process, $session->progressPath());
(new Progress($process, $session, $this->url))->tail();
}

$process->wait();
Expand All @@ -68,7 +66,7 @@ public function start(): Result
$metrics = json_decode($summary, true, 512, JSON_THROW_ON_ERROR);
assert(is_array($metrics));

$result = new Result($metrics);
$result = new Result($metrics); // @phpstan-ignore-line

if ($this->verbose) {
$blocks = new Blocks();
Expand All @@ -80,110 +78,4 @@ public function start(): Result

return $result;
}

private function tailProgress(Process $process, string $progressPath): void
{
$date = date('H:i:s');
$url = str_starts_with($this->url, 'http') ? $this->url : 'https://'.$this->url;
$url = explode('//', (string) $url)[1];

render(<<<HTML
<div class="flex mx-2">
<span class="text-gray">$date</span>
<span class="flex-1"></span>
<span class="text-gray">Running stress test on <span class="text-blue">$url</span></span>
</div>
HTML);

sleep(1);

$tail = new Process(['tail', '-f', $progressPath]);

$tail
->setTty(false)
->setTimeout(null)
->start();

$points = [];

$buffer = '';
$lastTime = null;
while ($process->isRunning()) {
$output = $tail->getIncrementalOutput();

if (empty($output)) {
continue;
}

$output = $buffer.$output;
$buffer = '';

$lines = explode("\n", $output);

foreach ($lines as $line) {
if (str_starts_with($line, '{"metric":"http_req_duration","type":"Point"')) {
$decodedLine = json_decode($line, true, 512, JSON_THROW_ON_ERROR);

if (is_array($decodedLine)) {
$currentTime = substr((string) $decodedLine['data']['time'], 0, 19);
if ($lastTime !== $currentTime) {
$this->printCurrentPoints($points);
$points = [];

$lastTime = $currentTime;
}

$points[] = $decodedLine;
} else {
$buffer .= $line;
}
}
}

usleep(100000); // 100ms
}
}

private function printCurrentPoints(array $points): void
{
static $maxResponseTime;

if ($points !== []) {
$average = array_sum(array_map(fn ($point) => $point['data']['value'], $points)) / count($points);
$average = round($average, 2);

// only time
$time = substr((string) $points[0]['data']['time'], 11, 8);

$width = max(0, terminal()->width());
$width = $width - 4 - strlen($time);

if ($maxResponseTime === null) {
$maxResponseTime = max($average * 3, 1000);
}

$greenDots = (int) (($average * $width) / $maxResponseTime);

$greenDots = str_repeat('', $greenDots);

render(<<<HTML
<div class="flex mx-2">
<span class="text-gray">
<span>{$time}│</span>
<span class="">$greenDots</span>
</span>
<span class="flex-1"></span>
<span class="text-gray ml-1">{$average}ms</span>
</div>
HTML);
}
}

/**
* Destroys the run instance.
*/
public function __destruct()
{
//
}
}
Loading

0 comments on commit 446ab49

Please sign in to comment.