diff --git a/v3/scale-tests/main.go b/v3/scale-tests/main.go new file mode 100644 index 000000000..bd8246714 --- /dev/null +++ b/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) +} diff --git a/v3/scale-tests/normalize.go b/v3/scale-tests/normalize.go new file mode 100644 index 000000000..250da53cc --- /dev/null +++ b/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 + } +} diff --git a/v3/scale-tests/normalize_test.go b/v3/scale-tests/normalize_test.go new file mode 100644 index 000000000..0bd915e24 --- /dev/null +++ b/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 +}