HawkTracer  0.10.0
Tutorial: Define custom timeline feature

In this tutorial we will learn how to extend timeline capabilities by defining custom timeline features.

Before you start...

We recommend you to go through the Tutorial: Hello world! tutorial to make sure you have the HawkTracer library properly installed.

Problem statement

We'd like to know how many events we pushed to a specific timeline object.

Code

Please copy the text below to a files named hawktracer-custom-timeline-feature.c, push-counter-feature.c and push-counter-feature.h (you can find those files in the repository as well).

hawktracer-custom-timeline-feature.c

/* Contains declaration of custome feature's API */
#include "push-counter-feature.h"
#include <hawktracer.h>
#include <stdio.h>
int main(int argc, char** argv)
{
/* initialize HawkTracer library */
ht_init(argc, argv);
/* register our custom feature */
PushCounterFeature_register();
HT_ErrorCode error_code;
/* create timeline object. Instead, global timeline could be used (ht_global_timeline_get()) */
HT_Timeline* timeline = ht_timeline_create(1024, HT_FALSE, HT_FALSE, NULL, &error_code);
if (!timeline)
{
printf("Failed to create timeline object: %d\n", error_code);
return 1;
}
/* attach the feature to the timeline object */
push_counter_feature_enable(timeline);
/* push 10 events to the timeline through the feature method */
for (int i = 0; i < 10; i++)
{
push_counter_feature_push_event(timeline, &event);
}
printf("Number of events pushed to the timeline: %d\n",
push_counter_feature_get_count(timeline));
/* destroy the timeline. It will also destroy the feature */
return 0;
}

push-counter-feature.h

#ifndef PUSH_COUNTER_FEATURE_H
#define PUSH_COUNTER_FEATURE_H
#include <hawktracer.h>
/*
* Registers feature in HawkTracer system.
* The definition of the function is automatically generated
* by HT_FEATURE_DEFINE() macro.
*/
HT_ErrorCode PushCounterFeature_register(void);
/*
* Helper method for enabling a feature for a specific timeline object
*/
HT_ErrorCode push_counter_feature_enable(HT_Timeline* timeline);
/*
* Feature method - pushes an event to a timeline and increments the counter.
* The method is a wrapper for ht_timeline_push_event()
*/
void push_counter_feature_push_event(HT_Timeline* timeline, HT_Event* event);
/*
* Feature method - returns count of events pushed to the timeline
*/
int push_counter_feature_get_count(HT_Timeline* timeline);
#endif /* PUSH_COUNTER_FEATURE_H */

push-counter-feature.c

#include "push-counter-feature.h"
#include <stdio.h>
/* feature object. This should contain all fields that will be used
* by the feature methods */
typedef struct
{
/* required field - the feature must inherit from HT_Feature class */
HT_Feature base;
/* other feature members go here. we'll be using count field for
* counting number of events being pushed to the timeline
*/
int counter;
} PushCounterFeature;
static void push_counter_feature_destroy(HT_Feature* feature);
/* the macro defines a few helper methods for the feature.
* All the methods are prefixed with PushCounterFeature_
*/
HT_FEATURE_DEFINE(PushCounterFeature, push_counter_feature_destroy)
/* A helper method for attaching the feature to the timeline.
* User can simply call this function with her timeline object
* as a parameter to enable the feature for that timeline
*/
HT_ErrorCode push_counter_feature_enable(HT_Timeline* timeline)
{
/* Allocate memory for the feature. This method should be used for
* allocating the object, as it not only allocates memory, but
* initializes base HT_Feature member.
*/
PushCounterFeature* feature = PushCounterFeature_alloc();
if (feature == NULL)
{
}
/* Initialize all the fields of the feature. In this case, we reset the counter */
feature->counter = 0;
/* The function attaches the feature to a timeline */
return ht_timeline_set_feature(timeline, (HT_Feature*)feature);
}
void push_counter_feature_push_event(HT_Timeline* timeline, HT_Event* event)
{
/* Obtain the feature from the timeline */
PushCounterFeature* feature = PushCounterFeature_from_timeline(timeline);
/* The feature might not be enabled for the pipeline, so this needs to be checked. */
if (!feature)
{
printf("Feature has not been enabled for the pipeline\n");
return;
}
/* Perform the feature's logic */
ht_timeline_push_event(timeline, event);
feature->counter++;
}
int push_counter_feature_get_count(HT_Timeline* timeline)
{
/* Obtain the feature from the timeline */
PushCounterFeature* feature = PushCounterFeature_from_timeline(timeline);
/* The feature might not be enabled for the pipeline, so this needs to be checked. */
if (!feature)
{
return -1;
}
return feature->counter;
}
/* Destroys the feature object */
static void push_counter_feature_destroy(HT_Feature* feature)
{
PushCounterFeature* push_counter_feature = (PushCounterFeature*)feature;
/* Here all the resources of the feature should be released */
ht_free(push_counter_feature);
}

Walkthrough

We assume you already went through the Tutorial: Hello world! tutorial, so we only focus here on the new part.

Defining the feature

We start with defining the feature.

typedef struct
{
HT_Feature base;
int counter;
} PushCounterFeature;
HT_FEATURE_DEFINE(PushCounterFeature, push_counter_feature_destroy)

First, we define the structure that holds all the required information about the feature. We agreed that we want to count number of events being pushed to a timeline, therefore we need a counter field. All the features must inherit from HT_Feature class, so we need to create base field of this type as a first one in the structure.

HT_FEATURE_DEFINE() is a helper macro that defines a few helper methods which we use in the implementation. It takes struct name and destroy callback.

HT_ErrorCode push_counter_feature_enable(HT_Timeline* timeline)

We'd like the feature to be easily accessible to our users (including ourselves), so we define push_counter_feature_enable() that will be used to enable the feature for a specific timeline. As enabling feature might fail because of many reasons, we return HT_ErrorCode to inform our users about exact problem.

PushCounterFeature* feature = PushCounterFeature_alloc();

We allocate an instance of the object using PushCounterFeature_alloc() method - the method was auto-generated by HT_FEATURE_DEFINE(). It allocates memory (using ht_alloc()) but also initializes the base field of the structure. If you want to use allocation method different than ht_alloc(), you need to make sure that you initialize the structure on your own.

if (feature == NULL)
{
}

Allocating the feature might fail, so we need to handle this scenario correctly.

feature->counter = 0;

We need to initialize the data structure. In our case, it means reseting the counter value.

return ht_timeline_set_feature(timeline, (HT_Feature*)feature);

When the feature is initialized, we can attach it to the timeline, using ht_timeline_set_feature. From now on, the timeline manages the memory of the feature (even if the function doesn't complete successfully, it will still release the feature).

static void push_counter_feature_destroy(HT_Feature* feature)
{
PushCounterFeature* push_counter_feature = (PushCounterFeature*)feature;
ht_free(push_counter_feature);
}

We also need to define a method for destroying the feature. As mentioned above, by default HawkTracer uses ht_alloc() method for allocating the memory for the object, therefore we use ht_free() for releasing it. If the feature object allocated additional resources, the method is a right place to release them.

Defining the feature logic

We defined the feature, but without the logic implemented for the feature, it's kind of useless.

void push_counter_feature_push_event(HT_Timeline* timeline, HT_Event* event)

We'll define a method which will push an event to a timeline, and at the same time increase the counter of the feature.

PushCounterFeature* feature = PushCounterFeature_from_timeline(timeline);

Please note we don't pass the feature as an argument to the function. In order to get it, HT_DEFINE_FEATURE() generates PushCounterFeature_from_timeline() function that gets the feature from the timeline.

if (!feature)
{
printf("Feature has not been enabled for the pipeline\n");
return;
}

The timeline being passed to the function might not have this feature enabled, so we need to check that before executing the logic.

ht_timeline_push_event(timeline, event);
feature->counter++;

This is the actual logic of the feature - we push the event to the timeline, and increment the counter.

int push_counter_feature_get_count(HT_Timeline* timeline)
{
PushCounterFeature* feature = PushCounterFeature_from_timeline(timeline);
if (!feature)
{
return -1;
}
return feature->counter;
}

We also define a simple method for returning the counter to the user.

Declaring interface of the feature

In the previous paragraph we defined the feature. Now, we need to expose an interface to the user so she can use it in her code.

HT_ErrorCode PushCounterFeature_register(void);
HT_ErrorCode push_counter_feature_enable(HT_Timeline* timeline);
int push_counter_feature_get_count(HT_Timeline* timeline);
void push_counter_feature_push_event(HT_Timeline* timeline, HT_Event* event);

Apart from the three methods we defined in the previous paragraph, there's one extra method we expose to users: HT_ErrorCode PushCounterFeature_register() - we didn't implement the function, but it was auto-generated by HT_DEFINE_FEATURE(). The feature must be registered before usage, so we need to expose the function so she can register it at any time in her code.

### Using the feature in the code

PushCounterFeature_register();

Before we start, the feature must be registered. The function should be called after ht_init(), but before any usage of the feature.

push_counter_feature_enable(timeline);

We enable the feature for the timeline. We defined a nice function that wraps all the initializations, so our user needs to call only one function to attach the feature to the timeline.

push_counter_feature_push_event(timeline, &event);

Instead of using ht_timeline_push_event() function, we use function defined for our feature, so we can keep track of number of events being pushed to the timeline.

printf("Number of events pushed to the timeline: %d\n",
push_counter_feature_get_count(timeline));

At the end of the program we print the count, so we now how many events we pushed to the timeline.

There's no need for destroying the feature - timeline destructor takes care of releasing the object by calling push_counter_feature_destroy() function.

Program output

After running the program, you should see the following output on the screen:

$ ./custom-timeline-feature
Number of events pushed to the timeline: 10