Skip to content

Commit 53eafe8

Browse files
committed
[hyperlight-host/trace] Fixup: update the way the TSC frequency is calculated
- Rely on TSC readings on the host from when the guest is started and when the first records are received. Use these two moments in time and TSC readings to calculate the TSC frequency. - Afterwards we store the TSC reading from inside the guest and calculate all incoming TSC readings relative to the first one and using the frequency we can calculate the amount of time it took. Signed-off-by: Doru Blânzeanu <[email protected]>
1 parent 330dcfd commit 53eafe8

File tree

5 files changed

+96
-35
lines changed

5 files changed

+96
-35
lines changed

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -763,8 +763,10 @@ impl Hypervisor for HypervLinuxDriver {
763763
} else {
764764
#[cfg(feature = "trace_guest")]
765765
if self.trace_info.guest_start_epoch.is_none() {
766-
// Set the guest start epoch to the current time, before running the vcpu
767-
crate::debug!("HyperV - Guest Start Epoch set");
766+
// Store the guest start epoch and cycles to trace the guest execution time
767+
crate::debug!("MSHV - Guest Start Epoch set");
768+
self.trace_info.guest_start_tsc =
769+
Some(hyperlight_guest_tracing::invariant_tsc::read_tsc());
768770
self.trace_info.guest_start_epoch = Some(std::time::Instant::now());
769771
}
770772
// Note: if a `InterruptHandle::kill()` called while this thread is **here**

src/hyperlight_host/src/hypervisor/hyperv_windows.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,8 +734,10 @@ impl Hypervisor for HypervWindowsDriver {
734734
} else {
735735
#[cfg(feature = "trace_guest")]
736736
if self.trace_info.guest_start_epoch.is_none() {
737-
// Set the guest start epoch to the current time, before running the vcpu
738-
crate::debug!("MSHV - Guest Start Epoch set");
737+
// Store the guest start epoch and cycles to trace the guest execution time
738+
crate::debug!("HyperV - Guest Start Epoch set");
739+
self.trace_info.guest_start_tsc =
740+
Some(hyperlight_guest_tracing::invariant_tsc::read_tsc());
739741
self.trace_info.guest_start_epoch = Some(std::time::Instant::now());
740742
}
741743
self.processor.run()?

src/hyperlight_host/src/hypervisor/kvm.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,9 +645,11 @@ impl Hypervisor for KVMDriver {
645645
} else {
646646
#[cfg(feature = "trace_guest")]
647647
if self.trace_info.guest_start_epoch.is_none() {
648-
// Set the guest start epoch to the current time, before running the vcpu
648+
// Store the guest start epoch and cycles to trace the guest execution time
649649
crate::debug!("KVM - Guest Start Epoch set");
650650
self.trace_info.guest_start_epoch = Some(std::time::Instant::now());
651+
self.trace_info.guest_start_tsc =
652+
Some(hyperlight_guest_tracing::invariant_tsc::read_tsc());
651653
}
652654

653655
// Note: if a `InterruptHandle::kill()` called while this thread is **here**

src/hyperlight_host/src/sandbox/mod.rs

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub type ExtraAllowedSyscall = i64;
8787
/// Determine whether a suitable hypervisor is available to run
8888
/// this sandbox.
8989
///
90-
// Returns a boolean indicating whether a suitable hypervisor is present.
90+
/// Returns a boolean indicating whether a suitable hypervisor is present.
9191
#[instrument(skip_all, parent = Span::current())]
9292
pub fn is_hypervisor_present() -> bool {
9393
hypervisor::get_available_hypervisor().is_some()
@@ -100,18 +100,23 @@ pub fn is_hypervisor_present() -> bool {
100100
pub(crate) struct TraceInfo {
101101
/// The epoch against which trace events are timed; at least as
102102
/// early as the creation of the sandbox being traced.
103-
#[allow(dead_code)]
104103
pub epoch: std::time::Instant,
105104
/// The frequency of the timestamp counter.
106-
#[allow(dead_code)]
107-
pub tsc_freq: u64,
105+
pub tsc_freq: Option<u64>,
108106
/// The epoch at which the guest started, if it has started.
109107
/// This is used to calculate the time spent in the guest relative to the
110-
/// time of the host.
111-
#[allow(dead_code)]
108+
/// time when the host started.
112109
pub guest_start_epoch: Option<std::time::Instant>,
113-
/// The start guest time, in TSC cycles, for the current guest.
114-
#[allow(dead_code)]
110+
/// The start guest time, in TSC cycles, for the current guest has a double purpose.
111+
/// This field is used in two ways:
112+
/// 1. It contains the TSC value recorded on the host when the guest started.
113+
/// This is used to calculate the TSC frequency which is the same on the host and guest.
114+
/// The TSC frequency is used to convert TSC values to timestamps in the trace.
115+
/// **NOTE**: This is only used until the TSC frequency is calculated, when the first
116+
/// records are received.
117+
/// 2. To store the TSC value at recorded on the guest when the guest started (first record
118+
/// received)
119+
/// This is used to calculate the records timestamps relative to when guest started.
115120
pub guest_start_tsc: Option<u64>,
116121
/// The file to which the trace is being written
117122
#[allow(dead_code)]
@@ -136,8 +141,17 @@ impl TraceInfo {
136141
) -> crate::Result<Self> {
137142
let mut path = std::env::current_dir()?;
138143
path.push("trace");
144+
145+
// create directory if it does not exist
146+
if !path.exists() {
147+
std::fs::create_dir(&path)?;
148+
}
139149
path.push(uuid::Uuid::new_v4().to_string());
140150
path.set_extension("trace");
151+
152+
log::info!("Creating trace file at: {}", path.display());
153+
println!("Creating trace file at: {}", path.display());
154+
141155
#[cfg(feature = "unwind_guest")]
142156
let hash = unwind_module.hash();
143157
#[cfg(feature = "unwind_guest")]
@@ -147,11 +161,17 @@ impl TraceInfo {
147161
let cache = framehop::x86_64::CacheX86_64::new();
148162
(unwinder, Arc::new(Mutex::new(cache)))
149163
};
150-
let tsc_freq = Self::calculate_tsc_freq()?;
164+
if !hyperlight_guest_tracing::invariant_tsc::has_invariant_tsc() {
165+
// If the platform does not support invariant TSC, warn the user.
166+
// On Azure nested virtualization, the TSC invariant bit is not correctly reported, this is a known issue.
167+
log::warn!(
168+
"Invariant TSC is not supported on this platform, trace timestamps may be inaccurate"
169+
);
170+
}
151171

152172
let ret = Self {
153173
epoch: std::time::Instant::now(),
154-
tsc_freq,
174+
tsc_freq: None,
155175
guest_start_epoch: None,
156176
guest_start_tsc: None,
157177
file: Arc::new(Mutex::new(std::fs::File::create_new(path)?)),
@@ -170,22 +190,40 @@ impl TraceInfo {
170190
Ok(ret)
171191
}
172192

173-
/// Calculate the TSC frequency based on the RDTSC instruction.
174-
fn calculate_tsc_freq() -> crate::Result<u64> {
175-
if !hyperlight_guest_tracing::invariant_tsc::has_invariant_tsc() {
176-
return Err(crate::new_error!(
177-
"Invariant TSC is not supported on this platform"
178-
));
179-
}
180-
let start = hyperlight_guest_tracing::invariant_tsc::read_tsc();
181-
let start_time = std::time::Instant::now();
182-
// Sleep for 1 second to get a good sample
183-
std::thread::sleep(std::time::Duration::from_secs(1));
184-
let end = hyperlight_guest_tracing::invariant_tsc::read_tsc();
193+
/// Calculate the TSC frequency based on the RDTSC instruction on the host.
194+
fn calculate_tsc_freq(&mut self) -> crate::Result<()> {
195+
let (start, start_time) = match (
196+
self.guest_start_tsc.as_ref(),
197+
self.guest_start_epoch.as_ref(),
198+
) {
199+
(Some(start), Some(start_time)) => (*start, *start_time),
200+
_ => {
201+
// If the guest start TSC and time are not set, we use the current time and TSC.
202+
// This is not ideal, but it allows us to calculate the TSC frequency without
203+
// failing.
204+
// This is a fallback mechanism to ensure that we can still calculate, however it
205+
// should be noted that this may lead to inaccuracies in the TSC frequency.
206+
// The start time should be already set before running the guest for each sandbox.
207+
log::error!(
208+
"Guest start TSC and time are not set. Calculating TSC frequency will use current time and TSC."
209+
);
210+
(
211+
hyperlight_guest_tracing::invariant_tsc::read_tsc(),
212+
std::time::Instant::now(),
213+
)
214+
}
215+
};
216+
185217
let end_time = std::time::Instant::now();
218+
let end = hyperlight_guest_tracing::invariant_tsc::read_tsc();
219+
186220
let elapsed = end_time.duration_since(start_time).as_secs_f64();
221+
let tsc_freq = ((end - start) as f64 / elapsed) as u64;
222+
223+
log::info!("Calculated TSC frequency: {} Hz", tsc_freq);
224+
self.tsc_freq = Some(tsc_freq);
187225

188-
Ok(((end - start) as f64 / elapsed) as u64)
226+
Ok(())
189227
}
190228
}
191229

src/hyperlight_host/src/sandbox/outb.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,13 @@ pub(super) fn record_guest_trace_frame<F: FnOnce(&mut std::fs::File)>(
232232
.guest_start_tsc
233233
.as_ref()
234234
.map_or_else(|| 0, |c| *c);
235+
235236
// Convert cycles to microseconds based on the TSC frequency
236-
let micros = cycles_spent as f64 / trace_info.tsc_freq as f64 * 1_000_000f64;
237+
let tsc_freq = trace_info
238+
.tsc_freq
239+
.as_ref()
240+
.ok_or_else(|| new_error!("TSC frequency not set in TraceInfo"))?;
241+
let micros = cycles_spent as f64 / *tsc_freq as f64 * 1_000_000f64;
237242

238243
// Convert to a Duration
239244
let guest_duration = std::time::Duration::from_micros(micros as u64);
@@ -353,7 +358,6 @@ fn handle_outb_impl(
353358
.as_ref()
354359
.shared_mem
355360
.copy_to_slice(buffer, ptr as usize - SandboxMemoryLayout::BASE_ADDRESS)
356-
// .read::<u64>((addr - SandboxMemoryLayout::BASE_ADDRESS as u64) as usize)
357361
.map_err(|e| {
358362
new_error!(
359363
"Failed to copy trace records from guest memory to host: {:?}",
@@ -367,11 +371,24 @@ fn handle_outb_impl(
367371

368372
{
369373
let trace_info = _hv.trace_info_as_mut();
370-
// Store the start guest cycles if not already set
371-
// This is the `entrypoint` of the guest execution
372-
// This should be set only once, at the start of the guest execution
373-
if trace_info.guest_start_tsc.is_none() && !traces.is_empty() {
374-
trace_info.guest_start_tsc = Some(traces[0].cycles);
374+
375+
// Calculate the TSC frequency based on the current TSC reading
376+
// This is done only once, when the first trace record is received
377+
// Ideally, we should use a timer or a clock to measure the time elapsed,
378+
// but that adds delays.
379+
// To avoid that we store the TSC value and a timestamp right
380+
// before starting the guest execution and then calculate the TSC frequency when
381+
// the first trace record is received, based on the current TSC value and clock.
382+
if trace_info.tsc_freq.is_none() {
383+
trace_info.calculate_tsc_freq()?;
384+
385+
// After the TSC frequency is calculated, we no longer need the value of TSC
386+
// recorded on the host when the guest started, so we can set the guest_start_tsc field
387+
// to store the TSC value recorded on the guest when the guest started executing.
388+
// This is used to calculate the records timestamps relative to the first trace record.
389+
if !traces.is_empty() {
390+
trace_info.guest_start_tsc = Some(traces[0].cycles);
391+
}
375392
}
376393
}
377394

0 commit comments

Comments
 (0)