diff --git a/prof.c b/prof.c index 8105201..9e2d59f 100644 --- a/prof.c +++ b/prof.c @@ -1310,12 +1310,14 @@ static struct { long long delay; long long epoch; long long exec_time; + char host_info[1024]; } info; struct { int unsigned version; - // https://github.com/wolfpld/tracy/blame/master/public/common/TracyQueue.hpp#L10 + // https://github.com/wolfpld/tracy/blob/master/public/common/TracyQueue.hpp#L10 + char unsigned message_app_info; char unsigned zone_begin; char unsigned zone_end; char unsigned zone_color; @@ -1326,6 +1328,7 @@ static struct { char unsigned response_server_query_noop; char unsigned response_source_code_unavail; char unsigned response_symbol_code_unavail; + char unsigned response_single_string_data; char unsigned response_string_data; char unsigned response_thread_name; @@ -1384,6 +1387,15 @@ static struct { _Alignas(UTRACY_L1_LINE_SIZE) int padding; #endif } queue; + + struct { + struct app_info_msg { + char *str; + int unsigned len; + } *messages; + int unsigned count; + int unsigned capacity; + } app_info_buffer; } utracy; /* queue api */ @@ -1457,23 +1469,19 @@ static int unsigned linux_main_tid; UTRACY_INTERNAL UTRACY_INLINE int unsigned utracy_tid(void) { #if defined(UTRACY_WINDOWS) -# if defined(UTRACY_CLANG) || defined(UTRACY_GCC) +#if defined(UTRACY_CLANG) || defined(UTRACY_GCC) int unsigned tid; - __asm__("mov %%fs:0x24, %0;" :"=r"(tid)); + __asm__("mov %%fs:0x24, %0" :"=r"(tid)); return tid; - -# elif defined(UTRACY_MSVC) - +#elif defined(UTRACY_MSVC) __asm { mov eax, fs:[0x24]; } - -# else - +#else return GetCurrentThreadId(); +#endif -# endif #elif defined(UTRACY_LINUX) /* too damn slow return syscall(__NR_gettid); */ @@ -1663,6 +1671,7 @@ void get_cpu_info(char *manufacturer, int unsigned *cpu_id) { #define UTRACY_PROTOCOL_0_12_0 (74) // Also covers 0.12.1 and 0.12.2 #define UTRACY_PROTOCOL_0_13_0 (76) +#define UTRACY_EVT_MESSAGEAPPINFO (6) #define UTRACY_EVT_ZONEBEGIN (15) #define UTRACY_EVT_ZONEEND (17) #define UTRACY_EVT_PLOTDATA (43) @@ -1717,6 +1726,69 @@ void utracy_emit_frame_mark(char *const name) { }); } +UTRACY_INTERNAL UTRACY_INLINE +void utracy_emit_app_info(char const *const str, int unsigned len) { + // Always buffer the message for replay on connect + if(utracy.app_info_buffer.count >= utracy.app_info_buffer.capacity) { + // Expand buffer capacity (start at 8, double each time) + int unsigned new_capacity = utracy.app_info_buffer.capacity == 0 ? 8 : utracy.app_info_buffer.capacity * 2; + struct app_info_msg *new_messages = realloc(utracy.app_info_buffer.messages, new_capacity * sizeof(struct app_info_msg)); + if(!new_messages) { + LOG_DEBUG_ERROR; + return; + } + utracy.app_info_buffer.messages = new_messages; + utracy.app_info_buffer.capacity = new_capacity; + } + + // Allocate and store a copy of the string + char *stored_str = malloc(len); + if(!stored_str) { + LOG_DEBUG_ERROR; + return; + } + UTRACY_MEMCPY(stored_str, str, len); + + utracy.app_info_buffer.messages[utracy.app_info_buffer.count].str = stored_str; + utracy.app_info_buffer.messages[utracy.app_info_buffer.count].len = len; + utracy.app_info_buffer.count++; + + // If connected, send immediately + if(utracy.sock.connected) { + if(0 != utracy_write_app_info(str, len)) { + LOG_DEBUG_ERROR; + return; + } + + if(0 != utracy_commit()) { + LOG_DEBUG_ERROR; + return; + } + } +} + +UTRACY_INTERNAL UTRACY_INLINE +void utracy_replay_app_info_buffer(void) { + if(utracy.app_info_buffer.count == 0) { + return; + } + + for(int unsigned i = 0; i < utracy.app_info_buffer.count; i++) { + struct app_info_msg *msg = &utracy.app_info_buffer.messages[i]; + + if(0 != utracy_write_app_info(msg->str, msg->len)) { + LOG_DEBUG_ERROR; + continue; + } + } + + // Commit all the replayed messages + if(0 != utracy_commit()) { + LOG_DEBUG_ERROR; + return; + } +} + /* removed in 0.13 */ UTRACY_INTERNAL long long calibrate_delay(void) { @@ -1866,6 +1938,7 @@ int utracy_protocol_init(int unsigned version) { switch(version) { case UTRACY_PROTOCOL_0_8_1: case UTRACY_PROTOCOL_0_8_2: + utracy.protocol.message_app_info = 6; utracy.protocol.zone_begin = 15; utracy.protocol.zone_end = 17; utracy.protocol.zone_color = 62; @@ -1876,6 +1949,7 @@ int utracy_protocol_init(int unsigned version) { utracy.protocol.response_server_query_noop = 87; utracy.protocol.response_source_code_unavail = 88; utracy.protocol.response_symbol_code_unavail = 89; + utracy.protocol.response_single_string_data = 91; utracy.protocol.response_string_data = 94; utracy.protocol.response_thread_name = 95; @@ -1892,6 +1966,7 @@ int utracy_protocol_init(int unsigned version) { /* also covers 0.9.1 */ case UTRACY_PROTOCOL_0_9_0: + utracy.protocol.message_app_info = 6; utracy.protocol.zone_begin = 15; utracy.protocol.zone_end = 17; utracy.protocol.zone_color = 64; @@ -1902,6 +1977,7 @@ int utracy_protocol_init(int unsigned version) { utracy.protocol.response_server_query_noop = 90; utracy.protocol.response_source_code_unavail = 91; utracy.protocol.response_symbol_code_unavail = 92; + utracy.protocol.response_single_string_data = 94; utracy.protocol.response_string_data = 97; utracy.protocol.response_thread_name = 98; @@ -1917,6 +1993,7 @@ int utracy_protocol_init(int unsigned version) { break; case UTRACY_PROTOCOL_0_10_0: + utracy.protocol.message_app_info = 6; utracy.protocol.zone_begin = 15; utracy.protocol.zone_end = 17; utracy.protocol.zone_color = 64; @@ -1927,6 +2004,7 @@ int utracy_protocol_init(int unsigned version) { utracy.protocol.response_server_query_noop = 91; utracy.protocol.response_source_code_unavail = 92; utracy.protocol.response_symbol_code_unavail = 93; + utracy.protocol.response_single_string_data = 95; utracy.protocol.response_string_data = 98; utracy.protocol.response_thread_name = 99; @@ -1942,6 +2020,7 @@ int utracy_protocol_init(int unsigned version) { break; case UTRACY_PROTOCOL_0_11_0: + utracy.protocol.message_app_info = 6; utracy.protocol.zone_begin = 15; utracy.protocol.zone_end = 17; utracy.protocol.zone_color = 65; @@ -1952,6 +2031,7 @@ int utracy_protocol_init(int unsigned version) { utracy.protocol.response_server_query_noop = 92; utracy.protocol.response_source_code_unavail = 93; utracy.protocol.response_symbol_code_unavail = 94; + utracy.protocol.response_single_string_data = 96; utracy.protocol.response_string_data = 99; utracy.protocol.response_thread_name = 100; @@ -1967,6 +2047,7 @@ int utracy_protocol_init(int unsigned version) { break; case UTRACY_PROTOCOL_0_11_1: + utracy.protocol.message_app_info = 6; utracy.protocol.zone_begin = 15; utracy.protocol.zone_end = 17; utracy.protocol.zone_color = 65; @@ -1977,6 +2058,7 @@ int utracy_protocol_init(int unsigned version) { utracy.protocol.response_server_query_noop = 92; utracy.protocol.response_source_code_unavail = 93; utracy.protocol.response_symbol_code_unavail = 94; + utracy.protocol.response_single_string_data = 96; utracy.protocol.response_string_data = 100; utracy.protocol.response_thread_name = 101; @@ -1993,6 +2075,7 @@ int utracy_protocol_init(int unsigned version) { /* Also covers 0.12.1 and 0.12.2 */ case UTRACY_PROTOCOL_0_12_0: + utracy.protocol.message_app_info = 6; utracy.protocol.zone_begin = 15; utracy.protocol.zone_end = 17; utracy.protocol.zone_color = 67; @@ -2003,6 +2086,7 @@ int utracy_protocol_init(int unsigned version) { utracy.protocol.response_server_query_noop = 94; utracy.protocol.response_source_code_unavail = 95; utracy.protocol.response_symbol_code_unavail = 96; + utracy.protocol.response_single_string_data = 98; utracy.protocol.response_string_data = 102; utracy.protocol.response_thread_name = 103; @@ -2018,6 +2102,7 @@ int utracy_protocol_init(int unsigned version) { break; case UTRACY_PROTOCOL_0_13_0: + utracy.protocol.message_app_info = 6; utracy.protocol.zone_begin = 15; utracy.protocol.zone_end = 17; utracy.protocol.zone_color = 68; @@ -2028,6 +2113,7 @@ int utracy_protocol_init(int unsigned version) { utracy.protocol.response_server_query_noop = 95; utracy.protocol.response_source_code_unavail = 96; utracy.protocol.response_symbol_code_unavail = 97; + utracy.protocol.response_single_string_data = 99; utracy.protocol.response_string_data = 104; utracy.protocol.response_thread_name = 105; @@ -2067,6 +2153,8 @@ int utracy_client_accept(void) { LOG_INFO("received connection: %s\n", ip); utracy.sock.connected = 1; + + utracy_replay_app_info_buffer(); return 0; } @@ -2323,6 +2411,80 @@ int utracy_write_frame_mark(struct event evt) { return 0; } +UTRACY_INTERNAL UTRACY_INLINE +int utracy_write_single_string(char const *const str, int unsigned len) { +#pragma pack(push, 1) + struct network_single_string { + char unsigned type; + short unsigned len; + char str[]; + }; + _Static_assert(3 == sizeof(struct network_single_string), "incorrect size"); +#pragma pack(pop) + + size_t size = sizeof(struct network_single_string) + len; + static char buf[sizeof(struct network_single_string) + 65536]; + struct network_single_string *msg = (struct network_single_string *) buf; + + msg->type = utracy.protocol.response_single_string_data; + msg->len = (short unsigned) len; + (void) UTRACY_MEMCPY(msg->str, str, len); + + if(0 != utracy_write_packet(msg, size)) { + LOG_DEBUG_ERROR; + return -1; + } + + return 0; +} + +UTRACY_INTERNAL UTRACY_INLINE +int utracy_write_app_info(char const *const str, int unsigned len) { +#pragma pack(push, 1) + struct network_app_info { + char unsigned type; + long long timestamp; + long long unsigned ptr; + short unsigned size; + }; + _Static_assert(19 == sizeof(struct network_app_info), "incorrect size"); +#pragma pack(pop) + + char *persistent_str = malloc(len); + if(!persistent_str) { + LOG_DEBUG_ERROR; + return -1; + } + UTRACY_MEMCPY(persistent_str, str, len); + + // IMPORTANT: Send SingleStringData FIRST (before the MessageAppInfo packet) + // This matches Tracy's SendSingleString followed by AppendData pattern + if(0 != utracy_write_single_string(persistent_str, len)) { + LOG_DEBUG_ERROR; + free(persistent_str); + return -1; + } + + struct network_app_info msg = { + .type = utracy.protocol.message_app_info, + .timestamp = utracy_tsc(), + .ptr = (uintptr_t) persistent_str, + .size = (short unsigned) len + }; + + if(0 != utracy_write_packet(&msg, sizeof(msg))) { + LOG_DEBUG_ERROR; + free(persistent_str); + return -1; + } + + // Do NOT free persistent_str - Tracy uses the ptr value as a key and may + // not have processed/been sent the string yet. The memory will be leaked + // but this is intentional for the protocol to work correctly. + + return 0; +} + UTRACY_INTERNAL UTRACY_INLINE int utracy_write_srcloc(struct utracy_source_location const *const srcloc) { #pragma pack(push, 1) @@ -2473,7 +2635,9 @@ int utracy_consume_request(void) { } if(req.type == utracy.protocol.query_string) { - if(0 != utracy_write_stringdata(utracy.protocol.response_string_data, (char *) (uintptr_t) req.ptr, req.ptr)) { + char *str = (char *) (uintptr_t) req.ptr; + + if(0 != utracy_write_stringdata(utracy.protocol.response_string_data, str, req.ptr)) { LOG_DEBUG_ERROR; return -1; } @@ -2703,10 +2867,10 @@ int utracy_send_welcome(void) { .cpu_arch = CpuArchX86, .cpu_id = cpu_id, .program_name = "DREAMDAEMON", - .host_info = "???" }; UTRACY_MEMCPY(welcome_old.cpu_manufacturer, cpu_manufacturer, 12); + UTRACY_MEMCPY(welcome_old.host_info, utracy.info.host_info, min(strlen(utracy.info.host_info), 1024)); if(0 != utracy_client_send(&welcome_old, sizeof(welcome_old))) { LOG_DEBUG_ERROR; @@ -2748,10 +2912,10 @@ int utracy_send_welcome(void) { .cpu_arch = CpuArchX86, .cpu_id = cpu_id, .program_name = "DREAMDAEMON", - .host_info = "???" }; UTRACY_MEMCPY(welcome.cpu_manufacturer, cpu_manufacturer, 12); + UTRACY_MEMCPY(welcome.host_info, utracy.info.host_info, min(strlen(utracy.info.host_info), 1024)); if(0 != utracy_client_send(&welcome, sizeof(welcome))) { LOG_DEBUG_ERROR; @@ -3122,6 +3286,13 @@ char *UTRACY_WINDOWS_CDECL UTRACY_LINUX_CDECL init(int argc, char **argv) { utracy.info.init_begin = utracy_tsc(); + // Set host info based on OS +#if defined(UTRACY_WINDOWS) + UTRACY_MEMCPY(utracy.info.host_info, "\nOS: Windows", 13); +#elif defined(UTRACY_LINUX) + UTRACY_MEMCPY(utracy.info.host_info, "\nOS: Linux", 11); +#endif + if(0 != event_queue_init()) { LOG_DEBUG_ERROR; return "event_queue_init failed"; @@ -3290,12 +3461,48 @@ char *UTRACY_WINDOWS_CDECL UTRACY_LINUX_CDECL init(int argc, char **argv) { return "0"; } +UTRACY_EXTERNAL +char *UTRACY_WINDOWS_CDECL UTRACY_LINUX_CDECL app_info(int argc, char **argv) { + if(!initialized) { + return "profiler not initialized"; + } + if(argc != 1) { + return "expected 1 argument"; + } + + char const *const text = argv[0]; + + if(!text) { + return "text argument is null"; + } + + int unsigned const len = (int unsigned) strlen(text); + + if(len == 0) { + return "text argument is empty"; + } + if(len >= 65535) { + return "text argument is too long (max 65534 bytes)"; + } + + utracy_emit_app_info(text, len); + + return "0"; +} + UTRACY_EXTERNAL char *UTRACY_WINDOWS_CDECL UTRACY_LINUX_CDECL destroy(int argc, char **argv) { (void) argc; (void) argv; - /* not yet implemented */ + // Free buffered app_info messages + for(int unsigned i = 0; i < utracy.app_info_buffer.count; i++) { + free(utracy.app_info_buffer.messages[i].str); + } + free(utracy.app_info_buffer.messages); + utracy.app_info_buffer.messages = NULL; + utracy.app_info_buffer.count = 0; + utracy.app_info_buffer.capacity = 0; return "0"; } diff --git a/readme.md b/readme.md index 4a02763..4372d92 100644 --- a/readme.md +++ b/readme.md @@ -37,11 +37,15 @@ byond-tracy glues together a byond server with the tracy profiler allowing you t *except `515.1612` on Linux as there was no release* ## supported tracy versions -`0.8.1` `0.8.2` `0.9.0` `0.9.1` `0.10.0` `0.11.0` `0.11.1` +`0.8.1` `0.8.2` `0.9.*` `0.10.0` `0.11.*` `0.12.*` `0.13.0` ## usage simply call `init` from `prof.dll` to begin collecting profile data and connect using [tracy-server](https://github.com/wolfpld/tracy/releases) `Tracy.exe` -```ts + +To send custom application information to Tracy (such as server name, version, or other metadata), use the `app_info` function. +Messages are buffered and automatically sent to Tracy clients when they connect. Each call to `app_info` adds a new line to the display: + +```dm /proc/prof_init() var/lib @@ -53,6 +57,10 @@ simply call `init` from `prof.dll` to begin collecting profile data and connect var/init = call_ext(lib, "init")() if("0" != init) CRASH("[lib] init error: [init]") + // Optionally send application info to Tracy + var/app_info_text = "[world.name] - [world.byond_version].[world.byond_build]" + call_ext(lib, "app_info")(app_info_text) + /world/New() prof_init() . = ..()