package ldclient import ( "fmt" "testing" "github.com/launchdarkly/go-sdk-common/v3/ldcontext" "github.com/launchdarkly/go-sdk-common/v3/ldtime" "github.com/launchdarkly/go-sdk-common/v3/lduser" "github.com/launchdarkly/go-sdk-common/v3/ldvalue" ldevents "github.com/launchdarkly/go-sdk-events/v2" ) // These benchmarks cover the DefaultEventProcessor logic for digesting analytics event inputs and producing // output event data, but not actually sending the event data anywhere. // // In the current implementation, event processor tasks are divided between several goroutines. Therefore, // timing of these operations will have more variability than other benchmarks. However, execution time // should still be roughly proportional to the volume of work, and allocations should be fairly consistent. type mockEventSender struct { sentCh chan struct{} } func (m *mockEventSender) SendEventData(kind ldevents.EventDataKind, data []byte, eventCount int) ldevents.EventSenderResult { m.sentCh <- struct{}{} // allows benchmarks to detect that the event payload has been generated and fake-sent return ldevents.EventSenderResult{Success: true} } type eventsBenchmarkEnv struct { eventProcessor ldevents.EventProcessor mockEventSender *mockEventSender targetFeatureKey string users []ldcontext.Context variations []ldvalue.Value } func newEventsBenchmarkEnv() *eventsBenchmarkEnv { return &eventsBenchmarkEnv{} } func (env *eventsBenchmarkEnv) setUp(bc eventsBenchmarkCase) { env.mockEventSender = &mockEventSender{sentCh: make(chan struct{}, 10)} config := ldevents.EventsConfiguration{ Capacity: bc.bufferSize, EventSender: env.mockEventSender, } env.eventProcessor = ldevents.NewDefaultEventProcessor(config) env.targetFeatureKey = "flag-key" env.variations = make([]ldvalue.Value, bc.numVariations) for i := 0; i < bc.numVariations; i++ { env.variations[i] = ldvalue.Int(i) } env.users = make([]ldcontext.Context, bc.numUsers) for i := 0; i < bc.numUsers; i++ { env.users[i] = lduser.NewUser(makeEvalBenchmarkTargetUserKey(i)) } } func (env *eventsBenchmarkEnv) tearDown() { env.eventProcessor.Close() env.eventProcessor = nil } func (env *eventsBenchmarkEnv) waitUntilEventsSent() { <-env.mockEventSender.sentCh } type eventsBenchmarkCase struct { bufferSize int numEvents int numVariations int numUsers int } var eventsBenchmarkCases = []eventsBenchmarkCase{ { bufferSize: 1000, numEvents: 100, numVariations: 2, numUsers: 10, }, { bufferSize: 1000, numEvents: 100, numVariations: 2, numUsers: 100, }, { bufferSize: 1000, numEvents: 1000, numVariations: 2, numUsers: 10, }, { bufferSize: 1000, numEvents: 1000, numVariations: 2, numUsers: 100, }, } func benchmarkEvents(b *testing.B, cases []eventsBenchmarkCase, action func(*eventsBenchmarkEnv, eventsBenchmarkCase)) { env := newEventsBenchmarkEnv() for _, bc := range cases { env.setUp(bc) b.Run(fmt.Sprintf("%+v", bc), func(b *testing.B) { for i := 0; i < b.N; i++ { action(env, bc) } }) env.tearDown() } } func BenchmarkFeatureRequestEventsSummaryOnly(b *testing.B) { benchmarkEvents(b, eventsBenchmarkCases, func(env *eventsBenchmarkEnv, bc eventsBenchmarkCase) { for i := 0; i < bc.numEvents; i++ { user := env.users[i%bc.numUsers] variation := i % bc.numVariations value := env.variations[variation] event := ldevents.EvaluationData{ BaseEvent: ldevents.BaseEvent{ CreationDate: ldtime.UnixMillisNow(), Context: ldevents.Context(user), }, Key: env.targetFeatureKey, Variation: ldvalue.NewOptionalInt(variation), Value: value, } env.eventProcessor.RecordEvaluation(event) } env.eventProcessor.Flush() env.waitUntilEventsSent() }) } func BenchmarkFeatureRequestEventsWithFullTracking(b *testing.B) { benchmarkEvents(b, eventsBenchmarkCases, func(env *eventsBenchmarkEnv, bc eventsBenchmarkCase) { for i := 0; i < bc.numEvents; i++ { user := env.users[i%bc.numUsers] variation := i % bc.numVariations value := env.variations[variation] event := ldevents.EvaluationData{ BaseEvent: ldevents.BaseEvent{ CreationDate: ldtime.UnixMillisNow(), Context: ldevents.Context(user), }, Key: env.targetFeatureKey, Variation: ldvalue.NewOptionalInt(variation), Value: value, RequireFullEvent: true, } env.eventProcessor.RecordEvaluation(event) } env.eventProcessor.Flush() env.waitUntilEventsSent() }) } func BenchmarkCustomEvents(b *testing.B) { data := ldvalue.ObjectBuild().SetString("eventData", "value").Build() benchmarkEvents(b, eventsBenchmarkCases, func(env *eventsBenchmarkEnv, bc eventsBenchmarkCase) { for i := 0; i < bc.numEvents; i++ { user := env.users[i%bc.numUsers] event := ldevents.CustomEventData{ BaseEvent: ldevents.BaseEvent{ CreationDate: ldtime.UnixMillisNow(), Context: ldevents.Context(user), }, Key: "event-key", Data: data, } env.eventProcessor.RecordCustomEvent(event) } env.eventProcessor.Flush() env.waitUntilEventsSent() }) }