// 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" "internal/race" "io" "log/slog" "log/slog/internal" "testing" ) func init() { flag.BoolVar(&internal.IgnorePC, "nopc", false, "do not invoke runtime.Callers") } // We pass Attrs 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 skipRace bool }{ {"disabled", disabledHandler{}, false}, {"async discard", newAsyncHandler(), true}, {"fastText discard", newFastTextHandler(io.Discard), false}, {"Text discard", slog.NewTextHandler(io.Discard, nil), false}, {"JSON discard", slog.NewJSONHandler(io.Discard, nil), false}, } { logger := slog.New(handler.h) b.Run(handler.name, func(b *testing.B) { if handler.skipRace && race.Enabled { b.Skip("skipping benchmark in race mode") } 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), ) }, }, { // Try an extreme value to see if the results are reasonable. "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() } }) }) } }) } }