eq2go/internal/groups/benchmark_test.go

666 lines
16 KiB
Go

package groups
import (
"fmt"
"math/rand"
"sync/atomic"
"testing"
"time"
)
// Mock implementations for benchmarking use the existing mock entities from groups_test.go
// Helper functions for creating test data
// createTestEntity creates a mock entity for benchmarking
func createTestEntity(id int32, name string, isPlayer bool) *mockEntity {
entity := createMockEntity(id, name, isPlayer)
// Randomize some properties for more realistic benchmarking
entity.level = int8(rand.Intn(80) + 1)
entity.class = int8(rand.Intn(25) + 1)
entity.race = int8(rand.Intn(18) + 1)
entity.hp = int32(rand.Intn(5000) + 1000)
entity.maxHP = int32(rand.Intn(5000) + 1000)
entity.power = int32(rand.Intn(3000) + 500)
entity.maxPower = int32(rand.Intn(3000) + 500)
entity.isBot = !isPlayer && rand.Intn(2) == 1
entity.isNPC = !isPlayer && rand.Intn(2) == 0
entity.zone = &mockZone{
zoneID: int32(rand.Intn(100) + 1),
instanceID: int32(rand.Intn(10)),
zoneName: fmt.Sprintf("Zone %d", rand.Intn(100)+1),
}
return entity
}
// createTestGroup creates a group with test data for benchmarking
func createTestGroup(b *testing.B, groupID int32, memberCount int) *Group {
b.Helper()
group := NewGroup(groupID, nil, nil)
// Add test members
for i := 0; i < memberCount; i++ {
entity := createTestEntity(
int32(i+1),
fmt.Sprintf("Player%d", i+1),
true,
)
isLeader := (i == 0)
err := group.AddMember(entity, isLeader)
if err != nil {
b.Fatalf("Failed to add member to group: %v", err)
}
}
return group
}
// BenchmarkGroupCreation measures group creation performance
func BenchmarkGroupCreation(b *testing.B) {
b.Run("NewGroup", func(b *testing.B) {
for i := 0; i < b.N; i++ {
group := NewGroup(int32(i+1), nil, nil)
group.Disband() // Clean up background goroutine
}
})
b.Run("NewGroupWithOptions", func(b *testing.B) {
options := DefaultGroupOptions()
for i := 0; i < b.N; i++ {
group := NewGroup(int32(i+1), &options, nil)
group.Disband() // Clean up background goroutine
}
})
b.Run("NewGroupParallel", func(b *testing.B) {
var idCounter int64
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
id := atomic.AddInt64(&idCounter, 1)
group := NewGroup(int32(id), nil, nil)
group.Disband() // Clean up background goroutine
}
})
})
}
// BenchmarkGroupMemberOperations measures member management performance
func BenchmarkGroupMemberOperations(b *testing.B) {
group := createTestGroup(b, 1001, 3)
defer group.Disband() // Clean up background goroutine
b.Run("AddMember", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Create a new group for each iteration to avoid full group
testGroup := NewGroup(int32(i+1000), nil, nil)
entity := createTestEntity(int32(i+1), fmt.Sprintf("BenchPlayer%d", i), true)
testGroup.AddMember(entity, false)
testGroup.Disband() // Clean up background goroutine
}
})
b.Run("GetSize", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.GetSize()
}
})
})
b.Run("GetMembers", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = group.GetMembers()
}
})
b.Run("GetLeaderName", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.GetLeaderName()
}
})
})
b.Run("UpdateGroupMemberInfo", func(b *testing.B) {
members := group.GetMembers()
if len(members) == 0 {
b.Skip("No members to update")
}
for i := 0; i < b.N; i++ {
member := members[i%len(members)]
if member.Member != nil {
group.UpdateGroupMemberInfo(member.Member, false)
}
}
})
}
// BenchmarkGroupOptions measures group options performance
func BenchmarkGroupOptions(b *testing.B) {
group := createTestGroup(b, 1001, 3)
defer group.Disband() // Clean up background goroutine
b.Run("GetGroupOptions", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.GetGroupOptions()
}
})
})
b.Run("SetGroupOptions", func(b *testing.B) {
options := DefaultGroupOptions()
for i := 0; i < b.N; i++ {
options.LootMethod = int8(i % 4)
group.SetGroupOptions(&options)
}
})
b.Run("GetLastLooterIndex", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.GetLastLooterIndex()
}
})
})
b.Run("SetNextLooterIndex", func(b *testing.B) {
for i := 0; i < b.N; i++ {
group.SetNextLooterIndex(int8(i % 6))
}
})
}
// BenchmarkGroupRaidOperations measures raid functionality performance
func BenchmarkGroupRaidOperations(b *testing.B) {
group := createTestGroup(b, 1001, 6)
defer group.Disband() // Clean up background goroutine
// Setup some raid groups
raidGroups := []int32{1001, 1002, 1003, 1004}
group.ReplaceRaidGroups(raidGroups)
b.Run("GetRaidGroups", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.GetRaidGroups()
}
})
})
b.Run("IsGroupRaid", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.IsGroupRaid()
}
})
})
b.Run("IsInRaidGroup", func(b *testing.B) {
for i := 0; i < b.N; i++ {
targetID := int32((i % 4) + 1001)
_ = group.IsInRaidGroup(targetID, false)
}
})
b.Run("AddGroupToRaid", func(b *testing.B) {
for i := 0; i < b.N; i++ {
// Cycle through a limited set of group IDs to avoid infinite growth
groupID := int32(2000 + (i % 10))
group.AddGroupToRaid(groupID)
}
})
b.Run("ReplaceRaidGroups", func(b *testing.B) {
newGroups := []int32{2001, 2002, 2003}
for i := 0; i < b.N; i++ {
group.ReplaceRaidGroups(newGroups)
}
})
b.Run("ClearGroupRaid", func(b *testing.B) {
for i := 0; i < b.N; i++ {
group.ClearGroupRaid()
// Re-add groups for next iteration
group.ReplaceRaidGroups(raidGroups)
}
})
}
// BenchmarkGroupMessaging measures messaging performance
func BenchmarkGroupMessaging(b *testing.B) {
group := createTestGroup(b, 1001, 6)
defer group.Disband() // Clean up background goroutine
b.Run("SimpleGroupMessage", func(b *testing.B) {
for i := 0; i < b.N; i++ {
group.SimpleGroupMessage(fmt.Sprintf("Benchmark message %d", i))
}
})
b.Run("SendGroupMessage", func(b *testing.B) {
for i := 0; i < b.N; i++ {
group.SendGroupMessage(GROUP_MESSAGE_TYPE_SYSTEM, fmt.Sprintf("System message %d", i))
}
})
b.Run("GroupChatMessageFromName", func(b *testing.B) {
for i := 0; i < b.N; i++ {
group.GroupChatMessageFromName(
fmt.Sprintf("Player%d", i%6+1),
0,
fmt.Sprintf("Chat message %d", i),
CHANNEL_GROUP_CHAT,
)
}
})
}
// BenchmarkGroupState measures group state operations
func BenchmarkGroupState(b *testing.B) {
group := createTestGroup(b, 1001, 6)
defer group.Disband() // Clean up background goroutine
b.Run("GetID", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.GetID()
}
})
})
b.Run("IsDisbanded", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.IsDisbanded()
}
})
})
b.Run("GetCreatedTime", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.GetCreatedTime()
}
})
})
b.Run("GetLastActivity", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = group.GetLastActivity()
}
})
})
}
// BenchmarkMasterListOperations measures master list performance
func BenchmarkMasterListOperations(b *testing.B) {
ml := NewMasterList()
// Pre-populate with groups
const numGroups = 1000
groups := make([]*Group, numGroups)
b.StopTimer()
for i := 0; i < numGroups; i++ {
groups[i] = createTestGroup(b, int32(i+1), rand.Intn(6)+1)
ml.AddGroup(groups[i])
}
// Cleanup all groups when benchmark is done
defer func() {
for _, group := range groups {
if group != nil {
group.Disband()
}
}
}()
b.StartTimer()
b.Run("GetGroup", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
id := int32(rand.Intn(numGroups) + 1)
_ = ml.GetGroup(id)
}
})
})
b.Run("AddGroup", func(b *testing.B) {
startID := int32(numGroups + 1)
var addedGroups []*Group
b.ResetTimer()
for i := 0; i < b.N; i++ {
group := createTestGroup(b, startID+int32(i), 3)
ml.AddGroup(group)
addedGroups = append(addedGroups, group)
}
// Cleanup added groups
for _, group := range addedGroups {
group.Disband()
}
})
b.Run("GetAllGroups", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ml.GetAllGroups()
}
})
b.Run("GetActiveGroups", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ml.GetActiveGroups()
}
})
b.Run("GetGroupsByZone", func(b *testing.B) {
for i := 0; i < b.N; i++ {
zoneID := int32(rand.Intn(100) + 1)
_ = ml.GetGroupsByZone(zoneID)
}
})
b.Run("GetGroupsBySize", func(b *testing.B) {
for i := 0; i < b.N; i++ {
size := int32(rand.Intn(6) + 1)
_ = ml.GetGroupsBySize(size)
}
})
b.Run("GetRaidGroups", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ml.GetRaidGroups()
}
})
b.Run("GetGroupStatistics", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ml.GetGroupStatistics()
}
})
}
// BenchmarkManagerOperations measures manager performance
func BenchmarkManagerOperations(b *testing.B) {
config := GroupManagerConfig{
MaxGroups: 1000,
InviteTimeout: 30 * time.Second,
UpdateInterval: 1 * time.Second,
BuffUpdateInterval: 5 * time.Second,
EnableStatistics: true,
}
manager := NewManager(config, nil)
// Pre-populate with groups
b.StopTimer()
for i := 0; i < 100; i++ {
leader := createTestEntity(int32(i+1), fmt.Sprintf("Leader%d", i+1), true)
manager.NewGroup(leader, nil, 0)
}
b.StartTimer()
b.Run("NewGroup", func(b *testing.B) {
startID := int32(1000)
for i := 0; i < b.N; i++ {
leader := createTestEntity(startID+int32(i), fmt.Sprintf("BenchLeader%d", i), true)
_, err := manager.NewGroup(leader, nil, 0)
if err != nil {
b.Fatalf("Failed to create group: %v", err)
}
}
})
b.Run("GetGroup", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
groupID := int32(rand.Intn(100) + 1)
_ = manager.GetGroup(groupID)
}
})
})
b.Run("IsGroupIDValid", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
groupID := int32(rand.Intn(100) + 1)
_ = manager.IsGroupIDValid(groupID)
}
})
})
b.Run("GetGroupCount", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = manager.GetGroupCount()
}
})
})
b.Run("GetAllGroups", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = manager.GetAllGroups()
}
})
b.Run("GetStats", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = manager.GetStats()
}
})
})
}
// BenchmarkInviteSystem measures invitation system performance
func BenchmarkInviteSystem(b *testing.B) {
config := GroupManagerConfig{
InviteTimeout: 30 * time.Second,
EnableStatistics: true,
}
manager := NewManager(config, nil)
b.Run("AddInvite", func(b *testing.B) {
for i := 0; i < b.N; i++ {
leader := createTestEntity(int32(i+1), fmt.Sprintf("Leader%d", i+1), true)
member := createTestEntity(int32(i+1001), fmt.Sprintf("Member%d", i+1), true)
manager.AddInvite(leader, member)
}
})
b.Run("HasPendingInvite", func(b *testing.B) {
// Add some invites first
for i := 0; i < 100; i++ {
leader := createTestEntity(int32(i+1), fmt.Sprintf("TestLeader%d", i+1), true)
member := createTestEntity(int32(i+2001), fmt.Sprintf("TestMember%d", i+1), true)
manager.AddInvite(leader, member)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
member := createTestEntity(int32(rand.Intn(100)+2001), fmt.Sprintf("TestMember%d", rand.Intn(100)+1), true)
_ = manager.HasPendingInvite(member)
}
})
})
b.Run("Invite", func(b *testing.B) {
for i := 0; i < b.N; i++ {
leader := createTestEntity(int32(i+3001), fmt.Sprintf("InviteLeader%d", i+1), true)
member := createTestEntity(int32(i+4001), fmt.Sprintf("InviteMember%d", i+1), true)
_ = manager.Invite(leader, member)
}
})
b.Run("DeclineInvite", func(b *testing.B) {
// Add invites to decline
for i := 0; i < b.N; i++ {
leader := createTestEntity(int32(i+5001), fmt.Sprintf("DeclineLeader%d", i+1), true)
member := createTestEntity(int32(i+6001), fmt.Sprintf("DeclineMember%d", i+1), true)
manager.AddInvite(leader, member)
manager.DeclineInvite(member)
}
})
}
// BenchmarkConcurrentOperations measures concurrent access performance
func BenchmarkConcurrentOperations(b *testing.B) {
config := GroupManagerConfig{
MaxGroups: 1000,
EnableStatistics: true,
}
manager := NewManager(config, nil)
// Pre-populate
for i := 0; i < 50; i++ {
leader := createTestEntity(int32(i+1), fmt.Sprintf("ConcurrentLeader%d", i+1), true)
manager.NewGroup(leader, nil, 0)
}
b.Run("ConcurrentGroupAccess", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
groupID := int32(rand.Intn(50) + 1)
switch rand.Intn(4) {
case 0:
_ = manager.GetGroup(groupID)
case 1:
_ = manager.GetGroupSize(groupID)
case 2:
_ = manager.IsGroupIDValid(groupID)
case 3:
member := createTestEntity(int32(rand.Intn(1000)+10000), "ConcurrentMember", true)
manager.AddGroupMember(groupID, member, false)
}
}
})
})
b.Run("ConcurrentInviteOperations", func(b *testing.B) {
var counter int64
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
i := atomic.AddInt64(&counter, 1)
leader := createTestEntity(int32(i+20000), fmt.Sprintf("ConcurrentInviteLeader%d", i), true)
member := createTestEntity(int32(i+30000), fmt.Sprintf("ConcurrentInviteMember%d", i), true)
switch rand.Intn(3) {
case 0:
manager.AddInvite(leader, member)
case 1:
_ = manager.HasPendingInvite(member)
case 2:
manager.DeclineInvite(member)
}
}
})
})
}
// BenchmarkMemoryAllocation measures memory allocation patterns
func BenchmarkMemoryAllocation(b *testing.B) {
b.Run("GroupAllocation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
group := NewGroup(int32(i+1), nil, nil)
group.Disband() // Clean up background goroutine
}
})
b.Run("MasterListAllocation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
ml := NewMasterList()
_ = ml
}
})
b.Run("ManagerAllocation", func(b *testing.B) {
config := GroupManagerConfig{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
manager := NewManager(config, nil)
_ = manager
}
})
b.Run("GroupMemberInfoAllocation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
gmi := &GroupMemberInfo{
GroupID: int32(i + 1),
Name: fmt.Sprintf("Member%d", i+1),
Zone: "TestZone",
HPCurrent: 1000,
HPMax: 1000,
PowerCurrent: 500,
PowerMax: 500,
LevelCurrent: 50,
LevelMax: 80,
RaceID: 1,
ClassID: 1,
Leader: false,
IsClient: true,
JoinTime: time.Now(),
LastUpdate: time.Now(),
}
_ = gmi
}
})
}
// BenchmarkComparisonWithOldSystem provides comparison benchmarks
func BenchmarkComparisonWithOldSystem(b *testing.B) {
ml := NewMasterList()
const numGroups = 1000
// Setup
b.StopTimer()
groups := make([]*Group, numGroups)
for i := 0; i < numGroups; i++ {
groups[i] = createTestGroup(b, int32(i+1), rand.Intn(6)+1)
ml.AddGroup(groups[i])
}
// Cleanup all groups when benchmark is done
defer func() {
for _, group := range groups {
if group != nil {
group.Disband()
}
}
}()
b.StartTimer()
b.Run("ModernizedGroupLookup", func(b *testing.B) {
// Modern generic-based lookup
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
id := int32(rand.Intn(numGroups) + 1)
_ = ml.GetGroup(id)
}
})
})
b.Run("ModernizedGroupFiltering", func(b *testing.B) {
// Modern filter-based operations
for i := 0; i < b.N; i++ {
_ = ml.GetActiveGroups()
}
})
b.Run("ModernizedGroupStatistics", func(b *testing.B) {
// Modern statistics computation
for i := 0; i < b.N; i++ {
_ = ml.GetGroupStatistics()
}
})
}