#pragma once #ifndef CESIUM_OVERRIDE_TRACING // If the build system doesn't enable the tracing support // consider it disabled by default. #ifndef CESIUM_TRACING_ENABLED #define CESIUM_TRACING_ENABLED 0 #endif #if !CESIUM_TRACING_ENABLED #define CESIUM_TRACE_INIT(filename) #define CESIUM_TRACE_SHUTDOWN() #define CESIUM_TRACE(name) #define CESIUM_TRACE_BEGIN(name) #define CESIUM_TRACE_END(name) #define CESIUM_TRACE_BEGIN_IN_TRACK(name) #define CESIUM_TRACE_END_IN_TRACK(name) #define CESIUM_TRACE_DECLARE_TRACK_SET(id, name) #define CESIUM_TRACE_USE_TRACK_SET(id) #define CESIUM_TRACE_LAMBDA_CAPTURE_TRACK() tracingTrack = false #define CESIUM_TRACE_USE_CAPTURED_TRACK() #else #include #include #include #include #include #include #include // helper macros to avoid shadowing variables #define TRACE_NAME_AUX1(A, B) A##B #define TRACE_NAME_AUX2(A, B) TRACE_NAME_AUX1(A, B) /** * @brief Initializes the tracing framework and begins recording to a given JSON * filename. * * @param filename The path and named of the file in which to record traces. */ #define CESIUM_TRACE_INIT(filename) \ CesiumUtility::CesiumImpl::Tracer::instance().startTracing(filename) /** * @brief Shuts down tracing and closes the JSON tracing file. */ #define CESIUM_TRACE_SHUTDOWN() \ CesiumUtility::CesiumImpl::Tracer::instance().endTracing() /** * @brief Measures and records the time spent in the current scope. * * The time is measured from the `CESIUM_TRACE` line to the end of the scope. * If the current thread is enlisted in an async process * ({@link CESIUM_TRACE_ASYNC_ENLIST}), the time is recorded against the async * process. Otherwise, it is recorded against the current thread. * * @param name The name of the measured operation. */ #define CESIUM_TRACE(name) \ CesiumUtility::CesiumImpl::ScopedTrace TRACE_NAME_AUX2( \ cesiumTrace, \ __LINE__)(name) /** * @brief Begins measuring an operation which may span scope but not threads. * * Begins measuring the time of an operation which completes with a * corresponding call to {@link CESIUM_TRACE_END}. If the calling thread is * operating in a track ({@link CESIUM_TRACE_USE_TRACK_SET}), the * time is recorded in the track. Otherwise, is is recorded against the current * thread. * * Extreme care must be taken to match calls to `CESIUM_TRACE_BEGIN` and * `CESIUM_TRACE_END`: * * * Paired calls must use an identical `name`. * * If either BEGIN or END is called from a thread operating in a track, * then both must be, and it must be the same track in * both cases. In this case BEGIN and END may be called from different * threads. However, it safer to use {@link CESIUM_TRACE_BEGIN_IN_TRACK} * in this scenario. * * If either BEGIN or END is called from a thread _not_ enlisted into a * track, then both must be called from the same thread and neither * thread may be in a track. * * Paired calls must not be interleaved with other BEGIN/END calls for the * same thread or track. Other BEGIN/END pairs may be fully * nested within this one, but this pair must not END in between a nested * measurement's BEGIN and END calls. * * Failure to ensure the above may lead to generation of a trace file that the * Chromium trace viewer is not able to correctly interpret. * * @param name The name of the measured operation. */ #define CESIUM_TRACE_BEGIN(name) \ CesiumUtility::CesiumImpl::Tracer::instance().writeAsyncEventBegin(name) /** * @brief Ends measuring an operation which may span scopes but not threads. * * Finishes measuring the time of an operation that began with a call to * {@link CESIUM_TRACE_BEGIN}. See the documentation for that macro for more * details and caveats. * * @param name The name of the measured operation. */ #define CESIUM_TRACE_END(name) \ CesiumUtility::CesiumImpl::Tracer::instance().writeAsyncEventEnd(name) /** * @brief Begins measuring an operation that may span both scopes and threads. * * This macro is identical to {@link CESIUM_TRACE_BEGIN} except that it does * nothing if the calling thread and scope are not operating as part of a * track. This allows it to be safely used to measure operations that span * threads. Use {@link CESIUM_TRACE_USE_TRACK_SET} to use a track from a set. * * @param name The name of the measured operation. */ #define CESIUM_TRACE_BEGIN_IN_TRACK(name) \ if (CesiumUtility::CesiumImpl::TrackReference::current() != nullptr) { \ CESIUM_TRACE_BEGIN(name); \ } /** * @brief Ends measuring an operation that may span both scopes and threads. * * This macro is identical to {@link CESIUM_TRACE_END} except that it does * nothing if the calling thread and scope are not operating as part of a * track. This allows it to be safely used to measure operations that span * threads. Use {@link CESIUM_TRACE_USE_TRACK_SET} to use a track from a set. * * @param name The name of the measured operation. */ #define CESIUM_TRACE_END_IN_TRACK(name) \ if (CesiumUtility::CesiumImpl::TrackReference::current() != nullptr) { \ CESIUM_TRACE_END(name); \ } /** * @brief Declares a set of tracing tracks as a field inside a class. * * A track is a sequential process that may take place across multiple threads. * An instance of a class may have multiple such tracks running simultaneously. * For example, a single 3D Tiles tile will load in a particular track, while * other tiles will be loading in other parallel tracks in the same set. * * Note that when the track set is destroyed, an assertion will check that no * tracks are still in progress. * * @param id The name of the field to hold the track set. * @param name A human-friendly name for this set of tracks. */ #define CESIUM_TRACE_DECLARE_TRACK_SET(id, name) \ CesiumUtility::CesiumImpl::TrackSet id { name } /** * @brief Begins using a track set in this thread. * * The calling thread will be allocated a track from the track set, and will * continue using it for the remainder of the current scope. In addition, if * the thread starts an async operation using {@link CesiumAsync::AsyncSystem}, * all continuations of that async operation will use the same track as well. * * @param id The ID (field name) of the track set declared with * {@link CESIUM_TRACE_DECLARE_TRACK_SET}. */ #define CESIUM_TRACE_USE_TRACK_SET(id) \ CesiumUtility::CesiumImpl::TrackReference TRACE_NAME_AUX2( \ cesiumTraceEnlistTrack, \ __LINE__)(id); /** * @brief Capture the current tracing track for a lambda, so that the lambda may * use the same track. * * This macro should be used in a lambda's capture list to capture the track of * the current thread so that the lambda (which may execute in a different * thread) can use the same track by executing * {@link CESIUM_TRACE_USE_CAPTURED_TRACK}. */ #define CESIUM_TRACE_LAMBDA_CAPTURE_TRACK() \ tracingTrack = CesiumUtility::CesiumImpl::LambdaCaptureTrack() /** * @brief Uses a captured track for the current thread and the current scope. * * This macro should be used as the first line in a lambda that should inherit * the tracing track of the thread that created it. The lambda's capture list * must also contain {@link CESIUM_TRACE_USE_CAPTURED_TRACK}. */ #define CESIUM_TRACE_USE_CAPTURED_TRACK() \ CESIUM_TRACE_USE_TRACK_SET(tracingTrack) namespace CesiumUtility { namespace CesiumImpl { // The following are internal classes used by the tracing framework, do not use // directly. struct Trace { std::string name; int64_t start; int64_t duration; std::thread::id threadID; }; class TrackReference; class Tracer { public: static Tracer& instance(); ~Tracer(); void startTracing(const std::string& filePath = "trace.json"); void endTracing(); void writeCompleteEvent(const Trace& trace); void writeAsyncEventBegin(const char* name, int64_t id); void writeAsyncEventBegin(const char* name); void writeAsyncEventEnd(const char* name, int64_t id); void writeAsyncEventEnd(const char* name); int64_t allocateTrackID(); private: Tracer(); int64_t getCurrentThreadTrackID() const; void writeAsyncEvent( const char* category, const char* name, char type, int64_t id); std::ofstream _output; uint32_t _numTraces; std::mutex _lock; std::atomic _lastAllocatedID; }; class ScopedTrace { public: explicit ScopedTrace(const std::string& message); ~ScopedTrace(); void reset(); ScopedTrace(const ScopedTrace& rhs) = delete; ScopedTrace(ScopedTrace&& rhs) = delete; ScopedTrace& operator=(const ScopedTrace& rhs) = delete; ScopedTrace& operator=(ScopedTrace&& rhs) = delete; private: std::string _name; std::chrono::steady_clock::time_point _startTime; std::thread::id _threadId; bool _reset; }; class TrackSet { public: explicit TrackSet(const char* name); ~TrackSet(); size_t acquireTrack(); void addReference(size_t trackIndex) noexcept; void releaseReference(size_t trackIndex) noexcept; int64_t getTracingID(size_t trackIndex) noexcept; TrackSet(TrackSet&& rhs) noexcept; TrackSet& operator=(TrackSet&& rhs) noexcept; private: struct Track { Track(int64_t id_, bool inUse_) : id(id_), referenceCount(0), inUse(inUse_) {} int64_t id; int32_t referenceCount; bool inUse; }; std::string name; std::vector tracks; std::mutex mutex; }; class LambdaCaptureTrack { public: LambdaCaptureTrack(); LambdaCaptureTrack(LambdaCaptureTrack&& rhs) noexcept; LambdaCaptureTrack(const LambdaCaptureTrack& rhs) noexcept; ~LambdaCaptureTrack(); LambdaCaptureTrack& operator=(const LambdaCaptureTrack& rhs) noexcept; LambdaCaptureTrack& operator=(LambdaCaptureTrack&& rhs) noexcept; private: TrackSet* pSet; size_t index; friend class TrackReference; }; // An RAII object to reference an async operation track. // When the last instance associated with a particular track index is destroyed, // the track is released back to the track set. class TrackReference { public: static TrackReference* current(); TrackReference(TrackSet& set) noexcept; TrackReference(TrackSet& set, size_t index) noexcept; TrackReference(const LambdaCaptureTrack& lambdaCapture) noexcept; ~TrackReference() noexcept; operator bool() const noexcept; int64_t getTracingID() const noexcept; TrackReference(const TrackReference& rhs) = delete; TrackReference(TrackReference&& rhs) = delete; TrackReference& operator=(const TrackReference& rhs) = delete; TrackReference& operator=(TrackReference&& rhs) = delete; private: void enlistCurrentThread(); void dismissCurrentThread(); TrackSet* pSet; size_t index; static thread_local std::vector _threadEnlistedTracks; friend class LambdaCaptureTrack; }; } // namespace CesiumImpl } // namespace CesiumUtility #endif // CESIUM_TRACING_ENABLED #endif