From 7e8ebd7b062275983fb2ca17e873a66c844d0103 Mon Sep 17 00:00:00 2001 From: Tyler Helmuth <12352919+TylerHelmuth@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:19:43 -0600 Subject: [PATCH] [pkg/ottl] Improve time performance (#35129) **Description:** Improves `Time` performance by move the conversion from our format to Go's format to happen during startup. Benchmarks before: ``` goos: darwin goarch: arm64 pkg: github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs Benchmark_Time/simple_short_form-10 1000000000 0.0000079 ns/op 0 B/op 0 allocs/op Benchmark_Time/simple_short_form_with_short_year_and_slashes-10 1000000000 0.0000115 ns/op 0 B/op 0 allocs/op Benchmark_Time/month_day_year-10 1000000000 0.0000057 ns/op 0 B/op 0 allocs/op Benchmark_Time/simple_long_form-10 1000000000 0.0000075 ns/op 0 B/op 0 allocs/op Benchmark_Time/date_with_timestamp-10 1000000000 0.0000063 ns/op 0 B/op 0 allocs/op Benchmark_Time/day_of_the_week_long_form-10 1000000000 0.0000085 ns/op 0 B/op 0 allocs/op Benchmark_Time/short_weekday,_short_month,_long_format-10 1000000000 0.0000089 ns/op 0 B/op 0 allocs/op Benchmark_Time/short_months-10 1000000000 0.0000070 ns/op 0 B/op 0 allocs/op Benchmark_Time/timestamp_with_time_zone_offset-10 1000000000 0.0000665 ns/op 0 B/op 0 allocs/op Benchmark_Time/short_date_with_timestamp_without_time_zone_offset-10 1000000000 0.0000428 ns/op 0 B/op 0 allocs/op Benchmark_Time/RFC_3339_in_custom_format-10 1000000000 0.0000345 ns/op 0 B/op 0 allocs/op Benchmark_Time/RFC_3339_in_custom_format_before_2000-10 1000000000 0.0000349 ns/op 0 B/op 0 allocs/op Benchmark_Time/no_location-10 1000000000 0.0000035 ns/op 0 B/op 0 allocs/op Benchmark_Time/with_location_-_America-10 1000000000 0.0000104 ns/op 0 B/op 0 allocs/op Benchmark_Time/with_location_-_Asia-10 1000000000 0.0000084 ns/op 0 B/op 0 allocs/op Benchmark_Time/RFC_3339_in_custom_format_before_2000,_ignore_default_location-10 1000000000 0.0000379 ns/op 0 B/op 0 allocs/op PASS ok github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs 0.458s ``` Benchmark's after: ``` goos: darwin goarch: arm64 pkg: github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs Benchmark_Time/simple_short_form-10 1000000000 0.0000054 ns/op 0 B/op 0 allocs/op Benchmark_Time/simple_short_form_with_short_year_and_slashes-10 1000000000 0.0000037 ns/op 0 B/op 0 allocs/op Benchmark_Time/month_day_year-10 1000000000 0.0000053 ns/op 0 B/op 0 allocs/op Benchmark_Time/simple_long_form-10 1000000000 0.0000042 ns/op 0 B/op 0 allocs/op Benchmark_Time/date_with_timestamp-10 1000000000 0.0000087 ns/op 0 B/op 0 allocs/op Benchmark_Time/day_of_the_week_long_form-10 1000000000 0.0000035 ns/op 0 B/op 0 allocs/op Benchmark_Time/short_weekday,_short_month,_long_format-10 1000000000 0.0000036 ns/op 0 B/op 0 allocs/op Benchmark_Time/short_months-10 1000000000 0.0000031 ns/op 0 B/op 0 allocs/op Benchmark_Time/timestamp_with_time_zone_offset-10 1000000000 0.0000491 ns/op 0 B/op 0 allocs/op Benchmark_Time/short_date_with_timestamp_without_time_zone_offset-10 1000000000 0.0000381 ns/op 0 B/op 0 allocs/op Benchmark_Time/RFC_3339_in_custom_format-10 1000000000 0.0000365 ns/op 0 B/op 0 allocs/op Benchmark_Time/RFC_3339_in_custom_format_before_2000-10 1000000000 0.0000364 ns/op 0 B/op 0 allocs/op Benchmark_Time/no_location-10 1000000000 0.0000028 ns/op 0 B/op 0 allocs/op Benchmark_Time/with_location_-_America-10 1000000000 0.0000017 ns/op 0 B/op 0 allocs/op Benchmark_Time/with_location_-_Asia-10 1000000000 0.0000028 ns/op 0 B/op 0 allocs/op Benchmark_Time/RFC_3339_in_custom_format_before_2000,_ignore_default_location-10 1000000000 0.0000393 ns/op 0 B/op 0 allocs/op PASS ok github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs 0.441s ``` **Link to tracking Issue:** Closes https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/35078 **Testing:** Added benchmark test --- pkg/ottl/ottlfuncs/func_time.go | 8 +- pkg/ottl/ottlfuncs/func_time_test.go | 191 +++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 2 deletions(-) diff --git a/pkg/ottl/ottlfuncs/func_time.go b/pkg/ottl/ottlfuncs/func_time.go index b6d793cc3e5d..44371a94de6e 100644 --- a/pkg/ottl/ottlfuncs/func_time.go +++ b/pkg/ottl/ottlfuncs/func_time.go @@ -34,6 +34,11 @@ func Time[K any](inputTime ottl.StringGetter[K], format string, location ottl.Op if format == "" { return nil, fmt.Errorf("format cannot be nil") } + gotimeFormat, err := timeutils.StrptimeToGotime(format) + if err != nil { + return nil, err + } + var defaultLocation *string if !location.IsEmpty() { l := location.Get() @@ -44,7 +49,6 @@ func Time[K any](inputTime ottl.StringGetter[K], format string, location ottl.Op if err != nil { return nil, err } - return func(ctx context.Context, tCtx K) (any, error) { t, err := inputTime.Get(ctx, tCtx) if err != nil { @@ -53,7 +57,7 @@ func Time[K any](inputTime ottl.StringGetter[K], format string, location ottl.Op if t == "" { return nil, fmt.Errorf("time cannot be nil") } - timestamp, err := timeutils.ParseStrptime(format, t, loc) + timestamp, err := timeutils.ParseGotime(gotimeFormat, t, loc) if err != nil { return nil, err } diff --git a/pkg/ottl/ottlfuncs/func_time_test.go b/pkg/ottl/ottlfuncs/func_time_test.go index 41e62edaae04..cc9ce2a795f1 100644 --- a/pkg/ottl/ottlfuncs/func_time_test.go +++ b/pkg/ottl/ottlfuncs/func_time_test.go @@ -284,3 +284,194 @@ func Test_TimeFormatError(t *testing.T) { }) } } + +func Benchmark_Time(t *testing.B) { + locationAmericaNewYork, _ := time.LoadLocation("America/New_York") + locationAsiaShanghai, _ := time.LoadLocation("Asia/Shanghai") + + tests := []struct { + name string + time ottl.StringGetter[any] + format string + expected time.Time + location string + }{ + { + name: "simple short form", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "2023-04-12", nil + }, + }, + format: "%Y-%m-%d", + expected: time.Date(2023, 4, 12, 0, 0, 0, 0, time.Local), + }, + { + name: "simple short form with short year and slashes", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "11/11/11", nil + }, + }, + format: "%d/%m/%y", + expected: time.Date(2011, 11, 11, 0, 0, 0, 0, time.Local), + }, + { + name: "month day year", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "02/04/2023", nil + }, + }, + format: "%m/%d/%Y", + expected: time.Date(2023, 2, 4, 0, 0, 0, 0, time.Local), + }, + { + name: "simple long form", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "July 31, 1993", nil + }, + }, + format: "%B %d, %Y", + expected: time.Date(1993, 7, 31, 0, 0, 0, 0, time.Local), + }, + { + name: "date with timestamp", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "Mar 14 2023 17:02:59", nil + }, + }, + format: "%b %d %Y %H:%M:%S", + expected: time.Date(2023, 3, 14, 17, 02, 59, 0, time.Local), + }, + { + name: "day of the week long form", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "Monday, May 01, 2023", nil + }, + }, + format: "%A, %B %d, %Y", + expected: time.Date(2023, 5, 1, 0, 0, 0, 0, time.Local), + }, + { + name: "short weekday, short month, long format", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "Sat, May 20, 2023", nil + }, + }, + format: "%a, %b %d, %Y", + expected: time.Date(2023, 5, 20, 0, 0, 0, 0, time.Local), + }, + { + name: "short months", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "Feb 15, 2023", nil + }, + }, + format: "%b %d, %Y", + expected: time.Date(2023, 2, 15, 0, 0, 0, 0, time.Local), + }, + { + name: "timestamp with time zone offset", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "2023-05-26 12:34:56 HST", nil + }, + }, + format: "%Y-%m-%d %H:%M:%S %Z", + expected: time.Date(2023, 5, 26, 12, 34, 56, 0, time.FixedZone("HST", -10*60*60)), + }, + { + name: "short date with timestamp without time zone offset", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "2023-05-26T12:34:56 GMT", nil + }, + }, + format: "%Y-%m-%dT%H:%M:%S %Z", + expected: time.Date(2023, 5, 26, 12, 34, 56, 0, time.FixedZone("GMT", 0)), + }, + { + name: "RFC 3339 in custom format", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "2012-11-01T22:08:41+0000 EST", nil + }, + }, + format: "%Y-%m-%dT%H:%M:%S%z %Z", + expected: time.Date(2012, 11, 01, 22, 8, 41, 0, time.FixedZone("EST", 0)), + }, + { + name: "RFC 3339 in custom format before 2000", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "1986-10-01T00:17:33 MST", nil + }, + }, + format: "%Y-%m-%dT%H:%M:%S %Z", + expected: time.Date(1986, 10, 01, 00, 17, 33, 00, time.FixedZone("MST", -7*60*60)), + }, + { + name: "no location", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "2022/01/01", nil + }, + }, + format: "%Y/%m/%d", + expected: time.Date(2022, 01, 01, 0, 0, 0, 0, time.Local), + }, + { + name: "with location - America", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "2023-05-26 12:34:56", nil + }, + }, + format: "%Y-%m-%d %H:%M:%S", + location: "America/New_York", + expected: time.Date(2023, 5, 26, 12, 34, 56, 0, locationAmericaNewYork), + }, + { + name: "with location - Asia", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "2023-05-26 12:34:56", nil + }, + }, + format: "%Y-%m-%d %H:%M:%S", + location: "Asia/Shanghai", + expected: time.Date(2023, 5, 26, 12, 34, 56, 0, locationAsiaShanghai), + }, + { + name: "RFC 3339 in custom format before 2000, ignore default location", + time: &ottl.StandardStringGetter[any]{ + Getter: func(_ context.Context, _ any) (any, error) { + return "1986-10-01T00:17:33 MST", nil + }, + }, + location: "Asia/Shanghai", + format: "%Y-%m-%dT%H:%M:%S %Z", + expected: time.Date(1986, 10, 01, 00, 17, 33, 00, time.FixedZone("MST", -7*60*60)), + }, + } + for _, tt := range tests { + var locOptional ottl.Optional[string] + if tt.location != "" { + locOptional = ottl.NewTestingOptional(tt.location) + } + exprFunc, err := Time(tt.time, tt.format, locOptional) + assert.NoError(t, err) + + t.Run(tt.name, func(t *testing.B) { + result, err := exprFunc(nil, nil) + assert.NoError(t, err) + assert.Equal(t, tt.expected.UnixNano(), result.(time.Time).UnixNano()) + }) + } +}