Skip to content

Commit fc25d65

Browse files
authored
V8: measurements, todos, and docs (#3339)
# Description of Changes Prefix of #3276 to bisect a problem in smoketests. # API and ABI breaking changes None # Expected complexity level and risk 1 # Testing Future work.
1 parent ef56c90 commit fc25d65

5 files changed

Lines changed: 113 additions & 42 deletions

File tree

crates/core/src/host/v8/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ impl fmt::Display for JsStackTraceFrame {
206206

207207
// This isn't exactly the same format as chrome uses,
208208
// but it's close enough for now.
209-
// TODO(centril): make it more like chrome in the future.
209+
// TODO(v8): make it more like chrome in the future.
210210
f.write_fmt(format_args!(
211211
"at {} ({}:{}:{})",
212212
fn_name, script_name, &self.line, &self.column

crates/core/src/host/v8/mod.rs

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,23 @@ use crate::host::wasm_common::module_host_actor::{
99
};
1010
use crate::host::ArgsTuple;
1111
use crate::{host::Scheduler, module_host_context::ModuleCreationContext, replica_context::ReplicaContext};
12-
use anyhow::anyhow;
12+
use core::sync::atomic::{AtomicBool, Ordering};
1313
use core::time::Duration;
1414
use de::deserialize_js;
1515
use error::{catch_exception, exception_already_thrown, log_traceback, ExcResult, Throwable};
1616
use from_value::cast;
1717
use key_cache::get_or_create_key_cache;
1818
use ser::serialize_to_js;
19-
use spacetimedb_client_api_messages::energy::{EnergyQuanta, ReducerBudget};
19+
use spacetimedb_client_api_messages::energy::ReducerBudget;
2020
use spacetimedb_datastore::locking_tx_datastore::MutTxId;
2121
use spacetimedb_datastore::traits::Program;
2222
use spacetimedb_lib::RawModuleDef;
2323
use spacetimedb_lib::{ConnectionId, Identity};
2424
use spacetimedb_schema::auto_migrate::MigrationPolicy;
2525
use std::sync::{Arc, LazyLock};
26-
use v8::{Context, ContextOptions, ContextScope, Function, HandleScope, Isolate, Local, Value};
26+
use std::thread;
27+
use std::time::Instant;
28+
use v8::{Context, ContextOptions, ContextScope, Function, HandleScope, Isolate, IsolateHandle, Local, Value};
2729

2830
mod de;
2931
mod error;
@@ -81,9 +83,13 @@ impl V8RuntimeInner {
8183
);
8284

8385
if true {
84-
return Err::<JsModule, _>(anyhow!("v8_todo"));
86+
return Err::<JsModule, _>(anyhow::anyhow!("v8_todo"));
8587
}
8688

89+
// TODO(v8): determine min required ABI by module and check that it's supported?
90+
91+
// TODO(v8): validate function signatures like in WASM? Is that possible with V8?
92+
8793
let desc = todo!();
8894
// Validate and create a common module rom the raw definition.
8995
let common = build_common_module_from_raw(mcc, desc)?;
@@ -121,6 +127,17 @@ impl Module for JsModule {
121127
}
122128

123129
fn create_instance(&self) -> Self::Instance {
130+
// TODO(v8): consider some equivalent to `epoch_deadline_callback`
131+
// where we report `Js has been running for ...`.
132+
133+
// TODO(v8): timeout things like `extract_description`.
134+
135+
// TODO(v8): do we care about preinits / setup or are they unnecessary?
136+
137+
// TODO(v8): create `InstanceEnv`.
138+
139+
// TODO(v8): extract description.
140+
124141
todo!()
125142
}
126143
}
@@ -147,35 +164,49 @@ impl ModuleInstance for JsInstance {
147164
}
148165

149166
fn call_reducer(&mut self, tx: Option<MutTxId>, params: CallReducerParams) -> super::ReducerCallResult {
150-
// TODO(centril): snapshots, module->host calls
151-
let mut isolate = Isolate::new(<_>::default());
152-
let scope = &mut HandleScope::new(&mut isolate);
153-
let context = Context::new(scope, ContextOptions::default());
154-
let scope = &mut ContextScope::new(scope, context);
155-
156167
self.common.call_reducer_with_tx(
157168
&self.replica_ctx.clone(),
158169
tx,
159170
params,
160171
log_traceback,
161-
|tx, op, _budget| {
162-
let call_result = call_call_reducer_from_op(scope, op);
163-
// TODO(centril): energy metrering.
164-
let energy = EnergyStats {
165-
used: EnergyQuanta::ZERO,
166-
wasmtime_fuel_used: 0,
167-
remaining: ReducerBudget::ZERO,
168-
};
169-
// TODO(centril): timings.
172+
|tx, op, budget| {
173+
// TODO(v8): snapshots, module->host calls
174+
// Setup V8 scope.
175+
let mut isolate: v8::OwnedIsolate = Isolate::new(<_>::default());
176+
let isolate_handle = isolate.thread_safe_handle();
177+
let mut scope_1 = HandleScope::new(&mut isolate);
178+
let context = Context::new(&mut scope_1, ContextOptions::default());
179+
let mut scope_2 = ContextScope::new(&mut scope_1, context);
180+
181+
let timeout_thread_cancel_flag = run_reducer_timeout(isolate_handle, budget);
182+
183+
// Call the reducer.
184+
let start = Instant::now();
185+
let call_result = call_call_reducer_from_op(&mut scope_2, op);
186+
let total_duration = start.elapsed();
187+
// Cancel the execution timeout in `run_reducer_timeout`.
188+
timeout_thread_cancel_flag.store(true, Ordering::Relaxed);
189+
190+
// Handle energy and timings.
191+
let used = duration_to_budget(total_duration);
192+
let remaining = budget - used;
193+
let energy = EnergyStats { budget, remaining };
170194
let timings = ExecutionTimings {
171-
total_duration: Duration::ZERO,
195+
total_duration,
196+
// TODO(v8): call times.
172197
wasm_instance_env_call_times: CallTimes::new(),
173198
};
199+
200+
// Fetch the currently used heap size in V8.
201+
// The used size is ostensibly fairer than the total size.
202+
drop(scope_2);
203+
drop(scope_1);
204+
let memory_allocation = isolate.get_heap_statistics().used_heap_size();
205+
174206
let exec_result = ExecuteResult {
175207
energy,
176208
timings,
177-
// TODO(centril): memory allocation.
178-
memory_allocation: 0,
209+
memory_allocation,
179210
call_result,
180211
};
181212
(tx, exec_result)
@@ -184,6 +215,45 @@ impl ModuleInstance for JsInstance {
184215
}
185216
}
186217

218+
/// Spawns a thread that will terminate reducer execution
219+
/// when `budget` has been used up.
220+
fn run_reducer_timeout(isolate_handle: IsolateHandle, budget: ReducerBudget) -> Arc<AtomicBool> {
221+
let execution_done_flag = Arc::new(AtomicBool::new(false));
222+
let execution_done_flag2 = execution_done_flag.clone();
223+
let timeout = budget_to_duration(budget);
224+
225+
// TODO(v8): Using an OS thread is a bit heavy handed...?
226+
thread::spawn(move || {
227+
// Sleep until the timeout.
228+
thread::sleep(timeout);
229+
230+
if execution_done_flag2.load(Ordering::Relaxed) {
231+
// The reducer completed successfully.
232+
return;
233+
}
234+
235+
// Reducer is still running.
236+
// Terminate V8 execution.
237+
isolate_handle.terminate_execution();
238+
});
239+
240+
execution_done_flag
241+
}
242+
243+
/// Converts a [`ReducerBudget`] to a [`Duration`].
244+
fn budget_to_duration(_budget: ReducerBudget) -> Duration {
245+
// TODO(v8): This is fake logic that allows a maximum timeout.
246+
// Replace with sensible math.
247+
Duration::MAX
248+
}
249+
250+
/// Converts a [`Duration`] to a [`ReducerBudget`].
251+
fn duration_to_budget(_duration: Duration) -> ReducerBudget {
252+
// TODO(v8): This is fake logic that allows minimum energy usage.
253+
// Replace with sensible math.
254+
ReducerBudget::ZERO
255+
}
256+
187257
/// Returns the global property `key`.
188258
fn get_global_property<'scope>(
189259
scope: &mut HandleScope<'scope>,

crates/core/src/host/v8/ser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ impl<'this, 'scope> ser::Serializer for Serializer<'this, 'scope> {
150150
}
151151

152152
fn serialize_named_product(self, _len: usize) -> Result<Self::SerializeNamedProduct, Self::Error> {
153-
// TODO(noa): this can be more efficient if we tell it the names ahead of time
153+
// TODO(v8, noa): this can be more efficient if we tell it the names ahead of time
154154
let object = Object::new(self.scope);
155155
Ok(SerializeNamedProduct {
156156
inner: self,

crates/core/src/host/wasm_common/module_host_actor.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use tracing::span::EnteredSpan;
88
use super::instrumentation::CallTimes;
99
use crate::client::ClientConnectionSender;
1010
use crate::database_logger;
11-
use crate::energy::{EnergyMonitor, EnergyQuanta, ReducerBudget, ReducerFingerprint};
11+
use crate::energy::{EnergyMonitor, ReducerBudget, ReducerFingerprint};
1212
use crate::host::instance_env::InstanceEnv;
1313
use crate::host::module_common::{build_common_module_from_raw, ModuleCommon};
1414
use crate::host::module_host::{
@@ -60,11 +60,17 @@ pub trait WasmInstance: Send + Sync + 'static {
6060
}
6161

6262
pub struct EnergyStats {
63-
pub used: EnergyQuanta,
64-
pub wasmtime_fuel_used: u64,
63+
pub budget: ReducerBudget,
6564
pub remaining: ReducerBudget,
6665
}
6766

67+
impl EnergyStats {
68+
/// Returns the used energy amount.
69+
fn used(&self) -> ReducerBudget {
70+
(self.budget.get() - self.remaining.get()).into()
71+
}
72+
}
73+
6874
pub struct ExecutionTimings {
6975
pub total_duration: Duration,
7076
pub wasm_instance_env_call_times: CallTimes,
@@ -412,22 +418,24 @@ impl InstanceCommon {
412418
call_result,
413419
} = result;
414420

421+
let energy_used = energy.used();
422+
let energy_quanta_used = energy_used.into();
415423
vm_metrics.report(
416-
energy.wasmtime_fuel_used,
424+
energy_used.get(),
417425
timings.total_duration,
418426
&timings.wasm_instance_env_call_times,
419427
);
420428

421429
self.energy_monitor
422-
.record_reducer(&energy_fingerprint, energy.used, timings.total_duration);
430+
.record_reducer(&energy_fingerprint, energy_quanta_used, timings.total_duration);
423431
if self.allocated_memory != memory_allocation {
424432
self.metric_wasm_memory_bytes.set(memory_allocation as i64);
425433
self.allocated_memory = memory_allocation;
426434
}
427435

428436
reducer_span
429437
.record("timings.total_duration", tracing::field::debug(timings.total_duration))
430-
.record("energy.used", tracing::field::debug(energy.used));
438+
.record("energy.used", tracing::field::debug(energy_used));
431439

432440
maybe_log_long_running_reducer(reducer_name, timings.total_duration);
433441
reducer_span.exit();
@@ -486,7 +494,7 @@ impl InstanceCommon {
486494
args,
487495
},
488496
status,
489-
energy_quanta_used: energy.used,
497+
energy_quanta_used,
490498
host_execution_duration: timings.total_duration,
491499
request_id,
492500
timer,
@@ -495,7 +503,7 @@ impl InstanceCommon {
495503

496504
ReducerCallResult {
497505
outcome: ReducerOutcome::from(&event.status),
498-
energy_used: energy.used,
506+
energy_used: energy_quanta_used,
499507
execution_duration: timings.total_duration,
500508
}
501509
}

crates/core/src/host/wasmtime/wasmtime_module.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,8 @@ impl module_host_actor::WasmInstance for WasmtimeInstance {
192192
#[tracing::instrument(level = "trace", skip_all)]
193193
fn call_reducer(&mut self, op: ReducerOp<'_>, budget: ReducerBudget) -> module_host_actor::ExecuteResult {
194194
let store = &mut self.store;
195-
// note that ReducerBudget being a u64 is load-bearing here - although we convert budget right back into
196-
// EnergyQuanta at the end of this function, from_energy_quanta clamps it to a u64 range.
197-
// otherwise, we'd return something like `used: i128::MAX - u64::MAX`, which is inaccurate.
195+
// Set the fuel budget in WASM.
198196
set_store_fuel(store, budget.into());
199-
let original_fuel = get_store_fuel(store);
200197
store.set_epoch_deadline(EPOCH_TICKS_PER_SECOND);
201198

202199
// Prepare sender identity and connection ID, as LITTLE-ENDIAN byte arrays.
@@ -231,14 +228,10 @@ impl module_host_actor::WasmInstance for WasmtimeInstance {
231228

232229
let call_result = call_result.map(|code| handle_error_sink_code(code, error));
233230

231+
// Compute fuel and heap usage.
234232
let remaining_fuel = get_store_fuel(store);
235-
236233
let remaining: ReducerBudget = remaining_fuel.into();
237-
let energy = module_host_actor::EnergyStats {
238-
used: (budget - remaining).into(),
239-
wasmtime_fuel_used: original_fuel.0 - remaining_fuel.0,
240-
remaining,
241-
};
234+
let energy = module_host_actor::EnergyStats { budget, remaining };
242235
let memory_allocation = store.data().get_mem().memory.data_size(&store);
243236

244237
module_host_actor::ExecuteResult {

0 commit comments

Comments
 (0)