4. Examples

This section presents a few short and straightforward examples of Babeltrace Python bindings usage.

The examples are divided into two categories: those which demonstrate the reader API, and those which demonstrate the CTF writer API.

4.1. Reader API examples

The reader API includes everything needed to open traces and iterate on events in order.

4.1.1. Open one trace and print all event names

This example shows how to open a single CTF trace, iterate on all the events, and print their names.

import babeltrace.reader
import sys


# get the trace path from the first command line argument
trace_path = sys.argv[1]

trace_collection = babeltrace.reader.TraceCollection()

trace_collection.add_trace(trace_path, 'ctf')

for event in trace_collection.events:
    print(event.name)

4.1.2. Open multiple traces and print all event field names

This example opens multiple CTF traces (their paths are provided as command line arguments), iterates on all their correlated events in order, and prints a list of their field names.

import babeltrace.reader
import sys


trace_collection = babeltrace.reader.TraceCollection()

for path in sys.argv[1:]:
    trace_collection.add_trace(path, 'ctf')

for event in trace_collection.events:
    print(', '.join(event.keys()))

4.1.4. Bonus: top 5 running processes using LTTng

Since LTTng produces CTF traces, the Babeltrace Python binding can read LTTng traces.

This somewhat more complex example reads a whole LTTng Linux kernel trace, and outputs the short names of the top 5 running processes on CPU 0 during the whole trace.

from collections import Counter
import babeltrace.reader
import sys


# a trace collection holds one or more traces
col = babeltrace.reader.TraceCollection()

# add the trace provided by the user (first command line argument)
# (LTTng traces always have the 'ctf' format)
if col.add_trace(sys.argv[1], 'ctf') is None:
    raise RuntimeError('Cannot add trace')

# this counter dict will hold execution times:
#
#   task command name -> total execution time (ns)
exec_times = Counter()

# this holds the last `sched_switch` timestamp
last_ts = None

# iterate on events
for event in col.events:
    # keep only `sched_switch` events
    if event.name != 'sched_switch':
        continue

    # keep only events which happened on CPU 0
    if event['cpu_id'] != 0:
        continue

    # event timestamp
    cur_ts = event.timestamp

    if last_ts is None:
        # we start here
        last_ts = cur_ts

    # previous task command (short) name
    prev_comm = event['prev_comm']

    # initialize entry in our dict if not yet done
    if prev_comm not in exec_times:
        exec_times[prev_comm] = 0

    # compute previous command execution time
    diff = cur_ts - last_ts

    # update execution time of this command
    exec_times[prev_comm] += diff

    # update last timestamp
    last_ts = cur_ts

# print top 5
for name, ns in exec_times.most_common(5):
    s = ns / 1000000000
    print('{:20}{} s'.format(name, s))

4.1.5. Inspect event declarations and their field declarations

When babeltrace.reader.TraceCollection.add_trace() is called and a trace is successfully opened and added, a corresponding babeltrace.reader.TraceHandle object for this trace is returned. It is then possible to iterate on the event declarations of this trace handle using babeltrace.reader.TraceHandle.events. Each generated babeltrace.reader.EventDeclaration object contains common properties for this type of event, including its field declarations. This is useful for inspecting the available events of a trace, and their “signature” in terms of fields, before iterating its actual events.

This example adds a trace to a trace collection, and uses the returned trace handle to iterate on its event declarations. The goal here is to make sure the sched_switch event exists, and that it contains at least the following fields:

  • prev_comm, which should be an array of 8-bit integers
  • prev_tid, which should be an integer
import babeltrace.reader as btr
import sys


def validate_sched_switch_fields(event_decl):
    found_prev_comm = False
    found_prev_tid = False

    for field_decl in event_decl.fields:
        if field_decl.name == 'prev_comm':
            if isinstance(field_decl, btr.ArrayFieldDeclaration):
                elem_decl = field_decl.element_declaration

                if isinstance(elem_decl, btr.IntegerFieldDeclaration):
                    if elem_decl.size == 8:
                        found_prev_comm = True
        elif field_decl.name == 'prev_tid':
            if isinstance(field_decl, btr.IntegerFieldDeclaration):
                found_prev_tid = True

    return found_prev_comm and found_prev_tid


# get the trace path from the first command line argument
trace_path = sys.argv[1]

trace_collection = btr.TraceCollection()
trace_handle = trace_collection.add_trace(trace_path, 'ctf')
sched_switch_found = False

for event_decl in trace_handle.events:
    if event_decl.name == 'sched_switch':
        if validate_sched_switch_fields(event_decl):
            sched_switch_found = True
            break

print('trace path: {}'.format(trace_handle.path))

if sched_switch_found:
    print('found sched_switch!')
else:
    print('could not find sched_switch')

4.2. CTF writer API examples

The CTF writer API is a set of classes which allows a Python script to write complete CTF (Common Trace Format) traces.

4.2.1. One trace, one stream, one event, one field

This is the most simple example of using the CTF writer API. It creates one writer (responsible for writing one trace), then uses it to create one stream. One event with a single field is appended to this single stream, and everything is flushed.

The trace is written in a temporary directory (its path is printed at the beginning of the script).

import babeltrace.writer as btw
import tempfile


# temporary directory holding the CTF trace
trace_path = tempfile.mkdtemp()

print('trace path: {}'.format(trace_path))

# our writer
writer = btw.Writer(trace_path)

# create one default clock and register it to the writer
clock = btw.Clock('my_clock')
clock.description = 'this is my clock'
writer.add_clock(clock)

# create one default stream class and assign our clock to it
stream_class = btw.StreamClass('my_stream')
stream_class.clock = clock

# create one default event class
event_class = btw.EventClass('my_event')

# create one 32-bit signed integer field
int32_field_decl = btw.IntegerFieldDeclaration(32)
int32_field_decl.signed = True

# add this field declaration to our event class
event_class.add_field(int32_field_decl, 'my_field')

# register our event class to our stream class
stream_class.add_event_class(event_class)

# create our single event, based on our event class
event = btw.Event(event_class)

# assign an integer value to our single field
event.payload('my_field').value = -23

# create our single stream
stream = writer.create_stream(stream_class)

# append our single event to our single stream
stream.append_event(event)

# flush the stream
stream.flush()

4.2.2. Basic CTF fields

This example writes a few events with basic CTF fields: integers, floating point numbers, enumerations and strings.

The trace is written in a temporary directory (its path is printed at the beginning of the script).

import babeltrace.writer as btw
import babeltrace.common
import tempfile
import math


trace_path = tempfile.mkdtemp()

print('trace path: {}'.format(trace_path))


writer = btw.Writer(trace_path)

clock = btw.Clock('my_clock')
clock.description = 'this is my clock'
writer.add_clock(clock)

stream_class = btw.StreamClass('my_stream')
stream_class.clock = clock

event_class = btw.EventClass('my_event')

# 32-bit signed integer field declaration
int32_field_decl = btw.IntegerFieldDeclaration(32)
int32_field_decl.signed = True
int32_field_decl.base = btw.IntegerBase.HEX

# 5-bit unsigned integer field declaration
uint5_field_decl = btw.IntegerFieldDeclaration(5)
uint5_field_decl.signed = False

# IEEE 754 single precision floating point number field declaration
float_field_decl = btw.FloatingPointFieldDeclaration()
float_field_decl.exponent_digits = btw.FloatingPointFieldDeclaration.FLT_EXP_DIG
float_field_decl.mantissa_digits = btw.FloatingPointFieldDeclaration.FLT_MANT_DIG

# enumeration field declaration (based on the 5-bit unsigned integer above)
enum_field_decl = btw.EnumerationFieldDeclaration(uint5_field_decl)
enum_field_decl.add_mapping('DAZED', 3, 11)
enum_field_decl.add_mapping('AND', 13, 13)
enum_field_decl.add_mapping('CONFUSED', 17, 30)

# string field declaration
string_field_decl = btw.StringFieldDeclaration()
string_field_decl.encoding = babeltrace.common.CTFStringEncoding.UTF8

event_class.add_field(int32_field_decl, 'my_int32_field')
event_class.add_field(uint5_field_decl, 'my_uint5_field')
event_class.add_field(float_field_decl, 'my_float_field')
event_class.add_field(enum_field_decl, 'my_enum_field')
event_class.add_field(int32_field_decl, 'another_int32_field')
event_class.add_field(string_field_decl, 'my_string_field')

stream_class.add_event_class(event_class)

stream = writer.create_stream(stream_class)

# create and append first event
event = btw.Event(event_class)
event.payload('my_int32_field').value = 0xbeef
event.payload('my_uint5_field').value = 17
event.payload('my_float_field').value = -math.pi
event.payload('my_enum_field').value = 8  # label: 'DAZED'
event.payload('another_int32_field').value = 0x20141210
event.payload('my_string_field').value = 'Hello, World!'
stream.append_event(event)

# create and append second event
event = btw.Event(event_class)
event.payload('my_int32_field').value = 0x12345678
event.payload('my_uint5_field').value = 31
event.payload('my_float_field').value = math.e
event.payload('my_enum_field').value = 28  # label: 'CONFUSED'
event.payload('another_int32_field').value = -1
event.payload('my_string_field').value = trace_path
stream.append_event(event)

stream.flush()

4.2.3. Static array and sequence fields

This example demonstrates how to write static array and sequence fields. A static array has a fixed length, whereas a sequence reads its length dynamically from another (integer) field.

In this example, an event is appended to a single stream, in which three fields are present:

  • seqlen, the dynamic length of the sequence seq (set to the number of command line arguments)
  • array, a static array of 23 16-bit unsigned integers
  • seq, a sequence of seqlen strings, where the strings are the command line arguments

The trace is written in a temporary directory (its path is printed at the beginning of the script).

import babeltrace.writer as btw
import babeltrace.common
import tempfile
import sys


trace_path = tempfile.mkdtemp()

print('trace path: {}'.format(trace_path))


writer = btw.Writer(trace_path)

clock = btw.Clock('my_clock')
clock.description = 'this is my clock'
writer.add_clock(clock)

stream_class = btw.StreamClass('my_stream')
stream_class.clock = clock

event_class = btw.EventClass('my_event')

# 16-bit unsigned integer field declaration
uint16_field_decl = btw.IntegerFieldDeclaration(16)
uint16_field_decl.signed = False

# array field declaration (23 16-bit unsigned integers)
array_field_decl = btw.ArrayFieldDeclaration(uint16_field_decl, 23)

# string field declaration
string_field_decl = btw.StringFieldDeclaration()
string_field_decl.encoding = babeltrace.common.CTFStringEncoding.UTF8

# sequence field declaration of strings (length will be the `seqlen` field)
seq_field_decl = btw.SequenceFieldDeclaration(string_field_decl, 'seqlen')

event_class.add_field(uint16_field_decl, 'seqlen')
event_class.add_field(array_field_decl, 'array')
event_class.add_field(seq_field_decl, 'seq')

stream_class.add_event_class(event_class)

stream = writer.create_stream(stream_class)

# create event
event = btw.Event(event_class)

# set sequence length field
event.payload('seqlen').value = len(sys.argv)

# get array field
array_field = event.payload('array')

# populate array field
for i in range(array_field_decl.length):
    array_field.field(i).value = i * i

# get sequence field
seq_field = event.payload('seq')

# assign sequence field's length field
seq_field.length = event.payload('seqlen')

# populate sequence field
for i in range(seq_field.length.value):
    seq_field.field(i).value = sys.argv[i]

# append event
stream.append_event(event)

stream.flush()

4.2.4. Structure fields

A CTF structure is an ordered map of field names to actual fields, just like C structures. In fact, an event’s payload is a structure field, so structure fields may contain other structure fields, and so on.

This examples shows how to create a structure field from a structure field declaration, populate it, and write an event containing it as a payload field.

The trace is written in a temporary directory (its path is printed at the beginning of the script).

import babeltrace.writer as btw
import babeltrace.common
import tempfile


trace_path = tempfile.mkdtemp()

print('trace path: {}'.format(trace_path))


writer = btw.Writer(trace_path)

clock = btw.Clock('my_clock')
clock.description = 'this is my clock'
writer.add_clock(clock)

stream_class = btw.StreamClass('my_stream')
stream_class.clock = clock

event_class = btw.EventClass('my_event')

# 32-bit signed integer field declaration
int32_field_decl = btw.IntegerFieldDeclaration(32)
int32_field_decl.signed = True

# string field declaration
string_field_decl = btw.StringFieldDeclaration()
string_field_decl.encoding = babeltrace.common.CTFStringEncoding.UTF8

# structure field declaration
struct_field_decl = btw.StructureFieldDeclaration()

# add field declarations to our structure field declaration
struct_field_decl.add_field(int32_field_decl, 'field_one')
struct_field_decl.add_field(string_field_decl, 'field_two')
struct_field_decl.add_field(int32_field_decl, 'field_three')

event_class.add_field(struct_field_decl, 'my_struct')
event_class.add_field(string_field_decl, 'my_string')

stream_class.add_event_class(event_class)

stream = writer.create_stream(stream_class)

# create event
event = btw.Event(event_class)

# get event's structure field
struct_field = event.payload('my_struct')

# populate this structure field
struct_field.field('field_one').value = 23
struct_field.field('field_two').value = 'Achilles Last Stand'
struct_field.field('field_three').value = -1534

# set event's string field
event.payload('my_string').value = 'Tangerine'

# append event
stream.append_event(event)

stream.flush()

4.2.5. Variant fields

The CTF variant is the most versatile field type. It acts as a placeholder for any other type. Which type is selected depends on the current value of an outer enumeration field, known as a tag from the variant’s point of view.

Variants are typical constructs in communication protocols with dynamic types. For example, BSON, the protocol used by MongoDB, has specific numeric IDs for each element type.

This examples shows how to create a CTF variant field. The tag, an enumeration field, must also be created and associated with the variant. In this case, the tag selects between three types: a 32-bit signed integer, a string, or a floating point number.

The trace is written in a temporary directory (its path is printed at the beginning of the script).

import babeltrace.writer as btw
import babeltrace.common
import tempfile


trace_path = tempfile.mkdtemp()

print('trace path: {}'.format(trace_path))


writer = btw.Writer(trace_path)

clock = btw.Clock('my_clock')
clock.description = 'this is my clock'
writer.add_clock(clock)

stream_class = btw.StreamClass('my_stream')
stream_class.clock = clock

event_class = btw.EventClass('my_event')

# 32-bit signed integer field declaration
int32_field_decl = btw.IntegerFieldDeclaration(32)
int32_field_decl.signed = True

# string field declaration
string_field_decl = btw.StringFieldDeclaration()
string_field_decl.encoding = babeltrace.common.CTFStringEncoding.UTF8

# IEEE 754 single precision floating point number field declaration
float_field_decl = btw.FloatingPointFieldDeclaration()
float_field_decl.exponent_digits = btw.FloatingPointFieldDeclaration.FLT_EXP_DIG
float_field_decl.mantissa_digits = btw.FloatingPointFieldDeclaration.FLT_MANT_DIG

# enumeration field declaration (variant's tag)
enum_field_decl = btw.EnumerationFieldDeclaration(int32_field_decl)
enum_field_decl.add_mapping('INT', 0, 0)
enum_field_decl.add_mapping('STRING', 1, 1)
enum_field_decl.add_mapping('FLOAT', 2, 2)

# variant field declaration (variant's tag field will be named `vartag`)
variant_field_decl = btw.VariantFieldDeclaration(enum_field_decl, 'vartag')

# register selectable fields to variant
variant_field_decl.add_field(int32_field_decl, 'INT')
variant_field_decl.add_field(string_field_decl, 'STRING')
variant_field_decl.add_field(float_field_decl, 'FLOAT')

event_class.add_field(enum_field_decl, 'vartag')
event_class.add_field(variant_field_decl, 'var')

stream_class.add_event_class(event_class)

stream = writer.create_stream(stream_class)

# first event: integer is selected
event = btw.Event(event_class)
tag_field = event.payload('vartag')
tag_field.value = 0
event.payload('var').field(tag_field).value = 23
stream.append_event(event)

# second event: string is selected
event = btw.Event(event_class)
tag_field = event.payload('vartag')
tag_field.value = 1
event.payload('var').field(tag_field).value = 'The Battle of Evermore'
stream.append_event(event)

# third event: floating point number is selected
event = btw.Event(event_class)
tag_field = event.payload('vartag')
tag_field.value = 2
event.payload('var').field(tag_field).value = -15.34
stream.append_event(event)

stream.flush()