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.
Closes the hosting half of ops/features#33.
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;
|
|
}
|
|
}
|