# Что измерять в бенчмарке прокси

Source: https://ipvolt.com/ru/blog/what-a-proxy-benchmark-should-measure
Markdown: https://ipvolt.com/ru/blog/what-a-proxy-benchmark-should-measure.md
Language: ru

[ipvolt — главная](https://ipvolt.com/ru.md) / [Блог](https://ipvolt.com/ru/blog.md) / Что измерять в бенчмарке прокси

Анализ
Опубликовано: 2026-09-11
Обновлено: 2026-09-11
Автор: ipvolt team
7 мин чтения

Сравнивайте прокси по успешности получения контента, времени ответа и поведению сессий с помощью Python-обвязки на curl, которая фиксирует сбои и считает результаты.

Сравнивайте прокси по доле успеха на уровне приложения, распределениям времени ответа, установлению соединения и поведению сессий. Определите успех для каждого целевого сайта до сбора результатов. Примеры ниже включают наблюдаемый прямой базовый замер и небольшую обвязку, которая проверяет маркер в ответе и считает долю успеха и задержку. Сопоставимые результаты для резидентных и серверных прокси мы пока не публиковали.

## Определите успех до измерения скорости

Низкое время ответа может скрывать высокую стоимость одного успешного запроса, когда сбои вызывают повторы. Считайте выполненную работу, а не только затраченное время, и включайте трафик повторов в оценку стоимости.

Успеху нужно определение для каждого целевого сайта: ожидаемый статус, обязательное содержимое и приемлемый исход редиректа. Ответ HTTP 200 с CAPTCHA может этому определению не соответствовать. Отсутствующее поле или неполная загрузка — тоже. Простой текстовый маркер полезен для стартовой проверки; боевой нагрузке могут понадобиться разобранные записи, валидация по схеме или полный многошаговый поток.

Фиксируйте успешные ответы и каждую категорию сбоев по отдельности, затем разбирайтесь в причине. Таймаут может произойти на нескольких участках пути. Один лишь 403 не говорит, какой посредник или origin применил ограничение. Ни время ответа, ни статус недостаточны, чтобы назначить виновного.

## Записывайте время до первого байта и общее время

curl сообщает временные вехи, измеренные от начала передачи. Время до первого байта (TTFB) показывает, когда начался ответ; общее время включает получение тела. Записывайте обе величины, потому что быстрый первый байт всё ещё может предшествовать медленной или неполной загрузке.

11 сентября 2026 года три последовательных прямых запроса к `https://example.com/` с VPS в Хельсинки через curl 8.18 дали следующие значения. Прокси не использовался. Подписи ниже делают накопительный смысл явным:

```text
http=200 tcp_complete=0.009780s tls_complete=0.023224s ttfb=0.044821s total=0.044983s
http=200 tcp_complete=0.008289s tls_complete=0.023851s ttfb=0.039902s total=0.039966s
http=200 tcp_complete=0.008149s tls_complete=0.031935s ttfb=0.061614s total=0.061688s
```

Это наблюдаемая база примерно 40–62 мс до первого байта, а не нижняя граница для будущих запросов. Три наблюдения не устанавливают устойчивого распределения. Соберите свежий базовый замер на той же машине и том же целевом сайте незадолго до сравнения прокси и сохраните дату прогона, версию клиента и конфигурацию.

Эта ограниченная по времени команда собирает те же поля для нового прямого запроса:

```sh
curl --disable --silent --show-error --noproxy '*' \
  --connect-timeout 5 --max-time 20 --retry 0 \
  --output /dev/null \
  --write-out 'http=%{http_code} tcp_complete=%{time_connect}s tls_complete=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s exit=%{exitcode}\n' \
  'https://example.com/'
```

Для того же запроса через HTTP(S)-прокси используйте curl 8.3 или новее. Задайте в `PROXY_URL` шлюз провайдера без учётных данных, включая поддерживаемую схему и порт. Пусть менеджер секретов передаст `PROXY_USERNAME` и `PROXY_PASSWORD` в окружении. Этот пример следует [руководству по настройке curl](/ru/guides/curl-proxy-setup): curl импортирует учётные данные сам, поэтому раскрытый пароль не становится аргументом оболочки. Не включайте трассировку оболочки.

```sh
: "${PROXY_URL:?Set a credential-free HTTP(S) gateway URL}"
curl --disable --silent --show-error --noproxy '' \
  --proxy "$PROXY_URL" --proxy-basic \
  --variable %PROXY_USERNAME --variable %PROXY_PASSWORD \
  --expand-proxy-user '{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}' \
  --connect-timeout 5 --max-time 20 --retry 0 \
  --output /dev/null \
  --write-out 'http=%{http_code} tunnel=%{http_connect} tcp_complete=%{time_connect}s tls_complete=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s exit=%{exitcode}\n' \
  'https://example.com/'
```

`--disable` стоит первым, чтобы игнорировать личную конфигурацию curl. `--noproxy '*'` в прямой команде обходит унаследованные прокси; пустой список обхода в проксированной команде не даёт `NO_PROXY` исключить целевой сайт. Эти команды используют одно свежее соединение на процесс curl, не следуют редиректам и не делают повторов. При HTTP-ошибке код выхода curl всё равно может быть нулевым, потому что команды намеренно не содержат `--fail`; проверяйте HTTP-статус отдельно. О выборе маршрута см. [руководство curl](https://curl.se/docs/manpage.html#--noproxy), о дедлайнах — [руководство по таймаутам](/ru/guides/proxy-timeout-troubleshooting).

## Понимайте, что входит в тайминги соединения

Для HTTPS-сайта через HTTP-прокси curl устанавливает соединение с прокси, запрашивает туннель CONNECT, затем согласовывает TLS с целевым сервером через этот туннель. CONNECT определён в [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110#name-connect).

Интервал между `time_connect` и `time_appconnect` включает установку туннеля и установление TLS. Вычитание соответствующего прямого интервала не изолирует накладные расходы CONNECT: условия маршрутизации и рукопожатия тоже меняются. HTTPS-прокси добавляет ещё одно TLS-рукопожатие. Статический объект в CDN эти различия не убирает.

Используйте вехи, чтобы локализовать, где появляется задержка, затем исследуйте с помощью контролируемых конечных точек или должным образом инструментированных логов клиента и прокси. Вычитайте только вехи, завершившиеся в одной и той же простой передаче. Неудачные запросы, переиспользованные соединения и редиректы требуют отдельной интерпретации; нулевое значение может означать, что фаза не была достигнута.

`%{http_connect}` сообщает код ответа на HTTP CONNECT отдельно от HTTP-статуса передачи. `--proxy-header` отправляет заголовок прокси; статус туннеля он не сообщает. Предпочитайте выбранные поля статуса и тайминга подробным трассировкам, которые могут содержать данные аутентификации. Эти интерпретации следуют [документированным полям тайминга](https://curl.se/docs/manpage.html#-w) curl.

## Проверяйте ротацию по обещанной политике

Исходящий IP-адрес — это публичный адрес, который наблюдает целевой сайт. Обращайтесь к поддерживаемой провайдером диагностической конечной точке или к контролируемой вами конечной точке, которая сообщает адрес вызывающего. [ipify](https://www.ipify.org/) — один из публичных вариантов; проверьте его актуальные правила использования, прежде чем автоматизировать запросы.

Сохраняйте последовательность результатов с метками времени, прежде чем сводить их к числу уникальных адресов. Для каждой попытки записывайте выбранный регион, настройку сессии, транспортный результат и валидный IPv4- или IPv6-адрес. Исключайте неудачные запросы и невалидные тела из подсчёта адресов, но сообщайте об этих сбоях отдельно. В Python ответ с адресом в виде простого текста можно проверить через `ipaddress.ip_address(value.strip())`.

Сравните эту последовательность с политикой ротации провайдера. Ротация может происходить на каждый запрос, соединение или интервал времени, и пул может переиспользовать адрес. Десять запросов не требуют десяти различных исходящих узлов, если продукт явно этого не обещает. Sticky-сессия призвана сохранять исходящий узел на документированный срок; проверьте её истечение и поведение при отключении, прежде чем считать переназначение неисправностью.

Используйте настройки страны или региона и сессии, которые нужны вашей нагрузке. Повторите тест на протяжении соответствующего окна сессии. [Руководство по ротируемым и sticky-прокси](/ru/guides/rotating-vs-sticky-proxies) объясняет, какие виды поведения оценивать. Бенчмарк содержимого ниже ротацию не измеряет.

## Выберите выборку и окно наблюдения

Десять запросов могут выявить базовые сбои, но дают мало свидетельств о распределении задержек. Медленные запросы могут задерживать очередь; одно лишь среднее может скрывать медленные выбросы.

Наша предлагаемая отправная точка — 200 запросов на каждый целевой сайт на каждую конфигурацию, распределённых как минимум на час. Это решение о дизайне теста, а не универсальный порог надёжности. 95-й процентиль маленькой выборки зависит от очень немногих наблюдений. Сообщайте размер выборки, число сбоев и окно наблюдения, затем повторяйте в разное время и в условиях, релевантных нагрузке.

Сравнивая провайдеров, чередуйте их запросы в одном и том же окне. Это уменьшает различия, вызванные меняющейся нагрузкой на целевой сайт, не гарантируя идентичных условий. Держите содержимое запросов, настройки клиента и критерии успеха одинаковыми. Переиспользование соединений и боевую конкурентность тестируйте отдельно от последовательного примера со свежим процессом ниже.

## Небольшой бенчмарк содержимого со сводкой

Сохраните это как `proxy-bench.py` и запустите `python3 proxy-bench.py`. Требуются Python 3 и curl 8.3+. Используются те же закрытые переменные окружения прокси, что и выше. Задайте `BENCH_MODE=direct` для прямого прогона или оставьте `proxy`. Каждый прогон создаёт новый каталог результатов и печатает его путь.

Целевой сайт по умолчанию — небольшая иллюстративная страница. Для реального сравнения задайте в `BENCH_URL` разрешённую HTTPS-конечную точку и замените `BENCH_MARKER` на содержательную ожидаемую строку. Не используйте URL с учётными данными или чувствительными параметрами. Адаптируйте проверку содержимого под реальный формат ответа, прежде чем ссылаться на успех на уровне приложения.

```python
import collections
import datetime
import json
import math
import os
from pathlib import Path
import statistics
import subprocess
import tempfile
import time

url = os.environ.get("BENCH_URL", "https://example.com/")
marker = os.environ.get("BENCH_MARKER", "<h1>Example Domain</h1>").encode()
mode = os.environ.get("BENCH_MODE", "proxy")
if not url.startswith("https://") or not marker or mode not in {"direct", "proxy"}:
    raise SystemExit("Use an HTTPS URL, a nonempty marker and direct or proxy mode")
route = ["--noproxy", "*"]
if mode == "proxy":
    route = ["--noproxy", "", "--proxy", os.environ["PROXY_URL"], "--proxy-basic",
             "--variable", "%PROXY_USERNAME", "--variable", "%PROXY_PASSWORD",
             "--expand-proxy-user", "{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}"]
fields = ["http", "tunnel", "tcp_complete", "tls_complete", "ttfb", "total"]
write_out = "%{http_code} %{http_connect} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}"
count, window = 200, 3600.0
directory = Path(tempfile.mkdtemp(prefix="proxy-bench-", dir="."))
rows = []
started = time.monotonic()
with (directory / "results.jsonl").open("x") as log:
    for i in range(count):
        due = started + i * window / (count - 1)
        time.sleep(max(0, due - time.monotonic()))
        timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
        with tempfile.TemporaryDirectory() as scratch:
            body = Path(scratch) / "body"
            result = subprocess.run(
                ["curl", "--disable", "--silent", "--connect-timeout", "5",
                 "--max-time", "20", "--retry", "0", *route,
                 "--output", str(body), "--write-out", write_out, "--url", url],
                capture_output=True, text=True)
            values = result.stdout.split()
            timings = dict(zip(fields, values)) if len(values) == len(fields) else {}
            if result.returncode:
                outcome = f"curl_{result.returncode}"
            elif not timings:
                outcome = "missing_measurements"
            elif timings["http"] != "200":
                outcome = "http_" + timings["http"]
            elif not body.exists() or marker not in body.read_bytes():
                outcome = "content_mismatch"
            else:
                outcome = "success"
        row = dict(attempt=i + 1, timestamp=timestamp, mode=mode,
                   exit_code=result.returncode, outcome=outcome, **timings)
        rows.append(row)
        log.write(json.dumps(row) + "\n")
        log.flush()

successful = [row for row in rows if row["outcome"] == "success"]
summary = {"attempts": len(rows), "successes": len(successful),
           "success_rate": len(successful) / len(rows),
           "outcomes": dict(collections.Counter(row["outcome"] for row in rows))}
for metric in ("ttfb", "total"):
    samples = sorted(float(row[metric]) for row in successful)
    summary[metric] = ({"median_seconds": statistics.median(samples),
                        "p95_seconds": samples[math.ceil(0.95 * len(samples)) - 1]}
                       if samples else None)
(directory / "summary.json").write_text(json.dumps(summary, indent=2) + "\n")
print(directory)
print(json.dumps(summary, indent=2))
```

Первый запрос стартует сразу; 200-й запланирован через 3 600 секунд после первого. Запросы остаются последовательными. Если запрос занимает больше, чем интервал между ними, последующие старты сдвигаются, и прогон занимает больше времени. Фактические метки времени старта сохраняются в логе. Автоматических повторов и следования редиректам нет, поэтому ответ 3xx в этом примере — HTTP-сбой. Если вашей нагрузке нужны редиректы, определите, какие целевые адреса и итоговое содержимое приемлемы, прежде чем расширять обвязку.

Каждая строка лога содержит номер попытки, метку времени старта в UTC, режим маршрута, код выхода curl, исход, статусы HTTP и CONNECT и четыре накопительные временные вехи в секундах. Обвязка не сохраняет в файлах результатов ни тела ответов, ни учётные данные. Ведите отдельную закрытую заметку о прогоне с провайдером, пулом, целью, версией клиента и конфигурацией сессии.

Знаменатель доли успеха — **все предпринятые запросы**, включая таймауты и сбои содержимого. Коды выхода curl, такие как 28 (таймаут), 7 (сбой соединения) и 56 (сбой приёма), описывают широкие категории сбоев, а не подтверждённый неисправный компонент. При расследовании пользуйтесь [официальным справочником кодов выхода](https://everything.curl.dev/cmdline/exitcode.html).

Статистика задержек использует только успешные ответы, а сводка держит рядом с ней число сбоев. Медиана — среднее по положению значение, при необходимости усредняющее среднюю пару. 95-й процентиль использует метод ближайшего ранга: отсортируйте успешные образцы и выберите ранг `ceil(0.95 × sample_count)`, считая с единицы. Без успехов обе сводки задержек равны `null`.

Например, четыре успеха из пяти попыток дают долю успеха 80%. Если их значения TTFB равны 0,10, 0,12, 0,20 и 0,40 секунды, медиана — 0,16 секунды, а p95 по ближайшему рангу — 0,40 секунды. Это выдуманные значения для объяснения расчёта, а не измеренные результаты прокси.

Начните с короткой контролируемой проверки обвязки и вашего правила для содержимого, затем выполните согласованное окно наблюдения. Публикуйте конфигурацию, размер выборки, сбои и определение успеха вместе с любым сравнением задержек. Прямой базовый замер выше иллюстративен; он не устанавливает рейтинга прокси.

## Источники

- [curl manual: --write-out variables and proxy configuration](https://curl.se/docs/manpage.html#-w)
- [everything curl: exit codes](https://everything.curl.dev/cmdline/exitcode.html)
- [RFC 9110: HTTP Semantics, section 9.3.6 CONNECT](https://www.rfc-editor.org/rfc/rfc9110#name-connect)
- [ipify: a simple public IP address API](https://www.ipify.org/)

## Узнайте, когда откроется доступ к ipvolt.

Пока вы здесь

ipvolt в разработке. Оставьте email, и мы один раз напишем, когда откроется доступ.

Одно письмо, когда откроется доступ. Больше ничего.

[Получить ранний доступ](https://ipvolt.com/ru/blog/what-a-proxy-benchmark-should-measure#waitlist-blog-end)

[Конфиденциальность](https://ipvolt.com/privacy)


## Похожие статьи

- [Настройка прокси в MostLogin: добавить, проверить, поделиться](https://ipvolt.com/ru/blog/mostlogin-proxy-setup.md) (Анализ, 17 сент. 2026 г., 7 мин чтения): Пошагово добавляем прокси в профили MostLogin: протокол, хост и порт, учётные данные, проверка IP, доступ команды, массовый импорт и ошибки, которые всё ломают.
- [Цены на Amazon: ваш оффер против Featured Offer](https://ipvolt.com/ru/blog/amazon-offer-vs-featured-offer.md) (Анализ, 16 сент. 2026 г., 6 мин чтения): Как сравнивать свой оффер на Amazon с Featured Offer: сопоставленный контекст, известная доставка и протестированная офлайн-модель, сохраняющая отсутствующие данные.
- [Обновления листингов Amazon: «принято» не значит «в продаже»](https://ipvolt.com/ru/blog/amazon-listing-update-reconciliation.md) (Анализ, 14 сент. 2026 г., 7 мин чтения): Как разбирать принятые обновления листингов Amazon: отдельно сверяем отправленные атрибуты, живые офферы, остатки и возможность покупки по практической матрице сверки.

## Связанные руководства

- [Прокси в curl: флаг -x, переменные окружения, SOCKS5, аутентификация](https://ipvolt.com/ru/guides/curl-proxy-setup.md): Как использовать прокси в curl: флаг -x, переменные http_proxy и https_proxy, SOCKS5 через socks5h, аутентификация на прокси и разбор ошибок CONNECT и 407.
- [Диагностика таймаутов прокси: по одному этапу за раз](https://ipvolt.com/ru/guides/proxy-timeout-troubleshooting.md): Разделяем задержки DNS, TCP, CONNECT, TLS и ответа прокси с помощью таймингов curl, затем задаём дедлайны запросов и решаем, безопасен ли повтор.
- [Ротируемые и sticky-прокси: планируйте непрерывность сессии](https://ipvolt.com/ru/guides/rotating-vs-sticky-proxies.md): Выбирайте ротацию под наименьший законченный сценарий, отличайте привязку к выходу от cookie и проверяйте, что происходит, когда sticky-сессия завершается раньше срока.

## О ipvolt

Технический анализ от команды ipvolt.

Доступ к ipvolt пока не открыт.

[Читать оригинал на английском](https://ipvolt.com/blog/what-a-proxy-benchmark-should-measure.md)
