Skip to content

v2/log: check log level before using fmt.Sprint calls#19

Merged
Roasbeef merged 12 commits intobtcsuite:masterfrom
ellemouton:checkLevel
Nov 27, 2024
Merged

v2/log: check log level before using fmt.Sprint calls#19
Roasbeef merged 12 commits intobtcsuite:masterfrom
ellemouton:checkLevel

Conversation

@ellemouton
Copy link
Copy Markdown
Contributor

@ellemouton ellemouton commented Nov 21, 2024

The Enabled call is also called later on in the slog.Logger's log call but by then we have already built the string with fmt.Sprint which can result in quite a big performance hit.

Thanks to @bitromortac for realising this!

EDIT: this has since been updated quite a bit. During review, you can use the new BenchmarkLogger and BenchmarkV1vsV2Loggers benchmark tests to evaluate each commits impact.

Note that this has also been expanded to add a WithPrefix method on the Logger so that we can get rid of the PrefixedLogger in LND

@ellemouton
Copy link
Copy Markdown
Contributor Author

cc @Roasbeef

Copy link
Copy Markdown

@guggero guggero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉

Copy link
Copy Markdown

@bitromortac bitromortac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thank you for the fix 🎉! LGTM ⚡

@Roasbeef
Copy link
Copy Markdown
Member

Should we add some benchmarks here as a tool to catch regressions like this in the future?

Migth also be interesting to compare v1 to v2 w.r.t overhead.

@Roasbeef
Copy link
Copy Markdown
Member

call but by then we have already built the string with fmt.Sprint which can result in quite a big performance hit.

Seems the log closure pattern is an alternative? So we'd only construct the string later down the call stack if we know the log level matches.

@ellemouton
Copy link
Copy Markdown
Contributor Author

cool yeah great idea re benchmark and also having lazy compute values via closure. Currently implementing - will push up by EoD

@ellemouton
Copy link
Copy Markdown
Contributor Author

accidental push soz. will ping when ready for re-review

@ellemouton ellemouton force-pushed the checkLevel branch 3 times, most recently from 965a07b to 913b97d Compare November 25, 2024 09:54
@ellemouton
Copy link
Copy Markdown
Contributor Author

Cool cool cool, this has been updated quite a bit. Please see the new benchmark tests which can be used to evaluate the effectiveness of each commit here. Note that the main thing this aims to do is to let the new f methods be as close to the performance of the old v1 f methods.

I've also added a new WithPrefix method on the Logger so that we can get rid of the Prefixed logger in LND

@ellemouton
Copy link
Copy Markdown
Contributor Author

ellemouton commented Nov 25, 2024

Some results:

Importantly, we can see from these results that in the case where a log line is skipped due to the
current log level, with this PR the performance matches that of the V1 logger for both the simple
log and complex log case.

V1 logger (baseline)

BenchmarkV1vsV2Loggers/btclog_v1_simple-10                     159.9   ns/op
BenchmarkV1vsV2Loggers/btclog_v1_skipped_simple-10               2.085 ns/op
BenchmarkV1vsV2Loggers/btclog_v1_complex-10                   1695     ns/op
BenchmarkV1vsV2Loggers/btclog_v1_skipped_complex-10            197.4   ns/op

V2 Logger (current master branch)

BenchmarkV1vsV2Loggers/btclog_v2_simple-10                     364.9  ns/op
BenchmarkV1vsV2Loggers/btclog_v2_skipped_simple-10              33.34 ns/op
BenchmarkV1vsV2Loggers/btclog_v2_complex-10                    1972   ns/op
BenchmarkV1vsV2Loggers/btclog_v2_skipped_complex-10            1611   ns/op

V2 Logger with this PR:

BenchmarkV1vsV2Loggers/btclog_v2_simple-10                     299.9   ns/op
BenchmarkV1vsV2Loggers/btclog_v2_skipped_simple-10               2.080 ns/op
BenchmarkV1vsV2Loggers/btclog_v2_complex-10                   1899     ns/op
BenchmarkV1vsV2Loggers/btclog_v2_skipped_complex-10            198.9   ns/op

Copy link
Copy Markdown

@guggero guggero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice performance tests and improvements 💯

level: LevelInfo,
logFunc: func(log Logger) {
ctx := context.Background()
log.InfoS(ctx, "msg", Hex("hex_val", []byte{0x01, 0x02}))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Did you mean to call Hex6 here too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I wanted to test both :) But I'll change this one to have more than 6 bytes 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and I'll add a less-than-6-byte test for Hex6 👍

Copy link
Copy Markdown
Contributor Author

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the speedy review @guggero!! 🎉 addressed all comments

level: LevelInfo,
logFunc: func(log Logger) {
ctx := context.Background()
log.InfoS(ctx, "msg", Hex("hex_val", []byte{0x01, 0x02}))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I wanted to test both :) But I'll change this one to have more than 6 bytes 👍

level: LevelInfo,
logFunc: func(log Logger) {
ctx := context.Background()
log.InfoS(ctx, "msg", Hex("hex_val", []byte{0x01, 0x02}))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and I'll add a less-than-6-byte test for Hex6 👍

Copy link
Copy Markdown

@bitromortac bitromortac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvements 🔥! I've also done some pathfinding benchmarks with lnd (500 random payments, no mission control data). The first benchmark is lnd with v2 logging before this PR, the second one is with v1 logging, and the last one is with this PR. So I think practially, there shouldn't be a noticable difference now 🎉.

benchmark

@Roasbeef
Copy link
Copy Markdown
Member

Looking good to land, only blocker is this failing test:

=== RUN   TestDefaultHandler/Basic_calls_and_levels
    handler_test.go:33: Log result mismatch. Expected
        "1970-01-01 02:01:40.000 [INF]: Test Basic Log
        1970-01-01 02:01:40.000 [DBG]: Test basic log with format
        ", got
        "1969-12-31 18:01:40.000 [INF]: Test Basic Log
        1969-12-31 18:01:40.000 [DBG]: Test basic log with format
        "

Move the test cases to the bottom of the file so that we can add more
tests without the results (line number) of the call-site test changing.
Like the Hex helper, this helper prints a byte array in hex form but
this one prints a maximum of 6 bytes.
Along with helpers to convert closures into slog Attributes.
This commit adds two benchmark tests:

1) BenchmarkLogger tests the new V2 logger under various scenarios.
2) BenchmarkV1vsV2Loggers tests the performance of the V2 logger against
   that of the V1 logger.

As of this commit, the biggest issue is the time it takes for the new v2
logger to perform a log action that actually gets skipped due to the
current log level. This is due to a bug that builds the log string using
fmt.Sprintf _before_ the logger's Enabled check is run against the
current log level. This will be improved upon in the upcoming commits.

The BenchmarkLogger test will also help to assess the various
improvements that will be make in the upcoming commits.

We ideally want BenchmarkV2vsV2Loggers to show that the performance
between the same methods on the two loggers does not change much. This
will be improved upon in upcoming commits.
Instead of a fresh context per call to the old interface methods.
In this commit, we start checking the log level of the logger's handler
before we do any more evaluation. This does not change behaviour since
the level is checked by the slog library itself later on but by then we
have performed some evaluation which impacts performance.

In the benchmark tests, the biggest differences should be noticeable in
the "skipped" tests.
The default styled options use `fmt.Sprintf` calls. This commit removes
these defaults which improves the performance of the V2 logger for the
cases when styled options are not provided.
To prioritise performance of the logger, we opt for code duplication
instead of adding an extra code jump to the call-stack.
In this commit, instead of depending on the Handler for Level handling,
we add a level object to the logger itself. This makes checking the log
level before computing any expensive checks much faster. The handler
still needs to carry its own level member so that it can properly
implement the `Enabled` method of the Handler interface. This means that
for the cases where a log line is skipped due to log level not being
met, the performance will be faster. However, in the cases where the log
line will be printed, execution will be slightly slower due to the fact
that the level will in essence be checked twice.
So that a sub-system logger can be derived directly from a logger rather
than needing to first create a new handler.
Which can be used to derive a new prefixed logger which will prefix a
provided string to any message printed by the logger.
Copy link
Copy Markdown
Contributor Author

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for the thorough testing @bitromortac! 🙏 addressed all comments

@ellemouton
Copy link
Copy Markdown
Contributor Author

think this is g2g now @Roasbeef 🙏

Copy link
Copy Markdown
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🐦‍🔥

@Roasbeef Roasbeef merged commit e4cba5d into btcsuite:master Nov 27, 2024
@ellemouton ellemouton deleted the checkLevel branch November 27, 2024 03:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants