QTools  6.3.7
QS Target Component

The target-resident component of the QP/Spy™ tracing system is called QS. The purpose of QS is to provide facilities for instrumenting the target code so that it will produce interesting real-time trace from code execution. In this sense it is similar to peppering the code with printf statements. However, the main difference between QS and printf is where the data formatting and sending is done. When you use printfs, the data formatting and sending occur in the time-critical paths through your embedded code. In contrast, the QS target-resident component inserts raw binary data into the QS ring buffer, so all the time-consuming formatting is removed from the Target system and is done after the fact in the Host. Additionally, in QS, data logging and sending to the Host are separated so that the target system can typically perform the transmission outside of the time-critical path, for example in the idle processing of the target CPU.

The QS target component consists of the QS ring buffer, the QS filters, as well as the instrumentation added to the QP framework and the application, as shown in figure below. Additionally, the QS target component contains the receive-channel (QS-RX) with its own receive buffer, which can receive data from the QSPY host component.

qspy5.gif
Structure of the QP/Spy software tracing system

A nice byproduct of removing the data formatting from the Target is a natural data compression. For example, formatted output of a single byte takes two hexadecimal digits(and 3 decimal digits), so avoiding the formatting gives at least a factor of two in data density. On top of this natural compression, QS uses such techniques as data dictionaries, and compressed format information, which in practice result in compression factor of 4-5 compared to the expanded human-readable format.

Attention
The QS instrumentation is designed such that it is active only in the Spy build configuration (when the external macro Q_SPY is defined) and is inactive otherwise. This means that you don't need to comment-out or remove the instrumentation in the Debug or Release build configurations, but rather you can leave the instrumentation in your code for its future development, testing, profiling and maintenance.

Target Time-Stamps

Most QS trace records produced by QS are time-stamped with a high-resolution counter (the resolution depends on the availability of a hardware timer-counter in the Target, but typically provides sub-microsecond granularity). QS provides an efficient API for obtaining platform-specific timestamp information. Given the right timer-counter resource in you Target system, you can provide QS with as precise timestamp information as required. The size of the timestamp is configurable to be 1, 2, or 4 bytes (see QS_TIME_SIZE).

Note
The QS timestamps are used differently in the QUTest™ unit testing. In that case the timestamps are used to count the QS records produced.

Predefined QS Trace Records

The QP/C and QP/C++ frameworks contain the QS instrumentation for tracing the interesting occurrences within the frameworks, such as state machines activity (dispatching events, entering/exiting a state, executing transitions, etc.), active object activity (allocating events, posting/publishing events, time events, etc.), and more. All this instrumentation generates some 70 predefined QS trace records, which are enumerated in QSpyRecords. These QS records have pre-defined (hard-coded) structure both in the QS target-resident component and the QSPY host-based application. See also the documentation of the human-readable output generated from the predefined QS records.


Application-Specific QS Trace Records

Additionally to the predefined QS records, you can add your own, flexible, application-specific trace records, which are not known in advance to the QSPY host-resident component. You can think of the application-specific records as an equivalent to printf() but with much less overhead. The following code snippet shows an example of an application-specific QS record from your embedded code:

enum MyAppRecords {
PHILO_STAT = QS_USER, /* offset for User QS Records */
COMMAND_STAT,
~ ~ ~
};
~ ~ ~
QS_BEGIN(PHILO_STAT, AO_Philo[n]) /* application-specific record begin */
QS_U8(1, n); /* application-specific data element (Philo number) */
QS_STR(stat); /* application-specific data element (Philo status) */
QS_END() /* application-specific record end */

As you can see from the example above, an application-specific trace record always begins with QS_BEGIN(), followed by a number of application-specific data elements, followed by QS_END().

Attention
The application-specific records use the 55 record-IDs in the range QS_USER .. 124 (see also User-All group in Global Filters). Therefore, you need to offset your application-specific trace records by the QS_USER constant to avoid overlap with the predefined QS records already instrumented into the QP components.
Note
As all QS trace records, the application-specific records are produced in a critical section of code (interrupts disabled). Therefore, you should keep the number of application-specific data elements of the record reasonably short.

Application-Specific Record Representation

The biggest challenge in supporting flexible "application-specific" trace records is to provide the data type information with the data itself, so that QSPY "knows" how to parse such record and move on to the next data element within the record. The figure below shows the encoding of the application-specific trace record from the listing previous.

qspy_app.gif
Structure of an Application-Specific trace record

The application-specific trace record, like all QS records, starts with the Sequence Number and the Record-ID. Every application-specific trace record also contains the timestamp immediately following the Record ID. The number of bytes used by the timestamp is configurable by the macro QS_TIME_SIZE. After the timestamp, you see the data elements, such as a byte (QS_U8()) and a string (QS_STR()). Each of these data elements starts with a fmt (format) byte, which actually contains both the data-type information (in the lower nibble) and the format width for displaying that element (in the upper nibble). For example, the data element QS_U8(1, n) will cause the value 'n' to be encoded as uint8_t with the format width of 1 decimal digits.

Remarks
The maximum allowed format width is 15 decimal digits, while format width of 0 means that a numeric value should be formatted in minimum number of digits.

As shown in the listing above, typically the application-specific records are enclosed with the QS_BEGIN() / QS_END() pair of macros. This pair of macros disables interrupts at the beginning and enables at the end of each record. Occasionally you would want to generate trace data from within already established critical sections or ISRs. In such rare occasions, you would use the macros QS_BEGIN_NOCRIT() / QS_END_NOCRIT() to avoid nesting of critical sections.

The record-begin macro QS_BEGIN() takes two arguments. The first argument (e.g., PHILO_STAT) is the enumerated record-ID, which is used in the global filter and is part of the each record header.

The second argument (e.g., AO_Philo[n] in the example above) is used for the local filter, which allows you to selectively log only specific application-level objects. The code snippet shows an example of an application-specific trace record, including the use of the second parameter of the QS_BEGIN() macro.

Application-Specific Data Elements

The following table summarizes the supported data elements that can be used inside the Application-Specific trace records:

Data Element Example Comments
QS_U8() QS_U8(0, n); Outputs a uint8_t integer with format "%u"
QS_I8() QS_I8(3, m); Outputs a int8_t integer with format "%3d"
QS_U16() QS_U16(5, n); Outputs a uint16_t integer with format "%5u"
QS_I16() QS_I16(0, m); Outputs a int16_t integer with format "%d"
QS_U32() QS_U32(8, n); Outputs a uint32_t integer with format "%8u"
QS_I32() QS_I32(0, m); Outputs a int32_t integer with format "%d"
QS_U32_HEX() QS_U32_HEX(8, h) Outputs a uint32_t integer with format "%8X"
QS_U64() QS_U32(0, n); Outputs a uint32_t integer with format "%2"PRIi64
QS_F32() QS_F32(0, 3.1415F); Outputs a 32-bit float with format "%7.0e"
(zero digits after the comma)
QS_F64() QS_F64(4, sqrt(2.0)); Outputs a 64-bit double with format "%12.4e"
(4 digits after the comma)
QS_STR() QS_STR("Hello") Outputs a zero-terminated string with format "%s"
QS_MEM() QS_MEM(&my_struct, 16) Outputs 16 bytes of memory starting from &my_struct.
The bytes are output using the hex format "%02X"
QS_OBJ() QS_OBJ(&my_obj); Outputs an object pointer.
If an object dictionary for the object exists,
QSPY will display the symbolic name of the object
QS_FUN() QS_OBJ(&foo); Outputs a function pointer.
If a function dictionary for the function exists,
QSPY will display the symbolic name of the function
QS_SIG() QS_SIG(TIMEOUT_SIG, (void*)0); Outputs a signal.
If siganl dictionary for the siganl exists,
QSPY will display the symbolic name of the signal

Application-Specific Record Examples

The following examples show the QS application-specific trace records as C code on the left, and the output generated by the QSPY host application from these records. The examples assume that the QS dictionaries have been produced for the Record-IDs and function/object pointers used.

Trace Record

QSPY ouptut

QS_BEGIN(PHILO_STAT, AO_Philo[n])
QS_U8(1, n);
QS_STR(stat);

1018004718 PHILO_STAT 1 thinking

QS_BEGIN(IO_CALL, (void *)0)
QS_FUN(&IO_Read);
QS_I16(0, ret);
QS_U32(0, offset);

1055004424 IO_CALL IO_Read -129 0

QS_BEGIN(DATA_RX, (void *)0)
QS_OBJ(me);
QS_U16(data_len);
QS_MEM(data_buf, 16);

0207024814 DATA_RX l_uart2 10 17 84 BB 40 FD 15 00 00 99 0B 00 00 90 0D 00 20

QS_BEGIN(FP_DATA, (void *)0)
QS_F32(6, 3141.5F);
QS_F64(10, -2.718281828e5);

0991501750 FP_DATA 3.141500e+003 -2.7182818280e+005


QS Filters

Obviously, QS cannot eliminate completely the overhead of software tracing. But with the fine-granularity filters available in QS, you can make this impact as small as necessary. For greatest flexibility, QS uses two complementary levels of filters: Global Filters and Local Filters described below. Combination of such two complementary filtering criteria results in very selective tracing capabilities.

Note
The Global and Local filters are initialized in the Target. Subsequently, if the QS receive channel is enabled (QS-RX), the filters can be changed from the QUTest or QSpyView front-ends at runtime.

Global Filters

The Global Filters are based on trace Record-ID associated with each QS record (see QSpyRecords). This filter allows you to disable or enable each individual Record-ID, such as entry to a state (QS_QEP_STATE_ENTRY), exit from a state (QS_QEP_STATE_EXIT), state transition (QS_QEP_INIT_TRAN ), event posting (QS_QF_ACTIVE_POST_FIFO), event publishing (QS_QF_PUBLISH ), time event expiration (QS_QF_TIMEEVT_POST), and all other pre-defined and application-specific event types. This level works globally for all state machines, active objects, and time event objects in the entire system.

QS provides a simple interface for setting and clearing individual Record-IDs as well as groups of Record-IDs in the Target code:

The following table summarizes the Record-IDs and groups of Record-IDs that you can use as arguments to QS_FILTER_ON() and QS_FILTER_OFF()


Record-ID Group Example Applies to QS Records
All Record-IDs QS_FILTER_ON(QS_ALL_RECORDS);
QS_FILTER_OFF(QS_ALL_RECORDS);
all Record-IDs
State
Machine
QS_FILTER_ON(QS_SM_RECORDS);
QS_FILTER_OFF(QS_SM_RECORDS);
QS_QEP_STATE_ENTRY,
QS_QEP_STATE_EXIT,
QS_QEP_STATE_INIT,
QS_QEP_INIT_TRAN,
QS_QEP_INTERN_TRAN,
QS_QEP_TRAN,
QS_QEP_IGNORED,
QS_QEP_TRAN_HIST,
QS_QEP_TRAN_EP,
QS_QEP_TRAN_XP
Active
Object
QS_FILTER_ON(QS_AO_RECORDS);
QS_FILTER_OFF(QS_AO_RECORDS);
#QS_QF_ACTIVE_ADD,
#QS_QF_ACTIVE_REMOVE,
QS_QF_ACTIVE_SUBSCRIBE,
QS_QF_ACTIVE_UNSUBSCRIBE,
QS_QF_ACTIVE_POST_FIFO,
QS_QF_ACTIVE_POST_LIFO,
QS_QF_ACTIVE_GET,
QS_QF_ACTIVE_GET_LAST
Event
Queue
QS_FILTER_ON(QS_EQ_RECORDS);
QS_FILTER_OFF(QS_EQ_RECORDS);
#QS_QF_EQUEUE_INIT,
QS_QF_EQUEUE_POST_FIFO,
QS_QF_EQUEUE_POST_LIFO,
QS_QF_EQUEUE_GET,
QS_QF_EQUEUE_GET_LAST
Memory
Pool
QS_FILTER_ON(QS_MP_RECORDS);
QS_FILTER_OFF(QS_MP_RECORDS);
#QS_QF_MPOOL_INIT,
QS_QF_MPOOL_GET,
QS_QF_MPOOL_PUT
Time
Event
QS_FILTER_ON(QS_TE_RECORDS);
QS_FILTER_OFF(QS_TE_RECORDS);
QS_QF_TICK,
QS_QF_TIMEEVT_ARM,
QS_QF_TIMEEVT_AUTO_DISARM,
QS_QF_TIMEEVT_DISARM_ATTEMPT,
QS_QF_TIMEEVT_DISARM,
QS_QF_TIMEEVT_REARM,
QS_QF_TIMEEVT_POST
Event Management (QF) QS_FILTER_ON(QS_QF_RECORDS);
QS_FILTER_OFF(QS_QF_RECORDS);
QS_QF_NEW,
QS_QF_GC_ATTEMPT,
QS_QF_GC,
QS_QF_TIMEEVT_DISARM_ATTEMPT,
QS_QF_TICK,
Scheduler QS_FILTER_ON(QS_SC_RECORDS);
QS_FILTER_OFF(QS_SC_RECORDS);
QS_SCHED_LOCK,
QS_SCHED_UNLOCK,
QS_SCHED_NEXT,
QS_SCHED_IDLE,
QS_SCHED_RESUME,
QS_QF_TIMEEVT_DISARM_ATTEMPT,
QS_QF_TICK,
User Group-0 QS_FILTER_ON(QS_U0_RECORDS);
QS_FILTER_OFF(QS_U0_RECORDS);
QS_USER + 0 .. QS_USER + 9
User Group-1 QS_FILTER_ON(QS_U1_RECORDS);
QS_FILTER_OFF(QS_U1_RECORDS);
QS_USER + 10 .. QS_USER + 19
User Group-2 QS_FILTER_ON(QS_U2_RECORDS);
QS_FILTER_OFF(QS_U2_RECORDS);
QS_USER + 20 .. QS_USER + 29
User Group-3 QS_FILTER_ON(QS_U3_RECORDS);
QS_FILTER_OFF(QS_U3_RECORDS);
QS_USER + 30 .. QS_USER + 39
User Group-4 QS_FILTER_ON(QS_U4_RECORDS);
QS_FILTER_OFF(QS_U4_RECORDS);
QS_USER + 40 .. QS_USER + 54
User-All QS_FILTER_ON(QS_UA_RECORDS);
QS_FILTER_OFF(QS_UA_RECORDS);
QS_USER + 0 .. QS_USER + 54


Note
The QS global filter cannot disable a special class of QS records, which are called non-maskable. These non-maskable records include: dictionary records, target-information records, QS-RX status responses, QUTest-status responses, and the Target assertion record.

Local Filters

The Local Filters are component-specific. You can setup a filter to trace only a specific state machine object, only a specific active object, only a specific time event, etc. The following table summarizes the specific objects you can filter on:

Object
Type
Example Applies to QS Records
State
Machine
QS_FILTER_SM_OBJ(&l_qhsmTst); QS_QEP_STATE_ENTRY,
QS_QEP_STATE_EXIT,
QS_QEP_STATE_INIT,
QS_QEP_INIT_TRAN,
QS_QEP_INTERN_TRAN,
QS_QEP_TRAN,
QS_QEP_IGNORED,
QS_QEP_TRAN_HIST,
QS_QEP_TRAN_EP,
QS_QEP_TRAN_XP
Active
Object
QS_FILTER_AO_OBJ(&l_philo[3]); #QS_QF_ACTIVE_ADD,
#QS_QF_ACTIVE_REMOVE,
QS_QF_ACTIVE_SUBSCRIBE,
QS_QF_ACTIVE_UNSUBSCRIBE,
QS_QF_ACTIVE_POST_FIFO,
QS_QF_ACTIVE_POST_LIFO,
QS_QF_ACTIVE_GET,
QS_QF_ACTIVE_GET_LAST
Event
Queue
QS_FILTER_EQ_OBJ(l_philQueueSto[0]); #QS_QF_EQUEUE_INIT,
QS_QF_EQUEUE_POST_FIFO,
QS_QF_EQUEUE_POST_LIFO,
QS_QF_EQUEUE_GET,
QS_QF_EQUEUE_GET_LAST
Memory
Pool
QS_FILTER_MP_OBJ(l_medPoolSto); #QS_QF_MPOOL_INIT,
QS_QF_MPOOL_GET,
QS_QF_MPOOL_PUT
Time
Event
QS_FILTER_TE_OBJ(&l_philo[0] timeEvt); QS_QF_TICK,
QS_QF_TIMEEVT_ARM,
QS_QF_TIMEEVT_AUTO_DISARM,
QS_QF_TIMEEVT_DISARM_ATTEMPT,
QS_QF_TIMEEVT_DISARM,
QS_QF_TIMEEVT_REARM,
QS_QF_TIMEEVT_POST
Generic
Application
Object
QS_FILTER_AP_OBJ(&myAppObj); Application-specific records
starting with QS_USER
Note
Local filters can be disabled by setting them to NULL. For example, if you wish to receive data from all state machine objects, you should set the State Machine local filter to NULL ( QS_FILTER_SM_OBJ((void*)0 ).

Current Objects

QS maintains a set of Current Objects, to which it applies commands received through the QS-RX channel. For example, the event-post operation is applied to the current Active Object, while the peek/poke/fill operations are applied to the current Application Object. QS maintains the following Current Objects:

  • State Machine object
  • Active Object object
  • Memory Pool object
  • Event Queue object
  • Time Event object
Note
Current Objects can be set only by sending commands to the QS-RX receive channel.

QS Dictionaries

By the time you compile and load your application image to the Target, the symbolic names of various objects, function names, and event signal names are stripped from the code. Therefore, if you want to have the symbolic information available to the QSPY host-resident component, you need to supply it somehow to the software tracing system.

The QS Target-resident component provides special dictionary trace records designed expressly for providing the symbolic information about the target code in the trace itself. These "dictionary records" are very much like the symbolic information embedded in the object files for the traditional single-step debugger. QS can supply four types of dictionary trace records:

The dictionary trace records are typically generated during the system initialization and this is the only time when they are sent to the QSPY host component. It is your responsibility to code them in (by means of the QS_???_DICTIONARY() macros). The following code snippet provides some examples of generating QS dictionaries:

void main(void) {
~ ~ ~
/* global filter */
QS_FILTER_ON(QS_ALL_RECORDS); /* enable all maskable filters */
/* dictionaries... */
QS_USR_DICTIONARY(ON_TEST_SETUP);
QS_USR_DICTIONARY(ON_TEST_TEARDOWN);
QS_USR_DICTIONARY(COMMAND_X);
~ ~ ~
}
Note
The dictionary trace records are not absolutely required to generate the human-readable output, in the same way as the symbolic information in the object files is not absolutely required to debug your code. However, in both cases, the availability of the symbolic information greatly improves the productivity in working with the software trace or the debugger.

QS-RX Receive-Channel

The QS target component contains the receive-channel (QS-RX), which can receive data from the QSPY host application. The QS-RX channel provides the following services:

  • Remotely reset of the Target
  • Request target information (version, all sizes of objects, build time-stamp)
  • Execute a user-defined command inside the Target with arguments supplied from QSPY
  • Inject an arbitrary event to the Target (dispatch, post or publish)
  • Set global QS filters inside the Target
  • Set local QS filters inside the Target
  • Set current QS object inside the Target
  • Peek data inside the Target and send to QSPY
  • Poke data (supplied from QSPY) into the Target
  • Fill specified memory area in the Target with bit pattern supplied from QSPY
  • Execute clock tick inside the Target
  • Execute test setup inside the Target
  • Execute test teardown inside the Target
  • Store a Test Probe supplied from QSPY inside the Target
Note
The QS-RX channel is the backbone for interacting with the target system and implemeting such features as Unit Testing and Visualization/Monitoring of the target system.

Next: QSPY Host Application