Skip to content

Commit 7b09b19

Browse files
added agent load testing feature (#549)
* added agent load testing feature * added nanpa changeset * fix build errors * fix(agentloadtestting): fixed false agentTrackSubscribed assignment * feat(loadtesting): added alias for loadtesting commands * feat(loadtesting): updated agents loadtesting readme * Revert "feat(loadtesting): added alias for loadtesting commands" This reverts commit cade835.
1 parent 4306349 commit 7b09b19

File tree

6 files changed

+518
-0
lines changed

6 files changed

+518
-0
lines changed

.nanpa/agent-load-testing.kdl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
minor type="added" "Added agent load testing feature"

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,32 @@ You can customize various parameters of the test such as
333333
- `--layout`: layout to simulate (speaker, 3x3, 4x4, or 5x5)
334334
- `--simulate-speakers`: randomly rotate publishers to speak
335335

336+
### Agent Load Testing
337+
338+
The agent load testing utility allows you to dispatch a running agent to a number of rooms and simulate a user in each room that would echo whatever the agent says.
339+
340+
> **Note**: Before running the test, ensure that:
341+
> - Your agent is already running using `start` instead of `dev` with the specified `agent_name` configured
342+
> - The agent is configured to speak something first (e.g., a simple greeting)
343+
344+
To start an agent load test:
345+
346+
```shell
347+
lk agent-load-test \
348+
--rooms 5 \
349+
--agent-name test-agent \
350+
--echo-speech-delay 10s \
351+
--duration 5m
352+
```
353+
354+
The above simulates 5 concurrent rooms, where each room has:
355+
- Your agent `test-agent` dispatched
356+
- An echo participant that receives and plays back the agent's audio
357+
- A 10-second delay in the echo response from the agent speech
358+
- The test runs for 5 minutes before automatically stopping
359+
360+
Once the specified duration is over (or if the load test is manually stopped), the load test statistics will be displayed in the form of a table.
361+
336362
<!--BEGIN_REPO_NAV-->
337363
<br/><table>
338364
<thead><tr><th colspan="2">LiveKit Ecosystem</th></tr></thead>

cmd/lk/agent_loadtest.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2021-2024 LiveKit, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"context"
19+
"log"
20+
"time"
21+
22+
"github.com/go-logr/logr"
23+
"github.com/urfave/cli/v3"
24+
25+
"github.com/livekit/livekit-cli/v2/pkg/loadtester"
26+
"github.com/livekit/protocol/logger"
27+
lksdk "github.com/livekit/server-sdk-go/v2"
28+
)
29+
30+
var AgentLoadTestCommands = []*cli.Command{
31+
{
32+
Name: "agent-load-test",
33+
Usage: "Run load tests for a running agent",
34+
Action: agentLoadTest,
35+
Flags: []cli.Flag{
36+
&cli.IntFlag{
37+
Name: "rooms",
38+
Usage: "`NUMBER` of rooms to open",
39+
},
40+
&cli.StringFlag{
41+
Name: "agent-name",
42+
Usage: "name of the running agent to dispatch to the rooom",
43+
},
44+
&cli.DurationFlag{
45+
Name: "echo-speech-delay",
46+
Usage: "delay between when the echo track speaks and when the agent starts speaking (e.g. 5s, 1m)",
47+
Value: 5 * time.Second,
48+
},
49+
&cli.DurationFlag{
50+
Name: "duration",
51+
Usage: "`TIME` duration to run, 1m, 1h (by default will run until canceled)",
52+
Value: 0,
53+
},
54+
},
55+
},
56+
}
57+
58+
func agentLoadTest(ctx context.Context, cmd *cli.Command) error {
59+
pc, err := loadProjectDetails(cmd)
60+
if err != nil {
61+
return err
62+
}
63+
64+
if !cmd.Bool("verbose") {
65+
lksdk.SetLogger(logger.LogRLogger(logr.Discard()))
66+
}
67+
_ = raiseULimit()
68+
69+
params := loadtester.AgentLoadTestParams{
70+
URL: pc.URL,
71+
APIKey: pc.APIKey,
72+
APISecret: pc.APISecret,
73+
Rooms: int(cmd.Int("rooms")),
74+
AgentName: cmd.String("agent-name"),
75+
EchoSpeechDelay: cmd.Duration("echo-speech-delay"),
76+
Duration: cmd.Duration("duration"),
77+
}
78+
79+
test := loadtester.NewAgentLoadTest(params)
80+
81+
err = test.Run(ctx, params)
82+
if err != nil {
83+
log.Printf("Agent load test failed: %v", err)
84+
return err
85+
}
86+
return nil
87+
}

cmd/lk/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func main() {
7070
app.Commands = append(app.Commands, SIPCommands...)
7171
app.Commands = append(app.Commands, ReplayCommands...)
7272
app.Commands = append(app.Commands, LoadTestCommands...)
73+
app.Commands = append(app.Commands, AgentLoadTestCommands...)
7374

7475
// Register cleanup hook for SIGINT, SIGTERM, SIGQUIT
7576
ctx, stop := signal.NotifyContext(

pkg/loadtester/agentloadtest.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2021-2024 LiveKit, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package loadtester
16+
17+
import (
18+
"context"
19+
"log"
20+
"time"
21+
)
22+
23+
type AgentLoadTestParams struct {
24+
Rooms int
25+
AgentName string
26+
EchoSpeechDelay time.Duration
27+
Duration time.Duration
28+
URL string
29+
APIKey string
30+
APISecret string
31+
}
32+
33+
type AgentLoadTest struct {
34+
Params AgentLoadTestParams
35+
}
36+
37+
func NewAgentLoadTest(params AgentLoadTestParams) *AgentLoadTest {
38+
l := &AgentLoadTest{
39+
Params: params,
40+
}
41+
return l
42+
}
43+
44+
func (t *AgentLoadTest) Run(ctx context.Context, params AgentLoadTestParams) error {
45+
log.Printf("Starting agent load test with %d rooms", params.Rooms)
46+
agentLoadTester := NewAgentLoadTester(params)
47+
48+
duration := params.Duration
49+
if duration == 0 {
50+
// a really long time
51+
duration = 1000 * time.Hour
52+
}
53+
timeoutCtx, cancel := context.WithTimeout(ctx, duration)
54+
defer cancel()
55+
56+
err := agentLoadTester.Start(timeoutCtx)
57+
if err != nil {
58+
log.Printf("Failed to start agent load tester: %v", err)
59+
return err
60+
}
61+
62+
<-timeoutCtx.Done()
63+
agentLoadTester.Stop()
64+
log.Printf("Test completed or timed out")
65+
return nil
66+
}

0 commit comments

Comments
 (0)