Perfscale news #17. Свои шаги на WASM (RFC 009), Node.js-шаги, Faker и нагрузка с нескольких IP и типизированный инпут

Приветствую, любители и професионалы нагрузки! Сразу переходим к прошлым выпускам и далее по тексту:

Прошлые выпуски

  • Новости Perfscale № 1

  • Perfscale news #2: Fix Protocol, Magic metrics и MCP

  • perfscale news #3 Websocket, Inference, NPM

  • Perfscale news #4. GRPC, Fixed Triggers, Child Process

  • Perfscale news #5. SQL, Thresholds, неработающие Linked Accounts

  • Perfscale news #6. GraphQL, поддержка Import, и метрики

  • Perfscale news #7. Fix Import, SOAP, and more HTTP metrics

  • Perfscale news #8. WS Benchmark, Jmeter open sourced & fixed linked accounts

  • Perfscale news #9. GPU, LLM, Docker images

  • Perfscale news #10. Shared variables, Pub/Sub load testing & live metrics

  • Perscale news #11. Fine‑grained token, SQID, stdout log streaming

  • Perfscale news #12. WASM‑библиотеки, SDK для авторов и «живые» логи

  • Perfscale news #13. Выжигание WASM модулей, http3 и первый postmortem

  • Perfscale news #14. Фикс fix, api reference и поддержка webrtc

  • Perfscale news #15. Локальная документация, обновленный bench, и метрик сервер

  • Perfscale news #16. SDK Report API, оптимизации под задачи и опциональные pro модули

Главная новость цикла — в v0.30.0 реализован RFC 009: теперь можно написать свой шаг. Не функцию-генератор значения, а полноценный use:-шаг со своей логикой, проверками и метриками. Вокруг этого — Node.js-шаги как первая библиотека на новом механизме, Faker, gzip, нагрузка с нескольких IP и отдельные Docker-образы под каждый движок. И, конечно, фейлы, поехали

Perfscale OSS

RFC 009: свои шаги на WASM

До сих пор у пользователя с нестандартной задачей было три пути: втиснуть её в std/tcp и генераторы токенов, просить PR в движок или ждать pro-модуль. Проприетарный протокол поверх TCP, подпись всего запроса целиком, «посчитай → проверь → запиши метрику» — всё это упиралось в то, что расширять можно было только значения (${mylib.fn(...)}), но не шаги.

RFC 009 закрывает эту дыру. Тот же WASM-компонент, который раньше экспортировал функции-генераторы, теперь может экспортировать шаги, и они вызываются из use: как lib//:

libraries: - use: ./libs/acme-signer.wasm capabilities: [clock] as: sigsteps: - name: login use: lib/sig/login # шаг `login` из компонента outputs: auth with: user: "trader-${{ vu }}" password: ${{ env.DESK_PASSWORD }} - name: verify use: std/check@v1 with: on: auth.session

Да, я знаю, что lib/ префикс ужасный. Мы пока тестируем с префиксом, чтобы отделять «std» от «не std»

Для тех, кому нужно запустить внешний процесс, появилась новая capability process — одноразовый запуск через хост с ограничением вывода, таймаутом и зачисткой дерева процессов. Это сознательно «доверенный» режим, поэтому включается тройным согласием: грант в YAML, allow_library_capabilities: true и allow_process_actions: true. Без любого из трёх компонент просто не загрузится.

Node.js-шаги — первая библиотека на RFC 009

Первым применением process стала библиотека Perfscale/nodejs и с ней три шага:

Шаг

Что делает

lib/nodejs/install

Скачивает официальный релиз с nodejs.org, сверяет SHA-256 с SHASUMS256.txt до распаковки, кладёт в локальный кэш

lib/nodejs/exec

Запускает inline-скрипт (node -e) или файл нужной версией node

lib/nodejs/install-exec

Ставит (или берёт из кэша) и сразу запускает

# config.yaml — двойное согласие на process + установка node один разallow_library_capabilities: trueallow_process_actions: truelibraries: - use: ./nodejs.wasm capabilities: [process]before: - name: node use: lib/nodejs/install with: node-version: 24.16.0 install-dir: .perfscale-tools/nodejs outputs: setup
# test.yamlsteps: - name: inline-script use: lib/nodejs/exec with: node-path: "${{ config.setup.node-path }}" script: console.log(`hello from node ${process.version}`) check: message_contains: hello from node v24.16.0

Важная информация: количество RPS у вас упадет при использовании nodejs, это стоит понимать, поскольку nodejs не вытащит то количество RPS, которое может выдавать perfscale. Используйте внешние библиотеки на свой страх и риск

Никакого shell: каждый запуск — прямой argv с явным окружением, переменные хоста не наследуются. Пока Linux x86_64 и aarch64. Честное предупреждение: это шаги для оркестрации — подготовить фикстуры, сгенерировать данные, разовые проверки — а не горячий путь на тысячи RPS, каждый вызов — настоящий процесс.

Следом идёт Python по той же модели — официальный standalone CPython, проверка checksum, изолированная установка и те же три шага. Репозиторий Perfscale/python уже создан, библиотека в работе.

Faker

Perfscale/faker — фейковые данные для payload’ов: email, адреса, тестовые платёжные данные.

libraries: - use: ./faker.wasm capabilities: []steps: - use: std/http@v1 with: method: POST url: https://api.example.com/signup body: email: "${faker.internet("email")}" city: "${faker.address("city")}"

Faker подключается и через официальный GitHub Action — тот теперь умеет тянуть кастомные библиотеки из GitHub Releases.

Типизированные inputs: -i и -e

Один и тот же сценарий гоняют на разных окружениях, с разным числом пользователей и опциями. Раньше это решалось правкой YAML или зоопарком переменных окружения. Теперь конфиг объявляет типизированные параметры прогона, шаги читают их как ${{ inputs. }}, а значения задаются при запуске:

# config.yamlinputs: users: number # обязательный, без default host: # host type: string default: localhost verbose: { type: bool, default: false } # принимает 0/1/false/true env: { type: choice, options: [staging, prod], default: staging } # как в github actions pacing_ms: { type: number, min: 1, max: 10000 } # границы для number
# test.yamlsteps: - use: std/http@v1 with: url: "https://${{ inputs.host }}/api/orders?env=${{ inputs.env }}"

Ну и запуск выглядит так:

perfscale run -f test.yaml -c config.yaml -i users=10 -i env=prodperfscale run -f test.yaml -c config.yaml -i users=10,env=prod # через запятуюperfscale run -f test.yaml -c config.yaml -e staging.env # из env-файлаperfscale lint config.yaml -i users=10 # покажет итоговые значения

Приоритет простой: -i в командной строке, затем env-файл (-e, из него берутся только объявленные ключи, так что общий .env подойдёт), затем default:. Главное — всё проверяется до старта: пропущенный обязательный input, неизвестный ключ, users=ten вместо числа, число вне min/max, значение choice не из списка — это ошибка в perfscale lint и при запуске, а не полунастроенный прогон, который упадёт на десятой минуте.

Ограничение: inputs подставляются в with:/check: шагов, а vus: и duration: пока типизированы статически — профиль нагрузки на основе inputs сделаем следующим релизом.

Нагрузка с нескольких IP

Классическая стена в нагрузке на соединения — ~64 тысячи, и упирается она не в CPU и не в память, а в сам TCP. Соединение определяется четвёркой (IP источника, порт источника, IP назначения, порт назначения): с одного адреса на один эндпоинт меняется только порт источника, а эфемерных портов меньше 64k. Для сценариев «N тысяч пользователей держат постоянное соединение» это потолок, который обычно обходят кучей машин-генераторов.

std/http@v1 умеет привязывать исходящие соединения к списку локальных адресов — каждый адрес даёт свои ~64k портов:

steps: - use: std/http@v1 with: url: http://target:8080/api pool: dedicated # своё постоянное соединение на VU source_ips: ["10.0.0.2", "10.0.0.16/28", "fd00::/120"]

gzip в std/http@v1

Сжатые ответы (Content-Encoding: gzip) теперь декодируются прозрачно — достаточно отправить Accept-Encoding: gzip в headers, и распакованное тело уходит в check: и в пути вида ${{ feed.body.posts }} как обычный JSON. Лимит max_response_body_bytes применяется к распакованным данным, так что gzip-бомба не съест память генератора (ранее мы поймали такой сценарий в рамках тестов с LLM агентами, CVE потенциальный закрыт).

Docker: отдельный образ под каждый движок

Раньше варианты образа были суффиксами тегов одного пакета (perfscale:0.29.0-k6), и на странице пакетов это выглядело как «один образ со всем подряд». Теперь каждый вариант — отдельный пакет:

Образ

Состав

ghcr.io/perfscale/perfscale

только perfscale

ghcr.io/perfscale/perfscale-k6

+ k6

ghcr.io/perfscale/perfscale-jmeter

+ JMeter

ghcr.io/perfscale/perfscale-locust

+ locust

ghcr.io/perfscale/perfscale-full

все движки

Нужен только k6 — docker pull ghcr.io/perfscale/perfscale-k6:latest, без JRE и Python в довесок. Старые теги с суффиксом продолжают публиковаться на те же образы — ничего не сломается (надеюсь).

Бенчмарки: SyTester в общих таблицах

В bench появился ещё один конкурент — SyTester от СберТеха. Он встаёт в таблицы throughput, saturation, gRPC и SQL рядом с k6, locust и JMeter. VU-модели у SyTester нет — он держит фиксированный TPS, поэтому получает свой максимум (10 000 TPS по схеме плана). На локальном прогоне при одинаковых 10 000 RPS на GET /health:

RPS

p99

CPU на запрос

Peak RSS

perfscale (arrival)

9 997

0.57 мс

59 µs

27.5 MiB

SyTester

9 406

9.68 мс

743.5 µs

945.9 MiB

Это локальные цифры (macOS); канонические — в CI-отчёте bench. Заодно в workflow бенча появился input apps — выбор движков-конкурентов (k6 jmeter locust sytester), а опечатка в suites или apps теперь роняет прогон сразу, а не молча выкидывает строку из отчёта.

Фейлы недели

  1. arrival выбрасывал 85% итераций. Добавляя SyTester, мы сначала сравнили его с нашей открытой моделью (arrival:) — и проиграли: на 10 000 итераций/с perfscale выдавал 721 RPS и выбрасывал 42 777 итераций при 256 простаивающих воркерах. Причина — канал выдачи итераций глубиной в один слот: планировщик просыпается раз в ~1 мс и раздаёт накопившиеся итерации пачкой, а вторая итерация пачки видела занятый слот и считала, что «все VU заняты». Теперь решение принимает счётчик реально выполняемых итераций. Было 721 RPS, стало ~10 000 с почти нулём отбросов. Если в старых прогонах на высокой интенсивности вы видели много dropped_iterations — перезапустите на v0.29.0+.

  2. Бенчмарк был нечестным. Порты между строками отчёта освобождались только в сюитах db, pubsub и boundary. После gRPC-строки (подключение на каждый вызов) на порту висело 16 292 сокета в TIME_WAIT — и следующая строка, кем бы она ни была, мерила потолок свободных портов машины, а не движок. Теперь перед каждой строкой бенч ждёт, пока TIME_WAIT опустится ниже 1000.

  3. Кэш CI выедал сам себя. PR-прогоны сохраняли свои копии Rust-кэша по 2 ГБ, а релизные сборки — ещё ~2.9 ГБ на тег, которые никто никогда не восстановит. Лимит в 10 ГБ переполнялся, кэш main вытеснялся, и каждый PR компилировался с нуля: шаг тестов шёл 8 минут вместо 4. Теперь кэш пишет только main, а релизы его только читают.

Perfscale platform?

На этой неделе обновлений нет, кроме бампа на последний perfscale oss для синхронизации разработки, следите за обновлениями!

Ссылки

  • GitHub: github.com/Perfscale/perfscale

  • RFC 009: rfcs/009-custom-sdk-steps.md

  • Node.js-шаги: github.com/Perfscale/nodejs

  • Faker: github.com/Perfscale/faker

  • Документация: в репозитории и во вкладке Docs дашборда perfscale serve --ui

  • Платформа: perfscale.ru и perfscale.su

  • Релизы и бинарники: GitHub Releases, npm @perfscale/exe

Источник: habr.com

0 0 голоса
Рейтинг новости
1
0
Подписаться
Уведомить о
0 комментариев