The barman data format is a simple struct based data format for in-memory and on disk storage of the Streamline Barman bare-metal agent capture data.
In most cases the structs that form the basis of the format are tightly packed, with 1-byte alignment. There are only a few cases where there are specific alignment requirements on certain values, stemming from the in-memory usage of those structures.
The format consists of a fixed header followed by a sequence of zero or more data records. The data records are ordered either linearly (that is to say in order from first to last), or as a circular ring buffer (where logical records are arranged physically as 0...n or m...n, 0...(m-1) depending on whether or not the data in the buffer was wrapped).
The header is defined as a fixed length structure (where the length is a function of certain compile time parameters which are also stored in the structure). This means there is only a limited amount of space available for storing information about processes or ELF image memory layouts. Writers of this format will need to know ahead-of-time the maximum number of these structures they want to store in the header, or accept that not all information will be able to be encoded at runtime.
The generated barman agent uses a number of compile time constants to configure various aspects of its behaviour. Some of these constants affect the layout of data within the data file.
The table below lists the various compile time constants that a relevant to the data format:
| Constant | Description |
|---|---|
| BM_CONFIG_MAX_CORES | The maximum number of unique processors on the target device. Unique processor identifiers are in the range [0, BM_CONFIG_MAX_CORES) |
| BM_CONFIG_MAX_TASK_INFOS | The maximum number of task information structures that can be stored in the header to describe different processes/tasks running on the target. |
| BM_CONFIG_MAX_MMAP_LAYOUTS | The maximum number of memory map layout structures that can be stored in the header. These structures are used to provide the mapping from executable sections of an ELF image to the address of the executable code in memory. |
| BM_CUSTOM_CHARTS_COUNT | The number of custom charts defined |
| BM_NUM_CUSTOM_COUNTERS | The total number of series across all custom charts. Must be zero if BM_CUSTOM_CHARTS_COUNT is zero, otherwise must be greater than or equal to BM_CUSTOM_CHARTS_COUNT. |
| BM_PROTOCOL_STRING_TABLE_LENGTH | The size (in bytes) of the string table data area. |
| BM_MAX_PMU_COUNTERS | The maximum number of counters that may be supported by a processor PMU. In most cases this value is 32. |
Throughout this document the following custom integer types are used to represent various fixed size integers.
| Type | Description |
|---|---|
| bm_bool | Signed 1-byte integer, used for boolean values |
| bm_int8 | Signed 1-byte integer |
| bm_int16 | Signed 2-byte integer |
| bm_int32 | Signed 4-byte integer |
| bm_int64 | Signed 8-byte integer |
| bm_intptr | Signed integer of same size as pointer. On 32-bit targets this is the same as bm_int32 and on 64-bit targets this is the same as bm_int64. |
| bm_uint8 | Unsigned 1-byte integer |
| bm_uint16 | Unsigned 2-byte integer |
| bm_uint32 | Unsigned 4-byte integer |
| bm_uint64 | Unsigned 8-byte integer |
| bm_intptr | Unsigned integer of same size as pointer. On 32-bit targets this is the same as bm_uint32 and on 64-bit targets this is the same as bm_uint64. |
| bm_size_t | Same as bm_uintptr |
| bm_task_id_t | Same as bm_uint32 |
| bm_datastore_header_length | Same as bm_uint64 |
The header record is a fixed structure at the start of the capture file. This structure defines contains the magic bytes used to identify the file, as well as storing the configuration values used to generate the barman agent code, and static, one off information needed to interpret the remaining records in the file. It defines information about what counters were captured, details of any processes, clock and custom chart descriptions.
The data format begins with an 8 byte sequence of bytes which encodes both the bitness and endianness of the data file. The magic bytes form the string "BARMAN32" or "BARMAN64" depending on whether or not the target is a 32-bit or 64-bit target. The bytes are written as a 64-bit value in native endianness meaning that on a little-endian target the order of characters in the magic string is reversed:
| Target | Magic Bytes |
|---|---|
| Little Endian, 32-bit | 0x32 0x33 0x4E 0x41 0x4D 0x52 0x41 0x42 |
| Little Endian, 64-bit | 0x34 0x36 0x4E 0x41 0x4D 0x52 0x41 0x42 |
| Big Endian, 32-bit | 0x42 0x41 0x52 0x4D 0x41 0x4E 0x33 0x32 |
| Big Endian, 64-bit | 0x42 0x41 0x52 0x4D 0x41 0x4E 0x36 0x34 |
#define BM_PROTOCOL_MAGIC_BYTES_64 0x4241524D414E3634ull
#define BM_PROTOCOL_MAGIC_BYTES_32 0x4241524D414E3332ull
#define BM_PROTOCOL_MAGIC_BYTES (sizeof(void*) == 8 ? BM_PROTOCOL_MAGIC_BYTES_64 : BM_PROTOCOL_MAGIC_BYTES_32)
void write_magic_bytes(char * buffer)
{
*((u64 *) buffer) = BM_PROTOCOL_MAGIC_BYTES;
}
The header contains a single bm_protocol_header struct.
struct bm_protocol_header
{
bm_uint64 magic_bytes;
bm_uint32 protocol_version;
bm_uint32 header_length;
bm_uint32 data_store_type;
bm_uint32 target_name_ptr;
bm_uint64 last_timestamp;
bm_uint32 timer_sample_rate;
struct bm_protocol_config_values config_constants;
struct bm_protocol_clock_info clock_info;
struct bm_protocol_header_string_table string_table;
struct bm_protocol_header_pmu_settings per_core_pmu_settings[BM_CONFIG_MAX_CORES];
#if BM_CONFIG_MAX_TASK_INFOS > 0
bm_uint32 num_task_entries;
struct bm_protocol_header_task_info task_info[BM_CONFIG_MAX_TASK_INFOS];
#endif
#if BM_CONFIG_MAX_MMAP_LAYOUTS > 0
bm_uint32 num_mmap_layout_entries;
struct bm_protocol_header_mmap_layout mmap_layout[BM_CONFIG_MAX_MMAP_LAYOUTS];
#endif
#if BM_NUM_CUSTOM_COUNTERS > 0
bm_uint32 num_custom_charts;
struct bm_protocol_header_custom_chart custom_charts[BM_CUSTOM_CHARTS_COUNT];
struct bm_protocol_header_custom_chart_series custom_charts_series[BM_NUM_CUSTOM_COUNTERS];
#endif
struct bm_datastore_header_data data_store_parameters;
};
| Field | Type | Offset (bytes) | Description | ||||||
|---|---|---|---|---|---|---|---|---|---|
| magic_bytes | bm_uint64 | 0 | Magic bytes value used to identify the file type, bitness and endianness. | ||||||
| protocol_version | bm_uint32 | 8 | Protocol version value. This value should be 2 | ||||||
| header_length | bm_uint32 | 12 | The length of the header struct; i.e. sizeof(struct bm_protocol_header). Includes any magic bytes and is used to calculate the offset to the first record. This value may be rounded up to a multiple of 8. |
||||||
| data_store_type | bm_uint32 | 16 | Data store type. Valid values for this field are:
|
||||||
| target_name_ptr | bm_uint32 | 20 | The offset into the string table that contains the target description string | ||||||
| last_timestamp | bm_uint64 | 24 | Timestamp of last attempt to write a sample (even if write failed). This value is in arbitrary ticks as per struct bm_protocol_clock_info. | ||||||
| timer_sample_rate | bm_uint32 | 32 | Timer based sampling rate; in Hz. Zero indicates no timer based sampling. This information is supplied by the user and is used to store the sample rate of timer that performs period sampling. It is for information purposes only. | ||||||
| config_constants | struct bm_protocol_config_values | 36 | Config constant values. This structure contains the values of various compile time constants that affect the size of various structures in the data including the header. | ||||||
| clock_info | struct bm_protocol_clock_info | 60 | Clock parameters used to convert from arbitrary tick values to nanoseconds. | ||||||
| string_table | struct bm_protocol_header_string_table | 92 | The string table | ||||||
| per_core_pmu_settings | struct bm_protocol_header_pmu_settings[BM_CONFIG_MAX_CORES] | Per-core PMU configuration settings. Each index maps to a core as numbered by the function barman_ext_map_multiprocessor_affinity_to_core_no. Each entry in the table describes the type and multiprocessor ID of that particular core, as well as the event types programmed into the processors PMU. Entries for processors that are not used/valid should be zero initialized. |
|||||||
| num_task_entries | bm_uint32 | Number of task records that contain data. Must be in the range [0, BM_CONFIG_MAX_TASK_INFOS)
.
This field is only present if BM_CONFIG_MAX_TASK_INFOS is greater than 0. |
|||||||
| task_info | struct bm_protocol_header_task_info[BM_CONFIG_MAX_TASK_INFOS] | Task information. This array contains of all the processes/tasks mentioned in the trace.
This field is only present if BM_CONFIG_MAX_TASK_INFOS is greater than 0. |
|||||||
| num_mmap_layout_entries | bm_uint32 | Number of mmap records that contain data. Must be in the range [0, BM_CONFIG_MAX_MMAP_LAYOUTS)
.
This field is only present if BM_CONFIG_MAX_MMAP_LAYOUTS is greater than 0. |
|||||||
| mmap_layout | struct bm_protocol_header_mmap_layout[BM_CONFIG_MAX_MMAP_LAYOUTS] | MMAP information. This array contains all of ELF image mappings used to lookup process image names and to map sample addresses to functions in ELF
files.
This field is only present if BM_CONFIG_MAX_MMAP_LAYOUTS is greater than 0. |
|||||||
| num_custom_charts | bm_uint32 | Number of custom charts (must be equal to BM_CUSTOM_CHARTS_COUNT).
This field is only present if BM_NUM_CUSTOM_COUNTERS is greater than 0. |
|||||||
| custom_charts | struct bm_protocol_header_custom_chart[BM_CUSTOM_CHARTS_COUNT] | Custom chart descriptions.
This field is only present if BM_NUM_CUSTOM_COUNTERS is greater than 0. |
|||||||
| custom_charts_series | struct bm_protocol_header_custom_chart_series[BM_NUM_CUSTOM_COUNTERS] | Custom chart series'.
This field is only present if BM_NUM_CUSTOM_COUNTERS is greater than 0. |
|||||||
| data_store_parameters | struct bm_datastore_header_data | Aligned to 8-byte boundary | Data store parameters. Contains information about layout and number of records in linear/circular buffer. |
This struct stores various compile time constant values that are necessary to decode the data.
struct bm_protocol_config_values
{
bm_uint32 max_cores;
bm_uint32 max_task_infos;
bm_uint32 max_mmap_layout;
bm_uint32 max_pmu_counters;
bm_uint32 max_string_table_length;
bm_uint32 num_custom_counters;
};
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| max_cores | bm_uint32 | 0 | The value of BM_CONFIG_MAX_CORES. |
| max_task_infos | bm_uint32 | 4 | The value of BM_CONFIG_MAX_TASK_INFOS. |
| max_mmap_layout | bm_uint32 | 8 | The value of BM_CONFIG_MAX_MMAP_LAYOUTS. |
| max_pmu_counters | bm_uint32 | 12 | The value of BM_MAX_PMU_COUNTERS. |
| max_string_table_length | bm_uint32 | 16 | The value of BM_PROTOCOL_STRING_TABLE_LENGTH. |
| num_custom_counters | bm_uint32 | 20 | The value of BM_NUM_CUSTOM_COUNTERS. |
This structure has a size of 24 bytes.
This struct stores information about the monotonic clock used in the trace.
struct bm_protocol_clock_info
{
bm_uint64 timestamp_base;
bm_uint64 timestamp_multiplier;
bm_uint64 timestamp_divisor;
bm_uint64 unix_base_ns;
};
Timestamp information is stored in arbitrary units within samples. This reduces the overhead of making the trace by removing the need to transform the timestamp into nanoseconds at the point the sample is recorded. The host expects timestamps to be in nanoseconds.
The arbitrary timestamp information is transformed to nanoseconds according to the following formula:
bm_uint64 nanoseconds = (((timestamp - timestamp_base) * timestamp_multiplier) / timestamp_divisor;
Therefore for a clock that already returns time in nanoseconds, timestamp_multiplier and timestamp_divisor should be configured as 1 and 1.
If the clock counts in microseconds then timestamp_multiplier and timestamp_divisor should be set 1000 and 1 respectively.
If
the clock counts at a rate of n Hz, then timestamp_multiplier should be set 1000000000 and timestamp_divisor as n.
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| timestamp_base | bm_uint64 | 0 | The base offset for the timestamp value. A timestamp with this value will be equivalent to the starting point of the capture. |
| timestamp_multiplier | bm_uint64 | 8 | The timestamp multiplier used to convert from arbitrary ticks to nanoseconds. |
| timestamp_divisor | bm_uint64 | 16 | The timestamp divisor used to convert from arbitrary ticks to nanoseconds. |
| unix_base_ns | bm_uint64 | 24 | The unix timestamp in nanoseconds. The arbitrary timestamp_base tick maps to this time in wall-clock time. For information purposes only. |
This structure has a size of 32 bytes.
This struct stores the string table. A block of memory reserved for storing string data related to the header.
struct bm_protocol_header_string_table
{
bm_uint32 string_table_length;
char string_table[BM_PROTOCOL_STRING_TABLE_LENGTH];
};
All Strings are null terminated. It is acceptable for strings to overlap within the table if one is the suffix of another. Strings are given as integer values which are pointers into the string table data. The find an actual string within the table from its pointer (for example, to get the string given by bm_protocol_header.target_name_ptr) the following 'C' code could be used:
const char * get_string_from_string_table(const struct bm_protocol_header_string_table * string_table, bm_uint32 string_pointer)
{
assert(string_pointer < string_table->string_table_length);
return string_table->string_table + string_pointer;
}
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| string_table_length | bm_uint32 | 0 | The number of bytes in string_table that are used. |
| string_table | char[BM_PROTOCOL_STRING_TABLE_LENGTH] | 4 | The block of char data used to store strings. |
This structure has a size of BM_PROTOCOL_STRING_TABLE_LENGTH + 4 bytes.
Describes the per-core pmu settings. This record is used to describe the processor type, topology and PMU counter configuration.
struct bm_protocol_header_pmu_settings
{
bm_uint64 configuration_timestamp;
bm_uint32 midr;
bm_uintptr mpidr;
bm_uint32 cluster_id;
bm_uint32 num_counters;
bm_uint32 counter_types[BM_MAX_PMU_COUNTERS];
};
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| configuration_timestamp | bm_uint64 | 0 | The timestamp the configuration was written. This value is in arbitrary ticks as per struct bm_protocol_clock_info. |
| midr | bm_uint32 | 8 | The contents of the MIDR register. |
| mpidr | bm_uintptr | 12 | The multiprocessor affinity register value (MPIDR) |
| cluster_id | bm_uint32 | 16/20 (32-bit/64-bit) | The cluster number of the processor. (As given by the barman_ext_map_multiprocessor_affinity_to_cluster_no function). |
| num_counters | bm_uint32 | 20/24 (32-bit/64-bit) | The number of valid entries in counter_types. |
| counter_types | bm_uint32[BM_MAX_PMU_COUNTERS] | 24/28 (32-bit/64-bit) | The event types programmed into the PMU for that particular processor. |
This structure has a size of (24 + 4 * BM_MAX_PMU_COUNTERS) bytes on a 32-bit target, and (28 + 4 * BM_MAX_PMU_COUNTERS) bytes on a 64-bit target.
A task information record. Describes information about a unique task or process within the system.
struct bm_protocol_header_task_info
{
bm_uint64 timestamp;
bm_task_id_t task_id;
bm_uint32 task_name_ptr;
};
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| timestamp | bm_uint64 | 0 | The timestamp the record was inserted. This value is in arbitrary ticks as per struct bm_protocol_clock_info. |
| task_id | bm_task_id_t | 8 | The task id. A unique integer value used in the various other records/structures to identify this entry. |
| task_name_ptr | bm_uint32 | 12 | The offset of name of the task in the string table. |
This structure has a size of 16 bytes.
A MMAP record; describes the load address of a section of an executable ELF image in memory. This structure should be used whenever more than one ELF image is referenced by a sample, or where the load address of an image does not match the load address specified in the ELF image (it has been relocated).
struct bm_protocol_header_mmap_layout
{
#if BM_CONFIG_MAX_TASK_INFOS > 0
bm_uint64 timestamp;
bm_task_id_t task_id;
#endif
bm_uintptr base_address;
bm_uintptr length;
bm_uintptr image_offset;
bm_uint32 image_ptr;
};
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| timestamp | bm_uint64 | 0 | The timestamp the record was inserted.
This field is only present if BM_CONFIG_MAX_TASK_INFOS is greater than 0. |
| task_id | bm_task_id_t | 8 | The task ID to associate with the map.
This field is only present if BM_CONFIG_MAX_TASK_INFOS is greater than 0. |
| base_address | bm_uintptr | 0/12 (without/with BM_CONFIG_MAX_TASK_INFOS) | The base address of the image or image section as it was loaded/relocated in memory. |
| length | bm_uintptr | offsetof(base_address) + sizeof(bm_uintptr) | The length of the image or image section. |
| image_offset | bm_uintptr | offsetof(base_address) + 2 * sizeof(bm_uintptr) | The image section offset. The offset relative to the start of the ELF file. |
| image_ptr | bm_uint32 | offsetof(base_address) + 3 * sizeof(bm_uintptr) | The offset of name of the image in the string table. |
This structure has a size of ((BM_CONFIG_MAX_TASK_INFOS > 0 ? 12 : 0) + (3 * sizeof(bm_uintptr)) + 4) bytes.
Describes the details of a custom counter chart. Each custom chart consists of a number of series.
struct bm_protocol_header_custom_chart
{
bm_uint32 name_ptr;
bm_uint8 series_composition;
bm_uint8 rendering_type;
bm_uint8 boolean_flags;
};
| Field | Type | Offset (bytes) | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| name_ptr | bm_uint32 | 0 | The offset of name of the chart in the string table. | ||||||||||
| series_composition | bm_uint8 | 4 | The series composition (as per the chart configuration shown in the Streamline UI). Valid values for this field are:
|
||||||||||
| rendering_type | bm_uint8 | 5 | The rendering type (as per the chart configuration shown in the Streamline UI). Valid values for this field are:
|
||||||||||
| boolean_flags | bm_uint8 | 6 | Bitmap of boolean flags as follows:
|
This structure has a size of 7 bytes.
Describes a series within a custom chart.
struct bm_protocol_header_custom_chart_series
{
bm_uint32 chart_index;
bm_uint32 name_ptr;
bm_uint32 units_ptr;
bm_uint32 description_ptr;
bm_uint32 colour;
double multiplier;
bm_uint8 class;
bm_uint8 display;
bm_uint8 boolean_flags;
};
| Field | Type | Offset (bytes) | Description | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| chart_index | bm_uint32 | 0 | The index (into the array custom_charts in the header) of the chart the series belongs to. | ||||||||||||
| name_ptr | bm_uint32 | 4 | The offset of name of the series in the string table. | ||||||||||||
| units_ptr | bm_uint32 | 8 | The offset of the series units in the string table. | ||||||||||||
| description_ptr | bm_uint32 | 12 | The offset of the series description in the string table. | ||||||||||||
| colour | bm_uint32 | 16 | The colour value (as 0x00RRGGBB RGB encoded value) for the colour of the chart. | ||||||||||||
| multiplier | double | 20 | Multiplier value. All counter values are stored as bm_uint64 in the capture. This allows fixed conversion to some floating point value. | ||||||||||||
| class | bm_uint8 | 28 | Describes the class of data received. Valid values are:
|
||||||||||||
| display | bm_uint8 | 29 | How data within the series should be displayed when data is aggregated at lower zoom levels. Valid values are:
|
||||||||||||
| boolean_flags | bm_uint8 | 30 | Bitmap of boolean flags as follows:
|
This structure has a size of 31 bytes.
Describes how records are layout out in the part of the file following the header.
struct bm_datastore_header_data
{
bm_datastore_header_length buffer_length;
bm_datastore_header_length write_offset;
bm_datastore_header_length read_offset;
bm_datastore_header_length total_written;
bm_uint8 * base_pointer;
};
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| buffer_length | bm_datastore_header_length | 0 | The length of the region used for storing records |
| write_offset | bm_datastore_header_length | 8 | The last write offset; points to the first unwritten byte within the buffer. For circular buffers this is the first byte past the end of the ring. For linear buffers this is the end of the record data. |
| read_offset | bm_datastore_header_length | 16 | The initial read offset. For circular buffers this is the start of the ring. For linear buffers this should always be zero zero. |
| total_written | bm_datastore_header_length | 24 | Total number of bytes consumed; always increments. For circular buffers this can be used to determine whether or not the buffer wrapped or not. May be many multiples of buffer_length. |
| base_pointer | bm_uint8 * | 32 | The base address of the buffer. This field is not used outside of the running agent and can safely be set to zero. |
This structure has a size of 36 bytes on a 32-bit target, and 40 bytes on a 64-bit target bytes. This structure has an alignment requirement that it is aligned to 8 bytes. This means that when used within the header there may be between 0-7 bytes of padding before it.
Capture records are stored in the record buffer which follows the header data. The record buffer starts at offset ((header.header_length + 7u) & -7u), (the header length, aligned up to an 8-byte boundary), and continues for a maximum of header.data_store_parameters.buffer_length bytes. How the data in the record buffer is to be interpreted depends on the value of header.data_store_type.
The following sections describe the two different formats for the record buffer; linear and circular. They are written primarily from the point of view of decoding the data. It is strongly recommended that third-parties looking to implement a writer for the this data format, should write data using the linear format.
With the linear data storage format, records are written sequentially into the record buffer from beginning to end, stopping once the buffer is full. The contents of the record buffer are considered valid only between within the range of offset [header.data_store_parameters.read_offset, header.data_store_parameters.write_offset). In most cases header.data_store_parameters.read_offset will be set
To decode the contents of the record buffer when data is stored in linear format, one would seek to header.data_store_parameters.read_offset bytes past the beginning of the record buffer and from there continue to read records, stopping at offset header.data_store_parameters.write_offset from the beginning of the record buffer.
To decode a single record, first read a bm_uintptr value starting at the current read offset. This value encodes the record length and valid flag as per Record Length Encoding. Immediately following the length value is length bytes of data which will either be treated as record data and can be decoded as described in Record Types if the encoded length value indicates a valid block, or which must be discarded/skipped over if the encoded length value indicates the block is invalid/padding.
Records within the record buffer are tightly packed so the next record's length value will follow immediately after the current records data bytes.
With the circular data storage format, the record buffer is treated as a circular ring buffer. Initially records are written into the buffer as per the linear format in that records are written sequentially from beginning to end, however once the buffer is filled, rather than stopping, the circular buffer will wrap around and start overwriting blocks from the beginning of the buffer. It is important to note that when over writing a previous block, it will read the length of that block and adjust the read head accordingly so as to free the whole block. It will do this repeatedly until sufficient space is freed for the new block to be inserted. In this way it ensures that valid records remain tightly packed.
To decode the contents of the record buffer when data is stored in circular format the following values are defined:
const bm_uintptr data_length = calculate_circular_data_length(&header.data_store_parameters);
const bm_uintptr initial_read_offset = header.data_store_parameters.read_offset % header.data_store_parameters.buffer_length;
const bm_uintptr wrap_offset = header.data_store_parameters.buffer_length - initial_read_offset;
bm_uintptr linear_read_offset = 0;
static bm_uintptr calculate_circular_data_length(const struct bm_data_store_header_data * data_store_parameters)
{
if (data_store_parameters->read_offset == data_store_parameters->write_offset) {
if (data_store_parameters->total_written > 0) {
return data_store_parameters->buffer_length;
}
else {
return 0;
}
}
else if (data_store_parameters->read_offset < data_store_parameters->write_offset) {
return data_store_parameters->write_offset - data_store_parameters->read_offset;
}
else {
return (data_store_parameters->buffer_length - data_store_parameters->read_offset) + data_store_parameters->write_offset;
}
}The decoder proceeds as if it were parsing the linear buffer, but it uses linear_read_offset as if it were the linear read head within the buffer. Whenever bytes are read from the buffer, the linear read offset is translated into a physical offset within the ring buffer using the following function:
static bm_uintptr calculate_physical_read_offset(bm_uintptr initial_read_offset, bm_uintptr wrap_offset, bm_uintptr linear_read_offset)
{
if (linear_read_offset < wrap_offset) {
return initial_read_offset + linear_read_offset;
}
else {
return linear_read_offset - wrap_offset;
}
}
Decoding proceeds exactly as per the linear buffer, using the additional layer of translation, except for one minor difference regarding the length of the buffer and wrapping. If the length of the record buffer is not a multiple of sizeof(bm_uintptr) then the trailing bytes at the end of the buffer are treated automatically as if they were padding and are discarded, updating linear_read_offset accordingly.
No block will wrapped across the ring buffer end. When writing the data to the circular buffer, the barman agent will insert a padding block at the wrapping point if required to ensure that the new block can be written fully. No block will ever be split from its length either, which means if it is possible to write the length but not the block, padding will also be inserted to keep the length and data together.
Below is shown an example record buffer containing 5 records and one padding block.
| Buffer Contents | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | |
| 0x00 | 10 | |||||||||||||||
| 0x10 | 8 | 10 | ||||||||||||||
| 0x20 | 6 | |||||||||||||||
| 0x30 | 8 | |||||||||||||||
In this example header.data_store_parameters contains the following values:
Additionally:
The records are decoded as follows:
The record length field encodes both the length of the record data, and a boolean flag indicating whether or not the block is a valid record or a padding block. If the flag indicates the block is a padding block it should be skipped over rather than being decoded.
The record length value encodes the valid flag in the most significant bit, where a set bit indicates a padding block, and a clear bit indicates a valid record block.
The length and valid flag may be decoded as follows:
#define PADDING_BIT_MASK (1ul << ((sizeof(bm_uintptr) * 8) - 1))
bm_bool is_padding_block(bm_uintptr encoded_length)
{
return (encoded_length & PADDING_BIT_MASK) == PADDING_BIT_MASK;
}
bm_uintptr get_block_length(bm_uintptr encoded_length)
{
return encoded_length & ~PADDING_BIT_MASK;
}
Each valid record block decoded from the record buffer contains a single record object. The record object begins with a header containing an identifier value which determines the record type.
It is valid for the record block to be longer than the number of bytes required to store the record object, and in most cases it will be as the barman agent will pad a block to a multiple of 8-bytes.
All records start with the same header structure as follows:
struct bm_protocol_record_header
{
bm_uint32 record_type;
bm_uint32 core;
bm_uint64 timestamp;
};
| Field | Type | Offset (bytes) | Description | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| record_type | bm_uint32 | 0 | Identifies the record type. Valid values for this field are:
|
||||||||||||||
| core | bm_uint32 | 4 | The processor number. | ||||||||||||||
| timestamp | bm_uint64 | 8 | The timestamp of the event. This value is in arbitrary ticks as per struct bm_protocol_clock_info. |
This structure has a size of 16 bytes.
These two record types are used to store PMU counter samples (as well as any sampled custom counter series values). Each instance of one of these record types corresponds to a reading of the counter values for a particular processor at a particular time. Both record types have almost identical layouts, the only difference being whether or not the PC address sample value is present.
struct bm_protocol_sample
{
struct bm_protocol_record_header header;
#if BM_CONFIG_MAX_TASK_INFOS > 0
bm_task_id_t task_id;
#endif
#if BM_NUM_CUSTOM_COUNTERS > 0
bm_uint32 num_custom_counters;
#endif
bm_uintptr pc_sample; /* Only for Counter Sample with PC address sample */
bm_uint64 pmu_counter_delta_value[num_pmu_counters];
#if BM_NUM_CUSTOM_COUNTERS > 0
struct bm_custom_counter_sample custom_counter_values[num_custom_counters];
#endif
};
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| header | struct bm_protocol_record_header | 0 | The record header |
| task_id | bm_task_id_t | 16 | Task id field. The id of the currently running task.
This field is only present if BM_CONFIG_MAX_TASK_INFOS is greater than 0. |
| num_custom_counters | bm_uint32 | 16/20 | The number of custom counter values sent
This field is only present if BM_NUM_CUSTOM_COUNTERS is greater than 0. |
| pc_sample | bm_uintptr | 16/20 + 0/4 | The sampled program counter address
This field is only present if the record is a Counter Sample (with PC address sample) |
| pmu_counter_delta_value | bm_uint64[num_pmu_counters] | 16/20 + 0/4 + 0/sizeof(bm_uintptr) | The PMU counter sample delta values (i.e. the different between the counter and the last read of the counter).
The length of this field is determined by reading file_header.per_core_pmu_settings[record.header.core].num_counters for the corresponding processor number. |
| custom_counter_values | struct bm_custom_counter_sample[num_custom_counters] | 16/20 + 0/4 + 0/sizeof(bm_uintptr) + (num_pmu_counters * 8) | The custom counter value samples. The type of this field is a packed struct defined as follows:
struct bm_custom_counter_sample
{
bm_uint32 id;
bm_uint64 value;
};
Where id is the index of the custom counter series in file_header.custom_charts_series
and value is the sampled value for that counter series.
The length of this field is determined by reading the num_custom_counters field from the record. |
This record has a size of (16 + (BM_CONFIG_MAX_TASK_INFOS > 0 ? 4 : 0) + (BM_NUM_CUSTOM_COUNTERS > 0 ? 4 : 0) + (has_pc_sample ? sizeof(bm_uintptr) : 0) + (num_pmu_counters * 8) + (num_custom_counters * 12)) bytes.
This event is used to record a scheduler task switch event, such as when the scheduler switches to a new process.
struct bm_protocol_task_switch
{
struct bm_protocol_record_header header;
bm_task_id_t task_id;
bm_uint8 reason;
};
| Field | Type | Offset (bytes) | Description | ||||||
|---|---|---|---|---|---|---|---|---|---|
| header | struct bm_protocol_record_header | 0 | The record header | ||||||
| task_id | bm_task_id_t | 16 | Task id field. This identifies the task that is being switched to. | ||||||
| reason | bm_uint8 | 20 | The reason for the task switch. Valid values for this field are:
|
This record has a size of 21 bytes.
This record is only valid in a capture if BM_CONFIG_MAX_TASK_INFOS is greater than 0.
This event contains the value of a custom counter.
struct bm_protocol_custom_counter_record
{
struct bm_protocol_record_header header;
#if BM_CONFIG_MAX_TASK_INFOS > 0
bm_task_id_t task_id;
#endif
bm_uint32 counter;
bm_uint64 value;
};
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| header | struct bm_protocol_record_header | 0 | The record header |
| task_id | bm_task_id_t | 16 | Task id field. The id of the currently running task.
This field is only present if BM_CONFIG_MAX_TASK_INFOS is greater than 0. |
| counter | bm_uint32 | 16/20 | The custom counter id |
| value | bm_uint64 | 20/24 | The custom counter value |
This record has a size of (BM_CONFIG_MAX_TASK_INFOS > 0 ? 32 : 28) bytes.
This record is only valid in a capture if BM_NUM_CUSTOM_COUNTERS is greater than 0.
This event encodes a custom annotation value
struct bm_protocol_annotation_record
{
struct bm_protocol_record_header header;
#if BM_CONFIG_MAX_TASK_INFOS > 0
bm_task_id_t task_id;
#endif
bm_uintptr data_length;
bm_uint32 channel;
bm_uint32 group;
bm_uint32 colour;
bm_uint8 type;
bm_uint8 data[data_length]
};
| Field | Type | Offset (bytes) | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| header | struct bm_protocol_record_header | 0 | The record header | ||||||||||
| task_id | bm_task_id_t | 16 | Task id field. The id of the currently running task.
This field is only present if BM_CONFIG_MAX_TASK_INFOS is greater than 0. |
||||||||||
| data_length | bm_uintptr | 16/20 | Length of the byte data that follows the record | ||||||||||
| channel | bm_uint32 | 16/20 + sizeof(bm_uintptr) | Annotation channel | ||||||||||
| group | bm_uint32 | 16/20 + sizeof(bm_uintptr) + 4 | Annotation group | ||||||||||
| colour | bm_uint32 | 16/20 + sizeof(bm_uintptr) + 8 | Annotation colour (as 0x00RRGGBB RGB encoded value) | ||||||||||
| type | bm_uint8 | 16/20 + sizeof(bm_uintptr) + 12 | Annotation type. Valid values for this field are:
|
||||||||||
| data | bm_uint8[data_length] | 16/20 + sizeof(bm_uintptr) + 13 | String data associated with the annotation.
The length of this field is taken from the data_length field. |
This record has a size of ((BM_CONFIG_MAX_TASK_INFOS > 0 ? 33 : 29) + sizeof(bm_uintptr) + data_length) bytes.
This event records entry to or exit from some sort of halting event such as WFE or WFI instruction.
Records of this type are expected to come in entered-exited pairs for a given processor, with no other intervening record types as would be the case if the records would emitted just before and then just after a WFI instruction.
struct bm_protocol_halting_event_record
{
struct bm_protocol_record_header header;
bm_uint8 entered_halt;
};
| Field | Type | Offset (bytes) | Description |
|---|---|---|---|
| header | struct bm_protocol_record_header | 0 | The record header |
| entered_halt | bm_uint8 | 16 | Non-zero if entered halting state, Zero if exited. |
This record has a size of 17 bytes.