1// Copyright 2022 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package benchmarks
6
7// Handlers for benchmarking.
8
9import (
10	"context"
11	"fmt"
12	"io"
13	"log/slog"
14	"log/slog/internal/buffer"
15	"strconv"
16	"time"
17)
18
19// A fastTextHandler writes a Record to an io.Writer in a format similar to
20// slog.TextHandler, but without quoting or locking. It has a few other
21// performance-motivated shortcuts, like writing times as seconds since the
22// epoch instead of strings.
23//
24// It is intended to represent a high-performance Handler that synchronously
25// writes text (as opposed to binary).
26type fastTextHandler struct {
27	w io.Writer
28}
29
30func newFastTextHandler(w io.Writer) slog.Handler {
31	return &fastTextHandler{w: w}
32}
33
34func (h *fastTextHandler) Enabled(context.Context, slog.Level) bool { return true }
35
36func (h *fastTextHandler) Handle(_ context.Context, r slog.Record) error {
37	buf := buffer.New()
38	defer buf.Free()
39
40	if !r.Time.IsZero() {
41		buf.WriteString("time=")
42		h.appendTime(buf, r.Time)
43		buf.WriteByte(' ')
44	}
45	buf.WriteString("level=")
46	*buf = strconv.AppendInt(*buf, int64(r.Level), 10)
47	buf.WriteByte(' ')
48	buf.WriteString("msg=")
49	buf.WriteString(r.Message)
50	r.Attrs(func(a slog.Attr) bool {
51		buf.WriteByte(' ')
52		buf.WriteString(a.Key)
53		buf.WriteByte('=')
54		h.appendValue(buf, a.Value)
55		return true
56	})
57	buf.WriteByte('\n')
58	_, err := h.w.Write(*buf)
59	return err
60}
61
62func (h *fastTextHandler) appendValue(buf *buffer.Buffer, v slog.Value) {
63	switch v.Kind() {
64	case slog.KindString:
65		buf.WriteString(v.String())
66	case slog.KindInt64:
67		*buf = strconv.AppendInt(*buf, v.Int64(), 10)
68	case slog.KindUint64:
69		*buf = strconv.AppendUint(*buf, v.Uint64(), 10)
70	case slog.KindFloat64:
71		*buf = strconv.AppendFloat(*buf, v.Float64(), 'g', -1, 64)
72	case slog.KindBool:
73		*buf = strconv.AppendBool(*buf, v.Bool())
74	case slog.KindDuration:
75		*buf = strconv.AppendInt(*buf, v.Duration().Nanoseconds(), 10)
76	case slog.KindTime:
77		h.appendTime(buf, v.Time())
78	case slog.KindAny:
79		a := v.Any()
80		switch a := a.(type) {
81		case error:
82			buf.WriteString(a.Error())
83		default:
84			fmt.Fprint(buf, a)
85		}
86	default:
87		panic(fmt.Sprintf("bad kind: %s", v.Kind()))
88	}
89}
90
91func (h *fastTextHandler) appendTime(buf *buffer.Buffer, t time.Time) {
92	*buf = strconv.AppendInt(*buf, t.Unix(), 10)
93}
94
95func (h *fastTextHandler) WithAttrs([]slog.Attr) slog.Handler {
96	panic("fastTextHandler: With unimplemented")
97}
98
99func (*fastTextHandler) WithGroup(string) slog.Handler {
100	panic("fastTextHandler: WithGroup unimplemented")
101}
102
103// An asyncHandler simulates a Handler that passes Records to a
104// background goroutine for processing.
105// Because sending to a channel can be expensive due to locking,
106// we simulate a lock-free queue by adding the Record to a ring buffer.
107// Omitting the locking makes this little more than a copy of the Record,
108// but that is a worthwhile thing to measure because Records are on the large
109// side. Since nothing actually reads from the ring buffer, it can handle an
110// arbitrary number of Records without either blocking or allocation.
111type asyncHandler struct {
112	ringBuffer [100]slog.Record
113	next       int
114}
115
116func newAsyncHandler() *asyncHandler {
117	return &asyncHandler{}
118}
119
120func (*asyncHandler) Enabled(context.Context, slog.Level) bool { return true }
121
122func (h *asyncHandler) Handle(_ context.Context, r slog.Record) error {
123	h.ringBuffer[h.next] = r.Clone()
124	h.next = (h.next + 1) % len(h.ringBuffer)
125	return nil
126}
127
128func (*asyncHandler) WithAttrs([]slog.Attr) slog.Handler {
129	panic("asyncHandler: With unimplemented")
130}
131
132func (*asyncHandler) WithGroup(string) slog.Handler {
133	panic("asyncHandler: WithGroup unimplemented")
134}
135
136// A disabledHandler's Enabled method always returns false.
137type disabledHandler struct{}
138
139func (disabledHandler) Enabled(context.Context, slog.Level) bool  { return false }
140func (disabledHandler) Handle(context.Context, slog.Record) error { panic("should not be called") }
141
142func (disabledHandler) WithAttrs([]slog.Attr) slog.Handler {
143	panic("disabledHandler: With unimplemented")
144}
145
146func (disabledHandler) WithGroup(string) slog.Handler {
147	panic("disabledHandler: WithGroup unimplemented")
148}
149