Skip to content

Commit 29cd704

Browse files
committed
test(gateway): add 1000-agent concurrency benchmarks
1 parent 662fbea commit 29cd704

2 files changed

Lines changed: 498 additions & 0 deletions

File tree

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
package agenttoken
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
"sync"
7+
"sync/atomic"
8+
"testing"
9+
10+
"github.com/liveagent/agent-gateway/internal/db"
11+
)
12+
13+
const benchmarkAgentCount = 1000
14+
15+
func openBenchmarkStore(b *testing.B) *Store {
16+
b.Helper()
17+
database, err := db.Open(filepath.Join(b.TempDir(), "gateway.db"))
18+
if err != nil {
19+
b.Fatalf("open benchmark database: %v", err)
20+
}
21+
b.Cleanup(func() { _ = database.Close() })
22+
store, err := NewStore(database)
23+
if err != nil {
24+
b.Fatalf("open benchmark store: %v", err)
25+
}
26+
return store
27+
}
28+
29+
func benchmarkAgentID(index int) string {
30+
return fmt.Sprintf("agent-00000000-0000-4000-8000-%012x", index)
31+
}
32+
33+
func seedBenchmarkAgents(b *testing.B, store *Store, issueTokens bool) ([]string, []string) {
34+
b.Helper()
35+
ids := make([]string, benchmarkAgentCount)
36+
tokens := make([]string, benchmarkAgentCount)
37+
for index := range ids {
38+
ids[index] = benchmarkAgentID(index)
39+
if issueTokens {
40+
token, err := store.Issue(ids[index], "")
41+
if err != nil {
42+
b.Fatalf("issue benchmark token %d: %v", index, err)
43+
}
44+
tokens[index] = token
45+
continue
46+
}
47+
if err := store.Register(ids[index]); err != nil {
48+
b.Fatalf("register benchmark agent %d: %v", index, err)
49+
}
50+
}
51+
return ids, tokens
52+
}
53+
54+
func BenchmarkOpenStorePreloads1000Agents(b *testing.B) {
55+
path := filepath.Join(b.TempDir(), "gateway.db")
56+
database, err := db.Open(path)
57+
if err != nil {
58+
b.Fatalf("open seed database: %v", err)
59+
}
60+
store, err := NewStore(database)
61+
if err != nil {
62+
_ = database.Close()
63+
b.Fatalf("open seed store: %v", err)
64+
}
65+
ids, _ := seedBenchmarkAgents(b, store, true)
66+
if err := database.Close(); err != nil {
67+
b.Fatalf("close seed database: %v", err)
68+
}
69+
70+
b.ReportAllocs()
71+
b.ResetTimer()
72+
for range b.N {
73+
database, err := db.Open(path)
74+
if err != nil {
75+
b.Fatalf("reopen benchmark database: %v", err)
76+
}
77+
store, err := NewStore(database)
78+
if err != nil {
79+
_ = database.Close()
80+
b.Fatalf("reopen benchmark store: %v", err)
81+
}
82+
if _, ok := store.knownAgents.Load(ids[0]); !ok {
83+
_ = database.Close()
84+
b.Fatal("first agent was not preloaded")
85+
}
86+
if _, ok := store.knownAgents.Load(ids[len(ids)-1]); !ok {
87+
_ = database.Close()
88+
b.Fatal("last agent was not preloaded")
89+
}
90+
if err := database.Close(); err != nil {
91+
b.Fatalf("close benchmark database: %v", err)
92+
}
93+
}
94+
}
95+
96+
func BenchmarkAuthenticateAndRegisterSharedTokenCached(b *testing.B) {
97+
store := openBenchmarkStore(b)
98+
ids, _ := seedBenchmarkAgents(b, store, false)
99+
100+
b.ReportAllocs()
101+
b.ResetTimer()
102+
for index := 0; index < b.N; index++ {
103+
if _, err := store.AuthenticateAndRegister(ids[index%len(ids)], "", true); err != nil {
104+
b.Fatalf("authenticate shared token agent: %v", err)
105+
}
106+
}
107+
}
108+
109+
func BenchmarkAuthenticateAndRegisterAgentToken(b *testing.B) {
110+
store := openBenchmarkStore(b)
111+
ids, tokens := seedBenchmarkAgents(b, store, true)
112+
113+
b.ReportAllocs()
114+
b.ResetTimer()
115+
for index := 0; index < b.N; index++ {
116+
agentIndex := index % len(ids)
117+
if _, err := store.AuthenticateAndRegister(ids[agentIndex], tokens[agentIndex], false); err != nil {
118+
b.Fatalf("authenticate independent agent token: %v", err)
119+
}
120+
}
121+
}
122+
123+
func BenchmarkAuthenticateAndRegisterAgentTokenParallel(b *testing.B) {
124+
store := openBenchmarkStore(b)
125+
ids, tokens := seedBenchmarkAgents(b, store, true)
126+
var sequence atomic.Uint64
127+
128+
b.ReportAllocs()
129+
b.ResetTimer()
130+
b.RunParallel(func(pb *testing.PB) {
131+
for pb.Next() {
132+
agentIndex := int(sequence.Add(1)-1) % len(ids)
133+
if _, err := store.AuthenticateAndRegister(ids[agentIndex], tokens[agentIndex], false); err != nil {
134+
b.Errorf("authenticate independent agent token: %v", err)
135+
return
136+
}
137+
}
138+
})
139+
}
140+
141+
func benchmarkAuthenticate1000AgentsConcurrent(b *testing.B, sharedAuthenticated bool) {
142+
store := openBenchmarkStore(b)
143+
ids, tokens := seedBenchmarkAgents(b, store, !sharedAuthenticated)
144+
145+
b.ReportAllocs()
146+
b.ResetTimer()
147+
for range b.N {
148+
b.StopTimer()
149+
start := make(chan struct{})
150+
errs := make([]error, len(ids))
151+
var ready sync.WaitGroup
152+
var done sync.WaitGroup
153+
ready.Add(len(ids))
154+
done.Add(len(ids))
155+
for index := range ids {
156+
go func() {
157+
defer done.Done()
158+
ready.Done()
159+
<-start
160+
_, errs[index] = store.AuthenticateAndRegister(
161+
ids[index], tokens[index], sharedAuthenticated,
162+
)
163+
}()
164+
}
165+
ready.Wait()
166+
b.StartTimer()
167+
close(start)
168+
done.Wait()
169+
b.StopTimer()
170+
171+
for index, err := range errs {
172+
if err != nil {
173+
b.Fatalf("authenticate concurrent agent %d: %v", index, err)
174+
}
175+
}
176+
}
177+
elapsed := b.Elapsed()
178+
b.ReportMetric(
179+
float64(b.N*len(ids))/elapsed.Seconds(),
180+
"agents/s",
181+
)
182+
b.ReportMetric(
183+
float64(elapsed.Nanoseconds())/float64(b.N*len(ids)),
184+
"ns/agent",
185+
)
186+
}
187+
188+
func BenchmarkAuthenticateAndRegister1000SharedTokenAgentsConcurrent(b *testing.B) {
189+
benchmarkAuthenticate1000AgentsConcurrent(b, true)
190+
}
191+
192+
func BenchmarkAuthenticateAndRegister1000AgentTokensConcurrent(b *testing.B) {
193+
benchmarkAuthenticate1000AgentsConcurrent(b, false)
194+
}
195+
196+
func BenchmarkRegister1000SharedTokenAgentsConcurrent(b *testing.B) {
197+
ids := make([]string, benchmarkAgentCount)
198+
for index := range ids {
199+
ids[index] = benchmarkAgentID(index)
200+
}
201+
tempDir := b.TempDir()
202+
203+
b.ReportAllocs()
204+
b.ResetTimer()
205+
for iteration := range b.N {
206+
b.StopTimer()
207+
database, err := db.Open(filepath.Join(tempDir, fmt.Sprintf("gateway-%d.db", iteration)))
208+
if err != nil {
209+
b.Fatalf("open first-registration benchmark database: %v", err)
210+
}
211+
store, err := NewStore(database)
212+
if err != nil {
213+
_ = database.Close()
214+
b.Fatalf("open first-registration benchmark store: %v", err)
215+
}
216+
217+
start := make(chan struct{})
218+
errs := make([]error, len(ids))
219+
var ready sync.WaitGroup
220+
var done sync.WaitGroup
221+
ready.Add(len(ids))
222+
done.Add(len(ids))
223+
for index := range ids {
224+
go func() {
225+
defer done.Done()
226+
ready.Done()
227+
<-start
228+
_, errs[index] = store.AuthenticateAndRegister(ids[index], "", true)
229+
}()
230+
}
231+
ready.Wait()
232+
b.StartTimer()
233+
close(start)
234+
done.Wait()
235+
b.StopTimer()
236+
237+
for index, authErr := range errs {
238+
if authErr != nil {
239+
_ = database.Close()
240+
b.Fatalf("register concurrent shared-token agent %d: %v", index, authErr)
241+
}
242+
}
243+
if err := database.Close(); err != nil {
244+
b.Fatalf("close first-registration benchmark database: %v", err)
245+
}
246+
}
247+
elapsed := b.Elapsed()
248+
b.ReportMetric(float64(b.N*len(ids))/elapsed.Seconds(), "agents/s")
249+
b.ReportMetric(
250+
float64(elapsed.Nanoseconds())/float64(b.N*len(ids)),
251+
"ns/agent",
252+
)
253+
}
254+
255+
func BenchmarkList1000Agents(b *testing.B) {
256+
store := openBenchmarkStore(b)
257+
ids, _ := seedBenchmarkAgents(b, store, true)
258+
onlineIDs := make([]string, 0, benchmarkAgentCount/2)
259+
for index := 0; index < len(ids); index += 2 {
260+
onlineIDs = append(onlineIDs, ids[index])
261+
}
262+
263+
benchmarks := []struct {
264+
name string
265+
params PageParams
266+
}{
267+
{name: "all_page_50", params: PageParams{Page: 1, PageSize: 50}},
268+
{
269+
name: "online_page_25",
270+
params: PageParams{
271+
Page: 1,
272+
PageSize: 25,
273+
Status: StatusOnline,
274+
OnlineAgentIDs: onlineIDs,
275+
},
276+
},
277+
{
278+
name: "offline_page_25",
279+
params: PageParams{
280+
Page: 1,
281+
PageSize: 25,
282+
Status: StatusOffline,
283+
OnlineAgentIDs: onlineIDs,
284+
},
285+
},
286+
}
287+
288+
for _, benchmark := range benchmarks {
289+
b.Run(benchmark.name, func(b *testing.B) {
290+
b.ReportAllocs()
291+
for range b.N {
292+
page, err := store.List(benchmark.params)
293+
if err != nil {
294+
b.Fatalf("list benchmark agents: %v", err)
295+
}
296+
if len(page.Entries) != benchmark.params.PageSize {
297+
b.Fatalf("listed %d entries, want %d", len(page.Entries), benchmark.params.PageSize)
298+
}
299+
}
300+
})
301+
}
302+
}

0 commit comments

Comments
 (0)