mullvad-servers-ping-tester
Version: 
Инструмент для тестирования пинга серверов Mullvad VPN с расширенной аналитикой
189 lines • 9.83 kB
JavaScript
;
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