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+ memory_used_on_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 = "cpu-time-metrics" ) ]
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 ( ) . memory_used_on_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+ memory_used_on_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,43 @@ pub struct InstanceState<T, U> {
282328 core : spin_core:: State ,
283329 factors : T ,
284330 executor : U ,
331+ /// The component ID.
332+ component_id : String ,
333+
334+ /// The last time guest code started running in this instance.
335+ cpu_time_last_entry : Option < Instant > ,
336+ /// The total CPU time elapsed actively running guest code in this instance.
337+ cpu_time_elapsed : Duration ,
338+ /// The memory (in bytes) consumed on initialization.
339+ memory_used_on_init : u64 ,
340+ }
341+
342+ impl < T , U > Drop for InstanceState < T , U > {
343+ fn drop ( & mut self ) {
344+ // Record the component execution time.
345+ #[ cfg( feature = "cpu-time-metrics" ) ]
346+ spin_telemetry:: metrics:: histogram!(
347+ spin. component_cpu_time = self . cpu_time_elapsed. as_secs_f64( ) ,
348+ component_id = self . component_id,
349+ // According to the OpenTelemetry spec, instruments measuring durations should use "s" as the unit.
350+ // See https://opentelemetry.io/docs/specs/semconv/general/metrics/#units
351+ unit = "s"
352+ ) ;
353+
354+ // Record the component memory consumed on initialization.
355+ spin_telemetry:: metrics:: histogram!(
356+ spin. component_memory_used_on_init = self . memory_used_on_init,
357+ component_id = self . component_id,
358+ unit = "By"
359+ ) ;
360+
361+ // Record the component memory consumed during execution.
362+ spin_telemetry:: metrics:: histogram!(
363+ spin. component_memory_used = self . core. memory_consumed( ) ,
364+ component_id = self . component_id,
365+ unit = "By"
366+ ) ;
367+ }
285368}
286369
287370impl < T , U > InstanceState < T , U > {
0 commit comments