Activities
Functions or regions of instrumented code are represented by activities. When you instrument a function the profiler will create an activity with the name of the function to represent the code being ran. Activities are sorted under multiple channels. Often times you will have one channel per thread and just have the instrumentation automatically use the current thread’s channel. For more advanced instrumentation you can also create custom channels and have activities run on that channel instead.
Thread Setup
By default threads will not automatically link activities to an activity channel. This can easilly be changed on threads that you want to be instrumented. Simply create an activity channel and link it to the current thread. There is a RAII inspired class for it which can be used like so:
#include <quApi.hpp>
int main( int argc, const char* argv[] )
{
//Api initialization here...
QU_SCOPED_ACTIVITY_CHANNEL( channel, "Main Thread", true );
Run();
}
An activity channel with the name Main Thread is created. It is then linked to the current thread and the activity channel’s id is stored in the channel
object. Once this object goes out of scope the activity channel will be destroyed and no activities can be added to it anymore. Data previously added will still remain visible in the profiler.
Functions
There’s several ways to instrument a function. The best would be to use a macro inside the function. This is the least amount of work, uses the fastest possible instrumenting and automatically makes the function appear on any profiled thread calling it:
void EatApples( int amount );
void Update()
{
QU_INSTRUMENT_FUNCTION();
EatApples( 3 );
}
In the profiler this will be shown as an Update activity. Whenever the function name changes or the function starts being called from different threads it will automatically show up.
What if you want to give an activity a custom name though? That is possible as well. There are several scenarios in which you might want to use custom names. The main one being when you’re generating an activity name depending on some variable. Another one happens when you’re using unmodifyable libraries and want to instrument them from your own code. Both are demonstrated below:
void SomeLibraryFunction( ... );
void EatApple( int index )
{
//When the name needs to be dynamic, you must use a one-shot activity.
std::string activityName = std::string( "Eating apple " ) + std::to_string( index + 1 );
QU_ONESHOT_ACTIVITY( activityName.c_str() );
//Munch Munch!!!
}
void EatApples( int amount )
{
QU_INSTRUMENT_FUNCTION();
WashApples( amount );
CutApples( amount );
ApplyCinnamon();
/**
* Lets assume we are using a library that is very good at determining which apple to eat first.
* We want to make a call to the library, but we cant instrument it. In that case we can put the instrumentation
* outside of the library function like so:
*/
QU_RECURRING_ACTIVITY( "SomeLibraryFunction" );
SomeLibraryFunction( &EatApple, amount );
}
Recurring or Oneshot
While profiling, the instrumented code is generating data that will be processed by the profiler. For small applications this data is not a lot and oneshot activities can always be used. For larger applications with lots of instrumentation however this data can add up. The amount of data can also add up during longer profiling sessions.
In order to reduce the amount of data that needs to be processed there is a distinction between recurring and oneshot activities. Recurring activities are intended for activities that will happen more than once and whose data does not change over time. A good example of this are activities using the function’s name. The name is compiled into your program and thus cannot be changed. The data for recurring activities only has to be processed once and thus saves time and bandwidth while profiling.
Oneshot activities are activities for which you dont know their name yet in advance. Maybe you’re generating the name from some variable or it comes from somewhere other than the instrumented code. In either case, you’re using a variable somewhere. In this case you should use oneshot activities. These activities are processed in their entirety once and then no more.
Colors
By default, activities are colored based on their name. This way when activities are reused they all have the same color so you can recognise them easier. Sometimes you may want to specify custom colors though, for example when an activity represents something bad like a lock or blocking disk io. In this case you can specify the color that an activity will have. Both recurring and oneshot activities support this:
void EatApple( int index )
{
//When the name needs to be dynamic, you must use a one-shot activity.
std::string activityName = std::string( "Eating apple " ) + std::to_string( index + 1 );
QU_ONESHOT_ACTIVITY_COLOR( activityName.c_str(), QU_RGB( 35, 200, 35 ) );
//Munch Munch!!!
}
void EatApples( int amount )
{
QU_INSTRUMENT_FUNCTION();
WashApples( amount );
CutApples( amount );
ApplyCinnamon();
/**
* Lets assume we are using a library that is very good at determining which apple to eat first.
* We want to make a call to the library, but we cant instrument it. In that case we can put the instrumentation
* outside of the library function like so:
*/
QU_RECURRING_ACTIVITY_COLOR( "SomeLibraryFunction", QU_RGB( 220, 210, 20 ) );
SomeLibraryFunction( &EatApple, amount );
}
Note that nothing changed except for appending _COLOR
to the macros and providing a color built using the QU_RGB
macro. When profiling this code now the EatApple activity will show in a greenish tint while the SomeLibraryFunction activity will be yellow.