소스 검색

The tracer is now feature complete

Panagiotis Christopoulos Charitos 7 년 전
부모
커밋
b127d5bfbc
9개의 변경된 파일561개의 추가작업 그리고 259개의 파일을 삭제
  1. 1 0
      src/anki/Util.h
  2. 1 1
      src/anki/scene/SceneGraph.h
  3. 1 1
      src/anki/util/CMakeLists.txt
  4. 1 0
      src/anki/util/StdTypes.h
  5. 7 11
      src/anki/util/String.h
  6. 0 216
      src/anki/util/Trace.cpp
  7. 422 0
      src/anki/util/Tracer.cpp
  8. 70 30
      src/anki/util/Tracer.h
  9. 58 0
      tests/util/Tracer.cpp

+ 1 - 0
src/anki/Util.h

@@ -36,6 +36,7 @@
 #include <anki/util/INotify.h>
 #include <anki/util/INotify.h>
 #include <anki/util/SparseArray.h>
 #include <anki/util/SparseArray.h>
 #include <anki/util/ObjectAllocator.h>
 #include <anki/util/ObjectAllocator.h>
+#include <anki/util/Tracer.h>
 
 
 /// @defgroup util Utilities (like STL)
 /// @defgroup util Utilities (like STL)
 
 

+ 1 - 1
src/anki/scene/SceneGraph.h

@@ -245,7 +245,7 @@ private:
 
 
 	IntrusiveList<SceneNode> m_nodes;
 	IntrusiveList<SceneNode> m_nodes;
 	U32 m_nodesCount = 0;
 	U32 m_nodesCount = 0;
-	HashMap<CString, SceneNode*, CStringHasher> m_nodesDict;
+	HashMap<CString, SceneNode*> m_nodesDict;
 
 
 	SceneNode* m_mainCam = nullptr;
 	SceneNode* m_mainCam = nullptr;
 	Timestamp m_activeCameraChangeTimestamp = 0;
 	Timestamp m_activeCameraChangeTimestamp = 0;

+ 1 - 1
src/anki/util/CMakeLists.txt

@@ -1,4 +1,4 @@
-set(SOURCES Assert.cpp Functions.cpp File.cpp Filesystem.cpp Memory.cpp System.cpp HighRezTimer.cpp ThreadPool.cpp ThreadHive.cpp Hash.cpp Logger.cpp String.cpp StringList.cpp Trace.cpp)
+set(SOURCES Assert.cpp Functions.cpp File.cpp Filesystem.cpp Memory.cpp System.cpp HighRezTimer.cpp ThreadPool.cpp ThreadHive.cpp Hash.cpp Logger.cpp String.cpp StringList.cpp Tracer.cpp)
 
 
 if(LINUX OR ANDROID OR MACOS)
 if(LINUX OR ANDROID OR MACOS)
 	set(SOURCES ${SOURCES} HighRezTimerPosix.cpp FilesystemPosix.cpp ThreadPosix.cpp)
 	set(SOURCES ${SOURCES} HighRezTimerPosix.cpp FilesystemPosix.cpp ThreadPosix.cpp)

+ 1 - 0
src/anki/util/StdTypes.h

@@ -75,6 +75,7 @@ using Bool8 = I8; ///< Small 8bit boolean type
 using Bool32 = I32; ///< A 32bit boolean
 using Bool32 = I32; ///< A 32bit boolean
 
 
 using Second = F64; ///< The base time unit is second.
 using Second = F64; ///< The base time unit is second.
+const Second MAX_SECOND = MAX_F64;
 
 
 using Timestamp = U64; ///< Timestamp type.
 using Timestamp = U64; ///< Timestamp type.
 const Timestamp MAX_TIMESTAMP = MAX_U64;
 const Timestamp MAX_TIMESTAMP = MAX_U64;

+ 7 - 11
src/anki/util/String.h

@@ -234,6 +234,13 @@ public:
 	/// Convert to U32.
 	/// Convert to U32.
 	ANKI_USE_RESULT Error toNumber(U32& out) const;
 	ANKI_USE_RESULT Error toNumber(U32& out) const;
 
 
+	/// Compute the hash.
+	U32 computeHash() const
+	{
+		checkInit();
+		return anki::computeHash(m_ptr, getLength());
+	}
+
 private:
 private:
 	const Char* m_ptr = nullptr;
 	const Char* m_ptr = nullptr;
 
 
@@ -243,17 +250,6 @@ private:
 	}
 	}
 };
 };
 
 
-/// Hasher function for CStrings. Can be used in HashMap.
-class CStringHasher
-{
-public:
-	U64 operator()(CString str)
-	{
-		ANKI_ASSERT(!str.isEmpty());
-		return computeHash(&str[0], str.getLength());
-	}
-};
-
 /// Compare function for CStrings. Can be used in HashMap.
 /// Compare function for CStrings. Can be used in HashMap.
 class CStringCompare
 class CStringCompare
 {
 {

+ 0 - 216
src/anki/util/Trace.cpp

@@ -1,216 +0,0 @@
-// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#include <anki/util/Trace.h>
-#include <anki/util/HighRezTimer.h>
-
-namespace anki
-{
-
-thread_local Tracer::ThreadLocal Tracer::m_threadLocal;
-
-void Tracer::beginFrame()
-{
-	m_startFrameTime = HighRezTimer::getCurrentTime();
-}
-
-Tracer::ThreadLocal& Tracer::getThreadLocal()
-{
-	ThreadLocal& out = m_threadLocal;
-	if(ANKI_UNLIKELY(!out.m_tracerKnowsAboutThis))
-	{
-		LockGuard<Mutex> lock(m_threadLocalMtx);
-		m_allThreadLocal.emplaceBack(m_alloc, &out);
-		out.m_tracerKnowsAboutThis = true;
-	}
-
-	return out;
-}
-
-void Tracer::beginEvent()
-{
-	if(m_enabled)
-	{
-		ThreadLocal& threadLocal = getThreadLocal();
-		Event* event = threadLocal.m_eventAlloc.newInstance(m_alloc);
-		event->m_timestamp = HighRezTimer::getCurrentTime();
-		threadLocal.m_events.pushBack(event);
-	}
-}
-
-void Tracer::endEvent(const char* eventName)
-{
-	ANKI_ASSERT(eventName);
-
-	if(m_enabled)
-	{
-		// 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_timestamp = HighRezTimer::getCurrentTime() - event.m_timestamp;
-
-		// Store a counter as well
-		increaseCounter(eventName, U64(event.m_duration * 1000000000.0));
-	}
-}
-
-void Tracer::increaseCounter(const char* counterName, U64 value)
-{
-	ANKI_ASSERT(counterName);
-
-	if(m_enabled)
-	{
-		ThreadLocal& threadLocal = getThreadLocal();
-		Counter* counter = threadLocal.m_counterAlloc.newInstance(m_alloc);
-		counter->m_name = counterName;
-		counter->m_value = value;
-		counter->m_frame = m_frame;
-		counter->m_startFrameTime = m_startFrameTime;
-	}
-}
-
-void Tracer::compactCounters(DynamicArrayAuto<Counter>& allCounters)
-{
-	// Gather all the counters
-	DynamicArrayAuto<Counter> uncompactCounters(m_alloc);
-	for(ThreadLocal* threadLocal : m_allThreadLocal)
-	{
-		while(!threadLocal->m_counters.isEmpty())
-		{
-			// Pop counter
-			Counter& inCounter = threadLocal->m_counters.getFront();
-			threadLocal->m_counters.popFront();
-
-			// Copy
-			Counter newCounter = inCounter;
-			uncompactCounters.emplaceBack(newCounter);
-
-			// Delete poped counter
-			threadLocal->m_counterAlloc.deleteInstance(m_alloc, &inCounter);
-		}
-	}
-
-	if(uncompactCounters.getSize() == 0)
-	{
-		// Early exit
-		return;
-	}
-
-	// Sort them
-	std::sort(uncompactCounters.getBegin(), uncompactCounters.getEnd(), [](const Counter& a, const Counter& b) {
-		if(a.m_frame != b.m_frame)
-		{
-			return a.m_frame < b.m_frame;
-		}
-
-		ANKI_ASSERT(a.m_name && a.m_name);
-		return std::strcmp(a.m_name, b.m_name) < 0;
-	});
-
-	// Compact them
-	allCounters.emplaceBack(uncompactCounters[0]);
-	for(U i = 1; i < uncompactCounters.getSize(); ++i)
-	{
-		const Counter& inCounter = uncompactCounters[i];
-		Counter& outCounter = allCounters.getBack();
-		if(CString(inCounter.m_name) != CString(outCounter.m_name) || inCounter.m_frame != outCounter.m_frame)
-		{
-			// Create a new entry
-			allCounters.emplaceBack(inCounter);
-		}
-		else
-		{
-			// Append value to the existing counter
-			outCounter.m_value += inCounter.m_value;
-		}
-	}
-}
-
-Error Tracer::writeTraceJson(CString filename, const DynamicArrayAuto<Counter>& counters)
-{
-	class NEvent : public Event
-	{
-	public:
-		ThreadId m_tid;
-	};
-
-	// Gather all events from all threads
-	DynamicArrayAuto<NEvent> allEvents(m_alloc);
-	for(ThreadLocal* threadLocal : m_allThreadLocal)
-	{
-		while(!threadLocal->m_events.isEmpty())
-		{
-			// Pop event
-			Event& inEvent = threadLocal->m_events.getFront();
-			threadLocal->m_events.popFront();
-
-			// Copy
-			NEvent newEvent;
-			static_cast<Event&>(newEvent) = inEvent;
-			newEvent.m_tid = threadLocal->m_tid;
-			allEvents.emplaceBack(newEvent);
-
-			// Delete poped event
-			threadLocal->m_eventAlloc.deleteInstance(m_alloc, &inEvent);
-		}
-	}
-
-	if(allEvents.getSize() == 0)
-	{
-		// Early exit
-		return Error::NONE;
-	}
-
-	// Sort them
-	std::sort(allEvents.getBegin(), allEvents.getEnd(), [](const NEvent& a, const NEvent& b) {
-		return a.m_timestamp < b.m_timestamp;
-	});
-
-	// Write the events to the file
-	StringAuto newFname(m_alloc);
-	newFname.sprintf("%s_trace.json", filename.cstr());
-	File file;
-	ANKI_CHECK(file.open(newFname.toCString(), FileOpenFlag::WRITE));
-
-	for(const NEvent& event : allEvents)
-	{
-		U64 startMicroSec = U64(event.m_timestamp * 1000000.0);
-		U64 durMicroSec = U64(event.m_duration * 1000000.0);
-
-		if(durMicroSec == 0)
-		{
-			continue;
-		}
-
-		ANKI_CHECK(file.writeText("{\"name\": \"%s\", \"cat\": \"PERF\", \"ph\": \"X\", "
-								  "\"pid\": 1, \"tid\": %llu, \"ts\": %llu, \"dur\": %llu},\n",
-			event.m_name,
-			event.m_tid,
-			startMicroSec,
-			durMicroSec));
-	}
-
-	// Write the counters
-	for(const Counter& counter : counters)
-	{
-		ANKI_CHECK(m_traceFile.writeText("{\"name\": \"%s\", \"cat\": \"PERF\", \"ph\": \"C\", "
-										 "\"pid\": 1, \"ts\": %llu, \"args\": {\"val\": %llu}},\n",
-			counter.m_name,
-			U64(counter.m_startFrameTime * 1000000.0),
-			counter.m_value));
-	}
-
-	return Error::NONE;
-}
-
-Error Tracer::flush(CString filename)
-{
-	// TODO
-	return Error::NONE;
-}
-
-} // end namespace anki

+ 422 - 0
src/anki/util/Tracer.cpp

@@ -0,0 +1,422 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/util/Tracer.h>
+#include <anki/util/HighRezTimer.h>
+#include <anki/util/HashMap.h>
+
+namespace anki
+{
+
+thread_local Tracer::ThreadLocal Tracer::m_threadLocal;
+
+class Tracer::PerFrameCounters
+{
+public:
+	DynamicArrayAuto<Counter> m_counters;
+	U32 m_frameIdx;
+
+	PerFrameCounters(GenericMemoryPoolAllocator<U8> alloc)
+		: m_counters(alloc)
+	{
+	}
+};
+
+class Tracer::FlushCtx
+{
+public:
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	CString m_filename;
+	DynamicArrayAuto<CString> m_counterNames;
+	DynamicArrayAuto<PerFrameCounters> m_counters;
+	DynamicArrayAuto<Event> m_events;
+
+	FlushCtx(GenericMemoryPoolAllocator<U8> alloc, const CString& filename)
+		: m_alloc(alloc)
+		, m_filename(filename)
+		, m_counterNames(alloc)
+		, m_counters(alloc)
+		, m_events(alloc)
+	{
+	}
+};
+
+Tracer::~Tracer()
+{
+	for(ThreadLocal* threadLocal : m_allThreadLocal)
+	{
+		while(!threadLocal->m_counters.isEmpty())
+		{
+			Counter& counter = threadLocal->m_counters.getFront();
+			threadLocal->m_counters.popFront();
+			threadLocal->m_counterAlloc.deleteInstance(m_alloc, &counter);
+		}
+
+		while(!threadLocal->m_events.isEmpty())
+		{
+			Event& event = threadLocal->m_events.getFront();
+			threadLocal->m_events.popFront();
+			threadLocal->m_eventAlloc.deleteInstance(m_alloc, &event);
+		}
+	}
+
+	m_allThreadLocal.destroy(m_alloc);
+	m_frames.destroy(m_alloc);
+}
+
+void Tracer::beginFrame(U64 frame)
+{
+#if ANKI_ASSERTS_ENABLED
+	if(m_frames.getSize() > 0)
+	{
+		ANKI_ASSERT(frame > m_frames.getBack().m_frame);
+	}
+#endif
+
+	ANKI_ASSERT(!isInsideBeginEndFrame());
+
+	Frame f;
+	f.m_startFrameTime = HighRezTimer::getCurrentTime();
+	f.m_endFrameTime = 0.0;
+	f.m_frame = frame;
+#if ANKI_ASSERTS_ENABLED
+	f.m_canRecord = true;
+#endif
+	m_frames.emplaceBack(m_alloc, f);
+}
+
+void Tracer::endFrame()
+{
+	ANKI_ASSERT(isInsideBeginEndFrame());
+	m_frames.getBack().m_canRecord = false;
+	m_frames.getBack().m_endFrameTime = HighRezTimer::getCurrentTime();
+}
+
+Tracer::ThreadLocal& Tracer::getThreadLocal()
+{
+	ThreadLocal& out = m_threadLocal;
+	if(ANKI_UNLIKELY(!out.m_tracerKnowsAboutThis))
+	{
+		LockGuard<Mutex> lock(m_threadLocalMtx);
+		m_allThreadLocal.emplaceBack(m_alloc, &out);
+		out.m_tid = Thread::getCurrentThreadId();
+		out.m_tracerKnowsAboutThis = true;
+	}
+
+	return out;
+}
+
+void Tracer::beginEvent()
+{
+	ANKI_ASSERT(isInsideBeginEndFrame());
+
+	ThreadLocal& threadLocal = getThreadLocal();
+	Event* event = threadLocal.m_eventAlloc.newInstance(m_alloc);
+	event->m_timestamp = HighRezTimer::getCurrentTime();
+	threadLocal.m_events.pushBack(event);
+}
+
+void Tracer::endEvent(const char* eventName)
+{
+	ANKI_ASSERT(eventName);
+	ANKI_ASSERT(isInsideBeginEndFrame());
+
+	// 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;
+
+	// Store a counter as well. In ns
+	increaseCounter(eventName, U64(event.m_duration * 1000000000.0));
+}
+
+void Tracer::increaseCounter(const char* counterName, U64 value)
+{
+	ANKI_ASSERT(counterName);
+	ANKI_ASSERT(isInsideBeginEndFrame());
+
+	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);
+}
+
+void Tracer::gatherCounters(FlushCtx& ctx)
+{
+	// Gather all the counters
+	DynamicArrayAuto<Counter> allCounters(m_alloc);
+	for(ThreadLocal* threadLocal : m_allThreadLocal)
+	{
+		while(!threadLocal->m_counters.isEmpty())
+		{
+			// Pop counter
+			Counter& inCounter = threadLocal->m_counters.getFront();
+			threadLocal->m_counters.popFront();
+
+			// Copy
+			Counter newCounter = inCounter;
+			allCounters.emplaceBack(newCounter);
+
+			// Delete poped counter
+			threadLocal->m_counterAlloc.deleteInstance(m_alloc, &inCounter);
+		}
+	}
+
+	if(allCounters.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)
+	{
+		const Counter& inCounter = allCounters[i];
+
+		// Create new frame
+		if(ctx.m_counters.getSize() == 0 || ctx.m_counters.getBack().m_frameIdx != inCounter.m_frameIdx)
+		{
+			ctx.m_counters.emplaceBack(m_alloc);
+			ctx.m_counters.getBack().m_frameIdx = inCounter.m_frameIdx;
+		}
+
+		PerFrameCounters& crntFrame = ctx.m_counters.getBack();
+
+		// Check if we have a new counter
+		if(crntFrame.m_counters.getSize() == 0 || CString(crntFrame.m_counters.getBack().m_name) != inCounter.m_name)
+		{
+			// Create new counter
+			crntFrame.m_counters.emplaceBack(inCounter);
+
+			// Update the counter names
+			Bool found = false;
+			for(const CString& counterName : ctx.m_counterNames)
+			{
+				if(counterName == inCounter.m_name)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if(!found)
+			{
+				ctx.m_counterNames.emplaceBack(CString(inCounter.m_name));
+			}
+		}
+		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;
+		}
+	}
+
+	// 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
+	for(PerFrameCounters& perFrame : ctx.m_counters)
+	{
+		ANKI_ASSERT(perFrame.m_counters.getSize() <= ctx.m_counterNames.getSize());
+
+		for(U i = 0; i < ctx.m_counterNames.getSize(); ++i)
+		{
+			const CString& counterName = ctx.m_counterNames[i];
+
+			// Try to find the counter
+			Bool found = false;
+			for(const Counter& c : perFrame.m_counters)
+			{
+				if(counterName == c.m_name)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if(!found)
+			{
+				// Counter is missing
+				Counter missingCounter;
+				missingCounter.m_frameIdx = perFrame.m_frameIdx;
+				missingCounter.m_name = counterName.cstr();
+				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);
+		});
+
+		ANKI_ASSERT(perFrame.m_counters.getSize() == ctx.m_counterNames.getSize());
+	}
+}
+
+void Tracer::gatherEvents(FlushCtx& ctx)
+{
+	for(ThreadLocal* threadLocal : m_allThreadLocal)
+	{
+		while(!threadLocal->m_events.isEmpty())
+		{
+			// Pop event
+			Event& inEvent = threadLocal->m_events.getFront();
+			threadLocal->m_events.popFront();
+
+			// Copy
+			Event newEvent = inEvent;
+			newEvent.m_tid = threadLocal->m_tid;
+			ctx.m_events.emplaceBack(newEvent);
+
+			// Delete poped event
+			threadLocal->m_eventAlloc.deleteInstance(m_alloc, &inEvent);
+		}
+	}
+
+	// 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;
+	});
+}
+
+Error Tracer::writeTraceJson(const FlushCtx& ctx)
+{
+	// Open the file
+	StringAuto newFname(m_alloc);
+	newFname.sprintf("%s_trace.json", ctx.m_filename.cstr());
+	File file;
+	ANKI_CHECK(file.open(newFname.toCString(), FileOpenFlag::WRITE));
+
+	if(ctx.m_events.getSize() == 0)
+	{
+		// Early exit
+		return Error::NONE;
+	}
+
+	ANKI_CHECK(file.writeText("[\n"));
+
+	// Write the events to the file
+	for(const Event& event : ctx.m_events)
+	{
+		const U64 startMicroSec = U64(event.m_timestamp * 1000000.0);
+		const U64 durMicroSec = U64(event.m_duration * 1000000.0);
+
+		if(durMicroSec == 0)
+		{
+			continue;
+		}
+
+		ANKI_CHECK(file.writeText("{\"name\": \"%s\", \"cat\": \"PERF\", \"ph\": \"X\", "
+								  "\"pid\": 1, \"tid\": %llu, \"ts\": %llu, \"dur\": %llu},\n",
+			event.m_name,
+			event.m_tid,
+			startMicroSec,
+			durMicroSec));
+	}
+
+	// Write the counters
+	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 endFrameTime = m_frames[frame.m_frameIdx].m_endFrameTime;
+
+		const Array<Second, 2> timestamps = {{startFrameTime, endFrameTime}};
+		const U timestampCount = (i < ctx.m_counters.getSize() - 1) ? 1 : 2;
+
+		for(const Counter& 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,
+					U64(timestamps[j] * 1000000.0),
+					counter.m_value));
+			}
+		}
+	}
+
+	ANKI_CHECK(file.writeText("{}\n]\n"));
+
+	return Error::NONE;
+}
+
+Error Tracer::writeCounterCsv(const FlushCtx& ctx)
+{
+	// Open the file
+	StringAuto fname(m_alloc);
+	fname.sprintf("%s_counters.csv", ctx.m_filename.cstr());
+	File file;
+	ANKI_CHECK(file.open(fname.toCString(), FileOpenFlag::WRITE));
+
+	if(ctx.m_counters.getSize() == 0)
+	{
+		// If there are no counters leave the file empty and exit
+		return Error::NONE;
+	}
+
+	// Write the counter names
+	ANKI_CHECK(file.writeText("Frame"));
+	for(CString counterName : ctx.m_counterNames)
+	{
+		ANKI_CHECK(file.writeText(",%s", counterName.cstr()));
+	}
+	ANKI_CHECK(file.writeText("\n"));
+
+	// Dump the frames
+	for(const PerFrameCounters& frame : ctx.m_counters)
+	{
+		ANKI_CHECK(file.writeText("%llu", m_frames[frame.m_frameIdx].m_frame));
+
+		for(const Counter& c : frame.m_counters)
+		{
+			ANKI_CHECK(file.writeText(",%llu", c.m_value));
+		}
+
+		ANKI_CHECK(file.writeText("\n"));
+	}
+
+	return Error::NONE;
+}
+
+Error Tracer::flush(CString filename)
+{
+	ANKI_ASSERT(!isInsideBeginEndFrame());
+	FlushCtx ctx(m_alloc, filename);
+
+	gatherCounters(ctx);
+	gatherEvents(ctx);
+
+	ANKI_CHECK(writeTraceJson(ctx));
+	ANKI_CHECK(writeCounterCsv(ctx));
+
+	m_frames.destroy(m_alloc);
+
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 70 - 30
src/anki/util/Trace.h → src/anki/util/Tracer.h

@@ -8,6 +8,7 @@
 #include <anki/util/File.h>
 #include <anki/util/File.h>
 #include <anki/util/List.h>
 #include <anki/util/List.h>
 #include <anki/util/ObjectAllocator.h>
 #include <anki/util/ObjectAllocator.h>
+#include <anki/util/Singleton.h>
 
 
 namespace anki
 namespace anki
 {
 {
@@ -19,7 +20,9 @@ namespace anki
 class Tracer : public NonCopyable
 class Tracer : public NonCopyable
 {
 {
 public:
 public:
-	Tracer();
+	Tracer()
+	{
+	}
 
 
 	~Tracer();
 	~Tracer();
 
 
@@ -37,24 +40,11 @@ public:
 	/// Increase a counter.
 	/// Increase a counter.
 	void increaseCounter(const char* counterName, U64 value);
 	void increaseCounter(const char* counterName, U64 value);
 
 
-	/// TODO
-	void beginFrame();
+	/// Begin a new frame.
+	void beginFrame(U64 frame);
 
 
 	/// Call it to end the frame.
 	/// Call it to end the frame.
-	void endFrame()
-	{
-		m_frame += 0;
-	}
-
-	Bool getEnabled() const
-	{
-		return m_enabled;
-	}
-
-	void setEnabled(Bool enable)
-	{
-		m_enabled = enable;
-	}
+	void endFrame();
 
 
 	/// Flush all results to a file. Don't call that more than once.
 	/// Flush all results to a file. Don't call that more than once.
 	ANKI_USE_RESULT Error flush(CString filename);
 	ANKI_USE_RESULT Error flush(CString filename);
@@ -62,10 +52,18 @@ public:
 private:
 private:
 	GenericMemoryPoolAllocator<U8> m_alloc;
 	GenericMemoryPoolAllocator<U8> m_alloc;
 
 
-	Bool8 m_enabled = false;
+	class Frame
+	{
+	public:
+		U64 m_frame;
+		Second m_startFrameTime; ///< When the frame started
+		Second m_endFrameTime; ///< When it ended
+#if ANKI_ASSERTS_ENABLED
+		Bool8 m_canRecord;
+#endif
+	};
 
 
-	File m_traceFile;
-	File m_counterFile;
+	DynamicArray<Frame> m_frames;
 
 
 	/// Event.
 	/// Event.
 	class Event : public IntrusiveListEnabled<Event>
 	class Event : public IntrusiveListEnabled<Event>
@@ -74,6 +72,7 @@ private:
 		const char* m_name ANKI_DBG_NULLIFY;
 		const char* m_name ANKI_DBG_NULLIFY;
 		Second m_timestamp ANKI_DBG_NULLIFY;
 		Second m_timestamp ANKI_DBG_NULLIFY;
 		Second m_duration ANKI_DBG_NULLIFY;
 		Second m_duration ANKI_DBG_NULLIFY;
+		ThreadId m_tid ANKI_DBG_NULLIFY;
 	};
 	};
 
 
 	/// Counter.
 	/// Counter.
@@ -82,8 +81,7 @@ private:
 	public:
 	public:
 		const char* m_name ANKI_DBG_NULLIFY;
 		const char* m_name ANKI_DBG_NULLIFY;
 		U64 m_value ANKI_DBG_NULLIFY;
 		U64 m_value ANKI_DBG_NULLIFY;
-		U64 m_frame ANKI_DBG_NULLIFY;
-		Second m_startFrameTime;
+		U32 m_frameIdx ANKI_DBG_NULLIFY;
 	};
 	};
 
 
 	class ThreadLocal
 	class ThreadLocal
@@ -101,20 +99,62 @@ private:
 	DynamicArray<ThreadLocal*> m_allThreadLocal; ///< The Tracer should know about all the ThreadLocal.
 	DynamicArray<ThreadLocal*> m_allThreadLocal; ///< The Tracer should know about all the ThreadLocal.
 	Mutex m_threadLocalMtx;
 	Mutex m_threadLocalMtx;
 
 
-	U64 m_frame = 0;
-	Second m_startFrameTime = 0.0;
+	class FlushCtx;
+	class PerFrameCounters;
+
+	Bool isInsideBeginEndFrame() const
+	{
+		return m_frames.getSize() > 0 && m_frames.getBack().m_canRecord;
+	}
 
 
 	/// Get the thread local ThreadLocal structure.
 	/// Get the thread local ThreadLocal structure.
 	ThreadLocal& getThreadLocal();
 	ThreadLocal& getThreadLocal();
 
 
-	/// TODO
-	void compactCounters(DynamicArrayAuto<Counter>& allCounters);
+	/// Gather all counters from all the threads.
+	void gatherCounters(FlushCtx& ctx);
+
+	/// Gather the events from all the threads.
+	void gatherEvents(FlushCtx& ctx);
 
 
-	/// TODO
-	Error writeCounterCsv(CString filename, const DynamicArrayAuto<Counter>& counters);
+	/// Dump the counters to a CSV file
+	Error writeCounterCsv(const FlushCtx& ctx);
 
 
-	/// TODO
-	Error writeTraceJson(CString filename, const DynamicArrayAuto<Counter>& counters);
+	/// Dump the events and the counters to a chrome trace file.
+	Error writeTraceJson(const FlushCtx& ctx);
+};
+
+/// Tracer singleton.
+using TracerSingleton = Singleton<Tracer>;
+
+/// Convenience class to trace an event.
+class TraceScopedEvent
+{
+public:
+	TraceScopedEvent(const char* name)
+		: m_name(name)
+		, m_tracer(&TracerSingleton::get())
+	{
+		m_tracer->beginEvent();
+	}
+
+	~TraceScopedEvent()
+	{
+		m_tracer->endEvent(m_name);
+	}
+
+private:
+	const char* m_name;
+	Tracer* m_tracer;
+};
+
+/// Convenience class to increase a trace counter.
+class TraceIncreaseCounter
+{
+public:
+	TraceIncreaseCounter(const char* name, U64 value)
+	{
+		TracerSingleton::get().increaseCounter(name, value);
+	}
 };
 };
 /// @}
 /// @}
 
 

+ 58 - 0
tests/util/Tracer.cpp

@@ -0,0 +1,58 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <tests/framework/Framework.h>
+#include <anki/util/Tracer.h>
+#include <anki/util/HighRezTimer.h>
+
+ANKI_TEST(Util, Tracer)
+{
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+	Tracer tracer;
+	tracer.init(alloc);
+
+	// 1st frame
+	tracer.beginFrame(0);
+	tracer.endFrame();
+	ANKI_TEST_EXPECT_NO_ERR(tracer.flush("./0"));
+
+	// 2nd frame
+	// 2 same events
+	tracer.beginFrame(1);
+
+	tracer.beginEvent();
+	HighRezTimer::sleep(0.5);
+	tracer.endEvent("event");
+
+	tracer.beginEvent();
+	HighRezTimer::sleep(0.5);
+	tracer.endEvent("event");
+
+	tracer.endFrame();
+
+	// 4rd frame
+	// 2 different events & non zero counter
+	tracer.beginFrame(3);
+
+	tracer.beginEvent();
+	HighRezTimer::sleep(0.5);
+	tracer.endEvent("event");
+
+	tracer.beginEvent();
+	HighRezTimer::sleep(0.5);
+	tracer.endEvent("event2");
+
+	tracer.increaseCounter("counter", 100);
+
+	tracer.endFrame();
+
+	// 5th frame
+	tracer.beginFrame(4);
+	tracer.increaseCounter("counter", 150);
+	HighRezTimer::sleep(0.1);
+	tracer.endFrame();
+
+	ANKI_TEST_EXPECT_NO_ERR(tracer.flush("./1"));
+}