diff --git a/.github/workflows/loadtest-brazil-client-deploy.yml b/.github/workflows/loadtest-brazil-client-deploy.yml index dff7a3a14..7c369c227 100644 --- a/.github/workflows/loadtest-brazil-client-deploy.yml +++ b/.github/workflows/loadtest-brazil-client-deploy.yml @@ -16,8 +16,8 @@ on: type: choice description: Loadtest target server options: - - Brazil - - Europe + - loadtest-brazil-server.curseofmirra.com + - loadtest-europe-server.curseofmirra.com jobs: build-deploy: diff --git a/.github/workflows/loadtest-europe-client-deploy.yml b/.github/workflows/loadtest-europe-client-deploy.yml index 85ae93de1..9d66a3062 100644 --- a/.github/workflows/loadtest-europe-client-deploy.yml +++ b/.github/workflows/loadtest-europe-client-deploy.yml @@ -16,8 +16,8 @@ on: type: choice description: Loadtest target server options: - - Brazil - - Europe + - loadtest-europe-server.curseofmirra.com + - loadtest-brazil-server.curseofmirra.com jobs: build-deploy: diff --git a/apps/arena_load_test/lib/arena_load_test/application.ex b/apps/arena_load_test/lib/arena_load_test/application.ex index 2a8fca619..67532d1c4 100644 --- a/apps/arena_load_test/lib/arena_load_test/application.ex +++ b/apps/arena_load_test/lib/arena_load_test/application.ex @@ -8,7 +8,8 @@ defmodule ArenaLoadTest.Application do @impl true def start(_type, _args) do children = [ - ArenaLoadTest.SocketSupervisor + ArenaLoadTest.SocketSupervisor, + ArenaLoadTest.LoadtestManager # Starts a worker by calling: ArenaLoadTest.Worker.start_link(arg) # {ArenaLoadTest.Worker, arg} ] diff --git a/apps/arena_load_test/lib/arena_load_test/game_socket_handler.ex b/apps/arena_load_test/lib/arena_load_test/game_socket_handler.ex index 00faebd5c..65646f0a8 100644 --- a/apps/arena_load_test/lib/arena_load_test/game_socket_handler.ex +++ b/apps/arena_load_test/lib/arena_load_test/game_socket_handler.ex @@ -86,15 +86,16 @@ defmodule ArenaLoadTest.GameSocketHandler do {:reply, frame, state} end - def terminate(_, state) do - case :ets.lookup(:clients, state.client_id) do + def terminate(_, %{client_id: client_id} = _state) do + case :ets.lookup(:players, client_id) do [{client_id, _}] -> - :ets.delete(:clients, client_id) + :ets.delete(:players, client_id) [] -> - raise KeyError, message: "Client with ID #{state.client_id} doesn't exist." + raise KeyError, message: "Player with ID #{client_id} doesn't exist." end + SocketSupervisor.add_new_client(client_id) Logger.info("Player websocket terminated. Game Ended.") exit(:normal) end @@ -115,8 +116,7 @@ defmodule ArenaLoadTest.GameSocketHandler do "ws://localhost:4000/play/#{game_id}/#{client_id}" target_server -> - server_url = SocketSupervisor.get_server_url(target_server) - "wss://#{server_url}/play/#{game_id}/#{client_id}" + "wss://#{target_server}/play/#{game_id}/#{client_id}" end end diff --git a/apps/arena_load_test/lib/arena_load_test/loadtest_manager.ex b/apps/arena_load_test/lib/arena_load_test/loadtest_manager.ex new file mode 100644 index 000000000..ebc4bd2b5 --- /dev/null +++ b/apps/arena_load_test/lib/arena_load_test/loadtest_manager.ex @@ -0,0 +1,34 @@ +defmodule ArenaLoadTest.LoadtestManager do + @moduledoc """ + Genserver that interacts with the running loadtest for the user needs. + """ + use GenServer + require Logger + alias ArenaLoadTest.SocketSupervisor + + # API + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + # Callbacks + @impl true + def init(_) do + {:ok, %{}} + end + + @impl true + def handle_info(:clients_log, state) do + Logger.info("Clients waiting for a game: #{:ets.info(:clients, :size)}") + Logger.info("Players in match: #{:ets.info(:players, :size)}") + Process.send_after(self(), :clients_log, 1_000) + {:noreply, state} + end + + @impl true + def handle_info(:loadtest_finished, state) do + SocketSupervisor.terminate_children() + Logger.info("Loadtest finished.") + {:stop, :normal, state} + end +end diff --git a/apps/arena_load_test/lib/arena_load_test/socket_handler.ex b/apps/arena_load_test/lib/arena_load_test/socket_handler.ex index 4a8ecb33d..014c2c1ac 100644 --- a/apps/arena_load_test/lib/arena_load_test/socket_handler.ex +++ b/apps/arena_load_test/lib/arena_load_test/socket_handler.ex @@ -35,12 +35,22 @@ defmodule ArenaLoadTest.SocketHandler do game_id = Serialization.GameState.decode(game_state).game_id Logger.info("Client joining game with id: #{game_id}") + case :ets.lookup(:clients, state.client_id) do + [{client_id, _}] -> + :ets.delete(:clients, client_id) + + [] -> + raise KeyError, message: "Client with ID #{state.client_id} doesn't exist." + end + {:ok, pid} = SocketSupervisor.add_new_player( state.client_id, game_id ) + true = :ets.insert(:players, {state.client_id, game_id}) + Process.send(pid, :move, []) Process.send(pid, :attack, []) @@ -63,8 +73,7 @@ defmodule ArenaLoadTest.SocketHandler do "ws://localhost:4000/join/#{player_id}/#{character}/#{player_name}" target_server -> - server_url = SocketSupervisor.get_server_url(target_server) - "wss://#{server_url}/join/#{player_id}/#{character}/#{player_name}" + "wss://#{target_server}/join/#{player_id}/#{character}/#{player_name}" end end diff --git a/apps/arena_load_test/lib/arena_load_test/socket_supervisor.ex b/apps/arena_load_test/lib/arena_load_test/socket_supervisor.ex index d8bdf2d9e..cb2bfee76 100644 --- a/apps/arena_load_test/lib/arena_load_test/socket_supervisor.ex +++ b/apps/arena_load_test/lib/arena_load_test/socket_supervisor.ex @@ -1,23 +1,39 @@ defmodule ArenaLoadTest.SocketSupervisor do @moduledoc """ - Socket Supervisor + Dynamic Supervisor for the websockets connections. """ use DynamicSupervisor alias ArenaLoadTest.SocketHandler alias ArenaLoadTest.GameSocketHandler + alias ArenaLoadTest.LoadtestManager require Logger def start_link(args) do DynamicSupervisor.start_link(__MODULE__, args, name: __MODULE__, max_restarts: 1) end + @impl true + def init(_opts) do + create_ets_table(:clients) + create_ets_table(:players) + DynamicSupervisor.init(strategy: :one_for_one) + end + + @doc """ + Initializes a websocket that handles the client connection in the game waiting queue. + """ def add_new_client(client_id) do + true = :ets.insert(:clients, {client_id, client_id}) + DynamicSupervisor.start_child( __MODULE__, {SocketHandler, client_id} ) end + @doc """ + Initializes a websocket that handles the client connection in-game. + """ def add_new_player(client_id, game_id) do DynamicSupervisor.start_child( __MODULE__, @@ -25,26 +41,37 @@ defmodule ArenaLoadTest.SocketSupervisor do ) end - @impl true - def init(_opts) do - DynamicSupervisor.init(strategy: :one_for_one) - end - - # Creates `num_clients` clients to join a game - def spawn_players(num_clients) do - case :ets.whereis(:clients) do - :undefined -> :ets.new(:clients, [:set, :named_table, :public]) - _table_exists_already -> nil - end + @doc """ + Loadtests entrypoint. + Creates given amount of clients that will join and play a game for the given duration. + """ + def spawn_players(num_clients, playtime_duration_ms \\ 999_999) do + send(LoadtestManager, :clients_log) + Process.send_after(LoadtestManager, :loadtest_finished, playtime_duration_ms) Enum.each(1..num_clients, fn client_number -> Logger.info("Iteration: #{client_number}") {:ok, _pid} = add_new_client(client_number) - true = :ets.insert(:clients, {client_number, "1"}) - Logger.info("Clients alive: #{:ets.info(:clients, :size)}") end) end - def get_server_url("Brazil"), do: System.get_env("BRAZIL_HOST") - def get_server_url("Europe"), do: System.get_env("EUROPE_HOST") + @doc """ + Terminates all the websocket connections. + """ + def terminate_children() do + children = DynamicSupervisor.which_children(__MODULE__) + + Enum.each(children, fn {_, child_pid, _, _} -> + DynamicSupervisor.terminate_child(__MODULE__, child_pid) + end) + end + + # Create a public ets table by given name. + # Table is not created if it exists already. + defp create_ets_table(table_name) do + case :ets.whereis(table_name) do + :undefined -> :ets.new(table_name, [:set, :named_table, :public]) + _table_exists_already -> nil + end + end end diff --git a/mix.exs b/mix.exs index 78575fcbc..88856650e 100644 --- a/mix.exs +++ b/mix.exs @@ -44,10 +44,7 @@ defmodule MirraBackend.MixProject do defp releases() do [ arena: [applications: [arena: :permanent]], - # TODO ArenaLoadTest must deploy only arena - arena_load_test: [ - applications: [arena_load_test: :permanent, arena: :permanent, bot_manager: :permanent] - ], + arena_load_test: [applications: [arena_load_test: :permanent]], game_client: [applications: [game_client: :permanent]], game_backend: [ applications: [