Files
FlaxEngine/Source/Engine/Profiler/ProfilerCPU.cpp
2023-05-08 17:05:40 +02:00

227 lines
5.6 KiB
C++

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_PROFILER
#include "ProfilerCPU.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Threading/ThreadRegistry.h"
THREADLOCAL ProfilerCPU::Thread* ProfilerCPU::Thread::Current = nullptr;
Array<ProfilerCPU::Thread*, InlinedAllocation<64>> ProfilerCPU::Threads;
bool ProfilerCPU::Enabled = false;
ProfilerCPU::EventBuffer::EventBuffer()
{
_capacity = 8192;
_capacityMask = _capacity - 1;
_data = NewArray<Event>(_capacity);
_head = 0;
_count = 0;
}
ProfilerCPU::EventBuffer::~EventBuffer()
{
DeleteArray(_data, _capacity);
}
void ProfilerCPU::EventBuffer::Extract(Array<Event>& data, bool withRemoval)
{
data.Clear();
// Peek ring buffer state
int32 count = _count;
const int32 capacity = _capacity;
// Skip if empty
if (count == 0)
return;
// Fix iterators when buffer is full (begin == end)
if (count == capacity)
{
_count--;
count--;
}
// Find the first item (skip non-root events)
Iterator firstEvent = Begin();
for (auto i = firstEvent; i.IsNotEnd(); ++i)
{
if (i.Event().Depth == 0)
{
firstEvent = i;
break;
}
}
// Skip if no root event found inside the buffer
if (firstEvent.IsEnd())
return;
// Find the last item (last event in ended root event)
Iterator lastEndedRoot = End();
for (auto i = Last(); i != firstEvent; --i)
{
if (i.Event().Depth == 0 && i.Event().End > 0)
{
lastEndedRoot = i;
break;
}
}
// Skip if no finished root event found inside the buffer
if (lastEndedRoot.IsEnd())
return;
// Find the last non-root event in last root event
Iterator lastEvent = lastEndedRoot;
const double lastRootEventEndTime = lastEndedRoot.Event().End;
for (auto i = --End(); i != lastEndedRoot; --i)
{
if (i.Event().End > 0 && i.Event().End <= lastRootEventEndTime)
{
lastEvent = i;
break;
}
}
if (withRemoval)
{
// Remove all the events between [Begin(), lastEvent]
_count -= (lastEvent.Index() - Begin().Index()) & _capacityMask;
}
// Extract all the events between [firstEvent, lastEvent]
const int32 head = (lastEvent.Index() + 1) & _capacityMask;
count = (lastEvent.Index() - firstEvent.Index() + 1) & _capacityMask;
data.Resize(count, false);
const int32 tail = (head - count) & _capacityMask;
const int32 spaceLeft = capacity - tail;
const int32 spaceLeftCount = Math::Min(spaceLeft, count);
const int32 overflow = count - spaceLeft;
Platform::MemoryCopy(data.Get(), &_data[tail], spaceLeftCount * sizeof(Event));
if (overflow > 0)
Platform::MemoryCopy(data.Get() + spaceLeftCount, &_data[0], overflow * sizeof(Event));
}
int32 ProfilerCPU::Thread::BeginEvent()
{
const double time = Platform::GetTimeSeconds() * 1000.0;
const auto index = Buffer.Add();
Event& e = Buffer.Get(index);
e.Start = time;
e.End = 0;
e.Depth = _depth++;
e.NativeMemoryAllocation = 0;
e.ManagedMemoryAllocation = 0;
return index;
}
void ProfilerCPU::Thread::EndEvent(int32 index)
{
const double time = Platform::GetTimeSeconds() * 1000.0;
_depth--;
Event& e = Buffer.Get(index);
e.End = time;
}
void ProfilerCPU::Thread::EndEvent()
{
const double time = Platform::GetTimeSeconds() * 1000.0;
_depth--;
Event& e = (Buffer.Last()--).Event();
e.End = time;
}
bool ProfilerCPU::IsProfilingCurrentThread()
{
return Enabled && Thread::Current != nullptr;
}
ProfilerCPU::Thread* ProfilerCPU::GetCurrentThread()
{
return Enabled ? Thread::Current : nullptr;
}
int32 ProfilerCPU::BeginEvent()
{
if (!Enabled)
return -1;
auto thread = Thread::Current;
if (thread == nullptr)
{
const auto id = Platform::GetCurrentThreadID();
const auto t = ThreadRegistry::GetThread(id);
if (t)
thread = New<Thread>(t->GetName());
else if (id == Globals::MainThreadID)
thread = New<Thread>(TEXT("Main"));
else
thread = New<Thread>(TEXT("Thread"));
Thread::Current = thread;
Threads.Add(thread);
}
return thread->BeginEvent();
}
int32 ProfilerCPU::BeginEvent(const Char* name)
{
if (!Enabled)
return -1;
const auto index = BeginEvent();
const auto thread = Thread::Current;
auto& e = thread->Buffer.Get(index);
auto dst = e.Name;
auto src = name;
if (src)
{
const auto end = dst + ARRAY_COUNT(e.Name) - 1;
while (*src && dst != end)
*dst++ = *src++;
}
*dst = 0;
return index;
}
int32 ProfilerCPU::BeginEvent(const char* name)
{
if (!Enabled)
return -1;
const auto index = BeginEvent();
const auto thread = Thread::Current;
auto& e = thread->Buffer.Get(index);
auto dst = e.Name;
auto src = name;
if (src)
{
const auto end = dst + ARRAY_COUNT(e.Name) - 1;
while (*src && dst != end)
*dst++ = *src++;
}
*dst = 0;
return index;
}
void ProfilerCPU::EndEvent(int32 index)
{
if (Enabled && Thread::Current)
Thread::Current->EndEvent(index);
}
void ProfilerCPU::EndEvent()
{
if (Enabled && Thread::Current)
Thread::Current->EndEvent();
}
void ProfilerCPU::Dispose()
{
Enabled = false;
Threads.ClearDelete();
// Cleanup memory, note: calls to profiler after this point will end up with a crash (Thread::Current is invalid)
}
#endif