Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Creates a suite of tests to measure and benchmark the performance of features of the Go agent at scale.
- Loading branch information
Showing
3 changed files
with
335 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |