Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix load balancing heuristics, make load balancer public #36

Merged
merged 2 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,4 @@ docs/

.idea/artifacts
!**/build/generated/
core/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,11 @@ public abstract class AbstractLavakord internal constructor(
}


@Suppress("LeakingThis")
private val loadBalancer: LoadBalancer = LoadBalancer(options.loadBalancer.penaltyProviders, this)
/**
* The load balancer used to select nodes
*/
@Suppress("LeakingThis", "MemberVisibilityCanBePrivate")
public val loadBalancer: LoadBalancer = LoadBalancer(options.loadBalancer.penaltyProviders, this)

override val nodes: List<Node>
get() = nodesMap.values.toList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ import dev.schlaubi.lavakord.LavaKord
import dev.schlaubi.lavakord.audio.Node
import kotlin.math.pow

internal class LoadBalancer(
public class LoadBalancer(
private val penaltyProviders: List<PenaltyProvider>,
private val lavakord: LavaKord
) {

fun determineBestNode(guildId: ULong): Node? = lavakord.nodes
internal fun determineBestNode(guildId: ULong): Node? = lavakord.nodes
.asSequence()
.filter(Node::available)
.minByOrNull { calculatePenalties(it, penaltyProviders, guildId) }
.minByOrNull { calculatePenalties(it, guildId).sum }

// Inspired by: https://github.com/freyacodes/Lavalink-Client/blob/master/src/main/java/lavalink/client/io/LavalinkLoadBalancer.java#L111
private fun calculatePenalties(
/**
* Calculate the penalties for a given guild
* Adapted from https://github.com/freyacodes/Lavalink-Client/blob/master/src/main/java/lavalink/client/io/LavalinkLoadBalancer.java#L111
*/
public fun calculatePenalties(
node: Node,
penaltyProviders: List<PenaltyProvider>,
guildId: ULong
): Int {
): Penalties {
val playerPenalty: Int
val cpuPenalty: Int
val deficitFramePenalty: Int
Expand All @@ -36,20 +38,45 @@ internal class LoadBalancer(
playerPenalty = stats.playingPlayers

cpuPenalty = 1.05.pow(100 * stats.cpu.systemLoad).toInt() * 10 - 10
if ((stats.frameStats != null) && stats.frameStats?.deficit != 1) {
val frameStats = stats.frameStats
if (frameStats != null) {
val frameDeficit = frameStats.deficit.coerceAtLeast(0).toFloat()
val frameNulls = frameStats.nulled.coerceAtLeast(0).toFloat()

deficitFramePenalty =
(1.03.pow(((500f * (stats.frameStats?.deficit?.toFloat() ?: (0 / 3000f)))).toDouble()) * 600 - 600).toInt()
(1.03.pow(
((500f * frameDeficit / 3000f)).toDouble()
) * 600 - 600).toInt()
nullFramePenalty =
(1.03.pow(((500f * (stats.frameStats?.nulled?.toFloat() ?: (0 / 3000f)))).toDouble()) * 300 - 300).toInt() * 2
(1.03.pow(
((500f * frameNulls / 3000f)).toDouble()
) * 300 - 300).toInt() * 2
} else {
deficitFramePenalty = 0
nullFramePenalty = 0
}
}
return playerPenalty + cpuPenalty + deficitFramePenalty + nullFramePenalty + customPenalties
return Penalties(playerPenalty, cpuPenalty, deficitFramePenalty, nullFramePenalty, customPenalties)
}
}

/** Result of penalties used for load balancing */
public data class Penalties(
/** Penalty due to number of players */
val playerPenalty: Int,
/** Penalty due to high CPU */
val cpuPenalty: Int,
/** Penalty due to Lavalink struggling to send frames */
val deficitFramePenalty: Int,
/** Penalty due to Lavaplayer struggling to provide frames */
val nullFramePenalty: Int,
/** Penalties from [PenaltyProvider]*/
val customPenalties: Int
) {
/** The sum of all penalties */
val sum: Int = playerPenalty + cpuPenalty + deficitFramePenalty + nullFramePenalty + customPenalties
}

/**
* Interface to accept custom penalties for [Node]s.
*/
Expand Down