1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-22 13:34:04 +03:00

Refactor ServerStats weight calculation for improved clarity and accuracy

This commit is contained in:
Adolfo Gómez García 2024-11-04 18:15:49 +01:00
parent e1747bee13
commit 4861a10134
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
2 changed files with 18 additions and 22 deletions

View File

@ -148,14 +148,16 @@ class ServerManager(metaclass=singleton.Singleton):
stats_and_servers = self.get_server_stats(fltrs) stats_and_servers = self.get_server_stats(fltrs)
def _real_weight(stats: 'types.servers.ServerStats') -> float: def _real_weight(stats: 'types.servers.ServerStats') -> float:
stats_weight = stats.weight()
if weight_threshold == 0: if weight_threshold == 0:
return stats.weight() return stats_weight
# Values under threshold are better, weight is in between 0 and 1, lower is better # Values under threshold are better, weight is in between 0 and 1, lower is better
# To values over threshold, we will add 1, so they are always worse than any value under threshold # To values over threshold, we will add 1, so they are always worse than any value under threshold
# No matter if over threshold is overcalculed, it will be always worse than any value under threshold # No matter if over threshold is overcalculed, it will be always worse than any value under threshold
# and all values over threshold will be affected in the same way # and all values over threshold will be affected in the same way
return ( return (
weight_threshold - stats.weight() if stats.weight() < weight_threshold else 1 + stats.weight() weight_threshold - stats_weight if stats_weight < weight_threshold else 1 + stats_weight
) )
# Now, cachedStats has a list of tuples (stats, server), use it to find the best server # Now, cachedStats has a list of tuples (stats, server), use it to find the best server

View File

@ -33,6 +33,7 @@ import dataclasses
import enum import enum
import typing import typing
import collections.abc import collections.abc
import logging
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -41,6 +42,8 @@ from uds.core.util import ensure, singleton
IP_SUBTYPE: typing.Final[str] = 'ip' IP_SUBTYPE: typing.Final[str] = 'ip'
logger = logging.getLogger(__name__)
class ServerType(enum.IntEnum): class ServerType(enum.IntEnum):
TUNNEL = 1 TUNNEL = 1
@ -147,16 +150,6 @@ class ServerStats:
current_users: int = 0 # Number of current users current_users: int = 0 # Number of current users
stamp: float = 0 # Timestamp of this stats stamp: float = 0 # Timestamp of this stats
@property
def cpufree_ratio(self) -> float:
# Returns a valuen between 0 and 1, being 1 the best value (no cpu used per user) and 0 the worst
return (1 - self.cpuused) / (self.current_users + 1)
@property
def memfree_ratio(self) -> float:
# Returns a valuen between 0 and 1, being 1 the best value (no memory used per user) and 0 the worst
return (self.memtotal - self.memused) / (self.memtotal or 1) / (self.current_users + 1)
@property @property
def is_valid(self) -> bool: def is_valid(self) -> bool:
"""If the stamp is lesss than consts.cache.DEFAULT_CACHE_TIMEOUT, it is considered valid """If the stamp is lesss than consts.cache.DEFAULT_CACHE_TIMEOUT, it is considered valid
@ -174,20 +167,21 @@ class ServerStats:
def weight(self, min_memory: int = 0) -> float: def weight(self, min_memory: int = 0) -> float:
# Weights are calculated as: # Weights are calculated as:
# 0.5 * cpu_usage + 0.5 * (1 - mem_free / mem_total) / (current_users + 1) # 30% cpu usage
# +1 is because this weights the connection of current users + new user # 60% memory usage
# Dividing by number of users + 1 gives us a "ratio" of available resources per user when a new user is added # 10% current users, with a max of 1000 users
# Also note that +512 forces that if mem_free is less than 512 MB, this server will be put at the end of the list # Weights are normalized to 0-1
# Lower weight is better
if self.memtotal - self.memused < min_memory: if self.memtotal - self.memused < min_memory:
return 1000000000 # At the end of the list return 1000000000 # At the end of the list
# Lower is better w = (
# value can be between: 0.3 * self.cpuused
# (1 / (1 * 1.3 + 1) - 0.434) * 1.76 ~= 0.0 (worst case, no memory, all cpu) + 0.6 * (self.memused / (self.memtotal or 1))
# and + 0.1 * (min(1.0, self.current_users / 100.0))
# (1 / ((0 * 1.3 + 0) or 1) - 0.434) * 1.76 = 0.566 * 1.76 ~= 1.0 (best case, all memory, no cpu) )
w = (1 / ((self.cpufree_ratio * 1.3 + self.memfree_ratio) or 1) - 0.434) * 1.76
return min(max(0.0, w), 1.0) return min(max(0.0, w), 1.0)
def adjust(self, users_increment: int) -> 'ServerStats': def adjust(self, users_increment: int) -> 'ServerStats':