Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

838 lines
20 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. // Nasty headers!
  8. #include "MySqlDatabase.h"
  9. #include "tier1/strtools.h"
  10. #include "vmpi.h"
  11. #include "vmpi_dispatch.h"
  12. #include "mpi_stats.h"
  13. #include "cmdlib.h"
  14. #include "imysqlwrapper.h"
  15. #include "threadhelpers.h"
  16. #include "vmpi_tools_shared.h"
  17. #include "tier0/icommandline.h"
  18. /*
  19. -- MySQL code to create the databases, create the users, and set access privileges.
  20. -- You only need to ever run this once.
  21. create database vrad;
  22. use mysql;
  23. create user vrad_worker;
  24. create user vmpi_browser;
  25. -- This updates the "user" table, which is checked when someone tries to connect to the database.
  26. grant select,insert,update on vrad.* to vrad_worker;
  27. grant select on vrad.* to vmpi_browser;
  28. flush privileges;
  29. /*
  30. -- SQL code to (re)create the tables.
  31. -- Master generates a unique job ID (in job_master_start) and sends it to workers.
  32. -- Each worker (and the master) make a job_worker_start, link it to the primary job ID,
  33. -- get their own unique ID, which represents that process in that job.
  34. -- All JobWorkerID fields link to the JobWorkerID field in job_worker_start.
  35. -- NOTE: do a "use vrad" or "use vvis" first, depending on the DB you want to create.
  36. use vrad;
  37. drop table job_master_start;
  38. create table job_master_start (
  39. JobID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, index id( JobID, MachineName(5) ),
  40. BSPFilename TINYTEXT NOT NULL,
  41. StartTime TIMESTAMP NOT NULL,
  42. MachineName TEXT NOT NULL,
  43. RunningTimeMS INTEGER UNSIGNED NOT NULL,
  44. NumWorkers INTEGER UNSIGNED NOT NULL default 0
  45. );
  46. drop table job_master_end;
  47. create table job_master_end (
  48. JobID INTEGER UNSIGNED NOT NULL, PRIMARY KEY ( JobID ),
  49. NumWorkersConnected SMALLINT UNSIGNED NOT NULL,
  50. NumWorkersDisconnected SMALLINT UNSIGNED NOT NULL,
  51. ErrorText TEXT NOT NULL
  52. );
  53. drop table job_worker_start;
  54. create table job_worker_start (
  55. JobWorkerID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  56. index index_jobid( JobID ),
  57. index index_jobworkerid( JobWorkerID ),
  58. JobID INTEGER UNSIGNED NOT NULL, -- links to job_master_start::JobID
  59. IsMaster BOOL NOT NULL, -- Set to 1 if this "worker" is the master process.
  60. RunningTimeMS INTEGER UNSIGNED NOT NULL default 0,
  61. MachineName TEXT NOT NULL,
  62. WorkerState SMALLINT UNSIGNED NOT NULL default 0, -- 0 = disconnected, 1 = connected
  63. NumWorkUnits INTEGER UNSIGNED NOT NULL default 0, -- how many work units this worker has completed
  64. CurrentStage TINYTEXT NOT NULL, -- which compile stage is it on
  65. Thread0WU INTEGER NOT NULL default 0, -- which WU thread 0 is on
  66. Thread1WU INTEGER NOT NULL default 0, -- which WU thread 1 is on
  67. Thread2WU INTEGER NOT NULL default 0, -- which WU thread 2 is on
  68. Thread3WU INTEGER NOT NULL default 0 -- which WU thread 3 is on
  69. );
  70. drop table text_messages;
  71. create table text_messages (
  72. JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID, MessageIndex ),
  73. MessageIndex INTEGER UNSIGNED NOT NULL,
  74. Text TEXT NOT NULL
  75. );
  76. drop table graph_entry;
  77. create table graph_entry (
  78. JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID ),
  79. MSSinceJobStart INTEGER UNSIGNED NOT NULL,
  80. BytesSent INTEGER UNSIGNED NOT NULL,
  81. BytesReceived INTEGER UNSIGNED NOT NULL
  82. );
  83. drop table events;
  84. create table events (
  85. JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID ),
  86. Text TEXT NOT NULL
  87. );
  88. */
  89. // Stats set by the app.
  90. int g_nWorkersConnected = 0;
  91. int g_nWorkersDisconnected = 0;
  92. DWORD g_StatsStartTime;
  93. CMySqlDatabase *g_pDB = NULL;
  94. IMySQL *g_pSQL = NULL;
  95. CSysModule *g_hMySQLDLL = NULL;
  96. char g_BSPFilename[256];
  97. bool g_bMaster = false;
  98. unsigned long g_JobPrimaryID = 0; // This represents this job, but doesn't link to a particular machine.
  99. unsigned long g_JobWorkerID = 0; // A unique key in the DB that represents this machine in this job.
  100. char g_MachineName[MAX_COMPUTERNAME_LENGTH+1] = {0};
  101. unsigned long g_CurrentMessageIndex = 0;
  102. HANDLE g_hPerfThread = NULL;
  103. DWORD g_PerfThreadID = 0xFEFEFEFE;
  104. HANDLE g_hPerfThreadExitEvent = NULL;
  105. // These are set by the app and they go into the database.
  106. extern uint64 g_ThreadWUs[4];
  107. extern uint64 VMPI_GetNumWorkUnitsCompleted( int iProc );
  108. // ---------------------------------------------------------------------------------------------------- //
  109. // This is a helper class to build queries like the stream IO.
  110. // ---------------------------------------------------------------------------------------------------- //
  111. class CMySQLQuery
  112. {
  113. friend class CMySQL;
  114. public:
  115. // This is like a sprintf, but it will grow the string as necessary.
  116. void Format( const char *pFormat, ... );
  117. int Execute( IMySQL *pDB );
  118. private:
  119. CUtlVector<char> m_QueryText;
  120. };
  121. void CMySQLQuery::Format( const char *pFormat, ... )
  122. {
  123. #define QUERYTEXT_GROWSIZE 1024
  124. // This keeps growing the buffer and calling _vsnprintf until the buffer is
  125. // large enough to hold all the data.
  126. m_QueryText.SetSize( QUERYTEXT_GROWSIZE );
  127. while ( 1 )
  128. {
  129. va_list marker;
  130. va_start( marker, pFormat );
  131. int ret = _vsnprintf( m_QueryText.Base(), m_QueryText.Count(), pFormat, marker );
  132. va_end( marker );
  133. if ( ret < 0 )
  134. {
  135. m_QueryText.SetSize( m_QueryText.Count() + QUERYTEXT_GROWSIZE );
  136. }
  137. else
  138. {
  139. m_QueryText[ m_QueryText.Count() - 1 ] = 0;
  140. break;
  141. }
  142. }
  143. }
  144. int CMySQLQuery::Execute( IMySQL *pDB )
  145. {
  146. int ret = pDB->Execute( m_QueryText.Base() );
  147. m_QueryText.Purge();
  148. return ret;
  149. }
  150. // ---------------------------------------------------------------------------------------------------- //
  151. // This inserts the necessary backslashes in front of backslashes or quote characters.
  152. // ---------------------------------------------------------------------------------------------------- //
  153. char* FormatStringForSQL( const char *pText )
  154. {
  155. // First, count the quotes in the string. We need to put a backslash in front of each one.
  156. int nChars = 0;
  157. const char *pCur = pText;
  158. while ( *pCur != 0 )
  159. {
  160. if ( *pCur == '\"' || *pCur == '\\' )
  161. ++nChars;
  162. ++pCur;
  163. ++nChars;
  164. }
  165. pCur = pText;
  166. char *pRetVal = new char[nChars+1];
  167. for ( int i=0; i < nChars; )
  168. {
  169. if ( *pCur == '\"' || *pCur == '\\' )
  170. pRetVal[i++] = '\\';
  171. pRetVal[i++] = *pCur;
  172. ++pCur;
  173. }
  174. pRetVal[nChars] = 0;
  175. return pRetVal;
  176. }
  177. // -------------------------------------------------------------------------------- //
  178. // Commands to add data to the database.
  179. // -------------------------------------------------------------------------------- //
  180. class CSQLDBCommandBase : public ISQLDBCommand
  181. {
  182. public:
  183. virtual ~CSQLDBCommandBase()
  184. {
  185. }
  186. virtual void deleteThis()
  187. {
  188. delete this;
  189. }
  190. };
  191. class CSQLDBCommand_WorkerStats : public CSQLDBCommandBase
  192. {
  193. public:
  194. virtual int RunCommand()
  195. {
  196. int nCurConnections = VMPI_GetCurrentNumberOfConnections();
  197. // Update the NumWorkers entry.
  198. char query[2048];
  199. Q_snprintf( query, sizeof( query ), "update job_master_start set NumWorkers=%d where JobID=%lu",
  200. nCurConnections,
  201. g_JobPrimaryID );
  202. g_pSQL->Execute( query );
  203. // Update the job_master_worker_stats stuff.
  204. for ( int i=1; i < nCurConnections; i++ )
  205. {
  206. unsigned long jobWorkerID = VMPI_GetJobWorkerID( i );
  207. if ( jobWorkerID != 0xFFFFFFFF )
  208. {
  209. Q_snprintf( query, sizeof( query ), "update "
  210. "job_worker_start set WorkerState=%d, NumWorkUnits=%d where JobWorkerID=%lu",
  211. VMPI_IsProcConnected( i ),
  212. (int) VMPI_GetNumWorkUnitsCompleted( i ),
  213. VMPI_GetJobWorkerID( i )
  214. );
  215. g_pSQL->Execute( query );
  216. }
  217. }
  218. return 1;
  219. }
  220. };
  221. class CSQLDBCommand_JobMasterEnd : public CSQLDBCommandBase
  222. {
  223. public:
  224. virtual int RunCommand()
  225. {
  226. CMySQLQuery query;
  227. query.Format( "insert into job_master_end values ( %lu, %d, %d, \"no errors\" )", g_JobPrimaryID, g_nWorkersConnected, g_nWorkersDisconnected );
  228. query.Execute( g_pSQL );
  229. // Now set RunningTimeMS.
  230. unsigned long runningTimeMS = GetTickCount() - g_StatsStartTime;
  231. query.Format( "update job_master_start set RunningTimeMS=%lu where JobID=%lu", runningTimeMS, g_JobPrimaryID );
  232. query.Execute( g_pSQL );
  233. return 1;
  234. }
  235. };
  236. void UpdateJobWorkerRunningTime()
  237. {
  238. unsigned long runningTimeMS = GetTickCount() - g_StatsStartTime;
  239. char curStage[256];
  240. VMPI_GetCurrentStage( curStage, sizeof( curStage ) );
  241. CMySQLQuery query;
  242. query.Format( "update job_worker_start set RunningTimeMS=%lu, CurrentStage=\"%s\", "
  243. "Thread0WU=%d, Thread1WU=%d, Thread2WU=%d, Thread3WU=%d where JobWorkerID=%lu",
  244. runningTimeMS,
  245. curStage,
  246. (int) g_ThreadWUs[0],
  247. (int) g_ThreadWUs[1],
  248. (int) g_ThreadWUs[2],
  249. (int) g_ThreadWUs[3],
  250. g_JobWorkerID );
  251. query.Execute( g_pSQL );
  252. }
  253. class CSQLDBCommand_GraphEntry : public CSQLDBCommandBase
  254. {
  255. public:
  256. CSQLDBCommand_GraphEntry( DWORD msTime, DWORD nBytesSent, DWORD nBytesReceived )
  257. {
  258. m_msTime = msTime;
  259. m_nBytesSent = nBytesSent;
  260. m_nBytesReceived = nBytesReceived;
  261. }
  262. virtual int RunCommand()
  263. {
  264. CMySQLQuery query;
  265. query.Format( "insert into graph_entry (JobWorkerID, MSSinceJobStart, BytesSent, BytesReceived) "
  266. "values ( %lu, %lu, %lu, %lu )",
  267. g_JobWorkerID,
  268. m_msTime,
  269. m_nBytesSent,
  270. m_nBytesReceived );
  271. query.Execute( g_pSQL );
  272. UpdateJobWorkerRunningTime();
  273. ++g_CurrentMessageIndex;
  274. return 1;
  275. }
  276. DWORD m_nBytesSent;
  277. DWORD m_nBytesReceived;
  278. DWORD m_msTime;
  279. };
  280. class CSQLDBCommand_TextMessage : public CSQLDBCommandBase
  281. {
  282. public:
  283. CSQLDBCommand_TextMessage( const char *pText )
  284. {
  285. m_pText = FormatStringForSQL( pText );
  286. }
  287. virtual ~CSQLDBCommand_TextMessage()
  288. {
  289. delete [] m_pText;
  290. }
  291. virtual int RunCommand()
  292. {
  293. CMySQLQuery query;
  294. query.Format( "insert into text_messages (JobWorkerID, MessageIndex, Text) values ( %lu, %lu, \"%s\" )", g_JobWorkerID, g_CurrentMessageIndex, m_pText );
  295. query.Execute( g_pSQL );
  296. ++g_CurrentMessageIndex;
  297. return 1;
  298. }
  299. char *m_pText;
  300. };
  301. // -------------------------------------------------------------------------------- //
  302. // Internal helpers.
  303. // -------------------------------------------------------------------------------- //
  304. // This is the spew output before it has connected to the MySQL database.
  305. CCriticalSection g_SpewTextCS;
  306. CUtlVector<char> g_SpewText( 1024 );
  307. void VMPI_Stats_SpewHook( const char *pMsg )
  308. {
  309. CCriticalSectionLock csLock( &g_SpewTextCS );
  310. csLock.Lock();
  311. // Queue the text up so we can send it to the DB right away when we connect.
  312. g_SpewText.AddMultipleToTail( strlen( pMsg ), pMsg );
  313. }
  314. void PerfThread_SendSpewText()
  315. {
  316. // Send the spew text to the database.
  317. CCriticalSectionLock csLock( &g_SpewTextCS );
  318. csLock.Lock();
  319. if ( g_SpewText.Count() > 0 )
  320. {
  321. g_SpewText.AddToTail( 0 );
  322. if ( g_bMPI_StatsTextOutput )
  323. {
  324. g_pDB->AddCommandToQueue( new CSQLDBCommand_TextMessage( g_SpewText.Base() ), NULL );
  325. }
  326. else
  327. {
  328. // Just show one message in the vmpi_job_watch window to let them know that they need
  329. // to use a command line option to get the output.
  330. static bool bFirst = true;
  331. if ( bFirst )
  332. {
  333. char msg[512];
  334. V_snprintf( msg, sizeof( msg ), "%s not enabled", VMPI_GetParamString( mpi_Stats_TextOutput ) );
  335. bFirst = false;
  336. g_pDB->AddCommandToQueue( new CSQLDBCommand_TextMessage( msg ), NULL );
  337. }
  338. }
  339. g_SpewText.RemoveAll();
  340. }
  341. csLock.Unlock();
  342. }
  343. void PerfThread_AddGraphEntry( DWORD startTicks, DWORD &lastSent, DWORD &lastReceived )
  344. {
  345. // Send the graph entry with data transmission info.
  346. DWORD curSent = g_nBytesSent + g_nMulticastBytesSent;
  347. DWORD curReceived = g_nBytesReceived + g_nMulticastBytesReceived;
  348. g_pDB->AddCommandToQueue(
  349. new CSQLDBCommand_GraphEntry(
  350. GetTickCount() - startTicks,
  351. curSent - lastSent,
  352. curReceived - lastReceived ),
  353. NULL );
  354. lastSent = curSent;
  355. lastReceived = curReceived;
  356. }
  357. // This function adds a graph_entry into the database periodically.
  358. DWORD WINAPI PerfThreadFn( LPVOID pParameter )
  359. {
  360. DWORD lastSent = 0;
  361. DWORD lastReceived = 0;
  362. DWORD startTicks = GetTickCount();
  363. while ( WaitForSingleObject( g_hPerfThreadExitEvent, 1000 ) != WAIT_OBJECT_0 )
  364. {
  365. PerfThread_AddGraphEntry( startTicks, lastSent, lastReceived );
  366. // Send updates for text output.
  367. PerfThread_SendSpewText();
  368. // If we're the master, update all the worker stats.
  369. if ( g_bMaster )
  370. {
  371. g_pDB->AddCommandToQueue(
  372. new CSQLDBCommand_WorkerStats,
  373. NULL );
  374. }
  375. }
  376. // Add the remaining text and one last graph entry (which will include the current stage info).
  377. PerfThread_SendSpewText();
  378. PerfThread_AddGraphEntry( startTicks, lastSent, lastReceived );
  379. SetEvent( g_hPerfThreadExitEvent );
  380. return 0;
  381. }
  382. // -------------------------------------------------------------------------------- //
  383. // VMPI_Stats interface.
  384. // -------------------------------------------------------------------------------- //
  385. void VMPI_Stats_InstallSpewHook()
  386. {
  387. InstallExtraSpewHook( VMPI_Stats_SpewHook );
  388. }
  389. void UnloadMySQLWrapper()
  390. {
  391. if ( g_hMySQLDLL )
  392. {
  393. if ( g_pSQL )
  394. {
  395. g_pSQL->Release();
  396. g_pSQL = NULL;
  397. }
  398. Sys_UnloadModule( g_hMySQLDLL );
  399. g_hMySQLDLL = NULL;
  400. }
  401. }
  402. bool LoadMySQLWrapper(
  403. const char *pHostName,
  404. const char *pDBName,
  405. const char *pUserName
  406. )
  407. {
  408. UnloadMySQLWrapper();
  409. // Load the DLL and the interface.
  410. if ( !Sys_LoadInterface( "mysql_wrapper", MYSQL_WRAPPER_VERSION_NAME, &g_hMySQLDLL, (void**)&g_pSQL ) )
  411. return false;
  412. // Try to init the database.
  413. if ( !g_pSQL->InitMySQL( pDBName, pHostName, pUserName ) )
  414. {
  415. UnloadMySQLWrapper();
  416. return false;
  417. }
  418. return true;
  419. }
  420. bool VMPI_Stats_Init_Master(
  421. const char *pHostName,
  422. const char *pDBName,
  423. const char *pUserName,
  424. const char *pBSPFilename,
  425. unsigned long *pDBJobID )
  426. {
  427. Assert( !g_pDB );
  428. g_bMaster = true;
  429. // Connect the database.
  430. g_pDB = new CMySqlDatabase;
  431. if ( !g_pDB || !g_pDB->Initialize() || !LoadMySQLWrapper( pHostName, pDBName, pUserName ) )
  432. {
  433. delete g_pDB;
  434. g_pDB = NULL;
  435. return false;
  436. }
  437. DWORD size = sizeof( g_MachineName );
  438. GetComputerName( g_MachineName, &size );
  439. // Create the job_master_start row.
  440. Q_FileBase( pBSPFilename, g_BSPFilename, sizeof( g_BSPFilename ) );
  441. g_JobPrimaryID = 0;
  442. CMySQLQuery query;
  443. query.Format( "insert into job_master_start ( BSPFilename, StartTime, MachineName, RunningTimeMS ) values ( \"%s\", null, \"%s\", %lu )", g_BSPFilename, g_MachineName, RUNNINGTIME_MS_SENTINEL );
  444. query.Execute( g_pSQL );
  445. g_JobPrimaryID = g_pSQL->InsertID();
  446. if ( g_JobPrimaryID == 0 )
  447. {
  448. delete g_pDB;
  449. g_pDB = NULL;
  450. return false;
  451. }
  452. // Now init the worker portion.
  453. *pDBJobID = g_JobPrimaryID;
  454. return VMPI_Stats_Init_Worker( NULL, NULL, NULL, g_JobPrimaryID );
  455. }
  456. bool VMPI_Stats_Init_Worker( const char *pHostName, const char *pDBName, const char *pUserName, unsigned long DBJobID )
  457. {
  458. g_StatsStartTime = GetTickCount();
  459. // If pDBServerName is null, then we're the master and we just want to make the job_worker_start entry.
  460. if ( pHostName )
  461. {
  462. Assert( !g_pDB );
  463. // Connect the database.
  464. g_pDB = new CMySqlDatabase;
  465. if ( !g_pDB || !g_pDB->Initialize() || !LoadMySQLWrapper( pHostName, pDBName, pUserName ) )
  466. {
  467. delete g_pDB;
  468. g_pDB = NULL;
  469. return false;
  470. }
  471. // Get our machine name to store in the database.
  472. DWORD size = sizeof( g_MachineName );
  473. GetComputerName( g_MachineName, &size );
  474. }
  475. g_JobPrimaryID = DBJobID;
  476. g_JobWorkerID = 0;
  477. CMySQLQuery query;
  478. query.Format( "insert into job_worker_start ( JobID, CurrentStage, IsMaster, MachineName ) values ( %lu, \"none\", %d, \"%s\" )",
  479. g_JobPrimaryID, g_bMaster, g_MachineName );
  480. query.Execute( g_pSQL );
  481. g_JobWorkerID = g_pSQL->InsertID();
  482. if ( g_JobWorkerID == 0 )
  483. {
  484. delete g_pDB;
  485. g_pDB = NULL;
  486. return false;
  487. }
  488. // Now create a thread that samples perf data and stores it in the database.
  489. g_hPerfThreadExitEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
  490. g_hPerfThread = CreateThread(
  491. NULL,
  492. 0,
  493. PerfThreadFn,
  494. NULL,
  495. 0,
  496. &g_PerfThreadID );
  497. return true;
  498. }
  499. void VMPI_Stats_Term()
  500. {
  501. if ( !g_pDB )
  502. return;
  503. // Stop the thread.
  504. SetEvent( g_hPerfThreadExitEvent );
  505. WaitForSingleObject( g_hPerfThread, INFINITE );
  506. CloseHandle( g_hPerfThreadExitEvent );
  507. g_hPerfThreadExitEvent = NULL;
  508. CloseHandle( g_hPerfThread );
  509. g_hPerfThread = NULL;
  510. if ( g_bMaster )
  511. {
  512. // (Write a job_master_end entry here).
  513. g_pDB->AddCommandToQueue( new CSQLDBCommand_JobMasterEnd, NULL );
  514. }
  515. // Wait for up to a second for the DB to finish writing its data.
  516. DWORD startTime = GetTickCount();
  517. while ( GetTickCount() - startTime < 1000 )
  518. {
  519. if ( g_pDB->QueriesInOutQueue() == 0 )
  520. break;
  521. }
  522. delete g_pDB;
  523. g_pDB = NULL;
  524. UnloadMySQLWrapper();
  525. }
  526. static bool ReadStringFromFile( FILE *fp, char *pStr, int strSize )
  527. {
  528. int i=0;
  529. for ( i; i < strSize-2; i++ )
  530. {
  531. if ( fread( &pStr[i], 1, 1, fp ) != 1 ||
  532. pStr[i] == '\n' )
  533. {
  534. break;
  535. }
  536. }
  537. pStr[i] = 0;
  538. return i != 0;
  539. }
  540. // This looks for pDBInfoFilename in the same path as pBaseExeFilename.
  541. // The file has 3 lines: machine name (with database), database name, username
  542. void GetDBInfo( const char *pDBInfoFilename, CDBInfo *pInfo )
  543. {
  544. char baseExeFilename[512];
  545. if ( !GetModuleFileName( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) )
  546. Error( "GetModuleFileName failed." );
  547. // Look for the info file in the same directory as the exe.
  548. char dbInfoFilename[512];
  549. Q_strncpy( dbInfoFilename, baseExeFilename, sizeof( dbInfoFilename ) );
  550. Q_StripFilename( dbInfoFilename );
  551. if ( dbInfoFilename[0] == 0 )
  552. Q_strncpy( dbInfoFilename, ".", sizeof( dbInfoFilename ) );
  553. Q_strncat( dbInfoFilename, "/", sizeof( dbInfoFilename ), COPY_ALL_CHARACTERS );
  554. Q_strncat( dbInfoFilename, pDBInfoFilename, sizeof( dbInfoFilename ), COPY_ALL_CHARACTERS );
  555. FILE *fp = fopen( dbInfoFilename, "rt" );
  556. if ( !fp )
  557. {
  558. Error( "Can't open %s for database info.\n", dbInfoFilename );
  559. }
  560. if ( !ReadStringFromFile( fp, pInfo->m_HostName, sizeof( pInfo->m_HostName ) ) ||
  561. !ReadStringFromFile( fp, pInfo->m_DBName, sizeof( pInfo->m_DBName ) ) ||
  562. !ReadStringFromFile( fp, pInfo->m_UserName, sizeof( pInfo->m_UserName ) )
  563. )
  564. {
  565. Error( "%s is not a valid database info file.\n", dbInfoFilename );
  566. }
  567. fclose( fp );
  568. }
  569. void RunJobWatchApp( char *pCmdLine )
  570. {
  571. STARTUPINFO si;
  572. memset( &si, 0, sizeof( si ) );
  573. si.cb = sizeof( si );
  574. PROCESS_INFORMATION pi;
  575. memset( &pi, 0, sizeof( pi ) );
  576. // Working directory should be the same as our exe's directory.
  577. char dirName[512];
  578. if ( GetModuleFileName( NULL, dirName, sizeof( dirName ) ) != 0 )
  579. {
  580. char *s1 = V_strrchr( dirName, '\\' );
  581. char *s2 = V_strrchr( dirName, '/' );
  582. if ( s1 || s2 )
  583. {
  584. // Get rid of the last slash.
  585. s1 = max( s1, s2 );
  586. s1[0] = 0;
  587. if ( !CreateProcess(
  588. NULL,
  589. pCmdLine,
  590. NULL, // security
  591. NULL,
  592. TRUE,
  593. 0, // flags
  594. NULL, // environment
  595. dirName, // current directory
  596. &si,
  597. &pi ) )
  598. {
  599. Warning( "%s - error launching '%s'\n", VMPI_GetParamString( mpi_Job_Watch ), pCmdLine );
  600. }
  601. }
  602. }
  603. }
  604. void StatsDB_InitStatsDatabase(
  605. int argc,
  606. char **argv,
  607. const char *pDBInfoFilename )
  608. {
  609. // Did they disable the stats database?
  610. if ( !g_bMPI_Stats && !VMPI_IsParamUsed( mpi_Job_Watch ) )
  611. return;
  612. unsigned long jobPrimaryID;
  613. // Now open the DB.
  614. if ( g_bMPIMaster )
  615. {
  616. CDBInfo dbInfo;
  617. GetDBInfo( pDBInfoFilename, &dbInfo );
  618. if ( !VMPI_Stats_Init_Master( dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, argv[argc-1], &jobPrimaryID ) )
  619. {
  620. Warning( "VMPI_Stats_Init_Master( %s, %s, %s ) failed.\n", dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName );
  621. // Tell the workers not to use stats.
  622. dbInfo.m_HostName[0] = 0;
  623. }
  624. char cmdLine[2048];
  625. Q_snprintf( cmdLine, sizeof( cmdLine ), "vmpi_job_watch -JobID %d", jobPrimaryID );
  626. Msg( "\nTo watch this job, run this command line:\n%s\n\n", cmdLine );
  627. if ( VMPI_IsParamUsed( mpi_Job_Watch ) )
  628. {
  629. // Convenience thing to automatically launch the job watch for this job.
  630. RunJobWatchApp( cmdLine );
  631. }
  632. // Send the database info to all the workers.
  633. SendDBInfo( &dbInfo, jobPrimaryID );
  634. }
  635. else
  636. {
  637. // Wait to get DB info so we can connect to the MySQL database.
  638. CDBInfo dbInfo;
  639. unsigned long jobPrimaryID;
  640. RecvDBInfo( &dbInfo, &jobPrimaryID );
  641. if ( dbInfo.m_HostName[0] != 0 )
  642. {
  643. if ( !VMPI_Stats_Init_Worker( dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, jobPrimaryID ) )
  644. Error( "VMPI_Stats_Init_Worker( %s, %s, %s, %d ) failed.\n", dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, jobPrimaryID );
  645. }
  646. }
  647. }
  648. unsigned long StatsDB_GetUniqueJobID()
  649. {
  650. return g_JobPrimaryID;
  651. }
  652. unsigned long VMPI_Stats_GetJobWorkerID()
  653. {
  654. return g_JobWorkerID;
  655. }