From d6ea4d0bbfb22b241b9661d72bc8c1724b81beb1 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 18 Oct 2023 11:30:00 +0300 Subject: [PATCH] [#12] Use buffer pool for message encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` goos: linux goarch: amd64 pkg: git.frostfs.info/TrueCloudLab/zapjournald cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz │ 1 │ 2 │ │ sec/op │ sec/op vs base │ Logger/standard/no_fields-8 450.3n ± 2% 452.9n ± 3% ~ (p=0.138 n=10) Logger/standard/application_fields-8 610.4n ± 1% 608.4n ± 1% ~ (p=0.325 n=10) Logger/standard/journald_fields-8 595.8n ± 5% 604.5n ± 1% ~ (p=0.075 n=10) Logger/journald/no_fields-8 1809.0n ± 4% 900.4n ± 1% -50.23% (p=0.000 n=10) Logger/journald/application_fields-8 2.037µ ± 5% 1.087µ ± 1% -46.62% (p=0.000 n=10) Logger/journald/journald_fields-8 2.054µ ± 4% 1.119µ ± 1% -45.52% (p=0.000 n=10) geomean 1.036µ 753.1n -27.33% │ 1 │ 2 │ │ B/op │ B/op vs base │ Logger/standard/no_fields-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Logger/standard/application_fields-8 128.0 ± 0% 128.0 ± 0% ~ (p=1.000 n=10) ¹ Logger/standard/journald_fields-8 64.00 ± 0% 64.00 ± 0% ~ (p=1.000 n=10) ¹ Logger/journald/no_fields-8 1206.000 ± 0% 5.000 ± 0% -99.59% (p=0.000 n=10) Logger/journald/application_fields-8 1494.0 ± 0% 133.0 ± 0% -91.10% (p=0.000 n=10) Logger/journald/journald_fields-8 1478.00 ± 0% 85.00 ± 0% -94.25% (p=0.000 n=10) geomean ² -83.36% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ 1 │ 2 │ │ allocs/op │ allocs/op vs base │ Logger/standard/no_fields-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Logger/standard/application_fields-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ Logger/standard/journald_fields-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ Logger/journald/no_fields-8 29.000 ± 0% 1.000 ± 0% -96.55% (p=0.000 n=10) Logger/journald/application_fields-8 30.000 ± 0% 2.000 ± 0% -93.33% (p=0.000 n=10) Logger/journald/journald_fields-8 31.000 ± 0% 3.000 ± 0% -90.32% (p=0.000 n=10) geomean ² -75.38% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ``` Signed-off-by: Evgenii Stratonikov --- encoder.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ zapjournald.go | 32 +++++++++++++++++++++------- 2 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 encoder.go diff --git a/encoder.go b/encoder.go new file mode 100644 index 0000000..9ba9369 --- /dev/null +++ b/encoder.go @@ -0,0 +1,58 @@ +package zapjournald + +import ( + "bytes" + "strings" + + "go.uber.org/zap/buffer" +) + +var pool = buffer.NewPool() + +func writeFieldBytes(buf *buffer.Buffer, name string, value []byte) { + buf.Write([]byte(name)) + if bytes.ContainsRune(value, '\n') { + // According to the format, if the value includes a newline + // need to write the field name, plus a newline, then the + // size (64bit LE), the field data and a final newline. + + buf.Write([]byte{'\n'}) + appendUint64Binary(buf, uint64(len(value))) + } else { + buf.Write([]byte{'='}) + } + buf.Write(value) + buf.Write([]byte{'\n'}) +} + +func writeField(buf *buffer.Buffer, name string, value string) { + buf.Write([]byte(name)) + if strings.ContainsRune(value, '\n') { + // According to the format, if the value includes a newline + // need to write the field name, plus a newline, then the + // size (64bit LE), the field data and a final newline. + + buf.Write([]byte{'\n'}) + // 1 allocation here. + // binary.Write(w, binary.LittleEndian, uint64(len(value))) + appendUint64Binary(buf, uint64(len(value))) + } else { + buf.Write([]byte{'='}) + } + buf.WriteString(value) + buf.Write([]byte{'\n'}) +} + +func appendUint64Binary(buf *buffer.Buffer, v uint64) { + // Copied from https://github.com/golang/go/blob/go1.21.3/src/encoding/binary/binary.go#L119 + buf.Write([]byte{ + byte(v), + byte(v >> 8), + byte(v >> 16), + byte(v >> 24), + byte(v >> 32), + byte(v >> 40), + byte(v >> 48), + byte(v >> 56), + }) +} diff --git a/zapjournald.go b/zapjournald.go index 208fa97..6ffbdeb 100644 --- a/zapjournald.go +++ b/zapjournald.go @@ -2,6 +2,7 @@ package zapjournald import ( "fmt" + "strconv" "github.com/ssgreg/journald" "go.uber.org/zap/zapcore" @@ -70,14 +71,10 @@ func (core *Core) Write(entry zapcore.Entry, fields []zapcore.Field) error { return err } - // Generate the message. - buffer, err := core.encoder.EncodeEntry(entry, fields) - if err != nil { - return fmt.Errorf("failed to encode log entry: %w", err) - } - defer buffer.Free() + b := pool.Get() + defer b.Free() - message := buffer.String() + writeField(b, "PRIORITY", strconv.Itoa(int(prio))) structuredFields := maps.Clone(core.contextStructuredFields) for _, field := range fields { @@ -85,9 +82,28 @@ func (core *Core) Write(entry zapcore.Entry, fields []zapcore.Field) error { structuredFields[field.Key] = getFieldValue(field) } } + for k, v := range structuredFields { + switch v := v.(type) { + case []byte: + writeFieldBytes(b, k, v) + case string: + writeField(b, k, v) + default: + writeField(b, k, fmt.Sprint(v)) + } + } + + // Generate the message. + buffer, err := core.encoder.EncodeEntry(entry, fields) + if err != nil { + return fmt.Errorf("failed to encode log entry: %w", err) + } + defer buffer.Free() + + writeFieldBytes(b, "MESSAGE", buffer.Bytes()) // Write the message. - return core.j.Send(message, prio, structuredFields) + return core.j.WriteMsg(b.Bytes()) } // Sync flushes buffered logs (not used).