Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
f9206c7c99 | |||
7eae103756 | |||
8efc77cacc | |||
4ae4d6def2 | |||
1bb3b36df6 | |||
c4c66b5c23 | |||
ef903095c9 | |||
ba383db391 |
51
balancer.go
Normal file
51
balancer.go
Normal file
@ -0,0 +1,51 @@
|
||||
package gavalink
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type balancePenalty struct {
|
||||
node *Node
|
||||
penalty int
|
||||
}
|
||||
|
||||
func BestNodeByPenalties(nodes []*Node) (*Node, error) {
|
||||
penalties := make([]balancePenalty, len(nodes))
|
||||
|
||||
var playerPenalty, cpuPenalty, deficitFramePenalty, nullFramePenalty int
|
||||
|
||||
for i, node := range nodes {
|
||||
playerPenalty = 0
|
||||
cpuPenalty = 0
|
||||
deficitFramePenalty = 0
|
||||
nullFramePenalty = 0
|
||||
|
||||
if node.stats != nil {
|
||||
playerPenalty = node.stats.ActivePlayers
|
||||
cpuPenalty = int(math.Pow(1.05, 100*node.stats.Cpu.SystemLoad)*10 - 10)
|
||||
|
||||
if node.stats.Frames != nil && node.stats.Frames.Deficit != -1 {
|
||||
deficitFramePenalty = int(math.Pow(1.03, 500*float64(node.stats.Frames.Deficit/3000))*600 - 600)
|
||||
nullFramePenalty = int(math.Pow(1.03, 500*float64(node.stats.Frames.Nulled/3000))*300 - 300)
|
||||
nullFramePenalty *= 2
|
||||
}
|
||||
}
|
||||
|
||||
penalties[i] = balancePenalty{node, playerPenalty + cpuPenalty + deficitFramePenalty + nullFramePenalty}
|
||||
}
|
||||
|
||||
sort.SliceStable(penalties, func(i, j int) bool {
|
||||
return penalties[i].penalty < penalties[j].penalty
|
||||
})
|
||||
|
||||
return penalties[0].node, nil
|
||||
}
|
||||
|
||||
func BestNodeByLoad(n []*Node) (*Node, error) {
|
||||
sort.SliceStable(n, func(i, j int) bool {
|
||||
return n[i].stats.Cpu.LavalinkLoad < n[j].stats.Cpu.LavalinkLoad
|
||||
})
|
||||
|
||||
return n[0], nil
|
||||
}
|
50
lavalink.go
50
lavalink.go
@ -3,8 +3,9 @@ package gavalink
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Log sets the log.Logger gavalink will write to
|
||||
@ -19,8 +20,12 @@ type Lavalink struct {
|
||||
shards string
|
||||
userID string
|
||||
|
||||
nodes []Node
|
||||
nodes []*Node
|
||||
players map[string]*Player
|
||||
|
||||
capabilities map[string]interface{}
|
||||
|
||||
BestNodeFunc func([]*Node) (*Node, error)
|
||||
}
|
||||
|
||||
var (
|
||||
@ -38,19 +43,25 @@ func NewLavalink(shards string, userID string) *Lavalink {
|
||||
return &Lavalink{
|
||||
shards: shards,
|
||||
userID: userID,
|
||||
/* nodes: make([]Node, 1),*/
|
||||
players: make(map[string]*Player),
|
||||
|
||||
BestNodeFunc: BestNodeByPenalties,
|
||||
}
|
||||
}
|
||||
|
||||
// AddNodes adds a node to the Lavalink manager
|
||||
func (lavalink *Lavalink) AddNodes(nodeConfigs ...NodeConfig) error {
|
||||
nodes := make([]Node, len(nodeConfigs))
|
||||
nodes := make([]*Node, len(nodeConfigs))
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
for i, c := range nodeConfigs {
|
||||
n := Node{
|
||||
n := &Node{
|
||||
config: c,
|
||||
manager: lavalink,
|
||||
client: client,
|
||||
}
|
||||
|
||||
err := n.open()
|
||||
@ -71,7 +82,7 @@ func (lavalink *Lavalink) AddNodes(nodeConfigs ...NodeConfig) error {
|
||||
func (lavalink *Lavalink) removeNode(node *Node) error {
|
||||
idx := -1
|
||||
for i, n := range lavalink.nodes {
|
||||
if n == *node {
|
||||
if n == node {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
@ -82,6 +93,18 @@ func (lavalink *Lavalink) removeNode(node *Node) error {
|
||||
|
||||
node.stop()
|
||||
|
||||
for _, player := range lavalink.players {
|
||||
if player.node == node {
|
||||
n, err := lavalink.BestNode()
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
player.ChangeNode(n)
|
||||
}
|
||||
}
|
||||
|
||||
// temp var for easier reading
|
||||
n := lavalink.nodes
|
||||
z := len(n) - 1
|
||||
@ -99,11 +122,7 @@ func (lavalink *Lavalink) BestNode() (*Node, error) {
|
||||
return nil, errNoNodes
|
||||
}
|
||||
|
||||
sort.SliceStable(lavalink.nodes, func(i, j int) bool {
|
||||
return lavalink.nodes[i].load < lavalink.nodes[j].load
|
||||
})
|
||||
|
||||
return &lavalink.nodes[0], nil
|
||||
return lavalink.BestNodeFunc(lavalink.nodes)
|
||||
}
|
||||
|
||||
// GetPlayer gets a player for a guild
|
||||
@ -116,3 +135,12 @@ func (lavalink *Lavalink) GetPlayer(guild string) (*Player, error) {
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Add capabilities mappings to the client, letting the server know what we support
|
||||
func (lavalink *Lavalink) AddCapability(key string, i interface{}) {
|
||||
if lavalink.capabilities == nil {
|
||||
lavalink.capabilities = make(map[string]interface{})
|
||||
}
|
||||
|
||||
lavalink.capabilities[key] = i
|
||||
}
|
||||
|
1
model.go
1
model.go
@ -91,6 +91,7 @@ type VoiceProcessingData struct {
|
||||
io.ReadCloser
|
||||
|
||||
Client *http.Client
|
||||
UserID string
|
||||
URL string
|
||||
File string
|
||||
|
||||
|
67
node.go
67
node.go
@ -6,6 +6,7 @@ import (
|
||||
"github.com/valyala/fastjson"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
@ -29,12 +30,41 @@ type NodeConfig struct {
|
||||
// Node wraps a Lavalink Node
|
||||
type Node struct {
|
||||
config NodeConfig
|
||||
load float32
|
||||
stats *RemoteStats
|
||||
manager *Lavalink
|
||||
wsConn *websocket.Conn
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type RemoteStats struct {
|
||||
Op string `json:"op"`
|
||||
Players int `json:"players"`
|
||||
ActivePlayers int `json:"playingPlayers"`
|
||||
Uptime int64 `json:"uptime"`
|
||||
Memory *MemoryStats `json:"memory"`
|
||||
Cpu *CpuStats `json:"cpu"`
|
||||
Frames *FrameStats `json:"frameStats"`
|
||||
}
|
||||
|
||||
type MemoryStats struct {
|
||||
Free uint64 `json:"free"`
|
||||
Used uint64 `json:"used"`
|
||||
Allocated uint64 `json:"allocated"`
|
||||
Reserveable uint64 `json:"reserveable"`
|
||||
}
|
||||
|
||||
type CpuStats struct {
|
||||
Cores int `json:"cores"`
|
||||
SystemLoad float64 `json:"systemLoad"`
|
||||
LavalinkLoad float64 `json:"lavalinkLoad"`
|
||||
}
|
||||
|
||||
type FrameStats struct {
|
||||
Sent int `json:"sent"`
|
||||
Nulled int `json:"nulled"`
|
||||
Deficit int `json:"deficit"`
|
||||
}
|
||||
|
||||
func (node *Node) open() error {
|
||||
header := http.Header{}
|
||||
|
||||
@ -42,6 +72,22 @@ func (node *Node) open() error {
|
||||
header.Set("Num-Shards", node.manager.shards)
|
||||
header.Set("User-Id", node.manager.userID)
|
||||
|
||||
if node.manager.capabilities != nil {
|
||||
v := make([]string, 0)
|
||||
|
||||
for k, vals := range node.manager.capabilities {
|
||||
b, err := json.Marshal(vals)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
v = append(v, k+"="+string(b))
|
||||
}
|
||||
|
||||
header.Set("Capabilities", strings.Join(v, ";"))
|
||||
}
|
||||
|
||||
ws, resp, err := websocket.DefaultDialer.Dial(node.config.WebSocket, header)
|
||||
|
||||
if err != nil {
|
||||
@ -108,16 +154,25 @@ func (node *Node) listen() {
|
||||
continue
|
||||
}
|
||||
|
||||
node.onEvent(v)
|
||||
node.onEvent(v, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (node *Node) onEvent(v *fastjson.Value) error {
|
||||
func (node *Node) onEvent(v *fastjson.Value, msg []byte) error {
|
||||
op := jsonStringValue(v, "op")
|
||||
|
||||
switch op {
|
||||
case opStats:
|
||||
node.stats = &RemoteStats{}
|
||||
|
||||
err := json.Unmarshal(msg, &node.stats)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case opPlayerUpdate:
|
||||
player, err := node.manager.GetPlayer(jsonStringValue(v, "guildId"))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -153,13 +208,13 @@ func (node *Node) onEvent(v *fastjson.Value) error {
|
||||
track := jsonStringValue(v, "track")
|
||||
|
||||
data := &VoiceProcessingData{
|
||||
Client: node.client,
|
||||
UserID: jsonStringValue(v, "userId"),
|
||||
URL: fmt.Sprintf("%s/audio/%s", node.config.REST, track),
|
||||
File: track,
|
||||
}
|
||||
|
||||
return player.handler.OnVoiceProcessed(player, data, v.GetBool("hotword"), v.GetBool("override"))
|
||||
case opStats:
|
||||
node.load = float32(v.GetFloat64("cpu", "lavalinkLoad"))
|
||||
default:
|
||||
return errUnknownPayload
|
||||
}
|
||||
@ -184,10 +239,12 @@ func (node *Node) CreatePlayer(guildID string, sessionID string, event VoiceServ
|
||||
|
||||
player := &Player{
|
||||
guildID: guildID,
|
||||
sessionID: sessionID,
|
||||
manager: node.manager,
|
||||
node: node,
|
||||
handler: handler,
|
||||
vol: 100,
|
||||
lastVoiceServerUpdate: event,
|
||||
}
|
||||
|
||||
node.manager.players[guildID] = player
|
||||
|
14
player.go
14
player.go
@ -7,6 +7,7 @@ import (
|
||||
// Player is a Lavalink player
|
||||
type Player struct {
|
||||
guildID string
|
||||
sessionID string
|
||||
time int
|
||||
position int
|
||||
paused bool
|
||||
@ -15,6 +16,7 @@ type Player struct {
|
||||
manager *Lavalink
|
||||
node *Node
|
||||
handler EventHandler
|
||||
lastVoiceServerUpdate VoiceServerUpdate
|
||||
}
|
||||
|
||||
// GuildID returns this player's Guild ID
|
||||
@ -166,6 +168,8 @@ func (player *Player) UserLeave(userId string) error {
|
||||
// To move a player to a new Node, first player.Destroy() it, and then
|
||||
// create a new player on the new node.
|
||||
func (player *Player) Forward(sessionID string, event VoiceServerUpdate) error {
|
||||
player.sessionID = sessionID
|
||||
|
||||
msg := voiceUpdateMessage{
|
||||
Op: opVoiceUpdate,
|
||||
GuildID: player.guildID,
|
||||
@ -173,9 +177,19 @@ func (player *Player) Forward(sessionID string, event VoiceServerUpdate) error {
|
||||
Event: &event,
|
||||
}
|
||||
|
||||
player.lastVoiceServerUpdate = event
|
||||
|
||||
return player.node.wsConn.WriteJSON(msg)
|
||||
}
|
||||
|
||||
func (player *Player) ChangeNode(node *Node) error {
|
||||
player.node = node
|
||||
|
||||
player.Forward(player.sessionID, player.lastVoiceServerUpdate)
|
||||
|
||||
return player.PlayAt(player.track, player.position, 0)
|
||||
}
|
||||
|
||||
// Destroy will destroy this player
|
||||
func (player *Player) Destroy() error {
|
||||
msg := basicMessage{
|
||||
|
Reference in New Issue
Block a user