Skip to content

Commit

Permalink
feat: print result in format
Browse files Browse the repository at this point in the history
- Refactor code to have types to carry test config and results.
- Add support to print test result in Json.
- Print logs to stderr while test res to stdout.
- Update README.md.
  • Loading branch information
Taowyoo committed May 30, 2024
1 parent 9ae7bf2 commit 87220bc
Show file tree
Hide file tree
Showing 17 changed files with 677 additions and 285 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
dsm-perf-tool
*.env
*.log
*.json

# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
Expand Down
77 changes: 56 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,65 @@ in test-setup subcommand to fail.

# Running

Usage:
Get how usage message by:

```
dsm-perf-tool [command]
```
Available Commands:

```
completion Generates bash completion scripts
get-version Call version API in a loop
help Help about any command
load-test A collection of load tests for various types of operations.
test-setup Setup a test account useful for testing.
```shell
./dsm-perf-tool --help
```

Flags:
```
-h, --help help for dsm-perf-tool
--idle-connection-timeout duration Idle connection timeout, 0 means no timeout (default behavior)
--insecure Do not validate server's TLS certificate
-p, --port uint16 DSM server port (default 443)
--request-timeout duration HTTP request timeout, 0 means no timeout (default 1m0s)
-s, --server string DSM server host name (default "sdkms.test.fortanix.com")
```
## Example steps to run a performance test

1. Test setup
You need to run `./dsm-perf-tool test-setup` to create groups, keys and plugins before run a performance test:
- Create a account: `./dsm-perf-tool --server sdkms.test.fortanix.com test-setup --create-test-user --test-user [email protected] --test-user-pwd testuse1_password | tee test.env`
- Use an existing account: `./dsm-perf-tool --server sdkms.test.fortanix.com test-setup --test-user [email protected] --test-user-pwd testuse1_password | tee test.env`

Note:
- This command may takes ~10 seconds.
- `| tee test.env` is used for print and save output (multiple lines of `export XXX=abc`) to a file which could be sourced later.
- Please update `sdkms.test.fortanix.com` to the hostname or ip address to the server you want to target.
- You could add `--insecure` option to ignore self-signed certificate on remote host.
- You could add `--port 123456` option to change port (default value is `443`).

2. Run a performance test
Once you run the test setup and got a env file, such as `test.env`.
You could start to use environment variables in the env file to run some performance test.

Here is one example of running AES CBC decryption test:
```shell
source test.env && \
./dsm-perf-tool --server sdkms.test.fortanix.com load-test --api-key $TEST_API_KEY --connections 5 --create-session --duration 10s --qps 2000 --warmup 5s symmetric-crypto --decrypt --mode CBC --kid $TEST_AES_KEY_ID
```
Explanation:
```shell
source test.env; # load environment variables in env file
./dsm-perf-tool --server sdkms.test.fortanix.com \ # Specify remote server host name
load-test \
--api-key $TEST_API_KEY \ # App API key
--connections 5 \ # Concurrent connections will be created
--create-session \ # Will create session for each connection
--duration 10s \ # Test duration value is a golang time string
--qps 2000 \ # Target QPS, you could set this to a very big value if you want to test max QPS
--warmup 5s \ # Warmup happened before test
symmetric-crypto \
--decrypt \ # Add this option will test decryption instead of encryption by default
--mode CBC \
--kid $TEST_AES_KEY_ID # AES Key UUID, you could use $TEST_HIVOL_AES_KEY_ID to test against high volume key
```

You could add `--output-format json` to print test result in JSON format.

Since test result is printed in stdout and logs are printed to stderr. You could redirect the test result to a file.

```shell
source test.env && \
./dsm-perf-tool --server sdkms.test.fortanix.com load-test --api-key $TEST_API_KEY --connections 5 --create-session --duration 10s --qps 2000 --warmup 5s symmetric-crypto --decrypt --mode CBC --kid $TEST_AES_KEY_ID | tee res.json
```

## Note

- All logs will are printed to stderr.
- Test summary will be printed to stdout.

# Contributing

Expand Down
106 changes: 8 additions & 98 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package cmd
import (
"context"
"crypto/tls"
"encoding/csv"
"encoding/json"
"fmt"
"log"
Expand All @@ -18,8 +17,6 @@ import (
"net/http"
"os"
"os/signal"
"reflect"
"strconv"
"strings"
"syscall"
"time"
Expand Down Expand Up @@ -62,7 +59,7 @@ func setupCloseHandler(onClose func()) {
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
fmt.Println("\r- Ctrl+C detected")
log.Println("\r- Ctrl+C detected")
onClose()
os.Exit(0)
}()
Expand Down Expand Up @@ -189,103 +186,16 @@ func GetSobject(kid *string) *sdkms.Sobject {
return key
}

func summarizeProfilingData(dataArr profilingDataArr) {
var inQueueData stats.Float64Data
var parseRequestData stats.Float64Data
var sessionLookupData stats.Float64Data
var validateInputData stats.Float64Data
var checkAccessData stats.Float64Data
var operateData stats.Float64Data
var dbFlushData stats.Float64Data
var totalData stats.Float64Data
additionalDataArr := make(map[string]stats.Float64Data)
actionNameMaxLen := len("SessionLookup")
for _, data := range dataArr {
inQueueData = append(inQueueData, float64(data.InQueue))
parseRequestData = append(parseRequestData, float64(data.ParseRequest))
sessionLookupData = append(sessionLookupData, float64(data.SessionLookup))
validateInputData = append(validateInputData, float64(data.ValidateInput))
checkAccessData = append(checkAccessData, float64(data.CheckAccess))
operateData = append(operateData, float64(data.Operate))
dbFlushData = append(dbFlushData, float64(data.DbFlush))
totalData = append(totalData, float64(data.Total))
actionNameMaxLen = Max(actionNameMaxLen, processAdditionalProfilingData(&data.AdditionalProfilingData, &additionalDataArr, actionNameMaxLen, ""))
}
fmt.Printf("%s: %s\n", StrPad("InQueue", actionNameMaxLen, " ", "LEFT"), summarizeData(inQueueData))
fmt.Printf("%s: %s\n", StrPad("ParseRequest", actionNameMaxLen, " ", "LEFT"), summarizeData(parseRequestData))
fmt.Printf("%s: %s\n", StrPad("SessionLookup", actionNameMaxLen, " ", "LEFT"), summarizeData(sessionLookupData))
fmt.Printf("%s: %s\n", StrPad("ValidateInput", actionNameMaxLen, " ", "LEFT"), summarizeData(validateInputData))
fmt.Printf("%s: %s\n", StrPad("CheckAccess", actionNameMaxLen, " ", "LEFT"), summarizeData(checkAccessData))
fmt.Printf("%s: %s\n", StrPad("Operate", actionNameMaxLen, " ", "LEFT"), summarizeData(operateData))
fmt.Printf("%s: %s\n", StrPad("DbFlush", actionNameMaxLen, " ", "LEFT"), summarizeData(dbFlushData))
fmt.Printf("%s: %s\n", StrPad("Total", actionNameMaxLen, " ", "LEFT"), summarizeData(totalData))
for actionName, timeArr := range additionalDataArr {
fmt.Printf("%s: %s\n", StrPad(actionName, actionNameMaxLen, " ", "LEFT"), summarizeData(timeArr))
}
}

func processAdditionalProfilingData(dataArr *[]additionalProfilingData, additionalDataArr *map[string]stats.Float64Data, actionNameMaxLen int, parentActionName string) int {
for _, customProfilingData := range *dataArr {
actionName := parentActionName + "/" + customProfilingData.Action
(*additionalDataArr)[actionName] = append((*additionalDataArr)[actionName], float64(customProfilingData.TookNs))
actionNameMaxLen = Max(actionNameMaxLen, len(actionName))
actionNameMaxLen = Max(actionNameMaxLen, processAdditionalProfilingData(&customProfilingData.SubActions, additionalDataArr, actionNameMaxLen, actionName))
}
return actionNameMaxLen
}
type httpHeaderKey string

func (dataArr profilingDataArr) getCSVHeaders() (header []string) {
e := reflect.ValueOf(&dataArr[0]).Elem()
for i := 0; i < e.NumField(); i++ {
varName := e.Type().Field(i).Name
header = append(header, varName)
}
return header
}

func (dataArr profilingDataArr) getCSVValues() (values [][]string) {
for _, data := range dataArr {
values = append(values, []string{
strconv.FormatUint(data.InQueue, 10),
strconv.FormatUint(data.ParseRequest, 10),
strconv.FormatUint(data.SessionLookup, 10),
strconv.FormatUint(data.ValidateInput, 10),
strconv.FormatUint(data.CheckAccess, 10),
strconv.FormatUint(data.Operate, 10),
strconv.FormatUint(data.DbFlush, 10),
strconv.FormatUint(data.Total, 10),
})
}
return values
}
const (
responseHeaderKey httpHeaderKey = "ResponseHeader"
)

func saveProfilingDataToCSV(dataArr profilingDataArr) {
csvFile, err := os.CreateTemp(".", "profilingData.*.csv")
func toJsonStr(v any) string {
val, err := json.Marshal(v)
if err != nil {
log.Fatalf("Fatal error: %v\n", err)
}
w := csv.NewWriter(csvFile)
headers := dataArr.getCSVHeaders()
values := dataArr.getCSVValues()
if err := w.Write(headers); err != nil {
log.Fatalf("Fatal error: %v\n", err)
}
if err := w.WriteAll(values); err != nil {
log.Fatalf("Fatal error: %v\n", err)
}

fmt.Println("Saved profiling data to:", csvFile.Name())
}

func parseProfilingDataStrArr(profilingDataStrArr []profilingDataStr) profilingDataArr {
var dataArr profilingDataArr
for _, profilingDataStr := range profilingDataStrArr {
var profilingData profilingData
err := json.Unmarshal([]byte(string(profilingDataStr)), &profilingData)
if err != nil {
log.Fatalf("Fatal error: %v\n", err)
}
dataArr = append(dataArr, profilingData)
}
return dataArr
return string(val)
}
4 changes: 2 additions & 2 deletions cmd/getVersion.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ package cmd

import (
"context"
"fmt"
"log"
"time"

"github.com/spf13/cobra"
)

// TODO: get rid of global variables, tracking issue: #16
var getVersionCount uint
var newConnectionPerRequest bool
var getVersionDelay time.Duration
Expand Down Expand Up @@ -45,7 +45,7 @@ func getVersion() {
}
log.Printf("%v version: %v (%v)", serverName, v.Version, v.ServerMode)
var durations []time.Duration
printSummary := func() { fmt.Printf("%v\n", summarizeTimings(durations)) }
printSummary := func() { log.Printf("%v\n", summarizeTimings(durations)) }
setupCloseHandler(printSummary)
var count uint
for {
Expand Down
Loading

0 comments on commit 87220bc

Please sign in to comment.