//===- 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 #include #include #include 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> TestRegistrar::Tests; #endif static std::atomic 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 OmptDeviceSetTy; typedef std::unique_ptr 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(); 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