| //===- GenericProfiler.h - GenericProfiler interface for use in Plugins ---===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // The GenericProfiler interface allows to implement profiler logic for various |
| // backends, such as OMPT or other tracing mechanisms. |
| // This enables the plugins to be agnostic of the actual high-level language |
| // that is implemented. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef OFFLOAD_PLUGINS_NEXTGEN_COMMON_INCLUDE_GENERICPROFILER_H |
| #define OFFLOAD_PLUGINS_NEXTGEN_COMMON_INCLUDE_GENERICPROFILER_H |
| |
| #include "Shared/APITypes.h" |
| |
| #include <cstdint> |
| #include <functional> |
| #include <tuple> |
| |
| namespace llvm { |
| namespace omp { |
| namespace target { |
| namespace plugin { |
| |
| struct GenericDeviceTy; |
| class GenericProfilerTy; |
| |
| /// Helper function to unpack a tuple of arguments and call a function with |
| /// them. |
| template <typename FunT, typename... ArgsT, size_t... IdxSequence> |
| void callViaIndexSeq(FunT F, GenericProfilerTy *P, uint64_t StartNanos, |
| uint64_t EndNanos, std::tuple<ArgsT...> Args, |
| std::index_sequence<IdxSequence...>) { |
| F(P, StartNanos, EndNanos, std::get<IdxSequence>(Args)...); |
| } |
| |
| /// Helper function to unpack a tuple of arguments and call a function with |
| /// them. |
| template <typename FunT, typename... ArgsT> |
| void callViaUnpack(FunT F, GenericProfilerTy *P, uint64_t StartNanos, |
| uint64_t EndNanos, std::tuple<ArgsT...> Tup) { |
| callViaIndexSeq(F, P, StartNanos, EndNanos, Tup, |
| std::index_sequence_for<ArgsT...>{}); |
| } |
| |
| /// Abstraction layer to implement different profiler backends. |
| /// |
| /// The plugins call into the GenericProfilerTy to handle the specific events |
| /// with whatever specific backend was instantiated. For now, the supported |
| /// backends are limited to an OMPT implementation. |
| class GenericProfilerTy { |
| public: |
| GenericProfilerTy() = default; |
| virtual ~GenericProfilerTy() = default; |
| |
| /// Returns true if profiling is enabled, false otherwise. |
| virtual bool isProfilingEnabled() { return false; } |
| |
| /// Set the factors which are used to interpolate the device clock compared to |
| /// the host clock. This follows a simple linear interpolation: Slope * <time> |
| /// + Offset. |
| void setTimeConversionFactors(double Slope, double Offset) { |
| HostToDeviceSlope = Slope; |
| HostToDeviceOffset = Offset; |
| setTimeConversionFactorsImpl(HostToDeviceSlope, HostToDeviceOffset); |
| } |
| |
| /// Hook that is called when memory is allocated on the device. |
| virtual void handleDataAlloc(uint64_t StartNanos, uint64_t EndNanos, |
| void *HostPtr, uint64_t Size, void *Data) {} |
| |
| /// Hook that is called when memory is freed on the device. |
| virtual void handleDataDelete(uint64_t StartNanos, uint64_t EndNanos, |
| void *TgtPtr, void *Data) {} |
| |
| /// Hook that is called before launching a kernel. |
| virtual void handlePreKernelLaunch(GenericDeviceTy *Device, |
| uint32_t NumBlocks[3], |
| __tgt_async_info *AI) {} |
| |
| /// Hook that is called when the kernel is finished to extract the specific |
| /// timing info for that kernel execution. |
| virtual void handleKernelCompletion(uint64_t StartNanos, uint64_t EndNanos, |
| void *Data) {} |
| |
| /// Hook that is called when a data transfer happens to extract timing info |
| /// for that transfer. |
| virtual void handleDataTransfer(uint64_t StartNanos, uint64_t EndNanos, |
| void *Data) {} |
| |
| /// Allow factors for time conversion between host and device. |
| virtual void setTimeConversionFactorsImpl(double Slope, double Offset) {} |
| |
| /// RAII style timer that measures the elapsed time between construction and |
| /// destruction, then invokes a callback with the profiler, start/end times, |
| /// and any captured arguments. It wraps an existing GenericProfiler instance |
| /// to delay the call of the ProfilerFunction (F) until this object's dtor is |
| /// called. |
| template <typename ProfilerFuncTy, typename... ProfilerFuncArgsTy> |
| class ProfTimerTy { |
| public: |
| /// On creation, saves the timestamp from the profiler and device, and |
| /// stores the callback function and its arguments. |
| ProfTimerTy(ProfilerFuncTy &&F, GenericProfilerTy *P, GenericDeviceTy *D, |
| ProfilerFuncArgsTy... As) |
| : ProfilerFunction(F), ProfilerInstance(P), Device(D), |
| ProfilerFuncArgs(As...) { |
| assert(ProfilerInstance && "GenericProfilerTy is null"); |
| assert(Device && "GenericDeviceTy is null"); |
| if (ProfilerInstance) |
| StartTime = ProfilerInstance->getDeviceTimeStamp(Device); |
| } |
| |
| /// On destruction, saves the end timestamp and invokes the callback |
| /// function with the profiler, start/end times, and captured arguments. |
| ~ProfTimerTy() { |
| assert(ProfilerInstance && "GenericProfilerTy is null"); |
| assert(Device && "GenericDeviceTy is null"); |
| if (ProfilerInstance) { |
| uint64_t EndTime = ProfilerInstance->getDeviceTimeStamp(Device); |
| callViaUnpack(ProfilerFunction, ProfilerInstance, StartTime, EndTime, |
| ProfilerFuncArgs); |
| } |
| } |
| |
| private: |
| /// The callback function to be invoked on destruction, which takes the |
| /// profiler, start/end times, and captured arguments. |
| ProfilerFuncTy ProfilerFunction; |
| /// The profiler instance to be used for timing and invoking the callback |
| /// function. |
| GenericProfilerTy *ProfilerInstance; |
| /// The device instance to be used for timing and invoking the callback |
| /// function. |
| GenericDeviceTy *Device; |
| /// The start time of the timer, captured at construction. |
| uint64_t StartTime = 0; |
| /// The captured arguments to be passed to the callback function on |
| /// destruction. |
| std::tuple<ProfilerFuncArgsTy...> ProfilerFuncArgs; |
| }; |
| |
| template <typename ProfilerFuncTy, typename... ProfilerFuncArgsTy> |
| [[maybe_unused]] |
| ProfTimerTy(ProfilerFuncTy &&, GenericProfilerTy *, ProfilerFuncArgsTy...) |
| -> ProfTimerTy<ProfilerFuncTy, ProfilerFuncArgsTy...>; |
| |
| template <typename ProfilerFuncTy, typename... ProfilerFuncArgsTy> |
| friend class ProfTimerTy; |
| |
| /// Returns an RAII style timer, which handles data allocation timing. |
| [[nodiscard]] auto getScopedDataAllocTimer(GenericDeviceTy *Dev, |
| void *HostPtr, uint64_t Size, |
| void *ProfData = nullptr) { |
| return ProfTimerTy( |
| [](GenericProfilerTy *P, auto... args) { |
| assert(P && "P was null"); |
| P->handleDataAlloc(args...); |
| }, |
| this, Dev, HostPtr, Size, ProfData); |
| } |
| |
| /// Returns an RAII style timer, which handles data deletion timing. |
| [[nodiscard]] auto getScopedDataDeleteTimer(GenericDeviceTy *Dev, |
| void *TgtPtr, |
| void *ProfData = nullptr) { |
| return ProfTimerTy( |
| [](GenericProfilerTy *P, auto... args) { |
| assert(P && "P was null"); |
| P->handleDataDelete(args...); |
| }, |
| this, Dev, TgtPtr, ProfData); |
| } |
| |
| protected: |
| /// Factors to convert host time to device time. The default values are |
| /// Computed in a way Slope * Time + Offset (inspired by Score-P). |
| double HostToDeviceSlope = 1.0; |
| double HostToDeviceOffset = .0; |
| |
| private: |
| /// Vendor-specific implementation to obtain device time. |
| uint64_t getDeviceTimeStamp(GenericDeviceTy *D); |
| }; |
| |
| /// Null-object used to normalize an absent profiler. Callers that do not have a |
| /// profiler pass none, and the receiving method resolves to this instance, so |
| /// profiling logic never has to test for a null pointer. |
| GenericProfilerTy &getNoOpProfiler(); |
| } // namespace plugin |
| } // namespace target |
| } // namespace omp |
| } // namespace llvm |
| |
| #endif // OFFLOAD_PLUGINS_NEXTGEN_COMMON_INCLUDE_GENERICPROFILER_H |