sampler.cpp 12 KB

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