diff --git a/server/matchmaker/matchmaker_queue.py b/server/matchmaker/matchmaker_queue.py index 0cc343edd..96138db60 100644 --- a/server/matchmaker/matchmaker_queue.py +++ b/server/matchmaker/matchmaker_queue.py @@ -108,6 +108,15 @@ def initialize(self): def num_players(self) -> int: return sum(len(search.players) for search in self._queue.keys()) + @property + def num_new_queued_players(self) -> int: + """ Get the number of players who have joined the queue and not yet had a failed matching attempt in the last pop. + + Returns: + int: Number of players who have joined the queue since last matching attempt. + """ + return sum(len(search.players) for search in self._queue.keys() if search.failed_matching_attempts == 0) + async def queue_pop_timer(self) -> None: """ Periodically tries to match all Searches in the queue. The amount of time until next queue 'pop' is determined by the number of players diff --git a/server/matchmaker/pop_timer.py b/server/matchmaker/pop_timer.py index facc1128f..22adf65da 100644 --- a/server/matchmaker/pop_timer.py +++ b/server/matchmaker/pop_timer.py @@ -56,9 +56,12 @@ async def next_pop(self): num_players = self.queue.num_players metrics.matchmaker_players.labels(self.queue.name).set(num_players) + num_new_players = self.queue.num_new_queued_players + metrics.matchmaker_new_players.labels(self.queue.name).set(num_new_players) + self._last_queue_pop = time() self.next_queue_pop = self._last_queue_pop + self.time_until_next_pop( - num_players, time_remaining + num_new_players, time_remaining ) def time_until_next_pop(self, num_queued: int, time_queued: float) -> float: @@ -82,8 +85,9 @@ def time_until_next_pop(self, num_queued: int, time_queued: float) -> float: players_per_match = self.queue.team_size * 2 desired_players = config.QUEUE_POP_DESIRED_MATCHES * players_per_match - # Obtained by solving $ NUM_PLAYERS = rate * time $ for time. - next_pop_time = desired_players * total_times / total_players + player_addition_rate = total_players / total_times + # Obtained by solving $ desired_players = player_addition_rate * time $ for time. + next_pop_time = desired_players / player_addition_rate if next_pop_time > config.QUEUE_POP_TIME_MAX: self._logger.info( "Required time (%.2fs) for %s is larger than max pop time (%ds). " diff --git a/server/metrics.py b/server/metrics.py index 52b725c1f..c854ba7e1 100644 --- a/server/metrics.py +++ b/server/metrics.py @@ -67,6 +67,10 @@ class MatchLaunch: "server_matchmaker_queue_players", "Players in the queue at pop time", ["queue"] ) +matchmaker_new_players = Gauge( + "server_matchmaker_queue_new_players", "Number of newly added players to the queue at pop time", ["queue"] +) + matchmaker_queue_pop = Gauge( "server_matchmaker_queue_pop_timer_seconds", "Queue pop timer duration in seconds", diff --git a/tests/unit_tests/test_pop_timer.py b/tests/unit_tests/test_pop_timer.py index 350a411db..a4c7764b8 100644 --- a/tests/unit_tests/test_pop_timer.py +++ b/tests/unit_tests/test_pop_timer.py @@ -43,3 +43,16 @@ def test_queue_pop_time_moving_average_size(queue_factory): # The rate should be extremely low, meaning the pop time should be high assert t1.time_until_next_pop(0, 100) == config.QUEUE_POP_TIME_MAX + + +def test_queue_pop_time_moving_average_size_stability(queue_factory): + # Test perfect number of players joining has a stable pop time + team_size = 2 + desired_players = team_size * config.QUEUE_POP_DESIRED_MATCHES + t1 = PopTimer(queue_factory(team_size=2)) + initial_time_to_pop = (config.QUEUE_POP_TIME_MAX + config.QUEUE_POP_TIME_MIN) / 2 # Arbitrary time between min and max + next_time_to_pop = initial_time_to_pop + for _ in range(100): + next_time_to_pop = t1.time_until_next_pop(desired_players, next_time_to_pop) + + assert t1.time_until_next_pop(desired_players, next_time_to_pop) == next_time_to_pop # Perfect size should not change the next time