Skip to content

Commit f16a03a

Browse files
authored
Misc improvment in load test (#42)
* Use docker to run them * Use `gun` instead of `httpc`. `gun` supports http2, `httpc` does not. * Align some default value between load test and neurow to have green load tests without setting env var
1 parent 18cb6e2 commit f16a03a

File tree

14 files changed

+143
-81
lines changed

14 files changed

+143
-81
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,17 @@ jobs:
3535
steps:
3636
- uses: actions/checkout@v4
3737
- run: |
38-
cd neurow
38+
cd neurow
3939
docker build . -t neurow:${{ github.sha }}
4040
docker run -e JWT_CONFIG="{\"local_name\":\"neurow\",\"service_name\":\"neurow\",\"algorithm\":\"HS256\",\"clients\":{\"test\":\"secret\"}}" -d --name neurow_${{ github.sha }}_test neurow:${{ github.sha }}
4141
docker exec neurow_${{ github.sha }}_test curl -v --retry 5 --retry-connrefused --retry-max-time 30 --retry-delay 6 http://localhost:3000/ping
4242
docker stop neurow_${{ github.sha }}_test
4343
docker rm neurow_${{ github.sha }}_test
44+
45+
docker_load_test:
46+
runs-on: ubuntu-latest
47+
steps:
48+
- uses: actions/checkout@v4
49+
- run: |
50+
cd load_test
51+
docker build . -t neurow:${{ github.sha }}

load_test/Dockerfile

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
ARG BUILDER_IMAGE=elixir
2+
ARG VERSION=1.17-slim
3+
4+
FROM ${BUILDER_IMAGE}:${VERSION} AS builder
5+
6+
RUN apt-get update \
7+
&& apt-get install -y --no-install-recommends git build-essential ca-certificates \
8+
&& apt-get clean \
9+
&& rm -rf /var/lib/apt/lists/*
10+
11+
RUN mkdir /app
12+
WORKDIR /app
13+
ENV MIX_ENV=prod
14+
15+
RUN mix local.hex --force
16+
17+
COPY mix.exs mix.lock /app/
18+
RUN mix deps.get \
19+
&& mix deps.compile
20+
21+
COPY config /app/config/
22+
COPY lib /app/lib/
23+
24+
ARG GIT_COMMIT_SHA1=no_commit
25+
RUN mix release
26+
27+
FROM ${BUILDER_IMAGE}:${VERSION}
28+
29+
RUN apt-get update \
30+
&& apt-get install -y --no-install-recommends curl dnsutils ca-certificates \
31+
&& apt-get clean \
32+
&& rm -rf /var/lib/apt/lists/*
33+
34+
RUN mkdir /app
35+
WORKDIR /app
36+
37+
COPY start.sh /
38+
COPY --from=builder /app/_build/prod/rel/load_test /app/
39+
40+
ENV RELEASE_TMP=/tmp/
41+
ENV RELEASE_COOKIE=changme
42+
43+
CMD [ "/app/bin/load_test", "start"]

load_test/config/runtime.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ config :load_test, port: String.to_integer(System.get_env("PORT") || "2999")
88
config :load_test, nb_user: String.to_integer(System.get_env("NB_USER") || "1")
99

1010
config :load_test, sse_user_agent: System.get_env("SSE_USER_AGENT") || "neurow_load_test/1.0"
11-
config :load_test, sse_timeout: String.to_integer(System.get_env("SSE_TIMEOUT") || "15000")
11+
config :load_test, sse_timeout: String.to_integer(System.get_env("SSE_TIMEOUT") || "900000")
1212
config :load_test, sse_url: System.get_env("SSE_URL") || "http://localhost:4000/v1/subscribe"
1313
config :load_test, sse_jwt_issuer: System.get_env("SSE_JWT_ISSUER") || "test_issuer1"
1414

load_test/lib/load_test/main.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ defmodule LoadTest.Main do
7575
number_of_messages_max: number_of_messages_max
7676
}
7777

78-
Logger.warning("SSE BASE URL: #{sse_url}")
79-
Logger.warning("PUBLISH BASE URL: #{publish_url}")
78+
Logger.warning("SSE base url: #{sse_url}")
79+
Logger.warning("Publish base url: #{publish_url}")
80+
Logger.warning("User agent: #{sse_user_agent}")
8081
Logger.warning("Starting load test with #{nb_user} users")
8182

8283
Enum.map(1..nb_user, fn _ ->

load_test/lib/load_test/user/publisher.ex

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ defmodule LoadTest.User.Publisher do
1212
:publish_jwt_audience,
1313
:delay_between_messages_min,
1414
:delay_between_messages_max,
15-
:start_time
15+
:start_time,
16+
:message_count
1617
]
1718
end
1819

@@ -35,7 +36,8 @@ defmodule LoadTest.User.Publisher do
3536
publish_jwt_audience: context.publish_jwt_audience,
3637
delay_between_messages_min: context.delay_between_messages_min,
3738
delay_between_messages_max: context.delay_between_messages_max,
38-
start_time: start_time
39+
start_time: start_time,
40+
message_count: length(messages)
3941
}
4042

4143
Logger.debug(fn ->
@@ -73,7 +75,7 @@ defmodule LoadTest.User.Publisher do
7375
duration = :os.system_time(:millisecond) - state.start_time
7476

7577
Logger.info(fn ->
76-
"publisher_#{state.user_name}: All messages published to #{state.publish_url}, duration: #{duration / 1000}"
78+
"publisher_#{state.user_name}: All messages published (#{state.message_count}) to #{state.publish_url}, duration: #{duration / 1000}"
7779
end)
7880
end
7981

load_test/lib/load_test/user/sse.ex

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,23 @@ defmodule SseUser do
4646
end)
4747

4848
headers = build_headers(context, topic)
49-
http_request_opts = []
5049

51-
{:ok, request_id} =
52-
:httpc.request(:get, {url, headers}, http_request_opts, [{:sync, false}, {:stream, :self}])
50+
parsed_url = URI.parse(url)
51+
52+
opts = %{
53+
tls_opts: [
54+
{:customize_hostname_check,
55+
[{:match_fun, :public_key.pkix_verify_hostname_match_fun(:https)}]}
56+
],
57+
http_opts: %{
58+
closing_timeout: :infinity
59+
}
60+
}
61+
62+
{:ok, conn_pid} = :gun.open(String.to_atom(parsed_url.host), parsed_url.port, opts)
63+
{:ok, proto} = :gun.await_up(conn_pid)
64+
Logger.debug(fn -> "Connection established with proto #{inspect(proto)}" end)
65+
stream_ref = :gun.get(conn_pid, parsed_url.path, headers)
5366

5467
state = %SseState{
5568
user_name: user_name,
@@ -63,65 +76,54 @@ defmodule SseUser do
6376
end
6477
}
6578

66-
wait_for_messages(state, request_id, expected_messages)
79+
wait_for_messages(state, conn_pid, stream_ref, expected_messages)
6780
end
6881

69-
defp wait_for_messages(state, request_id, [first_message | remaining_messages]) do
82+
defp wait_for_messages(state, conn_pid, stream_ref, [first_message | remaining_messages]) do
7083
Logger.debug(fn -> "#{header(state)} Waiting for message: #{first_message}" end)
7184

72-
receive do
73-
{:http, {_, {:error, msg}}} ->
74-
Logger.error("#{header(state)} Http error: #{inspect(msg)}")
75-
:ok = :httpc.cancel_request(request_id)
85+
result = :gun.await(conn_pid, stream_ref, state.sse_timeout)
86+
87+
case result do
88+
{:response, _, code, _} when code == 200 ->
89+
Logger.debug(
90+
"#{header(state)} Connected, waiting: #{length(remaining_messages) + 1} messages, url #{state.url}"
91+
)
92+
93+
state.start_publisher_callback.()
94+
wait_for_messages(state, conn_pid, stream_ref, [first_message | remaining_messages])
95+
96+
{:response, _, code, _} ->
97+
Logger.error("#{header(state)} Error: #{inspect(code)}")
98+
:ok = :gun.close(conn_pid)
7699
Stats.inc_msg_received_http_error()
77-
raise("#{header(state)} Http error")
100+
raise("#{header(state)} Error")
78101

79-
{:http, {_, :stream, msg}} ->
102+
{:data, _, msg} ->
80103
msg = String.trim(msg)
81104
Logger.debug(fn -> "#{header(state)} Received message: #{inspect(msg)}" end)
82105

83106
if msg =~ "event: ping" do
84-
wait_for_messages(state, request_id, [first_message | remaining_messages])
107+
wait_for_messages(state, conn_pid, stream_ref, [first_message | remaining_messages])
85108
else
86109
if check_message(state, msg, first_message) == :error do
87-
:ok = :httpc.cancel_request(request_id)
110+
:ok = :gun.close(conn_pid)
88111
raise("#{header(state)} Message check error")
89112
end
90113

91114
state = Map.put(state, :current_message, state.current_message + 1)
92-
wait_for_messages(state, request_id, remaining_messages)
115+
wait_for_messages(state, conn_pid, stream_ref, remaining_messages)
93116
end
94117

95-
{:http, {_, :stream_start, headers}} ->
96-
{~c"x-sse-server", server} = List.keyfind(headers, ~c"x-sse-server", 0)
97-
98-
Logger.debug(fn ->
99-
"#{header(state)} Connected, waiting: #{length(remaining_messages) + 1} messages, url #{state.url}, remote server: #{server}"
100-
end)
101-
102-
state.start_publisher_callback.()
103-
104-
wait_for_messages(state, request_id, [first_message | remaining_messages])
105-
106118
msg ->
107119
Logger.error("#{header(state)} Unexpected message #{inspect(msg)}")
108-
:ok = :httpc.cancel_request(request_id)
120+
:ok = :gun.close(conn_pid)
109121
raise("#{header(state)} Unexpected message")
110-
after
111-
state.sse_timeout ->
112-
Logger.error(
113-
"#{header(state)} Timeout waiting for message (timeout=#{state.sse_timeout}ms), remaining: #{length(remaining_messages) + 1} messages, url #{state.url}"
114-
)
115-
116-
Stats.inc_msg_received_timeout()
117-
118-
:ok = :httpc.cancel_request(request_id)
119-
raise("#{header(state)} Timeout waiting for message")
120122
end
121123
end
122124

123-
defp wait_for_messages(state, request_id, []) do
124-
:ok = :httpc.cancel_request(request_id)
125+
defp wait_for_messages(state, conn_pid, _, []) do
126+
:ok = :gun.close(conn_pid)
125127
Logger.info("#{header(state)} All messages received, url #{state.url}")
126128
end
127129

load_test/mix.exs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ defmodule LoadTest.MixProject do
2323

2424
def application do
2525
[
26-
extra_applications: [:logger, :inets],
26+
extra_applications: [:logger, :inets, :gun],
2727
mod: {LoadTest.Application, []}
2828
]
2929
end
@@ -38,7 +38,8 @@ defmodule LoadTest.MixProject do
3838
{:finch, "~> 0.18"},
3939
{:jose, "~> 1.11"},
4040
{:jiffy, "~> 1.1"},
41-
{:observer_cli, "~> 1.7"}
41+
{:observer_cli, "~> 1.7"},
42+
{:gun, "~> 2.1"}
4243
]
4344
end
4445
end

load_test/mix.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
66
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
77
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
8+
"gun": {:hex, :gun, "2.1.0", "b4e4cbbf3026d21981c447e9e7ca856766046eff693720ba43114d7f5de36e87", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "52fc7fc246bfc3b00e01aea1c2854c70a366348574ab50c57dfe796d24a0101d"},
89
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
910
"jiffy": {:hex, :jiffy, "1.1.2", "a9b6c9a7ec268e7cf493d028f0a4c9144f59ccb878b1afe42841597800840a1b", [:rebar3], [], "hexpm", "bb61bc42a720bbd33cb09a410e48bb79a61012c74cb8b3e75f26d988485cf381"},
1011
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},

load_test/start.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh -e
2+
3+
ulimit -n 1000000
4+
exec /app/bin/load_test start

load_test/terraform/datasource.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
data "aws_ami" "ami-x86" {
22
filter {
33
name = "name"
4-
values = ["amzn2-ami-kernel-*-hvm-*x86*"]
4+
values = ["*/ubuntu-noble-24.04-amd64-server*"]
55
}
66

77
most_recent = true
8-
owners = ["137112412989"]
8+
owners = ["099720109477"]
99
}
1010

1111
data "aws_subnet" "first_public" {

0 commit comments

Comments
 (0)