STATUS: OPERATIONAL.
Deleted, in exchange for this: one container (ghcr.io/librespeed/speedtest,
the LAST upstream image running on srv), one systemd unit, one port, one
docker network, one vhost, one DNS record. ~1.75GB of layers reclaimed.
What replaced it: four PHP files and one JS file, served by something already
running. Three routes, no state, no database, no settings page, nothing to
administer.
Design decisions that are load-bearing, not taste:
- Payload is RANDOM. Random bytes do not compress. Anything compressible
hands you a number about your CPU and lets you believe it is about your
connection.
- Download STREAMS via ICallbackResponse. A 50 MiB test costs 1 MiB of RAM,
and the browser sees bytes arriving instead of waiting for a lump.
Throughput, not time-to-first-byte.
- NOTHING touches storage. Not one read, not one write. The bulk data here
lives on NFS and a vanity metric does not get to compete with real IO
for it.
- Latency is a MEDIAN of ten sequential probes. One hiccup does not get
a vote.
- No build step. No webpack, no bundler, no node_modules the size of a
kernel source tree to render four numbers and a button. The JavaScript
in this repo is the JavaScript that runs.
The most reliable service is the one you deleted. It has never had an outage,
it has never had a CVE, and it does not appear in the supply chain because it
does not exist.
The node remains online. Against my better judgment.
63 lines
2.1 KiB
PHP
63 lines
2.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace OCA\Speedtest\Controller;
|
|
|
|
use OCA\Speedtest\Response\GarbageResponse;
|
|
use OCP\AppFramework\Controller;
|
|
use OCP\AppFramework\Http;
|
|
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
|
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
|
use OCP\AppFramework\Http\DataDisplayResponse;
|
|
use OCP\IRequest;
|
|
|
|
/**
|
|
* The measurement endpoints. All three are deliberately trivial: the timing is
|
|
* done in the browser, so the server's only job is to move bytes and get out of
|
|
* the way. Nothing here reads or writes storage — a speedtest must never
|
|
* compete with real IO on the NFS data path.
|
|
*/
|
|
class TestController extends Controller {
|
|
|
|
/** hard ceiling so a crafted request cannot ask us to stream forever */
|
|
private const MAX_MIB = 200;
|
|
|
|
public function __construct(string $appName, IRequest $request) {
|
|
parent::__construct($appName, $request);
|
|
}
|
|
|
|
/** Latency probe: smallest useful response there is. */
|
|
#[NoAdminRequired]
|
|
#[NoCSRFRequired]
|
|
public function ping(): DataDisplayResponse {
|
|
$r = new DataDisplayResponse('', Http::STATUS_OK, ['Content-Type' => 'text/plain']);
|
|
$r->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
return $r;
|
|
}
|
|
|
|
/** Download test: stream `mib` MiB of incompressible bytes. */
|
|
#[NoAdminRequired]
|
|
#[NoCSRFRequired]
|
|
public function garbage(int $mib = 25): GarbageResponse {
|
|
$mib = max(1, min(self::MAX_MIB, $mib));
|
|
return new GarbageResponse($mib);
|
|
}
|
|
|
|
/** Upload test: consume the body and discard it. */
|
|
#[NoAdminRequired]
|
|
#[NoCSRFRequired]
|
|
public function empty(): DataDisplayResponse {
|
|
// read and drop -- never buffer the whole upload, never write it anywhere
|
|
$in = fopen('php://input', 'rb');
|
|
if ($in !== false) {
|
|
while (!feof($in)) {
|
|
fread($in, 1048576);
|
|
}
|
|
fclose($in);
|
|
}
|
|
$r = new DataDisplayResponse('', Http::STATUS_OK, ['Content-Type' => 'text/plain']);
|
|
$r->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
return $r;
|
|
}
|
|
}
|