Skip to content

Commit 606ddba

Browse files
committed
[trace-guest] add Subscriber to capture traces/events
- Adds a type that implements the Subscriber trait of the tracing_core crate that allows the type to be set as the global Subscriber of the crate - This way we can handle the adding of new spans and events and store them where/how we want Signed-off-by: Doru Blânzeanu <[email protected]>
1 parent aad95e7 commit 606ddba

File tree

5 files changed

+220
-0
lines changed

5 files changed

+220
-0
lines changed

src/hyperlight_guest_bin/src/guest_function/call.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ pub(crate) extern "C" fn dispatch_function() {
128128
// part of the big identity-mapped region at the base of the
129129
// guest.
130130
crate::paging::flush_tlb();
131+
132+
// Read the current TSC to report it to the host with the spans/events
133+
// This helps calculating the timestamps relative to the guest call
134+
#[cfg(feature = "trace_guest")]
135+
{
136+
let guest_start_tsc = hyperlight_guest_tracing::invariant_tsc::read_tsc();
137+
hyperlight_guest_tracing::set_start_tsc(guest_start_tsc);
138+
}
139+
131140
internal_dispatch_function();
132141
halt();
133142
}

src/hyperlight_guest_bin/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ static INIT: Once = Once::new();
181181

182182
#[unsafe(no_mangle)]
183183
pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_level: u64) {
184+
// Save the guest start TSC for tracing
185+
#[cfg(feature = "trace_guest")]
186+
let guest_start_tsc = hyperlight_guest_tracing::invariant_tsc::read_tsc();
187+
184188
if peb_address == 0 {
185189
panic!("PEB address is null");
186190
}
@@ -229,6 +233,12 @@ pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_leve
229233
.expect("Invalid log level");
230234
init_logger(max_log_level);
231235

236+
// It is important that all the tracing events are produced after the tracing is initialized.
237+
#[cfg(feature = "trace_guest")]
238+
if max_log_level != LevelFilter::Off {
239+
hyperlight_guest_tracing::init_guest_tracing(guest_start_tsc);
240+
}
241+
232242
hyperlight_main();
233243
}
234244
});

src/hyperlight_guest_tracing/src/lib.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,55 @@ use heapless as hl;
1919

2020
/// Expose invariant TSC module
2121
pub mod invariant_tsc;
22+
23+
/// Defines internal guest state
24+
#[cfg(feature = "trace")]
25+
mod state;
26+
27+
/// Defines guest tracing Subscriber
28+
#[cfg(feature = "trace")]
29+
mod subscriber;
30+
31+
#[cfg(feature = "trace")]
32+
pub use trace::{init_guest_tracing, set_start_tsc};
33+
34+
/// This module is gated because some of these types are also used on the host, but we want
35+
/// only the guest to allocate and allow the functionality intended for the guest.
36+
#[cfg(feature = "trace")]
37+
mod trace {
38+
extern crate alloc;
39+
use alloc::sync::{Arc, Weak};
40+
41+
use spin::Mutex;
42+
43+
use super::*;
44+
use crate::state::GuestState;
45+
use crate::subscriber::GuestSubscriber;
46+
47+
/// Weak reference to the guest state so we can manually trigger flush to host
48+
static GUEST_STATE: spin::Once<Weak<Mutex<GuestState>>> = spin::Once::new();
49+
50+
/// Initialize the guest tracing subscriber as global default.
51+
pub fn init_guest_tracing(guest_start_tsc: u64) {
52+
// Set as global default if not already set.
53+
if tracing_core::dispatcher::has_been_set() {
54+
return;
55+
}
56+
let sub = GuestSubscriber::new(guest_start_tsc);
57+
let state = sub.state();
58+
// Store state Weak<GuestState> to use later at runtime
59+
GUEST_STATE.call_once(|| Arc::downgrade(state));
60+
61+
// Set global dispatcher
62+
let _ = tracing_core::dispatcher::set_global_default(tracing_core::Dispatch::new(sub));
63+
}
64+
65+
/// Sets the guset starting timestamp reported to the host on a VMExit
66+
pub fn set_start_tsc(guest_start_tsc: u64) {
67+
if let Some(w) = GUEST_STATE.get() {
68+
if let Some(state) = w.upgrade() {
69+
state.lock().set_start_tsc(guest_start_tsc);
70+
}
71+
}
72+
}
73+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Copyright 2025 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
extern crate alloc;
17+
18+
use core::sync::atomic::{AtomicU64, Ordering};
19+
20+
use heapless as hl;
21+
use hyperlight_common::outb::OutBAction;
22+
use tracing_core::Event;
23+
use tracing_core::span::{Attributes, Id, Record};
24+
25+
/// Internal state of the tracing subscriber
26+
pub(crate) struct GuestState {
27+
/// The timestamp counter at the start of the guest execution.
28+
guest_start_tsc: u64,
29+
}
30+
31+
impl GuestState {
32+
pub(crate) fn new(guest_start_tsc: u64) -> Self {
33+
Self { guest_start_tsc }
34+
}
35+
36+
/// Set a new guest start tsc
37+
pub(crate) fn set_start_tsc(&mut self, guest_start_tsc: u64) {
38+
self.guest_start_tsc = guest_start_tsc;
39+
}
40+
41+
/// Create a new span and push it on the stack
42+
pub(crate) fn new_span(&mut self, attrs: &Attributes) -> Id {
43+
unimplemented!()
44+
}
45+
46+
/// Record an event in the current span (top of the stack)
47+
pub(crate) fn event(&mut self, event: &Event<'_>) {
48+
unimplemented!()
49+
}
50+
51+
/// Record new values for an existing span
52+
pub(crate) fn record(&mut self, id: &Id, values: &Record<'_>) {
53+
unimplemented!()
54+
}
55+
56+
/// Enter a span (push it on the stack)
57+
pub(crate) fn enter(&mut self, id: &Id) {
58+
unimplemented!()
59+
}
60+
61+
/// Exit a span (pop it from the stack)
62+
pub(crate) fn exit(&mut self, _id: &Id) {
63+
unimplemented!()
64+
}
65+
66+
/// Try to close a span by ID, returning true if successful
67+
/// Records the end timestamp for the span.
68+
pub(crate) fn try_close(&mut self, id: Id) -> bool {
69+
unimplemented!()
70+
}
71+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
Copyright 2025 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
extern crate alloc;
17+
18+
use alloc::sync::Arc;
19+
20+
use spin::Mutex;
21+
use tracing_core::span::{Attributes, Id, Record};
22+
use tracing_core::subscriber::Subscriber;
23+
use tracing_core::{Event, Metadata};
24+
25+
use crate::state::GuestState;
26+
27+
/// The subscriber is used to collect spans and events in the guest.
28+
pub(crate) struct GuestSubscriber {
29+
/// Internal state that holds the spans and events
30+
/// Protected by a Mutex for inner mutability
31+
/// A reference to this state is stored in a static variable
32+
state: Arc<Mutex<GuestState>>,
33+
}
34+
35+
impl GuestSubscriber {
36+
pub(crate) fn new(guest_start_tsc: u64) -> Self {
37+
Self {
38+
state: Arc::new(Mutex::new(GuestState::new(guest_start_tsc))),
39+
}
40+
}
41+
pub(crate) fn state(&self) -> &Arc<Mutex<GuestState>> {
42+
&self.state
43+
}
44+
}
45+
46+
impl Subscriber for GuestSubscriber {
47+
fn enabled(&self, _md: &Metadata<'_>) -> bool {
48+
true
49+
}
50+
51+
fn new_span(&self, attrs: &Attributes<'_>) -> Id {
52+
self.state.lock().new_span(attrs)
53+
}
54+
55+
fn record(&self, id: &Id, values: &Record<'_>) {
56+
self.state.lock().record(id, values)
57+
}
58+
59+
fn event(&self, event: &Event<'_>) {
60+
self.state.lock().event(event)
61+
}
62+
63+
fn enter(&self, id: &Id) {
64+
self.state.lock().enter(id)
65+
}
66+
67+
fn exit(&self, id: &Id) {
68+
self.state.lock().exit(id)
69+
}
70+
71+
fn try_close(&self, id: Id) -> bool {
72+
self.state.lock().try_close(id)
73+
}
74+
75+
fn record_follows_from(&self, _span: &Id, _follows: &Id) {
76+
// no-op: we don't track follows-from relationships
77+
}
78+
}

0 commit comments

Comments
 (0)