fix(mnesia): apply dump_log_write_threshold before mnesia start.
CI / test (push) Successful in 16m25s
CI / deploy-ift (push) Successful in 5m49s
CI / e2e-ift (push) Failing after 4m38s
CI / deploy-stage (push) Has been skipped
CI / e2e-stage (push) Has been skipped

change_config rejected the key; set_env + verify enables the IFT ceiling ladder. Refs EventHub/EventHubBack#30 Refs EventHub/EventHubBack#34

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-16 19:22:32 +03:00
parent 6dc06ccdf4
commit 4327de486f
7 changed files with 371 additions and 29 deletions
+4 -1
View File
@@ -16,7 +16,10 @@
]} ]}
]}, ]},
{mnesia, [ {mnesia, [
{dir, "data/Mnesia.eventhub@${NODE_NAME}"} {dir, "data/Mnesia.eventhub@${NODE_NAME}"},
%% Soften dump_log under write load; override via MNESIA_DUMP_LOG_WRITE_THRESHOLD
%% before start (infra_mnesia:configure_dump_log/0). OTP default ~1000.
{dump_log_write_threshold, 50000}
]}, ]},
{ cowboy_swagger, [ { cowboy_swagger, [
{ static_files, "./_build/default/lib/cowboy_swagger/priv/swagger" } { static_files, "./_build/default/lib/cowboy_swagger/priv/swagger" }
+2 -1
View File
@@ -46,8 +46,9 @@ start_application() ->
io:format("~nCluster: discovered nodes ~p, joining cluster~n", [Nodes]), io:format("~nCluster: discovered nodes ~p, joining cluster~n", [Nodes]),
application:set_env(eventhub, extra_db_nodes, Nodes) application:set_env(eventhub, extra_db_nodes, Nodes)
end, end,
application:ensure_all_started(mnesia),
ok = infra_mnesia:configure_dump_log(), ok = infra_mnesia:configure_dump_log(),
application:ensure_all_started(mnesia),
ok = infra_mnesia:verify_dump_log(),
ok = infra_mnesia:init_tables(), ok = infra_mnesia:init_tables(),
ok = infra_mnesia:wait_for_tables(), ok = infra_mnesia:wait_for_tables(),
ok = migration_engine:ensure_applied(), ok = migration_engine:ensure_applied(),
+20 -5
View File
@@ -8,7 +8,7 @@
-export([start_link/0, init_tables/0, wait_for_tables/0, wait_for_table/1]). -export([start_link/0, init_tables/0, wait_for_tables/0, wait_for_table/1]).
-export([add_cluster_nodes/1]). -export([add_cluster_nodes/1]).
-export([configure_dump_log/0]). -export([configure_dump_log/0, verify_dump_log/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]). terminate/2, code_change/3]).
@@ -48,16 +48,31 @@ add_cluster_nodes(Nodes) ->
%% @doc Soften dump_log overload under write-heavy load (IFT stress). %% @doc Soften dump_log overload under write-heavy load (IFT stress).
%% OTP warns {dump_log, write_threshold|time_threshold} when a new dump %% OTP warns {dump_log, write_threshold|time_threshold} when a new dump
%% is requested while the previous dump still runs. %% is requested while the previous dump still runs.
%% Must be set BEFORE mnesia starts — change_config/2 does not accept
%% dump_log_write_threshold (returns {error, dump_log_write_threshold}).
%% Override: MNESIA_DUMP_LOG_WRITE_THRESHOLD (default 50000; OTP default ~1000). %% Override: MNESIA_DUMP_LOG_WRITE_THRESHOLD (default 50000; OTP default ~1000).
-spec configure_dump_log() -> ok. -spec configure_dump_log() -> ok.
configure_dump_log() -> configure_dump_log() ->
Threshold = env_int("MNESIA_DUMP_LOG_WRITE_THRESHOLD", 50000), Threshold = env_int("MNESIA_DUMP_LOG_WRITE_THRESHOLD", 50000),
case mnesia:change_config(dump_log_write_threshold, Threshold) of case application:load(mnesia) of
{ok, Old} -> ok -> ok;
io:format("Mnesia dump_log_write_threshold: ~p -> ~p~n", [Old, Threshold]), {error, {already_loaded, mnesia}} -> ok;
{error, LoadErr} ->
io:format("Mnesia load before dump_log config failed: ~p~n", [LoadErr])
end,
ok = application:set_env(mnesia, dump_log_write_threshold, Threshold),
io:format("Mnesia dump_log_write_threshold set to ~p (before start)~n", [Threshold]),
ok.
%% @doc Log active threshold after mnesia:start (sanity check for IFT logs).
-spec verify_dump_log() -> ok.
verify_dump_log() ->
case catch mnesia:system_info(dump_log_write_threshold) of
N when is_integer(N) ->
io:format("Mnesia dump_log_write_threshold active: ~p~n", [N]),
ok; ok;
Other -> Other ->
io:format("Mnesia dump_log_write_threshold change failed: ~p~n", [Other]), io:format("Mnesia dump_log_write_threshold verify: ~p~n", [Other]),
ok ok
end. end.
+64 -20
View File
@@ -7,7 +7,9 @@
| `eventhub_ift_smoke.xml` | Короткий smoke на IFT после prepare | | `eventhub_ift_smoke.xml` | Короткий smoke на IFT после prepare |
| `eventhub_ift.xml` | Mix: `auth_browse` 60% + `create_flow` 40%, фазы warmup/ramp/peak | | `eventhub_ift.xml` | Mix: `auth_browse` 60% + `create_flow` 40%, фазы warmup/ramp/peak |
| `eventhub_ift_stress.xml` | **Hot-path stress**: browse 70% / write 30%, ladder arrival, поиск верхней границы | | `eventhub_ift_stress.xml` | **Hot-path stress**: browse 70% / write 30%, ladder arrival, поиск верхней границы |
| `eventhub_ift_stress_hold.xml` | Controlled hold: maxusers=300, thinktime=1s, ladder 0.2→0.1→0.05→0.035 | | `eventhub_ift_stress_hold.xml` | Controlled hold: maxusers=100, `use_controller_vm`, ladder 0.2→0.1→0.05→0.035 |
| `eventhub_ift_stress_hold_hot.xml` | Hot ladder только 0.1→0.05→0.035, maxusers=100, `use_controller_vm` (`stress-hold-hot`) |
| `eventhub_ift_stress_hold_beams.xml` | **Опасно:** `use_controller_vm=false` — slave beams раздувают concurrent (не для IFT без `CONFIRM_DANGEROUS=1`) |
| `eventhub_ift_stress_bump.xml` | **Запрещён по умолчанию** (uncapped arrival; клинит WSL IFT) | | `eventhub_ift_stress_bump.xml` | **Запрещён по умолчанию** (uncapped arrival; клинит WSL IFT) |
| `eventhub_tsung.xml` | Legacy localhost (не для IFT) | | `eventhub_tsung.xml` | Legacy localhost (не для IFT) |
| `eventhub_http.xml` | Legacy короткий localhost | | `eventhub_http.xml` | Legacy короткий localhost |
@@ -21,17 +23,28 @@ IFT = Docker Swarm **внутри WSL2 на Windows**. Aggressive open-loop бе
| Правило | Значение | | Правило | Значение |
|---------|----------| |---------|----------|
| Рекомендуемый stress | `eventhub_ift_stress_hold.xml` (`stress-hold`) | | Рекомендуемый stress | `eventhub_ift_stress_hold.xml` (`stress-hold`) |
| `maxusers` | ≤ **300** | | `maxusers` | ≤ **300** (hold default **100** после ×3/2022 — CPU/RAM saturation при 200) |
| `thinktime` | ≥ **1** s на hot-path hold | | `thinktime` | ≥ **1** s на hot-path hold |
| Плановый interarrival | ≥ **0.1** s (~10 VU/s); ниже — только с наблюдением | | Плановый interarrival | ≥ **0.1** s (~10 VU/s); ниже — только с наблюдением |
| Uncapped / bump / maxusers≫300 | **запрещено** на IFT без явного согласования | | Uncapped / bump / maxusers≫300 | **запрещено** на IFT без явного согласования |
| Рабочий потолок (2026-07-15) | ~**10 VU/s** hot-path на текущем железе | | Рабочий потолок (2026-07-15, ×3, с WAF) | ~**10 VU/s** hot-path; **пересчитываем** после Mnesia+loadtest-Traefik |
| Traefik loadtest | prepare подменяет conf **без WAF** (`LOADTEST_TRAEFIK=1`); rate limit 2000/1s остаётся |
| Лестница реплик | потолок сначала **×1**, затем **×2**, **×3** — отдельно (влияние репликации Mnesia) |
**Recover после клина (на Windows-хосте IFT):** **После каждого stress (обязательно — см. EventHubSpec/WORKFLOW.md §7):**
1. Сбор логов: `\\192.168.1.116\eventhub-cursor\scripts\restart-wsl-and-collect-logs.ps1` (или `collect-ift-loadtest-logs.ps1`) 1. Summary: `python3 test/tsung/summarize-tsung-stress.py ~/.tsung/log/<stamp>/tsung.log`
2. `wsl --shutdown` при необходимости 2. Сбор логов IFT → exchange `ift-to-dev/loadtest-<stamp>/`:
3. Restore: `restore-ift.ps1` (Windows OpenSSH) → `ift-loadtest-restore.sh` (retry/`--force` при `update out of sequence`) - SSH ок: `\\192.168.1.116\eventhub-cursor\scripts\collect-ift-loadtest-logs.ps1` (чистит remote dump stamp после download)
- SSH hang: `restart-wsl-and-collect-logs.ps1` или `wsl --shutdown` на HOME-PC → снова collect
3. Разбор: Tsung vs прошлый прогон; архив (429 / Mnesia / archive / OOM); docker-stats;
**метрики бэка:** `GET /metrics` (Prometheus text) + `GET /v1/admin/nodes/metrics?from=&to=` (admin JWT, история в Mnesia);
compose Grafana/Prom — опционально (`KEEP_OBSERVABILITY=1`), при scale 0 mid-run там пусто
4. Очистка leftover на IFT: `cleanup-ift-loadtest-dumps.ps1` / `EventHubDevOps/scripts/cleanup-ift-loadtest-dumps.sh`
5. Отчёт в issue (#30) / строка «Результат» ниже
6. Restore: `restore-ift.ps1``ift-loadtest-restore.sh`
Прогон без п.1–4 не считается завершённым.
### Auth path (smoke / full) ### Auth path (smoke / full)
@@ -60,17 +73,17 @@ IFT = Docker Swarm **внутри WSL2 на Windows**. Aggressive open-loop бе
**Controlled hold ×2** (`20260715-1336/`, `EVENTHUB_REPLICAS=2`): фазы 0.2/0.1 GREEN (mean ≈37 ms); 0.05 OVER мягче (timeout 106, ~3%); overall UNDER_THRESHOLD (err ~0.94%, mean ~64 ms). **Controlled hold ×2** (`20260715-1336/`, `EVENTHUB_REPLICAS=2`): фазы 0.2/0.1 GREEN (mean ≈37 ms); 0.05 OVER мягче (timeout 106, ~3%); overall UNDER_THRESHOLD (err ~0.94%, mean ~64 ms).
**Controlled hold ×3** (`20260715-1411/`, `EVENTHUB_REPLICAS=3`): connect по фазам 0.2→0.035 GREEN (0.05 mean≈40 ms); overall BOUNDARY_HIT из‑за **3772× HTTP 429** узкое место сместилось на Traefik `api-ratelimit` (per source IP, было 5000/1m≈83 rps). После поднятия IFT-лимитов до 2000/1s (EventHubBack#32) перепроверить hold ×3. **Controlled hold ×3** (`20260715-1411/`, `EVENTHUB_REPLICAS=3`, до фиксов): connect по фазам 0.2→0.035 GREEN (0.05 mean≈40 ms); overall BOUNDARY_HIT из‑за **3772× HTTP 429** — Traefik `api-ratelimit` (было 5000/1m≈83 rps).
**Controlled hold ×3 maxusers=100** (`20260715-2102/`, `sha-7fa24e831c16`): **UNDER_THRESHOLD** — 0 errors, mean ~115 ms, late ~331 ms; только фазы 0.2/0.1 (~4 мин); фазы 0.05/0.035 не стартовали после hard-stop `use_controller_vm`. `nodes/metrics`: CPU peak ~60%, mem_available min ~335 MiB (без клина WSL).
**Controlled hold ×3 maxusers=200** (`20260715-2022/`): BOUNDARY_HIT (err 1.74%, timeouts 175); CPU ~100%, mem ~11 MiB; клин SSH.
**Controlled hold ×3 после фиксов** (`20260715-1845/`, maxusers=300): **0× HTTP 4xx/5xx**; mean ≈47 ms; **106× error_timeout**; overall BOUNDARY_HIT (error_rate **1.018%**); зависание ~194 VU + клин WSL.
Под hold также: `Mnesia is overloaded: {dump_log, write_threshold|time_threshold}`. Краткосрочно: `MNESIA_DUMP_LOG_WRITE_THRESHOLD` (IFT default 50000, EventHubBack#34). Долгосрочно: меньше sync-транзакций / hot-path вне Mnesia. Под hold также: `Mnesia is overloaded: {dump_log, write_threshold|time_threshold}`. Краткосрочно: `MNESIA_DUMP_LOG_WRITE_THRESHOLD` (IFT default 50000, EventHubBack#34). Долгосрочно: меньше sync-транзакций / hot-path вне Mnesia.
Restore fallback: оркестратор → WSL SSH, при fail Windows `restore-ift.ps1`. Restore fallback: оркестратор → WSL SSH, при fail Windows `restore-ift.ps1`. Restore **после** collect+анализа (см. gate выше); если SSH мёртв — `wsl --shutdown` → collect → restore.
Если после stress SSH «висит на banner» — **reboot IFT**, затем:
```bash
bash /opt/eventhub-ift/devops/scripts/ift-loadtest-restore.sh
```
## Подготовка учётки admin ## Подготовка учётки admin
@@ -113,20 +126,51 @@ tsung -f test/tsung/eventhub_ift_smoke.xml start
tsung -f test/tsung/eventhub_ift.xml start tsung -f test/tsung/eventhub_ift.xml start
tsung -f test/tsung/eventhub_ift_stress.xml start tsung -f test/tsung/eventhub_ift_stress.xml start
# оркестратор (prepare + seed при необходимости + restore): # оркестратор (prepare + seed при необходимости + restore):
bash ~/.cursor/eventhub/run-ift-loadtest.sh smoke|full|stress|stress-bump|stress-hold bash ~/.cursor/eventhub/run-ift-loadtest.sh smoke|full|stress|stress-bump|stress-hold|stress-hold-hot|stress-hold-beams
# replicas: EVENTHUB_REPLICAS=2|3 bash ~/.cursor/eventhub/run-ift-loadtest.sh stress-hold # replicas: EVENTHUB_REPLICAS=1|2|3 bash ~/.cursor/eventhub/run-ift-loadtest.sh stress-hold
# фазы 0.05/0.035: stress-hold-hot (без slave beams); stress-hold-beams — только с CONFIRM_DANGEROUS=1
# Traefik без WAF в prepare: LOADTEST_TRAEFIK=1 (default); штатный WAF: LOADTEST_TRAEFIK=0
# restore fallback: если WSL SSH не отвечает — Windows OpenSSH # restore fallback: если WSL SSH не отвечает — Windows OpenSSH
# powershell -File $env:USERPROFILE\.cursor\eventhub\restore-ift.ps1 # powershell -File $env:USERPROFILE\.cursor\eventhub\restore-ift.ps1
``` ```
### Пересчёт потолка (лестница реплик)
Цель — отделить raw capacity одной ноды от стоимости репликации Mnesia.
1. Задеплоить образ с рабочим `dump_log_write_threshold` (до `mnesia:start`).
2. Для каждой ступени **×1 → ×2 → ×3** (отдельно, с полным gate после каждого прогона):
- `EVENTHUB_REPLICAS=N bash ~/.cursor/eventhub/run-ift-loadtest.sh stress-hold` (зелёный baseline)
- при UNDER на 0.2/0.1 — `stress-hold-hot` (0.1→0.05→0.035)
3. В отчёте #30: replicas, verdict по фазам, CPU/mem, Mnesia dump_log, образ `sha-*`.
4. Не смешивать ступени в одном прогоне; не использовать beams без `CONFIRM_DANGEROUS=1`.
Отчёт: `~/.tsung/log/<timestamp>/`. Для stress — также `~/.tsung/ift-docker-stats-*.log`. Отчёт: `~/.tsung/log/<timestamp>/`. Для stress — также `~/.tsung/ift-docker-stats-*.log`.
## После прогона ## После прогона (обязательный gate)
```bash ```powershell
bash /opt/eventhub-ift/devops/scripts/ift-loadtest-restore.sh # 1) Tsung 2A (с дев WSL)
python3 test/tsung/summarize-tsung-stress.py ~/.tsung/log/<stamp>/tsung.log
# 2) Логи стенда на шару (+ auto-cleanup remote dump этого stamp)
powershell -NoProfile -File \\192.168.1.116\eventhub-cursor\scripts\collect-ift-loadtest-logs.ps1
# 3) Разбор архива + метрики бэка:
# curl -sk https://api.ift.eventhub.local/metrics
# GET /v1/admin/nodes/metrics?from=&to= (admin JWT) — история узлов
# 4) Leftover dumps на IFT (все stamps)
powershell -NoProfile -File $env:USERPROFILE\.cursor\eventhub\cleanup-ift-loadtest-dumps.ps1
# 5) Restore
powershell -NoProfile -File $env:USERPROFILE\.cursor\eventhub\restore-ift.ps1
``` ```
Prepare с внешним Prometheus/Grafana (не путать с `/metrics` приложения):
```bash
KEEP_OBSERVABILITY=1 EVENTHUB_REPLICAS=3 bash /opt/eventhub-ift/devops/scripts/ift-loadtest-prepare.sh
```
## Настройка интенсивности ## Настройка интенсивности
В `eventhub_ift.xml` / `eventhub_ift_stress*.xml` блок `<load>`: `interarrival` и `duration` фаз. В `eventhub_ift.xml` / `eventhub_ift_stress*.xml` блок `<load>`: `interarrival` и `duration` фаз.
+2 -2
View File
@@ -3,13 +3,13 @@
<!-- <!--
IFT controlled hold: поиск устойчивого (зелёного) потолка hot-path. IFT controlled hold: поиск устойчивого (зелёного) потолка hot-path.
Пул: ift-users.csv. Mix browse 70% / write 30%. Пул: ift-users.csv. Mix browse 70% / write 30%.
maxusers=300; ladder 0.2→0.1→0.05→0.035; hold ≥2 мин. maxusers=100 (use_controller_vm); ladder 0.2→0.1→0.05→0.035; hold ≥2 мин.
thinktime value=1 (целые секунды) — темп без connect-storm. thinktime value=1 (целые секунды) — темп без connect-storm.
Критерий 2A: error >1% ИЛИ mean >500 ms. Критерий 2A: error >1% ИЛИ mean >500 ms.
--> -->
<tsung loglevel="notice" version="1.0"> <tsung loglevel="notice" version="1.0">
<clients> <clients>
<client host="localhost" use_controller_vm="true" maxusers="300"/> <client host="localhost" use_controller_vm="true" maxusers="100"/>
</clients> </clients>
<servers> <servers>
@@ -0,0 +1,142 @@
<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/usr/share/tsung/tsung-1.0.dtd">
<!--
IFT controlled hold + slave beams: дойти до фаз 0.05/0.035.
use_controller_vm=false — новые beam при заполнении maxusers (не hard-stop).
Пул: ift-users.csv. Mix browse 70% / write 30%.
maxusers=100; ladder 0.2→0.1→0.05→0.035; thinktime=1s.
Критерий 2A: error >1% ИЛИ mean >500 ms.
Риск: выше нагрузка на дев-генератор; не uncapped bump.
-->
<tsung loglevel="notice" version="1.0">
<clients>
<client host="localhost" use_controller_vm="false" maxusers="100" cpu="2"/>
</clients>
<servers>
<server host="api.ift.eventhub.local" port="443" type="ssl"/>
</servers>
<load>
<arrivalphase phase="1" duration="2" unit="minute">
<users interarrival="0.2" unit="second"/>
</arrivalphase>
<arrivalphase phase="2" duration="2" unit="minute">
<users interarrival="0.1" unit="second"/>
</arrivalphase>
<arrivalphase phase="3" duration="3" unit="minute">
<users interarrival="0.05" unit="second"/>
</arrivalphase>
<arrivalphase phase="4" duration="3" unit="minute">
<users interarrival="0.035" unit="second"/>
</arrivalphase>
</load>
<options>
<option name="ssl_reuse" value="true"/>
<option name="file_server" id="usercred" value="test/tsung/ift-users.csv"/>
</options>
<sessions>
<session name="browse" probability="70" type="ts_http">
<setdynvars sourcetype="file" fileid="usercred" delimiter=";" order="iter">
<var name="user_email"/>
<var name="user_password"/>
</setdynvars>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<dyn_variable name="token" re="&quot;token&quot;\s*:\s*&quot;([^&quot;]+)"/>
<http url="https://api.ift.eventhub.local/v1/login" method="POST" version="1.1"
content_type="application/json"
contents='{"email":"%%_user_email%%","password":"%%_user_password%%"}'/>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/user/me" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/calendars" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="log" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/search?q=lt" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="log" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/user/bookings" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
</session>
<session name="write" probability="30" type="ts_http">
<setdynvars sourcetype="file" fileid="usercred" delimiter=";" order="iter">
<var name="user_email"/>
<var name="user_password"/>
</setdynvars>
<setdynvars sourcetype="random_number" start="100000" end="9999999">
<var name="rand_id"/>
</setdynvars>
<setdynvars sourcetype="random_number" start="10" end="28">
<var name="day"/>
</setdynvars>
<setdynvars sourcetype="random_number" start="10" end="59">
<var name="sec"/>
</setdynvars>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<dyn_variable name="token" re="&quot;token&quot;\s*:\s*&quot;([^&quot;]+)"/>
<http url="https://api.ift.eventhub.local/v1/login" method="POST" version="1.1"
content_type="application/json"
contents='{"email":"%%_user_email%%","password":"%%_user_password%%"}'/>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<dyn_variable name="calendar_id" re="&quot;id&quot;\s*:\s*&quot;([^&quot;]+)"/>
<http url="https://api.ift.eventhub.local/v1/calendars" method="POST" version="1.1"
content_type="application/json"
contents='{"title":"Stress Cal %%_rand_id%%","confirmation":"auto"}'>
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<dyn_variable name="event_id" re="&quot;id&quot;\s*:\s*&quot;([^&quot;]+)"/>
<http url="https://api.ift.eventhub.local/v1/calendars/%%_calendar_id%%/events" method="POST"
version="1.1" content_type="application/json"
contents='{"title":"Stress Ev %%_rand_id%%","start_time":"2032-08-%%_day%%T14:00:%%_sec%%Z","duration":30,"capacity":20}'>
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/events/%%_event_id%%" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="log" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/calendars" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
</session>
</sessions>
</tsung>
+137
View File
@@ -0,0 +1,137 @@
<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/usr/share/tsung/tsung-1.0.dtd">
<!--
IFT hold HOT: только фазы 0.1→0.05→0.035 (без 0.2 warmup).
use_controller_vm=true, maxusers=100 — без slave beams.
Цель: промерить OVER ladder без hard-stop до фаз 3/4.
Критерий 2A: error >1% ИЛИ mean >500 ms.
-->
<tsung loglevel="notice" version="1.0">
<clients>
<client host="localhost" use_controller_vm="true" maxusers="100"/>
</clients>
<servers>
<server host="api.ift.eventhub.local" port="443" type="ssl"/>
</servers>
<load>
<arrivalphase phase="1" duration="2" unit="minute">
<users interarrival="0.1" unit="second"/>
</arrivalphase>
<arrivalphase phase="2" duration="3" unit="minute">
<users interarrival="0.05" unit="second"/>
</arrivalphase>
<arrivalphase phase="3" duration="3" unit="minute">
<users interarrival="0.035" unit="second"/>
</arrivalphase>
</load>
<options>
<option name="ssl_reuse" value="true"/>
<option name="file_server" id="usercred" value="test/tsung/ift-users.csv"/>
</options>
<sessions>
<session name="browse" probability="70" type="ts_http">
<setdynvars sourcetype="file" fileid="usercred" delimiter=";" order="iter">
<var name="user_email"/>
<var name="user_password"/>
</setdynvars>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<dyn_variable name="token" re="&quot;token&quot;\s*:\s*&quot;([^&quot;]+)"/>
<http url="https://api.ift.eventhub.local/v1/login" method="POST" version="1.1"
content_type="application/json"
contents='{"email":"%%_user_email%%","password":"%%_user_password%%"}'/>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/user/me" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/calendars" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="log" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/search?q=lt" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="log" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/user/bookings" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
</session>
<session name="write" probability="30" type="ts_http">
<setdynvars sourcetype="file" fileid="usercred" delimiter=";" order="iter">
<var name="user_email"/>
<var name="user_password"/>
</setdynvars>
<setdynvars sourcetype="random_number" start="100000" end="9999999">
<var name="rand_id"/>
</setdynvars>
<setdynvars sourcetype="random_number" start="10" end="28">
<var name="day"/>
</setdynvars>
<setdynvars sourcetype="random_number" start="10" end="59">
<var name="sec"/>
</setdynvars>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<dyn_variable name="token" re="&quot;token&quot;\s*:\s*&quot;([^&quot;]+)"/>
<http url="https://api.ift.eventhub.local/v1/login" method="POST" version="1.1"
content_type="application/json"
contents='{"email":"%%_user_email%%","password":"%%_user_password%%"}'/>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<dyn_variable name="calendar_id" re="&quot;id&quot;\s*:\s*&quot;([^&quot;]+)"/>
<http url="https://api.ift.eventhub.local/v1/calendars" method="POST" version="1.1"
content_type="application/json"
contents='{"title":"Stress Cal %%_rand_id%%","confirmation":"auto"}'>
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<dyn_variable name="event_id" re="&quot;id&quot;\s*:\s*&quot;([^&quot;]+)"/>
<http url="https://api.ift.eventhub.local/v1/calendars/%%_calendar_id%%/events" method="POST"
version="1.1" content_type="application/json"
contents='{"title":"Stress Ev %%_rand_id%%","start_time":"2032-08-%%_day%%T14:00:%%_sec%%Z","duration":30,"capacity":20}'>
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="abort" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/events/%%_event_id%%" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
<thinktime value="1"/>
<request subst="true">
<match do="log" when="nomatch">HTTP/[0-9.]+ 20</match>
<http url="https://api.ift.eventhub.local/v1/calendars" method="GET" version="1.1">
<http_header name="Authorization" value="Bearer %%_token%%"/>
</http>
</request>
</session>
</sessions>
</tsung>