Skip to content

Commit f9cec74

Browse files
Ivorforceenetheru
andcommitted
Add support for profiling GDScript with tracy.
This adds macro `GodotProfileZoneGroupedFirstScript`, and uses interning for speedy lookups. Co-authored-by: Samuel Nicholas <nicholas.samuel@gmail.com>
1 parent 9dd6c4d commit f9cec74

File tree

11 files changed

+196
-1
lines changed

11 files changed

+196
-1
lines changed

core/profiling/profiling.cpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,165 @@
3131
#include "profiling.h"
3232

3333
#if defined(GODOT_USE_TRACY)
34+
// Use the tracy profiler.
35+
36+
#include "core/os/mutex.h"
37+
#include "core/string/string_name.h"
38+
#include "core/templates/paged_allocator.h"
39+
#include "core/variant/variant.h"
40+
41+
namespace TracyInternal {
42+
static bool configured = false;
43+
44+
// Implementation similar to StringName.
45+
struct StringInternData {
46+
StringName name;
47+
CharString name_utf8;
48+
uint32_t hash = 0;
49+
50+
StringInternData *prev = nullptr;
51+
StringInternData *next = nullptr;
52+
53+
StringInternData() {}
54+
};
55+
56+
struct SourceLocationInternData {
57+
const StringInternData *file;
58+
const StringInternData *function;
59+
uint32_t function_ptr_hash = 0;
60+
61+
tracy::SourceLocationData source_location_data;
62+
63+
SourceLocationInternData *prev = nullptr;
64+
SourceLocationInternData *next = nullptr;
65+
66+
SourceLocationInternData() {}
67+
};
68+
69+
struct TracyInternTable {
70+
constexpr static uint32_t TABLE_BITS = 16;
71+
constexpr static uint32_t TABLE_LEN = 1 << TABLE_BITS;
72+
constexpr static uint32_t TABLE_MASK = TABLE_LEN - 1;
73+
74+
static inline BinaryMutex mutex;
75+
76+
static inline SourceLocationInternData *source_location_table[TABLE_LEN];
77+
static inline PagedAllocator<SourceLocationInternData> source_location_allocator;
78+
79+
static inline StringInternData *string_table[TABLE_LEN];
80+
static inline PagedAllocator<StringInternData> string_allocator;
81+
};
82+
83+
const StringInternData *_intern_name(const StringName &p_name, uint32_t p_hash) {
84+
CRASH_COND(!configured);
85+
86+
const uint32_t idx = p_hash & TracyInternTable::TABLE_MASK;
87+
88+
StringInternData *_data = TracyInternTable::string_table[idx];
89+
90+
while (_data) {
91+
if (_data->hash == p_hash) {
92+
return _data;
93+
}
94+
_data = _data->next;
95+
}
96+
97+
_data = TracyInternTable::string_allocator.alloc();
98+
_data->name = StringName(p_name, true);
99+
_data->name_utf8 = p_name.operator String().utf8();
100+
101+
_data->next = TracyInternTable::string_table[idx];
102+
_data->prev = nullptr;
103+
104+
if (TracyInternTable::string_table[idx]) {
105+
TracyInternTable::string_table[idx]->prev = _data;
106+
}
107+
TracyInternTable::string_table[idx] = _data;
108+
109+
return _data;
110+
}
111+
112+
const char *intern_name(const StringName &p_name) {
113+
const uint32_t hash = HashMapHasherDefault::hash(p_name);
114+
MutexLock lock(TracyInternTable::mutex);
115+
return _intern_name(p_name, hash)->name_utf8.get_data();
116+
}
117+
118+
const tracy::SourceLocationData *intern_source_location(const void *p_function_ptr, const StringName &p_file, const StringName &p_function, uint32_t p_line) {
119+
CRASH_COND(!configured);
120+
121+
const uint32_t hash = HashMapHasherDefault::hash(p_function_ptr);
122+
const uint32_t idx = hash & TracyInternTable::TABLE_MASK;
123+
124+
MutexLock lock(TracyInternTable::mutex);
125+
SourceLocationInternData *_data = TracyInternTable::source_location_table[idx];
126+
127+
while (_data) {
128+
if (_data->function_ptr_hash == hash && _data->file->name == p_file && _data->function->name == p_function && _data->source_location_data.line == p_line) {
129+
return &_data->source_location_data;
130+
}
131+
_data = _data->next;
132+
}
133+
134+
static uint64_t count = 0;
135+
_data = TracyInternTable::source_location_allocator.alloc();
136+
count++;
137+
print_line(count);
138+
139+
_data->function_ptr_hash = hash;
140+
_data->file = _intern_name(p_file, HashMapHasherDefault::hash(p_file));
141+
_data->function = _intern_name(p_function, HashMapHasherDefault::hash(p_function));
142+
143+
_data->source_location_data.file = _data->file->name_utf8.get_data();
144+
_data->source_location_data.function = _data->function->name_utf8.get_data();
145+
_data->source_location_data.name = _data->source_location_data.function;
146+
147+
_data->source_location_data.line = p_line;
148+
_data->source_location_data.color = 0x478cbf; // godot_logo_blue
149+
150+
_data->next = TracyInternTable::source_location_table[idx];
151+
_data->prev = nullptr;
152+
153+
if (TracyInternTable::source_location_table[idx]) {
154+
TracyInternTable::source_location_table[idx]->prev = _data;
155+
}
156+
TracyInternTable::source_location_table[idx] = _data;
157+
158+
return &_data->source_location_data;
159+
}
160+
} // namespace TracyInternal
161+
34162
void godot_init_profiler() {
163+
ERR_FAIL_COND(TracyInternal::configured);
164+
for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) {
165+
TracyInternal::TracyInternTable::source_location_table[i] = nullptr;
166+
}
167+
TracyInternal::configured = true;
168+
35169
// Send our first event to tracy; otherwise it doesn't start collecting data.
36170
// FrameMark is kind of fitting because it communicates "this is where we started tracing".
37171
FrameMark;
38172
}
173+
174+
void godot_cleanup_profiler() {
175+
MutexLock lock(TracyInternal::TracyInternTable::mutex);
176+
177+
for (uint32_t i = 0; i < TracyInternal::TracyInternTable::TABLE_LEN; i++) {
178+
while (TracyInternal::TracyInternTable::source_location_table[i]) {
179+
TracyInternal::SourceLocationInternData *d = TracyInternal::TracyInternTable::source_location_table[i];
180+
TracyInternal::TracyInternTable::source_location_table[i] = TracyInternal::TracyInternTable::source_location_table[i]->next;
181+
TracyInternal::TracyInternTable::source_location_allocator.free(d);
182+
}
183+
184+
while (TracyInternal::TracyInternTable::string_table[i]) {
185+
TracyInternal::StringInternData *d = TracyInternal::TracyInternTable::string_table[i];
186+
TracyInternal::TracyInternTable::string_table[i] = TracyInternal::TracyInternTable::string_table[i]->next;
187+
TracyInternal::TracyInternTable::string_allocator.free(d);
188+
}
189+
}
190+
TracyInternal::configured = false;
191+
}
192+
39193
#elif defined(GODOT_USE_PERFETTO)
40194
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
41195

@@ -47,8 +201,17 @@ void godot_init_profiler() {
47201
perfetto::Tracing::Initialize(args);
48202
perfetto::TrackEvent::Register();
49203
}
204+
205+
void godot_cleanup_profiler() {
206+
// Stub
207+
}
208+
50209
#else
51210
void godot_init_profiler() {
52211
// Stub
53212
}
213+
214+
void godot_cleanup_profiler() {
215+
// Stub
216+
}
54217
#endif

core/profiling/profiling.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
#pragma once
3232

33-
#include "core/typedefs.h"
3433
#include "profiling.gen.h"
3534

3635
// This header provides profiling primitives (implemented as macros) for various backends.
@@ -46,9 +45,17 @@
4645
#if defined(GODOT_USE_TRACY)
4746
// Use the tracy profiler.
4847

48+
#include "core/string/string_name.h"
49+
4950
#define TRACY_ENABLE
51+
5052
#include <tracy/Tracy.hpp>
5153

54+
namespace TracyInternal {
55+
const char *intern_name(const StringName &p_name);
56+
const tracy::SourceLocationData *intern_source_location(const void *p_function_ptr, const StringName &p_file, const StringName &p_function, uint32_t p_line);
57+
} //namespace TracyInternal
58+
5259
// Define tracing macros.
5360
#define GodotProfileFrameMark FrameMark
5461
#define GodotProfileZone(m_zone_name) ZoneNamedN(GD_UNIQUE_NAME(__godot_tracy_szone_), m_zone_name, true)
@@ -65,12 +72,15 @@
6572
static constexpr tracy::SourceLocationData TracyConcat(__tracy_source_location, TracyLine){ m_zone_name, TracyFunction, TracyFile, (uint32_t)TracyLine, 0 }; \
6673
new (&__godot_tracy_zone_##m_group_name) tracy::ScopedZone(&TracyConcat(__tracy_source_location, TracyLine), TRACY_CALLSTACK, true)
6774
#endif
75+
#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line) \
76+
tracy::ScopedZone __godot_tracy_zone_##m_group_name(TracyInternal::intern_source_location(m_ptr, m_file, m_function, m_line))
6877

6978
// Memory allocation
7079
#define GodotProfileAlloc(m_ptr, m_size) TracyAlloc(m_ptr, m_size)
7180
#define GodotProfileFree(m_ptr) TracyFree(m_ptr)
7281

7382
void godot_init_profiler();
83+
void godot_cleanup_profiler();
7484

7585
#elif defined(GODOT_USE_PERFETTO)
7686
// Use the perfetto profiler.
@@ -101,15 +111,19 @@ struct PerfettoGroupedEventEnder {
101111
#define GodotProfileZoneGrouped(m_group_name, m_zone_name) \
102112
__godot_perfetto_zone_##m_group_name._end_now(); \
103113
TRACE_EVENT_BEGIN("godot", m_zone_name);
114+
#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line) \\ TODO
104115

105116
#define GodotProfileAlloc(m_ptr, m_size)
106117
#define GodotProfileFree(m_ptr)
118+
107119
void godot_init_profiler();
120+
void godot_cleanup_profiler();
108121

109122
#else
110123
// No profiling; all macros are stubs.
111124

112125
void godot_init_profiler();
126+
void godot_cleanup_profiler();
113127

114128
// Tell the profiling backend that a new frame has started.
115129
#define GodotProfileFrameMark
@@ -128,4 +142,11 @@ void godot_init_profiler();
128142
// Tell the profiling backend that an allocation was freed.
129143
// There must be a one to one correspondence of GodotProfileAlloc and GodotProfileFree calls.
130144
#define GodotProfileFree(m_ptr)
145+
146+
// Define a zone with custom source information (for scripting)
147+
// m_varname is equivalent to GodotProfileZoneGrouped varnames.
148+
// m_ptr is a pointer to the function instance, which will be used for the lookup.
149+
// m_file, m_function are StringNames, m_line is a uint32_t, all used for the source location.
150+
#define GodotProfileZoneGroupedFirstScript(m_varname, m_ptr, m_file, m_function, m_line)
151+
131152
#endif

modules/gdscript/gdscript_vm.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "gdscript_lambda_callable.h"
3434

3535
#include "core/os/os.h"
36+
#include "core/profiling/profiling.h"
3637

3738
#ifdef DEBUG_ENABLED
3839

@@ -495,6 +496,8 @@ void (*type_init_function_table[])(Variant *) = {
495496
#define METHOD_CALL_ON_FREED_INSTANCE_ERROR(method_pointer) "Cannot call method '" + (method_pointer)->get_name() + "' on a previously freed instance."
496497

497498
Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) {
499+
GodotProfileZoneGroupedFirstScript(zone, this, source, name, _initial_line);
500+
498501
OPCODES_TABLE;
499502

500503
if (!_code_ptr) {

platform/android/java_godot_lib_jni.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ static void _terminate(JNIEnv *env, bool p_restart = false) {
129129
NetSocketAndroid::terminate();
130130

131131
cleanup_android_class_loader();
132+
godot_cleanup_profiler();
132133

133134
if (godot_java) {
134135
godot_java->on_godot_terminating(env);

platform/ios/main_ios.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,6 @@ int apple_embedded_main(int argc, char **argv) {
7474

7575
void apple_embedded_finish() {
7676
Main::cleanup();
77+
godot_cleanup_profiler();
7778
delete os;
7879
}

platform/linuxbsd/godot_linuxbsd.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,6 @@ int main(int argc, char *argv[]) {
131131
}
132132
free(cwd);
133133

134+
godot_cleanup_profiler();
134135
return os.get_exit_code();
135136
}

platform/macos/godot_main_macos.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,5 +151,6 @@ int main(int argc, char **argv) {
151151

152152
memdelete(os);
153153

154+
godot_cleanup_profiler();
154155
return exit_code;
155156
}

platform/macos/os_macos.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,7 @@ static void handle_interrupt(int sig) {
11441144
}
11451145

11461146
terminate();
1147+
godot_cleanup_profiler();
11471148
}
11481149

11491150
void OS_MacOS_NSApp::terminate() {

platform/visionos/main_visionos.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@ int apple_embedded_main(int argc, char **argv) {
6969

7070
void apple_embedded_finish() {
7171
Main::cleanup();
72+
godot_cleanup_profiler();
7273
delete os;
7374
}

platform/web/web_main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ void exit_callback() {
6666
int exit_code = OS_Web::get_singleton()->get_exit_code();
6767
memdelete(os);
6868
os = nullptr;
69+
godot_cleanup_profiler();
6970
emscripten_force_exit(exit_code); // Exit runtime.
7071
}
7172

0 commit comments

Comments
 (0)