byteMemoryLimit(ini_get('memory_limit')) < 512 * 1024 * 1024) { ini_set('memory_limit', '512M'); } return $this->runBenchmark($orchestrator); } private function runBenchmark(SettlementOrchestrator $orchestrator): int { $itemCount = max(1, (int) $this->option('items')); $maxSeconds = max(1, (int) $this->option('max-seconds')); $maxMs = $maxSeconds * 1000; $this->info(sprintf('Seeding %d ticket items…', $itemCount)); $fixture = $this->seedFixture($itemCount); $draw = $fixture['draw']; $this->info('Running trySettleDraw…'); $started = hrtime(true); $ran = $orchestrator->trySettleDraw($draw->fresh()); $elapsedMs = (int) ((hrtime(true) - $started) / 1_000_000); if (! $ran) { $this->error('Settlement did not run (check draw status / published result batch).'); return self::FAILURE; } $settled = TicketItem::query() ->where('draw_id', $draw->id) ->whereIn('status', ['pending_payout', 'settled_lose', 'settled_win']) ->count(); $pass = $elapsedMs <= $maxMs && $settled === $itemCount; $this->table( ['metric', 'value'], [ ['items', (string) $itemCount], ['settled_rows', (string) $settled], ['elapsed_ms', (string) $elapsedMs], ['threshold_ms', (string) $maxMs], ['result', $pass ? 'PASS' : 'FAIL'], ], ); return $pass ? self::SUCCESS : self::FAILURE; } private function byteMemoryLimit(string $value): int { $value = trim($value); if ($value === '' || $value === '-1') { return PHP_INT_MAX; } $unit = strtolower(substr($value, -1)); $number = (int) $value; return match ($unit) { 'g' => $number * 1024 * 1024 * 1024, 'm' => $number * 1024 * 1024, 'k' => $number * 1024, default => $number, }; } /** * @return array{draw: Draw} */ private function seedFixture(int $itemCount): array { return DB::transaction(function () use ($itemCount): array { $uniq = bin2hex(random_bytes(4)); $player = Player::query()->create([ 'site_code' => 'perf', 'site_player_id' => 'perf-settle-'.$uniq, 'username' => 'perf_'.$uniq, 'nickname' => null, 'default_currency' => 'NPR', 'status' => 0, ]); PlayerWallet::query()->create([ 'player_id' => $player->id, 'wallet_type' => 'lottery', 'currency_code' => 'NPR', 'balance' => 0, 'frozen_balance' => 0, 'status' => 0, 'version' => 0, ]); $draw = Draw::query()->create([ 'draw_no' => '20990101-'.str_pad((string) random_int(1, 999), 3, '0', STR_PAD_LEFT), 'business_date' => '2099-01-01', 'sequence_no' => 1, 'status' => DrawStatus::Settling->value, 'start_time' => now()->subHour(), 'close_time' => now()->subMinutes(30), 'draw_time' => now()->subMinutes(20), 'cooling_end_time' => now()->subMinutes(5), 'result_source' => 'rng', 'current_result_version' => 1, 'settle_version' => 0, 'is_reopened' => false, ]); $batch = DrawResultBatch::query()->create([ 'draw_id' => $draw->id, 'result_version' => 1, 'source_type' => 'rng', 'rng_seed_hash' => 'perf-bench', 'raw_seed_encrypted' => null, 'status' => DrawResultBatchStatus::Published->value, 'created_by' => null, 'confirmed_by' => null, 'confirmed_at' => now(), ]); foreach (DrawPrizeLayout::slots() as $slot) { DrawResultItem::query()->create([ 'draw_id' => $draw->id, 'result_batch_id' => $batch->id, 'prize_type' => $slot['prize_type'], 'prize_index' => $slot['prize_index'], 'number_4d' => '0001', 'suffix_3d' => '001', 'suffix_2d' => '01', 'head_digit' => 0, 'tail_digit' => 1, ]); } $order = TicketOrder::query()->create([ 'order_no' => 'TO-PERF-'.$uniq, 'player_id' => $player->id, 'draw_id' => $draw->id, 'currency_code' => 'NPR', 'total_bet_amount' => $itemCount * 10, 'total_rebate_amount' => 0, 'total_actual_deduct' => $itemCount * 10, 'total_estimated_payout' => 0, 'status' => 'placed', 'submit_source' => 'perf', 'client_trace_id' => 'perf-settle-'.$uniq, ]); $now = now(); $oddsJson = json_encode(['1st' => 250000], JSON_THROW_ON_ERROR); $ruleJson = json_encode([], JSON_THROW_ON_ERROR); $chunk = 500; for ($offset = 0; $offset < $itemCount; $offset += $chunk) { $size = min($chunk, $itemCount - $offset); $itemRows = []; $ticketNos = []; for ($i = 0; $i < $size; $i++) { $seq = $offset + $i + 1; $num = str_pad((string) ($seq % 10000), 4, '0', STR_PAD_LEFT); $ticketNo = sprintf('TK-PERF-%s-%06d', $uniq, $seq); $ticketNos[] = $ticketNo; $itemRows[] = [ 'ticket_no' => $ticketNo, 'order_id' => $order->id, 'player_id' => $player->id, 'draw_id' => $draw->id, 'original_number' => $num, 'normalized_number' => $num, 'play_code' => 'big', 'dimension' => 4, 'digit_slot' => null, 'bet_mode' => 'straight', 'unit_bet_amount' => 10, 'total_bet_amount' => 10, 'rebate_rate_snapshot' => 0, 'commission_rate_snapshot' => 0, 'actual_deduct_amount' => 10, 'odds_snapshot_json' => $oddsJson, 'rule_snapshot_json' => $ruleJson, 'combination_count' => 1, 'estimated_max_payout' => 250, 'risk_locked_amount' => 0, 'status' => 'pending_draw', 'created_at' => $now, 'updated_at' => $now, ]; } DB::table('ticket_items')->insert($itemRows); $comboRows = DB::table('ticket_items') ->whereIn('ticket_no', $ticketNos) ->get(['id', 'normalized_number']) ->map(fn ($row): array => [ 'ticket_item_id' => $row->id, 'combination_no' => 0, 'number_4d' => $row->normalized_number, 'bet_amount' => 10, 'estimated_payout' => 250, 'created_at' => $now, ]) ->all(); DB::table('ticket_combinations')->insert($comboRows); } return ['draw' => $draw]; }); } }