prepare($sqlList); $stmt->execute(); $mikrotiks = $stmt->fetchAll(PDO::FETCH_ASSOC); $pdo = null; if (empty($mikrotiks)) { echo "[" . date('Y-m-d H:i:s') . "] Tidak ada data MikroTik.\n"; exit(0); } // Fork parallel $child_pids = []; $outfiles = []; foreach ($mikrotiks as $data) { while (count($child_pids) >= $max_processes) { foreach ($child_pids as $key => $pid) { if (pcntl_waitpid($pid, $status, WNOHANG) > 0) { unset($child_pids[$key]); } } usleep(100000); } $safeId = preg_replace('~[^A-Za-z0-9_\-]~', '_', substr($data['board_id'], 0, 64)); $outfile = $tmpDir . "/mt_" . $safeId . "_" . microtime(true) . "_" . mt_rand(1000,9999) . ".json"; $outfiles[] = $outfile; $pid = pcntl_fork(); if ($pid == -1) { echo "[" . date('Y-m-d H:i:s') . "] Gagal fork.\n"; continue; } elseif ($pid) { $child_pids[] = $pid; } else { process_mikrotik_child($data, $timeout_api, $outfile); exit(0); } } foreach ($child_pids as $pid) { pcntl_waitpid($pid, $status); } echo "[" . date('Y-m-d H:i:s') . "] Semua child selesai. Mulai update DB...\n"; // Update DB include "/www/wwwroot/bilspro/config/connect.php"; $updateSql = " UPDATE setting_mikrotik SET pppa = :pppa, hsa = :hsa, uptime = :ut, board_name = :bm, status = :st WHERE board_id = :id "; $statusOnlySql = " UPDATE setting_mikrotik SET status = :st WHERE board_id = :id "; $updateStmt = $pdo->prepare($updateSql); $statusOnlyStmt = $pdo->prepare($statusOnlySql); $execWithRetry = function($stmt, $params) use (&$pdo, $updateSql, $statusOnlySql, &$updateStmt, &$statusOnlyStmt) { try { $stmt->execute($params); return true; } catch (PDOException $e) { $msg = $e->getMessage(); if (stripos($msg, '2006 MySQL server has gone away') !== false) { $pdo = null; include "/www/wwwroot/bilspro/config/connect.php"; $updateStmt = $pdo->prepare($updateSql); $statusOnlyStmt = $pdo->prepare($statusOnlySql); $stmt = (isset($params[':pppa']) ? $updateStmt : $statusOnlyStmt); $stmt->execute($params); return true; } throw $e; } }; $updated = 0; $failed = 0; foreach ($outfiles as $file) { if (!is_file($file)) continue; $json = file_get_contents($file); @unlink($file); $row = json_decode($json, true); if (!$row || !isset($row['board_id'], $row['status'])) { $failed++; continue; } $paramsStatus = [ ':st' => $row['status'], ':id' => $row['board_id'], ]; // Cek data lama untuk log perbandingan $cekStmt = $pdo->prepare("SELECT pppa,hsa,uptime,board_name,status FROM setting_mikrotik WHERE board_id = :id"); $cekStmt->execute([':id' => $row['board_id']]); $current = $cekStmt->fetch(PDO::FETCH_ASSOC); if ($row['status'] === 'Connected') { $params = [ ':pppa' => $row['pppa'] ?? '', ':hsa' => $row['hsa'] ?? '', ':ut' => $row['uptime'] ?? '', ':bm' => $row['board_name'] ?? '', ':st' => 'Connected', ':id' => $row['board_id'], ]; try { $execWithRetry($updateStmt, $params); $updated++; echo "[" . date('H:i:s') . "] [UPDATE DB] {$row['board_id']} \n "; // echo "OLD: " . json_encode($current, JSON_UNESCAPED_UNICODE) . "\n NEW: " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n"; } catch (PDOException $e) { echo "[" . date('H:i:s') . "] [DB ERROR] {$row['board_id']} - " . $e->getMessage() . "\n"; $failed++; } } else { try { $execWithRetry($statusOnlyStmt, $paramsStatus); $updated++; echo "[" . date('H:i:s') . "] [UPDATE STATUS] {$row['board_id']} ({$current['status']} -> {$row['status']})\n"; } catch (PDOException $e) { echo "[" . date('H:i:s') . "] [DB ERROR] {$row['board_id']} - " . $e->getMessage() . "\n"; $failed++; } } } $pdo = null; echo "[" . date('Y-m-d H:i:s') . "] Update DB selesai. OK: {$updated}, FAIL: {$failed}\n"; $end_time = microtime(true); $duration = $end_time - $start_time; echo "[" . date('Y-m-d H:i:s') . "] Lama eksekusi: " . number_format($duration, 2) . " detik\n"; exit(0); // ================================ // CHILD: konek RouterOS & tulis file hasil // ================================ function process_mikrotik_child(array $data, int $timeout_api, string $outfile): void { $ip = $data['ip_address']; $username = $data['username']; $password = $data['password']; $port = $data['port_api']; $board_id = $data['board_id']; $API = new RouterosAPI(); $API->debug = false; $API->timeout = $timeout_api; $result = [ 'board_id' => $board_id, 'status' => 'Disconnected', 'pppa' => '', 'hsa' => '', 'board_name' => '', 'uptime' => '', 'ip' => $ip, ]; if ($API->connect2($ip, $username, $password, $port)) { $pppoe_active = []; $pppoe_data = $API->comm("/ppp/active/print"); if (is_array($pppoe_data)) { foreach ($pppoe_data as $item) { $name = $item['name'] ?? ''; $addr = $item['address'] ?? ''; $mac = $item['caller-id'] ?? ''; if ($name !== '' && $addr !== '') { $pppoe_active[] = "{$name}_{$addr}_{$mac}"; } } } $hotspot_active = []; $hotspot_users = $API->comm("/ip/hotspot/user/print"); if (is_array($hotspot_users)) { foreach ($hotspot_users as $item) { $user = $item['name'] ?? ''; $comment = $item['comment'] ?? ''; if ($user !== '' && strpos($comment, 'Login.') === 0) { $hotspot_active[] = "{$user}_" . substr($comment, 6); } } } $sys = $API->comm("/system/resource/print"); $board_name = ''; $uptime = ''; if (is_array($sys) && !empty($sys[0])) { $board_name = $sys[0]['board-name'] ?? ''; $uptime = $sys[0]['uptime'] ?? ''; } $API->disconnect(); $result['status'] = 'Connected'; $result['pppa'] = implode('|', $pppoe_active); $result['hsa'] = implode('|', $hotspot_active); $result['board_name'] = $board_name; $result['uptime'] = $uptime; echo "[" . date('H:i:s') . "] [OK] {$ip}\n"; } else { echo "[" . date('H:i:s') . "] [FAIL] {$ip}\n"; } file_put_contents($outfile, json_encode($result, JSON_UNESCAPED_UNICODE)); }