Торговые фиды, чаты, состояние мультиплеера, GraphQL-подписки — realtime-половина вашего стека говорит на WebSocket, а perfscale до сих пор заставлял тестировать её через k6-скрипты. Начиная с v0.6.0 WebSocket — полноценный нативный транспорт: шесть actions std/ws* в open-source-движке, рядом со std/http, std/tcp и std/udp.
И в отличие от нашей поддержки FIX, WebSocket бесплатен для всех. Это commodity-транспорт; прятать его за план было бы глупо. Actions, генератор сообщений и метрики живут в open-source-репозитории.
Один шаг — одна сессия
Самый быстрый вход — std/ws@v1: подключиться, обменяться сообщениями, закрыть, всё за один замер:
steps:
- uses: std/ws@v1
with:
url: wss://stream.example.com/feed
messages:
- send: '{"op":"subscribe","channel":"trades","id":"sub-${seq}"}'
until_json: { type: trade }
check:
message_matches: { type: trade }
Правило until_json объясняет шагу, что считать ответом: читать из стрима, пока сообщение не совпадёт по JSON с { type: trade }, затем идти дальше. Не совпало за таймаут? Шаг падает — это и есть сигнал нагрузочного теста.
Или держите соединение между шагами
Реальные сценарии редко умещаются в один шаг. std/ws-connect@v1 открывает живое соединение, к которому следующие шаги обращаются по id — WebSocket-трафик чередуется с чем угодно ещё:
steps:
- uses: std/ws-connect@v1
with: { url: "wss://stream.example.com/feed" }
outputs: feed
- uses: std/ws-send@v1
with:
id: "${{ feed.id }}"
send: '{"op":"order","id":"ord-${seq}","px":${randf(1.05,1.15,5)}}'
repeat: 100
interval_ms: 50
- uses: std/ws-recv@v1
with: { id: "${{ feed.id }}", until_json: { type: fill } }
- uses: std/ws-close@v1
with: { id: "${{ feed.id }}" }
Эта строка send — один шаблон, порождающий 100 уникальных ордеров: токены ${seq}, ${uuid}, ${rand}, ${randf}, ${choice} и ${now_*} раскрываются на каждую отправку. Тот же генератор, что питает наши FIX-actions, теперь живёт в открытом движке — один словарь токенов на все протоколы.
Метрика задержки, которая не врёт
Большинство WebSocket-тестов выдают мутное число: время в цикле приёма, которое в основном измеряет, когда серверу захотелось push'нуть. Мы отказались класть это в гистограмму латенси.
Вместо этого perfscale сообщает три честные вещи:
- Время handshake и one-shot-сессии попадает в
http_req_duration— операции с определённым началом и концом, сравнимые с перцентилями HTTP/TCP/UDP. ws_msg_rtt— время от отправки до первого ответа, совпавшего с вашим until-правилом. Метрика существует, только когда вы объявили, что считать ответом — поэтому её p95 что-то значит.ws_msgs_sent/ws_msgs_received— обычные скорости обмена.
ws_msg_rtt: avg=12.40ms p(50)=11.00ms p(90)=18.20ms p(95)=21.50ms p(99)=30.10ms min=4.10ms max=35.00ms count=400
ws_msgs_sent: 2000 400.00/s
ws_msgs_received: 1200 240.00/s
На платформе WebSocket-прогон распознаётся автоматически, и дашборд переключается на WS-плитки: throughput сообщений, перцентили RTT, латенси handshake.
Ассерты на стримах
Стримы шумные — heartbeat'ы, presence-события, чужие сообщения. Поэтому ассерты сообщений работают в семантике «хотя бы одно совпало» — и на любом протокольном action, который отдаёт список messages (WebSocket и FIX одинаково):
check:
message_matches: { type: fill, symbol: EURUSD }
messages_count_gte: 5
Остальное тоже на месте: бинарные фреймы как base64, согласование subprotocol'ов (graphql-ws, STOMP), wss:// со skipTLSVerify для self-signed staging, переиспользуемые connection-профили и транспортный std/ws-ping@v1 для keep-alive-проверок.
Попробуйте
npm install -g @perfscale/exe # или: perfscale self-update
perfscale run -f examples/websocket.test.yaml
Начните с гайда по WebSocket, документации или готового examples/websocket.test.yaml. Если ваш realtime-стек плавится на пике — лучше узнать об этом из YAML-файла, чем от пользователей.
