Babeltrace 2 C API  2.0.0
Open-source trace manipulation framework
API fundamentals

This page explains the basic principles of the Babeltrace 2 C API.

You must understand what the API expects before you create a Babeltrace 2 plugin or an application which uses the API.

Header file

To use the Babeltrace 2 C API, include babeltrace2/babeltrace.h:

#include <babeltrace2/babeltrace.h>

Do not include any other header file found in the babeltrace2 directory: the compiler prints an error when you try to.

Namespace

  • All libbabeltrace2 functions and types start with bt_.
  • All libbabeltrace2 definitions, macros, and enumerators start with BT_.

Function precondition and postcondition checking

All the functions of libbabeltrace2 which have parameters check that the caller meets their preconditions.

All the functions of libbabeltrace2 which call a user function which returns something check that the returned value meets their postconditions.

The function descriptions in the API reference modules list all their preconditions and postconditions, if any.

libbabeltrace2 is very strict regarding function preconditions and postconditions: when you break any of them, the library prints how the precondition or postcondition was not satisfied, with details, and then calls abort().

Here's an example of what the library prints to the standard error before aborting when you break a precondition:

10-06 09:12:20.228 62362 62362 F LIB/VALUE bt_value_array_get_length@value.c:887 Babeltrace 2 library precondition not satisfied; error is:
10-06 09:12:20.228 62362 62362 F LIB/VALUE bt_value_array_get_length@value.c:887 Value object is NULL:
10-06 09:12:20.228 62362 62362 F LIB/VALUE bt_value_array_get_length@value.c:887 Aborting...

Because precondition and postcondition checks detect programming errors, libbabeltrace2's approach is to abort as soon as possible so that you fix the error. Therefore, the libbabeltrace2 functions never return a programming error status (like what EINVAL means on Unix systems, for example).

Attention
Some precondition and postcondition checks which occur on the fast path and which would therefore significantly impact performance during a typical trace processing graph run are only enabled in developer mode.

Common function preconditions are:

  • A pointer parameter is not NULL.
  • An index parameter is not ouf of bounds.
  • A string or container parameter is not empty.
  • An object parameter has a given conceptual type. For example, you cannot call bt_value_array_get_length() with a boolean value.
  • An object parameter is not frozen.
  • An object parameter has some specific state.

Object model

The Babeltrace 2 C API is object-oriented.

With a few exceptions, API functions are actually methods which operate on objects: their first parameter points to said object. For example:

uint64_t bt_value_array_get_length(const bt_value *value);

You can create some types of objects with functions that contain the word create, while for some other types, only the library can create them behind the scenes. For example, you can create a boolean value object with bt_value_bool_create(), but you cannot directly create a event object: you need to borrow one from a event message which contains it.

Each type of object has its own C type. Learn more about typing in C typing below.

Some types of objects conceptually inherit other types of objects. If an object type A inherits an object type B, then you can use both the A and B API functions with an object of type A. For example, because an enumeration field class is conceptually an integer field class, you can use any integer field class function with an enumeration field class. The API reference modules always indicate the inheritance relations.

Shared vs. unique objects

Some Babeltrace 2 objects are shared while some others are unique:

Shared object

A shared object has a reference count.

A shared object's creation function returns a new reference.

The API of a given shared object type contains:

  • A function to get a new reference, increasing the reference count, which ends with _get_ref.
  • A function to put an existing reference, decreasing the reference count, which ends with _put_ref.
  • A macro to put an existing reference and then set the passed expression to NULL. This macro ends with _PUT_REF_AND_RESET.
  • A macro to move an existing reference from a source expression to a destination expression, putting the destination expression's existing reference, and setting the source expression to NULL. This macro ends with _MOVE_REF.

For example, bt_value_get_ref() and bt_value_put_ref() get and put value object references, BT_VALUE_PUT_REF_AND_RESET() puts a value reference and sets the expression to NULL, and BT_VALUE_MOVE_REF() moves a value reference.

All *_get_ref() and *_put_ref() functions, and all *_PUT_REF_AND_RESET() macros accept a NULL parameter.

When the reference count of a given object reaches zero, it can be destroyed. Some shared objects, however, have a lifetime that is managed by another shared object. For example, an event class is not destroyed until its parent stream class is also destroyed, even if its reference count technically reaches zero.

A function which accepts a shared object never "takes" or steals the caller's reference unless its name contains the word move: you still have your own reference when the function returns. For example:

bt_event_class *event_class = bt_event_class_create(stream_class);
/*
* At this point, we still have a reference of `stream_class`.
* We need to put it with bt_stream_class_put_ref() at some point.
*/

A function which contains the word borrow returns a borrowed reference: if you need your own reference, get one with the appropriate *_get_ref() function.

Unique object

A unique object does not have a reference count: another object is always its sole owner.

Because you cannot get a new unique object reference, you must ensure that you own the unique object's owner to keep it alive. The API reference modules make it clear, depending on the context, which shared object is the ultimate owner of a given unique object.

In general, you cannot create a unique object: the library creates it, and then you borrow it from another object (shared or unique itself).

Unique objects exist for performance reasons: some optimizations are challenging to implement without this concept.

In the API reference, each module indicates whether the documented objects are shared or unique.

Object freezing

The library can freeze some types of Babeltrace 2 objects when specific functions succeed.

A frozen object is immutable: trying to set an object's property once it's frozen represents a precondition break.

For example, the library freezes the source component initialization parameters when you call bt_graph_add_source_component(): this guarantees to the component's initialization method that the parameters will never change for the rest of their lifetime.

When an object becomes frozen, its contained objects, if any, also become frozen, recursively.

There's no function to check whether or not a given object is frozen. Because the API reference modules document which functions freeze which objects, the "frozen" property is only useful for libbabeltrace2 to catch programming errors (precondition checks).

Attention
Some "frozen" property checks which occur on the fast path and which would therefore significantly impact performance during a typical trace processing graph run are only enabled in developer mode.

C typing

The Babeltrace 2 C API typing system is very strict to catch many programming errors at compile time.

Each type of object has its own C type. Consequently, functions accept and return specific C types. For example, all the event functions accept a bt_event pointer.

The API uses opaque pointers, so that you don't having access to the object type's actual C structure. This helps with the development of features and fixes in future releases of Babeltrace 2.

Some objects share the same C type when different conceptual types can be contained in some collection. For example, all value objects have the type bt_value because an array value can contain different types of values. You must be careful to only call the functions which apply to a specific type of such objects. The API reference modules make this clear in the precondition section. Such objects always have a *_get_type() function to get the object's exact type enumerator. For example, bt_value_get_type() returns the type enumerator of a given value object.

When an object type A conceptually inherits an object type B, and when A and B have different C types, the API offers a dedicated, inline upcasting function named bt_A_as_B() to have access to the B API at no cost. For example, an unsigned enumeration field class mapping is conceptually an enumeration field class mapping, but they have different C types: bt_field_class_enumeration_unsigned_mapping and bt_field_class_enumeration_mapping. Get the latter from the former with bt_field_class_enumeration_unsigned_mapping_as_mapping_const(). The bt_A_as_B() functions do not change the object's reference count and they accept NULL.

Attention
Never directly cast a Babeltrace 2 object pointer from some C type to another C type: the API is designed so that you never need to do that.

const correctness

The Babeltrace 2 C API is const-correct: when a function has a const object pointer parameter, it never modifies that object from the user's viewpoint.

As such, when a function returns a const object pointer, directly or through an output parameter, you can't modify the object.

Attention
Never remove a Babeltrace 2 object pointer's const qualifier. The API is designed so that you never need to do that.

Functions which accept or return a const object pointer end with _const when they have (or could have in the future) a non const equivalent. For example, bt_value_map_borrow_entry_value_const() is the const version of bt_value_map_borrow_entry_value().

Simple property getter functions do not end with _const.

Reference count changing functions, ending with _get_ref and _put_ref(), accept a const object pointer parameter: the library does not consider that an object's nature is altered when its reference count changes.

C integer types

The API only uses uint64_t and int64_t as C integer types for clarity and consistency.

Common C types and definitions

There are a few C types and definitions which are common to many parts of the Babeltrace 2 C API.

See Common C types.

Function return

libbabeltrace2 functions which cannot fail return a property or an object pointer directly. For example, bt_value_array_get_length() returns the length of an array value, and bt_value_array_borrow_element_by_index_const() returns a value contained in an array value. Both functions cannot fail: any programming error makes the program abort.

When a function returns an optional property or object:

If it's a pointer

The function returns NULL if the property/object is missing.

If it's not a pointer
If the property is available

The function returns the property by output parameter and returns BT_PROPERTY_AVAILABILITY_AVAILABLE.

If the property is not available
The function returns BT_PROPERTY_AVAILABILITY_NOT_AVAILABLE.

Many libbabeltrace2 functions return a status code, that is, a C enumerator containing the word STATUS. For example, bt_value_copy() returns either BT_VALUE_COPY_STATUS_OK or BT_VALUE_COPY_STATUS_MEMORY_ERROR.

Although the API guarantees that any status enumerator which has the _OK status has the value 0, we recommend that you compare the returned value to exact status enumerators for clarity, for example:

bt_value_copy_status status = bt_value_copy(obj, &val_copy);
if (status != BT_VALUE_COPY_STATUS_OK) {
/* handle error */
}

The API reference modules document, for each function, what each return status enumerator means.

Some functions return properties or objects by output parameter. When such a function which accepts a property or object pointer ptr fails, the library does not guarantee that *ptr remains unchanged. Therefore, such a pattern is not safe:

bt_some_object *some_object = NULL;
status = bt_get_some_object(obj, &some_object);
if (some_object) {
/* ... */
}

Always rely on the returned status code:

bt_some_object *some_object;
status = bt_get_some_object(obj, &some_object);
if (status == BT_GET_SOME_OBJECT_STATUS_OK) {
/* ... */
}

User classes

The whole Babeltrace 2 project is about extensibility: you can implement component classes, and then package and distribute them as plugins.

When you implement a Babeltrace 2 component class, you override protected methods, just like you would do in any object-oriented programming (OOP) language.

Here's the mapping of typical OOP language features to the Babeltrace 2 library domain:

OOP concept Babeltrace 2 equivalent
User class.

Class object with implemented user functions.

For example: bt_component_class_source.

User class instance.

Instance object, created from a class object.

For example: bt_component_source.

Instance pointer (this keyword in C++/Java and self variable in Python, for example).

"Self" (private) object.

A "self" object has a specific, dedicated C type which starts with bt_self_.

For example: bt_self_component_source.

Protected, final method.

Library function accepting an instance pointer ("self" object) as its first parameter.

Those functions always start with bt_self_.

For example: bt_self_component_source_add_output_port().

Protected, overridable method.

User function with a specific signature, accepting an instance pointer ("self" object) as its first parameter.

For example: bt_component_class_source_initialize_method.

Private user method. Custom static user function having access to the instance pointer ("self" object) somehow.
Private user property or attribute. Custom void * data which you set and get using dedicated protected methods (for example, bt_self_component_set_data() and bt_self_component_get_data()).

Error reporting

libbabeltrace2 features a rich error reporting mechanism to augment an error with custom causes without having to explicitly pass an error object to the library functions.

When a library function or user method returns an error status code (any status enumerator which contains the word ERROR), it can add one or more error causes to the current thread's error object.

This makes it possible for the end user to understand the contexts which lead to the error, possibly across many plugins written by different developers.

An error cause contains information about the source location where the error occured, the actor involved in the error, and a message.

When you "catch" an error, that is, react to a function returning an error status code without returning an error status code yourself, you can:

Attention
You cannot call any libbabeltrace2 function when the current thread has an error, except the reference counting functions (ending with _get_ref() or _put_ref()).

The babeltrace2 CLI uses this feature to pretty-print an error's causes to the end user, for example:

ERROR: [Babeltrace CLI] (babeltrace2.c:2521)
Cannot create components.
CAUSED BY [Babeltrace CLI] (babeltrace2.c:2336)
Cannot create component: plugin-name="ctf", comp-cls-name="fs", comp-cls-type=0,
comp-name="auto-disc-source-ctf-fs"
CAUSED BY [libbabeltrace2] (graph.c:1343)
Component initialization method failed: status=ERROR, comp-addr=0x562fbd275f40,
comp-name="auto-disc-source-ctf-fs", comp-log-level=WARNING, comp-class-type=SOURCE,
comp-class-name="fs", comp-class-partial-descr="Read CTF traces from the file sy",
comp-class-is-frozen=1, comp-class-so-handle-addr=0x562fbd285810,
comp-class-so-handle-path="/usr/lib/babeltrace2/plugins/babeltrace-plugin-ctf.so",
comp-input-port-count=0, comp-output-port-count=0
CAUSED BY [auto-disc-source-ctf-fs: 'source.ctf.fs'] (fs.c:1148)
Cannot create trace for `/path/to/trace`.
CAUSED BY [auto-disc-source-ctf-fs: 'source.ctf.fs'] (fs.c:928)
Cannot add stream file `/path/to/trace/channel0_1` to stream file group
CAUSED BY [auto-disc-source-ctf-fs: 'source.ctf.fs'] (fs.c:734)
Cannot get stream file's first packet's header and context fields (`/path/to/trace/channel0_1`).

Logging

libbabeltrace2 contains many hundreds of logging statements to help you follow and debug your plugin or program.

By default, the library's logging is disabled. To enable it, use bt_logging_set_global_level().

To set the library's initial logging level (checked once at library loading time), set the LIBBABELTRACE2_INIT_LOG_LEVEL environment variable, with one of:

  • N or NONE
  • F or FATAL
  • E or ERROR
  • W, WARN, or WARNING
  • I or INFO
  • D or DEBUG
  • T or TRACE

By default, the minimal, build-time logging level is DEBUG. We recommend that you build libbabeltrace2 with the TRACE minimal logging level for development. See Build Babeltrace 2 for development.

libbabeltrace2 writes its logging statements to the standard error stream.

A libbabeltrace2 (and Babeltrace 2 project plugin) logging line looks like this:

05-11 00:58:03.691 23402 23402 D VALUES bt_value_destroy@values.c:498 Destroying value: addr=0xb9c3eb0

The line contains, in order:

  1. The date and time (05-11 00:58:03.691).
  2. The process and thread IDs (23402 23402).
  3. The logging level (D for DEBUG).
  4. The function logging (bt_value_destroy).
  5. The file and line number logging (values.c:498).
  6. The message, which typically ends with a list of fields adding details.
bt_value
struct bt_value bt_value
Value.
Definition: types.h:107
bt_value_array_get_length
uint64_t bt_value_array_get_length(const bt_value *value)
Returns the length of the array value value.
bt_value_copy_status
bt_value_copy_status
Status codes for bt_value_copy().
Definition: value.h:2302
BT_VALUE_COPY_STATUS_OK
Success.
Definition: value.h:2307
bt_event_class_create
bt_event_class * bt_event_class_create(bt_stream_class *stream_class)
Creates a default event class and adds it to the stream class stream_class.
bt_value_copy
bt_value_copy_status bt_value_copy(const bt_value *value, bt_value **copy_value)
Deep-copies a value object.
bt_event_class
struct bt_event_class bt_event_class
Event class.
Definition: types.h:51