sampler.cpp
Engine/source/util/sampler.cpp
Classes:
class
A sampler backend that outputs samples to a CSV file.
class
Value holder for an individual sample.
class
A sampler backend is responsible for storing the actual sampling data.
class
Bookkeeping structure for registered sampling keys.
Public Defines
define
SAMPLE_FUNC(type) ( key, type ) \ { \ ( gSamplerRunning \ && gCurrentFrameDelta == gSamplingFrequency \ && gSampleKeys[ key - 1 ].mEnabled ) \ gSamplerBackend->sample( key, ); \ }
Public Variables
Public Functions
beginSampling(const char * location, const char * backend)
DefineEngineFunction(beginSampling , void , (const char *location, const char *backend) , ("CSV") , "(location, [backend]) -" "@brief Takes <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> string informing the backend where <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> store " "sample data and optionally <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> name of the specific logging " "backend <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use. The default is the CSV backend. In most " " cases, the logging store will be <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> name." " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "beginSampling(\"mysamples.csv\" );\n" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@ingroup Rendering" )
DefineEngineFunction(enableSamples , void , (const char *pattern, bool state) , (true) , "(pattern, [state]) -" "@brief Enable sampling <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> all keys that match the given name " "pattern. Slashes are treated as <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">separators.\n\n</a>" "@ingroup Rendering" )
DefineEngineFunction(stopSampling , void , () , "()" "@brief Stops the rendering <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sampler\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
SAMPLE_FUNC(bool )
SAMPLE_FUNC(const char * )
Detailed Description
Public Defines
SAMPLE_FUNC(type) ( key, type ) \ { \ ( gSamplerRunning \ && gCurrentFrameDelta == gSamplingFrequency \ && gSampleKeys[ key - 1 ].mEnabled ) \ gSamplerBackend->sample( key, ); \ }
Public Variables
U32 gCurrentFrameDelta
Vector< SampleKey > gSampleKeys (__FILE__, __LINE__)
ISamplerBackend * gSamplerBackend
bool gSamplerRunning
S32 gSamplingFrequency
Frequency = samples taken every nth frame.
Public Functions
beginSampling(const char * location, const char * backend)
DefineEngineFunction(beginSampling , void , (const char *location, const char *backend) , ("CSV") , "(location, [backend]) -" "@brief Takes <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> string informing the backend where <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> store " "sample data and optionally <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> name of the specific logging " "backend <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use. The default is the CSV backend. In most " " cases, the logging store will be <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> name." " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "beginSampling(\"mysamples.csv\" );\n" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@ingroup Rendering" )
DefineEngineFunction(enableSamples , void , (const char *pattern, bool state) , (true) , "(pattern, [state]) -" "@brief Enable sampling <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> all keys that match the given name " "pattern. Slashes are treated as <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">separators.\n\n</a>" "@ingroup Rendering" )
DefineEngineFunction(stopSampling , void , () , "()" "@brief Stops the rendering <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sampler\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
SAMPLE_FUNC(bool )
SAMPLE_FUNC(const char * )
SAMPLE_FUNC(F32 )
SAMPLE_FUNC(S32 )
stopSampling()
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "util/sampler.h" 26 27#include "core/util/safeDelete.h" 28#include "core/util/tVector.h" 29#include "core/stream/fileStream.h" 30#include "console/console.h" 31#include "console/engineAPI.h" 32#include "console/consoleTypes.h" 33 34/// Bookkeeping structure for registered sampling keys. 35 36struct SampleKey 37{ 38 bool mEnabled; 39 const char* mName; 40 41 bool matchesPattern( const char* pattern ) 42 { 43 U32 indexInName = 0; 44 U32 indexInPattern = 0; 45 46 while( mName[ indexInName ] != '\0' ) 47 { 48 if( pattern[ indexInPattern ] == '\0' ) 49 break; 50 else if( dToupper( mName[ indexInName ] ) == dToupper( pattern[ indexInPattern ] ) ) 51 { 52 indexInName ++; 53 indexInPattern ++; 54 } 55 else if( pattern[ indexInPattern ] == '*' ) 56 { 57 // Handle senseless concatenation of wildcards. 58 while( pattern[ indexInPattern ] == '*' ) 59 indexInPattern ++; 60 61 // Skip to next slash in name. 62 while( mName[ indexInName ] && mName[ indexInName ] != '/' ) 63 indexInName ++; 64 } 65 else 66 return false; 67 } 68 69 return ( pattern[ indexInPattern ] == '\0' 70 || ( indexInPattern > 0 && pattern[ indexInPattern ] == '*' ) ); 71 } 72}; 73 74/// A sampler backend is responsible for storing the actual sampling data. 75 76struct ISamplerBackend 77{ 78 virtual ~ISamplerBackend() {} 79 80 virtual bool init( const char* location ) = 0; 81 virtual void beginFrame() = 0; 82 virtual void endFrame() = 0; 83 84 virtual void sample( U32 key, bool value ) = 0; 85 virtual void sample( U32 key, S32 value ) = 0; 86 virtual void sample( U32 key, F32 value ) = 0; 87 virtual void sample( U32 key, const char* value ) = 0; 88}; 89 90static bool gSamplerRunning; 91static S32 gSamplingFrequency = 1; ///< Frequency = samples taken every nth frame. 92static U32 gCurrentFrameDelta; 93static Vector< SampleKey> gSampleKeys( __FILE__, __LINE__ ); 94static ISamplerBackend* gSamplerBackend; 95 96//-------------------------------------------------------------------------------- 97// CSV Backend. 98//-------------------------------------------------------------------------------- 99 100/// A sampler backend that outputs samples to a CSV file. 101 102class CSVSamplerBackend : public ISamplerBackend 103{ 104 /// Value holder for an individual sample. Unfortunately, since the 105 /// order in which samples arrive at the sampler may vary from frame to 106 /// frame, we cannot emit data immediately but rather have to buffer 107 /// it in these sample records and then flush them to disk once we receive 108 /// the endFrame call. 109 struct SampleRecord 110 { 111 U32 mKey; 112 U32 mType; //< Console type code. 113 bool mSet; 114 union 115 { 116 bool mBool; 117 S32 mS32; 118 F32 mF32; 119 const char* mString; 120 } mValue; 121 122 SampleRecord() : mKey(0), mSet(false), mType(TypeBool) { mValue.mBool = false; } 123 SampleRecord( U32 key ) 124 : mKey( key ), mSet( false ), mType(TypeBool) { mValue.mBool = false; } 125 126 void set( bool value ) 127 { 128 mType = TypeBool; 129 mValue.mBool = value; 130 mSet = true; 131 } 132 void set( S32 value ) 133 { 134 mType = TypeS32; 135 mValue.mS32 = value; 136 mSet = true; 137 } 138 void set( F32 value ) 139 { 140 mType = TypeF32; 141 mValue.mF32 = value; 142 mSet = true; 143 } 144 void set( const char* str ) 145 { 146 mType = TypeString; 147 mValue.mString = dStrdup( str ); 148 mSet = true; 149 } 150 151 void clean() 152 { 153 if( mType == TypeString ) 154 dFree( ( void* ) mValue.mString ); 155 mSet = false; 156 } 157 }; 158 159 FileStream mStream; 160 Vector< SampleRecord> mRecords; 161 162 ~<a href="/coding/class/classcsvsamplerbackend/">CSVSamplerBackend</a>() 163 { 164 mStream.close(); 165 } 166 167 /// Open the file and emit a row with the names of all enabled keys. 168 virtual bool init( const char* fileName ) 169 { 170 if( !mStream.open( fileName, Torque::FS::File::Write ) ) 171 { 172 Con::errorf( "CSVSamplerBackend::init -- could not open '%s' for writing", fileName ); 173 return false; 174 } 175 176 Con::printf( "CSVSamplerBackend::init -- writing samples to '%s'", fileName ); 177 178 bool first = true; 179 for( U32 i = 0; i < gSampleKeys.size(); ++ i ) 180 { 181 SampleKey& key = gSampleKeys[ i ]; 182 if( key.mEnabled ) 183 { 184 if( !first ) 185 mStream.write( 1, "," ); 186 187 mRecords.push_back( SampleRecord( i + 1 ) ); 188 mStream.write( dStrlen( key.mName ), key.mName ); 189 first = false; 190 } 191 } 192 193 newline(); 194 return true; 195 } 196 197 virtual void beginFrame() 198 { 199 } 200 201 virtual void endFrame() 202 { 203 char buffer[ 256 ]; 204 205 for( U32 i = 0; i < mRecords.size(); ++ i ) 206 { 207 if( i != 0 ) 208 mStream.write( 1, "," ); 209 210 SampleRecord& record = mRecords[ i ]; 211 if( record.mSet ) 212 { 213 if( record.mType == TypeBool ) 214 { 215 if( record.mValue.mBool ) 216 mStream.write( 4, "true" ); 217 else 218 mStream.write( 5, "false" ); 219 } 220 else if( record.mType == TypeS32 ) 221 { 222 dSprintf( buffer, sizeof( buffer ), "%d", record.mValue.mS32 ); 223 mStream.write( dStrlen( buffer ), buffer ); 224 } 225 else if( record.mType == TypeF32 ) 226 { 227 dSprintf( buffer, sizeof( buffer ), "%f", record.mValue.mF32 ); 228 mStream.write( dStrlen( buffer ), buffer ); 229 } 230 else if( record.mType == TypeString ) 231 { 232 //FIXME: does not do doubling of double quotes in the string at the moment 233 mStream.write( 1, "\"" ); 234 mStream.write( dStrlen( record.mValue.mString ), record.mValue.mString ); 235 mStream.write( 1, "\"" ); 236 } 237 else 238 AssertWarn( false, "CSVSamplerBackend::endFrame - bug: invalid sample type" ); 239 } 240 241 record.clean(); 242 } 243 244 newline(); 245 } 246 247 void newline() 248 { 249 mStream.write( 1, "\n" ); 250 } 251 252 SampleRecord* lookup( U32 key ) 253 { 254 //TODO: do this properly with a binary search (the mRecords array is already sorted by key) 255 256 for( U32 i = 0; i < mRecords.size(); ++ i ) 257 if( mRecords[ i ].mKey == key ) 258 return &mRecords[ i ]; 259 260 AssertFatal( false, "CSVSamplerBackend::lookup - internal error: sample key not found" ); 261 return NULL; // silence compiler 262 } 263 264 virtual void sample( U32 key, bool value ) 265 { 266 lookup( key )->set( value ); 267 } 268 virtual void sample( U32 key, S32 value ) 269 { 270 lookup( key )->set( value ); 271 } 272 virtual void sample( U32 key, F32 value ) 273 { 274 lookup( key )->set( value ); 275 } 276 virtual void sample( U32 key, const char* value ) 277 { 278 lookup( key )->set( value ); 279 } 280}; 281 282//-------------------------------------------------------------------------------- 283// Internal Functions. 284//-------------------------------------------------------------------------------- 285 286static void stopSampling() 287{ 288 if( gSamplerRunning ) 289 { 290 SAFE_DELETE( gSamplerBackend ); 291 gSamplerRunning = false; 292 } 293} 294 295static void beginSampling( const char* location, const char* backend ) 296{ 297 if( gSamplerRunning ) 298 stopSampling(); 299 300 if( dStricmp( backend, "CSV" ) == 0 ) 301 gSamplerBackend = new CSVSamplerBackend; 302 else 303 { 304 Con::errorf( "beginSampling -- No backend called '%s'", backend ); 305 return; 306 } 307 308 if( !gSamplerBackend->init( location ) ) 309 { 310 SAFE_DELETE( gSamplerBackend ); 311 } 312 else 313 { 314 gSamplerRunning = true; 315 gCurrentFrameDelta = 0; 316 } 317} 318 319//-------------------------------------------------------------------------------- 320// Sampler Functions. 321//-------------------------------------------------------------------------------- 322 323void Sampler::init() 324{ 325 Con::addVariable( "Sampler::frequency", TypeS32, &gSamplingFrequency, "Samples taken every nth frame.\n" 326 "@ingroup Rendering"); 327} 328 329void Sampler::beginFrame() 330{ 331 gCurrentFrameDelta ++; 332 if( gSamplerBackend && gCurrentFrameDelta == gSamplingFrequency ) 333 gSamplerBackend->beginFrame(); 334} 335 336void Sampler::endFrame() 337{ 338 if( gSamplerBackend && gCurrentFrameDelta == gSamplingFrequency ) 339 { 340 gSamplerBackend->endFrame(); 341 gCurrentFrameDelta = 0; 342 } 343} 344 345void Sampler::destroy() 346{ 347 if( gSamplerBackend ) 348 SAFE_DELETE( gSamplerBackend ); 349} 350 351U32 Sampler::registerKey( const char* name ) 352{ 353 gSampleKeys.push_back( SampleKey() ); 354 U32 index = gSampleKeys.size(); 355 SampleKey& key = gSampleKeys.last(); 356 357 key.mName = name; 358 key.mEnabled = false; 359 360 return index; 361} 362 363void Sampler::enableKeys( const char* pattern, bool state ) 364{ 365 if( gSamplerRunning ) 366 { 367 Con::errorf( "Sampler::enableKeys -- cannot change key states while sampling" ); 368 return; 369 } 370 371 for( U32 i = 0; i < gSampleKeys.size(); ++ i ) 372 if( gSampleKeys[ i ].matchesPattern( pattern ) ) 373 { 374 gSampleKeys[ i ].mEnabled = state; 375 Con::printf( "Sampler::enableKeys -- %s %s", state ? "enabling" : "disabling", 376 gSampleKeys[ i ].mName ); 377 } 378} 379 380#define SAMPLE_FUNC( type ) \ 381 void Sampler::sample( U32 key, type value ) \ 382{ \ 383 if( gSamplerRunning \ 384 && gCurrentFrameDelta == gSamplingFrequency \ 385 && gSampleKeys[ key - 1 ].mEnabled ) \ 386 gSamplerBackend->sample( key, value ); \ 387} 388 389SAMPLE_FUNC( bool ); 390SAMPLE_FUNC( S32 ); 391SAMPLE_FUNC( F32 ); 392SAMPLE_FUNC( const char* ); 393 394//-------------------------------------------------------------------------------- 395// Console Functions. 396//-------------------------------------------------------------------------------- 397 398DefineEngineFunction( beginSampling, void, (const char * location, const char * backend), ("CSV"), "(location, [backend]) -" 399 "@brief Takes a string informing the backend where to store " 400 "sample data and optionally a name of the specific logging " 401 "backend to use. The default is the CSV backend. In most " 402 "cases, the logging store will be a file name." 403 "@tsexample\n" 404 "beginSampling( \"mysamples.csv\" );\n" 405 "@endtsexample\n\n" 406 "@ingroup Rendering") 407{ 408 409 beginSampling( location, backend ); 410} 411 412DefineEngineFunction( stopSampling, void, (), , "()" 413 "@brief Stops the rendering sampler\n\n" 414 "@ingroup Rendering\n") 415{ 416 stopSampling(); 417} 418 419DefineEngineFunction( enableSamples, void, (const char * pattern, bool state), (true), "(pattern, [state]) -" 420 "@brief Enable sampling for all keys that match the given name " 421 "pattern. Slashes are treated as separators.\n\n" 422 "@ingroup Rendering") 423{ 424 425 Sampler::enableKeys( pattern, state ); 426} 427