430 lines
12 KiB
C
430 lines
12 KiB
C
#define SCOPE_NAME_LENGTH 256
|
|
struct scope_record
|
|
{
|
|
u32 NameHash;
|
|
s64 StartCycles;
|
|
s64 EndCycles;
|
|
s32 CallDepth;
|
|
};
|
|
|
|
struct collated_scope_record
|
|
{
|
|
u32 NameHash;
|
|
s64 TotalCycles;
|
|
s32 CallCount;
|
|
|
|
r32 PercentFrameTime;
|
|
r32 TotalSeconds;
|
|
|
|
r32 AverageSecondsPerCall;
|
|
};
|
|
|
|
#define SCOPE_NAME_BUFFER_LENGTH 128
|
|
struct scope_name
|
|
{
|
|
u32 Hash;
|
|
string Name;
|
|
char Buffer[SCOPE_NAME_BUFFER_LENGTH];
|
|
};
|
|
|
|
struct debug_scope_record_list
|
|
{
|
|
s32 ThreadId;
|
|
s32 Max;
|
|
s32 Count;
|
|
scope_record* Calls;
|
|
|
|
s32 CurrentScopeCallDepth;
|
|
};
|
|
|
|
#define DEBUG_FRAME_GROW_SIZE 8102
|
|
struct debug_frame
|
|
{
|
|
s64 FrameStartCycles;
|
|
s64 FrameEndCycles;
|
|
|
|
s32 ScopeNamesMax;
|
|
scope_name* ScopeNamesHash;
|
|
|
|
s32 ThreadCount;
|
|
debug_scope_record_list* ThreadCalls;
|
|
|
|
s32 CollatedScopesMax;
|
|
collated_scope_record* CollatedScopes;
|
|
};
|
|
|
|
#define FRAME_VIEW_PROFILER 0
|
|
#define FRAME_VIEW_SCOPE_LIST 1
|
|
|
|
struct debug_interface
|
|
{
|
|
b32 ShowCameraMouse;
|
|
b32 ShowTrackedScopes;
|
|
b32 RenderSculpture;
|
|
b32 SendSACNData;
|
|
|
|
s32 FrameView;
|
|
};
|
|
|
|
typedef s32 debug_get_thread_id();
|
|
typedef s64 debug_timing_proc();
|
|
typedef u8* debug_alloc(s32 ElementSize, s32 ElementCount);
|
|
typedef u8* debug_realloc(u8* Memory, s32 OldSize, s32 NewSize);
|
|
|
|
#define HISTOGRAM_DEPTH 10
|
|
struct debug_histogram_entry
|
|
{
|
|
char ScopeName_[SCOPE_NAME_LENGTH];
|
|
string ScopeName;
|
|
|
|
u32 PerFrame_Cycles[HISTOGRAM_DEPTH];
|
|
u32 PerFrame_CallCount[HISTOGRAM_DEPTH];
|
|
s32 CurrentFrame;
|
|
|
|
// NOTE(Peter): Cached Values, recalculated ever frame
|
|
u32 Average_Cycles;
|
|
u32 Average_CallCount;
|
|
u32 Total_Cycles;
|
|
u32 Total_CallCount;
|
|
};
|
|
|
|
#define DEBUG_FRAME_COUNT 128
|
|
struct debug_services
|
|
{
|
|
s64 PerformanceCountFrequency;
|
|
|
|
b32 RecordFrames;
|
|
s32 CurrentDebugFrame;
|
|
debug_frame Frames[DEBUG_FRAME_COUNT];
|
|
|
|
debug_interface Interface;
|
|
|
|
debug_get_thread_id* GetThreadId;
|
|
debug_timing_proc* GetWallClock;
|
|
debug_alloc* Alloc;
|
|
debug_realloc* Realloc;
|
|
};
|
|
|
|
internal void
|
|
InitializeDebugFrame (debug_frame* Frame, s32 NameHashMax, s32 ThreadCount, s32 ScopeCallsMax, debug_services* Services)
|
|
{
|
|
Frame->ScopeNamesMax = NameHashMax;
|
|
Frame->ScopeNamesHash = (scope_name*)Services->Alloc(sizeof(scope_name), NameHashMax);
|
|
|
|
// NOTE(Peter): We use the same size as scope names because we're only storing a single instance
|
|
// per scope. If ScopeNamesMax can't hold all the scopes, this will never get filled and
|
|
// we should assert and recompile with a resized NameHashMax
|
|
Frame->CollatedScopesMax = NameHashMax;
|
|
Frame->CollatedScopes = (collated_scope_record*)Services->Alloc(sizeof(collated_scope_record), NameHashMax);
|
|
|
|
for (s32 i = 0; i < Frame->ScopeNamesMax; i++)
|
|
{
|
|
scope_name* Entry = Frame->ScopeNamesHash + i;
|
|
Entry->Name = MakeString(Entry->Buffer, 0, SCOPE_NAME_BUFFER_LENGTH);
|
|
}
|
|
|
|
Frame->ThreadCount = ThreadCount;
|
|
Frame->ThreadCalls = (debug_scope_record_list*)Services->Alloc(sizeof(debug_scope_record_list),
|
|
ThreadCount);
|
|
|
|
for (s32 i = 0; i < ThreadCount; i++)
|
|
{
|
|
Frame->ThreadCalls[i].Max = ScopeCallsMax;
|
|
Frame->ThreadCalls[i].Count = 0;
|
|
Frame->ThreadCalls[i].Calls = (scope_record*)Services->Alloc(sizeof(scope_record), ScopeCallsMax);
|
|
Frame->ThreadCalls[i].CurrentScopeCallDepth = 0;
|
|
Frame->ThreadCalls[i].ThreadId = 0;
|
|
}
|
|
|
|
for (s32 c = 0; c < Frame->CollatedScopesMax; c++)
|
|
{
|
|
Frame->CollatedScopes[c].NameHash = 0;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
StartDebugFrame(debug_frame* Frame, debug_services* Services)
|
|
{
|
|
Frame->FrameStartCycles = Services->GetWallClock();
|
|
for (s32 i = 0; i < Frame->ThreadCount; i++)
|
|
{
|
|
Frame->ThreadCalls[i].Count = 0;
|
|
Frame->ThreadCalls[i].CurrentScopeCallDepth = 0;
|
|
}
|
|
|
|
for (s32 c = 0; c < Frame->CollatedScopesMax; c++)
|
|
{
|
|
s32 Hash = Frame->CollatedScopes[c].NameHash;
|
|
Frame->CollatedScopes[c] = {};
|
|
Frame->CollatedScopes[c].NameHash = Hash;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
InitDebugServices (debug_services* Services,
|
|
s64 PerformanceCountFrequency,
|
|
debug_alloc* Alloc,
|
|
debug_realloc* Realloc,
|
|
debug_timing_proc* GetWallClock,
|
|
debug_get_thread_id* GetThreadId,
|
|
s32 ThreadCount)
|
|
{
|
|
Services->Alloc = Alloc;
|
|
Services->Realloc = Realloc;
|
|
Services->GetWallClock = GetWallClock;
|
|
Services->GetThreadId = GetThreadId;
|
|
|
|
Services->RecordFrames = true;
|
|
|
|
Services->CurrentDebugFrame = 0;
|
|
s32 NameHashMax = 4096;
|
|
s32 ScopeCallsMax = 4096;
|
|
for (s32 i = 0; i < DEBUG_FRAME_COUNT; i++)
|
|
{
|
|
InitializeDebugFrame(&Services->Frames[i], NameHashMax, ThreadCount, ScopeCallsMax, Services);
|
|
}
|
|
|
|
Services->Interface.RenderSculpture = true;
|
|
|
|
Services->PerformanceCountFrequency = PerformanceCountFrequency;
|
|
|
|
Services->Interface.ShowCameraMouse = false;
|
|
Services->Interface.ShowTrackedScopes = false;
|
|
Services->Interface.RenderSculpture = true;
|
|
Services->Interface.SendSACNData = false;
|
|
}
|
|
|
|
internal debug_frame*
|
|
GetCurrentDebugFrame (debug_services* Services)
|
|
{
|
|
debug_frame* Result = Services->Frames + Services->CurrentDebugFrame;
|
|
return Result;
|
|
}
|
|
|
|
internal debug_frame*
|
|
GetLastDebugFrame(debug_services* Services)
|
|
{
|
|
s32 Index = (Services->CurrentDebugFrame - 1);
|
|
if (Index < 0) { Index += DEBUG_FRAME_COUNT; }
|
|
debug_frame* Result = Services->Frames + Index;
|
|
return Result;
|
|
}
|
|
|
|
internal s32
|
|
GetIndexForNameHash(debug_frame* Frame, u32 NameHash)
|
|
{
|
|
s32 Result = -1;
|
|
|
|
for (s32 Offset = 0; Offset < Frame->ScopeNamesMax; Offset++)
|
|
{
|
|
u32 Index = (NameHash + Offset) % Frame->ScopeNamesMax;
|
|
if ((Frame->ScopeNamesHash[Index].Hash == NameHash))
|
|
{
|
|
Result = Index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// NOTE(Peter): Its not technically wrong to return a -1 here, just means we didn't find it.
|
|
// At the time of writing however, this function is only being called in contexts where we
|
|
// know there should be an entry in the Name table, so a -1 actually indicates a problem.
|
|
Assert(Result >= 0);
|
|
return Result;
|
|
}
|
|
|
|
internal debug_scope_record_list*
|
|
GetScopeListForThreadInFrame(debug_services* Services, debug_frame* Frame)
|
|
{
|
|
debug_scope_record_list* List = 0;
|
|
|
|
s32 CurrentThreadId = Services->GetThreadId();
|
|
for (s32 Offset = 0; Offset < Frame->ThreadCount; Offset++)
|
|
{
|
|
s32 Index = (CurrentThreadId + Offset) % Frame->ThreadCount;
|
|
if (Frame->ThreadCalls[Index].ThreadId == CurrentThreadId)
|
|
{
|
|
List = Frame->ThreadCalls + Index;
|
|
break;
|
|
}
|
|
else if (Frame->ThreadCalls[Index].ThreadId == 0)
|
|
{
|
|
Frame->ThreadCalls[Index].ThreadId = CurrentThreadId;
|
|
List = Frame->ThreadCalls + Index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert(List);
|
|
return List;
|
|
}
|
|
|
|
internal void
|
|
CollateThreadScopeCalls (debug_scope_record_list* ThreadRecords, debug_frame* Frame)
|
|
{
|
|
for (s32 i = 0; i < ThreadRecords->Count; i++)
|
|
{
|
|
scope_record Record = ThreadRecords->Calls[i];
|
|
s32 Index = GetIndexForNameHash (Frame, Record.NameHash);
|
|
collated_scope_record* CollatedRecord = Frame->CollatedScopes + Index;
|
|
|
|
if (CollatedRecord->NameHash != Record.NameHash)
|
|
{
|
|
CollatedRecord->NameHash = Record.NameHash;
|
|
CollatedRecord->TotalCycles = 0;
|
|
CollatedRecord->CallCount = 0;
|
|
}
|
|
|
|
CollatedRecord->TotalCycles += Record.EndCycles - Record.StartCycles;
|
|
CollatedRecord->CallCount += 1;
|
|
}
|
|
}
|
|
|
|
internal void
|
|
EndDebugFrame (debug_services* Services)
|
|
{
|
|
debug_frame* ClosingFrame = GetCurrentDebugFrame(Services);
|
|
ClosingFrame->FrameEndCycles = Services->GetWallClock();
|
|
|
|
s64 FrameTotalCycles = ClosingFrame->FrameEndCycles - ClosingFrame->FrameStartCycles;
|
|
|
|
for (s32 t = 0; t < ClosingFrame->ThreadCount; t++)
|
|
{
|
|
CollateThreadScopeCalls(ClosingFrame->ThreadCalls + t, ClosingFrame);
|
|
}
|
|
|
|
for (s32 n = 0; n < ClosingFrame->ScopeNamesMax; n++)
|
|
{
|
|
if (ClosingFrame->ScopeNamesHash[n].Hash != 0)
|
|
{
|
|
collated_scope_record* CollatedRecord = ClosingFrame->CollatedScopes + n;
|
|
CollatedRecord->TotalSeconds = (r32)CollatedRecord->TotalCycles / (r32)Services->PerformanceCountFrequency;
|
|
CollatedRecord->PercentFrameTime = (r32)CollatedRecord->TotalCycles / (r32)FrameTotalCycles;
|
|
CollatedRecord->AverageSecondsPerCall = CollatedRecord->TotalSeconds / CollatedRecord->CallCount;
|
|
}
|
|
}
|
|
|
|
Services->CurrentDebugFrame = (Services->CurrentDebugFrame + 1) % DEBUG_FRAME_COUNT;
|
|
StartDebugFrame(&Services->Frames[Services->CurrentDebugFrame], Services);
|
|
}
|
|
|
|
internal u32
|
|
HashScopeName (char* ScopeName)
|
|
{
|
|
// djb2 hash
|
|
u32 Hash = 5381;
|
|
char* C = ScopeName;
|
|
while(*C)
|
|
{
|
|
Hash = ((Hash << 5) + Hash) + *C;
|
|
C++;
|
|
}
|
|
return Hash;
|
|
}
|
|
|
|
internal scope_name*
|
|
GetOrAddNameHashEntry(debug_frame* Frame, u32 NameHash)
|
|
{
|
|
scope_name* Result = 0;
|
|
|
|
for (s32 Offset = 0; Offset < Frame->ScopeNamesMax; Offset++)
|
|
{
|
|
u32 Index = (NameHash + Offset) % Frame->ScopeNamesMax;
|
|
if ((Frame->ScopeNamesHash[Index].Hash == 0) || (Frame->ScopeNamesHash[Index].Hash == NameHash))
|
|
{
|
|
Result = Frame->ScopeNamesHash + Index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
internal u32
|
|
BeginTrackingScopeAndGetNameHash (debug_services* Services, char* ScopeName)
|
|
{
|
|
debug_frame* CurrentFrame = GetCurrentDebugFrame(Services);
|
|
debug_scope_record_list* ThreadList = GetScopeListForThreadInFrame(Services, CurrentFrame);
|
|
|
|
ThreadList->CurrentScopeCallDepth++;
|
|
|
|
u32 NameHash = HashScopeName(ScopeName);
|
|
scope_name* Entry = GetOrAddNameHashEntry(CurrentFrame, NameHash);
|
|
if (Entry->Hash == 0) // If its new
|
|
{
|
|
Entry->Hash = NameHash;
|
|
// TODO(Peter): need to initialize all entry name strings to point at the buffer
|
|
// This will break eventually. when it does, do this ^^^^ when on startup
|
|
CopyCharArrayToString(ScopeName, &Entry->Name);
|
|
}
|
|
|
|
return NameHash;
|
|
}
|
|
|
|
internal void
|
|
PushScopeTimeOnFrame (debug_services* Services, s32 NameHash, u64 StartCycles, u64 EndCycles)
|
|
{
|
|
debug_frame* CurrentFrame = GetCurrentDebugFrame(Services);
|
|
debug_scope_record_list* ThreadList = GetScopeListForThreadInFrame(Services, CurrentFrame);
|
|
|
|
if (ThreadList->Count >= ThreadList->Max)
|
|
{
|
|
s32 CurrentSize = ThreadList->Max * sizeof(scope_record);
|
|
s32 NewMax = (ThreadList->Max + DEBUG_FRAME_GROW_SIZE);
|
|
s32 NewSize = NewMax * sizeof(scope_record);
|
|
ThreadList->Calls = (scope_record*)Services->Realloc((u8*)ThreadList->Calls, CurrentSize, NewSize);
|
|
ThreadList->Max = NewMax;
|
|
}
|
|
|
|
Assert(ThreadList->Count < ThreadList->Max);
|
|
|
|
s32 EntryIndex = ThreadList->Count++;
|
|
scope_record* Record = ThreadList->Calls + EntryIndex;
|
|
Record->NameHash = NameHash;
|
|
Record->StartCycles = StartCycles;
|
|
Record->EndCycles = EndCycles;
|
|
Record->CallDepth = --ThreadList->CurrentScopeCallDepth;
|
|
}
|
|
|
|
internal r32 DEBUGGetSecondsElapsed (s64 Start, s64 End, r32 PerformanceCountFrequency)
|
|
{
|
|
r32 Result = ((r32)(End - Start) / (r32)PerformanceCountFrequency);
|
|
return Result;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
#define DEBUG_TRACK_FUNCTION scope_tracker ScopeTracker (__FUNCTION__, GlobalDebugServices)
|
|
#define DEBUG_TRACK_SCOPE(name) scope_tracker ScopeTracker_##name (#name, GlobalDebugServices)
|
|
#else
|
|
#define DEBUG_TRACK_FUNCTION
|
|
#define DEBUG_TRACK_SCOPE(name)
|
|
#endif
|
|
struct scope_tracker
|
|
{
|
|
s64 ScopeStart;
|
|
u32 ScopeNameHash;
|
|
debug_services* DebugServices;
|
|
|
|
scope_tracker(char* ScopeName, debug_services* DebugServices)
|
|
{
|
|
if (DebugServices->RecordFrames)
|
|
{
|
|
this->ScopeNameHash = BeginTrackingScopeAndGetNameHash(DebugServices, ScopeName);
|
|
this->ScopeStart = DebugServices->GetWallClock();
|
|
this->DebugServices = DebugServices;
|
|
}
|
|
else
|
|
{
|
|
this->DebugServices = 0;
|
|
}
|
|
}
|
|
|
|
~scope_tracker()
|
|
{
|
|
if (this->DebugServices) // NOTE(Peter): If DebugServices == 0, then we werent' recording this frame
|
|
{
|
|
s64 ScopeEnd = this->DebugServices->GetWallClock();
|
|
PushScopeTimeOnFrame(this->DebugServices, this->ScopeNameHash, this->ScopeStart, ScopeEnd);
|
|
}
|
|
}
|
|
};
|