#INCLUDE "PInnacle.CH" #INCLUDE "DCDialog.CH" #INCLUDE "Thread.CH" #INCLUDE "GRA.CH" #INCLUDE "FILEIO.CH" #INCLUDE "DIRECTRY.CH" #DEFINE DEF_THREAD_SNIFFER_BUTTON_BOX 1 #DEFINE DEF_THREAD_SNIFFER_BROWSE_BOX 2 #DEFINE DEF_THREAD_SNIFFER_BROWSE 3 #DEFINE DEF_THREAD_SNIFFER_RESIZE_CHAR 4 MemVar goMainWindow Class PITAS_ThreadSniffer From Thread Protected: Var bTerminated //Var oWindow Var oReadyState Exported: Class Method Create Method Destroy Inline Method Wait4ReadyToService() ::oReadyState:Wait(0) Return(Self) InLine Method Init ::Thread:Init() ::bTerminated := .F. ::oReadyState := Signal():New() Return Self InLine Method Terminate Return ( ::bTerminated := .T. ) Method Execute() EndClass Class Method PITAS_ThreadSniffer:Create() Local loThreadSniffer loThreadSniffer := PITAS_ThreadSniffer():New() loThreadSniffer:Start() loThreadSniffer:Wait4ReadyToService() If(loThreadSniffer:bTerminated) Return(Nil) Endif Return(loThreadSniffer) Method PITAS_ThreadSniffer:Destroy() // Send the thread a termination signal meaning setting Terminate = .T. to end the while loop ::Terminate() // Wait for the thread to complete code execution, this wait does not consume any CPU because it is using signals internally. ::Synchronize(0) Return Method PITAS_ThreadSniffer:Execute() Local loDialog Local laSize := { AppDeskTop():CurrentSize()[1] / 2 , AppDeskTop():CurrentSize()[2] / 2} Local laPos := {50,50} Local laControls := {} Local laButtonSize := GetTasGlobal("APPLICATION_BUTTON_SIZE") Local Mp1 := Nil Local Mp2 := Nil Local loXbp := Nil Local lnEvent := Nil Local lnCurrentRecord := 1 Local loButtonBox := Nil Local loResizeChar := Nil Local loBrowse := Nil Local loBrowseBox := Nil Local loButton1 := Nil Local loButton2 := Nil Local lnInterval := GetTasGlobal("THREAD_SNIFFER_INTERVAL") * 100 Local lbWriteLog := GetTasGlobal("THREAD_SNIFFER_WRITE_LOG") Local lcLogFile := If( lbWriteLog , SetDebugFileName() , "" ) Local laThreadInfo := ThreadInfo( THREADINFO_TID + ; THREADINFO_SYSTHND + ; THREADINFO_FUNCINFO ; ) loDialog := XbpDialog():New( AppDesktop() , AppDesktop() , laPos, laSize,, .F. ) loDialog:Icon := ICON_PINNACLE2 loDialog:TaskList := .T. If lbWriteLog loDialog:Title := "Thread Sniffer writing to: " + lcLogFile + " every " + NTrim(GetTasGlobal("THREAD_SNIFFER_INTERVAL")) + " second(s)." Else loDialog:Title := "Thread Sniffer" Endif loDialog:Resize := {|| _Resize( @loDialog,@laControls )} loDialog:MinButton := .T. loDialog:MaxButton := .F. loDialog:DrawingArea:ClipChildren := .T. loDialog:Create() loButtonBox := MakeButtonBar( loDialog:DrawingArea , .T. ) loResizeChar := ResizeChar(@loButtonBox) loResizeChar:Show() loResizeChar:Disable() loBrowseBox := XbpStatic():New( loDialog:DrawingArea ,Nil, { 0,0} , { 10,10} ) loBrowseBox:Type := XBPSTATIC_TYPE_RECESSEDBOX loBrowseBox:SetColorBG( GRA_CLR_RED ) loBrowseBox:Create() loBrowse := XbpBrowse():New( loBrowseBox ,Nil, {2,laButtonSize[2] + 5 }, { 100,100} , PITAS_BrowPres(), .T. ):Create() loBrowse:CursorMode:= XBPBRW_CURSOR_ROW loBrowse:SetFontCompoundName(GetTasGlobal("BROWSE_DATA_AREA_FONT")) // Navigation code blocks for the browser loBrowse:SkipBlock := {|nSkip,oBrowse| ArrayBrowseSkip(nSkip,@lnCurrentRecord,Len(laThreadInfo)) } loBrowse:GoBottomBlock := {|| lnCurrentRecord := Len(laThreadInfo) } loBrowse:GoTopBlock := {|| lnCurrentRecord := 1 } loBrowse:PosBlock := {|| lnCurrentRecord } loBrowse:PhyPosBlock := {|| lnCurrentRecord } loBrowse:LastPosBlock := {|| Len(laThreadInfo) } loBrowse:FirstPosBlock := {|| 1 } loBrowse:AddColumn ( {|| laThreadInfo[lnCurrentRecord][1] }, , "Internal Thread Id" ) loBrowse:AddColumn ( {|| laThreadInfo[lnCurrentRecord][2] }, , "OS Thread Id" ) loBrowse:AddColumn ( {|| laThreadInfo[lnCurrentRecord][4] }, , "PInnacle Function Name" ) loBrowse:AddColumn ( {|| laThreadInfo[lnCurrentRecord][3] }, , "Line Number" ) loBrowse:Configure() // Create the Refresh Button loButton1:= XbpPushButton():New() loButton1:Caption := "&Refresh" loButton1:SetFontCompoundName(GetTasGlobal("APPLICATION_BUTTON_FONT_NAME")) loButton1:Create( loButtonBox ,Nil , {2,2}, laButtonSize ) loButton1:Activate:= {|| PostAppEvent( xbeP_Keyboard, xbeK_ALT_R ,,loDialog ) } // Create the Write to Log Button loButton2:= XbpPushButton():New() loButton2:Caption := "&Write Log" loButton2:SetFontCompoundName(GetTasGlobal("APPLICATION_BUTTON_FONT_NAME")) loButton2:Create( loButtonBox ,Nil , {2 + laButtonSize[1] , 2 }, laButtonSize ) loButton2:Activate:= {|| PostAppEvent( xbeP_Keyboard, xbeK_ALT_W ,,loDialog ) } AAdd(laControls , loButtonBox) AAdd(laControls , loBrowseBox) AAdd(laControls , loBrowse) AAdd(laControls , loResizeChar) loDialog:SetModalState(XBP_DISP_APPMODAL) loButtonBox:Show() If GetTasGlobal("THREAD_SNIFFER_VISIBLE") loDialog:Show() Endif SetAppFocus(goMainWindow) ::oReadyState:Signal() While !::bTerminated lnEvent := AppEvent( @Mp1, @Mp2, @loXbp, lnInterval ) If lnEvent == xbe_None .OR. ( lnEvent = xbeP_Keyboard .AND. Mp1 == xbeK_ALT_R ) laThreadInfo := ThreadInfo( THREADINFO_TID + ; THREADINFO_SYSTHND + ; THREADINFO_FUNCINFO ; ) If lbWriteLog WriteLog(laThreadInfo,lcLogFile) Endif loBrowse:RefreshAll() ElseIf ( lnEvent = xbeP_Keyboard .AND. Mp1 == xbeK_ALT_W ) WriteLog(laThreadInfo,lcLogFile) ElseIf loXbp <> Nil loXbp:HandleEvent( lnEvent, Mp1, Mp2, loXbp ) Endif Enddo // Because we may end up here due to some error occured we first clean the event queue and then destroy our UI resources to avoid // garbage in the event queue While (lnEvent := AppEvent( @Mp1, @Mp2, @loXbp , 10 ))!=0 loXbp:HandleEvent( lnEvent, Mp1, Mp2, loXbp ) EndDo loDialog:Destroy() ::oReadyState:Signal() ::Quit() Return Self Static Function WriteLog(paThreadInfo,pcLogFile) Local lnHandle := FOpen(pcLogFile , FO_READWRITE + FO_SHARED ) Local lnI Local lnLength := Len(paThreadInfo) Begin Sequence If lnHandle = -1 Break Else // Move to EOF() FSeek(lnHandle,0,FS_END) Endif FWrite(lnHandle, "Snapshot written at: " + DTOC(DATE()) + " " + TIME() + DEF_CR_LF ) For lnI := 1 To lnLength FWrite(lnHandle, PadL( NTrim(paThreadInfo[lnI][1]),13) + " " ) FWrite(lnHandle, PadL( NTrim(paThreadInfo[lnI][2]),12) + " " ) FWrite(lnHandle, PadL( paThreadInfo[lnI][4] , 50) + " " ) If paThreadInfo[lnI][3] # Nil FWrite(lnHandle, PadL( NTrim(paThreadInfo[lnI][3]),11) ) Else FWrite(lnHandle, "") Endif FWrite(lnHandle, DEF_CR_LF ) Next lnI FClose(lnHandle) End Sequence Return Nil Static Function SetDebugFileName() Local lcExtension := GetTasGlobal("CTI_EXTENSION") Local laFiles := Directory( GetTasGlobal("SYSTEM_LOG_PATH") + "ThreadSnifferLog_*." + lcExtension ) Local lcLogfile Local lnHandle If Len(laFiles) > 0 // Sort array by Date/Time ASort( laFiles,,, {|Array1,Array2| DToc(Array1[ F_DATE ]) + Array1[ F_TIME ] > DToc(Array2[ F_DATE ]) + Array2[ F_TIME ] } ) If Len(laFiles) >= 30 lcLogfile := GetTasGlobal("SYSTEM_LOG_PATH") + laFiles[ 30, F_NAME] Else lcLogfile := GetTasGlobal("SYSTEM_LOG_PATH") + "ThreadSnifferLog_" + AllTrim(Str(Len(laFiles)+1)) + "." + lcExtension Endif Else lcLogfile := GetTasGlobal("SYSTEM_LOG_PATH") + "ThreadSnifferLog_1." + lcExtension Endif lnHandle := FCreate(lcLogfile) FWrite(lnHandle, "Auto log interval set to: " + NTrim(GetTasGlobal("THREAD_SNIFFER_INTERVAL")) + " second(s)." + DEF_CR_LF ) FWrite(lnHandle, DEF_CR_LF ) FWrite(lnHandle, "Int Thread Id " ) // 13 FWrite(lnHandle, "OS Thread Id " ) // 12 FWrite(lnHandle, PadC("PInnacle Function Name",50) ) // 50 FWrite(lnHandle, " Line Number" ) // 11 FWrite(lnHandle, DEF_CR_LF ) FWrite(lnHandle, Replicate("-",13) + " " ) FWrite(lnHandle, Replicate("-",12) + " " ) FWrite(lnHandle, Replicate("-",50) + " " ) FWrite(lnHandle, Replicate("-",11) ) FWrite(lnHandle, DEF_CR_LF ) // Over write / Recreate file FClose(lnHandle) Return lcLogfile Static Function _Resize( poDialog, paControls ) Local laButtonSize := GetTasGlobal("APPLICATION_BUTTON_SIZE") poDialog:DrawingArea:Hide() paControls[DEF_THREAD_SNIFFER_BROWSE_BOX]:SetSize( { poDialog:DrawingArea:CurrentSize()[1] , poDialog:DrawingArea:CurrentSize()[2] - ( laButtonSize[2] + 5) - (DEF_CONTROL_OFFSET * 2 ) } ) paControls[DEF_THREAD_SNIFFER_BROWSE_BOX]:SetPos( { 0, DEF_CONTROL_OFFSET + laButtonSize[2] + 5 } ) paControls[DEF_THREAD_SNIFFER_BROWSE]:SetSize( { paControls[DEF_THREAD_SNIFFER_BROWSE_BOX]:CurrentSize()[1] - 2 , paControls[DEF_THREAD_SNIFFER_BROWSE_BOX]:CurrentSize()[2] - 2 } ) paControls[DEF_THREAD_SNIFFER_BROWSE]:SetPos( {1,1} ) paControls[DEF_THREAD_SNIFFER_BUTTON_BOX]:SetSize( { poDialog:DrawingArea:CurrentSize()[1] , laButtonSize[2] + 5 } ) paControls[DEF_THREAD_SNIFFER_BUTTON_BOX]:SetPos( { 0,0 } ) paControls[DEF_THREAD_SNIFFER_RESIZE_CHAR]:SetPos( { paControls[DEF_THREAD_SNIFFER_BUTTON_BOX]:CurrentSize()[1]-20,1} ) poDialog:DrawingArea:Show() Return Nil