154 lines
5.7 KiB
PHP
154 lines
5.7 KiB
PHP
|
|
<?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;
|
||
|
|
}
|
||
|
|
}
|