Repository source: docs/plans/qdb_memory_audit/qdb_memory_audit_probe_source.txt
<?php
$project = dirname(__DIR__, 2);
require $project . '/autoload.php';
use ForumRewrite\ReadModel\ReadModelBuilder;
use ForumRewrite\Canonical\CanonicalRecordRepository;
// Audit-only synthetic data. Use a fresh directory outside the project.
$dir = realpath($argv[2] ?? '');
if ($dir === false || !is_dir($dir) || $dir === $project || str_starts_with($dir, $project . '/')) {
throw new RuntimeException('Pass an existing temporary workspace outside the repository as argument 2.');
}
$marker = $dir . '/qdb-memory-audit.txt';
if (($argv[1] ?? '') === 'setup') {
if (count(scandir($dir)) !== 2) throw new RuntimeException('Setup requires an empty workspace.');
file_put_contents($marker, "Purpose: Synthetic QDB memory audit\n");
} elseif (!is_file($marker)) {
throw new RuntimeException('Run setup in this workspace first.');
}
$db = $dir . '/model.sqlite';
$root = $dir . '/repository';
$mode = $argv[1];
if ($mode === 'setup') {
mkdir($root);
$src = $project . '/tests/fixtures/parity_minimal_v1';
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($src, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $file) {
$target = $root . substr($file->getPathname(), strlen($src));
$file->isDir() ? mkdir($target) : copy($file->getPathname(), $target);
}
(new ReadModelBuilder($root, $db, new CanonicalRecordRepository($root)))->rebuild();
$pdo = new PDO('sqlite:' . $db, null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$pdo->beginTransaction();
$post = $pdo->prepare("INSERT INTO posts(post_id,thread_id,created_at,subject,body,board_tags_json,sequence_number,author_label) VALUES (?,?,'2026-10-30T00:00:00Z','quote',?,'[\"general\"]',1,'guest')");
$thread = $pdo->prepare("INSERT INTO threads(root_post_id,root_post_created_at,last_activity_at,subject,body_preview,board_tags_json,thread_labels_json,reply_count,last_post_id,score_total,vote_count) VALUES (?,'2026-10-30T00:00:00Z','2026-10-30T00:00:00Z','quote',?,'[\"general\"]','[]',0,?,0,0)");
for ($i = 1; $i <= 40000; $i++) {
$id = 'thread-20261030000000-qdb-' . (400000 + $i);
$post->execute([$id, $id, str_repeat('q', 2048)]);
$thread->execute([$id, str_repeat('q', 1024), $id]);
}
$pdo->commit();
echo "Setup: 40000 synthetic quotes, 2048-byte bodies, 1024-byte previews\n";
exit;
}
if ($mode === 'setup-records') {
for ($i = 1; $i <= 40000; $i++) {
$id = 'thread-20261030000000-qdb-' . (400000 + $i);
file_put_contents($root . '/records/posts/' . $id . '.txt',
"Post-ID: {$id}\nCreated-At: 2026-10-30T00:00:00Z\nBoard-Tags: general\nSubject: quote\n\n" . str_repeat('q', 2048) . "\n");
}
echo "Canonical posts generated: 40000\n";
exit;
}
if ($mode === 'setup-commit') {
$gitRoot = $dir . '/large-commit';
if (file_exists($gitRoot)) throw new RuntimeException('Commit fixture already exists.');
mkdir($gitRoot);
$run = static function (array $command): string {
$process = proc_open($command, [0 => ['file', '/dev/null', 'r'], 1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $pipes);
if (!is_resource($process)) throw new RuntimeException('Cannot start Git.');
$out = stream_get_contents($pipes[1]); fclose($pipes[1]);
if (proc_close($process) !== 0) throw new RuntimeException('Git fixture command failed.');
return $out;
};
$run(['git', 'init', '-q', $gitRoot]);
$process = proc_open(['git', '-C', $gitRoot, 'fast-import', '--quiet'], [0 => ['pipe', 'r'], 1 => ['file', '/dev/null', 'w'], 2 => ['file', '/dev/null', 'w']], $pipes);
if (!is_resource($process)) throw new RuntimeException('Cannot start Git import.');
fwrite($pipes[0], "blob\nmark :1\ndata 1\nx\ncommit refs/heads/main\ncommitter Audit <audit@example.invalid> 1793318400 +0000\ndata 5\naudit\n");
for ($i = 0; $i < 100000; $i++) fwrite($pipes[0], sprintf("M 100644 :1 records/posts/post-%06d.txt\n", $i));
fwrite($pipes[0], "\ndone\n"); fclose($pipes[0]);
if (proc_close($process) !== 0) throw new RuntimeException('Git fixture import failed.');
file_put_contents($dir . '/sha', trim($run(['git', '-C', $gitRoot, 'rev-parse', 'refs/heads/main'])));
echo "Synthetic commit paths: 100000\n";
exit;
}
$pdo = new PDO('sqlite:' . $db, null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
$retainedMiB = (int) ($argv[3] ?? 0);
$retained = $retainedMiB ? str_repeat('x', $retainedMiB * 1024 * 1024) : '';
echo "Mode: $mode\nRetained MiB: $retainedMiB\n";
if ($mode === 'board') {
$result = ForumRewrite\ReadModel\ThreadRepository::fetchThreads($pdo);
} elseif ($mode === 'qdb-static') {
putenv('FORUM_SITE_ID=qdb');
$builder = new ForumRewrite\Host\StaticArtifactBuilder($project, $root, $db, $dir . '/static');
$builder->buildFromReadModel();
$result = [];
} elseif ($mode === 'offline-select') {
$builder = new ForumRewrite\Offline\PublicOfflineSnapshotBuilder($project);
$result = (new ReflectionMethod($builder, 'snapshotThreadRows'))->invoke($builder, $pdo, 200);
} elseif ($mode === 'static-tags') {
$builder = new ForumRewrite\Host\StaticArtifactBuilder($project, $root, $db, $dir . '/static');
$result = (new ReflectionMethod($builder, 'fetchVisibleTagRoutes'))->invoke($builder);
} elseif ($mode === 'fastmod-audit') {
$store = new ForumRewrite\Scoring\SqliteFastScoreStore(new PDO('sqlite:' . $dir . '/scores.sqlite'));
$result = (new ForumRewrite\Scoring\FastmodHistoricalAuditService($pdo, $store, 'audit'))->audit();
} elseif ($mode === 'commit-evidence') {
$service = new ForumRewrite\Activity\ActivityService(static fn (): PDO => $pdo, $dir . '/large-commit', $db);
$service->primeCommitEvidence([trim(file_get_contents($dir . '/sha'))]);
$result = [];
} elseif ($mode === 'rebuild') {
(new ReadModelBuilder($root, $dir . '/rebuilt.sqlite', new CanonicalRecordRepository($root), 'memory_audit', static function (string $message): void {
if (str_ends_with($message, '...')) echo $message . "\n";
}))->rebuild();
$result = [];
} elseif ($mode === 'access-log') {
file_put_contents($dir . '/access.log', "127.0.0.1 - - [30/Oct/2026:00:00:00 +0000] \"GET /latest HTTP/1.1\" 200 100\n");
$cache = new ForumRewrite\Host\HighTrafficCache($dir, $root, $db, $dir . '/public');
$result = (new ForumRewrite\Host\HighTrafficRefresh($cache))->popular($dir . '/access.log');
} elseif ($mode === 'parser-headers') {
$contents = str_repeat("A: x\n", 2500000) . "\nbody\n";
echo 'Input bytes: ' . strlen($contents) . "\n";
$result = [(new ForumRewrite\Canonical\GenericTextRecordParser())->parse($contents)];
} else { throw new RuntimeException('Unknown probe'); }
echo 'Result count: ' . count($result) . "\nPeak PHP MiB: " . round(memory_get_peak_usage(true) / 1048576, 2) . "\n";