|
|
@@ -10,20 +10,88 @@
|
|
|
namespace anki
|
|
|
{
|
|
|
|
|
|
-thread_local Tracer::ThreadLocal Tracer::m_threadLocal;
|
|
|
+/// Lightweight event storage.
|
|
|
+class Tracer::Event
|
|
|
+{
|
|
|
+public:
|
|
|
+ const char* m_name;
|
|
|
+ Second m_timestamp;
|
|
|
+ Second m_duration;
|
|
|
+};
|
|
|
+
|
|
|
+/// Event batch allocation.
|
|
|
+class Tracer::EventsChunk : public IntrusiveListEnabled<EventsChunk>
|
|
|
+{
|
|
|
+public:
|
|
|
+ Array<Event, EVENTS_PER_CHUNK> m_events;
|
|
|
+ U32 m_eventCount = 0;
|
|
|
+};
|
|
|
+
|
|
|
+/// A heavyweight event with more info.
|
|
|
+class Tracer::GatherEvent
|
|
|
+{
|
|
|
+public:
|
|
|
+ CString m_name;
|
|
|
+ Second m_timestamp;
|
|
|
+ Second m_duration;
|
|
|
+ ThreadId m_tid;
|
|
|
+};
|
|
|
+
|
|
|
+/// Lightweight counter storage.
|
|
|
+class Tracer::Counter
|
|
|
+{
|
|
|
+public:
|
|
|
+ const char* m_name;
|
|
|
+ U64 m_value;
|
|
|
+};
|
|
|
+
|
|
|
+/// Counter batch allocation.
|
|
|
+class Tracer::CountersChunk : public IntrusiveListEnabled<CountersChunk>
|
|
|
+{
|
|
|
+public:
|
|
|
+ U64 m_frame;
|
|
|
+ Second m_startFrameTime;
|
|
|
+ Array<Counter, COUNTERS_PER_CHUNK> m_counters;
|
|
|
+ U32 m_counterCount = 0;
|
|
|
+};
|
|
|
+
|
|
|
+/// Heavyweight counter storage.
|
|
|
+class Tracer::GatherCounter
|
|
|
+{
|
|
|
+public:
|
|
|
+ CString m_name;
|
|
|
+ U64 m_value;
|
|
|
+};
|
|
|
+
|
|
|
+/// Thread local storage.
|
|
|
+class Tracer::ThreadLocal
|
|
|
+{
|
|
|
+public:
|
|
|
+ ThreadId m_tid ANKI_DBG_NULLIFY;
|
|
|
+
|
|
|
+ IntrusiveList<CountersChunk> m_counterChunks;
|
|
|
+ IntrusiveList<EventsChunk> m_eventChunks;
|
|
|
+};
|
|
|
+
|
|
|
+thread_local Tracer::ThreadLocal* Tracer::m_threadLocal = nullptr;
|
|
|
|
|
|
+/// Storage of counters per frame.
|
|
|
class Tracer::PerFrameCounters
|
|
|
{
|
|
|
public:
|
|
|
- DynamicArrayAuto<Counter> m_counters;
|
|
|
- U32 m_frameIdx;
|
|
|
+ DynamicArrayAuto<GatherCounter> m_counters;
|
|
|
+ DynamicArrayAuto<GatherCounter> m_tempCounters; ///< A temp storage.
|
|
|
+ U64 m_frame;
|
|
|
+ Second m_startFrameTime;
|
|
|
|
|
|
PerFrameCounters(GenericMemoryPoolAllocator<U8> alloc)
|
|
|
: m_counters(alloc)
|
|
|
+ , m_tempCounters(alloc)
|
|
|
{
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+/// Context for Tracer::flush().
|
|
|
class Tracer::FlushCtx
|
|
|
{
|
|
|
public:
|
|
|
@@ -31,7 +99,7 @@ public:
|
|
|
CString m_filename;
|
|
|
DynamicArrayAuto<CString> m_counterNames;
|
|
|
DynamicArrayAuto<PerFrameCounters> m_counters;
|
|
|
- DynamicArrayAuto<Event> m_events;
|
|
|
+ DynamicArrayAuto<GatherEvent> m_events;
|
|
|
|
|
|
FlushCtx(GenericMemoryPoolAllocator<U8> alloc, const CString& filename)
|
|
|
: m_alloc(alloc)
|
|
|
@@ -47,75 +115,82 @@ Tracer::~Tracer()
|
|
|
{
|
|
|
for(ThreadLocal* threadLocal : m_allThreadLocal)
|
|
|
{
|
|
|
- while(!threadLocal->m_counters.isEmpty())
|
|
|
+ while(!threadLocal->m_counterChunks.isEmpty())
|
|
|
{
|
|
|
- Counter& counter = threadLocal->m_counters.getFront();
|
|
|
- threadLocal->m_counters.popFront();
|
|
|
- threadLocal->m_counterAlloc.deleteInstance(m_alloc, &counter);
|
|
|
+ CountersChunk& chunk = threadLocal->m_counterChunks.getFront();
|
|
|
+ threadLocal->m_counterChunks.popFront();
|
|
|
+ m_alloc.deleteInstance(&chunk);
|
|
|
}
|
|
|
|
|
|
- while(!threadLocal->m_events.isEmpty())
|
|
|
+ while(!threadLocal->m_eventChunks.isEmpty())
|
|
|
{
|
|
|
- Event& event = threadLocal->m_events.getFront();
|
|
|
- threadLocal->m_events.popFront();
|
|
|
- threadLocal->m_eventAlloc.deleteInstance(m_alloc, &event);
|
|
|
+ EventsChunk& chunk = threadLocal->m_eventChunks.getFront();
|
|
|
+ threadLocal->m_eventChunks.popFront();
|
|
|
+ m_alloc.deleteInstance(&chunk);
|
|
|
}
|
|
|
+
|
|
|
+ m_alloc.deleteInstance(threadLocal);
|
|
|
}
|
|
|
|
|
|
m_allThreadLocal.destroy(m_alloc);
|
|
|
- m_frames.destroy(m_alloc);
|
|
|
}
|
|
|
|
|
|
void Tracer::newFrame(U64 frame)
|
|
|
{
|
|
|
-#if ANKI_ASSERTS_ENABLED
|
|
|
- if(m_frames.getSize() > 0)
|
|
|
- {
|
|
|
- ANKI_ASSERT(frame > m_frames.getBack().m_frame);
|
|
|
- }
|
|
|
-#endif
|
|
|
+ ANKI_ASSERT(frame == 0 || frame > m_frame);
|
|
|
+
|
|
|
+ LockGuard<SpinLock> lock(m_frameMtx);
|
|
|
|
|
|
- Frame f;
|
|
|
- f.m_startFrameTime = HighRezTimer::getCurrentTime();
|
|
|
- f.m_frame = frame;
|
|
|
- m_frames.emplaceBack(m_alloc, f);
|
|
|
+ m_startFrameTime = HighRezTimer::getCurrentTime();
|
|
|
+ m_frame = frame;
|
|
|
}
|
|
|
|
|
|
Tracer::ThreadLocal& Tracer::getThreadLocal()
|
|
|
{
|
|
|
- ThreadLocal& out = m_threadLocal;
|
|
|
- if(ANKI_UNLIKELY(!out.m_tracerKnowsAboutThis))
|
|
|
+ ThreadLocal* out = m_threadLocal;
|
|
|
+ if(ANKI_UNLIKELY(out == nullptr))
|
|
|
{
|
|
|
+ out = m_alloc.newInstance<ThreadLocal>();
|
|
|
+ out->m_tid = Thread::getCurrentThreadId();
|
|
|
+ m_threadLocal = out;
|
|
|
+
|
|
|
+ // Store it
|
|
|
LockGuard<Mutex> lock(m_threadLocalMtx);
|
|
|
- m_allThreadLocal.emplaceBack(m_alloc, &out);
|
|
|
- out.m_tid = Thread::getCurrentThreadId();
|
|
|
- out.m_tracerKnowsAboutThis = true;
|
|
|
+ m_allThreadLocal.emplaceBack(m_alloc, out);
|
|
|
}
|
|
|
|
|
|
- return out;
|
|
|
+ return *out;
|
|
|
}
|
|
|
|
|
|
-void Tracer::beginEvent()
|
|
|
+TracerEventHandle Tracer::beginEvent()
|
|
|
{
|
|
|
ThreadLocal& threadLocal = getThreadLocal();
|
|
|
- Event* event = threadLocal.m_eventAlloc.newInstance(m_alloc);
|
|
|
+
|
|
|
+ // Allocate new chunk
|
|
|
+ if(threadLocal.m_eventChunks.isEmpty() || threadLocal.m_eventChunks.getBack().m_eventCount >= EVENTS_PER_CHUNK)
|
|
|
+ {
|
|
|
+ EventsChunk* chunk = m_alloc.newInstance<EventsChunk>();
|
|
|
+ threadLocal.m_eventChunks.pushBack(chunk);
|
|
|
+ }
|
|
|
+
|
|
|
+ EventsChunk& chunk = threadLocal.m_eventChunks.getBack();
|
|
|
+ Event* event = &chunk.m_events[chunk.m_eventCount++];
|
|
|
event->m_timestamp = HighRezTimer::getCurrentTime();
|
|
|
- threadLocal.m_events.pushBack(event);
|
|
|
+
|
|
|
+ return event;
|
|
|
}
|
|
|
|
|
|
-void Tracer::endEvent(const char* eventName)
|
|
|
+void Tracer::endEvent(const char* eventName, TracerEventHandle eventHandle)
|
|
|
{
|
|
|
ANKI_ASSERT(eventName);
|
|
|
+ ANKI_ASSERT(eventHandle);
|
|
|
|
|
|
- // Set the time in the event
|
|
|
- ThreadLocal& threadLocal = getThreadLocal();
|
|
|
- ANKI_ASSERT(!threadLocal.m_events.isEmpty());
|
|
|
- Event& event = threadLocal.m_events.getBack();
|
|
|
- event.m_name = eventName;
|
|
|
- event.m_duration = HighRezTimer::getCurrentTime() - event.m_timestamp;
|
|
|
+ Event* event = static_cast<Event*>(eventHandle);
|
|
|
+ event->m_name = eventName;
|
|
|
+ event->m_duration = HighRezTimer::getCurrentTime() - event->m_timestamp;
|
|
|
|
|
|
// Store a counter as well. In ns
|
|
|
- increaseCounter(eventName, U64(event.m_duration * 1000000000.0));
|
|
|
+ increaseCounter(eventName, U64(event->m_duration * 1000000000.0));
|
|
|
}
|
|
|
|
|
|
void Tracer::increaseCounter(const char* counterName, U64 value)
|
|
|
@@ -123,103 +198,140 @@ void Tracer::increaseCounter(const char* counterName, U64 value)
|
|
|
ANKI_ASSERT(counterName);
|
|
|
|
|
|
ThreadLocal& threadLocal = getThreadLocal();
|
|
|
- Counter* counter = threadLocal.m_counterAlloc.newInstance(m_alloc);
|
|
|
- counter->m_name = counterName;
|
|
|
- counter->m_value = value;
|
|
|
- counter->m_frameIdx = m_frames.getSize() - 1;
|
|
|
|
|
|
- threadLocal.m_counters.pushBack(counter);
|
|
|
+ // Create chunk
|
|
|
+ if(threadLocal.m_counterChunks.isEmpty() || threadLocal.m_counterChunks.getBack().m_frame != m_frame
|
|
|
+ || threadLocal.m_counterChunks.getBack().m_counterCount >= COUNTERS_PER_CHUNK)
|
|
|
+ {
|
|
|
+ CountersChunk* newChunk = m_alloc.newInstance<CountersChunk>();
|
|
|
+ threadLocal.m_counterChunks.pushBack(newChunk);
|
|
|
+
|
|
|
+ {
|
|
|
+ LockGuard<SpinLock> lock(m_frameMtx);
|
|
|
+ newChunk->m_frame = m_frame;
|
|
|
+ newChunk->m_startFrameTime = m_startFrameTime;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ CountersChunk& chunk = threadLocal.m_counterChunks.getBack();
|
|
|
+
|
|
|
+ Counter& counter = chunk.m_counters[chunk.m_counterCount++];
|
|
|
+ counter.m_name = counterName;
|
|
|
+ counter.m_value = value;
|
|
|
}
|
|
|
|
|
|
void Tracer::gatherCounters(FlushCtx& ctx)
|
|
|
{
|
|
|
- // Gather all the counters
|
|
|
- DynamicArrayAuto<Counter> allCounters(m_alloc);
|
|
|
+ // Iterate all the chunks and create the PerFrameCounters
|
|
|
for(ThreadLocal* threadLocal : m_allThreadLocal)
|
|
|
{
|
|
|
- while(!threadLocal->m_counters.isEmpty())
|
|
|
+ while(!threadLocal->m_counterChunks.isEmpty())
|
|
|
{
|
|
|
- // Pop counter
|
|
|
- Counter& inCounter = threadLocal->m_counters.getFront();
|
|
|
- threadLocal->m_counters.popFront();
|
|
|
+ // Pop chunk
|
|
|
+ CountersChunk& chunk = threadLocal->m_counterChunks.getFront();
|
|
|
+ threadLocal->m_counterChunks.popFront();
|
|
|
|
|
|
- // Copy
|
|
|
- Counter newCounter = inCounter;
|
|
|
- allCounters.emplaceBack(newCounter);
|
|
|
+ // Iterate the PerFrameCounters to find if the frame is present
|
|
|
+ PerFrameCounters* perFrame = nullptr;
|
|
|
+ for(PerFrameCounters& pf : ctx.m_counters)
|
|
|
+ {
|
|
|
+ if(pf.m_frame == chunk.m_frame)
|
|
|
+ {
|
|
|
+ perFrame = &pf;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!perFrame)
|
|
|
+ {
|
|
|
+ ctx.m_counters.emplaceBack(m_alloc);
|
|
|
+
|
|
|
+ perFrame = &ctx.m_counters.getBack();
|
|
|
+ perFrame->m_frame = chunk.m_frame;
|
|
|
+ perFrame->m_startFrameTime = chunk.m_startFrameTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ ANKI_ASSERT(chunk.m_frame == perFrame->m_frame);
|
|
|
|
|
|
- // Delete poped counter
|
|
|
- threadLocal->m_counterAlloc.deleteInstance(m_alloc, &inCounter);
|
|
|
+ // Copy the counters
|
|
|
+ for(U i = 0; i < chunk.m_counterCount; ++i)
|
|
|
+ {
|
|
|
+ const Counter& inCounter = chunk.m_counters[i];
|
|
|
+
|
|
|
+ GatherCounter outCounter;
|
|
|
+ outCounter.m_name = inCounter.m_name;
|
|
|
+ outCounter.m_value = inCounter.m_value;
|
|
|
+
|
|
|
+ perFrame->m_tempCounters.emplaceBack(outCounter);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Delete chunk
|
|
|
+ m_alloc.deleteInstance(&chunk);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if(allCounters.getSize() == 0)
|
|
|
+ if(ctx.m_counters.getSize() == 0)
|
|
|
{
|
|
|
// Early exit
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // Sort them
|
|
|
- std::sort(allCounters.getBegin(), allCounters.getEnd(), [](const Counter& a, const Counter& b) {
|
|
|
- if(a.m_frameIdx != b.m_frameIdx)
|
|
|
- {
|
|
|
- return a.m_frameIdx < b.m_frameIdx;
|
|
|
- }
|
|
|
-
|
|
|
- ANKI_ASSERT(a.m_name && b.m_name);
|
|
|
- return a.m_name < b.m_name;
|
|
|
- });
|
|
|
-
|
|
|
- // Compact them
|
|
|
- for(U i = 0; i < allCounters.getSize(); ++i)
|
|
|
+ // Compact the counters and get all counter names
|
|
|
+ for(PerFrameCounters& perFrame : ctx.m_counters)
|
|
|
{
|
|
|
- const Counter& inCounter = allCounters[i];
|
|
|
-
|
|
|
- // Create new frame
|
|
|
- if(ctx.m_counters.getSize() == 0 || ctx.m_counters.getBack().m_frameIdx != inCounter.m_frameIdx)
|
|
|
+ if(perFrame.m_tempCounters.getSize() == 0)
|
|
|
{
|
|
|
- ctx.m_counters.emplaceBack(m_alloc);
|
|
|
- ctx.m_counters.getBack().m_frameIdx = inCounter.m_frameIdx;
|
|
|
+ continue;
|
|
|
}
|
|
|
|
|
|
- PerFrameCounters& crntFrame = ctx.m_counters.getBack();
|
|
|
+ // Sort counters
|
|
|
+ std::sort(perFrame.m_tempCounters.getBegin(),
|
|
|
+ perFrame.m_tempCounters.getEnd(),
|
|
|
+ [](const GatherCounter& a, const GatherCounter& b) { return a.m_name < b.m_name; });
|
|
|
|
|
|
- // Check if we have a new counter
|
|
|
- if(crntFrame.m_counters.getSize() == 0 || CString(crntFrame.m_counters.getBack().m_name) != inCounter.m_name)
|
|
|
+ // Compact counters
|
|
|
+ for(const GatherCounter& tmpCounter : perFrame.m_tempCounters)
|
|
|
{
|
|
|
- // Create new counter
|
|
|
- crntFrame.m_counters.emplaceBack(inCounter);
|
|
|
-
|
|
|
- // Update the counter names
|
|
|
- Bool found = false;
|
|
|
- for(const CString& counterName : ctx.m_counterNames)
|
|
|
+ if(perFrame.m_counters.getSize() == 0 || perFrame.m_counters.getBack().m_name != tmpCounter.m_name)
|
|
|
{
|
|
|
- if(counterName == inCounter.m_name)
|
|
|
+ // Create new counter
|
|
|
+ perFrame.m_counters.emplaceBack(tmpCounter);
|
|
|
+
|
|
|
+ // Update the counter names
|
|
|
+ Bool found = false;
|
|
|
+ for(const CString& counterName : ctx.m_counterNames)
|
|
|
{
|
|
|
- found = true;
|
|
|
- break;
|
|
|
+ if(counterName == tmpCounter.m_name)
|
|
|
+ {
|
|
|
+ found = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if(!found)
|
|
|
+ if(!found)
|
|
|
+ {
|
|
|
+ ctx.m_counterNames.emplaceBack(tmpCounter.m_name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- ctx.m_counterNames.emplaceBack(CString(inCounter.m_name));
|
|
|
+ // Merge counters
|
|
|
+ GatherCounter& mergeTo = perFrame.m_counters.getBack();
|
|
|
+ ANKI_ASSERT(mergeTo.m_name == tmpCounter.m_name);
|
|
|
+ mergeTo.m_value += tmpCounter.m_value;
|
|
|
}
|
|
|
}
|
|
|
- else
|
|
|
- {
|
|
|
- // Merge counters
|
|
|
- Counter& mergeTo = crntFrame.m_counters.getBack();
|
|
|
- ANKI_ASSERT(CString(mergeTo.m_name) == inCounter.m_name);
|
|
|
- ANKI_ASSERT(mergeTo.m_frameIdx == inCounter.m_frameIdx);
|
|
|
- mergeTo.m_value += inCounter.m_value;
|
|
|
- }
|
|
|
+
|
|
|
+ // Free some memory
|
|
|
+ perFrame.m_tempCounters.destroy();
|
|
|
}
|
|
|
|
|
|
// Sort the counter names
|
|
|
ANKI_ASSERT(ctx.m_counterNames.getSize() > 0);
|
|
|
std::sort(ctx.m_counterNames.getBegin(), ctx.m_counterNames.getEnd(), [](CString a, CString b) { return a < b; });
|
|
|
|
|
|
- // Fill the gaps. Some counters might have not appeared in some frames
|
|
|
+ // Fill the gaps. Some counters might have not appeared in some frames. Those counters need to have a zero value
|
|
|
+ // because the CSV wants all counters present on all rows
|
|
|
for(PerFrameCounters& perFrame : ctx.m_counters)
|
|
|
{
|
|
|
ANKI_ASSERT(perFrame.m_counters.getSize() <= ctx.m_counterNames.getSize());
|
|
|
@@ -230,7 +342,7 @@ void Tracer::gatherCounters(FlushCtx& ctx)
|
|
|
|
|
|
// Try to find the counter
|
|
|
Bool found = false;
|
|
|
- for(const Counter& c : perFrame.m_counters)
|
|
|
+ for(const GatherCounter& c : perFrame.m_counters)
|
|
|
{
|
|
|
if(counterName == c.m_name)
|
|
|
{
|
|
|
@@ -242,18 +354,17 @@ void Tracer::gatherCounters(FlushCtx& ctx)
|
|
|
if(!found)
|
|
|
{
|
|
|
// Counter is missing
|
|
|
- Counter missingCounter;
|
|
|
- missingCounter.m_frameIdx = perFrame.m_frameIdx;
|
|
|
- missingCounter.m_name = counterName.cstr();
|
|
|
+ GatherCounter missingCounter;
|
|
|
+ missingCounter.m_name = counterName;
|
|
|
missingCounter.m_value = 0;
|
|
|
perFrame.m_counters.emplaceBack(missingCounter);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- std::sort(perFrame.m_counters.getBegin(), perFrame.m_counters.getEnd(), [](const Counter& a, const Counter& b) {
|
|
|
- ANKI_ASSERT(a.m_name && b.m_name);
|
|
|
- return CString(a.m_name) < CString(b.m_name);
|
|
|
- });
|
|
|
+ // Sort again
|
|
|
+ std::sort(perFrame.m_counters.getBegin(),
|
|
|
+ perFrame.m_counters.getEnd(),
|
|
|
+ [](const GatherCounter& a, const GatherCounter& b) { return a.m_name < b.m_name; });
|
|
|
|
|
|
ANKI_ASSERT(perFrame.m_counters.getSize() == ctx.m_counterNames.getSize());
|
|
|
}
|
|
|
@@ -263,25 +374,44 @@ void Tracer::gatherEvents(FlushCtx& ctx)
|
|
|
{
|
|
|
for(ThreadLocal* threadLocal : m_allThreadLocal)
|
|
|
{
|
|
|
- while(!threadLocal->m_events.isEmpty())
|
|
|
+ while(!threadLocal->m_eventChunks.isEmpty())
|
|
|
{
|
|
|
- // Pop event
|
|
|
- Event& inEvent = threadLocal->m_events.getFront();
|
|
|
- threadLocal->m_events.popFront();
|
|
|
+ // Pop chunk
|
|
|
+ EventsChunk& chunk = threadLocal->m_eventChunks.getFront();
|
|
|
+ threadLocal->m_eventChunks.popFront();
|
|
|
|
|
|
// Copy
|
|
|
- Event newEvent = inEvent;
|
|
|
- newEvent.m_tid = threadLocal->m_tid;
|
|
|
- ctx.m_events.emplaceBack(newEvent);
|
|
|
+ for(U i = 0; i < chunk.m_eventCount; ++i)
|
|
|
+ {
|
|
|
+ const Event& inEvent = chunk.m_events[i];
|
|
|
+
|
|
|
+ GatherEvent outEvent;
|
|
|
+ outEvent.m_duration = inEvent.m_duration;
|
|
|
+ outEvent.m_name = inEvent.m_name;
|
|
|
+ outEvent.m_timestamp = inEvent.m_timestamp;
|
|
|
+ outEvent.m_tid = threadLocal->m_tid;
|
|
|
+
|
|
|
+ ctx.m_events.emplaceBack(outEvent);
|
|
|
+ }
|
|
|
|
|
|
- // Delete poped event
|
|
|
- threadLocal->m_eventAlloc.deleteInstance(m_alloc, &inEvent);
|
|
|
+ // Delete poped chunk
|
|
|
+ m_alloc.deleteInstance(&chunk);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Sort them
|
|
|
- std::sort(ctx.m_events.getBegin(), ctx.m_events.getEnd(), [](const Event& a, const Event& b) {
|
|
|
- return a.m_timestamp < b.m_timestamp;
|
|
|
+ std::sort(ctx.m_events.getBegin(), ctx.m_events.getEnd(), [](const GatherEvent& a, const GatherEvent& b) {
|
|
|
+ if(a.m_timestamp != b.m_timestamp)
|
|
|
+ {
|
|
|
+ return a.m_timestamp < b.m_timestamp;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(a.m_duration != b.m_duration)
|
|
|
+ {
|
|
|
+ return a.m_duration < b.m_duration;
|
|
|
+ }
|
|
|
+
|
|
|
+ return a.m_name < b.m_name;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -289,7 +419,7 @@ Error Tracer::writeTraceJson(const FlushCtx& ctx)
|
|
|
{
|
|
|
// Open the file
|
|
|
StringAuto newFname(m_alloc);
|
|
|
- newFname.sprintf("%s_trace.json", ctx.m_filename.cstr());
|
|
|
+ newFname.sprintf("%s.trace.json", ctx.m_filename.cstr());
|
|
|
File file;
|
|
|
ANKI_CHECK(file.open(newFname.toCString(), FileOpenFlag::WRITE));
|
|
|
|
|
|
@@ -302,7 +432,7 @@ Error Tracer::writeTraceJson(const FlushCtx& ctx)
|
|
|
ANKI_CHECK(file.writeText("[\n"));
|
|
|
|
|
|
// Write the events to the file
|
|
|
- for(const Event& event : ctx.m_events)
|
|
|
+ for(const GatherEvent& event : ctx.m_events)
|
|
|
{
|
|
|
const U64 startMicroSec = U64(event.m_timestamp * 1000000.0);
|
|
|
const U64 durMicroSec = U64(event.m_duration * 1000000.0);
|
|
|
@@ -314,7 +444,7 @@ Error Tracer::writeTraceJson(const FlushCtx& ctx)
|
|
|
|
|
|
ANKI_CHECK(file.writeText("{\"name\": \"%s\", \"cat\": \"PERF\", \"ph\": \"X\", "
|
|
|
"\"pid\": 1, \"tid\": %llu, \"ts\": %llu, \"dur\": %llu},\n",
|
|
|
- event.m_name,
|
|
|
+ event.m_name.cstr(),
|
|
|
event.m_tid,
|
|
|
startMicroSec,
|
|
|
durMicroSec));
|
|
|
@@ -324,19 +454,19 @@ Error Tracer::writeTraceJson(const FlushCtx& ctx)
|
|
|
for(U i = 0; i < ctx.m_counters.getSize(); ++i)
|
|
|
{
|
|
|
const PerFrameCounters& frame = ctx.m_counters[i];
|
|
|
- const Second startFrameTime = m_frames[frame.m_frameIdx].m_startFrameTime;
|
|
|
+ const Second startFrameTime = frame.m_startFrameTime;
|
|
|
|
|
|
- // TODO
|
|
|
+ // The counters need a range in order to appear. Add a dummy counter for the last frame
|
|
|
const Array<Second, 2> timestamps = {{startFrameTime, startFrameTime + 1.0}};
|
|
|
const U timestampCount = (i < ctx.m_counters.getSize() - 1) ? 1 : 2;
|
|
|
|
|
|
- for(const Counter& counter : frame.m_counters)
|
|
|
+ for(const GatherCounter& counter : frame.m_counters)
|
|
|
{
|
|
|
for(U j = 0; j < timestampCount; ++j)
|
|
|
{
|
|
|
ANKI_CHECK(file.writeText("{\"name\": \"%s\", \"cat\": \"PERF\", \"ph\": \"C\", "
|
|
|
"\"pid\": 1, \"ts\": %llu, \"args\": {\"val\": %llu}},\n",
|
|
|
- counter.m_name,
|
|
|
+ counter.m_name.cstr(),
|
|
|
U64(timestamps[j] * 1000000.0),
|
|
|
counter.m_value));
|
|
|
}
|
|
|
@@ -352,7 +482,7 @@ Error Tracer::writeCounterCsv(const FlushCtx& ctx)
|
|
|
{
|
|
|
// Open the file
|
|
|
StringAuto fname(m_alloc);
|
|
|
- fname.sprintf("%s_counters.csv", ctx.m_filename.cstr());
|
|
|
+ fname.sprintf("%s.counters.csv", ctx.m_filename.cstr());
|
|
|
File file;
|
|
|
ANKI_CHECK(file.open(fname.toCString(), FileOpenFlag::WRITE));
|
|
|
|
|
|
@@ -371,16 +501,36 @@ Error Tracer::writeCounterCsv(const FlushCtx& ctx)
|
|
|
ANKI_CHECK(file.writeText("\n"));
|
|
|
|
|
|
// Dump the frames
|
|
|
+ U rowCount = 0;
|
|
|
for(const PerFrameCounters& frame : ctx.m_counters)
|
|
|
{
|
|
|
- ANKI_CHECK(file.writeText("%llu", m_frames[frame.m_frameIdx].m_frame));
|
|
|
+ ANKI_CHECK(file.writeText("%llu", frame.m_frame));
|
|
|
|
|
|
- for(const Counter& c : frame.m_counters)
|
|
|
+ for(const GatherCounter& c : frame.m_counters)
|
|
|
{
|
|
|
ANKI_CHECK(file.writeText(",%llu", c.m_value));
|
|
|
}
|
|
|
|
|
|
ANKI_CHECK(file.writeText("\n"));
|
|
|
+ ++rowCount;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Dump some spreadsheet functions
|
|
|
+ ANKI_CHECK(file.writeText("SUM"));
|
|
|
+ for(U i = 0; i < ctx.m_counterNames.getSize(); ++i)
|
|
|
+ {
|
|
|
+ Array<char, 3> columnName;
|
|
|
+ getSpreadsheetColumnName(i + 1, columnName);
|
|
|
+ ANKI_CHECK(file.writeText(",=SUM(%s2:%s%u)", &columnName[0], &columnName[0], rowCount + 1u));
|
|
|
+ }
|
|
|
+ ANKI_CHECK(file.writeText("\n"));
|
|
|
+
|
|
|
+ ANKI_CHECK(file.writeText("AVG"));
|
|
|
+ for(U i = 0; i < ctx.m_counterNames.getSize(); ++i)
|
|
|
+ {
|
|
|
+ Array<char, 3> columnName;
|
|
|
+ getSpreadsheetColumnName(i + 1, columnName);
|
|
|
+ ANKI_CHECK(file.writeText(",=AVERAGE(%s2:%s%u)", &columnName[0], &columnName[0], rowCount + 1u));
|
|
|
}
|
|
|
|
|
|
return Error::NONE;
|
|
|
@@ -396,9 +546,26 @@ Error Tracer::flush(CString filename)
|
|
|
ANKI_CHECK(writeTraceJson(ctx));
|
|
|
ANKI_CHECK(writeCounterCsv(ctx));
|
|
|
|
|
|
- m_frames.destroy(m_alloc);
|
|
|
-
|
|
|
return Error::NONE;
|
|
|
}
|
|
|
|
|
|
+void Tracer::getSpreadsheetColumnName(U column, Array<char, 3>& arr)
|
|
|
+{
|
|
|
+ U major = column / 26;
|
|
|
+ U minor = column % 26;
|
|
|
+
|
|
|
+ if(major)
|
|
|
+ {
|
|
|
+ arr[0] = 'A' + (major - 1);
|
|
|
+ arr[1] = 'A' + minor;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ arr[0] = 'A' + minor;
|
|
|
+ arr[1] = '\0';
|
|
|
+ }
|
|
|
+
|
|
|
+ arr[2] = '\0';
|
|
|
+}
|
|
|
+
|
|
|
} // end namespace anki
|