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 (
"context"
"fmt"
"io"
"strconv"
"time"
"golang.org/x/exp/slog"
"golang.org/x/exp/slog/internal/buffer"
)
// 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() {
buf.WriteString("time=")
h.appendTime(buf, r.Time)
buf.WriteByte(' ')
}
buf.WriteString("level=")
*buf = strconv.AppendInt(*buf, int64(r.Level), 10)
buf.WriteByte(' ')
buf.WriteString("msg=")
buf.WriteString(r.Message)
r.Attrs(func(a slog.Attr) bool {
buf.WriteByte(' ')
buf.WriteString(a.Key)
buf.WriteByte('=')
h.appendValue(buf, a.Value)
return true
})
buf.WriteByte('\n')
_, err := h.w.Write(*buf)
return err
}
func (h *fastTextHandler) appendValue(buf *buffer.Buffer, v slog.Value) {
switch v.Kind() {
case slog.KindString:
buf.WriteString(v.String())
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:
buf.WriteString(a.Error())
default:
buf.WriteString(fmt.Sprint(a))
}
default:
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[h.next] = r.Clone()
h.next = (h.next + 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")
}