festbench-web/app/Console/Commands/FestbenchImport.php

154 lines
5.7 KiB
PHP
Raw Normal View History

<?php
namespace App\Console\Commands;
use App\Models\BenchmarkRun;
use App\Models\RunResult;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use PDO;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'festbench:import', description: 'Import a festbench benchmarks.db (SQLite) into the application database')]
class FestbenchImport extends Command
{
protected $signature = 'festbench:import
{path : Path to the festbench benchmarks.db}
{--user= : Email of the user to attribute the imported runs to}
{--private : Import runs as private instead of public}';
public function handle(): int
{
$path = $this->argument('path');
if (! file_exists($path)) {
$this->error("Database not found: {$path}");
return self::FAILURE;
}
$user = $this->resolveUser();
$sqlite = new PDO('sqlite:'.$path, null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$importedRuns = $skippedRuns = $importedResults = 0;
$runs = $sqlite->query('SELECT * FROM runs')->fetchAll();
$progress = $this->output->createProgressBar(count($runs));
$progress->start();
foreach ($runs as $source) {
if (BenchmarkRun::where('run_id', $source['run_id'])->exists()) {
$skippedRuns++;
$progress->advance();
continue;
}
$run = BenchmarkRun::create([
'run_id' => $source['run_id'],
'ran_at' => $source['ts'] ? Carbon::parse($source['ts']) : null,
'suite' => $source['suite'] ?? 'unknown',
'run_key' => $source['run_key'] ?? null,
'model_id' => $source['model_id'] ?? null,
'model_quant' => $source['model_quant'] ?? null,
'model_quant_raw' => $source['model_quant_raw'] ?? null,
'model_path' => $source['model_path'] ?? null,
'engine' => $source['engine'] ?? null,
'engine_build' => $source['engine_build'] ?? null,
'n_ctx' => $source['n_ctx'] ?? null,
'slots' => $source['slots'] ?? null,
'gpu' => $source['gpu'] ?? null,
'gpu_count' => $source['gpu_count'] ?? null,
'gpu_source' => $source['gpu_source'] ?? null,
'host' => $source['host'] ?? null,
'client' => $source['client'] ?? null,
'endpoint' => $source['endpoint'] ?? null,
'label' => $source['label'] ?? null,
'config' => $source['config'] ?? null,
'image_digest' => $source['image_digest'] ?? null,
'validators' => $this->decodeJson($source['validators'] ?? null),
'suite_versions' => $this->decodeJson($source['suite_versions'] ?? null),
'harness' => $source['harness'] ?? null,
'harness_ver' => $source['harness_ver'] ?? null,
'harness_mode' => $source['harness_mode'] ?? null,
'user_id' => $user?->id,
'visibility' => $this->option('private') ? BenchmarkRun::VISIBILITY_PRIVATE : BenchmarkRun::VISIBILITY_PUBLIC,
'notes' => $source['notes'] ?? null,
]);
$importedRuns++;
$stmt = $sqlite->prepare('SELECT * FROM results WHERE run_id = ?');
$stmt->execute([$source['run_id']]);
foreach ($stmt->fetchAll() as $row) {
RunResult::create([
'benchmark_run_id' => $run->id,
'suite' => $row['suite'] ?? $source['suite'],
'section' => $row['section'] ?? null,
'task' => $row['task'] ?? ($row['name'] ?? 'unknown'),
'level' => $row['level'] ?? null,
'ok' => $row['ok'] === null ? null : (bool) $row['ok'],
'decode' => $row['decode'] ?? null,
'user_tps' => $row['user_tps'] ?? null,
'ttft' => $row['ttft'] ?? null,
't_answer' => $row['t_answer'] ?? null,
'wall' => $row['wall'] ?? null,
'reasoning' => $row['reasoning'] ?? null,
'visible' => $row['visible'] ?? null,
'total' => $row['total'] ?? null,
'tool_calls' => $row['tool_calls'] ?? null,
'checks_passed' => $row['checks_passed'] ?? null,
'checks_total' => $row['checks_total'] ?? null,
'finish' => $row['finish'] ?? null,
'detail' => ($row['detail'] ?? '') !== '' ? $row['detail'] : null,
'payload' => $row,
]);
$importedResults++;
}
$progress->advance();
}
$progress->finish();
$this->newLine(2);
$this->info("Imported {$importedRuns} runs ({$importedResults} results), skipped {$skippedRuns} already present.");
return self::SUCCESS;
}
private function resolveUser(): ?User
{
$email = $this->option('user');
if ($email === null) {
return null;
}
$user = User::where('email', $email)->first();
if ($user === null) {
$this->warn("User {$email} not found — importing without attribution.");
}
return $user;
}
private function decodeJson(?string $value): ?array
{
if ($value === null || $value === '' || $value === '{}') {
return null;
}
$decoded = json_decode($value, true);
return is_array($decoded) ? $decoded : null;
}
}