-module(lightspeed@ops@load_harness).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/ops/load_harness.gleam").
-export([default_scenario/0, run/1, summary_signature/1, run_fixture_scenario/1, run_fixture_matrix/0, fixture_scenario_label/1, fixture_pass_fail_label/1, fixture_signature/1, fixture_deterministic/1, fixture_scenario/1, fixture_outcomes/1, failed_scenarios/1, nondeterministic_failures/1, fixture_report_signature/1, snapshot_signature/0, snapshot_report_markdown/0]).
-export_type([scenario/0, session_outcome/0, summary/0, fixture_scenario/0, fixture_outcome/0, fixture_report/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(" Deterministic load-testing harness for session behavior.\n").
-type scenario() :: {scenario,
integer(),
integer(),
boolean(),
boolean(),
integer()}.
-type session_outcome() :: {session_outcome,
binary(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer()}.
-type summary() :: {summary,
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
list(session_outcome())}.
-type fixture_scenario() :: baseline_load_profile |
slow_ack_backlog_profile |
clean_ack_profile.
-type fixture_outcome() :: {fixture_outcome,
fixture_scenario(),
boolean(),
boolean(),
binary()}.
-type fixture_report() :: {fixture_report,
list(fixture_outcome()),
integer(),
integer()}.
-file("src/lightspeed/ops/load_harness.gleam", 74).
?DOC(" Default scenario tuned for quick local checks.\n").
-spec default_scenario() -> scenario().
default_scenario() ->
{scenario, 16, 32, true, true, 2}.
-file("src/lightspeed/ops/load_harness.gleam", 405).
-spec fold_outcomes(
list(session_outcome()),
fun((session_outcome(), integer()) -> integer()),
integer()
) -> integer().
fold_outcomes(Outcomes, Reducer, Total) ->
case Outcomes of
[] ->
Total;
[Outcome | Rest] ->
fold_outcomes(Rest, Reducer, Reducer(Outcome, Total))
end.
-file("src/lightspeed/ops/load_harness.gleam", 424).
-spec clamp_positive(integer(), integer()) -> integer().
clamp_positive(Value, Fallback) ->
case Value =< 0 of
true ->
Fallback;
false ->
Value
end.
-file("src/lightspeed/ops/load_harness.gleam", 376).
-spec summarize(list(session_outcome()), scenario()) -> summary().
summarize(Outcomes, Scenario) ->
{summary,
erlang:length(Outcomes),
clamp_positive(erlang:element(2, Scenario), 1) * clamp_positive(
erlang:element(3, Scenario),
0
),
fold_outcomes(
Outcomes,
fun(Outcome, Total) -> Total + erlang:element(6, Outcome) end,
0
),
fold_outcomes(
Outcomes,
fun(Outcome@1, Total@1) ->
Total@1 + erlang:element(7, Outcome@1)
end,
0
),
fold_outcomes(
Outcomes,
fun(Outcome@2, Total@2) ->
Total@2 + erlang:element(8, Outcome@2)
end,
0
),
fold_outcomes(
Outcomes,
fun(Outcome@3, Total@3) ->
Total@3 + erlang:element(9, Outcome@3)
end,
0
),
Outcomes}.
-file("src/lightspeed/ops/load_harness.gleam", 358).
-spec build_outcome(lightspeed@agent@session:session()) -> session_outcome().
build_outcome(State) ->
Telemetry_summary = lightspeed@agent@session:telemetry_summary(State),
Reconnects = lightspeed@agent@session:summary_session_rehydrated(
Telemetry_summary
)
+ lightspeed@agent@session:summary_session_remounted(Telemetry_summary),
{session_outcome,
lightspeed@agent@session:id(State),
lightspeed@agent@session:counter(State),
lightspeed@agent@session:summary_total_events(Telemetry_summary),
lightspeed@agent@session:pending_patch_count(State),
lightspeed@agent@session:summary_patch_queued(Telemetry_summary),
lightspeed@agent@session:summary_patch_acked(Telemetry_summary),
lightspeed@agent@session:summary_session_crashed(Telemetry_summary),
Reconnects}.
-file("src/lightspeed/ops/load_harness.gleam", 416).
-spec send(
lightspeed@agent@session:session(),
binary(),
lightspeed@agent@session:inbox_event()
) -> lightspeed@agent@session:session().
send(State, Owner, Event) ->
lightspeed@agent@session:handle(State, {inbox_message, Owner, Event}).
-file("src/lightspeed/ops/load_harness.gleam", 345).
-spec maybe_reconnect(
lightspeed@agent@session:session(),
binary(),
binary(),
boolean(),
integer()
) -> lightspeed@agent@session:session().
maybe_reconnect(State, Owner, Route, Include_reconnect, Now_ms) ->
case Include_reconnect of
true ->
send(State, Owner, {reconnect, Route, Now_ms});
false ->
State
end.
-file("src/lightspeed/ops/load_harness.gleam", 330).
-spec maybe_crash_and_restart(
lightspeed@agent@session:session(),
binary(),
boolean(),
integer()
) -> lightspeed@agent@session:session().
maybe_crash_and_restart(State, Owner, Include_crash_restart, Now_ms) ->
case Include_crash_restart of
true ->
_pipe = State,
_pipe@1 = send(_pipe, Owner, {crash, <<"load_harness"/utf8>>}),
send(_pipe@1, Owner, {restart, Now_ms});
false ->
State
end.
-file("src/lightspeed/ops/load_harness.gleam", 320).
-spec should_ack(integer(), integer()) -> boolean().
should_ack(Step, Ack_every) ->
case Ack_every =< 1 of
true ->
true;
false ->
Remainder = case Ack_every of
0 -> 0;
Gleam@denominator -> Step rem Gleam@denominator
end,
Remainder =:= 0
end.
-file("src/lightspeed/ops/load_harness.gleam", 307).
-spec maybe_ack(
lightspeed@agent@session:session(),
binary(),
binary(),
integer(),
integer()
) -> lightspeed@agent@session:session().
maybe_ack(State, Owner, Patch_ref, Step, Ack_every) ->
case should_ack(Step, Ack_every) of
true ->
send(State, Owner, {ack, Patch_ref});
false ->
State
end.
-file("src/lightspeed/ops/load_harness.gleam", 289).
-spec apply_increments(
lightspeed@agent@session:session(),
binary(),
integer(),
integer(),
integer()
) -> lightspeed@agent@session:session().
apply_increments(State, Owner, Remaining, Ack_every, Step) ->
case Remaining =< 0 of
true ->
State;
false ->
Next = send(State, Owner, increment),
Patch_ref = erlang:integer_to_binary(Step + 1),
Next@1 = maybe_ack(Next, Owner, Patch_ref, Step, Ack_every),
apply_increments(Next@1, Owner, Remaining - 1, Ack_every, Step + 1)
end.
-file("src/lightspeed/ops/load_harness.gleam", 260).
-spec run_one(integer(), scenario()) -> session_outcome().
run_one(Index, Scenario) ->
Session_id = <<"load-"/utf8, (erlang:integer_to_binary(Index))/binary>>,
Owner = <<"proc-"/utf8, (erlang:integer_to_binary(Index))/binary>>,
Route = <<"/counter/"/utf8, (erlang:integer_to_binary(Index))/binary>>,
Increments = clamp_positive(erlang:element(3, Scenario), 0),
Ack_every = clamp_positive(erlang:element(6, Scenario), 1),
Started = lightspeed@agent@session:start(
Session_id,
Owner,
rehydrate,
0,
100
),
Connected = send(
Started,
Owner,
{connect, Route, <<"csrf-"/utf8, Session_id/binary>>, 0}
),
Connected@1 = maybe_ack(Connected, Owner, <<"1"/utf8>>, 0, Ack_every),
Updated = apply_increments(Connected@1, Owner, Increments, Ack_every, 1),
Updated@1 = maybe_crash_and_restart(
Updated,
Owner,
erlang:element(5, Scenario),
500
),
Updated@2 = maybe_reconnect(
Updated@1,
Owner,
Route,
erlang:element(4, Scenario),
700
),
build_outcome(Updated@2).
-file("src/lightspeed/ops/load_harness.gleam", 244).
-spec run_sessions(integer(), integer(), scenario(), list(session_outcome())) -> list(session_outcome()).
run_sessions(Index, Max_index, Scenario, Outcomes_rev) ->
case Index > Max_index of
true ->
lists:reverse(Outcomes_rev);
false ->
run_sessions(
Index + 1,
Max_index,
Scenario,
[run_one(Index, Scenario) | Outcomes_rev]
)
end.
-file("src/lightspeed/ops/load_harness.gleam", 85).
?DOC(" Run a deterministic load scenario.\n").
-spec run(scenario()) -> summary().
run(Scenario) ->
Count = clamp_positive(erlang:element(2, Scenario), 1),
Outcomes = run_sessions(1, Count, Scenario, []),
summarize(Outcomes, Scenario).
-file("src/lightspeed/ops/load_harness.gleam", 540).
-spec count_nondeterministic_fixture_scenarios(list(fixture_outcome())) -> integer().
count_nondeterministic_fixture_scenarios(Outcomes) ->
case Outcomes of
[] ->
0;
[Outcome | Rest] ->
case erlang:element(4, Outcome) of
true ->
count_nondeterministic_fixture_scenarios(Rest);
false ->
1 + count_nondeterministic_fixture_scenarios(Rest)
end
end.
-file("src/lightspeed/ops/load_harness.gleam", 529).
-spec count_failed_fixture_scenarios(list(fixture_outcome())) -> integer().
count_failed_fixture_scenarios(Outcomes) ->
case Outcomes of
[] ->
0;
[Outcome | Rest] ->
case erlang:element(3, Outcome) of
true ->
count_failed_fixture_scenarios(Rest);
false ->
1 + count_failed_fixture_scenarios(Rest)
end
end.
-file("src/lightspeed/ops/load_harness.gleam", 585).
-spec session_outcome_signature(session_outcome()) -> binary().
session_outcome_signature(Outcome) ->
<<<<<<<<<<<<<<<<<<<<<<<<<<<<(erlang:element(2, Outcome))/binary, ":"/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(
3,
Outcome
)
))/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(4, Outcome)
))/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(5, Outcome)
))/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(6, Outcome)
))/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(7, Outcome)))/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(8, Outcome)))/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(9, Outcome)))/binary>>.
-file("src/lightspeed/ops/load_harness.gleam", 610).
-spec join_with(binary(), list(binary())) -> binary().
join_with(Separator, Values) ->
case Values of
[] ->
<<""/utf8>>;
[Value] ->
Value;
[Value@1 | Rest] ->
<<<<Value@1/binary, Separator/binary>>/binary,
(join_with(Separator, Rest))/binary>>
end.
-file("src/lightspeed/ops/load_harness.gleam", 578).
-spec sum_pending(list(session_outcome())) -> integer().
sum_pending(Outcomes) ->
case Outcomes of
[] ->
0;
[Outcome | Rest] ->
erlang:element(5, Outcome) + sum_pending(Rest)
end.
-file("src/lightspeed/ops/load_harness.gleam", 225).
?DOC(" Stable summary signature for one scenario run.\n").
-spec summary_signature(summary()) -> binary().
summary_signature(Summary) ->
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"sessions="/utf8,
(erlang:integer_to_binary(
erlang:element(
2,
Summary
)
))/binary>>/binary,
"|increments="/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(
3,
Summary
)
))/binary>>/binary,
"|patch_queued="/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(4, Summary)
))/binary>>/binary,
"|patch_acked="/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(5, Summary)
))/binary>>/binary,
"|crashes="/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(6, Summary)
))/binary>>/binary,
"|reconnects="/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(7, Summary)))/binary>>/binary,
"|pending_total="/utf8>>/binary,
(erlang:integer_to_binary(
sum_pending(erlang:element(8, Summary))
))/binary>>/binary,
"|outcomes="/utf8>>/binary,
(join_with(
<<","/utf8>>,
gleam@list:map(
erlang:element(8, Summary),
fun session_outcome_signature/1
)
))/binary>>.
-file("src/lightspeed/ops/load_harness.gleam", 564).
-spec max_pending(list(session_outcome()), integer()) -> integer().
max_pending(Outcomes, Current) ->
case Outcomes of
[] ->
Current;
[Outcome | Rest] ->
Next = case erlang:element(5, Outcome) > Current of
true ->
erlang:element(5, Outcome);
false ->
Current
end,
max_pending(Rest, Next)
end.
-file("src/lightspeed/ops/load_harness.gleam", 553).
-spec count_pending_sessions(list(session_outcome())) -> integer().
count_pending_sessions(Outcomes) ->
case Outcomes of
[] ->
0;
[Outcome | Rest] ->
case erlang:element(5, Outcome) > 0 of
true ->
1 + count_pending_sessions(Rest);
false ->
count_pending_sessions(Rest)
end
end.
-file("src/lightspeed/ops/load_harness.gleam", 498).
-spec evaluate_clean_ack_profile() -> {boolean(), binary()}.
evaluate_clean_ack_profile() ->
Summary = run({scenario, 10, 20, false, false, 1}),
Pending_sessions = count_pending_sessions(erlang:element(8, Summary)),
Max_pending = max_pending(erlang:element(8, Summary), 0),
Passed = ((((((erlang:element(2, Summary) =:= 10) andalso (erlang:element(
3,
Summary
)
=:= 200))
andalso (erlang:element(6, Summary) =:= 0))
andalso (erlang:element(7, Summary) =:= 0))
andalso (erlang:element(4, Summary) =:= erlang:element(5, Summary)))
andalso (Pending_sessions =:= 0))
andalso (Max_pending =:= 0),
{Passed,
<<<<<<<<(summary_signature(Summary))/binary, "|pending_sessions="/utf8>>/binary,
(erlang:integer_to_binary(Pending_sessions))/binary>>/binary,
"|max_pending="/utf8>>/binary,
(erlang:integer_to_binary(Max_pending))/binary>>}.
-file("src/lightspeed/ops/load_harness.gleam", 464).
-spec evaluate_slow_ack_backlog_profile() -> {boolean(), binary()}.
evaluate_slow_ack_backlog_profile() ->
Summary = run({scenario, 12, 24, true, true, 10}),
Pending_sessions = count_pending_sessions(erlang:element(8, Summary)),
Max_pending = max_pending(erlang:element(8, Summary), 0),
Ack_gap = erlang:element(4, Summary) - erlang:element(5, Summary),
Passed = ((((((erlang:element(2, Summary) =:= 12) andalso (erlang:element(
3,
Summary
)
=:= 288))
andalso (erlang:element(6, Summary) =:= 12))
andalso (erlang:element(7, Summary) =:= 12))
andalso (Ack_gap > 0))
andalso (Pending_sessions > 0))
andalso (Max_pending > 0),
{Passed,
<<<<<<<<<<<<(summary_signature(Summary))/binary, "|ack_gap="/utf8>>/binary,
(erlang:integer_to_binary(Ack_gap))/binary>>/binary,
"|pending_sessions="/utf8>>/binary,
(erlang:integer_to_binary(Pending_sessions))/binary>>/binary,
"|max_pending="/utf8>>/binary,
(erlang:integer_to_binary(Max_pending))/binary>>}.
-file("src/lightspeed/ops/load_harness.gleam", 439).
-spec evaluate_baseline_load_profile() -> {boolean(), binary()}.
evaluate_baseline_load_profile() ->
Summary = run(default_scenario()),
Pending_sessions = count_pending_sessions(erlang:element(8, Summary)),
Max_pending = max_pending(erlang:element(8, Summary), 0),
Passed = (((((((erlang:element(2, Summary) =:= 16) andalso (erlang:element(
3,
Summary
)
=:= 512))
andalso (erlang:element(6, Summary) =:= 16))
andalso (erlang:element(7, Summary) =:= 16))
andalso (erlang:element(4, Summary) >= erlang:element(5, Summary)))
andalso (erlang:element(5, Summary) > 0))
andalso (Pending_sessions >= 0))
andalso (Max_pending >= 0),
{Passed,
<<<<<<<<(summary_signature(Summary))/binary, "|pending_sessions="/utf8>>/binary,
(erlang:integer_to_binary(Pending_sessions))/binary>>/binary,
"|max_pending="/utf8>>/binary,
(erlang:integer_to_binary(Max_pending))/binary>>}.
-file("src/lightspeed/ops/load_harness.gleam", 431).
-spec evaluate_fixture_scenario(fixture_scenario()) -> {boolean(), binary()}.
evaluate_fixture_scenario(Scenario) ->
case Scenario of
baseline_load_profile ->
evaluate_baseline_load_profile();
slow_ack_backlog_profile ->
evaluate_slow_ack_backlog_profile();
clean_ack_profile ->
evaluate_clean_ack_profile()
end.
-file("src/lightspeed/ops/load_harness.gleam", 107).
?DOC(" Run one M34 fixture scenario twice and require deterministic parity.\n").
-spec run_fixture_scenario(fixture_scenario()) -> fixture_outcome().
run_fixture_scenario(Scenario) ->
{First_passed, First_signature} = evaluate_fixture_scenario(Scenario),
{Second_passed, Second_signature} = evaluate_fixture_scenario(Scenario),
Deterministic = (First_passed =:= Second_passed) andalso (First_signature
=:= Second_signature),
Passed = (First_passed andalso Second_passed) andalso Deterministic,
{fixture_outcome, Scenario, Passed, Deterministic, First_signature}.
-file("src/lightspeed/ops/load_harness.gleam", 92).
?DOC(" Run the deterministic M34 fixture matrix.\n").
-spec run_fixture_matrix() -> fixture_report().
run_fixture_matrix() ->
Outcomes = begin
_pipe = [baseline_load_profile,
slow_ack_backlog_profile,
clean_ack_profile],
gleam@list:map(_pipe, fun run_fixture_scenario/1)
end,
{fixture_report,
Outcomes,
count_failed_fixture_scenarios(Outcomes),
count_nondeterministic_fixture_scenarios(Outcomes)}.
-file("src/lightspeed/ops/load_harness.gleam", 123).
?DOC(" Scenario label.\n").
-spec fixture_scenario_label(fixture_scenario()) -> binary().
fixture_scenario_label(Scenario) ->
case Scenario of
baseline_load_profile ->
<<"baseline_load_profile"/utf8>>;
slow_ack_backlog_profile ->
<<"slow_ack_backlog_profile"/utf8>>;
clean_ack_profile ->
<<"clean_ack_profile"/utf8>>
end.
-file("src/lightspeed/ops/load_harness.gleam", 132).
?DOC(" Stable pass/fail label.\n").
-spec fixture_pass_fail_label(fixture_outcome()) -> binary().
fixture_pass_fail_label(Outcome) ->
case erlang:element(3, Outcome) of
true ->
<<"pass"/utf8>>;
false ->
<<"fail"/utf8>>
end.
-file("src/lightspeed/ops/load_harness.gleam", 140).
?DOC(" Outcome signature.\n").
-spec fixture_signature(fixture_outcome()) -> binary().
fixture_signature(Outcome) ->
erlang:element(5, Outcome).
-file("src/lightspeed/ops/load_harness.gleam", 145).
?DOC(" Determinism accessor.\n").
-spec fixture_deterministic(fixture_outcome()) -> boolean().
fixture_deterministic(Outcome) ->
erlang:element(4, Outcome).
-file("src/lightspeed/ops/load_harness.gleam", 150).
?DOC(" Scenario accessor.\n").
-spec fixture_scenario(fixture_outcome()) -> fixture_scenario().
fixture_scenario(Outcome) ->
erlang:element(2, Outcome).
-file("src/lightspeed/ops/load_harness.gleam", 155).
?DOC(" Report outcomes.\n").
-spec fixture_outcomes(fixture_report()) -> list(fixture_outcome()).
fixture_outcomes(Report) ->
erlang:element(2, Report).
-file("src/lightspeed/ops/load_harness.gleam", 160).
?DOC(" Failed scenario count.\n").
-spec failed_scenarios(fixture_report()) -> integer().
failed_scenarios(Report) ->
erlang:element(3, Report).
-file("src/lightspeed/ops/load_harness.gleam", 165).
?DOC(" Nondeterministic scenario count.\n").
-spec nondeterministic_failures(fixture_report()) -> integer().
nondeterministic_failures(Report) ->
erlang:element(4, Report).
-file("src/lightspeed/ops/load_harness.gleam", 603).
-spec bool_label(boolean()) -> binary().
bool_label(Value) ->
case Value of
true ->
<<"true"/utf8>>;
false ->
<<"false"/utf8>>
end.
-file("src/lightspeed/ops/load_harness.gleam", 170).
?DOC(" Stable fixture report signature.\n").
-spec fixture_report_signature(fixture_report()) -> binary().
fixture_report_signature(Report) ->
Entries = gleam@list:map(
erlang:element(2, Report),
fun(Outcome) ->
<<<<<<<<<<<<(fixture_scenario_label(erlang:element(2, Outcome)))/binary,
"="/utf8>>/binary,
(fixture_pass_fail_label(Outcome))/binary>>/binary,
":deterministic="/utf8>>/binary,
(bool_label(erlang:element(4, Outcome)))/binary>>/binary,
":"/utf8>>/binary,
(erlang:element(5, Outcome))/binary>>
end
),
join_with(<<";"/utf8>>, Entries).
-file("src/lightspeed/ops/load_harness.gleam", 186).
?DOC(" Deterministic snapshot signature for M34 drift gates.\n").
-spec snapshot_signature() -> binary().
snapshot_signature() ->
<<<<<<"m34.snapshot.v"/utf8, (erlang:integer_to_binary(1))/binary>>/binary,
"|"/utf8>>/binary,
(fixture_report_signature(run_fixture_matrix()))/binary>>.
-file("src/lightspeed/ops/load_harness.gleam", 194).
?DOC(" Deterministic markdown report for M34 fixture scripts.\n").
-spec snapshot_report_markdown() -> binary().
snapshot_report_markdown() ->
Report = run_fixture_matrix(),
Failed = failed_scenarios(Report),
Nondeterministic = nondeterministic_failures(Report),
Status = case (Failed =:= 0) andalso (Nondeterministic =:= 0) of
true ->
<<"OK"/utf8>>;
false ->
<<"FAIL"/utf8>>
end,
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"# Load Fixture Report\n\n"/utf8,
"snapshot_version: "/utf8>>/binary,
(erlang:integer_to_binary(
1
))/binary>>/binary,
"\n"/utf8>>/binary,
"status: "/utf8>>/binary,
Status/binary>>/binary,
"\n"/utf8>>/binary,
"failed_scenarios: "/utf8>>/binary,
(erlang:integer_to_binary(
Failed
))/binary>>/binary,
"\n"/utf8>>/binary,
"nondeterministic_failures: "/utf8>>/binary,
(erlang:integer_to_binary(Nondeterministic))/binary>>/binary,
"\n\n"/utf8>>/binary,
"snapshot_signature: "/utf8>>/binary,
(snapshot_signature())/binary>>/binary,
"\n\n"/utf8>>/binary,
"report_signature: "/utf8>>/binary,
(fixture_report_signature(Report))/binary>>/binary,
"\n"/utf8>>.