Skip to content

JS expressions for trigger filter#3771

Closed
slinkydeveloper wants to merge 21 commits into
knative:masterfrom
slinkydeveloper:el
Closed

JS expressions for trigger filter#3771
slinkydeveloper wants to merge 21 commits into
knative:masterfrom
slinkydeveloper:el

Conversation

@slinkydeveloper
Copy link
Copy Markdown
Contributor

@slinkydeveloper slinkydeveloper commented Aug 3, 2020

Signed-off-by: Francesco Guardiani francescoguard@gmail.com

Fixes #3359

Proposed Changes

  • Use goja JS engine to compute expressions for the trigger filter
  • TriggerFilter (v1 and v1beta1) now has a field called Expression to code the JS expression to perform the filtering
  • Added unit tests and e2e tests to the new filtering capabilities

Release Note

Now you can specify Javascript expressions in trigger to filter events 

Docs
Docs will be available in next PRs in knative.dev/docs

The API to access the event attributes are as simple as event.attributename or event.extensionname. An example expression:

event.id.indexOf("francesco") != -1 &&
  event.time != null &&
  event.time.getFullYear() >= 2020 &&
  event.exta != null

This filter will accept an event with id containing the string francesco, time with year greater or equal to 2020 and with an extension named exta.

There is no access to the data of the event. I believe this requires a separate discussion because there are a lot of open questions that we need to address:

  • How do we serve data? as byte array? as string?
  • Do we want to serve json data as js objects? how much is heavy?

Some notes

  • When we run the trigger expression there is a timeout to the code execution, in order to avoid bad code to run indefinitely
  • Every time we run the filter, the runtime environment (called vm *goja.Runtime in the code) is re-created from scratch, so one filter execution cannot affect any other filter
  • The ast is parsed separately from the code execution, so we can cache it. I also implemented a check for the ast token, in order to discard bad code (code that is not an expression)

PR is still pretty much drafted, I would love to hear some feedback about the approach.

TODO

  • Cache parsed expressions ast (maybe not in this pr, because it may require some further changes to the filter_handler code)
  • Validate on webhook side the expression, to reject if it's a bad one
  • Define APIs to access event within the expression
  • Doc stuff (in knative.dev/docs too)
  • Proper unit tests
  • Proper e2e tests

@knative-prow-robot knative-prow-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 3, 2020
@googlebot googlebot added the cla: yes Indicates the PR's author has signed the CLA. label Aug 3, 2020
@knative-prow-robot knative-prow-robot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Aug 3, 2020
@knative-prow-robot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: slinkydeveloper

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@knative-prow-robot knative-prow-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 3, 2020
@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

There is also this newer js engine https://github.com/dop251/goja we should give a try. I obviously avoided all bindings to v8 because they require cgo in place and that's a huge pain for our infra

@grantr grantr self-requested a review August 4, 2020 19:21
@knative-prow-robot knative-prow-robot added the area/test-and-release Test infrastructure, tests or release label Aug 5, 2020
@slinkydeveloper slinkydeveloper changed the title [WIP] JS expressions for trigger filter JS expressions for trigger filter Aug 5, 2020
@knative-prow-robot knative-prow-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 5, 2020
@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

/retest

@antoineco
Copy link
Copy Markdown
Contributor

Not all superheroes wear capes.
-- Abraham Lincoln

This is some fantastic work, sire Francesco <3

Copy link
Copy Markdown
Member

@pierDipi pierDipi left a comment

Choose a reason for hiding this comment

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

Looks great!

Comment thread pkg/eventfilter/filter_engine.go Outdated
Comment thread pkg/eventfilter/filter_engine.go Outdated
Comment thread pkg/eventfilter/filter_engine.go Outdated
Comment on lines +46 to +71
for name, fn := range map[string]func() interface{}{
"specversion": func() interface{} {
return event.SpecVersion()
},
"type": func() interface{} {
return event.Type()
},
"source": func() interface{} {
return event.Source()
},
"subject": func() interface{} {
return event.Subject()
},
"id": func() interface{} {
return event.ID()
},
"time": func() interface{} {
return event.Time()
},
"dataschema": func() interface{} {
return event.DataSchema()
},
"datacontenttype": func() interface{} {
return event.DataContentType()
},
} {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Splitting the map declaration and usage would be cleaner.
Does sdk-go have constants for keys?

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.

Does sdk-go have constants for keys?

Nope, but in any case these getters are "spec version" agnostic (look at dataschema vs schemaurl) and i intentionally wrote that code to decouple sdk-go apis and filtering apis, which i think should be something stable (probably a change to that requires bumping the trigger crd version)

Comment thread pkg/eventfilter/filter_engine.go Outdated
Comment thread pkg/eventfilter/filter_engine.go Outdated
Comment thread test/e2e/broker_expression_test.go Outdated
Comment thread pkg/eventfilter/filter_engine.go Outdated
Comment on lines +84 to +111
func bindValue(vm *goja.Runtime, propName string, eventVal interface{}, obj *goja.Object) error {
return obj.DefineAccessorProperty(propName, vm.ToValue(func(fc goja.FunctionCall) goja.Value {
return coerceToJsTypes(vm, eventVal)
}), nil, goja.FLAG_FALSE, goja.FLAG_TRUE)
}

func bindValueExtractor(vm *goja.Runtime, propName string, extractFn func() interface{}, obj *goja.Object) error {
return obj.DefineAccessorProperty(propName, vm.ToValue(func(fc goja.FunctionCall) goja.Value {
return coerceToJsTypes(vm, extractFn())
}), nil, goja.FLAG_FALSE, goja.FLAG_TRUE)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

They are similar, can we do

func bindValueExtractor(vm *goja.Runtime, propName string, extractFn func() interface{}, obj *goja.Object) error {
    return bindValue(vm, propName, extractFn(), obj)
}

Or get rid of bindValueExtractor completely?

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.

nope because i want to keep the extraction lazy, that's the reason why we have these two methods. in one case the bindValueExtractor enclose in the getter closure the function pointer, in the other case it enclose the value itself

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nope because i want to keep the extraction lazy

What is the goal? Speed?

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.

What is the goal? Speed?

Mostly avoid unnecessary memory allocations & type conversions to copy all the stuff inside the engine, when you just access a bunch of fields of the event

Comment thread test/e2e/helpers/broker_expression_test_helper.go Outdated
Comment on lines +66 to +59
client.CreateTriggerOrFailV1Beta1(triggerNameOk,
resources.WithSubscriberServiceRefForTriggerV1Beta1(subscriberNamePass),
resources.WithExpressionTriggerFilterV1Beta1("event.id != null"),
resources.WithBrokerV1Beta1(brokerName),
)

subscriberNameFail := "recordevents-expression-fail"
eventTrackerFail, _ := recordevents.StartEventRecordOrFail(client, subscriberNameFail)
defer eventTrackerFail.Cleanup()

triggerNameFail := "trigger-expression-fail"
client.CreateTriggerOrFailV1Beta1(triggerNameFail,
resources.WithSubscriberServiceRefForTriggerV1Beta1(subscriberNameFail),
resources.WithExpressionTriggerFilterV1Beta1("event.id == null"),
resources.WithBrokerV1Beta1(brokerName),
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would test Trigger v1 as well.

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.

In theory it's already tested b/c the v1beta1 get converted to v1

Comment thread pkg/apis/eventing/v1/trigger_validation.go Outdated
@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

Open question for people here: should this pr add filter.expression only to v1 apis, more than both on v1beta1 and v1?

@aslom
Copy link
Copy Markdown
Contributor

aslom commented Aug 5, 2020

It seems that with this code only expression language supported will be JS?

@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

It seems that with this code only expression language supported will be JS?

Yes that's the idea

@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

@aslom I renamed the field expression to jsExpression, this way we ensure the extendibility of filter expressions

@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

/retest

@knative-prow-robot knative-prow-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 5, 2020
@knative-prow-robot knative-prow-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 6, 2020
Comment thread pkg/eventfilter/filter_engine.go Outdated
Comment thread pkg/eventfilter/filter_engine_test.go Outdated
Comment thread test/e2e/broker_expression_test.go Outdated
@knative-test-reporter-robot
Copy link
Copy Markdown

The following jobs failed:

Test name Triggers Retries
pull-knative-eventing-integration-tests 0/3

Failed non-flaky tests preventing automatic retry of pull-knative-eventing-integration-tests:

test/e2e.TestDefaultBrokerWithManyTriggers/test_default_broker_with_many_attribute_and_extension_triggers
test/e2e.TestDefaultBrokerWithManyTriggers
test/e2e.TestBrokerChannelFlowTriggerV1BrokerV1/Channel-messaging.knative.dev/v1
test/e2e.TestBrokerChannelFlowTriggerV1BrokerV1

@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

/retest

@knative-prow-robot knative-prow-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 11, 2020
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Added doc about multiple filter types
Removed useless code in e2e test
Some nits in errors of filter_engine.go

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

Raw results:

BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_id-8         	1000000000	         2.83 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_id-8         	1000000000	         2.91 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_id-8         	1000000000	         2.94 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_id-8         	1000000000	         3.16 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_id-8         	1000000000	         3.04 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_id-8              	 1787139	      3420 ns/op	    2453 B/op	      19 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_id-8              	 1749876	      3520 ns/op	    2453 B/op	      19 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_id-8              	 1647187	      3457 ns/op	    2453 B/op	      19 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_id-8              	 1667799	      3594 ns/op	    2453 B/op	      19 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_id-8              	 1622290	      3670 ns/op	    2453 B/op	      19 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8         	1000000000	         3.52 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8         	1000000000	         3.48 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8         	1000000000	         3.38 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8         	1000000000	         3.31 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8         	1000000000	         3.31 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8              	 1656928	      3698 ns/op	    2517 B/op	      20 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8              	 1576752	      3983 ns/op	    2517 B/op	      20 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8              	 1567952	      3837 ns/op	    2517 B/op	      20 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8              	 1580230	      3884 ns/op	    2517 B/op	      20 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8              	 1526204	      3844 ns/op	    2517 B/op	      20 allocs/op
BenchmarkAttributesFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                             	1000000000	         3.52 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                             	1000000000	         3.51 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                             	1000000000	         3.53 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                             	1000000000	         3.39 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                             	1000000000	         3.38 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                  	 1595536	      3920 ns/op	    2645 B/op	      22 allocs/op
BenchmarkAttributesFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                  	 1516168	      3874 ns/op	    2645 B/op	      22 allocs/op
BenchmarkAttributesFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                  	 1493767	      4106 ns/op	    2645 B/op	      22 allocs/op
BenchmarkAttributesFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                  	 1492711	      4015 ns/op	    2645 B/op	      22 allocs/op
BenchmarkAttributesFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                  	 1522333	      4020 ns/op	    2645 B/op	      22 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_id-8                                             	 2253002	      2764 ns/op	    1632 B/op	      33 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_id-8                                             	 2200225	      2761 ns/op	    1632 B/op	      33 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_id-8                                             	 2216330	      2723 ns/op	    1632 B/op	      33 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_id-8                                             	 2213048	      2725 ns/op	    1632 B/op	      33 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_id-8                                             	 2152292	      2814 ns/op	    1632 B/op	      33 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_id-8                                                  	   26928	    196487 ns/op	  142050 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_id-8                                                  	   32259	    177403 ns/op	  142020 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_id-8                                                  	   32337	    177426 ns/op	  142030 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_id-8                                                  	   33133	    175308 ns/op	  142029 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_id-8                                                  	   33296	    177772 ns/op	  142024 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8           	  473851	     13175 ns/op	    7728 B/op	     124 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8           	  457773	     12900 ns/op	    7728 B/op	     124 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8           	  441338	     13374 ns/op	    7728 B/op	     124 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8           	  462418	     13019 ns/op	    7728 B/op	     124 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8           	  448740	     13094 ns/op	    7728 B/op	     124 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8                	   30570	    185851 ns/op	  142260 B/op	    1782 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8                	   29923	    185826 ns/op	  142266 B/op	    1782 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8                	   30957	    182368 ns/op	  142262 B/op	    1782 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8                	   30241	    183604 ns/op	  142265 B/op	    1782 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8                	   31306	    182780 ns/op	  142260 B/op	    1782 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                               	 1000000	      5235 ns/op	    2772 B/op	      52 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                               	 1000000	      5242 ns/op	    2772 B/op	      52 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                               	 1000000	      5150 ns/op	    2772 B/op	      52 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                               	 1000000	      5156 ns/op	    2772 B/op	      52 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                               	 1000000	      5238 ns/op	    2772 B/op	      52 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                    	   30036	    183991 ns/op	  142024 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                    	   30373	    179828 ns/op	  142026 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                    	   31456	    179258 ns/op	  142028 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                    	   31030	    181553 ns/op	  142025 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                    	   30190	    181634 ns/op	  142021 B/op	    1768 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_if_then-8                                                    	  801115	      6313 ns/op	    3056 B/op	      60 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_if_then-8                                                    	  907027	      6345 ns/op	    3056 B/op	      60 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_if_then-8                                                    	  872156	      6319 ns/op	    3056 B/op	      60 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_if_then-8                                                    	  930031	      6302 ns/op	    3056 B/op	      60 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_if_then-8                                                    	  909020	      6308 ns/op	    3056 B/op	      60 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_if_then-8                                                         	   30309	    182721 ns/op	  142053 B/op	    1770 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_if_then-8                                                         	   30831	    181332 ns/op	  142052 B/op	    1770 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_if_then-8                                                         	   30343	    180305 ns/op	  142059 B/op	    1770 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_if_then-8                                                         	   31112	    181957 ns/op	  142050 B/op	    1770 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_if_then-8                                                         	   30060	    180316 ns/op	  142057 B/op	    1770 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_nested_logic-8                                               	  540919	     10007 ns/op	    4992 B/op	      88 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_nested_logic-8                                               	  581228	     10050 ns/op	    4992 B/op	      88 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_nested_logic-8                                               	  541041	     10081 ns/op	    4992 B/op	      88 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_nested_logic-8                                               	  582280	     10042 ns/op	    4992 B/op	      88 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_nested_logic-8                                               	  568146	     10105 ns/op	    4992 B/op	      88 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_nested_logic-8                                                    	   31276	    182307 ns/op	  142089 B/op	    1772 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_nested_logic-8                                                    	   30637	    179924 ns/op	  142084 B/op	    1772 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_nested_logic-8                                                    	   31437	    180167 ns/op	  142086 B/op	    1772 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_nested_logic-8                                                    	   30988	    180885 ns/op	  142086 B/op	    1772 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_nested_logic-8                                                    	   30457	    182041 ns/op	  142088 B/op	    1772 allocs/op

Benchstat:

name                                                                                        time/op
AttributesFilter/Creation:_Pass_with_exact_match_of_id-8                                    2.98ns ± 6%
AttributesFilter/Run:_Pass_with_exact_match_of_id-8                                         3.53µs ± 4%
AttributesFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8  3.40ns ± 4%
AttributesFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8       3.85µs ± 4%
AttributesFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                      3.47ns ± 2%
AttributesFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                           3.99µs ± 3%
JsEngineFilter/Creation:_Pass_with_exact_match_of_id-8                                      2.76µs ± 2%
JsEngineFilter/Run:_Pass_with_exact_match_of_id-8                                            177µs ± 1%
JsEngineFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8    13.1µs ± 2%
JsEngineFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8          184µs ± 1%
JsEngineFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                        5.20µs ± 1%
JsEngineFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                              181µs ± 2%
JsEngineFilter/Creation:_No_pass_with_if_then-8                                             6.32µs ± 0%
JsEngineFilter/Run:_No_pass_with_if_then-8                                                   181µs ± 1%
JsEngineFilter/Creation:_No_pass_with_nested_logic-8                                        10.1µs ± 0%
JsEngineFilter/Run:_No_pass_with_nested_logic-8                                              181µs ± 1%

name                                                                                        alloc/op
AttributesFilter/Creation:_Pass_with_exact_match_of_id-8                                     0.00B     
AttributesFilter/Run:_Pass_with_exact_match_of_id-8                                         2.45kB ± 0%
AttributesFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8   0.00B     
AttributesFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8       2.52kB ± 0%
AttributesFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                       0.00B     
AttributesFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                           2.65kB ± 0%
JsEngineFilter/Creation:_Pass_with_exact_match_of_id-8                                      1.63kB ± 0%
JsEngineFilter/Run:_Pass_with_exact_match_of_id-8                                            142kB ± 0%
JsEngineFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8    7.73kB ± 0%
JsEngineFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8          142kB ± 0%
JsEngineFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                        2.77kB ± 0%
JsEngineFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                              142kB ± 0%
JsEngineFilter/Creation:_No_pass_with_if_then-8                                             3.06kB ± 0%
JsEngineFilter/Run:_No_pass_with_if_then-8                                                   142kB ± 0%
JsEngineFilter/Creation:_No_pass_with_nested_logic-8                                        4.99kB ± 0%
JsEngineFilter/Run:_No_pass_with_nested_logic-8                                              142kB ± 0%

name                                                                                        allocs/op
AttributesFilter/Creation:_Pass_with_exact_match_of_id-8                                      0.00     
AttributesFilter/Run:_Pass_with_exact_match_of_id-8                                           19.0 ± 0%
AttributesFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8    0.00     
AttributesFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8         20.0 ± 0%
AttributesFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                        0.00     
AttributesFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                             22.0 ± 0%
JsEngineFilter/Creation:_Pass_with_exact_match_of_id-8                                        33.0 ± 0%
JsEngineFilter/Run:_Pass_with_exact_match_of_id-8                                            1.77k ± 0%
JsEngineFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8       124 ± 0%
JsEngineFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8          1.78k ± 0%
JsEngineFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                          52.0 ± 0%
JsEngineFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                              1.77k ± 0%
JsEngineFilter/Creation:_No_pass_with_if_then-8                                               60.0 ± 0%
JsEngineFilter/Run:_No_pass_with_if_then-8                                                   1.77k ± 0%
JsEngineFilter/Creation:_No_pass_with_nested_logic-8                                          88.0 ± 0%
JsEngineFilter/Run:_No_pass_with_nested_logic-8                                              1.77k ± 0%

@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

Comments of the results with a comparison with json schema proposal https://docs.google.com/document/d/1Pz2vaLWKUrMQDLNyDW7ksjgLG4GAxBY546dT7_jF5Rk/edit#heading=h.nqj7kw3uqc6m

@markusthoemmes
Copy link
Copy Markdown
Contributor

I guess the benchstat is kinda useless as it's not comparing AttribteFilter vs. JsEngine filter, right?

At least this proofs what I guess was expected: The JsEngine stuff is 2 orders of magnitude slower than a "native" filter. Whether or not that will impact workloads remains to be seen in real-world benchmarks I guess, but the sheer amount of allocations would have me quite cautious towards GC pressure. That area also looks like it could be vastly improved as I don't quite see a reason why we'd have to have that many allocations per filter.

@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

I guess the benchstat is kinda useless as it's not comparing AttribteFilter vs. JsEngine filter, right?

It tests the variance through the different runs of the same test

@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

@markusthoemmes yep there is definitely something wrong going on with this implementation, i'm trying to figure out what's wrong. Looking at the profiling data right now

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

Preliminary results with the hacks I made:

goos: linux
goarch: amd64
pkg: knative.dev/eventing/pkg/eventfilter/benchmarks
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_id-8         	408984840	         3.02 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_id-8              	  344989	      3586 ns/op	    2453 B/op	      19 allocs/op
BenchmarkAttributesFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8         	375486108	         3.22 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8              	  324550	      3821 ns/op	    2517 B/op	      20 allocs/op
BenchmarkAttributesFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                             	378230247	         3.15 ns/op	       0 B/op	       0 allocs/op
BenchmarkAttributesFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                  	  343671	      4022 ns/op	    2645 B/op	      22 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_id-8                                             	    6823	    167224 ns/op	  135049 B/op	    1755 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_id-8                                                  	   55447	     19431 ns/op	   13807 B/op	     152 allocs/op
BenchmarkJsEngineFilter/Creation:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8           	    6004	    167064 ns/op	  143049 B/op	    1879 allocs/op
BenchmarkJsEngineFilter/Run:_Pass_with_exact_match_of_all_context_attributes_(except_time)-8                	   70494	     17805 ns/op	   14047 B/op	     166 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_exact_match_of_id_and_source-8                               	    6962	    174949 ns/op	  136602 B/op	    1779 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_exact_match_of_id_and_source-8                                    	   61304	     16613 ns/op	   13807 B/op	     152 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_if_then-8                                                    	    8217	    158952 ns/op	  136723 B/op	    1787 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_if_then-8                                                         	   72492	     16807 ns/op	   13839 B/op	     154 allocs/op
BenchmarkJsEngineFilter/Creation:_No_pass_with_nested_logic-8                                               	    6883	    174537 ns/op	  139074 B/op	    1825 allocs/op
BenchmarkJsEngineFilter/Run:_No_pass_with_nested_logic-8                                                    	   73675	     15922 ns/op	   13870 B/op	     156 allocs/op
PASS
ok  	knative.dev/eventing/pkg/eventfilter/benchmarks	21.216s

which is pretty much similar to the json schema results

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
@knative-metrics-robot
Copy link
Copy Markdown

The following is the coverage report on the affected files.
Say /test pull-knative-eventing-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/apis/eventing/v1/trigger_validation.go 96.4% 90.2% -6.3
pkg/apis/eventing/v1beta1/trigger_validation.go 96.1% 89.3% -6.8
pkg/mtbroker/filter/filter_handler.go 87.7% 86.8% -0.9

@knative-prow-robot
Copy link
Copy Markdown
Contributor

@slinkydeveloper: The following tests failed, say /retest to rerun all failed tests:

Test name Commit Details Rerun command
pull-knative-eventing-unit-tests 75ac356 link /test pull-knative-eventing-unit-tests
pull-knative-eventing-integration-tests 75ac356 link /test pull-knative-eventing-integration-tests

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@slinkydeveloper
Copy link
Copy Markdown
Contributor Author

slinkydeveloper commented Nov 18, 2020

Death Report

So after running the benchmarks, I quickly identified that there was a crucial issue in this PR: while trying to isolate every execution of script, I was creating one JS Engine per request. Isolation was guaranteed, but every engine allocated quite a lot of memory for the built-in objects, the vm, etc... The total allocation was up to 170kb, which means that creating a new engine for each filtering request doesn't work in real world.

So I took a different approach: reuse the same engine per expression definition. Doing some static analysis of the ast and hacking a bit around the engine, I identified how to make the executions isolated and avoid the user to run malicious code, like Doom. But, cherry on the cake, the engine is not thread safe: you cannot run an exported function from multiple goroutines (which, in the end, it makes sense for JS per se).

I ended up giving up with this engine, it doesn't have the requirements we need. Maybe in future we could re-explore the idea embedding V8 or duktape, but they require CGO which opens to a complete different set of problems for our infra.

The quest for powerful filtering solution doesn't end here, rest assured! In fact, you should look at https://docs.google.com/document/d/1Pz2vaLWKUrMQDLNyDW7ksjgLG4GAxBY546dT7_jF5Rk/edit#heading=h.3w9ulpur0y16

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. area/test-and-release Test infrastructure, tests or release cla: yes Indicates the PR's author has signed the CLA. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expression language for Trigger filter