Концепции
Библиотеки
WASM-библиотеки генераторов значений для токенов ${alias.fn(...)} — встроенная @std/random, собственные компоненты на Rust или TypeScript, сандбокс с capabilities и распространение через install/lockfile
Обзор
В любой payload perfscale можно встроить генераторные токены ${...} —
${seq}, ${uuid}, ${rand(1,100)} — которые движок раскрывает при
отправке сообщения. Встроенный набор намеренно мал. Библиотеки — это
точка расширения: библиотека — WASM-компонент (или нативный встроенный
модуль), который добавляет собственные функции, вызываемые из любого
payload как ${alias.fn(...)}:
libraries:
- use: '@std/random@v1' # встроенная, файлы не нужны
steps:
- use: std/db-query@v1
with:
query: INSERT INTO orders (id, code, who) VALUES (?, ?, ?)
params:
- "${random.ulid()}"
- "${random.pattern("ORD-####-^^")}"
- "${random.email()}"
Настоящие UUID v4/v7, ULID, faker-данные (имена, email), взвешенный выбор,
заполнение шаблонов — без правок движка и с единым синтаксисом ${...}
для всех протоколов (HTTP, WebSocket, gRPC, GraphQL, сырой TCP/UDP,
промпты LLM, параметры БД).
Объявление библиотек
libraries: принимается в конфиге, в файле теста или в обоих сразу
(объявления конкатенируются через import:; дубликат алиаса — ошибка
валидации):
libraries:
- use: '@std/random@v1' # встроенная; алиас по умолчанию: random
- use: '@std/random@v1'
as: ids # второй экземпляр, токены: ${ids.fn(...)}
- use: ./libs/fixer-ids.wasm # локальный WASM-компонент, путь относительно этого файла
- use: 'https://vendor.example.com/faker-1.2.0.wasm'
sha256: '9f2c…64 hex…' # обязателен для HTTPS-источников
- use: 'git+https://github.com/org/repo.git@v1.2.3#libs/fixer-ids.wasm'
use:— встроенная ссылка@ns/name@vN(кавычки обязательны:@— зарезервированный индикатор YAML), локальный путь.wasm, HTTPS-URL или ссылкаgit+<repo>@<ref>#<path>.as:— префикс токенов. По умолчанию — имя самой библиотеки.capabilities:— явные гранты сандбокса, см. ниже.with:— JSON-конфиг, передаваемый вinit()библиотеки; можно использовать${{ env.X }}, чтобы секреты маскировались в логах.
Удалённые источники (https://, git+) никогда не скачиваются во время
прогона. perfscale install скачивает каждую удалённую библиотеку один
раз, проверяет дайджест, кладёт в контентно-адресуемый кэш и записывает
perfscale.lock рядом с объявляющим файлом — лок стоит коммитить. Далее
run и lint резолвят всё полностью офлайн; отсутствующая запись в
локе — жёсткая ошибка с подсказкой запустить perfscale install
(--refresh перерезолвит git-ссылки).
Семантика токенов
- Встроенные токены (
${seq},${rand}, …) матчатся первыми и не изменились; алиас не может их перекрыть. - Неизвестный алиас → токен остаётся как есть (обратная совместимость;
perfscale lintподсветит). Известный алиас + неизвестная функция или плохие аргументы → шаг падает. Опечатка не должна молча уезжать в payload. - Аргументы маппятся текст → JSON по фиксированному контракту: разбиение по
запятым вне двойных кавычек, trim, снятие одной пары кавычек, парсинг как
JSON, если возможно.
int(1,100)передаёт два числа;pick(a|b|c)— одну строку. - Опциональный последний аргумент
keyу любой функции мемоизирует результат в пределах одного сообщения: два${random.uuid4(order)}в одном сообщении дают один id; следующее сообщение генерирует новый. seed: 42в конфиге делает прогон воспроизводимым — каждый экземпляр библиотеки получает seed какhash(seed, vu_id, conn_seq).- Упавший вызов (ошибка гостя, trap, таймаут) роняет шаг с записанной причиной — нагрузочный тест никогда не отправит неверные данные и не отрапортует зелёным.
Встроенная @std/random@v1
Поставляется с движком — без WASM, без install, без capabilities. Каждая
функция принимает опциональный мемо-ключ key в конце:
| Функция | Возвращает |
|---|---|
uuid4([key]) / uuid7([key]) | Случайный v4 / упорядоченный по времени v7 UUID |
ulid([key]) | 26-символьный Crockford-base32 ULID |
nanoid([len], [key]) | URL-safe id (длина по умолчанию 21) |
int(a, b, [key]) | Случайное целое в [a, b] |
float(a, b, [dp], [key]) | Случайное число, dp знаков после запятой |
pick(a|b|c, [key]) | Случайный выбор из вариантов через | |
weighted(a:10|b:90, [key]) | Взвешенный случайный выбор |
seq(name) | Именованный монотонный счётчик, с 1 |
pattern("ORD-####-????") | Шаблон: #→цифра, ?→a-z, ^→A-Z, *→буква-цифра |
first_name() / last_name() / name() | Случайные имена |
username() / email() / company() | Случайный username / email / компания |
lorem([words]) | Слова lorem ipsum (по умолчанию 5) |
phone() | Случайный номер телефона |
date(a, b) / timestamp(a, b) / datetime(a, b) | Случайные даты и таймстампы в диапазоне |
Capabilities и сандбокс
Пользовательские библиотеки работают в сандбоксе wasmtime с fail-closed
моделью capabilities: WASI-импорты компонента сверяются с грантом
capabilities: в YAML, и хост подключает ровно их пересечение. Компонент,
импортирующий больше разрешённого, — жёсткая ошибка загрузки.
| Capability | Хост предоставляет | Границы |
|---|---|---|
fs | preopens wasi:filesystem | только пути под fs_root, read-only |
clock | wasi:clocks | время также приходит в контексте вызова |
Любой непустой грант требует allow_library_capabilities: true в конфиге —
тот же fail-closed паттерн, что у allow_file_actions. Сырые сокеты не
предоставляются никогда. wasi:random тоже не предоставляется: библиотеки
черпают случайность из seeded PRNG в своём SDK — именно это делает
воспроизводимыми прогоны с seed:. Fuel на вызов и таймаут по wall-time
ограничивают ущерб от взбесившегося компонента на горячем пути, а время
внутри вызова библиотеки учитывается в длительности шага — стоимость
библиотеки видна в метриках, а не спрятана.
Своя библиотека
Одна форма, три языка — каждый SDK скрывает всю WIT-обвязку и даёт seeded
PRNG (бит-в-бит совпадающий между SDK и движком), memo() для переиспользования
в пределах сообщения, хелперы аргументов и тестовый харнесс без рантайма:
- Rust —
perfscale-library-sdk, собирается вwasm32-wasip2без дополнительного тулчейна. Репозиторий library-random — WASM-порт@std/random— служит шаблоном для авторов. - TypeScript / JavaScript —
@perfscale/library-sdk:defineLibrary({...}), затемnpx perfscale-library-build mylib.ts -o mylib.wasm(jco componentize). Учтите: jco-компоненты всегда импортируютwasi:filesystem, поэтому TS-библиотекам нужен грантfs, даже если они чистые. - Go — задокументированный рецепт на TinyGo в том же репозитории sdk-libraries (экспериментально).
Доступность и lint
Библиотеки — часть YAML теста/конфига, поэтому одно и то же определение
работает из CLI и на ваших машинах. Встроенной @std/random вообще не
нужны файлы; пользовательские WASM-компоненты должны присутствовать на
генераторе нагрузки (относительные пути резолвятся от объявляющего файла,
удалённые ссылки — из кэша install). perfscale lint загружает объявленные
библиотеки и валидирует каждый токен ${alias.fn(...)} по реально
экспортируемым функциям — опечатки ловятся до прогона.
Полная поверхность конфигурации — в YAML-справочнике движка; обоснование дизайна — в RFC 005.