//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // JobWatchDlg.cpp : implementation file // #include "stdafx.h" #include "JobWatchDlg.h" #include "tier1/strtools.h" #include "consolewnd.h" #include "vmpi_browser_helpers.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define IMPLEMENT_SORT_NUMBER_FN( FnName, VarName ) \ static int CALLBACK FnName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) \ { \ CWorkerInfo *pInfo1 = (CWorkerInfo*)iItem1; \ CWorkerInfo *pInfo2 = (CWorkerInfo*)iItem2; \ return pInfo1->VarName > pInfo2->VarName; \ } static int CALLBACK SortByName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CWorkerInfo *pInfo1 = (CWorkerInfo*)iItem1; CWorkerInfo *pInfo2 = (CWorkerInfo*)iItem2; return strcmp( pInfo1->m_ComputerName, pInfo2->m_ComputerName ); } static int CALLBACK SortByCurrentStage( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) { CWorkerInfo *pInfo1 = (CWorkerInfo*)iItem1; CWorkerInfo *pInfo2 = (CWorkerInfo*)iItem2; return strcmp( pInfo1->m_CurrentStage, pInfo2->m_CurrentStage ); } IMPLEMENT_SORT_NUMBER_FN( SortByConnected, m_bConnected ) IMPLEMENT_SORT_NUMBER_FN( SortByWorkUnitsDone, m_nWorkUnitsDone ); IMPLEMENT_SORT_NUMBER_FN( SortByRunningTime, m_RunningTimeMS ); IMPLEMENT_SORT_NUMBER_FN( SortByThread0WU, m_ThreadWUs[0] ); IMPLEMENT_SORT_NUMBER_FN( SortByThread1WU, m_ThreadWUs[1] ); IMPLEMENT_SORT_NUMBER_FN( SortByThread2WU, m_ThreadWUs[2] ); IMPLEMENT_SORT_NUMBER_FN( SortByThread3WU, m_ThreadWUs[3] ); typedef int (CALLBACK *ServicesSortFn)( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ); struct { char *pText; int width; ServicesSortFn sortFn; } g_ColumnInfos[] = { {"Computer Name", 150, SortByName}, {"Connected", 70, SortByConnected}, {"Work Units Done", 100, SortByWorkUnitsDone}, {"Running Time", 80, SortByRunningTime}, {"Current Stage", 180, SortByCurrentStage}, {"Thread 0", 70, SortByThread0WU}, {"Thread 1", 70, SortByThread1WU}, {"Thread 2", 70, SortByThread2WU}, {"Thread 3", 70, SortByThread3WU} }; #define COLUMN_COMPUTER_NAME 0 #define COLUMN_CONNECTED 1 #define COLUMN_WORK_UNITS_DONE 2 #define COLUMN_RUNNING_TIME 3 #define COLUMN_CURRENT_STAGE 4 #define COLUMN_THREAD0_WU 5 #define COLUMN_THREAD1_WU 6 #define COLUMN_THREAD2_WU 7 #define COLUMN_THREAD3_WU 8 int g_iSortColumn = 0; ///////////////////////////////////////////////////////////////////////////// // CJobWatchDlg dialog CJobWatchDlg::CJobWatchDlg(CWnd* pParent /*=NULL*/) : CIdleDialog(CJobWatchDlg::IDD, pParent) { m_CurMessageIndex = 0; m_CurGraphTime = 0; m_pSQL = NULL; m_hMySQLDLL = NULL; m_CurWorkerTextToken = 0; m_LastQueryTime = 0; //{{AFX_DATA_INIT(CJobWatchDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } CJobWatchDlg::~CJobWatchDlg() { if ( m_pSQL ) { m_pSQL->Release(); } if ( m_hMySQLDLL ) { Sys_UnloadModule( m_hMySQLDLL ); } } void CJobWatchDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CJobWatchDlg) DDX_Control(pDX, IDC_WORKERS, m_Workers); DDX_Control(pDX, IDC_TEXTOUTPUT, m_TextOutput); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CJobWatchDlg, CIdleDialog) //{{AFX_MSG_MAP(CJobWatchDlg) ON_LBN_SELCHANGE(IDC_WORKERS, OnSelChangeWorkers) ON_WM_SIZE() ON_NOTIFY(LVN_ODSTATECHANGED, IDC_WORKERS, OnOdstatechangedWorkers) ON_NOTIFY(LVN_ITEMCHANGED, IDC_WORKERS, OnItemchangedWorkers) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CJobWatchDlg message handlers const char* FindArg( const char *pArgName, const char *pDefault="" ) { for ( int i=1; i < __argc; i++ ) { if ( Q_stricmp( pArgName, __argv[i] ) == 0 ) { if ( (i+1) < __argc ) return __argv[i+1]; else return pDefault; } } return NULL; } bool ReadStringFromFile( FILE *fp, char *pStr, int strSize ) { int i=0; for ( i; i < strSize-2; i++ ) { if ( fread( &pStr[i], 1, 1, fp ) != 1 || pStr[i] == '\n' ) { break; } } pStr[i] = 0; return i != 0; } BOOL CJobWatchDlg::OnInitDialog() { CDialog::OnInitDialog(); m_Workers.SetExtendedStyle( LVS_EX_FULLROWSELECT ); // Setup the headers. for ( int i=0; i < ARRAYSIZE( g_ColumnInfos ); i++ ) { m_Workers.InsertColumn( i, g_ColumnInfos[i].pText, LVCFMT_LEFT, g_ColumnInfos[i].width, i ); } m_GraphControl.SubclassDlgItem( IDC_GRAPH_AREA, this ); CString str; // Get all our startup info from the command line. const char *pJobID = FindArg( "-JobID", NULL ); const char *pDBName = FindArg( "-dbname", NULL ); const char *pHostName = FindArg( "-hostname", NULL ); const char *pUserName = FindArg( "-username", NULL ); if ( !pJobID ) { str.Format( "Missing a command line parameter (-JobID or -dbname or -hostname or -username)" ); MessageBox( str, "Error", MB_OK ); EndDialog( 1 ); return FALSE; } char hostName[512], dbName[512], userName[512]; if ( !pDBName || !pHostName || !pUserName ) { char errString[512]; // If they don't specify the DB info, get it from where const char *pFilename = "dbinfo_job_search.txt"; FILE *fp = fopen( pFilename, "rt" ); if ( !fp ) { Q_snprintf( errString, sizeof( errString ), "Can't open '%s' for database info.", pFilename ); MessageBox( errString, "Error", MB_OK ); EndDialog( 0 ); return FALSE; } if ( !ReadStringFromFile( fp, hostName, sizeof( hostName ) ) || !ReadStringFromFile( fp, dbName, sizeof( dbName ) ) || !ReadStringFromFile( fp, userName, sizeof( userName ) ) ) { fclose( fp ); Q_snprintf( errString, sizeof( errString ), "'%s' has invalid format.", pFilename ); MessageBox( errString, "Error", MB_OK ); EndDialog( 0 ); return FALSE; } pDBName = dbName; pHostName = hostName; pUserName = userName; fclose( fp ); } m_JobID = atoi( pJobID ); // Get the mysql interface. IMySQL *pSQL; if ( !Sys_LoadInterface( "mysql_wrapper", MYSQL_WRAPPER_VERSION_NAME, &m_hMySQLDLL, (void**)&pSQL ) ) return false; if ( !pSQL->InitMySQL( pDBName, pHostName, pUserName ) ) { pSQL->Release(); str.Format( "Can't init MYSQL db (db = '%s', host = '%s', user = '%s')", pDBName, pHostName, pUserName ); MessageBox( str, "Error", MB_OK ); EndDialog( 0 ); return FALSE; } m_pSQL = CreateMySQLAsync( pSQL ); if ( !m_pSQL ) { pSQL->Release(); str.Format( "Can't create IMySQLAsync" ); MessageBox( str, "Error", MB_OK ); EndDialog( 0 ); return FALSE; } memset( m_bQueriesInProgress, 0, sizeof( m_bQueriesInProgress ) ); // (Init the idle processor so we can update text and graphs). StartIdleProcessing( 100 ); // Fill in the command line control. char cmdLine[2048]; Q_snprintf( cmdLine, sizeof( cmdLine ), "vmpi_job_watch -JobID %s -hostname %s -dbname %s -username %s", pJobID, pHostName, pDBName, pUserName ); SetDlgItemText( IDC_COMMAND_LINE, cmdLine ); // Setup anchors. m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_WORKERS_PANEL ), ANCHOR_LEFT, ANCHOR_TOP, ANCHOR_WIDTH_PERCENT, ANCHOR_HEIGHT_PERCENT ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_WORKERS ), ANCHOR_LEFT, ANCHOR_TOP, ANCHOR_WIDTH_PERCENT, ANCHOR_HEIGHT_PERCENT ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_TEXT_OUTPUT_PANEL ), ANCHOR_LEFT, ANCHOR_HEIGHT_PERCENT, ANCHOR_WIDTH_PERCENT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_TEXTOUTPUT ), ANCHOR_LEFT, ANCHOR_HEIGHT_PERCENT, ANCHOR_WIDTH_PERCENT, ANCHOR_BOTTOM ); m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_GRAPHS_PANEL ), ANCHOR_WIDTH_PERCENT, ANCHOR_TOP, ANCHOR_RIGHT, ANCHOR_HEIGHT_PERCENT ); m_AnchorMgr.AddAnchor( this, &m_GraphControl, ANCHOR_WIDTH_PERCENT, ANCHOR_TOP, ANCHOR_RIGHT, ANCHOR_HEIGHT_PERCENT ); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } CWorkerInfo* CJobWatchDlg::FindWorkerByID( unsigned long jobWorkerID ) { int nIndex = -1; while ( ( nIndex = m_Workers.GetNextItem( nIndex, LVNI_ALL ) ) != -1 ) { CWorkerInfo *pInfo = (CWorkerInfo*)m_Workers.GetItemData( nIndex ); if ( pInfo->m_JobWorkerID == jobWorkerID ) return pInfo; } return NULL; } CWorkerInfo* CJobWatchDlg::FindWorkerByMachineName( const char *pMachineName ) { int nIndex = -1; while ( ( nIndex = m_Workers.GetNextItem( nIndex, LVNI_ALL ) ) != -1 ) { CWorkerInfo *pInfo = (CWorkerInfo*)m_Workers.GetItemData( nIndex ); if ( Q_stricmp( pInfo->m_ComputerName, pMachineName ) == 0 ) return pInfo; } return NULL; } void CJobWatchDlg::SetWorkerListItemInt( int nIndex, int iColumn, int value ) { char str[512]; Q_snprintf( str, sizeof( str ), "%d", value ); m_Workers.SetItemText( nIndex, iColumn, str ); } void CJobWatchDlg::UpdateWorkersList() { int nIndex = -1; while ( ( nIndex = m_Workers.GetNextItem( nIndex, LVNI_ALL ) ) != -1 ) { CWorkerInfo *pInfo = (CWorkerInfo*)m_Workers.GetItemData( nIndex ); char *pConnectedStr = pInfo->m_bConnected ? "yes" : "no"; m_Workers.SetItemText( nIndex, COLUMN_CONNECTED, pConnectedStr ); SetWorkerListItemInt( nIndex, COLUMN_WORK_UNITS_DONE, pInfo->m_nWorkUnitsDone ); char timeStr[1024]; FormatTimeString( pInfo->m_RunningTimeMS / 1000, timeStr, sizeof( timeStr ) ); m_Workers.SetItemText( nIndex, COLUMN_RUNNING_TIME, timeStr ); // Current stage. SetWorkerListItemInt( nIndex, COLUMN_WORK_UNITS_DONE, pInfo->m_nWorkUnitsDone ); m_Workers.SetItemText( nIndex, COLUMN_CURRENT_STAGE, pInfo->m_CurrentStage ); SetWorkerListItemInt( nIndex, COLUMN_THREAD0_WU, pInfo->m_ThreadWUs[0] ); SetWorkerListItemInt( nIndex, COLUMN_THREAD1_WU, pInfo->m_ThreadWUs[1] ); SetWorkerListItemInt( nIndex, COLUMN_THREAD2_WU, pInfo->m_ThreadWUs[2] ); SetWorkerListItemInt( nIndex, COLUMN_THREAD3_WU, pInfo->m_ThreadWUs[3] ); } ResortItems(); } bool CJobWatchDlg::GetCurJobWorkerID( unsigned long &id ) { POSITION pos = m_Workers.GetFirstSelectedItemPosition(); if ( !pos ) return false; int index = m_Workers.GetNextSelectedItem( pos ); CWorkerInfo *pInfo = (CWorkerInfo*)m_Workers.GetItemData( index ); id = pInfo->m_JobWorkerID; return true; } void CJobWatchDlg::OnSelChangeWorkers() { // Clear the text output and invalidate any old queries for text. int nLen = m_TextOutput.SendMessage( EM_GETLIMITTEXT, 0, 0 ); m_TextOutput.SendMessage( EM_SETSEL, 0, nLen ); m_TextOutput.SendMessage( EM_REPLACESEL, FALSE, (LPARAM)"" ); m_CurMessageIndex = 0; m_CurWorkerTextToken++; m_LastQueryTime = 0; // force a query. m_GraphControl.Clear(); m_CurGraphTime = -1; } void CJobWatchDlg::ResortItems() { m_Workers.SortItems( g_ColumnInfos[g_iSortColumn].sortFn, (LPARAM)this ); } void CJobWatchDlg::OnIdle() { // Issue any queries that we need to. DWORD curTime = GetTickCount(); if ( curTime - m_LastQueryTime >= 1000 ) { m_LastQueryTime = curTime; char query[2048]; unsigned long jobWorkerID; bool bJobWorkerIDValid = GetCurJobWorkerID( jobWorkerID ); if ( !m_bQueriesInProgress[QUERY_TEXT] && bJobWorkerIDValid ) { Q_snprintf( query, sizeof( query ), "select * from text_messages where JobWorkerID=%lu and MessageIndex >= %lu", jobWorkerID, m_CurMessageIndex ); m_pSQL->Execute( query, (void*)(QUERY_TEXT | (m_CurWorkerTextToken << 16)) ); m_bQueriesInProgress[QUERY_TEXT] = true; } if ( !m_bQueriesInProgress[QUERY_GRAPH] && bJobWorkerIDValid ) { Q_snprintf( query, sizeof( query ), "select * from graph_entry where JobWorkerID=%lu", jobWorkerID ); m_pSQL->Execute( query, (void*)QUERY_GRAPH ); m_bQueriesInProgress[QUERY_GRAPH] = true; } if ( !m_bQueriesInProgress[QUERY_WORKER_STATS] ) { Q_snprintf( query, sizeof( query ), "select JobWorkerID, WorkerState, NumWorkUnits, " "RunningTimeMS, CurrentStage, Thread0WU, Thread1WU, Thread2WU, Thread3WU, IsMaster, MachineName " " from job_worker_start where JobID=%lu", m_JobID ); m_pSQL->Execute( query, (void*)QUERY_WORKER_STATS ); m_bQueriesInProgress[QUERY_WORKER_STATS] = true; } } // Pickup query results. CQueryResults results; while ( m_pSQL->GetNextResults( results ) && results.m_pResults ) { int iQueryID = ((int)results.m_pUserData) & 0xFFFF; int iExtraData = ((int)results.m_pUserData) >> 16; if ( results.m_pResults ) { if ( iQueryID == QUERY_TEXT ) { if ( iExtraData == m_CurWorkerTextToken ) { ProcessQueryResults_Text( results.m_pResults ); } } else if ( iQueryID == QUERY_GRAPH ) { ProcessQueryResults_Graph( results.m_pResults ); } else if ( iQueryID == QUERY_WORKER_STATS ) { ProcessQueryResults_WorkerStats( results.m_pResults ); } results.m_pResults->Release(); } m_bQueriesInProgress[iQueryID] = false; } } void CJobWatchDlg::ProcessQueryResults_WorkerStats( IMySQLRowSet *pSet ) { bool bChange = false; while ( pSet->NextRow() ) { int iColumn = 0; int workerID = pSet->GetColumnValue_Int( iColumn++ ); int workerState = pSet->GetColumnValue_Int( iColumn++ ); int nWorkUnits = pSet->GetColumnValue_Int( iColumn++ ); unsigned long runningTimeMS = pSet->GetColumnValue_Int( iColumn++ ); const char *pCurrentStage = pSet->GetColumnValue_String( iColumn++ ); int iThread0WU = pSet->GetColumnValue_Int( iColumn++ ); int iThread1WU = pSet->GetColumnValue_Int( iColumn++ ); int iThread2WU = pSet->GetColumnValue_Int( iColumn++ ); int iThread3WU = pSet->GetColumnValue_Int( iColumn++ ); int bIsMaster = pSet->GetColumnValue_Int( iColumn++ ); const char *pMachineName = pSet->GetColumnValue_String( iColumn ); CWorkerInfo *pInfo = FindWorkerByID( workerID ); if ( pInfo ) { if ( workerState != pInfo->m_bConnected || nWorkUnits != pInfo->m_nWorkUnitsDone || runningTimeMS != pInfo->m_RunningTimeMS || stricmp( pCurrentStage, pInfo->m_CurrentStage ) != 0 || iThread0WU != pInfo->m_ThreadWUs[0] || iThread1WU != pInfo->m_ThreadWUs[1] || iThread2WU != pInfo->m_ThreadWUs[2] || iThread3WU != pInfo->m_ThreadWUs[3] ) { bChange = true; pInfo->m_bConnected = workerState; pInfo->m_nWorkUnitsDone = nWorkUnits; pInfo->m_RunningTimeMS = runningTimeMS; pInfo->m_CurrentStage = pCurrentStage; pInfo->m_ThreadWUs[0] = iThread0WU; pInfo->m_ThreadWUs[1] = iThread1WU; pInfo->m_ThreadWUs[2] = iThread2WU; pInfo->m_ThreadWUs[3] = iThread3WU; } } else { // Add a new entry. CWorkerInfo *pInfo = new CWorkerInfo; pInfo->m_ComputerName = pMachineName; pInfo->m_bConnected = false; pInfo->m_nWorkUnitsDone = 0; pInfo->m_RunningTimeMS = 0; pInfo->m_JobWorkerID = workerID; int index; if ( bIsMaster ) { char tempStr[512]; Q_snprintf( tempStr, sizeof( tempStr ), "%s [master]", (const char*)pInfo->m_ComputerName ); index = m_Workers.InsertItem( COLUMN_COMPUTER_NAME, tempStr, NULL ); } else { index = m_Workers.InsertItem( COLUMN_COMPUTER_NAME, pInfo->m_ComputerName, NULL ); } m_Workers.SetItemData( index, (DWORD)pInfo ); bChange = true; } } if ( bChange ) { UpdateWorkersList(); } } void CJobWatchDlg::ProcessQueryResults_Text( IMySQLRowSet *pSet ) { CUtlVector text; while ( pSet->NextRow() ) { const char *pTextStr = pSet->GetColumnValue( "text" ).String(); int len = strlen( pTextStr ); text.AddMultipleToTail( len, pTextStr ); m_CurMessageIndex = pSet->GetColumnValue( "MessageIndex" ).Int32() + 1; } text.AddToTail( 0 ); FormatAndSendToEditControl( m_TextOutput.GetSafeHwnd(), text.Base() ); } void CJobWatchDlg::ProcessQueryResults_Graph( IMySQLRowSet *pSet ) { int iMSTime = pSet->GetColumnIndex( "MSSinceJobStart" ); int iBytesSent = pSet->GetColumnIndex( "BytesSent" ); int iBytesReceived = pSet->GetColumnIndex( "BytesReceived" ); // See if there's anything new. CUtlVector entries; int highest = m_CurGraphTime; while ( pSet->NextRow() ) { CGraphEntry entry; entry.m_msTime = pSet->GetColumnValue( iMSTime ).Int32(); entry.m_nBytesSent = pSet->GetColumnValue( iBytesSent ).Int32(); entry.m_nBytesReceived = pSet->GetColumnValue( iBytesReceived ).Int32(); entries.AddToTail( entry ); highest = max( highest, entry.m_msTime ); } if ( highest > m_CurGraphTime ) { m_CurGraphTime = highest; m_GraphControl.Clear(); m_GraphControl.Fill( entries ); } } void CJobWatchDlg::OnSize(UINT nType, int cx, int cy) { CIdleDialog::OnSize(nType, cx, cy); m_AnchorMgr.UpdateAnchors( this ); } BOOL CJobWatchDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) { NMHDR *pHdr = (NMHDR*)lParam; if ( pHdr->idFrom == IDC_WORKERS ) { if ( pHdr->code == LVN_COLUMNCLICK ) { LPNMLISTVIEW pListView = (LPNMLISTVIEW)lParam; // Now sort by this column. g_iSortColumn = max( 0, min( pListView->iSubItem, (int)ARRAYSIZE( g_ColumnInfos ) - 1 ) ); ResortItems(); } } return CIdleDialog::OnNotify(wParam, lParam, pResult); } void CJobWatchDlg::OnOdstatechangedWorkers(NMHDR* pNMHDR, LRESULT* pResult) { NMLVODSTATECHANGE* pStateChanged = (NMLVODSTATECHANGE*)pNMHDR; if ( !( pStateChanged->uOldState & LVIS_SELECTED ) && ( pStateChanged->uNewState & LVIS_SELECTED ) ) { OnSelChangeWorkers(); } *pResult = 0; } void CJobWatchDlg::OnItemchangedWorkers(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; if ( !( pNMListView->uOldState & LVIS_SELECTED ) && ( pNMListView->uNewState & LVIS_SELECTED ) ) { OnSelChangeWorkers(); } *pResult = 0; }