350 lines
11 KiB
C
350 lines
11 KiB
C
|
|
#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 <atomic>
|
||
|
|
#include <chrono>
|
||
|
|
#include <fstream>
|
||
|
|
#include <mutex>
|
||
|
|
#include <string>
|
||
|
|
#include <thread>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
// 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<int64_t> _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<Track> 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<TrackReference*> _threadEnlistedTracks;
|
||
|
|
|
||
|
|
friend class LambdaCaptureTrack;
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace CesiumImpl
|
||
|
|
} // namespace CesiumUtility
|
||
|
|
|
||
|
|
#endif // CESIUM_TRACING_ENABLED
|
||
|
|
|
||
|
|
#endif
|