1 /**************************************************************************
3 * Copyright 2013-2014 RAD Game Tools and Valve Software
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 **************************************************************************/
32 #include <sys/types.h>
39 #include <vogl_core.h>
40 #include <vogl_json.h>
42 #include "vogl_intercept.h"
44 #include "../common/vogllogging.h"
45 #include "../common/channel.h"
47 #include "../common/commands.h"
48 #include "../common/toclientmsg.h"
49 #include "../common/pinggame.h"
52 // Needs to be the same in the server gpusession code
53 int g_vogl_traceport = TRACE_PORT;
54 char g_procname[256] = { 0 };
56 void *invoke_listener(void *arg);
58 bool log_output_func(vogl::eConsoleMessageType type, const char *pMsg, void * /*pData*/);
59 void send_status_to_client(const char *message);
60 void send_status_to_client(const char *message, int token);
62 void vogl_init_listener(int vogl_traceport)
67 openlog("vogl-remote", LOG_PID | LOG_CONS | LOG_PERROR, LOG_USER);
70 // Get the process name
71 // If, in the unlikely event, the name of the process doesn't fit, then readlink won't 0-terminate the string.
72 // We initialize it to all 0, so if it all got overwritten, then 0 out the last character in the string.
74 (void) readlink("/proc/self/exe", g_procname, sizeof(g_procname));
75 g_procname[sizeof(g_procname)-1] = '\0';
79 // Launching a VOGL Listener
80 if (0 == vogl_traceport)
82 vogl_traceport = TRACE_PORT;
83 syslog(VOGL_INFO, "No port # passed in, %s listening on default port %d\n", g_procname, vogl_traceport);
86 syslog(VOGL_INFO, "Spinning up listener for vogl_remoting of %s on port %d...\n", g_procname, vogl_traceport);
88 g_vogl_traceport = vogl_traceport;
90 err = pthread_create(&threadCmd, NULL, invoke_listener, NULL);
93 syslog(VOGL_ERROR, "%s:%d %s Unable to create vogl_remoting cmd thread. returned %d, errno %d\n", __FILE__, __LINE__, __func__, err, errno);
97 err = pthread_setname_np(threadCmd, "vogl_remoting");
100 syslog(VOGL_ERROR, "%s:%d %s Unable to set the name of the vogl_remoting cmd thread. returned %d, errno %d\n", __FILE__, __LINE__, __func__, err, errno);
107 void process_trace_command(void *callback_param, unsigned int buffer_size, char *buffer);
109 bool g_fKillApp = false;
110 network::channelmgr *g_clientChannelMgr = NULL;
113 invoke_listener(void * /*arg*/)
116 // Setup to wait for a client to connect
118 pthread_t thisThread = pthread_self();
119 network::CHEC chec = network::EC_NONE;
123 err = pthread_setname_np(thisThread, "vogl_listen");
126 syslog(VOGL_ERROR, "%s:%d %s Unable to set the name of the thread. returned %d, errno %d\n", __FILE__, __LINE__, __func__, err, errno);
129 syslog(VOGL_INFO, "Listener for VOGL Tracing in operation...\n");
131 g_clientChannelMgr = new network::channelmgr();
132 if (NULL == g_clientChannelMgr)
134 syslog(VOGL_ERROR, "%s:%d %s Unable to allocation networking channelmgr. OOM?\n", __FILE__, __LINE__, __func__);
138 // Create a client channel
139 chec = g_clientChannelMgr->Connect( const_cast<char *>("localhost"), g_vogl_traceport, (SENDASYNC|RECVASYNC), process_trace_command, NULL );
140 //chec = g_clientChannelMgr->Accept( g_vogl_traceport, (RECVASYNC|SENDASYNC), true, process_trace_command, NULL, NULL, NULL );
141 if (network::EC_NONE != chec)
143 syslog(VOGL_ERROR, "%s:%d %s Unable to connect on port %d\nExiting.\n", __FILE__, __LINE__, __func__, g_vogl_traceport);
148 // Tell the client we're up and running.
149 send_status_to_client("Game is up and running.");
152 // Start capturing output from the tracer
153 vogl::console::add_console_output_func(log_output_func, NULL);
161 sleep(1); // Don't busy spin...
166 syslog(VOGL_INFO, "%lx: VOGL Tracing server thread: Terminating the application.\n", thisThread);
167 kill(getpid(), SIGTERM); // SIGINT);
170 syslog(VOGL_INFO, "%lx: VOGL Tracing server thread: Terminating.\n", thisThread);
175 bool SetNullMode(unsigned int buffer_size, char *buffer);
176 bool SetDumpGLCalls(unsigned int buffer_size, char *buffer);
177 bool SetDumpGLBuffers(unsigned int buffer_size, char *buffer);
178 bool SetDumpGLShaders(unsigned int buffer_size, char *buffer);
179 bool SetBackTrace(unsigned int buffer_size, char *buffer);
181 bool KillTracer(unsigned int buffer_size, char *buffer);
182 int StartCapture(unsigned int buffer_size, char *buffer);
183 int StopCapture(unsigned int buffer_size, char *buffer);
185 extern bool g_null_mode;
186 extern bool g_backtrace_all_calls;
187 extern bool g_dump_gl_calls_flag;
188 extern bool g_dump_gl_buffers_flag;
189 extern bool g_dump_gl_shaders_flag;
191 // Async calls to send contents to our client
193 void CaptureCompleteCallback(const char *pFilename, void *pOpaque);
196 void process_trace_command(void * /*callback_param*/, unsigned int buffer_size, char *buffer)
199 int32_t command = -1;
201 command = *(int32_t *)buffer;
203 unsigned int buffer_size_temp = buffer_size - sizeof(int32_t);
204 char *buffer_temp = buffer + sizeof(int32_t);
208 case TRACE_SETNULLMODE:
210 g_null_mode = SetNullMode(buffer_size_temp, buffer_temp);
215 case TRACE_SETDUMPGLCALLS:
217 g_dump_gl_calls_flag = SetDumpGLCalls(buffer_size_temp, buffer_temp);
222 case TRACE_SETDUMPGLBUFFERS:
224 g_dump_gl_buffers_flag = SetDumpGLBuffers(buffer_size_temp, buffer_temp);
229 case TRACE_SETDUMPGLSHADERS:
231 g_dump_gl_shaders_flag = SetDumpGLShaders(buffer_size_temp, buffer_temp);
236 case TRACE_SETBACKTRACE:
238 g_backtrace_all_calls = SetBackTrace(buffer_size_temp, buffer_temp);
243 case TRACE_KILLTRACER:
245 g_fKillApp = KillTracer(buffer_size_temp, buffer_temp);
250 case TRACE_STARTCAPTURE:
253 err = StartCapture(buffer_size_temp, buffer_temp);
257 syslog(VOGL_ERROR, "%s:%d %s StartCapture() failed\n", __FILE__, __LINE__, __func__);
263 case TRACE_STOPCAPTURE:
266 err = StopCapture(buffer_size_temp, buffer_temp);
270 syslog(VOGL_ERROR, "%s:%d %s StopCapture() failed\n", __FILE__, __LINE__, __func__);
279 notif_id = PingGame(buffer_size_temp, buffer_temp);
281 // Tell the client we're up and running.
282 send_status_to_client("Game is up and running.", notif_id);
289 syslog(VOGL_ERROR, "%s:%d %s Unknown method (%d)\n", __FILE__, __LINE__, __func__, command);
300 bool SetNullMode(unsigned int buffer_size, char *buffer)
302 vogl::json_document cur_doc;
304 if (0 == buffer_size)
307 cur_doc.deserialize(buffer);
308 vogl::json_node *pjson_node = cur_doc.get_root()->find_child_object("parameters");
310 return pjson_node->value_as_bool("nullmode", false);
316 bool SetDumpGLCalls(unsigned int buffer_size, char *buffer)
318 vogl::json_document cur_doc;
320 if (0 == buffer_size)
323 cur_doc.deserialize(buffer);
324 vogl::json_node *pjson_node = cur_doc.get_root()->find_child_object("parameters");
326 return pjson_node->value_as_bool("dumpglcalls", false);
330 // SetDumpGLBuffersPB
333 bool SetDumpGLBuffers(unsigned int buffer_size, char *buffer)
335 vogl::json_document cur_doc;
337 if (0 == buffer_size)
340 cur_doc.deserialize(buffer);
341 vogl::json_node *pjson_node = cur_doc.get_root()->find_child_object("parameters");
343 return pjson_node->value_as_bool("dumpglbuffers", false);
350 bool SetDumpGLShaders(unsigned int buffer_size, char *buffer)
352 vogl::json_document cur_doc;
354 if (0 == buffer_size)
357 cur_doc.deserialize(buffer);
358 vogl::json_node *pjson_node = cur_doc.get_root()->find_child_object("parameters");
360 return pjson_node->value_as_bool("dumpglshaders", false);
367 // If turned on, captures callstacks for any GL call and put it in the tracefile.
369 bool SetBackTrace(unsigned int buffer_size, char *buffer)
371 vogl::json_document cur_doc;
373 if (0 == buffer_size)
376 cur_doc.deserialize(buffer);
377 vogl::json_node *pjson_node = cur_doc.get_root()->find_child_object("parameters");
379 return pjson_node->value_as_bool("backtrace", false);
385 // Kills the application that is currently being traced.
387 bool KillTracer(unsigned int /*buffer_size*/, char * /*buffer*/)
389 // No body to this message. Just an instruction.
394 char szTraceFileLocal[] = "/.local/share";
396 int StartCapture(unsigned int buffer_size, char *buffer)
398 const char *szBaseFileName;
401 vogl::json_document cur_doc;
402 vogl::json_node *pjson_node;
405 std::string strTraceLocation;
406 char *szTraceLocationT = NULL;
408 syslog(VOGL_INFO, "Checking to see if we're already capturing\n");
410 if (vogl_is_capturing())
415 if (0 == buffer_size)
417 syslog(VOGL_ERROR, "%s:%d %s No parameters passed\n", __FILE__, __LINE__, __func__);
421 cur_doc.deserialize(buffer);
422 pjson_node = cur_doc.get_root()->find_child_object("parameters");
424 syslog(VOGL_INFO, "Not already capturing...continuing\n");
426 cFrames = pjson_node->value_as_int("framestocapture", -1);
427 szBaseFileName = pjson_node->value_as_string_ptr("tracename", "");
429 syslog(VOGL_INFO, "Capturing to %s for %d frames\n", szBaseFileName, cFrames);
433 // Handle the destination directory for the file as per spec
434 // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
435 // If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
436 szTraceLocationT = getenv("XDG_DATA_HOME");
437 syslog(VOGL_INFO, "XDG_DATA_HOME is '%s'\n", (NULL == szTraceLocationT? "NULL": szTraceLocationT));
438 if (NULL == szTraceLocationT)
440 syslog(VOGL_INFO, "Building strTraceLocation from '%s' + '%s'\n", getenv("HOME"), szTraceFileLocal);
441 strTraceLocation = getenv("HOME");
442 strTraceLocation += szTraceFileLocal;
446 strTraceLocation = szTraceLocationT;
448 strTraceLocation += "/vogl/";
450 syslog(VOGL_INFO, "Will be tracing to '%s'\n", strTraceLocation.c_str());
452 // Ensure the destination directory is actually there
453 status = mkdir(strTraceLocation.c_str(), 0777 );
454 if (-1 == status && EEXIST != errno)
456 syslog(VOGL_INFO, "StartCapturePB: Unable to create trace destination directory (%d) %s\n", errno, strTraceLocation.c_str());
461 syslog(VOGL_INFO, "StartCapturePB: Capturing trace to %s%s for %d frames\n", strTraceLocation.c_str(), szBaseFileName, cFrames);
463 fWorked = vogl_capture_on_next_swap(cFrames, strTraceLocation.c_str(), szBaseFileName, CaptureCompleteCallback, NULL);
465 syslog(VOGL_INFO, "StartCapturePB: Tracing now started (%s). Trace Basefilename = %s\n", (fWorked ? "success" : "failed"), szBaseFileName);
473 int StopCapture(unsigned int /*buffer_size*/, char * /*buffer*/)
475 bool fStillCapturing = false;
476 bool fStopping = false;
479 // No body to this message. Not necessary.
481 fStillCapturing = vogl_is_capturing();
482 syslog(VOGL_INFO, "StopCapturePB(): Trace status = %d\n", fStillCapturing);
484 if (true == fStillCapturing)
486 // Need to terminate the trace
487 fStopping = vogl_stop_capturing(CaptureCompleteCallback, NULL);
488 syslog(VOGL_INFO, "StopCapturePB(): vogl_stop_capturing status = %d\n", fStopping);
496 // Sends off a message to the client that tracing has completed.
498 int CaptureCompleteCallbackMsg(const char *pFilename, int cFramesCaptured, unsigned int *message_size, char **pmessage);
499 void CaptureCompleteCallback(const char *pFilename, void * /*pOpaque*/)
502 network::CHEC chec = network::EC_NONE;
503 char *message = NULL;
504 unsigned int message_size = 0;
505 int cFramesCaptured = -1;
507 syslog(VOGL_INFO, "CaptureCompleteCallback(): Tracing complete...\n");
509 // Send off a status message to the client with:
512 // frames_capture (TBD)
514 ec = CaptureCompleteCallbackMsg(pFilename, cFramesCaptured, &message_size, &message);
517 syslog(VOGL_ERROR, "%s:%d %s Unable to send tracefile name (%s) back to client - OOM?\n", __FILE__, __LINE__, __func__, pFilename);
521 chec = g_clientChannelMgr->SendData(message_size, message);
522 if (network::EC_NONE != chec)
524 syslog(VOGL_ERROR, "%s:%d %s Unable to send tracefile name (%s) back to client - Network error.\n", __FILE__, __LINE__, __func__, pFilename);
534 int CaptureCompleteCallbackMsg(const char *pFilename, int cFramesCaptured, unsigned int *message_size, char **pmessage)
536 vogl::json_document cur_doc;
537 vogl::dynamic_string dst;
542 vogl::json_node &meta_node = cur_doc.get_root()->add_object("parameters");
544 meta_node.add_key_value("trace_file_name", pFilename);
545 meta_node.add_key_value("process_name", g_procname);
546 meta_node.add_key_value("frames_captured", cFramesCaptured);
548 cur_doc.serialize(dst);
550 cbBuff = dst.get_len() + 1 + sizeof(int);
551 pbBuff = (char *)malloc(cbBuff);
558 // First part of buffer is the command id
559 *((int *)pbBuff) = TRACE_CAPTURESTATUS;
560 strncpy((char *)(pbBuff+sizeof(int)), dst.get_ptr(), cbBuff - sizeof(int));
563 *message_size = cbBuff;
569 bool log_output_func(vogl::eConsoleMessageType /*type*/, const char *pMsg, void * /*pData*/)
572 send_status_to_client(pMsg);
578 void send_status_to_client(const char *message)
581 unsigned int cbBuff = 0;
584 // Prepare message to send
586 ec = ToClientMsgReq(g_procname, 0, message, &cbBuff, &pbBuff);
588 (void)g_clientChannelMgr->SendData(cbBuff, pbBuff);
596 void send_status_to_client(const char *message, int /*token*/)
598 // Ignoring token for a moment.
599 send_status_to_client(message);
604 #endif // VOGL_REMOTING