123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "platform/platform.h"
- #include "util/sampler.h"
- #include "core/util/safeDelete.h"
- #include "core/util/tVector.h"
- #include "core/stream/fileStream.h"
- #include "console/console.h"
- #include "console/engineAPI.h"
- #include "console/consoleTypes.h"
- /// Bookkeeping structure for registered sampling keys.
- struct SampleKey
- {
- bool mEnabled;
- const char* mName;
- bool matchesPattern( const char* pattern )
- {
- U32 indexInName = 0;
- U32 indexInPattern = 0;
- while( mName[ indexInName ] != '\0' )
- {
- if( pattern[ indexInPattern ] == '\0' )
- break;
- else if( dToupper( mName[ indexInName ] ) == dToupper( pattern[ indexInPattern ] ) )
- {
- indexInName ++;
- indexInPattern ++;
- }
- else if( pattern[ indexInPattern ] == '*' )
- {
- // Handle senseless concatenation of wildcards.
- while( pattern[ indexInPattern ] == '*' )
- indexInPattern ++;
- // Skip to next slash in name.
- while( mName[ indexInName ] && mName[ indexInName ] != '/' )
- indexInName ++;
- }
- else
- return false;
- }
- return ( pattern[ indexInPattern ] == '\0'
- || ( indexInPattern > 0 && pattern[ indexInPattern ] == '*' ) );
- }
- };
- /// A sampler backend is responsible for storing the actual sampling data.
- struct ISamplerBackend
- {
- virtual ~ISamplerBackend() {}
- virtual bool init( const char* location ) = 0;
- virtual void beginFrame() = 0;
- virtual void endFrame() = 0;
- virtual void sample( U32 key, bool value ) = 0;
- virtual void sample( U32 key, S32 value ) = 0;
- virtual void sample( U32 key, F32 value ) = 0;
- virtual void sample( U32 key, const char* value ) = 0;
- };
- static bool gSamplerRunning;
- static S32 gSamplingFrequency = 1; ///< Frequency = samples taken every nth frame.
- static U32 gCurrentFrameDelta;
- static Vector< SampleKey > gSampleKeys( __FILE__, __LINE__ );
- static ISamplerBackend* gSamplerBackend;
- //--------------------------------------------------------------------------------
- // CSV Backend.
- //--------------------------------------------------------------------------------
- /// A sampler backend that outputs samples to a CSV file.
- class CSVSamplerBackend : public ISamplerBackend
- {
- /// Value holder for an individual sample. Unfortunately, since the
- /// order in which samples arrive at the sampler may vary from frame to
- /// frame, we cannot emit data immediately but rather have to buffer
- /// it in these sample records and then flush them to disk once we receive
- /// the endFrame call.
- struct SampleRecord
- {
- U32 mKey;
- U32 mType; //< Console type code.
- bool mSet;
- union
- {
- bool mBool;
- S32 mS32;
- F32 mF32;
- const char* mString;
- } mValue;
- SampleRecord() : mKey(0), mSet(false), mType(TypeBool) { mValue.mBool = false; }
- SampleRecord( U32 key )
- : mKey( key ), mSet( false ), mType(TypeBool) { mValue.mBool = false; }
- void set( bool value )
- {
- mType = TypeBool;
- mValue.mBool = value;
- mSet = true;
- }
- void set( S32 value )
- {
- mType = TypeS32;
- mValue.mS32 = value;
- mSet = true;
- }
- void set( F32 value )
- {
- mType = TypeF32;
- mValue.mF32 = value;
- mSet = true;
- }
- void set( const char* str )
- {
- mType = TypeString;
- mValue.mString = dStrdup( str );
- mSet = true;
- }
- void clean()
- {
- if( mType == TypeString )
- dFree( ( void* ) mValue.mString );
- mSet = false;
- }
- };
- FileStream mStream;
- Vector< SampleRecord > mRecords;
- ~CSVSamplerBackend()
- {
- mStream.close();
- }
- /// Open the file and emit a row with the names of all enabled keys.
- virtual bool init( const char* fileName )
- {
- if( !mStream.open( fileName, Torque::FS::File::Write ) )
- {
- Con::errorf( "CSVSamplerBackend::init -- could not open '%s' for writing", fileName );
- return false;
- }
- Con::printf( "CSVSamplerBackend::init -- writing samples to '%s'", fileName );
- bool first = true;
- for( U32 i = 0; i < gSampleKeys.size(); ++ i )
- {
- SampleKey& key = gSampleKeys[ i ];
- if( key.mEnabled )
- {
- if( !first )
- mStream.write( 1, "," );
- mRecords.push_back( SampleRecord( i + 1 ) );
- mStream.write( dStrlen( key.mName ), key.mName );
- first = false;
- }
- }
- newline();
- return true;
- }
- virtual void beginFrame()
- {
- }
- virtual void endFrame()
- {
- char buffer[ 256 ];
- for( U32 i = 0; i < mRecords.size(); ++ i )
- {
- if( i != 0 )
- mStream.write( 1, "," );
- SampleRecord& record = mRecords[ i ];
- if( record.mSet )
- {
- if( record.mType == TypeBool )
- {
- if( record.mValue.mBool )
- mStream.write( 4, "true" );
- else
- mStream.write( 5, "false" );
- }
- else if( record.mType == TypeS32 )
- {
- dSprintf( buffer, sizeof( buffer ), "%d", record.mValue.mS32 );
- mStream.write( dStrlen( buffer ), buffer );
- }
- else if( record.mType == TypeF32 )
- {
- dSprintf( buffer, sizeof( buffer ), "%f", record.mValue.mF32 );
- mStream.write( dStrlen( buffer ), buffer );
- }
- else if( record.mType == TypeString )
- {
- //FIXME: does not do doubling of double quotes in the string at the moment
- mStream.write( 1, "\"" );
- mStream.write( dStrlen( record.mValue.mString ), record.mValue.mString );
- mStream.write( 1, "\"" );
- }
- else
- AssertWarn( false, "CSVSamplerBackend::endFrame - bug: invalid sample type" );
- }
- record.clean();
- }
- newline();
- }
- void newline()
- {
- mStream.write( 1, "\n" );
- }
- SampleRecord* lookup( U32 key )
- {
- //TODO: do this properly with a binary search (the mRecords array is already sorted by key)
- for( U32 i = 0; i < mRecords.size(); ++ i )
- if( mRecords[ i ].mKey == key )
- return &mRecords[ i ];
- AssertFatal( false, "CSVSamplerBackend::lookup - internal error: sample key not found" );
- return NULL; // silence compiler
- }
- virtual void sample( U32 key, bool value )
- {
- lookup( key )->set( value );
- }
- virtual void sample( U32 key, S32 value )
- {
- lookup( key )->set( value );
- }
- virtual void sample( U32 key, F32 value )
- {
- lookup( key )->set( value );
- }
- virtual void sample( U32 key, const char* value )
- {
- lookup( key )->set( value );
- }
- };
- //--------------------------------------------------------------------------------
- // Internal Functions.
- //--------------------------------------------------------------------------------
- static void stopSampling()
- {
- if( gSamplerRunning )
- {
- SAFE_DELETE( gSamplerBackend );
- gSamplerRunning = false;
- }
- }
- static void beginSampling( const char* location, const char* backend )
- {
- if( gSamplerRunning )
- stopSampling();
- if( dStricmp( backend, "CSV" ) == 0 )
- gSamplerBackend = new CSVSamplerBackend;
- else
- {
- Con::errorf( "beginSampling -- No backend called '%s'", backend );
- return;
- }
- if( !gSamplerBackend->init( location ) )
- {
- SAFE_DELETE( gSamplerBackend );
- }
- else
- {
- gSamplerRunning = true;
- gCurrentFrameDelta = 0;
- }
- }
- //--------------------------------------------------------------------------------
- // Sampler Functions.
- //--------------------------------------------------------------------------------
- void Sampler::init()
- {
- Con::addVariable( "Sampler::frequency", TypeS32, &gSamplingFrequency, "Samples taken every nth frame.\n"
- "@ingroup Rendering");
- }
- void Sampler::beginFrame()
- {
- gCurrentFrameDelta ++;
- if( gSamplerBackend && gCurrentFrameDelta == gSamplingFrequency )
- gSamplerBackend->beginFrame();
- }
- void Sampler::endFrame()
- {
- if( gSamplerBackend && gCurrentFrameDelta == gSamplingFrequency )
- {
- gSamplerBackend->endFrame();
- gCurrentFrameDelta = 0;
- }
- }
- void Sampler::destroy()
- {
- if( gSamplerBackend )
- SAFE_DELETE( gSamplerBackend );
- }
- U32 Sampler::registerKey( const char* name )
- {
- gSampleKeys.push_back( SampleKey() );
- U32 index = gSampleKeys.size();
- SampleKey& key = gSampleKeys.last();
- key.mName = name;
- key.mEnabled = false;
- return index;
- }
- void Sampler::enableKeys( const char* pattern, bool state )
- {
- if( gSamplerRunning )
- {
- Con::errorf( "Sampler::enableKeys -- cannot change key states while sampling" );
- return;
- }
- for( U32 i = 0; i < gSampleKeys.size(); ++ i )
- if( gSampleKeys[ i ].matchesPattern( pattern ) )
- {
- gSampleKeys[ i ].mEnabled = state;
- Con::printf( "Sampler::enableKeys -- %s %s", state ? "enabling" : "disabling",
- gSampleKeys[ i ].mName );
- }
- }
- #define SAMPLE_FUNC( type ) \
- void Sampler::sample( U32 key, type value ) \
- { \
- if( gSamplerRunning \
- && gCurrentFrameDelta == gSamplingFrequency \
- && gSampleKeys[ key - 1 ].mEnabled ) \
- gSamplerBackend->sample( key, value ); \
- }
- SAMPLE_FUNC( bool );
- SAMPLE_FUNC( S32 );
- SAMPLE_FUNC( F32 );
- SAMPLE_FUNC( const char* );
- //--------------------------------------------------------------------------------
- // Console Functions.
- //--------------------------------------------------------------------------------
- DefineEngineFunction( beginSampling, void, (const char * location, const char * backend), ("CSV"), "(location, [backend]) -"
- "@brief Takes a string informing the backend where to store "
- "sample data and optionally a name of the specific logging "
- "backend to use. The default is the CSV backend. In most "
- "cases, the logging store will be a file name."
- "@tsexample\n"
- "beginSampling( \"mysamples.csv\" );\n"
- "@endtsexample\n\n"
- "@ingroup Rendering")
- {
- beginSampling( location, backend );
- }
- DefineEngineFunction( stopSampling, void, (), , "()"
- "@brief Stops the rendering sampler\n\n"
- "@ingroup Rendering\n")
- {
- stopSampling();
- }
- DefineEngineFunction( enableSamples, void, (const char * pattern, bool state), (true), "(pattern, [state]) -"
- "@brief Enable sampling for all keys that match the given name "
- "pattern. Slashes are treated as separators.\n\n"
- "@ingroup Rendering")
- {
- Sampler::enableKeys( pattern, state );
- }
|