Skip to content

Commit

Permalink
Improve loadtest app part two (#450)
Browse files Browse the repository at this point in the history
* Select loadtests target server by its public URL in workflow

* Create another ets table to store the in-game websockets' clients

* Add genserver that logs current clients waiting for a game and current players in match

* Spawn a new client when a player finishes it match. This is to make the players in game constant

* Make ArenaLoadTest release start its app only

* Add LoadtestManager genserver that logs current opened websockets and also terminates the loadtest after given time

* Format big int

* Add LoadtestManager genserver that logs current opened websockets and also terminates the loadtest after given time

* Add documentation for the modules and functions

* Remove docs in callbacks

* Move ets tables creation to the supervisor init
  • Loading branch information
Nico-Sanchez authored Apr 8, 2024
1 parent 941d9f0 commit aba6660
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 33 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/loadtest-brazil-client-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/loadtest-europe-client-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion apps/arena_load_test/lib/arena_load_test/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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}
]
Expand Down
12 changes: 6 additions & 6 deletions apps/arena_load_test/lib/arena_load_test/game_socket_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
34 changes: 34 additions & 0 deletions apps/arena_load_test/lib/arena_load_test/loadtest_manager.ex
Original file line number Diff line number Diff line change
@@ -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
13 changes: 11 additions & 2 deletions apps/arena_load_test/lib/arena_load_test/socket_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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, [])

Expand All @@ -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

Expand Down
59 changes: 43 additions & 16 deletions apps/arena_load_test/lib/arena_load_test/socket_supervisor.ex
Original file line number Diff line number Diff line change
@@ -1,50 +1,77 @@
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__,
{GameSocketHandler, {client_id, game_id}}
)
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
5 changes: 1 addition & 4 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down

0 comments on commit aba6660

Please sign in to comment.