Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add C API for GuestProfiler #7854

Merged
merged 7 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/c-api/include/wasmtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@
#include <wasmtime/linker.h>
#include <wasmtime/memory.h>
#include <wasmtime/module.h>
#include <wasmtime/profiling.h>
#include <wasmtime/store.h>
#include <wasmtime/table.h>
#include <wasmtime/trap.h>
Expand Down
113 changes: 113 additions & 0 deletions crates/c-api/include/wasmtime/profiling.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* \file wasmtime/profiling.h
*
* \brief API for Wasmtime guest profiler
*/

#ifndef WASMTIME_PROFILING_H
#define WASMTIME_PROFILING_H

#include <wasm.h>
#include <wasmtime/error.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* \brief Collects basic profiling data for a single WebAssembly guest.
*
* To use this, you’ll need to arrange to call #wasmtime_guestprofiler_sample at
* regular intervals while the guest is on the stack. The most straightforward
* way to do that is to call it from a callback registered with
* #wasmtime_store_epoch_deadline_callback.
*
* For more information see the Rust documentation at:
* https://docs.wasmtime.dev/api/wasmtime/struct.GuestProfiler.html
*/
typedef struct wasmtime_guestprofiler wasmtime_guestprofiler_t;

/**
* \brief Deletes profiler without finishing it.
*
* \param guestprofiler profiler that is being deleted
*/
WASM_API_EXTERN void wasmtime_guestprofiler_delete(
Milek7 marked this conversation as resolved.
Show resolved Hide resolved
/* own */ wasmtime_guestprofiler_t *guestprofiler);

/**
* \typedef wasmtime_guestprofiler_modules_t
* \brief Alias to #wasmtime_guestprofiler_modules
*
* \struct #wasmtime_guestprofiler_modules
* \brief Tuple of name and module for passing into #wasmtime_guestprofiler_new.
*/
typedef struct wasmtime_guestprofiler_modules {
const wasm_name_t *name; //!< Name recorded in the profile.
const wasmtime_module_t
*mod; //!< Module that is being allowed to appear in captured stack trace.
Milek7 marked this conversation as resolved.
Show resolved Hide resolved
} wasmtime_guestprofiler_modules_t;

/**
* \brief Begin profiling a new guest.
*
* \param module_name name recorded in the profile
* \param interval_nanos intended sampling interval in nanoseconds recorded in
* the profile
* \param modules modules and associated names that will appear in
* captured stack traces, pointer to the first element
* \param modules_len count of elements in `modules`
*
* \return Created profiler that is owned by the caller.
*
* This function does not take ownership of the arguments.
*
* For more information see the Rust documentation at:
* https://docs.wasmtime.dev/api/wasmtime/struct.GuestProfiler.html#method.new
*/
WASM_API_EXTERN /* own */ wasmtime_guestprofiler_t *wasmtime_guestprofiler_new(
const wasm_name_t *module_name, uint64_t interval_nanos,
const wasmtime_guestprofiler_modules_t *modules, size_t modules_len);

/**
* \brief Add a sample to the profile.
*
* \param guestprofiler the profiler the sample is being added to
* \param store store that is being used to collect the backtraces
*
* This function does not take ownership of the arguments.
*
* For more information see the Rust documentation at:
* https://docs.wasmtime.dev/api/wasmtime/struct.GuestProfiler.html#method.sample
*/
WASM_API_EXTERN void
wasmtime_guestprofiler_sample(wasmtime_guestprofiler_t *guestprofiler,
const wasmtime_store_t *store);

/**
* \brief Writes out the captured profile.
*
* \param guestprofiler the profiler which is being finished and deleted
* \param out pointer to where #wasm_byte_vec_t containing generated
* file will be written
*
* \return Returns #wasmtime_error_t owned by the caller in case of error,
* `NULL` otherwise.
*
* This function takes ownership of `guestprofiler`, even when error is
* returned.
* Only when returning without error `out` is filled with #wasm_byte_vec_t owned
* by the caller.
*
* For more information see the Rust documentation at:
* https://docs.wasmtime.dev/api/wasmtime/struct.GuestProfiler.html#method.finish
*/
WASM_API_EXTERN /* own */ wasmtime_error_t *
wasmtime_guestprofiler_finish(/* own */ wasmtime_guestprofiler_t *guestprofiler,
/* own */ wasm_byte_vec_t *out);

#ifdef __cplusplus
} // extern "C"
#endif

#endif // WASMTIME_PROFILING_H
2 changes: 2 additions & 0 deletions crates/c-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ mod instance;
mod linker;
mod memory;
mod module;
#[cfg(feature = "profiling")]
mod profiling;
mod r#ref;
mod store;
mod table;
Expand Down
64 changes: 64 additions & 0 deletions crates/c-api/src/profiling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::{wasm_byte_vec_t, wasm_name_t, wasmtime_error_t, wasmtime_module_t, wasmtime_store_t};
use std::slice;
use std::str::from_utf8;
use std::time::Duration;
use wasmtime::GuestProfiler;

pub struct wasmtime_guestprofiler_t {
guest_profiler: GuestProfiler,
}

wasmtime_c_api_macros::declare_own!(wasmtime_guestprofiler_t);

#[repr(C)]
pub struct wasmtime_guestprofiler_modules_t<'a> {
name: &'a wasm_name_t,
module: &'a wasmtime_module_t,
}

#[no_mangle]
pub unsafe extern "C" fn wasmtime_guestprofiler_new(
module_name: &wasm_name_t,
interval_nanos: u64,
modules: *const wasmtime_guestprofiler_modules_t,
modules_len: usize,
) -> Box<wasmtime_guestprofiler_t> {
let module_name = from_utf8(&module_name.as_slice()).expect("not valid utf-8");
let list = slice::from_raw_parts(modules, modules_len)
.iter()
.map(|entry| {
(
from_utf8(entry.name.as_slice())
.expect("not valid utf-8")
.to_owned(),
entry.module.module.clone(),
)
})
.collect();
Box::new(wasmtime_guestprofiler_t {
guest_profiler: GuestProfiler::new(module_name, Duration::from_nanos(interval_nanos), list),
})
}

#[no_mangle]
pub extern "C" fn wasmtime_guestprofiler_sample(
guestprofiler: &mut wasmtime_guestprofiler_t,
store: &wasmtime_store_t,
) {
guestprofiler.guest_profiler.sample(&store.store);
}

#[no_mangle]
pub extern "C" fn wasmtime_guestprofiler_finish(
guestprofiler: Box<wasmtime_guestprofiler_t>,
out: &mut wasm_byte_vec_t,
) -> Option<Box<wasmtime_error_t>> {
let mut buf = vec![];
match guestprofiler.guest_profiler.finish(&mut buf) {
Ok(()) => {
out.set_buffer(buf);
None
}
Err(e) => Some(Box::new(e.into())),
}
}