complement/cmd/perftest/test.go
kegsay 765746a8f8
Move /internal/b to /b (#659)
The API is unchanged and is suitable to be made public.
2023-10-04 16:15:25 +01:00

221 lines
7 KiB
Go

package main
import (
"context"
"fmt"
"math/rand"
"net/url"
"time"
"github.com/matrix-org/complement/b"
"github.com/matrix-org/complement/internal/docker"
"github.com/matrix-org/complement/internal/instruction"
)
func withSpan(spanName, desc string, snapshots []Snapshot, absStartTime time.Time, deployment *docker.Deployment, fn func() error) ([]Snapshot, error) {
startTime := time.Now()
if err := fn(); err != nil {
return nil, fmt.Errorf("withSpan %s: %s", spanName, err)
}
duration := time.Since(startTime)
absDuration := time.Since(absStartTime)
snapshots = append(snapshots, snapshotStats(spanName, desc, deployment, absDuration, duration)...)
return snapshots, nil
}
func runTest(testName string, builder *docker.Builder, deployer *docker.Deployer, seed int64) ([]Snapshot, error) {
randSource := rand.NewSource(seed)
rnd := rand.New(randSource)
// deploy a base image
if err := builder.ConstructBlueprintIfNotExist(b.BlueprintCleanHS); err != nil {
return nil, err
}
deployment, err := deployer.Deploy(context.Background(), b.BlueprintCleanHS.Name)
if err != nil {
return nil, err
}
defer deployment.Deployer.Destroy(deployment, false, testName, false)
snapshots := snapshotStats("startup", "clean homeserver with no users", deployment, 0, 0)
runner := instruction.NewRunner(testName, false, true)
absStartTime := time.Now()
numUsers := 10
snapshots, err = withSpan("create_users", fmt.Sprintf("creates %d users concurrently", numUsers), snapshots, absStartTime, deployment, func() error {
// make N users
users := make([]b.User, numUsers)
for i := 0; i < len(users); i++ {
users[i] = b.User{
Localpart: fmt.Sprintf("user-%d", i),
DisplayName: fmt.Sprintf("User %d", i),
}
}
return runner.Run(b.Homeserver{
Name: "hs1",
Users: users,
}, deployment.HS["hs1"].BaseURL)
})
if err != nil {
return nil, err
}
var userRoomJoins [][2]string // list of [user_id, roomRef] of joined users who can send messages
numRooms := 50
snapshots, err = withSpan("create_rooms", fmt.Sprintf("creates %d public rooms with different users", numRooms), snapshots, absStartTime, deployment, func() error {
// make M rooms
rooms := make([]b.Room, numRooms)
for i := 0; i < numRooms; i++ {
userI := int(randSource.Int63() % int64(numUsers))
userID := fmt.Sprintf("@user-%d:hs1", userI)
roomRef := fmt.Sprintf("ref-%d", i)
rooms[i] = b.Room{
Creator: userID,
CreateRoom: map[string]interface{}{
"preset": "public_chat",
},
Ref: roomRef,
}
userRoomJoins = append(userRoomJoins, [2]string{userID, roomRef})
}
return runner.Run(b.Homeserver{
Name: "hs1",
Rooms: rooms,
}, deployment.HS["hs1"].BaseURL)
})
if err != nil {
return nil, err
}
// normal distribution around numRooms/2 to join M rooms, P times
numJoins := 100
stddev := float64(numRooms) / 6.0
mean := (float64(numRooms) / 2.0)
snapshots, err = withSpan("join_rooms", fmt.Sprintf("issues %d /join requests according to a normal distribution (mean=%v,std-dev=%v)", numJoins, mean, stddev), snapshots, absStartTime, deployment, func() error {
var hs b.Homeserver
roomMap := make(map[string]b.Room)
for i := 0; i < numJoins; i++ {
// random user
userI := int(randSource.Int63() % int64(numUsers))
userID := fmt.Sprintf("@user-%d:hs1", userI)
// normal distribution so we get some large rooms
roomI := int(rnd.NormFloat64()*stddev + mean)
ref := fmt.Sprintf("ref-%d", roomI)
room := roomMap[ref]
room.Ref = ref
room.Events = append(room.Events, b.Event{
Type: "m.room.member",
StateKey: &userID,
Sender: userID,
Content: map[string]interface{}{
"membership": "join",
},
})
roomMap[ref] = room
userRoomJoins = append(userRoomJoins, [2]string{userID, ref})
}
for _, room := range roomMap {
hs.Rooms = append(hs.Rooms, room)
}
return runner.Run(hs, deployment.HS["hs1"].BaseURL)
})
if err != nil {
return nil, err
}
numEvents := 100
snapshots, err = withSpan("send_msgs", fmt.Sprintf("sends %d m.room.message text events into random rooms users have joined", numEvents), snapshots, absStartTime, deployment, func() error {
var hs b.Homeserver
roomMap := make(map[string]b.Room)
for i := 0; i < numEvents; i++ {
userAndRoom := userRoomJoins[int(randSource.Int63()%int64(len(userRoomJoins)))]
userID := userAndRoom[0]
ref := userAndRoom[1]
room := roomMap[ref]
room.Ref = ref
room.Events = append(room.Events, b.Event{
Sender: userID,
Type: "m.room.message",
Content: map[string]interface{}{
"body": fmt.Sprintf("message %d", i),
"msgtype": "m.text",
},
})
roomMap[ref] = room
}
for _, room := range roomMap {
hs.Rooms = append(hs.Rooms, room)
}
return runner.Run(hs, deployment.HS["hs1"].BaseURL)
})
if err != nil {
return nil, err
}
runOpts := instruction.RunOpts{
Concurrency: instruction.ConcurrencyTypePerUser,
HSURL: deployment.HS["hs1"].BaseURL,
StoreNamespace: "_syncs",
}
syncInstructions := make([]instruction.Instr, numUsers)
for i := range syncInstructions {
userID := fmt.Sprintf("@user-%d:hs1", i)
syncInstructions[i] = instruction.Instr{
UserID: userID,
Method: "GET",
Path: "/_matrix/client/v3/sync",
Queries: map[string]string{"timeout": "0"},
Store: map[string]string{
userID: ".next_batch",
},
}
}
snapshots, err = withSpan("initial_syncs", fmt.Sprintf("performs /sync with no since token and timeout=0 for all users"), snapshots, absStartTime, deployment, func() error {
return runner.RunInstructions(runOpts, syncInstructions)
})
if err != nil {
return nil, err
}
syncInstructions = make([]instruction.Instr, 0, numUsers)
for i := 0; i < numUsers; i++ {
userID := fmt.Sprintf("@user-%d:hs1", i)
syncInstructions = append(syncInstructions, instruction.Instr{
UserID: userID,
Method: "PUT",
Path: fmt.Sprintf("/_matrix/client/v3/profile/%s/displayname", url.PathEscape(userID)),
Body: map[string]interface{}{
"displayname": fmt.Sprintf("Updated User %d", i),
},
})
}
snapshots, err = withSpan("display_name_change", fmt.Sprintf("updates the displayname of all %d users concurrently", numUsers), snapshots, absStartTime, deployment, func() error {
return runner.RunInstructions(runOpts, syncInstructions)
})
if err != nil {
return nil, err
}
syncInstructions = make([]instruction.Instr, numUsers)
for i := 0; i < numUsers; i++ {
userID := fmt.Sprintf("@user-%d:hs1", i)
syncInstructions[i] = instruction.Instr{
UserID: userID,
Method: "GET",
Path: "/_matrix/client/v3/sync",
Queries: map[string]string{"since": runner.GetStoredValue(runOpts, userID)},
Store: map[string]string{
userID: ".next_batch",
},
}
}
snapshots, err = withSpan("incremental_sync", fmt.Sprintf("performs an incremental sync on all %d users with the since token from the initial sync", numUsers), snapshots, absStartTime, deployment, func() error {
return runner.RunInstructions(runOpts, syncInstructions)
})
if err != nil {
return nil, err
}
return snapshots, nil
}