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

Add metrics runtime for Wasmer and debug for Wasmtime #40

Merged
merged 57 commits into from
Oct 4, 2022

Conversation

JanFalkin
Copy link
Contributor

First this pull is intentional; sorry about the last false alarm. I am not certain if this is the best way to introduce variants to the engine. This pull utilizes some new functionality on wasmer that exposes usage metrics from the wasm engine wasmerio/wasmer-go@586bbfe. Unfortunately this requires the use of unsafe as we must provide a C callback for the metrics calculation. In addition to the work for wasmer's metrics, I have also included the code for wasmtime to enable a debug session in the presence of a DWARF enable debugger. Both of these features are included in 2 new "new" functions. Another possibility would be to provide construct data such that "new" itself would have enough context to determine how the engine should be created. The pull will hopefully inspire some discussion and I can adapt the code anyway we decide.

@codefromthecrypt
Copy link
Contributor

Interesting.

There's an alternative to consider which is to allow wasmer and wasmtime to be configured independently, similar to wazero.

In #34 via @Arsen6331, we added wazero.EngineWithRuntime(newRuntime NewRuntime) which allowed runtime-specific functionality without bleeding things that aren't necessarily portable or portable by accident. This also allows you to test in the runtime itself which is probably needed as they can make some incompatible changes sometimes.

This also allows you to push down new functionality before abstracting it, which is handy as abstraction is hard to get right.

@codefromthecrypt
Copy link
Contributor

@JanFalkin ps if you are game with doing the same thing as we did in wazero (make a library-specific hole to configure the other runtimes), I'll offer the labor to make a PR that does that including testing the things that are testable. Lemme know!

Copy link
Contributor

@codefromthecrypt codefromthecrypt left a comment

Choose a reason for hiding this comment

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

lookin good

Copy link
Contributor

@codefromthecrypt codefromthecrypt left a comment

Choose a reason for hiding this comment

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

Food for thought. In wazero, the engine creation is deferrable, which allows you to decide whether to instantiate a new runtime per module or not. I'm not entirely sure how safe it is on the other runtimes to re-use their engine, especially in parallel.

Ex.

e := EngineWithRuntime(func(ctx context.Context) (wazero.Runtime, error) {
	return r, nil
})

@JanFalkin
Copy link
Contributor Author

Food for thought. In wazero, the engine creation is deferrable, which allows you to decide whether to instantiate a new runtime per module or not. I'm not entirely sure how safe it is on the other runtimes to re-use their engine, especially in parallel.

Ex.

e := EngineWithRuntime(func(ctx context.Context) (wazero.Runtime, error) {
	return r, nil
})

Based on the way we have it now, the engine instance is passed, so the same model could be tried. I am not certain at all that wasmer or wasmtime is safe to use in those instances. That would be for the user to protect by either getting new instances always or somehow safeguarding thread access and synchronize the threads such that only a single passes.

@codefromthecrypt
Copy link
Contributor

I don't think a user should have to protect calls to the engine. This would be unnecessary for safe ones. Maybe a benchmark can be used, if any reason just to show if something crashes or not?

Ex. I recall this because wazero tests ended up crashing on both wasmtime and wasmer. Basically I added a benchmark and then moved things to smaller scopes until they stopped crashing. If you do locking instead, then effectively you get no affect from pooling.

https://github.com/tetratelabs/wazero/blob/main/internal/integration_test/vs/wasmtime/wasmtime.go#L47-L48
https://github.com/tetratelabs/wazero/blob/main/internal/integration_test/vs/wasmer/wasmer.go#L56-L57

In any case the main advice is to change the structure but not the scope in this change. Ex change how it is initialized, but don't make it share an engine as the current code doesn't. If you want to make the change to have wasmer and wasmtime share an instance, maybe do that in another commit so it is easier to recognize and back-out (ex do it in a commit after this for example)

Copy link
Contributor

@codefromthecrypt codefromthecrypt left a comment

Choose a reason for hiding this comment

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

My feedback remains the same that the option for creating an engine should be a function of it (like in wasmtime) as that ensures the engine returned can close it. that matches the scope of the prior code, but still gives flexibility for the person using it to return a function of a constant engine.

PS whatever syntax here should also be applied to wazero as it would feel off otherwise. I can do that after the fact once things settle.

}

func Engine(engs ...*wasmer.Engine) wapc.Engine {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you meant to use the func type

Suggested change
func Engine(engs ...*wasmer.Engine) wapc.Engine {
func Engine(options ...EngineOption) wapc.Engine {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought I had it as such and there was feedback from either @pkedy or @syrusakbary that had me switch to the specific engine*. I can put it back; I just want to make sure we don't go back and forth. Thanks

Copy link
Contributor

Choose a reason for hiding this comment

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

yep. if the preference is to remove the option type I think it still exists in the file, so should also be removed from there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. Doing so now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry ran out of time today. Will commit hopefully Sat night or Sunday.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@codefromthecrypt Sorry about the delay; I was rushing on Friday night. I think the change was actually very small. Let me know if this is not what you were asking for.


func Engine() wapc.Engine {
return &engineInstance
func Engine(engs ...*wasmtime.Engine) wapc.Engine {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you meant to define a func type:

Suggested change
func Engine(engs ...*wasmtime.Engine) wapc.Engine {
// EngineOption is a wasmtime-specific configuration, applied via Engine
type Option func(*engine)
func Engine(options ...EngineOption) wapc.Engine {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

}

func Engine(engs ...*wasmer.Engine) wapc.Engine {
Copy link
Contributor

Choose a reason for hiding this comment

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

this feels a bit odd, is this common (using varargs to make one of the args optional)?

I think how wazero does it is a lot less confusing (having a second function vs being too clever with one)

func EngineWithRuntime(newRuntime NewRuntime) wapc.Engine {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@codefromthecrypt I was looking at that too but, if I remember, there was a comment regarding removing the function and just passing the engine ptr. Without using the varargs syntax you would have to pass nil if you want a new engine. I thought that was hideous looking but I am certainly willing to make it a function as orig, I just don't want to reignite the comment of removing the function for the ptr. I can make it look like wazero if that is desirable?

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel somehow the design for supplying an engine/runtime for wazero became reviewed and merged, so we can use that precedent to make the others able to supply their engine/runtimes the same way. This may get you out of the muck of trying to merge conflicting feedback into the same design :D

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Works for me! I'll give it a whirl.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@codefromthecrypt. Changes pushed

}

// UnwrapStore allows access to wasmtime-specific features.
func (m *Module) UnwrapStore() *wasmtime.Store {
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add functions like this to wazero for parity, noting that the naming suffix should reflect the type returned and wazero doesn't call things store :D

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. I think the names are consistent but different.

ctx := context.Background()

// Configure waPC to use a specific wasmer feature.
e := EngineWithRuntime(func() (*wasmer.Engine, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

someone may feel like this should be EngineWithEngine to match the type, but I feel runtime is nicer than stuttering.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. I was thinking along those lines as well, but the name is cumbersome to say the least.

Copy link
Contributor

@codefromthecrypt codefromthecrypt left a comment

Choose a reason for hiding this comment

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

one note and then I think it is ready for @pkedy to do a final pass

@@ -145,6 +144,16 @@ func (e *engine) New(ctx context.Context, host wapc.HostCallHandler, guest []byt
return
}

// UnwrapCompiledModule allows access to wazero-specific compiled module.
func (m *Module) UnwrapCompiledModule() *wazero.CompiledModule {
Copy link
Contributor

Choose a reason for hiding this comment

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

this one is a bit niche. I'd remove it and instead add Instance.UnwrapModule

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done and pushed

@pkedy
Copy link
Member

pkedy commented Oct 4, 2022

@JanFalkin I've been a bit busy but rest assured I'll get to this soon.

@JanFalkin
Copy link
Contributor Author

@JanFalkin I've been a bit busy but rest assured I'll get to this soon.

BTW I am away starting this evening till tomorrow evening. In case you are looking for me.

Copy link
Member

@pkedy pkedy left a comment

Choose a reason for hiding this comment

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

Just 2 small details

@@ -0,0 +1,30 @@
package wasmer
Copy link
Member

Choose a reason for hiding this comment

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

Please add this build tag. I have an M1 Mac apparently this feature is not supported.

//go:build amd64
// +build amd64


// Engine returns a new wapc.Engine which uses the DefaultRuntime.
func Engine() wapc.Engine {
Copy link
Member

Choose a reason for hiding this comment

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

Instead of having separate Engine and EngineWithRuntime functions, I wonder about using the Functional Options pattern here. It would be backward compatible. Is there any reason why we wouldn't use it?

https://golang.cafe/blog/golang-functional-options-pattern.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@pkedy I thought we had started with that pattern but moved to a wasm engine specific config/override. I am willing to modify as you all see fit, but I think we should now keep all engines consistent, and any change to wasmer should be reflected to wasmtime and wazero. This work specifically tried to normalize the creation/config behavior such that all engines look about the same.

Copy link
Member

Choose a reason for hiding this comment

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

This can be consistent and backward compatible with functional options, but thinking about it more there might be an advantage to having the signature for Engine be the same (passing to higher order functions). Let's keep this as is.

Copy link
Member

@pkedy pkedy left a comment

Choose a reason for hiding this comment

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

LGTM!

@JanFalkin Thank you for your unwavering engagement on this!
@codefromthecrypt Thank you for all the feedback!

@pkedy pkedy merged commit 0c9ebd9 into wapc:master Oct 4, 2022
@codefromthecrypt
Copy link
Contributor

@JanFalkin do you have any feedback since this is working? Are you using the feature successfully and how do you establish the cost and limits you use, if so. curious as we think about this generically.

@codefromthecrypt
Copy link
Contributor

guess it would be a follow-up to tetratelabs/wazero#422 (comment) about if things are working now or not, and if so any insights since putting it together.

@JanFalkin
Copy link
Contributor Author

@codefromthecrypt Stuff is working as expected. I have 2 scenarios that require the feature. The wasmer feature unfortunately has not been tested to a great extent however we have been using the dwarf debugging capabilities of wasmtime a lot. The metering is being tested at a basic level but we have not fully enabled the wasm engine in our demo and prod environments yet. I am ahead of the curve a bit.

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.

3 participants