UNPKG

mullvad-servers-ping-tester

Version:

Инструмент для тестирования пинга серверов Mullvad VPN с расширенной аналитикой

189 lines 9.83 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var PingService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.PingService = void 0; const inversify_1 = require("inversify"); const ping_1 = __importDefault(require("ping")); const os_1 = __importDefault(require("os")); const p_limit_1 = __importDefault(require("p-limit")); const config_1 = __importDefault(require("@/config/config")); const logger_1 = require("@/utils/logger"); /** * Сервис для пингования серверов * @implements {IPingService} */ let PingService = PingService_1 = class PingService { constructor() { this.logger = new logger_1.Logger(PingService_1.name); } /** * Пингует один сервер с механизмом повторных попыток * @param {IMullvadServer} server - Сервер для пингования * @param {number} retryCount - Количество оставшихся попыток * @returns {Promise<IPingResult>} - Промис с результатом пинга */ async pingServer(server, retryCount = config_1.default.PING_RETRIES) { try { // Определяем IP-адрес сервера // Проверяем разные возможные поля, так как формат API может меняться const serverIP = server.ipv4_addr_in || server.ipv4_address || server.ip_address || server.ip || server.address; if (!serverIP) { this.logger.error(`Не удалось определить IP-адрес для сервера ${server.hostname}`); this.logger.debug('Доступные поля сервера:', Object.keys(server)); return this.createUnreachableResult(server); } // Настраиваем параметры ping в зависимости от ОС const isWindows = os_1.default.platform() === 'win32'; const pingOptions = { timeout: config_1.default.PING_TIMEOUT / 1000, // Конвертируем мс в секунды // Используем разные параметры для Windows и других ОС // Уменьшаем количество пакетов для ускорения extra: isWindows ? ['-n', '2'] : ['-c', '2'], }; // Выполняем ping const result = await ping_1.default.promise.probe(serverIP, pingOptions); // Обрабатываем результат if (result.alive) { // Рассчитываем статистику const pingTime = parseFloat(result.time); const packetLoss = parseFloat(result.packetLoss); return { hostname: server.hostname, country_code: server.country_code, country: server.country_name, city: server.city_name, ip: serverIP, ping: pingTime, packetLoss: packetLoss, min: parseFloat(result.min) || pingTime, max: parseFloat(result.max) || pingTime, avg: parseFloat(result.avg) || pingTime, stddev: parseFloat(result.stddev) || 0, timestamp: new Date().toISOString(), status: this.getPingStatus(pingTime), server: server }; } else if (retryCount > 0) { // Повторяем попытку, если сервер недоступен this.logger.debug(`Server ${server.hostname} (${serverIP}) unreachable, retrying... (${retryCount} attempts left)`); return this.pingServer(server, retryCount - 1); } else { // Сервер недоступен после всех попыток this.logger.debug(`Server ${server.hostname} (${serverIP}) unreachable after all attempts`); return this.createUnreachableResult(server); } } catch (error) { if (retryCount > 0) { // Повторяем попытку при ошибке const errorMessage = error instanceof Error ? error.message : String(error); this.logger.debug(`Error pinging ${server.hostname}: ${errorMessage}, retrying... (${retryCount} attempts left)`); return this.pingServer(server, retryCount - 1); } else { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error(`Error pinging ${server.hostname} (${server.ipv4_addr_in || 'unknown IP'}):`, errorMessage); return this.createUnreachableResult(server); } } } /** * Создает объект результата для недоступных серверов * @param {IMullvadServer} server - Объект сервера * @returns {IPingResult} - Объект сервера с статусом недоступности */ createUnreachableResult(server) { return { hostname: server.hostname, country_code: server.country_code, country: server.country_name, city: server.city_name, ip: server.ipv4_addr_in || server.ipv4_address || server.ip_address || server.ip || server.address || 'unknown IP', ping: null, packetLoss: 100, min: 9999, max: 9999, avg: 9999, stddev: 0, timestamp: new Date().toISOString(), status: 'unreachable', server: server }; } /** * Определяет статус пинга на основе порогов * @param {number} pingTime - Время пинга в мс * @returns {string} - Статус: 'good', 'medium', или 'bad' */ getPingStatus(pingTime) { if (pingTime < config_1.default.PING_THRESHOLDS.GOOD) return 'good'; if (pingTime < config_1.default.PING_THRESHOLDS.MEDIUM) return 'medium'; return 'bad'; } /** * Пингует все серверы с ограничением параллельности * @param {IMullvadServer[]} servers - Массив серверов для пингования * @returns {Promise<IPingResult[]>} - Промис с массивом результатов пинга */ async pingAllServers(servers) { // Определяем оптимальное количество параллельных операций const cpuCount = os_1.default.cpus().length; const maxThreads = config_1.default.MAX_THREADS > 0 ? config_1.default.MAX_THREADS : cpuCount * 2; const concurrency = Math.min(config_1.default.CONCURRENT_PINGS, maxThreads); this.logger.info(`Using ${concurrency} concurrent connections (${cpuCount} CPU cores detected)`); // Создаем ограничитель параллельности const limit = (0, p_limit_1.default)(concurrency); // Создаем переменные для отслеживания прогресса const total = servers.length; let completed = 0; let successful = 0; let failed = 0; // Функция обновления прогресса const updateProgress = (result) => { completed++; if (result.ping !== null && result.ping < 9999) successful++; else failed++; const percent = Math.floor((completed / total) * 100); const progressBar = '[' + '='.repeat(Math.floor(percent / 2)) + ' '.repeat(50 - Math.floor(percent / 2)) + ']'; process.stdout.write(`\r${progressBar} ${percent}% | ${completed}/${total} | ✓ ${successful} | ✗ ${failed}`); return result; }; // Создаем задачи пинга с отслеживанием прогресса const tasks = servers.map(server => limit(() => this.pingServer(server).then(updateProgress))); // Выполняем все задачи пинга const results = await Promise.all(tasks); // Очищаем строку прогресса и выводим итоги process.stdout.write('\r' + ' '.repeat(100) + '\r'); this.logger.info(`Completed pinging ${total} servers: ${successful} reachable, ${failed} unreachable`); return results; } }; exports.PingService = PingService; exports.PingService = PingService = PingService_1 = __decorate([ (0, inversify_1.injectable)() ], PingService); //# sourceMappingURL=ping.service.js.map