sampler.cpp

Engine/source/util/sampler.cpp

More...

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

gSampleKeys (__FILE__, __LINE__)

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 * )

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