Skip to content

Commit 5291f57

Browse files
Stop conflating EnergyQuanta and FunctionBudget (#4930)
# Description of Changes A sort of followup to/unrevert of #4884, an alternative to #4927. In #3832, we made FunctionBudget a different unit than EnergyQuanta, but there were still many places where we treated them the same and directly converted between them. I got confused by that in #4884, and now this PR properly separates them and corrects the v8 energy calculation. Also, since we now benchmark the same module in rust and typescript in the form of the keynote benchmark, this patch adds a new assertion to that test that the fuel usage of both modules is within 2X of each other. # Expected complexity level and risk 3 - touches energy calculation without reverting the problematic #4884, but I'm confident it's correct this time. # Testing - [x] Verified that the conversion factors make sense for wasmtime and for v8. - [x] Assert that typescript and rust keynote bench runs have similar cpu usage --------- Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
1 parent 14e691b commit 5291f57

21 files changed

Lines changed: 213 additions & 107 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/client-api-messages/src/energy.rs

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ impl EnergyQuanta {
1717
pub const ZERO: Self = EnergyQuanta { quanta: 0 };
1818

1919
#[inline]
20-
pub fn new(quanta: u128) -> Self {
20+
pub const fn new(quanta: u128) -> Self {
2121
Self { quanta }
2222
}
2323

2424
#[inline]
25-
pub fn get(&self) -> u128 {
25+
pub const fn get(&self) -> u128 {
2626
self.quanta
2727
}
2828

@@ -121,46 +121,52 @@ impl fmt::Debug for EnergyBalance {
121121
}
122122
}
123123

124-
/// A measure of energy representing the energy budget for a reducer or any callable function.
124+
/// A measure of the energy budget for a reducer or any callable function.
125125
///
126-
/// In contrast to [`EnergyQuanta`], this is represented by a 64-bit integer. This makes energy handling
127-
/// for reducers easier, while still providing a unlikely-to-ever-be-reached maximum value (e.g. for wasmtime:
128-
/// `(u64::MAX eV / 1000 eV/instruction) * 3 ns/instruction = 640 days`)
129-
#[derive(Copy, Clone, From, Add, Sub, AddAssign, SubAssign)]
126+
/// This unit is not directly convertible to `EnergyQuanta`. It is currently
127+
/// 1:1 to wasmtime fuel, and we intend to treat it as representing a CPU
128+
/// instruction.
129+
#[derive(Copy, Clone, From, Add, Sub, AddAssign, SubAssign, Debug, PartialEq, Eq, PartialOrd, Ord)]
130130
pub struct FunctionBudget(u64);
131131

132132
impl FunctionBudget {
133-
// 1 second of wasm runtime is roughly 2 TeV, so this is
134-
// roughly 1 minute of wasm runtime
135-
pub const DEFAULT_BUDGET: Self = FunctionBudget(120_000_000_000_000);
133+
/// We've generally assumed that 1 second of wasm runtime uses 2_000_000_000 fuel.
134+
/// Currently, 1 wasmtime fuel unit is equivalent to 1 wasm instructions. Assuming
135+
/// 1 wasm instruction compiles to 1 CPU instruction (which it doesn't), this implies
136+
/// a 1 instruction-per-cycle abstract machine with a CPU frequency of 2GHz.
137+
pub const PER_EXECUTION_SEC: Self = FunctionBudget(2_000_000_000);
138+
139+
pub const PER_EXECUTION_NANOSEC: Self = Self(Self::PER_EXECUTION_SEC.0 / 1_000_000_000);
140+
141+
/// Roughly 1 minute of runtime.
142+
pub const DEFAULT_BUDGET: Self = FunctionBudget(Self::PER_EXECUTION_SEC.0 * 60);
136143

137144
pub const ZERO: Self = FunctionBudget(0);
138145
pub const MAX: Self = FunctionBudget(u64::MAX);
139146

140-
pub fn new(v: u64) -> Self {
147+
pub const fn new(v: u64) -> Self {
141148
Self(v)
142149
}
143150

144-
pub fn get(&self) -> u64 {
151+
pub const fn get(&self) -> u64 {
145152
self.0
146153
}
147154

148-
/// Convert from [`EnergyQuanta`]. Returns `None` if `energy` is too large to be represented.
149-
pub fn from_energy(energy: EnergyQuanta) -> Option<Self> {
150-
energy.get().try_into().ok().map(Self)
151-
}
152-
}
153-
154-
impl From<FunctionBudget> for EnergyQuanta {
155-
fn from(value: FunctionBudget) -> Self {
156-
EnergyQuanta::new(value.0.into())
155+
/// Convert `FunctionBudget` to `Duration` using the conversion factor
156+
/// [`FunctionBudget::PER_EXECUTION_SEC`].
157+
pub fn to_duration(self) -> Duration {
158+
Duration::from_nanos(self.0 / Self::PER_EXECUTION_NANOSEC.0)
157159
}
158-
}
159160

160-
impl fmt::Debug for FunctionBudget {
161-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162-
f.debug_tuple("ReducerBudget")
163-
.field(&EnergyQuanta::from(*self))
164-
.finish()
161+
/// Convert `Duration` to `FunctionBudget` using the conversion factor
162+
/// [`FunctionBudget::PER_EXECUTION_SEC`].
163+
///
164+
/// Returns `None` on overflow, which means that dur > 106.75 days.
165+
// in order for duration_nanos * budget_per_ns >= u64::MAX:
166+
// duration_nanos >= u64::MAX / budget_per_ns
167+
// duration_nanos >= (9223372036854775 ns = 106.75 days)
168+
pub fn from_duration(dur: Duration) -> Option<Self> {
169+
let duration_nanos = u64::try_from(dur.as_nanos()).ok()?;
170+
duration_nanos.checked_mul(Self::PER_EXECUTION_NANOSEC.get()).map(Self)
165171
}
166172
}

crates/client-api/src/auth.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use spacetimedb::auth::token_validation::{
1212
new_validator, DefaultValidator, TokenSigner, TokenValidationError, TokenValidator,
1313
};
1414
use spacetimedb::auth::JwtKeys;
15-
use spacetimedb::energy::EnergyQuanta;
15+
use spacetimedb::energy::FunctionBudget;
1616
use spacetimedb::identity::Identity;
1717
use spacetimedb_data_structures::map::HashMap;
1818
use std::time::{Duration, SystemTime};
@@ -518,7 +518,7 @@ impl headers::Header for SpacetimeIdentityToken {
518518
}
519519
}
520520

521-
pub struct SpacetimeEnergyUsed(pub EnergyQuanta);
521+
pub struct SpacetimeEnergyUsed(pub FunctionBudget);
522522
impl headers::Header for SpacetimeEnergyUsed {
523523
fn name() -> &'static http::HeaderName {
524524
static NAME: http::HeaderName = http::HeaderName::from_static("spacetime-energy-used");
@@ -530,9 +530,7 @@ impl headers::Header for SpacetimeEnergyUsed {
530530
}
531531

532532
fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
533-
let mut buf = itoa::Buffer::new();
534-
let value = buf.format(self.0.get());
535-
values.extend([value.try_into().unwrap()]);
533+
values.extend([self.0.get().into()]);
536534
}
537535
}
538536

crates/client-api/src/routes/database.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ pub async fn call<S: ControlStateDelegate + NodeDelegate>(
202202
let (status, body) = reducer_outcome_response(&owner_identity, &reducer, result.outcome);
203203
Ok((
204204
status,
205-
TypedHeader(SpacetimeEnergyUsed(result.energy_used)),
205+
TypedHeader(SpacetimeEnergyUsed(result.execution_budget_used)),
206206
TypedHeader(SpacetimeExecutionDurationMicros(result.execution_duration)),
207207
body,
208208
)

crates/core/src/client/message_handlers_v1.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::messages::{SubscriptionUpdateMessage, SwitchedServerMessage, ToProtocol, TransactionUpdateMessage};
22
use super::{ClientConnection, DataMessage, MessageHandleError, Protocol};
3-
use crate::energy::EnergyQuanta;
3+
use crate::energy::FunctionBudget;
44
use crate::host::module_host::{EventStatus, ModuleEvent, ModuleFunctionCall};
55
use crate::host::{FunctionArgs, ReducerId};
66
use crate::identity::Identity;
@@ -139,7 +139,7 @@ impl MessageExecutionError {
139139
},
140140
status: EventStatus::FailedInternal(format!("{:#}", err)),
141141
reducer_return_value: None,
142-
energy_quanta_used: EnergyQuanta::ZERO,
142+
execution_budget_used: FunctionBudget::ZERO,
143143
host_execution_duration: Duration::ZERO,
144144
request_id: Some(RequestId::default()),
145145
timer: None,

crates/core/src/client/messages.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::subscription::websocket_building::{brotli_compress, decide_compressio
77
use bytes::{BufMut, Bytes, BytesMut};
88
use bytestring::ByteString;
99
use derive_more::From;
10+
use spacetimedb_client_api_messages::energy::EnergyQuanta;
1011
use spacetimedb_client_api_messages::websocket::common::{self as ws_common, RowListLen as _};
1112
use spacetimedb_client_api_messages::websocket::v1::{self as ws_v1};
1213
use spacetimedb_client_api_messages::websocket::v2 as ws_v2;
@@ -434,7 +435,12 @@ impl ToProtocol for TransactionUpdateMessage {
434435
args,
435436
request_id,
436437
},
437-
energy_quanta_used: event.energy_quanta_used,
438+
// This conversion is lying. We used to tell the client how much eV a transaction
439+
// used, but now the database just tracks cpu usage, and it's converted to energy
440+
// elsewhere. So, we just pretend that this is `EnergyQuanta` when it's actually
441+
// a different unit, and it doesn't really matter to the client anyway.
442+
// TODO(noa): maybe we could just have this be zero, unconditionally?
443+
energy_quanta_used: EnergyQuanta::new(event.execution_budget_used.get().into()),
438444
total_host_execution_duration: event.host_execution_duration.into(),
439445
caller_connection_id: event.caller_connection_id.unwrap_or(ConnectionId::ZERO),
440446
};

crates/core/src/energy.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub trait EnergyMonitor: Send + Sync + 'static {
1717
fn record_reducer(
1818
&self,
1919
fingerprint: &FunctionFingerprint<'_>,
20-
energy_used: EnergyQuanta,
20+
execution_budget_used: FunctionBudget,
2121
execution_duration: Duration,
2222
);
2323
fn record_disk_usage(&self, database: &Database, replica_id: u64, disk_usage: u64, period: Duration);
@@ -36,7 +36,7 @@ impl EnergyMonitor for NullEnergyMonitor {
3636
fn record_reducer(
3737
&self,
3838
_fingerprint: &FunctionFingerprint<'_>,
39-
_energy_used: EnergyQuanta,
39+
_execution_budget_used: FunctionBudget,
4040
_execution_duration: Duration,
4141
) {
4242
}

crates/core/src/host/host_controller.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::database_logger::DatabaseLogger;
99
use crate::db::persistence::PersistenceProvider;
1010
use crate::db::relational_db::{self, spawn_view_cleanup_loop, DiskSizeFn, RelationalDB, Txdata};
1111
use crate::db::{self, spawn_tx_metrics_recorder};
12-
use crate::energy::{EnergyMonitor, EnergyQuanta, NullEnergyMonitor};
12+
use crate::energy::{EnergyMonitor, FunctionBudget, NullEnergyMonitor};
1313
use crate::host::v8::V8Runtime;
1414
use crate::host::ProcedureCallError;
1515
use crate::messages::control_db::{Database, HostType};
@@ -148,7 +148,7 @@ impl HostRuntimes {
148148
#[derive(Clone, Debug)]
149149
pub struct ReducerCallResult {
150150
pub outcome: ReducerOutcome,
151-
pub energy_used: EnergyQuanta,
151+
pub execution_budget_used: FunctionBudget,
152152
pub execution_duration: Duration,
153153
}
154154

crates/core/src/host/instance_env.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use core::mem;
1313
use futures::TryFutureExt;
1414
use parking_lot::{Mutex, MutexGuard};
1515
use smallvec::SmallVec;
16-
use spacetimedb_client_api_messages::energy::EnergyQuanta;
16+
use spacetimedb_client_api_messages::energy::FunctionBudget;
1717
use spacetimedb_datastore::db_metrics::DB_METRICS;
1818
use spacetimedb_datastore::execution_context::Workload;
1919
use spacetimedb_datastore::locking_tx_datastore::state_view::StateView;
@@ -793,7 +793,7 @@ impl InstanceEnv {
793793
request_id: None,
794794
timer: None,
795795
// The procedure will pick up the tab for the energy.
796-
energy_quanta_used: EnergyQuanta { quanta: 0 },
796+
execution_budget_used: FunctionBudget::ZERO,
797797
host_execution_duration: Duration::from_millis(0),
798798
};
799799
// Commit the tx and broadcast it.

crates/core/src/host/module_host.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::client::messages::{OneOffQueryResponseMessage, ProcedureResultMessage
66
use crate::client::{ClientActorId, ClientConnectionSender, WsVersion};
77
use crate::database_logger::{DatabaseLogger, LogLevel, Record};
88
use crate::db::relational_db::{RelationalDB, Tx};
9-
use crate::energy::EnergyQuanta;
109
use crate::error::DBError;
1110
use crate::estimation::{check_row_limit, estimate_rows_scanned};
1211
use crate::hash::Hash;
@@ -212,7 +211,7 @@ pub struct ModuleEvent {
212211
pub function_call: ModuleFunctionCall,
213212
pub status: EventStatus,
214213
pub reducer_return_value: Option<Bytes>,
215-
pub energy_quanta_used: EnergyQuanta,
214+
pub execution_budget_used: FunctionBudget,
216215
pub host_execution_duration: Duration,
217216
pub request_id: Option<RequestId>,
218217
pub timer: Option<Instant>,
@@ -1542,7 +1541,7 @@ impl From<EventStatus> for ViewOutcome {
15421541
pub struct ViewCallResult {
15431542
pub outcome: ViewOutcome,
15441543
pub tx: MutTxId,
1545-
pub energy_used: FunctionBudget,
1544+
pub execution_budget_used: FunctionBudget,
15461545
pub total_duration: Duration,
15471546
pub abi_duration: Duration,
15481547
}
@@ -1551,7 +1550,7 @@ impl fmt::Debug for ViewCallResult {
15511550
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15521551
f.debug_struct("ViewCallResult")
15531552
.field("outcome", &self.outcome)
1554-
.field("energy_used", &self.energy_used)
1553+
.field("execution_budget_used", &self.execution_budget_used)
15551554
.field("total_duration", &self.total_duration)
15561555
.field("abi_duration", &self.abi_duration)
15571556
.finish()
@@ -1562,7 +1561,7 @@ impl ViewCallResult {
15621561
pub fn default(tx: MutTxId) -> Self {
15631562
Self {
15641563
outcome: ViewOutcome::Success,
1565-
energy_used: FunctionBudget::ZERO,
1564+
execution_budget_used: FunctionBudget::ZERO,
15661565
total_duration: Duration::ZERO,
15671566
abi_duration: Duration::ZERO,
15681567
tx,
@@ -2997,7 +2996,7 @@ impl ModuleHost {
29972996
// Increment execution stats
29982997
tx = result.tx;
29992998
outcome = result.outcome;
3000-
energy_used += result.energy_used;
2999+
energy_used += result.execution_budget_used;
30013000
total_duration += result.total_duration;
30023001
abi_duration += result.abi_duration;
30033002
trapped |= trap;
@@ -3011,7 +3010,7 @@ impl ModuleHost {
30113010
ViewCallResult {
30123011
outcome,
30133012
tx,
3014-
energy_used,
3013+
execution_budget_used: energy_used,
30153014
total_duration,
30163015
abi_duration,
30173016
},

0 commit comments

Comments
 (0)