blob: 90c7aed233fa67e348e24fc646658b19d181ac65 [file] [log] [blame]
// 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
// Handlers for benchmarking.
import (
// A fastTextHandler writes a Record to an io.Writer in a format similar to
// slog.TextHandler, but without quoting or locking. It has a few other
// performance-motivated shortcuts, like writing times as seconds since the
// epoch instead of strings.
// It is intended to represent a high-performance Handler that synchronously
// writes text (as opposed to binary).
type fastTextHandler struct {
w io.Writer
func newFastTextHandler(w io.Writer) slog.Handler {
return &fastTextHandler{w: w}
func (h *fastTextHandler) Enabled(context.Context, slog.Level) bool { return true }
func (h *fastTextHandler) Handle(_ context.Context, r slog.Record) error {
buf := buffer.New()
defer buf.Free()
if !r.Time.IsZero() {
h.appendTime(buf, r.Time)
buf.WriteByte(' ')
*buf = strconv.AppendInt(*buf, int64(r.Level), 10)
buf.WriteByte(' ')
r.Attrs(func(a slog.Attr) bool {
buf.WriteByte(' ')
h.appendValue(buf, a.Value)
return true
_, err := h.w.Write(*buf)
return err
func (h *fastTextHandler) appendValue(buf *buffer.Buffer, v slog.Value) {
switch v.Kind() {
case slog.KindString:
case slog.KindInt64:
*buf = strconv.AppendInt(*buf, v.Int64(), 10)
case slog.KindUint64:
*buf = strconv.AppendUint(*buf, v.Uint64(), 10)
case slog.KindFloat64:
*buf = strconv.AppendFloat(*buf, v.Float64(), 'g', -1, 64)
case slog.KindBool:
*buf = strconv.AppendBool(*buf, v.Bool())
case slog.KindDuration:
*buf = strconv.AppendInt(*buf, v.Duration().Nanoseconds(), 10)
case slog.KindTime:
h.appendTime(buf, v.Time())
case slog.KindAny:
a := v.Any()
switch a := a.(type) {
case error:
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
func (h *fastTextHandler) appendTime(buf *buffer.Buffer, t time.Time) {
*buf = strconv.AppendInt(*buf, t.Unix(), 10)
func (h *fastTextHandler) WithAttrs([]slog.Attr) slog.Handler {
panic("fastTextHandler: With unimplemented")
func (*fastTextHandler) WithGroup(string) slog.Handler {
panic("fastTextHandler: WithGroup unimplemented")
// An asyncHandler simulates a Handler that passes Records to a
// background goroutine for processing.
// Because sending to a channel can be expensive due to locking,
// we simulate a lock-free queue by adding the Record to a ring buffer.
// Omitting the locking makes this little more than a copy of the Record,
// but that is a worthwhile thing to measure because Records are on the large
// side.
type asyncHandler struct {
ringBuffer [100]slog.Record
next int
func newAsyncHandler() *asyncHandler {
return &asyncHandler{}
func (*asyncHandler) Enabled(context.Context, slog.Level) bool { return true }
func (h *asyncHandler) Handle(_ context.Context, r slog.Record) error {
h.ringBuffer[] = r.Clone() = ( + 1) % len(h.ringBuffer)
return nil
func (*asyncHandler) WithAttrs([]slog.Attr) slog.Handler {
panic("asyncHandler: With unimplemented")
func (*asyncHandler) WithGroup(string) slog.Handler {
panic("asyncHandler: WithGroup unimplemented")
type disabledHandler struct{}
func (disabledHandler) Enabled(context.Context, slog.Level) bool { return false }
func (disabledHandler) Handle(context.Context, slog.Record) error { panic("should not be called") }
func (disabledHandler) WithAttrs([]slog.Attr) slog.Handler {
panic("disabledHandler: With unimplemented")
func (disabledHandler) WithGroup(string) slog.Handler {
panic("disabledHandler: WithGroup unimplemented")