Приветствую, любители и професионалы нагрузки! Сразу переходим к прошлым выпускам и далее по тексту:
Прошлые выпуски
-
Новости 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теперь роняет прогон сразу, а не молча выкидывает строку из отчёта.
Фейлы недели
-
arrival выбрасывал 85% итераций. Добавляя SyTester, мы сначала сравнили его с нашей открытой моделью (
arrival:) — и проиграли: на 10 000 итераций/с perfscale выдавал 721 RPS и выбрасывал 42 777 итераций при 256 простаивающих воркерах. Причина — канал выдачи итераций глубиной в один слот: планировщик просыпается раз в ~1 мс и раздаёт накопившиеся итерации пачкой, а вторая итерация пачки видела занятый слот и считала, что «все VU заняты». Теперь решение принимает счётчик реально выполняемых итераций. Было 721 RPS, стало ~10 000 с почти нулём отбросов. Если в старых прогонах на высокой интенсивности вы видели многоdropped_iterations— перезапустите на v0.29.0+. -
Бенчмарк был нечестным. Порты между строками отчёта освобождались только в сюитах db, pubsub и boundary. После gRPC-строки (подключение на каждый вызов) на порту висело 16 292 сокета в TIME_WAIT — и следующая строка, кем бы она ни была, мерила потолок свободных портов машины, а не движок. Теперь перед каждой строкой бенч ждёт, пока TIME_WAIT опустится ниже 1000.
-
Кэш 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