Files
harbour-core/harbour/source/rtl/profiler.prg
Przemyslaw Czerpak 618df79c9b 2008-10-11 04:56 UTC+0200 Przemyslaw Czerpak (druzus/at/priv.onet.pl)
* harbour/include/hbapigt.h
  * harbour/source/rtl/gtapi.c
    + added C functions hb_gtLock() and hb_gtUnlock()
      They block current GT for other threads

  * harbour/source/rtl/gtfunc.c
    + added PRG functions hb_gtLock() and hb_gtUnlock()
      They block current GT for other threads - be careful using them
      and always unlock locked GT

  * harbour/source/rtl/scroll.c
    + added HB_SCROLL() function - it works like SCROLL() but supports
      2 additional parameters: color and erase char. It's stateless and
      atomic in GT access

  * harbour/source/rtl/tbrowse.prg
    * use HB_SCROLL() instead of SCROLL(). Now whole TBROWSE class
      does not depend on other thread screen output and does not
      set any GT variables except of cursor positioning to active
      cell when it's enabled

  * harbour/source/rtl/memoedit.prg
  * harbour/source/rtl/tgetlist.prg
  * harbour/source/rtl/tlabel.prg
  * harbour/source/rtl/listbox.prg
  * harbour/source/rtl/tmenusys.prg
  * harbour/source/rtl/achoice.prg
  * harbour/source/rtl/profiler.prg
  * harbour/source/rtl/teditor.prg
    * use atomic stateless functions when possible - this code should
      be checked and updated by some who know it.
2008-10-11 02:56:22 +00:00

788 lines
18 KiB
Plaintext

/*
* $Id$
*/
/*
* Harbour Project source code:
* Profiler reporting classes
*
* Copyright 2001,2002 Dave Pearson <davep@davep.org>
* http://www.davep.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/).
*
* As a special exception, the Harbour Project gives permission for
* additional uses of the text contained in its release of Harbour.
*
* The exception is that, if you link the Harbour libraries with other
* files to produce an executable, this does not by itself cause the
* resulting executable to be covered by the GNU General Public License.
* Your use of that executable is in no way restricted on account of
* linking the Harbour library code into it.
*
* This exception does not however invalidate any other reasons why
* the executable file might be covered by the GNU General Public License.
*
* This exception applies only to the code released by the Harbour
* Project under the name Harbour. If you copy code from other
* Harbour Project or Free Software Foundation releases into a copy of
* Harbour, as the General Public License permits, the exception does
* not apply to the code that you add in this way. To avoid misleading
* anyone as to the status of such modified files, you must delete
* this exception notice from them.
*
* If you write modifications of your own for Harbour, it is your choice
* whether to permit this exception to apply to your modifications.
* If you do not wish that, delete this exception notice.
*
*/
/* Rationale:
*
* There are three aspects to profiling:
*
* 1) Gathering profile information.
* 2) Taking a snapshot of an application's profile information.
* 3) Reporting on the data gathered in the snapshot.
*
* Point 1 is handled in harbour's virtual machine. This source aims to
* provide code for performing points 2 and 3. A class is provided to
* gather, hold and manipulate the profile snapshot and a hierarchy of
* classes exist for reporting on that snapshot. The reporting classes are
* designed such that they are easy to inherit from and improve upon.
*
* The idea behind all of this is that the the design should provide all the
* necessary building blocks for writing profiler reporters that suite the
* user's needs (perhaps as part of an extended debugger or something).
*
*/
/* Notes:
*
* As much as possible, the profiler class and the profile report classes
* attempt to turn off the profiler to ensure that we don't get some sort of
* Heisenberg effect. In other words, we don't want the profiler showing up
* in the profiler.
*
* Many of the "PROTECTED:" scope specifiers in the source have been
* commented out where there's a problem with scope in harbour's class
* system. Note that those comments will be removed when the bug is fixed.
*
*/
/* TODO:
*
* o Handle any TODO: items in the source.
* o Document the classes and the class hierarchy.
*
*/
/* Thanks:
*
* Thanks to Antonio and Patrick for agreeing to replace the old profile
* reporting code with this approach.
*/
#include "hbclass.ch"
#include "fileio.ch"
#include "common.ch"
#ifdef __TEST__
#include "inkey.ch"
PROCEDURE Main()
LOCAL oProfile := HBProfile():new()
LOCAL n
// Turn on profiling.
__setProfiler( .T. )
// Make sure we've got something to see timewise.
DrawScreen( "Doing nothing for a couple of seconds" )
DoNothingForTwoSeconds()
// Make sure we've got something to see callwise.
FOR n := 1 TO 500
CallMe500Times()
NEXT
// Take a profile snapshot.
oProfile:gather()
// Report on calls greater than 0
DrawScreen( "All methods/functions called one or more times" )
memoedit( HBProfileReportToString():new( oProfile:callSort() ):generate( {| o | o:nCalls > 0 } ), 1,,,, .F. )
// Sorted by name
DrawScreen( "All methods/functions called one or more times, sorted by name" )
memoedit( HBProfileReportToString():new( oProfile:nameSort() ):generate( {| o | o:nCalls > 0 } ), 1,,,, .F. )
// Sorted by time
DrawScreen( "All methods/functions taking measurable time, sorted by time" )
memoedit( HBProfileReportToString():new( oProfile:timeSort() ):generate( {| o | o:nTicks > 0 } ), 1,,,, .F. )
// TBrowse all calls greater than 0
DrawScreen( "TBrowse all methods/functions called one or more times" )
Browser( HBProfileReportToTBrowse():new( oProfile:callSort() ):generate( {| o | o:nCalls > 0 }, 1 ) )
// Some closing stats
DrawScreen( "Totals" )
@ 2, 0 Say " Total Calls: " + Str( oProfile:totalCalls() )
@ 3, 0 Say " Total Ticks: " + Str( oProfile:totalTicks() )
@ 4, 0 Say "Total Seconds: " + Str( oProfile:totalSeconds() )
RETURN
STATIC PROCEDURE DrawScreen( cTitle )
CLEAR SCREEN
@ 0, 0 SAY PadR( cTitle, MaxCol() + 1 ) COLOR "N/W"
RETURN
PROCEDURE DoNothingForTwoSeconds()
Inkey( 2 )
RETURN
PROCEDURE CallMe500Times()
RETURN
STATIC PROCEDURE Browser( oBrowse )
LOCAL lBrowsing := .T.
LOCAL nKey
DO WHILE lBrowsing
oBrowse:forceStable()
nKey := Inkey( 0 )
DO CASE
CASE nKey == K_ESC
lBrowsing := .F.
CASE nKey == K_DOWN
oBrowse:down()
CASE nKey == K_UP
oBrowse:up()
CASE nKey == K_LEFT
oBrowse:left()
CASE nKey == K_RIGHT
oBrowse:right()
CASE nKey == K_PGDN
oBrowse:pageDown()
CASE nKey == K_PGUP
oBrowse:pageUp()
// And so on.... (not really necessary for this test)
ENDCASE
ENDDO
RETURN
#endif
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileEntity
CREATE CLASS HBProfileEntity
EXPORTED:
VAR cName ReadOnly
VAR nCalls ReadOnly
VAR nTicks ReadOnly
ACCESS nSeconds
ACCESS nMeanTicks
ACCESS nMeanSeconds
METHOD init
METHOD describe
ENDCLASS
/////
METHOD init( cName, aInfo ) CLASS HBProfileEntity
::cName := cName
::nCalls := aInfo[ 1 ]
::nTicks := aInfo[ 2 ]
RETURN Self
ACCESS nSeconds CLASS HBProfileEntity
RETURN HB_Clocks2Secs( ::nTicks )
ACCESS nMeanTicks CLASS HBProfileEntity
RETURN iif( ::nCalls == 0, 0, ::nTicks / ::nCalls )
ACCESS nMeanSeconds CLASS HBProfileEntity
RETURN iif( ::nCalls == 0, 0, ::nSeconds / ::nCalls )
METHOD describe CLASS HBProfileEntity
RETURN "Base Entity"
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileFunction
CREATE CLASS HBProfileFunction INHERIT HBProfileEntity
EXPORTED:
METHOD describe
ENDCLASS
/////
METHOD describe CLASS HBProfileFunction
RETURN "Function"
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileMethod
CREATE CLASS HBProfileMethod INHERIT HBProfileEntity
EXPORTED:
METHOD describe
ENDCLASS
/////
METHOD describe CLASS HBProfileMethod
RETURN "Method"
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileOPCode
CREATE CLASS HBProfileOPCode INHERIT HBProfileEntity
EXPORTED:
METHOD describe
ENDCLASS
/////
METHOD describe CLASS HBProfileOPCode
RETURN "OPCode"
////////////////////////////////////////////////////////////////////////////
// Class: HBProfile
CREATE CLASS HBProfile
EXPORTED:
VAR aProfile
METHOD init
METHOD gather
METHOD forEach
METHOD sort
METHOD nameSort
METHOD callSort
METHOD timeSort
METHOD totalCalls
METHOD totalTicks
METHOD totalSeconds
PROTECTED:
METHOD gatherFunctions
METHOD gatherMethods
METHOD reset
METHOD ignoreSymbol
ENDCLASS
/////
METHOD init CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
::reset()
__setProfiler( lProfile )
RETURN Self
METHOD reset CLASS HBProfile
::aProfile := {}
RETURN Self
METHOD ignoreSymbol( cSymbol ) CLASS HBProfile
LOCAL cProfPrefix := "HBPROFILE"
RETURN Left( cSymbol, Len( cProfPrefix ) ) == cProfPrefix .OR. cSymbol == "__SETPROFILER"
METHOD gatherFunctions CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
LOCAL nSymCount := __DynSCount()
LOCAL cName
LOCAL n
// For each known symbol.
// TODO: Question: Will the symbol count have changed because
// we've created variables?
FOR n := 1 TO nSymCount
// Is the symbol a function?
IF __DynSIsFun( n )
// If we're not ignoring the symbol...
IF !::ignoreSymbol( cName := __DynSGetName( n ) )
// Yes, it is, add it to the profile.
AAdd( ::aProfile, HBProfileFunction():new( cName, __DynSGetPrf( n ) ) )
ENDIF
ENDIF
NEXT
__setProfiler( lProfile )
RETURN Self
METHOD gatherMethods CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
LOCAL n := 1
LOCAL cClass
LOCAL nMembers
LOCAL aMembers
LOCAL nMember
// For each class in the environment...
DO WHILE !Empty( cClass := __className( n ) )
// If we're not ignoring the class' methods...
IF !::ignoreSymbol( cClass )
// Collect class members.
nMembers := Len( aMembers := __classSel( n ) )
FOR nMember := 1 TO nMembers
// If we've got a member name...
IF !empty( aMembers[ nMember ] )
// Add it to the profile.
AAdd( ::aProfile, HBProfileMethod():new( cClass + ":" + aMembers[ nMember ], __GetMsgPrf( n, aMembers[ nMember ] ) ) )
ENDIF
NEXT
ENDIF
++n
ENDDO
__setProfiler( lProfile )
RETURN Self
METHOD gather CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
// Reset the profile.
::reset()
// Gather function calls
::gatherFunctions()
// Gather method calls
::gatherMethods()
__setProfiler( lProfile )
RETURN Self
METHOD forEach( b ) CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
AEval( ::aProfile, b )
__setProfiler( lProfile )
RETURN Self
METHOD sort( b ) CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
ASort( ::aProfile,,, b )
__setProfiler( lProfile )
RETURN Self
METHOD nameSort CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
::sort( {| oX, oY | oX:cName < oY:cName } )
__setProfiler( lProfile )
RETURN Self
METHOD callSort CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
::sort( {| oX, oY | oX:nCalls > oY:nCalls } )
__setProfiler( lProfile )
RETURN Self
METHOD timeSort CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
::sort( {| oX, oY | oX:nTicks > oY:nTicks } )
__setProfiler( lProfile )
RETURN Self
METHOD totalCalls CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
LOCAL nCalls := 0
::forEach( {| o | nCalls += o:nCalls } )
__setProfiler( lProfile )
RETURN nCalls
METHOD totalTicks CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
LOCAL nTicks := 0
::forEach( {| o | nTicks += o:nTicks } )
__setProfiler( lProfile )
RETURN nTicks
METHOD totalSeconds CLASS HBProfile
LOCAL lProfile := __setProfiler( .F. )
LOCAL nSeconds := 0
::forEach( {| o | nSeconds += o:nSeconds } )
__setProfiler( lProfile )
RETURN nSeconds
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileLowLevel
CREATE CLASS HBProfileLowLevel INHERIT HBProfile
EXPORTED:
METHOD gather
PROTECTED:
METHOD gatherOPCodes
ENDCLASS
/////
METHOD gather CLASS HBProfileLowLevel
LOCAL lProfile := __setProfiler( .F. )
// Gather functions and methods.
::super:gather()
// Also gather opcodes.
::gatherOPCodes()
__setProfiler( lProfile )
RETURN Self
METHOD gatherOPCodes CLASS HBProfileLowLevel
LOCAL nMax := __opcount()
LOCAL cName
LOCAL nOP
// Loop over all the harbour OP codes. Note that they start at 0.
FOR nOP := 0 TO ( nMax - 1 )
// If we're not ignoring this opcode.
IF !::ignoreSymbol( cName := "OPCODE( " + PadL( nOP, 3 ) + " )" )
// Add it to the profile.
AAdd( ::aProfile, HBProfileOPCode():new( cName, __OpGetPrf( nOP ) ) )
ENDIF
NEXT
RETURN Self
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileReport
CREATE CLASS HBProfileReport
PROTECTED:
VAR oProfile
METHOD writeLines
METHOD header
METHOD emitHeader
METHOD line
METHOD emitLine
EXPORTED:
METHOD init
METHOD generate
ENDCLASS
/////
METHOD init( oProfile ) CLASS HBProfileReport
LOCAL lProfile := __setProfiler( .F. )
::oProfile := oProfile
__setProfiler( lProfile )
RETURN Self
METHOD writeLines( aLines ) CLASS HBProfileReport
AEval( aLines, {| c | QOut( c ) } )
RETURN Self
METHOD header CLASS HBProfileReport
RETURN { "Name Type Calls Ticks Seconds",;
"=================================== ========== ======== =========== ===========" }
METHOD emitHeader CLASS HBProfileReport
::writeLines( ::header() )
RETURN Self
METHOD line( oEntity ) CLASS HBProfileReport
RETURN { PadR( oEntity:cName, 35 ) + " " + ;
PadR( oEntity:describe(), 8 ) + " " + ;
PadL( oEntity:nCalls, 10 ) + " " + ;
PadL( oEntity:nTicks, 11 ) + " " + ;
Str( oEntity:nSeconds, 11, 2 ) }
METHOD emitLine( oEntity ) CLASS HBProfileReport
::writeLines( ::line( oEntity ) )
RETURN Self
METHOD generate( bFilter ) CLASS HBProfileReport
LOCAL lProfile := __setProfiler( .F. )
DEFAULT bFilter TO {|| .T. }
::emitHeader():oProfile:forEach( {| o | iif( Eval( bFilter, o ), ::emitLine( o ), NIL ) } )
__setProfiler( lProfile )
RETURN Self
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileReportToFile
CREATE CLASS HBProfileReportToFile INHERIT HBProfileReport
PROTECTED:
VAR hFile
METHOD writeLines
EXPORTED:
METHOD generate
ENDCLASS
/////
METHOD writeLines( aLines ) CLASS HBProfileReportToFile
IF ::hFile != F_ERROR
AEval( aLines, {| c | FWrite( ::hFile, c + HB_OSNewLine() ) } )
ENDIF
RETURN Self
METHOD generate( bFilter, cFile ) CLASS HBProfileReportToFile
LOCAL lProfile := __setProfiler( .F. )
DEFAULT cFile TO "hbprof.txt"
IF ( ::hFile := fcreate( cFile ) ) != F_ERROR
::super:generate( bFilter )
fclose( ::hFile )
ELSE
// TODO: Throw an error
ENDIF
__setProfiler( lProfile )
RETURN Self
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileReportToArray
CREATE CLASS HBProfileReportToArray INHERIT HBProfileReport
PROTECTED:
VAR aReport
METHOD writeLines
EXPORTED:
METHOD generate
ENDCLASS
/////
METHOD writeLines( aLines ) CLASS HBProfileReportToArray
AEval( aLines, {| c | AAdd( ::aReport, c ) } )
RETURN Self
METHOD generate( bFilter ) CLASS HBProfileReportToArray
::aReport := {}
::super:generate( bFilter )
RETURN ::aReport
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileReportToString
CREATE CLASS HBProfileReportToString INHERIT HBProfileReportToArray
EXPORTED:
METHOD generate
ENDCLASS
/////
METHOD generate( bFilter ) CLASS HBProfileReportToString
LOCAL cReport := ""
AEval( ::super:generate( bFilter ), {| c | cReport += c + HB_OSNewLine() } )
RETURN cReport
////////////////////////////////////////////////////////////////////////////
// Class: HBProfileReportToTBrowse
CREATE CLASS HBProfileReportToTBrowse INHERIT HBProfileReportToArray
PROTECTED:
VAR nEntity
METHOD emitHeader
METHOD emitLine
METHOD addColumns
EXPORTED:
METHOD generate
METHOD currentEntity
ENDCLASS
/////
METHOD emitHeader CLASS HBProfileReportToTBrowse
// No header required.
RETURN Self
METHOD emitLine( oEntity ) CLASS HBProfileReportToTBrowse
// Don't "emit" anything, simply add the entity to the array.
AAdd( ::aReport, oEntity )
RETURN Self
METHOD generate( bFilter, nTop, nLeft, nBottom, nRight ) CLASS HBProfileReportToTBrowse
LOCAL lProfile := __setProfiler( .F. )
LOCAL oBrowse
// Start with the first entity.
::nEntity := 1
// Generate the array.
::super:generate( bFilter )
// Build the browse.
oBrowse := TBrowseNew( nTop, nLeft, nBottom, nRight )
oBrowse:goTopBlock := {|| ::nEntity := 1 }
oBrowse:goBottomBlock := {|| ::nEntity := Len( ::aReport ) }
oBrowse:skipBlock := {| nSkip, nPos | nPos := ::nEntity, ;
::nEntity := iif( nSkip > 0, ;
Min( Len( ::aReport ), ::nEntity + nSkip ), ;
Max( 1, ::nEntity + nSkip ) ), ::nEntity - nPos }
::addColumns( oBrowse )
__setProfiler( lProfile )
RETURN oBrowse
METHOD addColumns( oBrowse ) CLASS HBProfileReportToTBrowse
oBrowse:addColumn( TBColumnNew( "Name", {|| PadR( ::currentEntity():cName, 35 ) } ) )
oBrowse:addColumn( TBColumnNew( "Type", {|| PadR( ::currentEntity():describe(), 8 ) } ) )
oBrowse:addColumn( TBColumnNew( "Calls", {|| PadL( ::currentEntity():nCalls, 10 ) } ) )
oBrowse:addColumn( TBColumnNew( "Ticks", {|| PadL( ::currentEntity():nTicks, 11 ) } ) )
oBrowse:addColumn( TBColumnNew( "Seconds", {|| Str( ::currentEntity():nSeconds, 11, 2 ) } ) )
oBrowse:addColumn( TBColumnNew( "Mean;Ticks", {|| Str( ::currentEntity():nMeanTicks, 11, 2 ) } ) )
oBrowse:addColumn( TBColumnNew( "Mean;Seconds", {|| Str( ::currentEntity():nMeanSeconds, 11, 2 ) } ) )
RETURN Self
METHOD currentEntity CLASS HBProfileReportToTBrowse
RETURN ::aReport[ ::nEntity ]