yssd
All checks were successful
Deploy / deploy-staging (push) Successful in 5s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-05-02 01:50:10 +02:00
parent f920991015
commit 58d0c18e25

View File

@@ -69,6 +69,10 @@ final class Router
$this->releaseSessionLock();
$method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
$path = trim($relativePath, '/');
$this->debug->add('router.handle.start', [
'path' => $path,
'method' => $method,
]);
if ($path === 'v1/health') {
$this->respond(['ok' => true, 'module' => 'mining-checker']);
@@ -285,11 +289,20 @@ final class Router
throw new ApiException('Ressource nicht gefunden.', 404, ['resource' => $resource, 'method' => $method]);
} catch (ApiException $exception) {
$this->debug->add('router.handle.api_exception', [
'status' => $exception->statusCode(),
'message' => $exception->getMessage(),
'context' => $exception->context(),
]);
Http::json([
'error' => $exception->getMessage(),
'context' => $exception->context(),
], $exception->statusCode());
} catch (\Throwable $exception) {
$this->debug->add('router.handle.error', [
'type' => get_debug_type($exception),
'message' => $exception->getMessage(),
]);
Http::json([
'error' => 'Unerwarteter Mining-Checker Fehler.',
'context' => ['message' => $exception->getMessage()],
@@ -300,7 +313,13 @@ final class Router
private function bootstrap(string $projectKey): array
{
$startedAt = microtime(true);
$settings = $this->safeRead(fn () => $this->settings($projectKey), [
$this->debug->add('bootstrap.start', [
'project_key' => $projectKey,
'measurement_limit' => self::BOOTSTRAP_MEASUREMENT_LIMIT,
'snapshot_limit' => self::BOOTSTRAP_SNAPSHOT_LIMIT,
]);
$settings = $this->safeTimed('bootstrap.settings', fn () => $this->settings($projectKey), [
'project_key' => $projectKey,
'baseline_measured_at' => null,
'baseline_coins_total' => null,
@@ -319,19 +338,38 @@ final class Router
'miner_offers' => [],
'purchased_miners' => [],
'measurement_rates' => [],
], ['project_key' => $projectKey]);
$measurements = $this->safeTimed('bootstrap.measurements', fn () => $this->bootstrapMeasurements($projectKey, $settings), [], [
'project_key' => $projectKey,
]);
$measurements = $this->safeRead(fn () => $this->bootstrapMeasurements($projectKey, $settings), []);
$targets = $this->safeRead(fn () => $this->targets($projectKey), []);
$dashboards = $this->safeRead(fn () => $this->dashboards($projectKey), []);
$fxSnapshots = $this->safeRead(fn () => $this->measurementFxSnapshots($measurements, self::BOOTSTRAP_SNAPSHOT_LIMIT), []);
$summary = $this->safeRead(fn () => $this->analytics()->buildSummary($measurements, $settings, $targets), [
$targets = $this->safeTimed('bootstrap.targets', fn () => $this->targets($projectKey), [], [
'project_key' => $projectKey,
]);
$dashboards = $this->safeTimed('bootstrap.dashboards', fn () => $this->dashboards($projectKey), [], [
'project_key' => $projectKey,
]);
$fxSnapshots = $this->safeTimed('bootstrap.fx_snapshots', fn () => $this->measurementFxSnapshots($measurements, self::BOOTSTRAP_SNAPSHOT_LIMIT), [], [
'measurement_count' => is_array($measurements) ? count($measurements) : 0,
]);
$summary = $this->safeTimed('bootstrap.summary', fn () => $this->analytics()->buildSummary($measurements, $settings, $targets), [
'latest_measurement' => $measurements !== [] ? $measurements[array_key_last($measurements)] : null,
'baseline' => $settings,
'targets' => [],
'payouts' => [],
'miner_offers' => [],
], [
'measurement_count' => is_array($measurements) ? count($measurements) : 0,
'target_count' => is_array($targets) ? count($targets) : 0,
]);
$measurementCount = is_array($measurements) ? count($measurements) : 0;
$this->debug->add('bootstrap.end', [
'project_key' => $projectKey,
'duration_ms' => round((microtime(true) - $startedAt) * 1000, 2),
'measurement_count' => $measurementCount,
'target_count' => is_array($targets) ? count($targets) : 0,
'dashboard_count' => is_array($dashboards) ? count($dashboards) : 0,
'snapshot_count' => is_array($fxSnapshots) ? count($fxSnapshots) : 0,
]);
return [
'project' => $this->repository()->getProject($projectKey),
@@ -783,22 +821,39 @@ final class Router
}
$startedAt = microtime(true);
$this->debug->add('legacy_fx_migrate.start', [
'project_key' => $projectKey,
]);
module_fn('fx-rates', 'ensure_schema');
$legacyRows = $this->repository()->listAllFxRates();
$legacyFetches = $this->groupLegacyFxFetches($legacyRows);
$fxRepository = module_fn('fx-rates', 'repository');
$this->debug->add('legacy_fx_migrate.loaded', [
'legacy_rows' => count($legacyRows),
'legacy_fetches' => count($legacyFetches),
]);
$fetchIdMap = [];
$importedFetches = 0;
$reusedFetches = 0;
$migratedRates = 0;
$legacyFetchIndex = 0;
foreach ($legacyFetches as $legacyFetchId => $legacyFetch) {
$this->assertRequestWithinBudget($startedAt, 'Legacy-FX-Migration dauert zu lange.');
$legacyFetchIndex++;
$existing = $this->findExistingFxFetchForLegacy($fxRepository, $legacyFetch);
if (is_array($existing) && !empty($existing['id'])) {
$fetchIdMap[$legacyFetchId] = (int) $existing['id'];
$reusedFetches++;
if ($legacyFetchIndex % 50 === 0) {
$this->debug->add('legacy_fx_migrate.fetch_progress', [
'processed' => $legacyFetchIndex,
'total' => count($legacyFetches),
'reused_fetches' => $reusedFetches,
'imported_fetches' => $importedFetches,
]);
}
continue;
}
@@ -816,19 +871,33 @@ final class Router
}
$importedFetches++;
$migratedRates += count($saved['rates'] ?? []);
if ($legacyFetchIndex % 50 === 0) {
$this->debug->add('legacy_fx_migrate.fetch_progress', [
'processed' => $legacyFetchIndex,
'total' => count($legacyFetches),
'reused_fetches' => $reusedFetches,
'imported_fetches' => $importedFetches,
]);
}
}
$measurements = $this->repository()->listAllMeasurements($projectKey);
$measurementRatesById = $this->groupMeasurementRatesByMeasurementId(
$this->repository()->listMeasurementRates($projectKey)
);
$this->debug->add('legacy_fx_migrate.measurements_loaded', [
'measurement_count' => count($measurements),
'measurement_rate_groups' => count($measurementRatesById),
]);
$updatedMeasurements = 0;
$reusedMeasurements = 0;
$unresolvedMeasurements = 0;
$measurementIndex = 0;
foreach ($measurements as $measurement) {
$this->assertRequestWithinBudget($startedAt, 'Legacy-FX-Migration dauert zu lange.');
$measurementIndex++;
$measurementId = is_numeric($measurement['id'] ?? null) ? (int) $measurement['id'] : 0;
if ($measurementId <= 0) {
continue;
@@ -837,6 +906,15 @@ final class Router
$currentFetchId = is_numeric($measurement['fx_fetch_id'] ?? null) ? (int) $measurement['fx_fetch_id'] : 0;
if ($currentFetchId > 0 && $this->fx()->snapshotByFetchId($currentFetchId, null, null) !== null) {
$reusedMeasurements++;
if ($measurementIndex % 100 === 0) {
$this->debug->add('legacy_fx_migrate.measurement_progress', [
'processed' => $measurementIndex,
'total' => count($measurements),
'updated' => $updatedMeasurements,
'reused' => $reusedMeasurements,
'unresolved' => $unresolvedMeasurements,
]);
}
continue;
}
@@ -849,13 +927,44 @@ final class Router
if (!is_numeric($resolvedFetchId) || (int) $resolvedFetchId <= 0) {
$unresolvedMeasurements++;
if ($measurementIndex % 100 === 0) {
$this->debug->add('legacy_fx_migrate.measurement_progress', [
'processed' => $measurementIndex,
'total' => count($measurements),
'updated' => $updatedMeasurements,
'reused' => $reusedMeasurements,
'unresolved' => $unresolvedMeasurements,
]);
}
continue;
}
$this->repository()->setMeasurementFxFetchId($projectKey, $measurementId, (int) $resolvedFetchId);
$updatedMeasurements++;
if ($measurementIndex % 100 === 0) {
$this->debug->add('legacy_fx_migrate.measurement_progress', [
'processed' => $measurementIndex,
'total' => count($measurements),
'updated' => $updatedMeasurements,
'reused' => $reusedMeasurements,
'unresolved' => $unresolvedMeasurements,
]);
}
}
$this->debug->add('legacy_fx_migrate.end', [
'duration_ms' => round((microtime(true) - $startedAt) * 1000, 2),
'legacy_fetches_found' => count($legacyFetches),
'legacy_rates_found' => count($legacyRows),
'fx_fetches_imported' => $importedFetches,
'fx_fetches_reused' => $reusedFetches,
'fx_rates_imported' => $migratedRates,
'measurements_checked' => count($measurements),
'measurements_updated' => $updatedMeasurements,
'measurements_reused' => $reusedMeasurements,
'measurements_unresolved' => $unresolvedMeasurements,
]);
return [
'message' => 'Legacy-FX-Rates wurden nach fx-rates migriert und Messpunkte aktualisiert.',
'legacy_fetches_found' => count($legacyFetches),
@@ -1148,6 +1257,11 @@ final class Router
private function bootstrapMeasurements(string $projectKey, array $settings): array
{
$rows = $this->repository()->listMeasurements($projectKey, self::BOOTSTRAP_MEASUREMENT_LIMIT);
$this->debug->add('bootstrap.measurements.loaded', [
'project_key' => $projectKey,
'row_count' => count($rows),
'limit' => self::BOOTSTRAP_MEASUREMENT_LIMIT,
]);
return $this->analytics()->enrichMeasurements($rows, $settings);
}
@@ -2145,6 +2259,10 @@ final class Router
if (function_exists('set_time_limit')) {
@set_time_limit(15);
}
$this->debug->add('runtime.guards', [
'time_limit_sec' => 15,
'budget_sec' => self::LONG_REQUEST_BUDGET_SECONDS,
]);
}
private function releaseSessionLock(): void
@@ -2160,8 +2278,41 @@ final class Router
private function assertRequestWithinBudget(float $startedAt, string $message): void
{
if ((microtime(true) - $startedAt) > self::LONG_REQUEST_BUDGET_SECONDS) {
throw new ApiException($message, 503, ['timeout' => true]);
$elapsed = microtime(true) - $startedAt;
if ($elapsed > self::LONG_REQUEST_BUDGET_SECONDS) {
$this->debug->add('request.budget_exceeded', [
'elapsed_ms' => round($elapsed * 1000, 2),
'budget_ms' => round(self::LONG_REQUEST_BUDGET_SECONDS * 1000, 2),
'message' => $message,
]);
throw new ApiException($message, 503, ['timeout' => true, 'elapsed_ms' => round($elapsed * 1000, 2)]);
}
}
private function safeTimed(string $event, callable $callback, mixed $fallback = null, array $context = []): mixed
{
$startedAt = microtime(true);
$this->debug->add($event . '.start', $context);
try {
$result = $callback();
$meta = [
'duration_ms' => round((microtime(true) - $startedAt) * 1000, 2),
];
if (is_array($result)) {
$meta['count'] = count($result);
}
$this->debug->add($event . '.end', $context + $meta);
return $result;
} catch (\Throwable $exception) {
$meta = [
'duration_ms' => round((microtime(true) - $startedAt) * 1000, 2),
'fallback_used' => true,
'error' => $exception->getMessage(),
'type' => get_debug_type($exception),
];
$this->debug->add($event . '.error', $context + $meta);
return $fallback;
}
}