Logging and Tracing 4 - Getting data into impulse

Posted in impulse Log

By default, impulse supports a lot of ports and data formats. Nevertheless you might get into the situation where a given data format or interface are not support or a trace interface is not yet existing on device side. This article handle possibilities to get signal data into impulse.

This article is based on impulse version 1.3/1.4

Using flux trace

This examples creates the simple uncompressed trace and writes it into a file.

Preparing the memory

The first step in setting up a flux trace is calculating the required memory. The core of flux does not use any c-library methods. The required memories for buffers and tables need to be passed as arguments to the flxCreateXXXXBuffer and flxCreateTrace methods. In this example (and most others) we use the macros FLX_BUFFER_BYTES and FLX_TRACE_BYTES to calculate the memory size for buffer and trace and define static memory chunks.

Createing buffers and trace objects

In this example, we use a simple flat buffer, created by the method flxCreateFixedBuffer. The third and fourth arguments are the 'write to file' handler and the file object. For the trace creation we need the following arguments:

the traceId,

the max itemId,

the maxEntrySize value,

the trace object memory and size (this is for trace handling, not for the trace data itself)

and the initial buffer (may be changed later) to store the trace data.

Adding items

The trace object is prepared and we can start to add content. With flxAddHead we start the trace with a label and description of the following trace. The method flxAddSignal adds a signal to the trace. The arguments are:

the trace object,

itemId for the signal,

parentId, here 0 for the root scope,

a name and description (2 arguments),

the type of the signal

and a signal descriptor (not used in this example)

Open and close

With flxOpen we start a trace sequence and finalize it with flxClose. Both methods are used to define the domain range and the process. The parameters of flxOpen are:

the trace object,

itemId to be opened (0 == root; we open all items)

domainBase ("ns": the smallest delta between two samples is 1 ns )

start position (0ns)

sample rate (0: discrete process)

And flxClose has:

the trace object,

itemId to be opened (0 == root; we close all items)

end position (50000 * 10 ns)

To open an item id != 0, you need to offer a larger memory space to the trace object (more complex handling). You do this by setting the first argument of FLX_TRACE_BYTES to 1.

The flxWriteXXXAt methods are intended to add events and value changes to a signal. The domain position (e.g. when a change occurs) can be given absolute or relative. If you choose an absolute domain position, the value must to be equal or larger than the one used in a previous write (and larger than the open position). A relative domain position (means relative to a previous write) must be larger or equal to 0. The parameters:

the trace object,

itemId of the signal,

the conflict flag (samples with enabled conflict flags are painted red in impulse),

Using the legacy impulse trace format

With version 1.8, impulse has migrated to flux trace. Legacy trace emitters are still functional but should not be used for new projects.

The Impulse trace format (*.trace) is an open format targeting typical embedded system use-cases. The trace data is packed into a binary format. To generate a trace, you can use open-source multi-language emitters (currently C and Java).

To get signal and log data into impulse, you might use the emitter code to generate a binary stream, use any existing interfaces to send the binary (like serial device or files/pipes) and connect impulse to that interface using impulse ports (as seen in article 3 - Combining multiple sources and live data).

Below you find a simple example using different c data types. Beside text messages (for logs) you can easily trace variables of different types.

First step is to define the required signals. For each signal you define the path, process type, signal type, type descriptor and domain. As a result you get a descriptor, used for further operations on the signal.

Then you just write the values into the defined signals. There are dedicated write functions for each signal/data type.