HawkTracer  0.10.0
Tutorial: Define custom event type

In this tutorial we will learn how to define custom event types.

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

Let's say we'd like to track all the fopen() and fclose() calls, and additionally we'd like to know how many files are currently open by the application, whether the operation passed or failed, and what were the arguments.

Code

Please copy the text below to a file named hawktracer-custom-event-type.c (you can find this file in the repository as well).

#include <hawktracer.h>
/* The header must be included for defining events */
#include <stdio.h>
/* Define base file operation event class */
HT_DECLARE_EVENT_KLASS(FileOperationEvent, HT_Event,
(INTEGER, uint32_t, opened_files_count),
(INTEGER, HT_Boolean, success))
/* Define event for OpenFile operation */
HT_DECLARE_EVENT_KLASS(OpenFileEvent, FileOperationEvent,
(STRING, const char*, name),
(STRING, const char*, mode))
/* Define event for CloseFile operation */
HT_DECLARE_EVENT_KLASS(CloseFileEvent, FileOperationEvent,
(STRING, const char*, label))
/* Global variable for holding the number of files currently open by the program */
static uint32_t opened_files_count = 0;
static FILE* my_fopen(const char* file_name, const char* mode)
{
FILE* f_ptr;
HT_Boolean success = HT_FALSE;
f_ptr = fopen(file_name, mode);
if (f_ptr)
{
opened_files_count++;
success = HT_TRUE;
}
/* Push OpenFileEvent to a global timeline */
HT_TIMELINE_PUSH_EVENT(ht_global_timeline_get(), OpenFileEvent, opened_files_count, success, file_name, mode);
return f_ptr;
}
static void my_fclose(FILE* f_ptr, const char* marker)
{
HT_Boolean success = f_ptr != NULL;
/* Push CloseFileEvent to a global timeline */
HT_TIMELINE_PUSH_EVENT(ht_global_timeline_get(), CloseFileEvent, opened_files_count, success, marker);
if (success)
{
opened_files_count--;
fclose(f_ptr);
}
}
static void tutorial_run(void)
{
FILE* fp1 = my_fopen("existing_file", "r");
FILE* fp2 = my_fopen("create-me.txt", "w");
my_fclose(fp1, "fp1");
FILE* fp3 = my_fopen("non_existing_file", "r");
my_fclose(fp2, "fp2");
my_fclose(fp3, "fp3");
}
int main(int argc, char** argv)
{
/* initialize HawkTracer library */
ht_init(argc, argv);
/* Register all the new event types before first usage */
HT_REGISTER_EVENT_KLASS(FileOperationEvent);
HT_REGISTER_EVENT_KLASS(OpenFileEvent);
HT_REGISTER_EVENT_KLASS(CloseFileEvent);
HT_ErrorCode error_code;
/* Create a listener and register it to a timeline, it'll handle all the HawkTracer events */
ht_global_timeline_get(), "tutorial-custom-event-type.htdump", 2048, &error_code);
/* Creating listener might fail (e.g. file can't be open),
* so we have to check the status
*/
if (error_code != HT_ERR_OK)
{
printf("Unable to create listener. Error code: %d\n", error_code);
return -1;
}
/* Run the actual code */
tutorial_run();
/* Uninitialize HawkTracer library */
}

Building and running the example

You can see Integrate HawkTracer to existing project in order to see how to integrate HawkTracer to your project. For the purpose of the example, here are few shortcuts for most popular platforms:

  • Linux In the terminal, run the following command:
    $ gcc hawktracer-custom-event-type.c $(pkg-config --cflags --libs hawktracer) -o hawktracer-custom-event-type # compiling the code
    $ ./hawktracer-custom-event-type # running the example

Walkthrough

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

Before we even start writing the logic, in order to define custom event types, we should include an extra header which defines helper macros for defining custom event types:

As discussed earlier, we want to track to operations - open and close file - and for each of the event we want to know how many files we currently have open in the application, whether the operation failed or completed successfully, and what were the parameters of those functions. Since number of opened files and the result are common values for both open and close operation, we can have a base class with those two fields. However, parameters for open and close are different, so we create two new classes which inherit from the base class:

HT_DECLARE_EVENT_KLASS(FileOperationEvent, HT_Event,
(INTEGER, uint32_t, opened_files_count),
(INTEGER, HT_Boolean, success))
HT_DECLARE_EVENT_KLASS(OpenFileEvent, FileOperationEvent,
(STRING, const char*, name),
(STRING, const char*, mode))
HT_DECLARE_EVENT_KLASS(CloseFileEvent, FileOperationEvent,
(STRING, const char*, label))

HT_DECLARE_EVENT_KLASS() as a first argument takes the type name of the new class, as a second argument - base class, and then list of field definitions. Every field definition consists of data kind (see HT_MKCREFLECT_Types_Ext enum), C type, and the field name. The code generates an actual C struct, e.g. for the OpenFileEvent, it is:

typedef struct {
FileOperationEvent base;
const char* name;
const char* mode;
} OpenFileEvent;

Additionally, the macro defines a few extra helper functions which are used for type registration or data serialization.

Let's move to the main function now. Every new data type must be registered to the HawkTracer registry, so the library knows how to deal with a particular event. It's done by calling HT_REGISTER_EVENT_KLASS() macro, which takes a class name as a parameter:

HT_REGISTER_EVENT_KLASS(FileOperationEvent);
HT_REGISTER_EVENT_KLASS(OpenFileEvent);
HT_REGISTER_EVENT_KLASS(CloseFileEvent);

Once we registered our event classes, we can now start using them. HawkTracer provides a convenient macro HT_TIMELINE_PUSH_EVENT() for creating and pushing events to a timeline:

HT_TIMELINE_PUSH_EVENT(ht_global_timeline_get(), OpenFileEvent, opened_files_count, success, file_name, mode);
HT_TIMELINE_PUSH_EVENT(ht_global_timeline_get(), CloseFileEvent, opened_files_count, success, marker);

The macro takes 3 arguments:

  • a timeline to push the event to
  • event type
  • a list of values for the particular event

The macro automatically creates an instance of the particular event, and all you need to do is to pass all the values in the order you defined earlier. The macro also sets correct timestamp for you.

Results

After running the application, it should generate a list following events:

[
{
"name": "existing_file",
"ts": 43138514192,
"mode": "r",
"opened_files_count": 1,
"success": 1
},
{
"name": "create-me.txt",
"ts": 43138514264,
"mode": "w",
"opened_files_count": 2,
"success": 1
},
{
"label": "fp1",
"ts": 43138514265,
"opened_files_count": 2,
"success": 1
},
{
"name": "non_existing_file",
"ts": 43138514284,
"mode": "r",
"opened_files_count": 1,
"success": 0
},
{
"label": "fp2",
"ts": 43138514285,
"opened_files_count": 1,
"success": 1
},
{
"label": "fp3",
"ts": 43138514301,
"opened_files_count": 0,
"success": 0
}
]