feat(sessions): Add list benchmarks
The list benchmark tests uses pre-created database dumps to run its benchmarks. This benchmark will be used to guide optimizations around listing of sessions. Fixes https://hashicorp.atlassian.net/browse/ICU-4042
This commit is contained in:
1 parent
5d042c7ec7
commit
e864652b33
4 files changed
+243
-4
No files matched your search
@@ -239,6 +239,19 @@ test-api:
|
||||
.PHONY: test-all
|
||||
test-all: test-sdk test-api test
|
||||
|
||||
BENCH_TIME?=1s
|
||||
BENCH_COUNT?=1s
|
||||
.PHONY: benchmark
|
||||
benchmark:
|
||||
@go test \
|
||||
-timeout 60m \
|
||||
./internal/servers/controller/handlers/sessions/ \
|
||||
-v \
|
||||
-bench '^BenchmarkSessionList$$' \
|
||||
-benchtime $(BENCH_TIME) \
|
||||
-count $(BENCH_COUNT) \
|
||||
-run '^$$'
|
||||
|
||||
.PHONY: install-go
|
||||
install-go:
|
||||
./ci/goinstall.sh
|
||||
|
||||
@@ -85,7 +85,10 @@ require (
|
||||
|
||||
require github.com/hashicorp/go-dbw v0.0.0-20211215222256-2ff0d37184ff // this is a branch and should be updated before merging
|
||||
|
||||
require github.com/prometheus/client_golang v1.12.1
|
||||
require (
|
||||
github.com/prometheus/client_golang v1.12.1
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||
@@ -177,7 +180,6 @@ require (
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
package sessions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers/sessions"
|
||||
"github.com/hashicorp/boundary/internal/session"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/boundary/testing/dbtest"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type template struct {
|
||||
name string
|
||||
sessions int
|
||||
connsPerSession int
|
||||
users int
|
||||
}
|
||||
|
||||
func BenchmarkSessionList(b *testing.B) {
|
||||
// See the explanation in testing/dbtest/session_list_benchmarks_dump_generation_test.go for
|
||||
// an overview of the assumptions made when creating these template scenarios.
|
||||
for _, template := range []template{
|
||||
{
|
||||
name: dbtest.Boundary1000Sessions10ConnsPerSession10Template,
|
||||
sessions: 1000,
|
||||
connsPerSession: 10,
|
||||
users: 10,
|
||||
},
|
||||
{
|
||||
name: dbtest.Boundary1000Sessions10ConnsPerSession25Template,
|
||||
sessions: 1000,
|
||||
connsPerSession: 10,
|
||||
users: 25,
|
||||
},
|
||||
{
|
||||
name: dbtest.Boundary1000Sessions10ConnsPerSession50Template,
|
||||
sessions: 1000,
|
||||
connsPerSession: 10,
|
||||
users: 50,
|
||||
},
|
||||
{
|
||||
name: dbtest.Boundary1000Sessions10ConnsPerSession75Template,
|
||||
sessions: 1000,
|
||||
connsPerSession: 10,
|
||||
users: 75,
|
||||
},
|
||||
{
|
||||
name: dbtest.Boundary1000Sessions10ConnsPerSession100Template,
|
||||
sessions: 1000,
|
||||
connsPerSession: 10,
|
||||
users: 100,
|
||||
},
|
||||
{
|
||||
name: dbtest.Boundary1000Sessions10ConnsPerSession500Template,
|
||||
sessions: 1000,
|
||||
connsPerSession: 10,
|
||||
users: 500,
|
||||
},
|
||||
} {
|
||||
b.Run(fmt.Sprintf("%d_sessions_%d_conns_per_session_%d_users", template.sessions, template.connsPerSession, template.users), func(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(b, "postgres", db.WithTemplate(template.name))
|
||||
rw := db.New(conn)
|
||||
kmsRepo, err := kms.NewRepository(rw, rw)
|
||||
require.NoError(b, err)
|
||||
kmsThing, err := kms.NewKms(kmsRepo)
|
||||
require.NoError(b, err)
|
||||
wrap, err := dbtest.GetBoundaryBenchmarksRootKeyWrapper(ctx)
|
||||
require.NoError(b, err)
|
||||
err = kmsThing.AddExternalWrappers(ctx, kms.WithRootWrapper(wrap))
|
||||
require.NoError(b, err)
|
||||
|
||||
iamRepo, err := iam.NewRepository(rw, rw, kmsThing)
|
||||
require.NoError(b, err)
|
||||
|
||||
sessRepo, err := session.NewRepository(rw, rw, kmsThing)
|
||||
require.NoError(b, err)
|
||||
|
||||
authTokenRepo, err := authtoken.NewRepository(rw, rw, kmsThing)
|
||||
require.NoError(b, err)
|
||||
|
||||
pwRepo, err := password.NewRepository(rw, rw, kmsThing)
|
||||
require.NoError(b, err)
|
||||
|
||||
serversRepo, err := servers.NewRepository(rw, rw, kmsThing)
|
||||
require.NoError(b, err)
|
||||
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
return iamRepo, nil
|
||||
}
|
||||
sessRepoFn := func() (*session.Repository, error) {
|
||||
return sessRepo, nil
|
||||
}
|
||||
authTokenRepoFn := func() (*authtoken.Repository, error) {
|
||||
return authTokenRepo, nil
|
||||
}
|
||||
serversRepoFn := func() (*servers.Repository, error) {
|
||||
return serversRepo, nil
|
||||
}
|
||||
|
||||
s, err := sessions.NewService(sessRepoFn, iamRepoFn)
|
||||
require.NoError(b, err)
|
||||
|
||||
var users []*userWithToken
|
||||
rows, err := rw.Query(ctx, "select public_id from iam_user where name like 'user%'", nil)
|
||||
require.NoError(b, err)
|
||||
var userId string
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&userId)
|
||||
require.NoError(b, err)
|
||||
require.NotEmpty(b, userId)
|
||||
u, accountIds, err := iamRepo.LookupUser(ctx, userId)
|
||||
require.NoError(b, err)
|
||||
require.NotEmpty(b, accountIds)
|
||||
account, err := pwRepo.LookupAccount(ctx, accountIds[0])
|
||||
require.NoError(b, err)
|
||||
require.NotNil(b, account)
|
||||
acct, err := pwRepo.Authenticate(ctx, u.ScopeId, account.AuthMethodId, u.Name, dbtest.BoundaryBenchmarksUserPassword)
|
||||
require.NoError(b, err)
|
||||
require.NotNil(b, acct)
|
||||
tok, err := authTokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
||||
require.NoError(b, err)
|
||||
require.NotEmpty(b, tok)
|
||||
tokString, err := authtoken.EncryptToken(ctx, kmsThing, u.ScopeId, tok.PublicId, tok.Token)
|
||||
require.NoError(b, err)
|
||||
req := &pbs.ListSessionsRequest{
|
||||
ScopeId: scope.Global.String(),
|
||||
Recursive: true,
|
||||
Filter: fmt.Sprintf(`"/item/user_id"==%q`, u.PublicId),
|
||||
}
|
||||
ctx := auth.NewVerifierContext(
|
||||
ctx,
|
||||
iamRepoFn,
|
||||
authTokenRepoFn,
|
||||
serversRepoFn,
|
||||
kmsThing,
|
||||
&authpb.RequestInfo{
|
||||
PublicId: tok.PublicId,
|
||||
EncryptedToken: tokString,
|
||||
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
||||
})
|
||||
users = append(users, &userWithToken{
|
||||
User: u,
|
||||
req: req,
|
||||
ctx: ctx,
|
||||
})
|
||||
}
|
||||
require.NoError(b, rows.Close())
|
||||
require.NoError(b, rows.Err())
|
||||
require.Len(b, users, template.users)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
eg := &errgroup.Group{}
|
||||
for i := range users {
|
||||
user := users[i]
|
||||
eg.Go(func() error {
|
||||
_, err := s.ListSessions(user.ctx, user.req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list failed: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
require.NoError(b, eg.Wait())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type userWithToken struct {
|
||||
*iam.User
|
||||
req *pbs.ListSessionsRequest
|
||||
ctx context.Context
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package dbtest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
@@ -9,6 +11,8 @@ import (
|
||||
// postgres dialect
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db/common"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/aead"
|
||||
_ "github.com/jackc/pgx/v4/stdlib"
|
||||
)
|
||||
|
||||
@@ -31,10 +35,20 @@ const (
|
||||
Postgres = "postgres"
|
||||
)
|
||||
|
||||
// BoundaryBenchmarksUserPassword is the password used for all users
|
||||
// created by the boundary benchmarks dump generator.
|
||||
const BoundaryBenchmarksUserPassword = "testpassword"
|
||||
|
||||
// Template Database Names
|
||||
const (
|
||||
BoundaryTemplate = "boundary_template"
|
||||
Template1 = "template1"
|
||||
BoundaryTemplate = "boundary_template"
|
||||
Template1 = "template1"
|
||||
Boundary1000Sessions10ConnsPerSession10Template = "boundary_session_1000_10_10_template"
|
||||
Boundary1000Sessions10ConnsPerSession25Template = "boundary_session_1000_10_25_template"
|
||||
Boundary1000Sessions10ConnsPerSession50Template = "boundary_session_1000_10_50_template"
|
||||
Boundary1000Sessions10ConnsPerSession75Template = "boundary_session_1000_10_75_template"
|
||||
Boundary1000Sessions10ConnsPerSession100Template = "boundary_session_1000_10_100_template"
|
||||
Boundary1000Sessions10ConnsPerSession500Template = "boundary_session_1000_10_500_template"
|
||||
)
|
||||
|
||||
var supportedDialects = map[string]struct{}{
|
||||
@@ -44,6 +58,12 @@ var supportedDialects = map[string]struct{}{
|
||||
var supportedTemplates = map[string]struct{}{
|
||||
BoundaryTemplate: {},
|
||||
Template1: {},
|
||||
Boundary1000Sessions10ConnsPerSession10Template: {},
|
||||
Boundary1000Sessions10ConnsPerSession25Template: {},
|
||||
Boundary1000Sessions10ConnsPerSession50Template: {},
|
||||
Boundary1000Sessions10ConnsPerSession75Template: {},
|
||||
Boundary1000Sessions10ConnsPerSession100Template: {},
|
||||
Boundary1000Sessions10ConnsPerSession500Template: {},
|
||||
}
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyz"
|
||||
@@ -53,6 +73,22 @@ const (
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
)
|
||||
|
||||
const boundaryBenchmarksRootKey = "rootkey1rootkey1rootkey1rootkey1" // 32 bytes for AES-256
|
||||
|
||||
// GetBoundaryBenchmarksRootKeyWrapper returns the root key wrapper used for the benchmark dumps.
|
||||
func GetBoundaryBenchmarksRootKeyWrapper(ctx context.Context) (wrapping.Wrapper, error) {
|
||||
wrap := aead.NewWrapper()
|
||||
_, err := wrap.SetConfig(ctx, wrapping.WithKeyId(base64.StdEncoding.EncodeToString([]byte(boundaryBenchmarksRootKey))))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = wrap.SetAesGcmKeyBytes([]byte(boundaryBenchmarksRootKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrap, nil
|
||||
}
|
||||
|
||||
func randStr(n int) string {
|
||||
b := make([]byte, n)
|
||||
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
|
||||
|
||||
Reference in new issue
Block a user