Skip to content

Commit

Permalink
feat(kas): collect metrics (#1702)
Browse files Browse the repository at this point in the history
- Adds [otel](https://opentelemetry.io/) based tracing to kas methods:
publicKey and rewrap
- adds `trace` top level config to viper config, including
`trace.enabled` (boolean) for enabling otel tracer, and `trace.folder`
for setting the folder to store the `trace.log` file
- Adds new `examples benchmark` command to run encrypt/decrypt and
rewrap benchmarks
- Also adds a job to the checks.yaml to run it, but it is pretty
bare-bones for now, and doesn't have any checks against performance
regressions or anything.
- Variety of small linter fixes

```
tdf3:
Benchmark Results:
Total Requests: 5000
Successful Requests: 5000
Failed Requests: 0
Concurrent Requests: 50
Total Time: 1m7.704277611s
Average Latency: 673.018399ms
Throughput: 73.85 requests/second

nano:
Benchmark Results:
Total Requests: 5000
Successful Requests: 4992
Failed Requests: 8
Concurrent Requests: 50
Total Time: 57.72426082s
Average Latency: 575.422342ms
Throughput: 86.48 requests/second

Error Summary:
ReadNanoTDF error: readSeeker.Seek failed: error making nano rewrap request to kas: error making rewrap request: rpc error: code = PermissionDenied desc = request error
rpc error: code = PermissionDenied desc = forbidden: 8 occurrences


Trace Statistics:

publickey:
  Total Requests: 2
  Average Duration: 0.073 ms (72.647 µs, 72647 ns)
  Min Duration: 0.030 ms (2[9](https://github.com/opentdf/platform/actions/runs/12053261131/job/33608512613?pr=1702#step:17:10).937 µs, 29937 ns)
  Max Duration: 0.[11](https://github.com/opentdf/platform/actions/runs/12053261131/job/33608512613?pr=1702#step:17:12)5 ms (115.357 µs, 115357 ns)

rewrap-tdf3:
  Total Requests: 5000
  Average Duration: 316.499 ms (316498.907 µs, 316498907 ns)
  Min Duration: 23.608 ms (23607.787 µs, 23607787 ns)
  Max Duration: 979.418 ms (979417.815 µs, 979417815 ns)

rewrap-nanotdf:
  Total Requests: 4776
  Average Duration: 320.718 ms (320717.904 µs, 320717904 ns)
  Min Duration: 22.802 ms (22801.981 µs, 22801981 ns)
  Max Duration: 874.830 ms (874829.7[13](https://github.com/opentdf/platform/actions/runs/12053261131/job/33608512613?pr=1702#step:17:14) µs, 874829713 ns)
```

---------

Co-authored-by: Dave Mihalcik <dmihalcik@virtru.com>
  • Loading branch information
sujankota and dmihalcik-virtru authored Nov 27, 2024
1 parent c34251e commit def28d1
Show file tree
Hide file tree
Showing 31 changed files with 821 additions and 228 deletions.
62 changes: 62 additions & 0 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,67 @@ jobs:
- name: validate custom entity rego policy
run: test/rego/custom-entity.bats

benchmark:
name: benchmark tests
runs-on: ubuntu-22.04
env:
TLS_ENABLED: "true"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7
with:
go-version-file: 'service/go.mod'
check-latest: false
cache-dependency-path: |
service/go.sum
examples/go.sum
protocol/go/go.sum
sdk/go.sum
- if: env.IS_RELEASE_BRANCH == 'true'
run: ./.github/scripts/work-init.sh
- run: go mod download
- run: go mod verify
- name: Install mkcert
run: |
sudo apt-get install -y libnss3-tools
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
chmod +x mkcert-v*-linux-amd64
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert
- run: |
.github/scripts/init-temp-keys.sh
mkcert -install
mkcert -cert-file ./keys/platform.crt -key-file ./keys/platform-key.pem localhost
cp opentdf-dev.yaml opentdf.yaml
yq eval '.server.tls.enabled = true' -i opentdf.yaml
yq eval '.trace = {"enabled":true,"folder":"traces"}' -i opentdf.yaml
- name: Added Trusted Certs
run: |
sudo chmod -R 777 ./keys
sudo apt-get install -y ca-certificates
sudo cp ./keys/localhost.crt /usr/local/share/ca-certificates
sudo update-ca-certificates
- run: docker compose up -d --wait --wait-timeout 240 || (docker compose logs && exit 1)
- run: go run ./service provision keycloak
- run: go run ./service provision fixtures
- uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635
name: start server in background
with:
run: >
go build -o opentdf -v service/main.go
&& .github/scripts/watch.sh opentdf.yaml ./opentdf start
wait-on: |
tcp:localhost:8080
log-output-if: true
wait-for: 90s
- name: build examples
run: cd examples && go build -o examples .
- name: run tdf3 benchmark tests
run: ./examples/examples benchmark --count=5000 --concurrent=50
- name: run nanotdf benchmark tests
run: ./examples/examples benchmark --storeCollectionHeaders=false --tdf=nanotdf --count=5000 --concurrent=50
- name: collect the metrics from the benchmark tests
run: ./examples/examples metrics

image:
name: image build
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -232,6 +293,7 @@ jobs:
- go
- image
- integration
- benchmark
- license
- platform-xtest
- otdfctl-test
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ sensitive.txt.tdf
keys/
/examples/sensitive.txt.ntdf
sensitive.txt.ntdf
traces/
228 changes: 228 additions & 0 deletions examples/cmd/benchmark.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package cmd

import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"
"sync"
"time"

"github.com/opentdf/platform/sdk"
"github.com/spf13/cobra"
)

type TDFFormat string

const (
TDF3 TDFFormat = "tdf3"
NanoTDF TDFFormat = "nanotdf"
)

func (f *TDFFormat) String() string {
return string(*f)
}

func (f *TDFFormat) Set(value string) error {
switch value {
case "tdf3", "nanotdf":
*f = TDFFormat(value)
return nil
default:
return errors.New("invalid TDF format")
}
}

func (f *TDFFormat) Type() string {
return "TDFFormat"
}

type BenchmarkConfig struct {
TDFFormat TDFFormat
ConcurrentRequests int
RequestCount int
RequestsPerSecond int
TimeoutSeconds int
}

var config BenchmarkConfig

func init() {
benchmarkCmd := &cobra.Command{
Use: "benchmark",
Short: "OpenTDF benchmark tool",
Long: `A OpenTDF benchmark tool to measure throughput and latency with configurable concurrency.`,
RunE: runBenchmark,
}

benchmarkCmd.Flags().IntVar(&config.ConcurrentRequests, "concurrent", 10, "Number of concurrent requests")
benchmarkCmd.Flags().IntVar(&config.RequestCount, "count", 100, "Total number of requests")
benchmarkCmd.Flags().IntVar(&config.RequestsPerSecond, "rps", 50, "Requests per second limit")
benchmarkCmd.Flags().IntVar(&config.TimeoutSeconds, "timeout", 30, "Timeout in seconds")
benchmarkCmd.Flags().Var(&config.TDFFormat, "tdf", "TDF format (tdf3 or nanotdf)")
ExamplesCmd.AddCommand(benchmarkCmd)
}

func runBenchmark(cmd *cobra.Command, args []string) error {
in := strings.NewReader("Hello, World!")

// Create new offline client
client, err := newSDK()
if err != nil {
return err
}

out := os.Stdout
if outputName != "-" {
out, err = os.Create("sensitive.txt.tdf")
if err != nil {
return err
}
}
defer func() {
if outputName != "-" {
out.Close()
}
}()

var dataAttributes = []string{"https://example.com/attr/attr1/value/value1"}
if config.TDFFormat == NanoTDF {
nanoTDFConfig, err := client.NewNanoTDFConfig()
if err != nil {
return err
}
nanoTDFConfig.SetAttributes(dataAttributes)
nanoTDFConfig.EnableECDSAPolicyBinding()
err = nanoTDFConfig.SetKasURL(fmt.Sprintf("http://%s/kas", "localhost:8080"))
if err != nil {
return err
}

_, err = client.CreateNanoTDF(out, in, *nanoTDFConfig)
if err != nil {
return err
}

if outputName != "-" {
err = cat(cmd, outputName)
if err != nil {
return err
}
}
} else {
tdf, err :=
client.CreateTDF(
out, in,
sdk.WithDataAttributes(dataAttributes...),
sdk.WithKasInformation(
sdk.KASInfo{
URL: fmt.Sprintf("http://%s", "localhost:8080"),
PublicKey: "",
}),
sdk.WithAutoconfigure(false))
if err != nil {
return err
}

manifestJSON, err := json.MarshalIndent(tdf.Manifest(), "", " ")
if err != nil {
return err
}
cmd.Println(string(manifestJSON))
}

var wg sync.WaitGroup
requests := make(chan struct{}, config.ConcurrentRequests)
results := make(chan time.Duration, config.RequestCount)
errors := make(chan error, config.RequestCount)

// Function to perform the operation
operation := func() {
defer wg.Done()
start := time.Now()

file, err := os.Open("sensitive.txt.tdf")
if err != nil {
errors <- fmt.Errorf("file open error: %v", err)
return
}
defer file.Close()

if config.TDFFormat == NanoTDF {
_, err = client.ReadNanoTDF(io.Discard, file)
if err != nil {
errors <- fmt.Errorf("ReadNanoTDF error: %v", err)
return
}
} else {
tdfreader, err := client.LoadTDF(file)
if err != nil {
errors <- fmt.Errorf("LoadTDF error: %v", err)
return
}

_, err = io.Copy(io.Discard, tdfreader)
if err != nil && err != io.EOF {
errors <- fmt.Errorf("read error: %v", err)
return
}
}

results <- time.Since(start)
}

// Start the benchmark
startTime := time.Now()
for i := 0; i < config.RequestCount; i++ {
wg.Add(1)
requests <- struct{}{}
go func() {
defer func() { <-requests }()
operation()
}()
}

wg.Wait()
close(results)
close(errors)

// Count errors and collect error messages
errorCount := 0
errorMsgs := make(map[string]int)
for err := range errors {
errorCount++
errorMsgs[err.Error()]++
}

successCount := 0
var totalDuration time.Duration
for result := range results {
successCount++
totalDuration += result
}

totalTime := time.Since(startTime)
averageLatency := totalDuration / time.Duration(successCount)
throughput := float64(successCount) / totalTime.Seconds()

// Print results
cmd.Printf("\nBenchmark Results:\n")
cmd.Printf("Total Requests: %d\n", config.RequestCount)
cmd.Printf("Successful Requests: %d\n", successCount)
cmd.Printf("Failed Requests: %d\n", errorCount)
cmd.Printf("Concurrent Requests: %d\n", config.ConcurrentRequests)
cmd.Printf("Total Time: %s\n", totalTime)
cmd.Printf("Average Latency: %s\n", averageLatency)
cmd.Printf("Throughput: %.2f requests/second\n", throughput)

if errorCount > 0 {
cmd.Printf("\nError Summary:\n")
for errMsg, count := range errorMsgs {
cmd.Printf("%s: %d occurrences\n", errMsg, count)
}
}

return nil
}
3 changes: 2 additions & 1 deletion examples/cmd/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"bytes"
"errors"
"fmt"
"github.com/spf13/cobra"
"io"
"os"
"path/filepath"

"github.com/spf13/cobra"
)

func init() {
Expand Down
30 changes: 23 additions & 7 deletions examples/cmd/examples.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import (
)

var (
platformEndpoint string
clientCredentials string
tokenEndpoint string
platformEndpoint string
clientCredentials string
tokenEndpoint string
storeCollectionHeaders bool
insecurePlaintextConn bool
insecureSkipVerify bool
)

var ExamplesCmd = &cobra.Command{
Expand All @@ -24,14 +27,27 @@ var ExamplesCmd = &cobra.Command{

func init() {
log.SetFlags(log.LstdFlags | log.Llongfile)
ExamplesCmd.PersistentFlags().StringVarP(&clientCredentials, "creds", "", "opentdf-sdk:secret", "client id:secret credentials")
ExamplesCmd.PersistentFlags().StringVarP(&platformEndpoint, "platformEndpoint", "e", "localhost:8080", "Platform Endpoint")
ExamplesCmd.PersistentFlags().StringVarP(&tokenEndpoint, "tokenEndpoint", "t", "http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token", "OAuth token endpoint")
f := ExamplesCmd.PersistentFlags()
f.StringVarP(&clientCredentials, "creds", "", "opentdf-sdk:secret", "client id:secret credentials")
f.StringVarP(&platformEndpoint, "platformEndpoint", "e", "localhost:8080", "Platform Endpoint")
f.StringVarP(&tokenEndpoint, "tokenEndpoint", "t", "http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token", "OAuth token endpoint")
f.BoolVar(&storeCollectionHeaders, "storeCollectionHeaders", false, "Store collection headers")
f.BoolVar(&insecurePlaintextConn, "insecurePlaintextConn", false, "Use insecure plaintext connection")
f.BoolVar(&insecureSkipVerify, "insecureSkipVerify", false, "Skip server certificate verification")
}

func newSDK() (*sdk.SDK, error) {
resolver.SetDefaultScheme("passthrough")
opts := []sdk.Option{sdk.WithStoreCollectionHeaders()}
opts := []sdk.Option{}
if insecurePlaintextConn {
opts = append(opts, sdk.WithInsecurePlaintextConn())
}
if insecureSkipVerify {
opts = append(opts, sdk.WithInsecureSkipVerifyConn())
}
if storeCollectionHeaders {
opts = append(opts, sdk.WithStoreCollectionHeaders())
}
if clientCredentials != "" {
i := strings.Index(clientCredentials, ":")
if i < 0 {
Expand Down
Loading

0 comments on commit def28d1

Please sign in to comment.