1+ use std:: time:: { Duration , Instant } ;
12use std:: { collections:: HashMap , sync:: Arc } ;
23
34use anyhow:: Context ;
45use spin_app:: { App , AppComponent } ;
5- use spin_core:: { async_trait, Component } ;
6+ use spin_core:: { async_trait, wasmtime :: CallHook , Component } ;
67use spin_factors:: {
78 AsInstanceState , ConfiguredApp , Factor , HasInstanceBuilder , RuntimeFactors ,
89 RuntimeFactorsInstanceState ,
@@ -255,9 +256,24 @@ impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
255256 core : Default :: default ( ) ,
256257 factors : self . factors . build_instance_state ( self . factor_builders ) ?,
257258 executor : executor_instance_state,
259+ cpu_time_elapsed : Duration :: from_millis ( 0 ) ,
260+ cpu_time_last_entry : None ,
261+ mem_usg_init : 0 ,
262+ component_id : self . app_component . id ( ) . into ( ) ,
258263 } ;
259264 let mut store = self . store_builder . build ( instance_state) ?;
265+
266+ #[ cfg( feature = "call-hook" ) ]
267+ store. as_mut ( ) . call_hook ( |mut store, hook| {
268+ CpuTimeCallHook . handle_call_event :: < T , U > ( store. data_mut ( ) , hook)
269+ } ) ;
270+
260271 let instance = self . instance_pre . instantiate_async ( & mut store) . await ?;
272+
273+ // Track memory usage after instantiation in the instance state.
274+ // Note: This only applies if the component has initial memory reservations.
275+ store. data_mut ( ) . mem_usg_init = store. data ( ) . core_state ( ) . memory_consumed ( ) ;
276+
261277 Ok ( ( instance, store) )
262278 }
263279
@@ -269,11 +285,41 @@ impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
269285 core : Default :: default ( ) ,
270286 factors : self . factors . build_instance_state ( self . factor_builders ) ?,
271287 executor : executor_instance_state,
288+ cpu_time_elapsed : Duration :: from_millis ( 0 ) ,
289+ cpu_time_last_entry : None ,
290+ mem_usg_init : 0 ,
291+ component_id : self . app_component . id ( ) . into ( ) ,
272292 } ;
273293 self . store_builder . build ( instance_state)
274294 }
275295}
276296
297+ // Tracks CPU time used by a Wasm guest.
298+ #[ allow( unused) ]
299+ struct CpuTimeCallHook ;
300+
301+ #[ allow( unused) ]
302+ impl CpuTimeCallHook {
303+ fn handle_call_event < T : RuntimeFactors , U > (
304+ & self ,
305+ state : & mut InstanceState < T :: InstanceState , U > ,
306+ ch : CallHook ,
307+ ) -> anyhow:: Result < ( ) > {
308+ match ch {
309+ CallHook :: CallingWasm | CallHook :: ReturningFromHost => {
310+ debug_assert ! ( state. cpu_time_last_entry. is_none( ) ) ;
311+ state. cpu_time_last_entry = Some ( Instant :: now ( ) ) ;
312+ }
313+ CallHook :: ReturningFromWasm | CallHook :: CallingHost => {
314+ let elapsed = state. cpu_time_last_entry . take ( ) . unwrap ( ) . elapsed ( ) ;
315+ state. cpu_time_elapsed += elapsed;
316+ }
317+ }
318+
319+ Ok ( ( ) )
320+ }
321+ }
322+
277323/// InstanceState is the [`spin_core::Store`] `data` for an instance.
278324///
279325/// It is generic over the [`RuntimeFactors::InstanceState`] and any ad-hoc
@@ -282,6 +328,42 @@ pub struct InstanceState<T, U> {
282328 core : spin_core:: State ,
283329 factors : T ,
284330 executor : U ,
331+ /// The last time guest code started running in this instance.
332+ pub cpu_time_last_entry : Option < Instant > ,
333+ /// The total CPU time elapsed actively running guest code in this instance.
334+ pub cpu_time_elapsed : Duration ,
335+ /// The component ID.
336+ pub component_id : String ,
337+ /// The memory (in bytes) consumed on initialization.
338+ pub mem_usg_init : u64 ,
339+ }
340+
341+ impl < T , U > Drop for InstanceState < T , U > {
342+ fn drop ( & mut self ) {
343+ // Record the component execution time.
344+ #[ cfg( feature = "call-hook" ) ]
345+ spin_telemetry:: metrics:: histogram!(
346+ spin. component_cpu_time = self . cpu_time_elapsed. as_micros( ) as f64 ,
347+ component_id = self . component_id,
348+ // According to the OpenTelemetry spec, instruments measuring durations should use "s" as the unit.
349+ // See https://opentelemetry.io/docs/specs/semconv/general/metrics/#units
350+ unit = "s"
351+ ) ;
352+
353+ // Record the component memory consumed on initialization.
354+ spin_telemetry:: metrics:: histogram!(
355+ spin. component_init_memory_used = self . mem_usg_init,
356+ component_id = self . component_id,
357+ unit = "bytes"
358+ ) ;
359+
360+ // Record the component memory consumed during execution.
361+ spin_telemetry:: metrics:: histogram!(
362+ spin. component_exec_memory_used = self . core. memory_consumed( ) ,
363+ component_id = self . component_id,
364+ unit = "bytes"
365+ ) ;
366+ }
285367}
286368
287369impl < T , U > InstanceState < T , U > {
0 commit comments