Setup
In order to instrument your application you need to use the instrumentation api. The easiest way to do this is to add the sdk https://github.com/mevicg/QuSDK as submodule to your project’s repository and follow the steps below.
Loader
The instrumentation api is compiled into a dynamic library and shipped with Qumulus or a seperate redistributable. To save you some work from having to load the entry-points, the sdk comes with a loader. You can compile this loader from your own project and link to it statically.
For Windows a Visual Studio project is provided. This needs to be added to your solution and have the project creating your executable depend on it.
For macOS an Xcode project is available. You can add this as child project to your own project or add it to the workspace.
API
The api is split up into several headers. You will only be including one of the headers though depending on if your using C or C++.
quApi.h
: This header defines the C-api as a set of callable api functions. Everything that the api can do is provided through this header, the C++ api uses these functions as well.quApi.hpp
: This header is intended for C++ developers. It contains some RAII based wrappers around api objects. There are also some macros that can be used to automatically create recurring activities and reuse them each time the code is executed.
Note
If you’re compiling without QU_API_ENABLED
defined the api functions all result in no-op. For this reason it is not necessary to use macros for instrumentation as the optimizer already optimizes out the empty functions.
Includes & Linking
As with all libraries you need to add the library to your include directories and link to it. In our case the api is a dynamic library, so we’re linking to the loader instead.
For Windows the sdk provides several property pages which you can add to your project depending on what you need.
QuApiDebug.props
: This property page adds the api’s include directory and links to the debug build of the loader.QuApiRelease.props
: This property page adds the api’s include directory and links to the debug build of the loader.QuApiDisabled.props
: This property page only adds the api’s include directory. The application can compile with the instrumentation in place but all instrumentation will be optimized out.
For macOS the sdk provides Xcode configuration files. Due to the lack of relative path support in these configuration files you need to define QU_API_PATH
in your own configuration file prior to including one from the sdk. This variable should be defined to the path from your project to the api folder inside the sdk.
QuApiEnabled.xcconfig
: This configuration file adds the api’s include directory and links to the loader.QuApiDisabled.xcconfig
: This property page only adds the api’s include directory. The application can compile with the instrumentation in place but all instrumentation will be optimized out.
Initialization
With everything set up properly you can now include the api and initialize it during application startup. First off you need to call quInitialize
providing the version of the headers you’re using. This enables the api to check if the header’s version matches that of the runtime installed on the machine. After successfully initializing the api you need to create (and start) an output to which the profiling data is being sent. To allow Qumulus to connect this needs to be a TCP output. Here’s an example showing initialization:
#include <quApi.h>
int main( int argc, const char* argv[] )
{
if( !quInitialize( QU_VERSION, nullptr ) )
return -1; //Invalid version
if( quSetupTCPOutput( "MyApp", true ) == QU_INVALID_OUTPUT_ID )
return -2; //Failed creating output
//Rest of app...
return 0;
}
Logging
Many API functions return identifiers which can be invalid or booleans which can indicate failure. None of the return values communicate anything about why calling an API function might have failed though. To get this information you can provide a function pointer that will be called to provide you such information.
#include <iostream>
#include <quApi.h>
void QU_CALL_CONV LogHook( quLogSeverity severity, const char* logMessage )
{
if( severity == QU_LOG_SEVERITY_INFO )
std::cout << logMessage;
else
std::cerr << logMessage;
}
int main( int argc, const char* argv[] )
{
if( !quInitialize( QU_VERSION, &LogHook ) )
return -1; //Error information will have been printed from the LogHook function.
//Rest of app...
return 0;
}
The callback you install during initialization is being retained inside the runtime and will also be used later when issues occur inside other API functions.
Warning
There must be no instrumentation inside the log hook itself. All API functions need to be able to call the hook, having the hook contain instrumentation will cause an endless recursion. This will crash your application due to a stack overflow.