Pythonic LoadRunner Framework
Высоконагруженный модульный фреймворк для Doomsday: Last Survivors. Синхронный линейный интерфейс скриптов VUser в стиле HP/MicroFocus LoadRunner, оркестрируемый асинхронным ядром конкурентности Trio для параллельного запуска 50+ ботов.
1. Концепция и разделение конкурентности
Фреймворк устраняет конфликт между высокой нагрузкой (50+ сетевых сокетов) и читаемостью прикладной логики.
Автор скрипта пишет линейный синхронный код без единого async или await,
а оркестратор Trio распределяет потоки воркеров и контролирует лимиты соединений.
Контроллер сценариев (Trio)
controller/scenario.py
Оркестратор теста на базе структурированной конкурентности trio.open_nursery.
Управляет CapacityLimiter (пул до 50 сокетов), поэтапным стартом Ramp-up
(по 5 ботов каждые 2 секунды), паузами Pacing между прогонами и автоматической отменой при остановке.
Скрипт VUser (LoadRunner C-Style)
scripts/*.py
Линейный синхронный код. Вся многопоточность изолирована в threading.local().
Любой вызов lr.transaction(), lr.think_time(), lr.save_string() или
lr.send_and_wait() автоматически привязан к персональному экземпляру бота.
2. Справочник функций модуля core.lr (LoadRunner VuGen API)
Модуль core.lr полностью воспроизводит интерфейс прикладного программирования MicroFocus LoadRunner (VuGen).
Все вызовы изолированы в контексте активного виртуального пользователя (threading.local), поддерживают типизацию и автокомплит в IDE.
2.1. Измерение транзакций (Transactions)
| Функция | Сигнатура Python | Поведение и особенности |
|---|---|---|
lr_start_transaction |
lr.start_transaction(name: str) |
Запуск таймера высокой точности (time.perf_counter()). Транзакции опциональны и служат только для замера задержек и SLA. |
lr_end_transaction |
lr.end_transaction(name: str, status: str = LR_AUTO) |
Остановка таймера и фиксация времени отклика. LR_AUTO автоматически выставляет PASS или FAIL. Также поддерживаются LR_PASS, LR_FAIL, LR_STOP. |
| Контекстный менеджер | with lr.transaction(name: str, status: str = LR_AUTO): |
Синтаксический сахар: автоматически замеряет длительность блока, а при возникновении необработанных ошибок транслирует статус в LR_FAIL. |
2.2. Сетевые вызовы и отправка Protobuf (Network Protocol)
| Функция | Сигнатура Python | Поведение и особенности |
|---|---|---|
lr_send |
lr.send(command, payload=None, **params) |
Асинхронная отправка фрейма в сокет без ожидания ответа. Поддерживает имена ("KMsgCl2GspingRequest"), шестнадцатеричные опкоды (0x2716) и нативные параметры. |
lr_send_and_wait |
lr.send_and_wait(command, expected_reply, timeout=5.0, **params) |
Синхронная отправка с ожиданием ответа сервера. Автоматически сериализует аргументы в бинарный Protobuf по схеме opcodes.json. Выбрасывает TimeoutError при превышении лимита времени. |
2.3. Корреляция и извлечение данных (Correlation)
| Функция | Сигнатура Python | Поведение и особенности |
|---|---|---|
lr_extract_param |
lr.extract_param(reply, field_path: str, param_name: str) |
Извлекает значение поля из ответа сервера (DlsFrame или словарь) и сохраняет в параметр param_name. Автоматически сопоставляет символьные имена ("value") с номерами тегов Protobuf через реестр opcodes.json. |
2.4. Параметризация и контекст VUser (Parameter Storage)
| Функция | Сигнатура Python | Поведение и особенности |
|---|---|---|
lr_save_string |
lr.save_string(name: str, value: str) |
Сохранение строкового параметра в изолированное локальное хранилище активного бота. |
lr_save_int |
lr.save_int(name: str, value: int) |
Сохранение целочисленного параметра. |
lr_get_param |
lr.get_param(name: str, default=None) |
Получение значения сохраненного параметра с поддержкой политики Update value on: Each occurrence. |
lr_eval_string |
lr.eval_string(template: str) |
Динамическая подстановка маркеров {ParamName} в строку перед отправкой на сервер. |
2.5. Управление потоком исполнения и обработка ошибок
| Функция | Сигнатура Python | Поведение и особенности |
|---|---|---|
lr_continue_on_error |
lr.continue_on_error(value: int = 1) |
Управление реакцией: 1 (продолжать при ошибках) или 0 (прерывать). Имеет наивысший приоритет над RTS. Поддерживается контекстный менеджер: with lr.continue_on_error(1):. |
lr_check_error_code |
lr.check_error_code(reply, allowed: list = [0]) |
Проверка серверного кода ошибки (поле 99). При недопустимом коде помечает активные транзакции как FAIL и останавливает итерацию при continue_on_error=0. |
lr_abort |
lr.abort(msg: str) |
Немедленное прерывание текущей итерации VUser с выбросом VUserAbortException. |
lr_exit |
lr.exit(option, status, msg: str) |
Управляемый выход: LR_EXIT_VUSER (остановить бота насовсем), LR_EXIT_ACTION_AND_CONTINUE (прервать текущее действие), LR_EXIT_ITERATION_AND_CONTINUE (перейти к следующей итерации). |
2.6. Управление сессией и паузы размышления (Think Time)
| Функция | Сигнатура Python | Поведение и особенности |
|---|---|---|
lr_login |
lr.login(user_id=None, email=None, password=None) |
Полный цикл входа: проверка токенов, TCP-соединение, рукопожатие канала (0x279f → 0x27a0) и вход в мир (0x27a1 → 0x27a2). Вызывается в vuser_init(). |
lr_logout |
lr.logout() |
Штатный выход из игры, закрытие сокета и корректное освобождение потоков. Вызывается в vuser_end(). |
lr_think_time |
lr.think_time(min_sec: float, max_sec: float = None) |
Имитация пауз размышления человека. Подчиняется настройкам RTS: поддерживает отключение (ignore), масштабирование (factor) и ограничение максимума (max_limit_sec). |
2.7. Логирование и вывод сообщений (Logging)
| Функция | Сигнатура Python | Поведение и особенности |
|---|---|---|
lr_output_message |
lr.output_message(msg: str) |
Информационное сообщение VUser. Автоматически форматирует плейсхолдеры {Param} через eval_string и пишет в персональный лог бота и общий лог. |
lr_error_message |
lr.error_message(msg: str) |
Сообщение об ошибке. Записывается в лог бота и системный errors.log. Автоматически помечает открытые транзакции как сбойные при включенной опции fail_open_transactions_on_error. |
log |
lr.log(msg: str) |
Краткий универсальный синоним для output_message. |
3. Жизненный цикл VUser и Run-Time Settings (RTS)
Инициализация и вход в игру (vuser_init)
Выполняется ровно один раз при запуске скрипта. Вызывает lr.login(): получение сессионных токенов, TCP-соединение с GameServer, авторизация канала (0x279f → 0x27a0) и вход в игровой мир (0x27a1 → 0x27a2).
Основной цикл действий (action)
Основное тело скрипта. Выполняется циклически N итераций (задается в параметре rts["run_logic"]["iterations"] скрипта). Между итерациями выдерживается настраиваемый Pacing (asap, fixed delay, random delay).
Завершение работы и выход из игры (vuser_end & teardown)
Выполняется ровно один раз по завершении всех итераций. Вызывает lr.logout(): штатный выход из игрового мира, закрытие сетевого сокета и освобождение системных ресурсов.
Настройки выполнения скрипта — Run-Time Settings (RTS)
В точности как в LoadRunner VuGen, параметры Run Logic (итерации, pacing), Think Time, Error Handling и Logging задаются прямо в коде скрипта:
4. Параметризация и датапулы (VuGen & Controller)
Каноническая реализация подсистемы параметров HP/MicroFocus LoadRunner:
параметр — это именованный маркер в коде ({UserName}, {UserId}),
а датапул — внешний табличный источник данных (напрямую logins.json, либо .dat/.csv).
Скрипт оперирует абстрактными именами параметров, а рантайм и контроллер сценария
управляют распределением непересекающихся блоков строк и синхронизацией столбцов.
Жизненный цикл параметра
VuGen → Controller → Runtime- Инициализация (старт VUser): Контроллер загружает файл датапула 1 раз и при режиме
Uniqueвыделяет боту персональный непересекающийся блок строк:[start_row, end_row). - Подстановка в рантайме: Маркер
{ParamName}подставляется движком в строку запроса непосредственно перед отправкой пакета на сервер. - Смена итерации: В начале каждой новой итерации
Each iterationсдвигает счетчик строки,Onceсохраняет значение, аEach occurrenceсбрасывается в начало. - Исчерпание данных: При выходе за границу блока срабатывает политика
When out of values(Abort Vuser,Cyclic,Last value).
Синхронизация столбцов (Same Line As)
Схема D (Логин + Пароль)
Если параметры UserId, Email и Password берут данные из единого файла logins.json,
настройка same_line_as="UserId" гарантирует, что пароль и почта берутся строго из той же самой записи,
исключая рассинхронизацию связок учетных записей.
| Атрибут | Возможные значения | Описание поведения |
|---|---|---|
select_row |
unique, sequential, random, same_line_as |
Unique — контроллер делит строки на равные блоки между VUsers без пересечений. Sequential — последовательно с 0. Random — случайная строка. Same line as — строка ведущего параметра. |
update_on |
each_iteration, each_occurrence, once |
Each iteration — смена строки на каждой новой итерации action(). Once — выбор 1 раз при старте VUser (идеально для логинов). Each occurrence — смена при каждом обращении к параметру. |
when_out_of_values |
abort, cyclic, last_value |
Abort Vuser — завершение бота с ошибкой при нехватке строк. Cyclic — сброс счетчика в начало блока. Last value — использование последнего значения. |
Типовые схемы применения в сценариях
- Схема A (Уникальный логин на бота):
Select: Unique+Update: Once+Abort— бот логинится один раз вvuser_initи выполняет итерации под своей учетной записью. - Схема B (Новый логин на каждую итерацию):
Select: Unique+Update: Each iteration+Abort— требует запаса данных:Строк ≥ VUsers × Итераций. - Схема C (Общий пул данных):
Select: Random+Update: Each iteration+Cyclic— все боты черпают данные из общего датапула. - Схема D (Синхронная пара):
Passwordссылается наsame_line_as="UserId".
5. Анатомия проекта и сетевой стек GameConnection
Внутреннее устройство репозитория, структура файлов конфигурации и пошаговый цикл работы сокета. Архитектура обеспечивает строгую изоляцию контекстов ботов при общем управлении пулом соединений.
5.1. Полная структура каталогов z:/igg/igg_framework/
5.2. Спецификация датапулов (Параметры и поля)
| Датапул / Файл | Ключевые поля | Тип данных | Назначение |
|---|---|---|---|
data/env_config.json |
game_id, login_server, game_server_port_offset |
Строки, числа, объекты | Глобальные неизменные константы игры. Общие для всех ботов кластера. Смещение портов кластера = 1060. |
data/logins.json |
email, password, group, user_ids |
Массив объектов | Учетные данные для входа. Позволяет связывать аккаунты веб-логина IGG со списками персонажей и группами. |
scenarios/sc_*.py |
ramp_up, duration, pacing, think_time, error_handling |
Словари Python (RTS) | Run-Time Settings сценария. Определяют расписание подачи нагрузки и параметры воспроизведения пауз ботов. |
data/accounts/*_config.json |
PassportToken, AccessKey, LoginSession, NetAddress |
JWT строки, токены, hex | Сессионный кэш персонажей. Хранит актуальные токены сессий и временные метки их действия. |
5.3. Пошаговый жизненный цикл сетевого сокета GameConnection
- DNS & Трансляция адреса: Принимается внутренний адрес кластера (например
10.246.40.6:11004). Функцияresolve_game_server_address()транслирует его во внешний edge-порт:11004 + 1060 = 12064на хосте204.141.172.24. - TCP Connect: Открытие неблокирующего TCP-сокета к порту 12064. Старт потока чтения
_recv_loop. - Авторизация канала (0x279f → 0x27a0): Отправка бинарного фрейма
MsgCL2GSLoginRequestс токеномLoginSessionи аппаратным UDID. Сервер возвращает0x27a0со строкой билда сервера и ErrorCode 0. - Цикл Heartbeat (0x2716): После успешной авторизации активируется фоновый поток, каждые 25 секунд отправляющий пинг
0x2716для удержания канала. - Вход в мир (0x27a1 → 0x27a2): Отправка запроса
MsgCL2GSEnterGameRequest. Сервер подтверждает вход и начинает трансляцию состояния базы и мира через бандлы0x04b3. - Распаковка бандлов 0x04b3: Пакеты с опкодом 0x04b3 распаковываются из ZLIB в последовательность отдельных DlsFrame и маршрутизируются в обработчики.
- Graceful Shutdown: При завершении сценария корректно закрывается сокет (
SHUT_RDWR) и останавливаются потоки.
5.4. Архитектурная модель конкурентности (Trio + Sync Workers)
6. Развертывание, запуск сценариев и инструментарий
6.1. Развертывание на чистой Ubuntu Linux
Фреймворк полностью автономен и не содержит привязок к платформе Windows или бинарным библиотекам игры.
Для выполнения тестов на чистом сервере Ubuntu Linux требуются только Python 3.10+ и легковесные пакеты из requirements.txt:
6.2. Управление сценариями нагрузки (LoadRunner Scenario)
Каждый сценарий нагрузки представляет собой автономный исполняемый файл в каталоге scenarios/.
Все параметры (Ramp-up, Pacing, длительность, группы ботов) конфигурируются прямо в коде сценария:
Режимы расписания нагрузки (Duration & Schedules)
| Тип расписания (type) | Параметры | Жизненный цикл VUser | Типовое применение |
|---|---|---|---|
"script" |
— | vuser_init → N итераций action() из RTS скрипта → vuser_end |
Однократные функциональные тесты, калибровка скриптов, автономная отладка. |
"interval" |
interval_sec (сек), repeats (число или None) |
vuser_init (1 раз) → раунд action() → пауза interval_sec → ... → vuser_end (в конце) |
Периодическая игровая рутина: пожертвования в технологии альянса каждые 5 часов, сбор шахт, забор наград. |
"duration" |
seconds (длительность теста) |
vuser_init (1 раз) → непрерывный цикл action() с Pacing → vuser_end |
Стресс-тестирование, поиск деградации производительности сервера во времени. |
"infinite" |
— | vuser_init (1 раз) → непрерывный цикл action() до Ctrl+C → vuser_end |
Непрерывный мониторинг, долговременная фоновая активность. |
"iterations" |
count (число циклов) |
vuser_init (1 раз) → ровно count итераций action() → vuser_end |
Пакетные тесты фиксированного объёма операций. |
Индивидуальные расписания групп: Каждая группа VUserGroup может иметь свой независимый параметр schedule={...}. Это позволяет в рамках одного массового сценария запускать скрипт технологий раз в 5 часов, а скрипт сбора ресурсов — каждые 30 минут.
Архитектура Session Gateway и Scout Bot (Наблюдатель альянса)
В архитектуре Doomsday GameServer одна учётная запись привязывается к одному TCP-сокету. Параллельное подключение со второго сокета приводит к разрыву сессии с кодом ошибки 1013 (KEcplayerLoginOther).
Для предотвращения коллизий и безопасной работы рядом с реальным игроком реализованы два взаимодополняющих компонента:
- SessionGateway (Шлюз сессий): Удерживает единственный постоянный сокет для каждого
UserId. Все скрипты прозрачно направляют вызовыlr.sendиlr.send_and_waitв потокобезопасную FIFO-очередь команд, исключая гонки и дублирующие подключения. - AllianceScout (Аккаунт-наблюдатель): Выделенный персонаж (по умолчанию
1820818432/visbot21), авторизованный в том же альянсе:- Периодически считывает состав альянса через
0x2752→0x2753(поляIsOnlineиLastLogoutTimeвProtomsg.GuildMemberData). - Перехватывает асинхронные события в реальном времени:
0x2d5e(вход соклановца) и0x2d5f(выход соклановца). - Бесконфликтный возврат (Player Precedence): При входе живого игрока в игру (код 1013) шлюз уступает дорогу и обращается к скауту. Пока скаут видит
IsOnline=True, на сокет игрока не отправляется ни единого байта. Человека гарантированно не выбивает из мобильного клиента. Как только скаут фиксирует оффлайн, шлюз безопасно восстанавливает связь.
- Периодически считывает состав альянса через
6.3. Интерактивный сниффер команд и генератор кода (tools/get_command.py)
Для быстрого создания новых скриптов без ручного поиска опкодов разработан интерактивный сниффер.
Он запускается на машине с игровым клиентом Doomsday.exe, перехватывает клики и генерирует готовый Python-код:
- Запустите игру
Doomsday.exeв обычном режиме. - В консоли запустите сниффер:
python tools/get_command.py. Он автоматически подключится к процессу через Frida. - Поверх экрана откроется компактное окно: «Doomsday Sniffer — Разметка действий».
- Перед кликом в игре введите действие (например: «Нажал кнопку забрать подарки альянса») и нажмите Enter. Комментарий крупно отобразится в консоли и разметит лог-файл.
- Кликните кнопку в игре — сниффер мгновенно декодирует команду, сопоставит её со схемой
opcodes.jsonи выведет готовый блок кода:# Сгенерированный блок (скопируйте в action(self) вашего скрипта): reply = lr.send_and_wait( command="KMsgCl2GsguildGiftClaimAllRequest", Type=1, expected_reply="KMsgGs2ClguildGiftClaimAllReply", timeout=5.0 ) - Все события и сниппеты сохраняются в файлы
logs/captured_commands_*.logиlogs/captured_snippets_*.py.
7. План развития: Модель состояния игрового мира (In-Memory GameState)
● TODO / Future Roadmap
В текущей архитектуре фреймворк функционирует по канонической Stateless-модели LoadRunner VuGen (Request-Reply):
виртуальный пользователь отправляет целевые запросы через lr.send_and_wait(), а входящий поток серверных сообщений вычитывается фоновым потоком _recv_loop для предотвращения переполнения буфера сокета ОС и отбрасывается, если на конкретный опкод не зарегистрировано прямое ожидание.
Однако сразу после завершения рукопожатия авторизации (0x27a1 → 0x27a2) сервер Doomsday выгружает клиенту гигантский пакет данных 0x04b3 (ZLIB-сжатый бандл), который содержит полный исходный слепок всего аккаунта:
уровни и координаты всех построек города (MsgGS2CLCityInfoReply 0x272c), текущие запасы ресурсов и балансы валют (0x2801), объёмы добычи на фермах и вышках с таймерами готовности (0x28f0), очереди строительства (BuildQueueInfo), содержимое инвентаря (0x2851) и список героев (0x2871).
В процессе активной игры сервер также непрерывно передаёт дельта-патчи (MsgGS2CLDataUpdateNotice 0x298d, MsgGS2CLCollectTimeUpdateNotice 0x28f7).
vuser.state, непрерывно актуализируя его на лету при приходе серверных уведомлений.
Это позволит прикладным скриптам мгновенно принимать решения без спама лишними запросами: проверять свободные очереди стройки (lr.is_build_queue_free()), узнавать текущие остатки ресурсов (lr.get_resource('food')) и запрашивать сбор только с тех зданий, где ресурсы действительно накопились.
🔬 Технические детали реализации: архитектура GameState, шина событий и расширение API lr.* (Нажмите для раскрытия)
7.1. Структура данных локального слепка мира (vuser.state: GameState)
У каждого экземпляра VUserScript создаётся потокобезопасный объект состояния с изолированными словарями сущностей:
| Ветка состояния | Тип данных | Серверный опкод-источник | Описание хранимых данных |
|---|---|---|---|
state.resources |
Dict[str, int] |
0x2801 (ResourceNotice), 0x298d |
Текущие остатки: нефть, еда, дерево, сталь, алмазы, очки альянса, монеты арены и экспедиций. |
state.buildings |
Dict[int, BuildingInfo] |
0x272c (CityInfoReply), 0x271d |
Словарь всех построек базы по BuildingId: тип (1004, 1007 и т.д.), текущий уровень, координаты ячейки (x, y), статус стройки/улучшения. |
state.collect |
Dict[int, CollectInfo] |
0x28f0 (CollectInfoReply), 0x28f3, 0x28f7 |
Производственные параметры: тип валюты (CurrencyType), накопленный объём в хранилище здания (Store), скорость в час (Speed), максимальная ёмкость (MaxCollection). |
state.queues |
Dict[str, QueueSlot] |
0x272c (BuildQueueInfo), 0x2724 |
Слоты очередей: строители (Queue 1 и 2), исследования в институте, тренировка войск в казармах с таймерами завершения. |
state.bag |
Dict[int, int] |
0x2851 (BagInfoNotice) |
Инвентарь персонажа: ItemId → Count (ускорители, сундуки ресурсов, щиты, телепорты). |
state.heroes |
Dict[int, HeroInfo] |
0x2871 (HeroListNotice) |
Разблокированные герои: ранг, уровень, распределение очков талантов, экипировка. |
7.2. Шина событий и автоматическая синхронизация (Event-Driven Auto-Sync)
Синхронизация состояния работает полностью автономно в фоновом потоке _recv_loop через регистрацию системных слушателей:
7.3. Предлагаемое расширение пользовательского API lr.*
Скрипт освобождается от ручного парсинга Protobuf и получает готовые методы для чтения локального контекста:
7.4. Потокобезопасность и изоляция VUser
- Изоляция экземпляров: Каждый бот (VUser) хранит собственный независимый экземпляр
GameState. Утечка данных между ботами исключена. - Блокировка чтения/записи (RLock): Обновление состояния из потока
_recv_loopи чтение из синхронного скриптаaction()защищаются внутреннимthreading.RLock()с миллисекундным временем удержания, что исключает блокировку сетевого ввода-вывода.