Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache transformations within a phase #537

Merged
merged 10 commits into from
Dec 26, 2022

Conversation

anuraaga
Copy link
Contributor

@anuraaga anuraaga commented Dec 22, 2022

We currently compute the same transformation per variable many many times per phase. This adds a cache to reduce this repetitive computation. It notably reduces allocations by half in the CRS benchmarks, which has some performance impact here and will have bigger performance impact where GC is slower like wasm.

After

BenchmarkCRSLargePOST
BenchmarkCRSLargePOST-10    	      12	  95001208 ns/op	 3735898 B/op	   45446 allocs/op
BenchmarkCRSSimplePOST
BenchmarkCRSSimplePOST-10    	     631	   1887620 ns/op	 1294996 B/op	   45180 allocs/op

Before

BenchmarkCRSLargePOST
BenchmarkCRSLargePOST-10    	      12	  97733875 ns/op	 7036534 B/op	   46887 allocs/op
BenchmarkCRSSimplePOST
BenchmarkCRSSimplePOST-10    	     631	   1883263 ns/op	 1302137 B/op

@anuraaga anuraaga requested a review from a team as a code owner December 22, 2022 05:14
@@ -154,3 +159,15 @@ func NewRuleGroup() RuleGroup {
rules: []*Rule{},
}
}

type transformationKey struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@M4tteoP The main difference with your initial approach is using a struct instead of string for the key, which I failed to communicate well offline :) There is significant cost including allocation to concatenate strings, while filling a struct is much faster and all on the stack so no allocation

Copy link
Member

@M4tteoP M4tteoP Dec 23, 2022

Choose a reason for hiding this comment

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

Rag, Many thanks for taking over it while explaining how you did it! Very appreciated.

@@ -104,6 +104,8 @@ type Transaction struct {
audit bool

variables TransactionVariables

transformationCache map[transformationKey]transformationValue
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@M4tteoP Though besides that reusing maps is also important, adding to the transaction which is already pooled made it pretty simple

@@ -109,7 +110,41 @@ func BenchmarkCRSSimplePOST(b *testing.B) {
tx.AddRequestHeader("Accept", "application/json")
tx.AddRequestHeader("Content-Type", "application/x-www-form-urlencoded")
tx.ProcessRequestHeaders()
if _, err := tx.ResponseBodyWriter().Write([]byte("parameters2=and&other2=Stuff")); err != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If anyone was trying to use this benchmark and confused like I was, it's because of the typo here preventing post body processing from happening at all (response body processing is disabled by default I guess)

Copy link
Member

Choose a reason for hiding this comment

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

🙀

@@ -280,8 +315,30 @@ func crsWAF(t testing.TB) coraza.WAF {
if err != nil {
t.Fatal(err)
}
customTestingConfig := `
Copy link
Contributor Author

@anuraaga anuraaga Dec 22, 2022

Choose a reason for hiding this comment

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

I added this so the benchmark runs the same rules as FTW (mostly for paranoia level), I'm not sure if there is a practical difference though

@codecov-commenter
Copy link

codecov-commenter commented Dec 22, 2022

Codecov Report

Base: 78.28% // Head: 78.41% // Increases project coverage by +0.12% 🎉

Coverage data is based on head (4b698a0) compared to base (f507750).
Patch coverage: 100.00% of modified lines in pull request are covered.

Additional details and impacted files
@@            Coverage Diff             @@
##           v3/dev     #537      +/-   ##
==========================================
+ Coverage   78.28%   78.41%   +0.12%     
==========================================
  Files         145      145              
  Lines        6884     6925      +41     
==========================================
+ Hits         5389     5430      +41     
  Misses       1263     1263              
  Partials      232      232              
Flag Coverage Δ
default 73.59% <94.82%> (+0.11%) ⬆️
examples 27.47% <72.41%> (+0.29%) ⬆️
ftw 56.56% <100.00%> (+0.26%) ⬆️
tinygo 71.67% <94.82%> (+0.13%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
internal/corazawaf/transaction.go 79.85% <ø> (ø)
internal/corazawaf/rule.go 94.61% <100.00%> (+0.86%) ⬆️
internal/corazawaf/rulegroup.go 94.18% <100.00%> (+0.28%) ⬆️
internal/corazawaf/waf.go 91.66% <100.00%> (+0.05%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

internal/corazawaf/rule.go Outdated Show resolved Hide resolved
@@ -155,11 +158,11 @@ func (r *Rule) Status() int {
// Evaluate will evaluate the current rule for the indicated transaction
// If the operator matches, actions will be evaluated, and it will return
// the matched variables, keys and values (MatchData)
func (r *Rule) Evaluate(tx rules.TransactionState) []types.MatchData {
return r.doEvaluate(tx.(*Transaction))
func (r *Rule) Evaluate(tx rules.TransactionState, cache map[transformationKey]transformationValue) []types.MatchData {
Copy link
Member

Choose a reason for hiding this comment

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

This change makes me wonder if evaluate should be exported or not as it is only used inside this package. Does not need to happen in this PR but this is a good justification cc @jptosso @anuraaga @M4tteoP

ars, es := r.executeTransformations(arg.Value())
args = []string{ars}
errs = es
switch {
Copy link
Member

Choose a reason for hiding this comment

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

I think 'doEvaluate' method is complex enough already so maybe we could extract this in another method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup extracted

@anuraaga anuraaga merged commit d2a0e8e into corazawaf:v3/dev Dec 26, 2022
anuraaga added a commit to anuraaga/coraza that referenced this pull request Dec 26, 2022
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