// Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package benchmarks import ( "context" "flag" "io" "testing" "golang.org/x/exp/slog" "golang.org/x/exp/slog/internal" ) func init() { flag.BoolVar(&internal.IgnorePC, "nopc", false, "do not invoke runtime.Callers") } // We pass Attrs (or zap.Fields) inline because it affects allocations: building // up a list outside of the benchmarked code and passing it in with "..." // reduces measured allocations. func BenchmarkAttrs(b *testing.B) { ctx := context.Background() for _, handler := range []struct { name string h slog.Handler }{ {"disabled", disabledHandler{}}, {"async discard", newAsyncHandler()}, {"fastText discard", newFastTextHandler(io.Discard)}, {"Text discard", slog.NewTextHandler(io.Discard, nil)}, {"JSON discard", slog.NewJSONHandler(io.Discard, nil)}, } { logger := slog.New(handler.h) b.Run(handler.name, func(b *testing.B) { for _, call := range []struct { name string f func() }{ { // The number should match nAttrsInline in slog/record.go. // This should exercise the code path where no allocations // happen in Record or Attr. If there are allocations, they // should only be from Duration.String and Time.String. "5 args", func() { logger.LogAttrs(nil, slog.LevelInfo, TestMessage, slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), ) }, }, { "5 args ctx", func() { logger.LogAttrs(ctx, slog.LevelInfo, TestMessage, slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), ) }, }, { "10 args", func() { logger.LogAttrs(nil, slog.LevelInfo, TestMessage, slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), ) }, }, { "40 args", func() { logger.LogAttrs(nil, slog.LevelInfo, TestMessage, slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), slog.String("string", TestString), slog.Int("status", TestInt), slog.Duration("duration", TestDuration), slog.Time("time", TestTime), slog.Any("error", TestError), ) }, }, } { b.Run(call.name, func(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { call.f() } }) }) } }) } }