Skip to content

Commit

Permalink
Merge 06b9380 into 6c3a201
Browse files Browse the repository at this point in the history
  • Loading branch information
matheuscscp authored Aug 12, 2019
2 parents 6c3a201 + 06b9380 commit 59ebd97
Show file tree
Hide file tree
Showing 18 changed files with 988 additions and 0 deletions.
70 changes: 70 additions & 0 deletions cmd/loadtest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cmd

import (
"github.com/spf13/cobra"
"github.com/topfreegames/khan/loadtest"
"github.com/topfreegames/khan/log"
"github.com/uber-go/zap"
)

var sharedClansFile string
var nGoroutines int

var loadtestCmd = &cobra.Command{
Use: "loadtest",
Short: "runs a load test against a remote Khan API",
Long: `Runs a load test against a remote Khan API with the specified arguments.
You can use environment variables to override configuration keys.`,
Run: func(cmd *cobra.Command, args []string) {
logger := zap.New(zap.NewJSONEncoder(), zap.InfoLevel)

exitChannel := make(chan bool)
routine := func() {
l := logger.With(
zap.String("source", "loadtestCmd"),
zap.String("operation", "Run/goroutine"),
)

app := loadtest.GetApp(ConfigFile, sharedClansFile, logger)
if err := app.Run(); err != nil {
log.E(l, "Goroutine exited with error.", func(cm log.CM) {
cm.Write(zap.String("error", err.Error()))
})
} else {
log.I(l, "Goroutine exited without errors.")
}

exitChannel <- true
}
for i := 0; i < nGoroutines; i++ {
go routine()
}
for i := 0; i < nGoroutines; i++ {
<-exitChannel
}

l := logger.With(
zap.String("source", "loadtestCmd"),
zap.String("operation", "Run"),
)
log.I(l, "Application exited without errors.")
},
}

func init() {
RootCmd.AddCommand(loadtestCmd)

loadtestCmd.Flags().StringVar(
&sharedClansFile,
"clans",
"./config/loadTestSharedClans.yaml",
"shared clans list for load test",
)

loadtestCmd.Flags().IntVar(
&nGoroutines,
"goroutines",
1,
"number of goroutines to spawn for concurrent load tests",
)
}
3 changes: 3 additions & 0 deletions config/loadTestSharedClans.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
clans:
- "clan1publicID"
- "clan2publicID"
20 changes: 20 additions & 0 deletions config/local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,23 @@ extensions:
prefix: khan.
tags_prefix: ""
rate: 1

loadtest:
game:
maxMembers: 50
membershipLevel: "member"
requests:
amount: 1
period:
ms: 1
operations:
updateSharedClanScore:
probability: 0.8
createPlayer:
probability: 0.01
createClan:
probability: 0.01
leaveClan:
probability: 0.01
applyForMembership:
probability: 0.01
1 change: 1 addition & 0 deletions lib/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type KhanInterface interface {
PromoteDemote(context.Context, *PromoteDemotePayload) (*Result, error)
RetrieveClan(context.Context, string) (*Clan, error)
RetrieveClansSummary(context.Context, []string) ([]*ClanSummary, error)
RetrieveClanMembers(context.Context, string) (*ClanMembers, error)
RetrieveClanSummary(context.Context, string) (*ClanSummary, error)
RetrievePlayer(context.Context, string) (*Player, error)
TransferOwnership(context.Context, string, string) (*TransferOwnershipResult, error)
Expand Down
19 changes: 19 additions & 0 deletions lib/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ func (k *Khan) buildRetrieveClanURL(clanID string) string {
return k.buildURL(pathname)
}

func (k *Khan) buildRetrieveClanMembersURL(clanID string) string {
pathname := fmt.Sprintf("clans/%s/members", clanID)
return k.buildURL(pathname)
}

func (k *Khan) buildRetrieveClanSummaryURL(clanID string) string {
pathname := fmt.Sprintf("clans/%s/summary", clanID)
return k.buildURL(pathname)
Expand Down Expand Up @@ -304,6 +309,20 @@ func (k *Khan) UpdateClan(ctx context.Context, clan *ClanPayload) (*Result, erro
return &result, err
}

// RetrieveClanMembers calls the route to retrieve clan members from khan
func (k *Khan) RetrieveClanMembers(ctx context.Context, clanID string) (*ClanMembers, error) {
route := k.buildRetrieveClanMembersURL(clanID)
body, err := k.sendTo(ctx, "GET", route, nil)

if err != nil {
return nil, err
}

var clanMembers ClanMembers
err = json.Unmarshal(body, &clanMembers)
return &clanMembers, err
}

// RetrieveClanSummary calls the route to retrieve clan summary from khan
func (k *Khan) RetrieveClanSummary(ctx context.Context, clanID string) (*ClanSummary, error) {
route := k.buildRetrieveClanSummaryURL(clanID)
Expand Down
5 changes: 5 additions & 0 deletions lib/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ type ShortPlayerInfo struct {
Metadata interface{} `json:"metadata"`
}

// ClanMembers is used to unmarshal the response payload for clan members route
type ClanMembers struct {
Members []string `json:"members"`
}

// ClanSummary defines the clan summary
type ClanSummary struct {
PublicID string `json:"publicID"`
Expand Down
71 changes: 71 additions & 0 deletions loadtest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
Load Test for Khan API
======================

This application performs a specified amount of sequential requests to a remote Khan API server, with a specified time period between two consecutive requests. Usage: `../khan loadtest --help`

# Game parameters
Khan does not offer a route to get game information, so the maximum number of members per clan and the membership level for application are defined under the key `loadtest` within `../config/local.yaml`:
```
loadtest:
game:
maxMembers: 50
membershipLevel: "member"
```
Or setting the following environment variables:
```
KHAN_LOADTEST_GAME_MAXMEMBERS (default: 0)
KHAN_LOADTEST_GAME_MEMBERSHIPLEVEL (default: "")
```

# Request parameters
Both amount and period are defined under the key `loadtest` within `../config/local.yaml`:
```
loadtest:
requests:
amount: 1
period:
ms: 1
```
Or setting the following environment variables:
```
KHAN_LOADTEST_REQUESTS_AMOUNT (default: 0)
KHAN_LOADTEST_REQUESTS_PERIOD_MS (default: 0)
```

# Request operations
For each request, an operation is chosen at random with probabilities defined at `../config/local.yaml`, like this:
```
loadtest:
operations:
retrieveSharedClan:
probability: 0.3
updateSharedClan:
probability: 0.3
```
Or setting the following environment variables:
```
KHAN_LOADTEST_OPERATIONS_RETRIEVESHAREDCLAN_PROBABILITY (no default value)
KHAN_LOADTEST_OPERATIONS_UPDATESHAREDCLAN_PROBABILITY (no default value)
```
Request operations are defined in files `player.go`, `clan.go` and `membership.go`. Check the operation keys to set the probabilities.

# Remote KHAN API server parameters
Khan API server URL and credentials should be set by environment variables, like this:
```
KHAN_KHAN_URL: URL including protocol and port to remote Khan API server
KHAN_KHAN_USER: basic auth username
KHAN_KHAN_PASS: basic auth password
KHAN_KHAN_GAMEID: game public ID
KHAN_KHAN_TIMEOUT: nanoseconds to wait before timing out a request (default: 500 ms)
KHAN_KHAN_MAXIDLECONNS: max keep-alive connections to keep among all hosts (default: 100)
KHAN_KHAN_MAXIDLECONNSPERHOST: max keep-alive connections to keep per-host (default: 2)
```

# Operations with clans shared among different load test processes
Some operations are targeted to a set of clans that should be shared among different processes running a load test. This is supposed to test the most common sequence of operations (`updatePlayer`, then `getClan`, then `updateClan`) in its most common use case, which is a number of different clients updating the score of a particular clan within its metadata field. Use the file `../config/loadTestSharedClans.yaml` to specify a list of clan public IDs, like this:

```
clans:
- "clan1publicID"
- "clan2publicID"
```
Loading

0 comments on commit 59ebd97

Please sign in to comment.