diff --git a/Cargo.lock b/Cargo.lock index 4ffca336..411a0239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -953,6 +953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -961,6 +962,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -980,9 +1009,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -1240,12 +1274,26 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ + "base64", "bytes", "futures-channel", "futures-core", @@ -1253,7 +1301,9 @@ dependencies = [ "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1305,6 +1355,18 @@ dependencies = [ "wasmparser 0.240.0", ] +[[package]] +name = "hyperlight-guest-tracing" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670c97963e19dd9e2855ed8ef1db47074d72036beb2ac5e621bd5796be2856b0" +dependencies = [ + "hyperlight-common", + "spin", + "tracing", + "tracing-core", +] + [[package]] name = "hyperlight-host" version = "0.11.0" @@ -1324,6 +1386,7 @@ dependencies = [ "gdbstub_arch", "goblin", "hyperlight-common", + "hyperlight-guest-tracing", "kvm-bindings", "kvm-ioctls", "lazy_static", @@ -1332,6 +1395,7 @@ dependencies = [ "metrics", "mshv-bindings", "mshv-ioctls", + "opentelemetry", "page_size", "rand", "rust-embed", @@ -1342,6 +1406,7 @@ dependencies = [ "tracing", "tracing-core", "tracing-log", + "tracing-opentelemetry", "uuid", "vmm-sys-util", "windows", @@ -1373,10 +1438,19 @@ dependencies = [ "metrics-exporter-prometheus", "metrics-util", "once_cell", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", "page_size", "tar", + "tokio", "toml", "tracing", + "tracing-forest", + "tracing-opentelemetry", + "tracing-subscriber", + "uuid", "windows", ] @@ -1540,6 +1614,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1746,6 +1830,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.7.6" @@ -1883,6 +1976,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1949,6 +2051,87 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.17", + "tracing", +] + +[[package]] +name = "opentelemetry-http" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" +dependencies = [ + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror 2.0.17", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", + "tonic-prost", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand", + "thiserror 2.0.17", + "tokio", + "tokio-stream", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1983,6 +2166,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "paste" version = "1.0.15" @@ -1995,6 +2201,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2111,6 +2337,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pulley-interpreter" version = "36.0.2" @@ -2304,6 +2553,40 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reqwest" +version = "0.12.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "ring" version = "0.17.14" @@ -2592,6 +2875,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2616,6 +2911,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shellexpand" version = "3.1.1" @@ -2631,6 +2935,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + [[package]] name = "sketches-ddsketch" version = "0.3.0" @@ -2700,6 +3013,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -2777,6 +3099,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -2806,11 +3137,25 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -2821,6 +3166,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.16" @@ -2873,6 +3229,86 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +[[package]] +name = "tonic" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" +dependencies = [ + "async-trait", + "base64", + "bytes", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -2912,6 +3348,21 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-forest" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3298fe855716711a00474eceb89cc7dc254bbe67f6bc4afafdeec5f0c538771c" +dependencies = [ + "chrono", + "serde", + "smallvec", + "thiserror 2.0.17", + "tracing", + "tracing-subscriber", + "uuid", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -2923,6 +3374,43 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6e5658463dd88089aba75c7791e1d3120633b1bfde22478b28f625a9bb1b8e" +dependencies = [ + "js-sys", + "opentelemetry", + "opentelemetry_sdk", + "rustversion", + "smallvec", + "thiserror 2.0.17", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -2991,6 +3479,7 @@ checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.4", "js-sys", + "serde", "wasm-bindgen", ] @@ -3069,6 +3558,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.105" @@ -3393,6 +3895,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/src/hyperlight_wasm/Cargo.toml b/src/hyperlight_wasm/Cargo.toml index 91d15c16..391bb3b4 100644 --- a/src/hyperlight_wasm/Cargo.toml +++ b/src/hyperlight_wasm/Cargo.toml @@ -49,6 +49,11 @@ name = "metrics" path = "examples/metrics/main.rs" test = true +[[example]] +name = "tracing-otlp" +path = "examples/tracing-otlp/main.rs" +test = true + [dependencies] hyperlight-host = { workspace = true } libc = { version = "0.2.176" } @@ -72,6 +77,16 @@ blake3 = "1.8" toml = "0.9.8" metrics-util = "0.20.0" metrics-exporter-prometheus = "0.17" +opentelemetry = "0.31.0" +opentelemetry-otlp = { version = "0.31.0", default-features = false, features = ["http-proto", "reqwest-blocking-client", "grpc-tonic"] } +opentelemetry-semantic-conventions = "0.31" +opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio"] } +tokio = { version = "1.48.0", features = ["full"] } +tracing-forest = { version = "0.2.0", features = ["uuid", "chrono", "smallvec", "serde", "env-filter"] } +tracing = "0.1.41" +tracing-subscriber = {version = "0.3.20", features = ["std", "env-filter"]} +tracing-opentelemetry = "0.32.0" +uuid = { version = "1.18.1", features = ["v4"] } [build-dependencies] cfg_aliases = "0.2.1" @@ -90,6 +105,7 @@ crashdump = ["hyperlight-host/crashdump"] gdb = ["hyperlight-host/gdb"] kvm = ["hyperlight-host/kvm"] mshv3 = ["hyperlight-host/mshv3"] +trace_guest = ["hyperlight-host/trace_guest"] [[bench]] name = "benchmarks" diff --git a/src/hyperlight_wasm/build.rs b/src/hyperlight_wasm/build.rs index 600be6a1..535925c3 100644 --- a/src/hyperlight_wasm/build.rs +++ b/src/hyperlight_wasm/build.rs @@ -144,6 +144,10 @@ fn build_wasm_runtime() -> PathBuf { if std::env::var("CARGO_FEATURE_GDB").is_ok() { cmd = cmd.arg("--features").arg("gdb"); } + // Enable the "trace_guest" feature if the corresponding Cargo feature is enabled + if std::env::var("CARGO_FEATURE_TRACE_GUEST").is_ok() { + cmd = cmd.arg("--features").arg("trace_guest"); + } let status = cmd .status() diff --git a/src/hyperlight_wasm/examples/tracing-otlp/main.rs b/src/hyperlight_wasm/examples/tracing-otlp/main.rs new file mode 100644 index 00000000..805738e4 --- /dev/null +++ b/src/hyperlight_wasm/examples/tracing-otlp/main.rs @@ -0,0 +1,200 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +//! This example demonstrates how to use OpenTelemetry (OTel) tracing with the Hyperlight Wasm +//! sandbox. +//! +//!! It initializes an OTel tracing subscriber that exports traces to an OTLP endpoint, spawns multiple +//! threads that create Wasm sandboxes, load Wasm modules, and call functions within those modules while +//! generating tracing spans for each operation. +//! +//! To run this example, ensure you have an OTLP-compatible tracing backend running at the +//! specified endpoint. +//! +//! Prerequisites: +//! - An OTLP-compatible tracing backend (e.g., OpenTelemetry Collector, Jaeger, +//! Zipkin) running and accessible at `http://localhost:4318/v1/traces`. +//! - The `rust_wasm_samples.aot` Wasm module available in the expected path (x64//). +//! - The `trace_guest` feature enabled in the `hyperlight-wasm` crate. +//! +//! ```sh +//! cargo run --example tracing-otlp --features="trace_guest" +//! ``` +//! +//! + +#![allow(clippy::disallowed_macros)] + +use std::error::Error; +use std::io::stdin; +use std::sync::{Arc, Mutex}; +use std::thread::{JoinHandle, spawn}; + +use examples_common::get_wasm_module_path; +use hyperlight_wasm::{LoadedWasmSandbox, Result as HyperlightResult, SandboxBuilder}; +use opentelemetry::trace::TracerProvider; +use opentelemetry::{KeyValue, global}; +use opentelemetry_otlp::{Protocol, SpanExporter, WithExportConfig}; +use opentelemetry_sdk::Resource; +use opentelemetry_sdk::trace::SdkTracerProvider; +use opentelemetry_semantic_conventions::attribute::SERVICE_VERSION; +use tracing::{Level, span}; +use tracing_opentelemetry::OpenTelemetryLayer; +use tracing_subscriber::EnvFilter; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use uuid::Uuid; + +const ENDPOINT_ADDR: &str = "http://localhost:4318/v1/traces"; + +fn init_tracing_subscriber( + addr: &str, +) -> std::result::Result> { + let exporter = SpanExporter::builder() + .with_http() + .with_protocol(Protocol::HttpBinary) + .with_endpoint(addr) + .build()?; + + let version = KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")); + let resource = Resource::builder() + .with_service_name("hyperlight_wasm_otel_example") + .with_attribute(version) + .build(); + + let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder() + .with_resource(resource) + .with_batch_exporter(exporter) + .build(); + + global::set_tracer_provider(provider.clone()); + let tracer = provider.tracer("trace-demo"); + + let otel_layer = OpenTelemetryLayer::new(tracer); + + // Try using the environment otherwise set default filters + let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| { + EnvFilter::from_default_env() + .add_directive("hyperlight_host=info".parse().unwrap()) + .add_directive("tracing=info".parse().unwrap()) + }); + + tracing_subscriber::registry() + .with(filter) + .with(otel_layer) + .try_init()?; + + Ok(provider) +} + +fn run_example(wait_input: bool) -> HyperlightResult<()> { + type TestFn = fn(&mut LoadedWasmSandbox, &str, &str) -> HyperlightResult; + let tests: &[(&str, &str, TestFn)] = &[ + ( + "rust_wasm_samples.aot", + "hello_world", + |sb, fn_name, module| { + println!("Calling {} Function in Wasm Module {}", fn_name, module,); + sb.call_guest_function(fn_name, ()) + }, + ), + ("rust_wasm_samples.aot", "add", |sb, fn_name, module| { + println!("Calling {} Function in Wasm Module {}", fn_name, module,); + sb.call_guest_function(fn_name, (5i32, 3i32)) + }), + ( + "rust_wasm_samples.aot", + "call_host_function", + |sb, fn_name, module| { + println!("Calling {} Function in Wasm Module {}", fn_name, module,); + sb.call_guest_function("call_host_function", 5i32) + }, + ), + ]; + + let mut join_handles: Vec>> = vec![]; + + // Construct a new span named "hyperlight otel tracing example" with INFO level. + let span = span!(Level::INFO, "hyperlight otel tracing example"); + let _entered = span.enter(); + + let should_exit = Arc::new(Mutex::new(false)); + let host_func = |a: i32| { + println!("host_func called with {}", a); + a + 1 + }; + + for i in 0..10 { + let exit = Arc::clone(&should_exit); + let handle = spawn(move || -> HyperlightResult<()> { + while !*exit.try_lock().unwrap() { + // Construct a new span named "hyperlight tracing example thread" with INFO level. + let id = Uuid::new_v4(); + let span = span!( + Level::INFO, + "hyperlight tracing example thread", + context = format!("Thread number {} GUID {}", i, id), + uuid = %id, + ); + let _entered = span.enter(); + + let mut sandbox = SandboxBuilder::new().build()?; + + sandbox.register("TestHostFunc", host_func)?; + let mut wasm_sandbox = Some(sandbox.load_runtime()?); + for (module, fn_name, func) in tests.iter() { + let mod_path = get_wasm_module_path(module)?; + + // Load a Wasm module into the sandbox + let mut loaded_wasm_sandbox = + wasm_sandbox.take().unwrap().load_module(mod_path)?; + + let _res = func(&mut loaded_wasm_sandbox, fn_name, module)?; + wasm_sandbox = Some(loaded_wasm_sandbox.unload_module()?); + } + + // Set exit to true to exit the loop after one iteration for this example. + *exit.try_lock().unwrap() = true; + } + Ok(()) + }); + join_handles.push(handle); + } + + if wait_input { + println!("Press enter to exit..."); + let mut input = String::new(); + stdin().read_line(&mut input)?; + } + + *should_exit.try_lock().unwrap() = true; + for join_handle in join_handles { + let result = join_handle.join(); + assert!(result.is_ok()); + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let provider = init_tracing_subscriber(ENDPOINT_ADDR)?; + + run_example(true)?; + + provider.shutdown()?; + + Ok(()) +} diff --git a/src/wasm_runtime/Cargo.lock b/src/wasm_runtime/Cargo.lock index cea0a56d..9273dc1e 100644 --- a/src/wasm_runtime/Cargo.lock +++ b/src/wasm_runtime/Cargo.lock @@ -1635,6 +1635,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1820,6 +1821,7 @@ dependencies = [ "hyperlight-wasm-macro", "reqwest", "spin 0.10.0", + "tracing", "wasmtime", ] diff --git a/src/wasm_runtime/Cargo.toml b/src/wasm_runtime/Cargo.toml index 29cfa0e5..ab9c3f1b 100644 --- a/src/wasm_runtime/Cargo.toml +++ b/src/wasm_runtime/Cargo.toml @@ -17,6 +17,7 @@ hyperlight-guest = { version = "0.11.0" } wasmtime = { version = "36.0.2", default-features = false, features = [ "runtime", "custom-virtual-memory", "custom-native-signals", "component-model" ] } hyperlight-wasm-macro = { path = "../hyperlight_wasm_macro" } spin = "0.10.0" +tracing = { version = "0.1.41", default-features = false, features = ["attributes", "log"] } [build-dependencies] cfg_aliases = "0.2.1" @@ -28,4 +29,6 @@ reqwest = {version = "0.12", default-features = false, features = ["blocking"," [workspace] # indicate that this crate is not part of any workspace [features] +default = [] gdb = ["wasmtime/debug-builtins"] +trace_guest = ["hyperlight-common/trace_guest", "hyperlight-guest/trace_guest", "hyperlight-guest-bin/trace_guest"] diff --git a/src/wasm_runtime/src/component.rs b/src/wasm_runtime/src/component.rs index 14711dcf..ea43d033 100644 --- a/src/wasm_runtime/src/component.rs +++ b/src/wasm_runtime/src/component.rs @@ -30,6 +30,7 @@ use hyperlight_guest_bin::guest_function::definition::GuestFunctionDefinition; use hyperlight_guest_bin::guest_function::register::register_function; use hyperlight_guest_bin::host_comm::call_host_function; use spin::Mutex; +use tracing::instrument; use wasmtime::component::{Component, Instance, Linker}; use wasmtime::{Config, Engine, Store}; @@ -43,10 +44,12 @@ static CUR_INSTANCE: Mutex> = Mutex::new(None); hyperlight_wasm_macro::wasm_guest_bindgen!(); // dummy for compatibility with the module loading approach +#[instrument(skip_all, level = "Info")] fn init_wasm_runtime(_function_call: &FunctionCall) -> Result> { Ok(get_flatbuffer_result::(0)) } +#[instrument(skip_all, level = "Info")] fn load_component_common(engine: &Engine, component: Component) -> Result<()> { let mut store = Store::new(engine, ()); let instance = (*CUR_LINKER.lock()) @@ -58,6 +61,7 @@ fn load_component_common(engine: &Engine, component: Component) -> Result<()> { Ok(()) } +#[instrument(skip_all, level = "Info")] fn load_wasm_module(function_call: &FunctionCall) -> Result> { if let ( ParameterValue::VecBytes(ref wasm_bytes), @@ -79,6 +83,7 @@ fn load_wasm_module(function_call: &FunctionCall) -> Result> { } } +#[instrument(skip_all, level = "Info")] fn load_wasm_module_phys(function_call: &FunctionCall) -> Result> { if let (ParameterValue::ULong(ref phys), ParameterValue::ULong(ref len), Some(ref engine)) = ( &function_call.parameters.as_ref().unwrap()[0], @@ -98,6 +103,7 @@ fn load_wasm_module_phys(function_call: &FunctionCall) -> Result> { } #[no_mangle] +#[instrument(skip_all, level = "Info")] pub extern "C" fn hyperlight_main() { platform::register_page_fault_handler(); @@ -133,6 +139,7 @@ pub extern "C" fn hyperlight_main() { } #[no_mangle] +#[instrument(skip_all, level = "Info")] pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { Err(HyperlightGuestError::new( ErrorCode::GuestFunctionNotFound, diff --git a/src/wasm_runtime/src/marshal.rs b/src/wasm_runtime/src/marshal.rs index a4490469..890cd698 100644 --- a/src/wasm_runtime/src/marshal.rs +++ b/src/wasm_runtime/src/marshal.rs @@ -57,17 +57,20 @@ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result; use hyperlight_guest::error::{HyperlightGuestError, Result}; use spin::Mutex; +use tracing::instrument; use wasmtime::{AsContextMut, Extern, Val}; // Global tracking for return value allocations that need to be freed on next VM entry static RETURN_VALUE_ALLOCATIONS: Mutex> = Mutex::new(Vec::new()); /// Track a return value allocation that should be freed on the next VM entry +#[instrument(skip_all, level = "Trace")] fn track_return_value_allocation(addr: i32) { RETURN_VALUE_ALLOCATIONS.lock().push(addr); } /// Free all tracked return value allocations from previous VM calls +#[instrument(skip_all, level = "Trace")] pub fn free_return_value_allocations( ctx: &mut C, get_export: &impl Fn(&mut C, &str) -> Option, @@ -79,6 +82,7 @@ pub fn free_return_value_allocations( Ok(()) } +#[instrument(skip_all, level = "Trace")] fn malloc( ctx: &mut C, get_export: &impl Fn(&mut C, &str) -> Option, @@ -96,6 +100,7 @@ fn malloc( Ok(addr) } +#[instrument(skip_all, level = "Trace")] fn free( ctx: &mut C, get_export: &impl Fn(&mut C, &str) -> Option, @@ -111,6 +116,7 @@ fn free( Ok(()) } +#[instrument(skip_all, level = "Trace")] fn write( ctx: &mut C, get_export: &impl Fn(&mut C, &str) -> Option, @@ -132,6 +138,7 @@ fn write( Ok(()) } +#[instrument(skip_all, level = "Trace")] fn read( ctx: &mut C, get_export: &impl Fn(&mut C, &str) -> Option, @@ -153,6 +160,7 @@ fn read( Ok(()) } +#[instrument(skip_all, level = "Trace")] fn read_cstr( ctx: &mut C, get_export: &impl Fn(&mut C, &str) -> Option, @@ -196,6 +204,7 @@ fn read_cstr( /// For String and VecBytes parameter types, this allocates memory in the guest's memory space /// and returns a pointer. The guest function is responsible for freeing this memory when it is no /// longer needed using the `free` function exported from the guest module. +#[instrument(skip_all, level = "Trace")] pub fn hl_param_to_val( mut ctx: C, get_export: impl Fn(&mut C, &str) -> Option, @@ -230,6 +239,7 @@ pub fn hl_param_to_val( /// For String and VecBytes return types, the guest has allocated memory in its own memory space /// and returned pointers. The host takes ownership of these allocations and tracks them for /// automatic cleanup on the next VM entry to prevent memory leaks. +#[instrument(skip_all, level = "Trace")] pub fn val_to_hl_result( mut ctx: C, get_export: impl Fn(&mut C, &str) -> Option, @@ -284,6 +294,7 @@ pub fn val_to_hl_result( /// For String and VecBytes parameter types, the guest passes pointers to data in its own /// memory space. The guest retains ownership of these allocations and remains responsible /// for freeing them. This function only reads the data without taking ownership. +#[instrument(skip_all, level = "Trace")] pub fn val_to_hl_param<'a, C: AsContextMut>( ctx: &mut C, get_export: impl Fn(&mut C, &str) -> Option, @@ -339,6 +350,7 @@ pub fn val_to_hl_param<'a, C: AsContextMut>( /// For String and VecBytes return types, this allocates memory in the guest's memory space /// and returns a pointer. The guest owns these allocations and must free them when no longer needed /// using the `free` function exported from the guest module. +#[instrument(skip_all, level = "Trace")] pub fn hl_return_to_val( ctx: &mut C, get_export: impl Fn(&mut C, &str) -> Option, diff --git a/src/wasm_runtime/src/module.rs b/src/wasm_runtime/src/module.rs index aca5d0e1..14e2d64a 100644 --- a/src/wasm_runtime/src/module.rs +++ b/src/wasm_runtime/src/module.rs @@ -30,6 +30,7 @@ use hyperlight_guest_bin::guest_function::definition::GuestFunctionDefinition; use hyperlight_guest_bin::guest_function::register::register_function; use hyperlight_guest_bin::host_comm::print_output_with_host_print; use spin::Mutex; +use tracing::instrument; use wasmtime::{Config, Engine, Linker, Module, Store, Val}; use crate::{hostfuncs, marshal, platform, wasip1}; @@ -43,6 +44,7 @@ static CUR_STORE: Mutex>> = Mutex::new(None); static CUR_INSTANCE: Mutex> = Mutex::new(None); #[no_mangle] +#[instrument(skip_all, level = "Info")] pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { let mut store = CUR_STORE.lock(); let store = store.deref_mut().as_mut().ok_or(HyperlightGuestError::new( @@ -93,6 +95,7 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { ) } +#[instrument(skip_all, level = "Info")] fn init_wasm_runtime() -> Result> { let mut config = Config::new(); config.with_custom_code_memory(Some(alloc::sync::Arc::new(platform::WasmtimeCodeMemory {}))); @@ -122,6 +125,7 @@ fn init_wasm_runtime() -> Result> { Ok(get_flatbuffer_result::(0)) } +#[instrument(skip_all, level = "Info")] fn load_wasm_module(function_call: &FunctionCall) -> Result> { if let ( ParameterValue::VecBytes(ref wasm_bytes), @@ -154,6 +158,7 @@ fn load_wasm_module(function_call: &FunctionCall) -> Result> { } } +#[instrument(skip_all, level = "Info")] fn load_wasm_module_phys(function_call: &FunctionCall) -> Result> { if let (ParameterValue::ULong(ref phys), ParameterValue::ULong(ref len), Some(ref engine)) = ( &function_call.parameters.as_ref().unwrap()[0], @@ -182,8 +187,10 @@ fn load_wasm_module_phys(function_call: &FunctionCall) -> Result> { } } +// GuestFunctionDefinition expects a function pointer as i64 #[no_mangle] -#[allow(clippy::fn_to_numeric_cast)] // GuestFunctionDefinition expects a function pointer as i64 +#[allow(clippy::fn_to_numeric_cast)] +#[instrument(skip_all, level = "Info")] pub extern "C" fn hyperlight_main() { platform::register_page_fault_handler();