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:
Johan Brandhorst-Satzkorn authored and Timothy Messier committed 2022-04-29 16:33:44 -04:00
1 parent 5d042c7ec7
commit e864652b33
4 files changed
+243 -4

No files matched your search

+13
View File
@@ -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
+4 -2
View File
@@ -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
}
+38 -2
View File
@@ -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!