Концепции

Библиотеки

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Хост предоставляетГраницы
fspreopens wasi:filesystemтолько пути под fs_root, read-only
clockwasi: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.