Skip to content

Commit

Permalink
stress-tests
Browse files Browse the repository at this point in the history
Creates a suite of tests to measure and benchmark the performance
of features of the Go agent at scale.
  • Loading branch information
iamemilio committed May 26, 2022
1 parent 2ff7fdd commit 81b6c0b
Show file tree
Hide file tree
Showing 3 changed files with 335 additions and 0 deletions.
173 changes: 173 additions & 0 deletions v3/scale-tests/main.go
@@ -0,0 +1,173 @@
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"fmt"
"os"
"time"

"github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrzerolog"
"github.com/newrelic/go-agent/v3/newrelic"
"github.com/rs/zerolog"
)

var (
TestNRZL = "Zerolog Plugin"
TestZerolog = "Zerolog"
TestCustomEvents = "Custom Events"
)

// Zerolog Test
func Zerolog(numEvents, numRuns int) Benchmark {
return Benchmark{
TestZerolog,
numEvents,
numRuns,
make([]int64, numRuns),
}
}

func (bench *Benchmark) timeZerologSet() int64 {
// Init logger
logger := zerolog.New(nil)

// Time Consumption
start := time.Now()
for i := 0; i < bench.numEvents; i++ {
logger.Info().Msg("Message " + fmt.Sprint(i))
}
return time.Since(start).Microseconds()
}

// NR Zerolog Plugin Test
func NRZerolog(numEvents, numRuns int) Benchmark {
return Benchmark{
TestNRZL,
numEvents,
numRuns,
make([]int64, numRuns),
}
}

func (bench *Benchmark) timeZerologPluginSet(app *newrelic.Application) int64 {
// Init Logger

nrHook := nrzerolog.Hook{
App: app,
}

logger := zerolog.New(nil).Hook(nrHook)

// Time Consumption
start := time.Now()
for i := 0; i < bench.numEvents; i++ {
logger.Info().Msg("Message " + fmt.Sprint(i))
}
return time.Since(start).Microseconds()
}

// Custom Events Test
func CustomEvent(numEvents, numRuns int) Benchmark {
return Benchmark{
TestCustomEvents,
numEvents,
numRuns,
make([]int64, numRuns),
}
}

func (bench *Benchmark) timeCustomEventSet(app *newrelic.Application) int64 {
// Time Consumption
start := time.Now()
for i := 0; i < bench.numEvents; i++ {
message := "Message " + fmt.Sprint(i)
app.RecordCustomEvent("TEST EVENT", map[string]interface{}{
"Message": message,
})
}
return time.Since(start).Microseconds()
}

// Benchmark Framework
type Benchmark struct {
eventType string
numEvents int
sets int
runTimes []int64
}

func (bench *Benchmark) Sprint() string {
output := fmt.Sprintf("Time taken to record %d %s:\n", bench.numEvents, bench.eventType)
for _, time := range bench.runTimes {
output += fmt.Sprintf("\t\tMicroseconds: %d\n", time)
}

validTimes, sum := normalize(bench.runTimes)
average := float64(sum) / float64(len(validTimes))
output += fmt.Sprintf("\t\tAverage Microseconds: %.3f\n", average)
return output
}

func (bench *Benchmark) Benchmark(app *newrelic.Application) {
for set := 0; set < bench.sets; set++ {
switch bench.eventType {
case TestZerolog:
bench.runTimes[set] = bench.timeZerologSet()
case TestNRZL:
bench.runTimes[set] = bench.timeZerologPluginSet(app)
case TestCustomEvents:
bench.runTimes[set] = bench.timeCustomEventSet(app)
}
}
}

func main() {
app, err := newrelic.NewApplication(
newrelic.ConfigAppName("ApplicationLogging Stress Test Golang"),
newrelic.ConfigZerologPluginEnabled(true),
newrelic.ConfigDistributedTracerEnabled(true),
newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
newrelic.ConfigInfoLogger(os.Stdout),
)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

tests := []Benchmark{
Zerolog(10, 10),
Zerolog(100, 10),
Zerolog(1000, 10),

NRZerolog(10, 10),
NRZerolog(100, 10),
NRZerolog(1000, 10),

CustomEvent(10, 10),
CustomEvent(100, 10),
CustomEvent(1000, 10),
}

// Wait for the application to connect.
if err := app.WaitForConnection(5 * time.Second); nil != err {
fmt.Println(err)
}

for _, test := range tests {
test.Benchmark(app)
}

// Make sure the metrics get sent
time.Sleep(60 * time.Second)
app.Shutdown(10 * time.Second)

// Compile metrics data as pretty printed strings
var metrics string
for _, test := range tests {
metrics += test.Sprint()
}

fmt.Println(metrics)
}
76 changes: 76 additions & 0 deletions v3/scale-tests/normalize.go
@@ -0,0 +1,76 @@
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//

package main

import (
"sort"
)

// Normalizes the data by removing outliers based on z score
func normalize(times []int64) ([]int64, int64) {
// not enough entries to do this calculation correctly
if len(times) < 3 {
var sum int64
for _, time := range times {
sum += time
}
return times, sum
}

var sum int64
validTimes := make([]int64, 0, len(times))

q1, q3 := interquartileRanges(times)
iqr := q3 - q1
upperFence := int64(q3 + (1.5 * iqr))
lowerFence := int64(q1 - (1.5 * iqr))

for _, time := range times {
if time >= lowerFence && time <= upperFence {
validTimes = append(validTimes, time)
sum += time
}
}

return validTimes, sum
}

func interquartileRanges(times []int64) (float64, float64) {
sorted := make([]int, len(times))
for i, val := range times {
sorted[i] = int(val)
}

sort.Ints(sorted)

var r1, r2 []int

if len(sorted)%2 == 1 {
r1 = sorted[:(len(sorted) / 2)]
r2 = sorted[(len(sorted)/2)+1:]
} else {
r1 = sorted[:(len(sorted))/2]
r2 = sorted[(len(sorted) / 2):]
}

q1 := median(r1)
q3 := median(r2)

return float64(q1), float64(q3)
}

func median(n []int) float64 {
if len(n) == 0 {
return 0
}
if len(n) == 1 {
return float64(n[0])
}
if len(n)%2 == 1 {
return float64(n[len(n)/2])
} else {
return float64((n[len(n)/2-1] + n[len(n)/2])) / 2
}
}
86 changes: 86 additions & 0 deletions v3/scale-tests/normalize_test.go
@@ -0,0 +1,86 @@
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//

package main

import (
"testing"
)

func TestInterquartileRangesEven(t *testing.T) {
vals := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
q1, q3 := interquartileRanges(vals)
if q1 != 3 {
t.Errorf("Expected Q1 to equal 3, got %v", q1)
}
if q3 != 8 {
t.Errorf("Expected Q3 to equal 8, got %v", q3)
}
}

func TestInterquartileRangesOdd(t *testing.T) {
vals := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9}
q1, q3 := interquartileRanges(vals)
if q1 != 2.5 {
t.Errorf("Expected Q1 to equal 2.5, got %v", q1)
}
if q3 != 7.5 {
t.Errorf("Expected Q3 to equal 7.5, got %v", q3)
}
}

func TestNormalizeEven(t *testing.T) {
vals := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 100}
expect := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9}
validTimes, sum := normalize(vals)

if !AssertInt64SliceEquals(validTimes, expect) {
t.Errorf("Array was not normalized: %v should be %v", vals, expect)
}

if sum != 45 {
t.Errorf("Sum should be 45, got %v", sum)
}
}

func TestNormalizeOdd(t *testing.T) {
vals := []int64{2, 3, 4, 5, 6, 7, 8, 9, 100}
expect := []int64{2, 3, 4, 5, 6, 7, 8, 9}
validTimes, sum := normalize(vals)

if !AssertInt64SliceEquals(validTimes, expect) {
t.Errorf("Array was not normalized: %v should be %v", vals, expect)
}

if sum != 44 {
t.Errorf("Sum should be 44, got %v", sum)
}
}

func TestNormalizeNoop(t *testing.T) {
vals := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
expect := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
validTimes, sum := normalize(vals)

if !AssertInt64SliceEquals(validTimes, expect) {
t.Errorf("Array was not normalized: %v should be %v", vals, expect)
}

if sum != 55 {
t.Errorf("Sum should be 55, got %v", sum)
}
}

func AssertInt64SliceEquals(a, b []int64) bool {
if len(a) != len(b) {
return false
}

for i, v := range a {
if v != b[i] {
return false
}
}
return true
}

0 comments on commit 81b6c0b

Please sign in to comment.