Logging and Tracing 4 - Getting data into impulse

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. 
#define MAX_ITEM_ID 2 // maximum id of scope/signal
#define MAX_ENTRY_SIZE 4096

	// output file
	FILE *out = fopen("flux.example.recTr", "wb");

	// calculate required memory for trace and buffers
	unsigned bufferSize = FLX_BUFFER_BYTES(MAX_ENTRY_SIZE);
	unsigned traceSize = FLX_TRACE_BYTES(0, MAX_ITEM_ID);

	// static memory
	unsigned char memoryBuffer[bufferSize];
	unsigned char memoryTrace[traceSize];

	// buffer
	flxBuffer buffer = flxCreateFixedBuffer(memoryBuffer, bufferSize,
			flxWriteToFile, out);

	// trace
	flxTrace trace = flxCreateTrace(0, MAX_ITEM_ID, MAX_ENTRY_SIZE, memoryTrace,
			traceSize, buffer);

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.
 
	// head
	flxAddHead(trace, "example", "flux example");

	// add signals
	// parent 0 is root
	flxAddSignal(trace, 1, 0, "integer", "an integer", FLX_TYPE_INTEGER, 0);
	flxAddSignal(trace, 2, 0, "float", "a float", FLX_TYPE_FLOAT, 0);

	// open
	flxOpen(trace, 0, "ns", 0, 0);
    
	// generate example trace
	for (n = 0; n < 500000; n++) {

		// time in ns
		flxdomain current = n * 10;

		// integer
		iVal = n % 444;
		flxWriteIntAt(trace, 1, 0, current, 0, &iVal, sizeof(int), 0);

		// float - same time - use domain=0; isDelta=1
		fVal = sin(n / 1000.0);
		flxWriteFloatAt(trace, 2, 0, 0, 1, &fVal, sizeof(float));
	}

	// close
	flxClose(trace, 0, n * 10);

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),
  • domain position,
  • isRelative flag (1: relative, 0:absolute),
  • pointer to the value,
  • and finally the size of the value in bytes.


For further infornation get to the flux trace main page.

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.

A03 recTr Trace Record Format

int main( int argc, const char* argv[] )
{	
	int n = 0;
	ptr_myfile=fopen("test.trace","wb");	
	
	int h1 = IpAddSignal("Integer/writeInt64",IpDiscrete,IpInteger,0,"ns");
	int h2 = IpAddSignal("Integer/writeInt32",IpDiscrete,IpInteger,0,"ns");
	int h3 = IpAddSignal("Integer/writeInt16",IpDiscrete,IpInteger,0,"ns");
	int h4 = IpAddSignal("Integer/writeInt8",IpDiscrete,IpInteger,0,"ns");
	int h5 = IpAddSignal("Integer/writeIntU64",IpDiscrete,IpInteger,0,"ns");
	int h6 = IpAddSignal("Integer/writeIntU32",IpDiscrete,IpInteger,0,"ns");
	int h7 = IpAddSignal("Integer/writeIntU16",IpDiscrete,IpInteger,0,"ns");
	int h8 = IpAddSignal("Integer/writeIntU8",IpDiscrete,IpInteger,0,"ns");
	int h9 = IpAddSignal("Float/writeFloat32",IpDiscrete,IpFloat,0,"ns");
	int h10 = IpAddSignal("Float/writeFloat64",IpDiscrete,IpFloat,0,"ns");
	int h11 = IpAddSignal("Text/writeText",IpDiscrete,IpText,0,"ns");
	int h12 = IpAddSignal("Event/writeEvent",IpDiscrete,IpEvent,0,"ns");
	
	IpSetOutput(IpDefaultOutput);
	
	for (n=0;n<1000;n++){
		long long current = n * 10;
		
	#ifndef __cplusplus
		IpWriteInt64(h1,current,n==100,n-1000);
		IpWriteInt32(h2,current,n==100,-n);
		IpWriteInt16(h3,current,n==100,n*3-100);
		IpWriteInt8(h4,current,n==100,(char)n);
		IpWriteIntU64(h5,current,n==100,n);
		IpWriteIntU32(h6,current,n==100,n%100);
		IpWriteIntU16(h7,current,n==100,n);
		IpWriteIntU8(h8,current,n==100,n*8);
		IpWriteFloat32(h9,current,n==100,(float)sin(n/23.0));
		IpWriteFloat64(h10,current,n==100,(double)cos(n/55.0)*10);
	#else
		IpWriteInt(h1,current,n==100,n-1000);
		IpWriteInt(h2,current,n==100,-n);
		IpWriteInt(h3,current,n==100,n*3-100);
		IpWriteInt(h4,current,n==100,(char)n);
		IpWriteInt(h5,current,n==100,n);
		IpWriteInt(h6,current,n==100,n%100);
		IpWriteInt(h7,current,n==100,n);
		IpWriteInt(h8,current,n==100,n*8);
		IpWriteFloat(h9,current,n==100,(float)sin(n/23.0));
		IpWriteFloat(h10,current,n==100,(double)cos(n/55.0)*10);
	#endif
		if (!(n%100))
			IpWriteText(h11,current,n==100,"text");
		IpWriteEvent(h12,current,n==100,n%8);
	}
	IpCloseAllSignals(10000);
	fclose(ptr_myfile);
}


void IpWriteOutput(int output, byte* buffer, int pos, int length) {
	fwrite(buffer+pos,length, 1, ptr_myfile);
}

Adding a new reader

If you have already defined or selected a trace format you can easily implement a reader for impulse.

Beside handling and parsing your data format, you need to define signals and write values into that signals - very similar to the trace format interface above.

You find ready to be used examples in the Impulse Extension Toolkit and a lot of articles getting you on the path.

Extending Impulse

 		// Init the record
        initRecord("Example Record", TimeBase.ns);
        Scope system = addScope(null, "System"); // null means at root
        Signal float1 = addSignal(system, "Signal1", "My first float Signal", 
        		ProcessType.Discrete, SignalType.Float, SignalDescriptor.Float64);
        ...
        
        ((IFloatSamplesWriter) getWriter(float1)).write(t, Math.sin((t + 100) / 3000.0) * 10.0);        		        		

Adding a new port

If you got a special interface or a piece of hardware to be connected, implementing an impulse port might be a solution for you.

A port is slightly more complicated than a reader, as you need to add a dialog and implement a larger interface.

Again you find several examples in the Impulse Extension Toolkit. You can start with these examples and migrate them into your target direction.

If you have problems, just Contact us

Next article is about understanding logs!

Print

User Rating: 0 / 5

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive