blob: 1a83f621173ad3c8aa062204f498245421082e8a [file] [log] [blame] [edit]
//===- OmptTester.cpp - ompTest OMPT tool implementation --------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file represents the core implementation file for the ompTest library.
/// It provides the actual OMPT tool implementation: registers callbacks, etc.
/// OMPT callbacks are passed to their corresponding handler, which in turn
/// notifies all registered asserters.
///
//===----------------------------------------------------------------------===//
#include "OmptTester.h"
#include <atomic>
#include <cassert>
#include <cstdlib>
#include <cstring>
using namespace omptest;
// Callback handler, which receives and relays OMPT callbacks
extern OmptCallbackHandler *Handler;
// EventListener, which actually prints the OMPT events
static OmptEventReporter *EventReporter;
// From openmp/runtime/test/ompt/callback.h
#define register_ompt_callback_t(name, type) \
do { \
type f_##name = &on_##name; \
if (ompt_set_callback(name, (ompt_callback_t)f_##name) == ompt_set_never) \
printf("0: Could not register callback '" #name "'\n"); \
} while (0)
#define register_ompt_callback(name) register_ompt_callback_t(name, name##_t)
#define OMPT_BUFFER_REQUEST_SIZE 256
#ifdef OPENMP_LIBOMPTEST_BUILD_STANDALONE
std::vector<std::pair<std::string, TestSuite>> TestRegistrar::Tests;
#endif
static std::atomic<ompt_id_t> NextOpId{0x8000000000000001};
static bool UseEMICallbacks = false;
static bool UseTracing = false;
static bool RunAsTestSuite = false;
static bool ColoredLog = false;
// OMPT entry point handles
static ompt_set_trace_ompt_t ompt_set_trace_ompt = 0;
static ompt_start_trace_t ompt_start_trace = 0;
static ompt_flush_trace_t ompt_flush_trace = 0;
static ompt_stop_trace_t ompt_stop_trace = 0;
static ompt_get_record_ompt_t ompt_get_record_ompt = 0;
static ompt_advance_buffer_cursor_t ompt_advance_buffer_cursor = 0;
static ompt_get_record_type_t ompt_get_record_type_fn = 0;
// OMPT device side tracing: Currently traced devices
typedef std::unordered_set<ompt_device_t *> OmptDeviceSetTy;
typedef std::unique_ptr<OmptDeviceSetTy> OmptDeviceSetPtrTy;
static OmptDeviceSetPtrTy TracedDevices;
// OMPT callbacks
// Trace record callbacks
static void on_ompt_callback_buffer_request(int device_num,
ompt_buffer_t **buffer,
size_t *bytes) {
*bytes = OMPT_BUFFER_REQUEST_SIZE;
*buffer = malloc(*bytes);
OmptCallbackHandler::get().handleBufferRequest(device_num, buffer, bytes);
}
// Note: This callback must handle a null begin cursor. Currently,
// ompt_get_record_ompt, print_record_ompt, and
// ompt_advance_buffer_cursor handle a null cursor.
static void on_ompt_callback_buffer_complete(
int device_num, ompt_buffer_t *buffer,
size_t bytes, /* bytes returned in this callback */
ompt_buffer_cursor_t begin, int buffer_owned) {
OmptCallbackHandler::get().handleBufferComplete(device_num, buffer, bytes,
begin, buffer_owned);
int Status = 1;
ompt_buffer_cursor_t CurrentPos = begin;
while (Status) {
ompt_record_ompt_t *Record = ompt_get_record_ompt(buffer, CurrentPos);
if (ompt_get_record_type_fn(buffer, CurrentPos) != ompt_record_ompt) {
printf("Warning: received non-ompt type buffer object\n");
}
// TODO: Sometimes it may happen that the retrieved record may be null?!
// Only handle non-null records
if (Record != nullptr)
OmptCallbackHandler::get().handleBufferRecord(Record);
Status = ompt_advance_buffer_cursor(/*device=*/NULL, buffer, bytes,
CurrentPos, &CurrentPos);
}
if (buffer_owned) {
OmptCallbackHandler::get().handleBufferRecordDeallocation(buffer);
free(buffer);
}
}
static ompt_set_result_t set_trace_ompt(ompt_device_t *Device) {
if (!ompt_set_trace_ompt)
return ompt_set_error;
if (UseEMICallbacks) {
ompt_set_trace_ompt(Device, /*enable=*/1,
/*etype=*/ompt_callback_target_emi);
ompt_set_trace_ompt(Device, /*enable=*/1,
/*etype=*/ompt_callback_target_data_op_emi);
ompt_set_trace_ompt(Device, /*enable=*/1,
/*etype=*/ompt_callback_target_submit_emi);
} else {
ompt_set_trace_ompt(Device, /*enable=*/1, /*etype=*/ompt_callback_target);
ompt_set_trace_ompt(Device, /*enable=*/1,
/*etype=*/ompt_callback_target_data_op);
ompt_set_trace_ompt(Device, /*enable=*/1,
/*etype=*/ompt_callback_target_submit);
}
return ompt_set_always;
}
/////// HOST-RELATED //////
static void on_ompt_callback_thread_begin(ompt_thread_t thread_type,
ompt_data_t *thread_data) {
OmptCallbackHandler::get().handleThreadBegin(thread_type, thread_data);
}
static void on_ompt_callback_thread_end(ompt_data_t *thread_data) {
OmptCallbackHandler::get().handleThreadEnd(thread_data);
}
static void on_ompt_callback_parallel_begin(
ompt_data_t *encountering_task_data,
const ompt_frame_t *encountering_task_frame, ompt_data_t *parallel_data,
unsigned int requested_parallelism, int flags, const void *codeptr_ra) {
OmptCallbackHandler::get().handleParallelBegin(
encountering_task_data, encountering_task_frame, parallel_data,
requested_parallelism, flags, codeptr_ra);
}
static void on_ompt_callback_parallel_end(ompt_data_t *parallel_data,
ompt_data_t *encountering_task_data,
int flags, const void *codeptr_ra) {
OmptCallbackHandler::get().handleParallelEnd(
parallel_data, encountering_task_data, flags, codeptr_ra);
}
static void
on_ompt_callback_task_create(ompt_data_t *encountering_task_data,
const ompt_frame_t *encountering_task_frame,
ompt_data_t *new_task_data, int flags,
int has_dependences, const void *codeptr_ra) {
OmptCallbackHandler::get().handleTaskCreate(
encountering_task_data, encountering_task_frame, new_task_data, flags,
has_dependences, codeptr_ra);
}
static void on_ompt_callback_task_schedule(ompt_data_t *prior_task_data,
ompt_task_status_t prior_task_status,
ompt_data_t *next_task_data) {
OmptCallbackHandler::get().handleTaskSchedule(
prior_task_data, prior_task_status, next_task_data);
}
static void on_ompt_callback_implicit_task(ompt_scope_endpoint_t endpoint,
ompt_data_t *parallel_data,
ompt_data_t *task_data,
unsigned int actual_parallelism,
unsigned int index, int flags) {
OmptCallbackHandler::get().handleImplicitTask(
endpoint, parallel_data, task_data, actual_parallelism, index, flags);
}
// Callbacks as of Table 19.4, which are not considered required for a minimal
// conforming OMPT implementation.
static void on_ompt_callback_work(ompt_work_t work_type,
ompt_scope_endpoint_t endpoint,
ompt_data_t *parallel_data,
ompt_data_t *task_data, uint64_t count,
const void *codeptr_ra) {
OmptCallbackHandler::get().handleWork(work_type, endpoint, parallel_data,
task_data, count, codeptr_ra);
}
static void on_ompt_callback_dispatch(ompt_data_t *parallel_data,
ompt_data_t *task_data,
ompt_dispatch_t kind,
ompt_data_t instance) {
OmptCallbackHandler::get().handleDispatch(parallel_data, task_data, kind,
instance);
}
static void on_ompt_callback_sync_region(ompt_sync_region_t kind,
ompt_scope_endpoint_t endpoint,
ompt_data_t *parallel_data,
ompt_data_t *task_data,
const void *codeptr_ra) {
OmptCallbackHandler::get().handleSyncRegion(kind, endpoint, parallel_data,
task_data, codeptr_ra);
}
/////// DEVICE-RELATED //////
// Synchronous callbacks
static void on_ompt_callback_device_initialize(int device_num, const char *type,
ompt_device_t *device,
ompt_function_lookup_t lookup,
const char *documentation) {
OmptCallbackHandler::get().handleDeviceInitialize(device_num, type, device,
lookup, documentation);
if (!UseTracing)
return;
if (!lookup) {
printf("Trace collection disabled on device %d\n", device_num);
return;
}
ompt_set_trace_ompt = (ompt_set_trace_ompt_t)lookup("ompt_set_trace_ompt");
ompt_start_trace = (ompt_start_trace_t)lookup("ompt_start_trace");
ompt_flush_trace = (ompt_flush_trace_t)lookup("ompt_flush_trace");
ompt_stop_trace = (ompt_stop_trace_t)lookup("ompt_stop_trace");
ompt_get_record_ompt = (ompt_get_record_ompt_t)lookup("ompt_get_record_ompt");
ompt_advance_buffer_cursor =
(ompt_advance_buffer_cursor_t)lookup("ompt_advance_buffer_cursor");
ompt_get_record_type_fn =
(ompt_get_record_type_t)lookup("ompt_get_record_type");
if (!ompt_get_record_type_fn) {
printf("Warning: No function ompt_get_record_type found in device "
"callbacks\n");
}
static bool IsDeviceMapInitialized = false;
if (!IsDeviceMapInitialized) {
TracedDevices = std::make_unique<OmptDeviceSetTy>();
IsDeviceMapInitialized = true;
}
set_trace_ompt(device);
// In many scenarios, this is a good place to start the
// trace. If start_trace is called from the main program before this
// callback is dispatched, the start_trace handle will be null. This
// is because this device_init callback is invoked during the first
// target construct implementation.
start_trace(device);
}
static void on_ompt_callback_device_finalize(int device_num) {
OmptCallbackHandler::get().handleDeviceFinalize(device_num);
}
static void on_ompt_callback_device_load(int device_num, const char *filename,
int64_t offset_in_file,
void *vma_in_file, size_t bytes,
void *host_addr, void *device_addr,
uint64_t module_id) {
OmptCallbackHandler::get().handleDeviceLoad(
device_num, filename, offset_in_file, vma_in_file, bytes, host_addr,
device_addr, module_id);
}
static void on_ompt_callback_device_unload(int device_num, uint64_t module_id) {
OmptCallbackHandler::get().handleDeviceUnload(device_num, module_id);
}
static void on_ompt_callback_target_data_op(
ompt_id_t target_id, ompt_id_t host_op_id, ompt_target_data_op_t optype,
void *src_addr, int src_device_num, void *dest_addr, int dest_device_num,
size_t bytes, const void *codeptr_ra) {
OmptCallbackHandler::get().handleTargetDataOp(
target_id, host_op_id, optype, src_addr, src_device_num, dest_addr,
dest_device_num, bytes, codeptr_ra);
}
static void on_ompt_callback_target(ompt_target_t kind,
ompt_scope_endpoint_t endpoint,
int device_num, ompt_data_t *task_data,
ompt_id_t target_id,
const void *codeptr_ra) {
OmptCallbackHandler::get().handleTarget(kind, endpoint, device_num, task_data,
target_id, codeptr_ra);
}
static void on_ompt_callback_target_submit(ompt_id_t target_id,
ompt_id_t host_op_id,
unsigned int requested_num_teams) {
OmptCallbackHandler::get().handleTargetSubmit(target_id, host_op_id,
requested_num_teams);
}
static void on_ompt_callback_target_data_op_emi(
ompt_scope_endpoint_t endpoint, ompt_data_t *target_task_data,
ompt_data_t *target_data, ompt_id_t *host_op_id,
ompt_target_data_op_t optype, void *src_addr, int src_device_num,
void *dest_addr, int dest_device_num, size_t bytes,
const void *codeptr_ra) {
assert(codeptr_ra != 0 && "Unexpected null codeptr");
// Both src and dest must not be null
// However, for omp_target_alloc only the END call holds a value for one of
// the two entries
if (optype != ompt_target_data_alloc)
assert((src_addr != 0 || dest_addr != 0) && "Both src and dest addr null");
if (endpoint == ompt_scope_begin)
*host_op_id = NextOpId.fetch_add(1, std::memory_order_relaxed);
OmptCallbackHandler::get().handleTargetDataOpEmi(
endpoint, target_task_data, target_data, host_op_id, optype, src_addr,
src_device_num, dest_addr, dest_device_num, bytes, codeptr_ra);
}
static void on_ompt_callback_target_emi(ompt_target_t kind,
ompt_scope_endpoint_t endpoint,
int device_num, ompt_data_t *task_data,
ompt_data_t *target_task_data,
ompt_data_t *target_data,
const void *codeptr_ra) {
assert(codeptr_ra != 0 && "Unexpected null codeptr");
if (endpoint == ompt_scope_begin)
target_data->value = NextOpId.fetch_add(1, std::memory_order_relaxed);
OmptCallbackHandler::get().handleTargetEmi(kind, endpoint, device_num,
task_data, target_task_data,
target_data, codeptr_ra);
}
static void on_ompt_callback_target_submit_emi(
ompt_scope_endpoint_t endpoint, ompt_data_t *target_data,
ompt_id_t *host_op_id, unsigned int requested_num_teams) {
OmptCallbackHandler::get().handleTargetSubmitEmi(
endpoint, target_data, host_op_id, requested_num_teams);
}
static void on_ompt_callback_target_map(ompt_id_t target_id,
unsigned int nitems, void **host_addr,
void **device_addr, size_t *bytes,
unsigned int *mapping_flags,
const void *codeptr_ra) {
assert(0 && "Target map callback is unimplemented");
}
static void on_ompt_callback_target_map_emi(ompt_data_t *target_data,
unsigned int nitems,
void **host_addr,
void **device_addr, size_t *bytes,
unsigned int *mapping_flags,
const void *codeptr_ra) {
assert(0 && "Target map emi callback is unimplemented");
}
/// Load the value of a given boolean environmental variable.
bool getBoolEnvironmentVariable(const char *VariableName) {
if (VariableName == nullptr)
return false;
if (const char *EnvValue = std::getenv(VariableName)) {
std::string S{EnvValue};
for (auto &C : S)
C = (char)std::tolower(C);
if (S == "1" || S == "on" || S == "true" || S == "yes")
return true;
}
return false;
}
/// Called by the OMP runtime to initialize the OMPT
int ompt_initialize(ompt_function_lookup_t lookup, int initial_device_num,
ompt_data_t *tool_data) {
ompt_set_callback_t ompt_set_callback = nullptr;
ompt_set_callback = (ompt_set_callback_t)lookup("ompt_set_callback");
if (!ompt_set_callback)
return 0; // failure
UseEMICallbacks = getBoolEnvironmentVariable("OMPTEST_USE_OMPT_EMI");
UseTracing = getBoolEnvironmentVariable("OMPTEST_USE_OMPT_TRACING");
RunAsTestSuite = getBoolEnvironmentVariable("OMPTEST_RUN_AS_TESTSUITE");
ColoredLog = getBoolEnvironmentVariable("OMPTEST_LOG_COLORED");
register_ompt_callback(ompt_callback_thread_begin);
register_ompt_callback(ompt_callback_thread_end);
register_ompt_callback(ompt_callback_parallel_begin);
register_ompt_callback(ompt_callback_parallel_end);
register_ompt_callback(ompt_callback_work);
register_ompt_callback(ompt_callback_dispatch);
register_ompt_callback(ompt_callback_task_create);
// register_ompt_callback(ompt_callback_dependences);
// register_ompt_callback(ompt_callback_task_dependence);
register_ompt_callback(ompt_callback_task_schedule);
register_ompt_callback(ompt_callback_implicit_task);
// register_ompt_callback(ompt_callback_masked);
register_ompt_callback(ompt_callback_sync_region);
// register_ompt_callback(ompt_callback_mutex_acquire);
// register_ompt_callback(ompt_callback_mutex);
// register_ompt_callback(ompt_callback_nestLock);
// register_ompt_callback(ompt_callback_flush);
// register_ompt_callback(ompt_callback_cancel);
register_ompt_callback(ompt_callback_device_initialize);
register_ompt_callback(ompt_callback_device_finalize);
register_ompt_callback(ompt_callback_device_load);
register_ompt_callback(ompt_callback_device_unload);
if (UseEMICallbacks) {
register_ompt_callback(ompt_callback_target_emi);
register_ompt_callback(ompt_callback_target_submit_emi);
register_ompt_callback(ompt_callback_target_data_op_emi);
register_ompt_callback(ompt_callback_target_map_emi);
} else {
register_ompt_callback(ompt_callback_target);
register_ompt_callback(ompt_callback_target_submit);
register_ompt_callback(ompt_callback_target_data_op);
register_ompt_callback(ompt_callback_target_map);
}
// Construct & subscribe the reporter, so it gets notified of events
EventReporter = new OmptEventReporter();
OmptCallbackHandler::get().subscribe(EventReporter);
if (RunAsTestSuite)
EventReporter->setActive(false);
return 1; // success
}
void ompt_finalize(ompt_data_t *tool_data) {
assert(Handler && "Callback handler should be present at this point");
assert(EventReporter && "EventReporter should be present at this point");
delete Handler;
delete EventReporter;
}
#ifdef __cplusplus
extern "C" {
#endif
/// Called from the OMP Runtime to start / initialize the tool
ompt_start_tool_result_t *ompt_start_tool(unsigned int omp_version,
const char *runtime_version) {
static ompt_start_tool_result_t ompt_start_tool_result = {
&ompt_initialize, &ompt_finalize, {0}};
return &ompt_start_tool_result;
}
int start_trace(ompt_device_t *Device) {
if (!ompt_start_trace)
return 0;
// Start tracing this device (add to set)
assert(TracedDevices->find(Device) == TracedDevices->end() &&
"Device already present in the map");
TracedDevices->insert(Device);
return ompt_start_trace(Device, &on_ompt_callback_buffer_request,
&on_ompt_callback_buffer_complete);
}
int flush_trace(ompt_device_t *Device) {
if (!ompt_flush_trace)
return 0;
return ompt_flush_trace(Device);
}
int flush_traced_devices() {
if (!ompt_flush_trace || TracedDevices == nullptr)
return 0;
size_t NumFlushedDevices = 0;
for (auto Device : *TracedDevices)
if (ompt_flush_trace(Device) == 1)
++NumFlushedDevices;
// Provide time to process triggered assert events
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return (NumFlushedDevices == TracedDevices->size());
}
int stop_trace(ompt_device_t *Device) {
if (!ompt_stop_trace)
return 0;
// Stop tracing this device (erase from set)
assert(TracedDevices->find(Device) != TracedDevices->end() &&
"Device not present in the map");
TracedDevices->erase(Device);
return ompt_stop_trace(Device);
}
// This is primarily used to stop unwanted prints from happening.
void libomptest_global_eventreporter_set_active(bool State) {
assert(EventReporter && "EventReporter should be present at this point");
EventReporter->setActive(State);
}
#ifdef __cplusplus
}
#endif