From fcb6a34f4b3df870dd16ec3ce0029e19923f6bff Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 4 Apr 2020 14:16:33 -0700 Subject: [PATCH] logtail: reduce allocations encoding text Signed-off-by: Brad Fitzpatrick --- logtail/logtail.go | 19 ++++++++++++++++++- logtail/logtail_test.go | 14 ++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/logtail/logtail.go b/logtail/logtail.go index 1793c8b78..f123d7fe9 100644 --- a/logtail/logtail.go +++ b/logtail/logtail.go @@ -334,10 +334,22 @@ func (l *logger) send(jsonBlob []byte) (int, error) { return n, err } +// TODO: instead of allocating, this should probably just append +// directly into the output log buffer. func (l *logger) encodeText(buf []byte, skipClientTime bool) []byte { now := l.timeNow() - b := make([]byte, 0, len(buf)+16) + // Factor in JSON encoding overhead to try to only do one alloc + // in the make below (so appends don't resize the buffer). + overhead := 13 + if !skipClientTime { + overhead += 67 + } + // TODO: do a pass over buf and count how many backslashes will be needed? + // For now just factor in a dozen. + overhead += 12 + + b := make([]byte, 0, len(buf)+overhead) b = append(b, '{') if !skipClientTime { @@ -364,9 +376,14 @@ func (l *logger) encodeText(buf []byte, skipClientTime bool) []byte { case '\\': b = append(b, '\\', '\\') default: + // TODO: what about binary gibberish or non UTF-8? b = append(b, c) } if l.lowMem && i > 254 { + // TODO: this can break a UTF-8 character + // mid-encoding. We don't tend to log + // non-ASCII stuff ourselves, but e.g. client + // names might be. b = append(b, "…"...) break } diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go index 13c115d8c..d26252479 100644 --- a/logtail/logtail_test.go +++ b/logtail/logtail_test.go @@ -7,6 +7,7 @@ package logtail import ( "context" "testing" + "time" ) func TestFastShutdown(t *testing.T) { @@ -18,3 +19,16 @@ func TestFastShutdown(t *testing.T) { }) l.Shutdown(ctx) } + +var sink []byte + +func TestLoggerEncodeTextAllocs(t *testing.T) { + lg := &logger{timeNow: time.Now} + inBuf := []byte("some text to encode") + n := testing.AllocsPerRun(1000, func() { + sink = lg.encodeText(inBuf, false) + }) + if int(n) != 1 { + t.Logf("allocs = %d; want 1", int(n)) + } +}