Skip to content

Commit bec2792

Browse files
adrianlizarragaCopilotedgchen1
authored
Plugin EP event profiling APIs (#27649)
### Description #### TLDR This PR ports the existing C++ [EpProfiler](https://github.com/microsoft/onnxruntime/blob/faad20f9d3264c7f3b6d4e4398990e13ee864512/include/onnxruntime/core/framework/execution_provider.h#L359) interfaces used by provider-bridge EPs to the binary-stable C APIs for plugin EPs. It introduces C/C++ APIs for creating/querying profiling events, a container for appending EP events, and callback hooks (`StartEvent`/`StopEvent`) that give EPs access to ORT event metadata in real-time. #### Changes to the original C++ API The original `EpProfiler` C++ interface was adapted for the C API with the following intentional changes: 1. **`StartProfiling`** now receives an offset indicating the elapsed time since profiling started, as opposed to receiving an absolute/epoch-dependent profiling start time. This prevents EPs from having to do epoch conversions. Credit to @edgchen1 for the idea. 2. **`StartEvent`/`StopEvent` receive an absolute, epoch-based correlation ID (`ort_event_correlation_id`)** instead of a relative ORT event ID. The `PluginEpProfiler` bridge layer automatically converts the C++ `relative_ort_event_id` (microseconds since profiling start) to an absolute `ort_event_correlation_id` by adding the epoch-based profiling start time. This means plugin EPs can use the correlation ID directly with profiling utilities like CUPTI or ROCTracer without computing the conversion themselves. 3. **`StopEvent` now receives the completed ORT event as a parameter.** This allows EPs to optionally inspect ORT event metadata (e.g., `op_name`, `event_name`) at the time the event ends, facilitating annotation of correlated EP events. 4. **`EndProfiling` only allows EPs to *append* events (via `OrtProfilingEventsContainer`), not read or modify the full events array.** This is motivated by: - Prevent any one EP from modifying events generated by ORT or another EP. - Certain EPs (VitisAI and WebGPU) already only append events without reading the entire events array. - The CUDA EP reads the entire events array solely to merge/sort its own EP events next to correlated ORT events and add `parent_name`/`op_name` metadata. However: - Merging/sorting is mostly unnecessary since trace viewers that load these files do their own event sorting. - This merging/sorting step was previously required to augment CUDA EP events with metadata from the correlated ORT event. However, that can now be obtained more simply via the new `StopEvent` parameter that provides the EP with the full correlated ORT event. - The [merge algorithm used by CUDA EP](https://github.com/microsoft/onnxruntime/blob/faad20f9d3264c7f3b6d4e4398990e13ee864512/include/onnxruntime/core/common/gpu_profiler_common.h#L391-L397) **incorrectly** assumes ORT events are sorted by non-decreasing *start* time, but they are actually sorted by [non-decreasing *end* time](https://github.com/microsoft/onnxruntime/blob/faad20f9d3264c7f3b6d4e4398990e13ee864512/onnxruntime/core/common/profiler.cc#L91) (also see #13706 (comment)). Fixing this would require sorting the entire Events array before asking a provider-bridge EP to merge in its events into the global events array. Not sure this is worth the runtime cost. #### Naming conventions for ORT event IDs - **C++ `EpProfiler` interface** (existing): Uses `relative_ort_event_id` — a timestamp offset in microseconds relative to profiling start. - **C API `OrtEpProfilerImpl`** (new in this PR): Uses `ort_event_correlation_id` — an absolute, epoch-based timestamp in microseconds computed from `std::chrono::high_resolution_clock` (platform-defined epoch). Unique across concurrent profiling sessions within the same process. - **Conversion**: The `PluginEpProfiler` bridge class (in `ep_event_profiling.cc`) performs `ort_event_correlation_id = relative_ort_event_id + profiling_start_time_epoch_us_`, mirroring the pattern in `GPUTracerManager::PushCorrelation`. ### New C APIs | API | Description | |-----|-------------| | `CreateProfilingEvent` | Create a profiling event with category, process/thread IDs, name, timestamp, duration, and key-value args | | `ReleaseProfilingEvent` | Release a profiling event | | `ProfilingEvent_GetCategory` | Get event category (`SESSION`, `NODE`, `KERNEL`, `API`) | | `ProfilingEvent_GetName` | Get event name | | `ProfilingEvent_GetTimestampUs` | Get event start timestamp (µs) | | `ProfilingEvent_GetDurationUs` | Get event duration (µs) | | `ProfilingEvent_GetArgValue` | Get an event argument value by key | | `ProfilingEventsContainer_AddEvents` | Append an array of EP events to the output container | | `OrtEp::CreateProfiler` | Returns an instance of the EP's profiler implementation | | `OrtEpProfilerImpl::StartProfiling` | Called by ORT to start a profiling session. Receives elapsed time offset (ns) since ORT profiling started | | `OrtEpProfilerImpl::StartEvent` | Called by ORT to notify that an ORT event has started. Receives an absolute `ort_event_correlation_id` | | `OrtEpProfilerImpl::StopEvent` | Called by ORT to notify that an ORT event has ended. Receives the same `ort_event_correlation_id` and ORT event metadata | | `OrtEpProfilerImpl::EndProfiling` | Called by ORT to end the profiling session and collect EP events into the output container | | `OrtEpProfilerImpl::Release` | Release the profiler instance | ### New C++ wrapper classes | Class | Description | |-------|-------------| | `Ort::ConstProfilingEvent` | Non-owning const wrapper for reading fields from an `OrtProfilingEvent` (e.g., in `StopEvent`) | | `Ort::ProfilingEvent` | Owning wrapper that creates and manages an `OrtProfilingEvent` (e.g., for `EndProfiling`) | | `Ort::UnownedProfilingEventsContainer` | Non-owning wrapper for adding events to an `OrtProfilingEventsContainer` during `EndProfiling` | ### Example EP profiling implementation This PR updates an example plugin EP to use the new profiling APIs: - Plugin EP code: [test/autoep/library/example_plugin_ep_kernel_registry](https://github.com/microsoft/onnxruntime/tree/adrianl/PluginEp_ProfilingApis/onnxruntime/test/autoep/library/example_plugin_ep_kernel_registry) - `OrtEpProfilerImpl` implementation: [ep_profiling.h](https://github.com/microsoft/onnxruntime/blob/adrianl/PluginEp_ProfilingApis/onnxruntime/test/autoep/library/example_plugin_ep_kernel_registry/ep_profiling.h) / [ep_profiling.cc](https://github.com/microsoft/onnxruntime/blob/adrianl/PluginEp_ProfilingApis/onnxruntime/test/autoep/library/example_plugin_ep_kernel_registry/ep_profiling.cc) - `OrtEp::CreateProfiler()` implementation: [ep.cc](https://github.com/microsoft/onnxruntime/blob/adrianl/PluginEp_ProfilingApis/onnxruntime/test/autoep/library/example_plugin_ep_kernel_registry/ep.cc) ### Existing bugs found Not fixed in this PR. - The [merge algorithm used by CUDA EP](https://github.com/microsoft/onnxruntime/blob/faad20f9d3264c7f3b6d4e4398990e13ee864512/include/onnxruntime/core/common/gpu_profiler_common.h#L391-L397) **incorrectly** assumes ORT events are sorted by non-decreasing *start* time, but they are actually sorted by [non-decreasing *end* time](https://github.com/microsoft/onnxruntime/blob/faad20f9d3264c7f3b6d4e4398990e13ee864512/onnxruntime/core/common/profiler.cc#L91) (also see #13706 (comment)). - Run profilers do not handle subgraphs (e.g., subgraph of a control-flow operator). Has been the case since run profilers were [introduced](#26846). ### Motivation and Context Allows plugin EPs to generate profiling events, further closing the functionality gap between provider-bridge EPs and plugin EPs. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Edward Chen <18449977+edgchen1@users.noreply.github.com>
1 parent a997c4f commit bec2792

25 files changed

Lines changed: 1742 additions & 33 deletions

cmake/onnxruntime_unittests.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2137,6 +2137,8 @@ if (onnxruntime_BUILD_SHARED_LIB AND
21372137
"${TEST_SRC_DIR}/autoep/library/example_plugin_ep_kernel_registry/ep_data_transfer.cc"
21382138
"${TEST_SRC_DIR}/autoep/library/example_plugin_ep_kernel_registry/ep_kernel_registration.h"
21392139
"${TEST_SRC_DIR}/autoep/library/example_plugin_ep_kernel_registry/ep_kernel_registration.cc"
2140+
"${TEST_SRC_DIR}/autoep/library/example_plugin_ep_kernel_registry/ep_profiling.h"
2141+
"${TEST_SRC_DIR}/autoep/library/example_plugin_ep_kernel_registry/ep_profiling.cc"
21402142
"${TEST_SRC_DIR}/autoep/library/example_plugin_ep_kernel_registry/kernels/utils.h"
21412143
"${TEST_SRC_DIR}/autoep/library/example_plugin_ep_kernel_registry/kernels/squeeze.h"
21422144
"${TEST_SRC_DIR}/autoep/library/example_plugin_ep_kernel_registry/kernels/squeeze.cc"

include/onnxruntime/core/common/gpu_profiler_common.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,10 @@ class GPUProfilerBase : public EpProfiler {
377377
virtual ~GPUProfilerBase() {}
378378

379379
void MergeEvents(std::map<uint64_t, Events>& events_to_merge, Events& events) {
380+
// TODO: Fix incorrect assumption that ORT events are sorted by non-decreasing start time.
381+
// They are actually sorted by non-decreasing end time, which prevents this algorithm
382+
// from properly merging and annotating all EP events.
383+
// See Profiler::EndTimeAndRecordEvent() in onnxruntime/core/common/profiler.cc
380384
Events merged_events;
381385

382386
auto event_iter = events.begin();
@@ -457,7 +461,7 @@ class GPUProfilerBase : public EpProfiler {
457461
manager.PushCorrelation(client_handle_, id, profiling_start_time_);
458462
}
459463

460-
virtual void Stop(uint64_t) override {
464+
virtual void Stop(uint64_t, const EventRecord&) override {
461465
auto& manager = TManager::GetInstance();
462466
manager.PopCorrelation();
463467
}

include/onnxruntime/core/common/profiler_common.h

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
namespace onnxruntime {
1212
namespace profiling {
1313

14+
// Profiling event categories.
15+
// Note: Keep in sync with OrtProfilingEventCategory in onnxruntime_ep_c_api.h.
1416
enum EventCategory {
1517
SESSION_EVENT = 0,
1618
NODE_EVENT,
@@ -79,10 +81,71 @@ using Events = std::vector<EventRecord>;
7981
class EpProfiler {
8082
public:
8183
virtual ~EpProfiler() = default;
82-
virtual bool StartProfiling(TimePoint profiling_start_time) = 0; // called when profiling starts
83-
virtual void EndProfiling(TimePoint start_time, Events& events) = 0; // called when profiling ends, save all captures numbers to "events"
84-
virtual void Start(uint64_t) {} // called before op start, accept an id as argument to identify the op
85-
virtual void Stop(uint64_t) {} // called after op stop, accept an id as argument to identify the op
84+
85+
/// <summary>
86+
/// Called when profiling starts.
87+
/// Allows EP profiler to initialize profiling utilities and record the profiling start time.
88+
/// </summary>
89+
/// <param name="profiling_start_time">Timepoint denoting the start of profiling.</param>
90+
/// <returns>True if profiling was started successfully.</returns>
91+
virtual bool StartProfiling(TimePoint profiling_start_time) = 0;
92+
93+
/// <summary>
94+
/// Called when profiling ends to collect the EP's new profiling events since the last call to StartProfiling.
95+
/// </summary>
96+
/// <param name="start_time">Timepoint denoting the start of profiling. Same value passed to StartProfiling.</param>
97+
/// <param name="events">Modifiable events container to which the EP profiler appends its events.</param>
98+
virtual void EndProfiling(TimePoint start_time, Events& events) = 0;
99+
100+
/// <summary>
101+
/// Optional to override (default implementation does nothing).
102+
///
103+
/// Called when an ORT event (e.g., session initialization, node kernel execution, etc.) starts.
104+
/// ORT pairs every Start call with a corresponding call to Stop with the same relative ORT event ID.
105+
/// EP profiler implementations may use the calls to Start and Stop to maintain a stack of ORT event IDs
106+
/// that can be correlated with EP events (e.g., GPU kernel events).
107+
///
108+
/// A relative ORT event ID is computed as a timestamp offset relative to the profiling start time:
109+
/// relative_ort_event_id =
110+
/// std::chrono::duration_cast<std::chrono::microseconds>(event_start_time - profiling_start_time).count();
111+
///
112+
/// Because relative ORT event IDs are relative to profiling start, different profiling sessions may reuse the same
113+
/// values. If the EP's profiling utilities (e.g., CUPTI or ROCTracer) require correlation IDs that are practically
114+
/// unique across concurrent profiling sessions (collisions require sub-microsecond event concurrency), then the
115+
/// EP profiler should compute an absolute correlation ID:
116+
/// absolute_ort_correlation_id =
117+
/// relative_ort_event_id +
118+
/// std::chrono::duration_cast<std::chrono::microseconds>(profiling_start_time.time_since_epoch()).count();
119+
///
120+
/// Note: For plugin EPs using the binary-stable C API (OrtEpProfilerImpl), ORT performs this conversion
121+
/// automatically. The C API's StartEvent/StopEvent receive the absolute correlation ID directly.
122+
/// </summary>
123+
/// <param name="relative_ort_event_id">
124+
/// Relative ID of the ORT event that is starting (microseconds since profiling start).
125+
/// The same value is passed to a corresponding call to Stop.
126+
/// </param>
127+
virtual void Start(uint64_t /*relative_ort_event_id*/) {}
128+
129+
/// <summary>
130+
/// Optional to override (default implementation does nothing).
131+
///
132+
/// Called when an ORT event (e.g., session initialization, node kernel execution, etc.) ends.
133+
/// ORT pairs every Start call with a corresponding call to Stop with the same relative ORT event ID.
134+
/// EP profiler implementations may use the calls to Start and Stop to maintain a stack of ORT event IDs
135+
/// that can be correlated with EP events (e.g., GPU kernel events).
136+
///
137+
/// The ort_event parameter provides the full ORT event record including metadata such as op_name
138+
/// (in event args), event name, category, timestamps, etc. EP profilers can use this to annotate
139+
/// their own events with ORT event context.
140+
/// </summary>
141+
/// <param name="relative_ort_event_id">
142+
/// Relative ID of the ORT event that is ending (microseconds since profiling start).
143+
/// The same value was passed to a corresponding call to Start.
144+
/// </param>
145+
/// <param name="ort_event">
146+
/// The ORT event record containing metadata for this event.
147+
/// </param>
148+
virtual void Stop(uint64_t /*relative_ort_event_id*/, const EventRecord& /*ort_event*/) {}
86149
};
87150

88151
// Demangle C++ symbols

include/onnxruntime/core/session/onnxruntime_cxx_api.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ ORT_DEFINE_RELEASE_FROM_API_STRUCT(KernelDef, GetEpApi);
662662
ORT_DEFINE_RELEASE_FROM_API_STRUCT(KernelDefBuilder, GetEpApi);
663663
ORT_DEFINE_RELEASE_FROM_API_STRUCT(KernelRegistry, GetEpApi);
664664
ORT_DEFINE_RELEASE_FROM_API_STRUCT(OpSchema, GetEpApi);
665+
ORT_DEFINE_RELEASE_FROM_API_STRUCT(ProfilingEvent, GetEpApi);
665666

666667
// This is defined explicitly since OrtTensorRTProviderOptionsV2 is not a C API type,
667668
// but the struct has V2 in its name to indicate that it is the second version of the options.
@@ -1231,6 +1232,95 @@ struct EpAssignedSubgraphImpl : Ort::detail::Base<T> {
12311232
*/
12321233
using ConstEpAssignedSubgraph = detail::EpAssignedSubgraphImpl<Ort::detail::Unowned<const OrtEpAssignedSubgraph>>;
12331234

1235+
namespace detail {
1236+
template <typename T>
1237+
struct ConstProfilingEventImpl : Ort::detail::Base<T> {
1238+
using B = Ort::detail::Base<T>;
1239+
using B::B;
1240+
1241+
/// \brief Get the event category. Wraps OrtEpApi::ProfilingEvent_GetCategory
1242+
OrtProfilingEventCategory GetCategory() const;
1243+
1244+
/// \brief Get the event name. Wraps OrtEpApi::ProfilingEvent_GetName
1245+
/// \return Null-terminated UTF-8 string owned by the OrtProfilingEvent instance. Do not free.
1246+
const char* GetName() const;
1247+
1248+
/// \brief Get the start timestamp in microseconds. Wraps OrtEpApi::ProfilingEvent_GetTimestampUs
1249+
int64_t GetTimestampUs() const;
1250+
1251+
/// \brief Get the duration in microseconds. Wraps OrtEpApi::ProfilingEvent_GetDurationUs
1252+
int64_t GetDurationUs() const;
1253+
1254+
/// \brief Get the value of an event argument by key. Wraps OrtEpApi::ProfilingEvent_GetArgValue
1255+
/// \param[in] key Null-terminated argument key to look up.
1256+
/// \return Null-terminated UTF-8 string owned by the OrtProfilingEvent, or nullptr if key not found.
1257+
const char* GetArgValue(const char* key) const;
1258+
};
1259+
} // namespace detail
1260+
1261+
/** \brief Non-owning const wrapper around ::OrtProfilingEvent.
1262+
*
1263+
* Use this to read fields from the OrtProfilingEvent pointer passed to OrtEpProfilerImpl::StopEvent.
1264+
*
1265+
* This is based on the Trace Event Format's "complete event".
1266+
* \since Version 1.25.
1267+
*/
1268+
using ConstProfilingEvent = detail::ConstProfilingEventImpl<Ort::detail::Unowned<const OrtProfilingEvent>>;
1269+
1270+
/** \brief Owning wrapper around ::OrtProfilingEvent.
1271+
*
1272+
* Use this to create profiling events via OrtEpApi::CreateProfilingEvent. The event is released
1273+
* automatically on destruction.
1274+
* \since Version 1.25.
1275+
*/
1276+
struct ProfilingEvent : detail::ConstProfilingEventImpl<OrtProfilingEvent> {
1277+
explicit ProfilingEvent(std::nullptr_t) {} ///< No instance is created
1278+
explicit ProfilingEvent(OrtProfilingEvent* p)
1279+
: ConstProfilingEventImpl<OrtProfilingEvent>{p} {} ///< Take ownership
1280+
1281+
/// \brief Wraps OrtEpApi::CreateProfilingEvent
1282+
ProfilingEvent(OrtProfilingEventCategory category,
1283+
int32_t process_id,
1284+
int32_t thread_id,
1285+
const char* event_name,
1286+
int64_t timestamp_us,
1287+
int64_t duration_us,
1288+
const std::unordered_map<std::string, std::string>& args = {});
1289+
1290+
/// \brief Wraps OrtEpApi::CreateProfilingEvent
1291+
ProfilingEvent(OrtProfilingEventCategory category,
1292+
int32_t process_id,
1293+
int32_t thread_id,
1294+
const char* event_name,
1295+
int64_t timestamp_us,
1296+
int64_t duration_us,
1297+
const char* const* arg_keys,
1298+
const char* const* arg_values,
1299+
size_t num_args);
1300+
1301+
ConstProfilingEvent GetConst() const { return ConstProfilingEvent{this->p_}; }
1302+
};
1303+
1304+
namespace detail {
1305+
template <typename T>
1306+
struct ProfilingEventsContainerImpl : Ort::detail::Base<T> {
1307+
using B = Ort::detail::Base<T>;
1308+
using B::B;
1309+
1310+
/// \brief Adds profiling events to this container. Events are copied.
1311+
/// Wraps OrtEpApi::ProfilingEventsContainer_AddEvents.
1312+
Ort::Status AddEvents(const OrtProfilingEvent* const* events, size_t num_events);
1313+
Ort::Status AddEvents(const std::vector<ProfilingEvent>& events);
1314+
};
1315+
} // namespace detail
1316+
1317+
/** \brief Non-owning wrapper around ::OrtProfilingEventsContainer.
1318+
*
1319+
* Use this to add EP profiling events to a container owned by ORT during the call to OrtEpProfilerImpl::EndProfiling.
1320+
* \since Version 1.25.
1321+
*/
1322+
using UnownedProfilingEventsContainer = detail::ProfilingEventsContainerImpl<detail::Unowned<OrtProfilingEventsContainer>>;
1323+
12341324
/** \brief The Env (Environment)
12351325
*
12361326
* The Env holds the logging state used by all other objects.

include/onnxruntime/core/session/onnxruntime_cxx_inline.h

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,98 @@ inline std::string EpAssignedNodeImpl<T>::GetOperatorType() const {
802802
}
803803
} // namespace detail
804804

805+
// ProfilingEvent implementations
806+
namespace detail {
807+
template <typename T>
808+
inline OrtProfilingEventCategory ConstProfilingEventImpl<T>::GetCategory() const {
809+
OrtProfilingEventCategory out{};
810+
ThrowOnError(GetEpApi().ProfilingEvent_GetCategory(this->p_, &out));
811+
return out;
812+
}
813+
814+
template <typename T>
815+
inline const char* ConstProfilingEventImpl<T>::GetName() const {
816+
const char* name = nullptr;
817+
ThrowOnError(GetEpApi().ProfilingEvent_GetName(this->p_, &name));
818+
return name;
819+
}
820+
821+
template <typename T>
822+
inline int64_t ConstProfilingEventImpl<T>::GetTimestampUs() const {
823+
int64_t out = 0;
824+
ThrowOnError(GetEpApi().ProfilingEvent_GetTimestampUs(this->p_, &out));
825+
return out;
826+
}
827+
828+
template <typename T>
829+
inline int64_t ConstProfilingEventImpl<T>::GetDurationUs() const {
830+
int64_t out = 0;
831+
ThrowOnError(GetEpApi().ProfilingEvent_GetDurationUs(this->p_, &out));
832+
return out;
833+
}
834+
835+
template <typename T>
836+
inline const char* ConstProfilingEventImpl<T>::GetArgValue(const char* key) const {
837+
const char* value = nullptr;
838+
ThrowOnError(GetEpApi().ProfilingEvent_GetArgValue(this->p_, key, &value));
839+
return value;
840+
}
841+
} // namespace detail
842+
843+
inline ProfilingEvent::ProfilingEvent(OrtProfilingEventCategory category,
844+
int32_t process_id,
845+
int32_t thread_id,
846+
const char* event_name,
847+
int64_t timestamp_us,
848+
int64_t duration_us,
849+
const std::unordered_map<std::string, std::string>& args) {
850+
const size_t num_args = args.size();
851+
std::vector<const char*> arg_keys;
852+
std::vector<const char*> arg_vals;
853+
854+
arg_keys.reserve(num_args);
855+
arg_vals.reserve(num_args);
856+
for (const auto& [k, v] : args) {
857+
arg_keys.push_back(k.c_str());
858+
arg_vals.push_back(v.c_str());
859+
}
860+
861+
ThrowOnError(GetEpApi().CreateProfilingEvent(category, process_id, thread_id, event_name,
862+
timestamp_us, duration_us,
863+
arg_keys.data(), arg_vals.data(), num_args, &p_));
864+
}
865+
866+
inline ProfilingEvent::ProfilingEvent(OrtProfilingEventCategory category,
867+
int32_t process_id,
868+
int32_t thread_id,
869+
const char* event_name,
870+
int64_t timestamp_us,
871+
int64_t duration_us,
872+
const char* const* arg_keys,
873+
const char* const* arg_values,
874+
size_t num_args) {
875+
ThrowOnError(GetEpApi().CreateProfilingEvent(category, process_id, thread_id, event_name,
876+
timestamp_us, duration_us,
877+
arg_keys, arg_values, num_args, &p_));
878+
}
879+
880+
// ProfilingEventsContainer implementations
881+
namespace detail {
882+
template <typename T>
883+
inline Status ProfilingEventsContainerImpl<T>::AddEvents(const OrtProfilingEvent* const* events, size_t num_events) {
884+
return Status{GetEpApi().ProfilingEventsContainer_AddEvents(this->p_, events, num_events)};
885+
}
886+
887+
template <typename T>
888+
inline Status ProfilingEventsContainerImpl<T>::AddEvents(const std::vector<ProfilingEvent>& events) {
889+
static_assert(sizeof(ProfilingEvent) == sizeof(OrtProfilingEvent*) &&
890+
alignof(ProfilingEvent) == alignof(OrtProfilingEvent*),
891+
"ProfilingEvent must have the same size and alignment as a raw pointer for reinterpret_cast");
892+
const auto* event_ptrs = reinterpret_cast<const OrtProfilingEvent* const*>(events.data());
893+
return AddEvents(event_ptrs, events.size());
894+
}
895+
} // namespace detail
896+
805897
inline Env::Env(OrtLoggingLevel logging_level, _In_ const char* logid) {
806898
ThrowOnError(GetApi().CreateEnv(logging_level, logid, &p_));
807899
if (strcmp(logid, "onnxruntime-node") == 0) {

0 commit comments

Comments
 (0)