2 * Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com>
4 * SPDX-License-Identifier: GPL-2.0-only
8 #include "tsdl-trace-class-visitor.hpp"
9 #include "clock-class.hpp"
11 #include <common/exception.hpp>
12 #include <common/format.hpp>
13 #include <common/make-unique.hpp>
14 #include <common/uuid.hpp>
16 #include <vendor/optional.hpp>
25 namespace lst
= lttng::sessiond::trace
;
26 namespace tsdl
= lttng::sessiond::tsdl
;
29 const auto ctf_spec_major
= 1;
30 const auto ctf_spec_minor
= 8;
33 * Although the CTF v1.8 specification recommends ignoring any leading underscore, Some readers,
34 * such as Babeltrace 1.x, expect special identifiers without a prepended underscore.
36 const std::set
<std::string
> safe_tsdl_identifiers
= {
54 * A previous implementation always prepended '_' to the identifiers in order to
55 * side-step the problem of escaping TSDL keywords and ensuring identifiers
56 * started with an alphabetic character.
58 * Changing this behaviour to a smarter algorithm would break readers that have
59 * come to expect this initial underscore.
61 std::string
escape_tsdl_identifier(const std::string
& original_identifier
)
63 if (original_identifier
.size() == 0) {
64 LTTNG_THROW_ERROR("Invalid 0-length identifier used in trace description");
67 if (safe_tsdl_identifiers
.find(original_identifier
) != safe_tsdl_identifiers
.end()) {
68 return original_identifier
;
71 std::string new_identifier
;
72 /* Optimisticly assume most identifiers are valid and allocate the same length. */
73 new_identifier
.reserve(original_identifier
.size());
76 /* Replace illegal characters by '_'. */
77 std::locale c_locale
{"C"};
78 for (const auto current_char
: original_identifier
) {
79 if (!std::isalnum(current_char
, c_locale
) && current_char
!= '_') {
80 new_identifier
+= '_';
82 new_identifier
+= current_char
;
86 return new_identifier
;
89 std::string
escape_tsdl_env_string_value(const std::string
& original_string
)
91 std::string escaped_string
;
93 escaped_string
.reserve(original_string
.size());
95 for (const auto c
: original_string
) {
98 escaped_string
+= "\\n";
101 escaped_string
+= "\\\\";
104 escaped_string
+= "\"";
112 return escaped_string
;
115 class tsdl_field_visitor
: public lttng::sessiond::trace::field_visitor
,
116 public lttng::sessiond::trace::type_visitor
{
118 tsdl_field_visitor(const lst::abi
& abi
,
119 unsigned int indentation_level
,
120 const nonstd::optional
<std::string
>& in_default_clock_class_name
=
122 _indentation_level
{indentation_level
},
124 _bypass_identifier_escape
{false},
125 _default_clock_class_name
{in_default_clock_class_name
?
126 in_default_clock_class_name
->c_str() :
131 /* Only call once. */
132 std::string
transfer_description()
134 return std::move(_description
);
138 virtual void visit(const lst::field
& field
) override final
141 * Hack: keep the name of the field being visited since
142 * the tracers can express sequences, variants, and arrays with an alignment
143 * constraint, which is not expressible in TSDL. To work around this limitation, an
144 * empty structure declaration is inserted when needed to express the aligment
145 * constraint. The name of this structure is generated using the field's name.
147 _current_field_name
.push(_bypass_identifier_escape
?
148 field
.name
: escape_tsdl_identifier(field
.name
));
150 field
.get_type().accept(*this);
152 _description
+= _current_field_name
.top();
153 _current_field_name
.pop();
156 * Some types requires suffixes to be appended (e.g. the length of arrays
157 * and sequences, the mappings of enumerations).
159 while (!_type_suffixes
.empty()) {
160 _description
+= _type_suffixes
.front();
161 _type_suffixes
.pop();
167 virtual void visit(const lst::integer_type
& type
) override final
169 _description
+= "integer { ";
171 /* Mandatory properties (no defaults). */
172 _description
+= fmt::format("size = {size}; align = {alignment};",
173 fmt::arg("size", type
.size
),
174 fmt::arg("alignment", type
.alignment
));
176 /* Defaults to unsigned. */
177 if (type
.signedness_
== lst::integer_type::signedness::SIGNED
) {
178 _description
+= " signed = true;";
181 /* Defaults to 10. */
182 if (type
.base_
!= lst::integer_type::base::DECIMAL
) {
185 switch (type
.base_
) {
186 case lst::integer_type::base::BINARY
:
189 case lst::integer_type::base::OCTAL
:
192 case lst::integer_type::base::HEXADECIMAL
:
196 LTTNG_THROW_ERROR(fmt::format(
197 "Unexpected base encountered while serializing integer type to TSDL: base = {}",
201 _description
+= fmt::format(" base = {};", base
);
204 /* Defaults to the trace's native byte order. */
205 if (type
.byte_order
!= _trace_abi
.byte_order
) {
206 const auto byte_order_str
= type
.byte_order
== lst::byte_order::BIG_ENDIAN_
? "be" : "le";
208 _description
+= fmt::format(" byte_order = {};", byte_order_str
);
211 if (_current_integer_encoding_override
) {
212 const char *encoding_str
;
214 switch (*_current_integer_encoding_override
) {
215 case lst::string_type::encoding::ASCII
:
216 encoding_str
= "ASCII";
218 case lst::string_type::encoding::UTF8
:
219 encoding_str
= "UTF8";
222 LTTNG_THROW_ERROR(fmt::format(
223 "Unexpected encoding encountered while serializing integer type to TSDL: encoding = {}",
224 (int) *_current_integer_encoding_override
));
227 _description
+= fmt::format(" encoding = {};", encoding_str
);
228 _current_integer_encoding_override
.reset();
231 if (std::find(type
.roles_
.begin(), type
.roles_
.end(),
232 lst::integer_type::role::DEFAULT_CLOCK_TIMESTAMP
) !=
234 std::find(type
.roles_
.begin(), type
.roles_
.end(),
235 lst::integer_type::role::
236 PACKET_END_DEFAULT_CLOCK_TIMESTAMP
) !=
238 LTTNG_ASSERT(_default_clock_class_name
);
239 _description
+= fmt::format(
240 " map = clock.{}.value;", _default_clock_class_name
);
243 _description
+= " }";
246 virtual void visit(const lst::floating_point_type
& type
) override final
248 _description
+= fmt::format(
249 "floating_point {{ align = {alignment}; mant_dig = {mantissa_digits}; exp_dig = {exponent_digits};",
250 fmt::arg("alignment", type
.alignment
),
251 fmt::arg("mantissa_digits", type
.mantissa_digits
),
252 fmt::arg("exponent_digits", type
.exponent_digits
));
254 /* Defaults to the trace's native byte order. */
255 if (type
.byte_order
!= _trace_abi
.byte_order
) {
256 const auto byte_order_str
= type
.byte_order
== lst::byte_order::BIG_ENDIAN_
? "be" : "le";
258 _description
+= fmt::format(" byte_order = {};", byte_order_str
);
261 _description
+= " }";
264 template <class EnumerationType
>
265 void visit_enumeration(const EnumerationType
& type
)
267 /* name follows, when applicable. */
268 _description
+= "enum : ";
270 tsdl_field_visitor integer_visitor
{_trace_abi
, _indentation_level
};
272 integer_visitor
.visit(static_cast<const lst::integer_type
&>(type
));
273 _description
+= integer_visitor
.transfer_description() + " {\n";
275 const auto mappings_indentation_level
= _indentation_level
+ 1;
277 bool first_mapping
= true;
278 for (const auto& mapping
: *type
.mappings_
) {
279 if (!first_mapping
) {
280 _description
+= ",\n";
283 _description
.resize(_description
.size() + mappings_indentation_level
, '\t');
284 if (mapping
.range
.begin
== mapping
.range
.end
) {
285 _description
+= fmt::format(
286 "\"{mapping_name}\" = {mapping_value}",
287 fmt::arg("mapping_name", mapping
.name
),
288 fmt::arg("mapping_value", mapping
.range
.begin
));
290 _description
+= fmt::format(
291 "\"{mapping_name}\" = {mapping_range_begin} ... {mapping_range_end}",
292 fmt::arg("mapping_name", mapping
.name
),
293 fmt::arg("mapping_range_begin",
294 mapping
.range
.begin
),
295 fmt::arg("mapping_range_end", mapping
.range
.end
));
298 first_mapping
= false;
301 _description
+= "\n";
302 _description
.resize(_description
.size() + _indentation_level
, '\t');
306 virtual void visit(const lst::signed_enumeration_type
& type
) override final
308 visit_enumeration(type
);
311 virtual void visit(const lst::unsigned_enumeration_type
& type
) override final
313 visit_enumeration(type
);
316 virtual void visit(const lst::static_length_array_type
& type
) override final
318 if (type
.alignment
!= 0) {
319 LTTNG_ASSERT(_current_field_name
.size() > 0);
320 _description
+= fmt::format(
321 "struct {{ }} align({alignment}) {field_name}_padding;\n",
322 fmt::arg("alignment", type
.alignment
),
323 fmt::arg("field_name", _current_field_name
.top()));
324 _description
.resize(_description
.size() + _indentation_level
, '\t');
327 type
.element_type
->accept(*this);
328 _type_suffixes
.emplace(fmt::format("[{}]", type
.length
));
331 virtual void visit(const lst::dynamic_length_array_type
& type
) override final
333 if (type
.alignment
!= 0) {
335 * Note that this doesn't support nested sequences. For
336 * the moment, tracers can't express those. However, we
337 * could wrap nested sequences in structures, which
338 * would allow us to express alignment constraints.
340 LTTNG_ASSERT(_current_field_name
.size() > 0);
341 _description
+= fmt::format(
342 "struct {{ }} align({alignment}) {field_name}_padding;\n",
343 fmt::arg("alignment", type
.alignment
),
344 fmt::arg("field_name", _current_field_name
.top()));
345 _description
.resize(_description
.size() + _indentation_level
, '\t');
348 type
.element_type
->accept(*this);
349 _type_suffixes
.emplace(fmt::format("[{}]",
350 _bypass_identifier_escape
?
351 *(type
.length_field_location
.elements_
.end() - 1) :
352 escape_tsdl_identifier(*(type
.length_field_location
.elements_
.end() - 1))));
355 virtual void visit(const lst::static_length_blob_type
& type
) override final
357 /* This type doesn't exist in CTF 1.x, express it as a static length array of uint8_t. */
358 std::unique_ptr
<const lst::type
> uint8_element
= lttng::make_unique
<lst::integer_type
>(8,
359 _trace_abi
.byte_order
, 8, lst::integer_type::signedness::UNSIGNED
,
360 lst::integer_type::base::HEXADECIMAL
);
361 const auto array
= lttng::make_unique
<lst::static_length_array_type
>(
362 type
.alignment
, std::move(uint8_element
), type
.length_bytes
);
367 virtual void visit(const lst::dynamic_length_blob_type
& type
) override final
369 /* This type doesn't exist in CTF 1.x, express it as a dynamic length array of uint8_t. */
370 std::unique_ptr
<const lst::type
> uint8_element
= lttng::make_unique
<lst::integer_type
>(0,
371 _trace_abi
.byte_order
, 8, lst::integer_type::signedness::UNSIGNED
,
372 lst::integer_type::base::HEXADECIMAL
);
373 const auto array
= lttng::make_unique
<lst::dynamic_length_array_type
>(
374 type
.alignment
, std::move(uint8_element
), type
.length_field_location
);
379 virtual void visit(const lst::null_terminated_string_type
& type
) override final
381 /* Defaults to UTF-8. */
382 if (type
.encoding_
== lst::null_terminated_string_type::encoding::ASCII
) {
383 _description
+= "string { encoding = ASCII }";
385 _description
+= "string";
389 virtual void visit(const lst::structure_type
& type
) override final
391 _indentation_level
++;
392 _description
+= "struct {";
394 const auto previous_bypass_identifier_escape
= _bypass_identifier_escape
;
395 _bypass_identifier_escape
= false;
396 for (const auto& field
: type
.fields_
) {
397 _description
+= "\n";
398 _description
.resize(_description
.size() + _indentation_level
, '\t');
399 field
->accept(*this);
402 _bypass_identifier_escape
= previous_bypass_identifier_escape
;
404 _indentation_level
--;
405 if (type
.fields_
.size() != 0) {
406 _description
+= "\n";
407 _description
.resize(_description
.size() + _indentation_level
, '\t');
413 template <class MappingIntegerType
>
414 void visit_variant(const lst::variant_type
<MappingIntegerType
>& type
)
416 if (type
.alignment
!= 0) {
417 LTTNG_ASSERT(_current_field_name
.size() > 0);
418 _description
+= fmt::format(
419 "struct {{ }} align({alignment}) {field_name}_padding;\n",
420 fmt::arg("alignment", type
.alignment
),
421 fmt::arg("field_name", _current_field_name
.top()));
422 _description
.resize(_description
.size() + _indentation_level
, '\t');
425 _indentation_level
++;
426 _description
+= fmt::format("variant <{}> {{\n",
427 _bypass_identifier_escape
?
428 *(type
.selector_field_location
.elements_
.end() - 1) :
429 escape_tsdl_identifier(*(type
.selector_field_location
.elements_
.end() - 1)));
432 * The CTF 1.8 specification only recommends that implementations ignore
433 * leading underscores in field names. Both babeltrace 1 and 2 expect the
434 * variant choice and enumeration mapping name to match perfectly. Given that we
435 * don't have access to the tag in this context, we have to assume they match.
437 const auto previous_bypass_identifier_escape
= _bypass_identifier_escape
;
438 _bypass_identifier_escape
= true;
439 for (const auto& field
: type
.choices_
441 _description
.resize(_description
.size() + _indentation_level
, '\t');
442 field
.second
->accept(*this);
443 _description
+= fmt::format(" {};\n", field
.first
.name
);
446 _bypass_identifier_escape
= previous_bypass_identifier_escape
;
448 _indentation_level
--;
449 _description
.resize(_description
.size() + _indentation_level
, '\t');
453 virtual void visit(const lst::variant_type
<lst::signed_enumeration_type::mapping::range_t::range_integer_t
>& type
) override final
458 virtual void visit(const lst::variant_type
<lst::unsigned_enumeration_type::mapping::range_t::range_integer_t
>& type
) override final
463 lst::type::cuptr
create_character_type(enum lst::string_type::encoding encoding
)
465 _current_integer_encoding_override
= encoding
;
466 return lttng::make_unique
<lst::integer_type
>(8, _trace_abi
.byte_order
, 8,
467 lst::integer_type::signedness::UNSIGNED
,
468 lst::integer_type::base::DECIMAL
);
471 virtual void visit(const lst::static_length_string_type
& type
) override final
474 * TSDL expresses static-length strings as arrays of 8-bit integer with
475 * an encoding specified.
477 const auto char_array
= lttng::make_unique
<lst::static_length_array_type
>(
478 type
.alignment
, create_character_type(type
.encoding_
), type
.length
);
483 virtual void visit(const lst::dynamic_length_string_type
& type
) override final
486 * TSDL expresses dynamic-length strings as arrays of 8-bit integer with
487 * an encoding specified.
489 const auto char_sequence
= lttng::make_unique
<lst::dynamic_length_array_type
>(
490 type
.alignment
, create_character_type(type
.encoding_
),
491 type
.length_field_location
);
493 visit(*char_sequence
);
496 std::stack
<std::string
> _current_field_name
;
498 * Encoding to specify for the next serialized integer type.
499 * Since the integer_type does not allow an encoding to be specified (it is a TSDL-specific
500 * concept), this attribute is used when expressing static or dynamic length strings as
501 * arrays/sequences of bytes with an encoding.
503 nonstd::optional
<enum lst::string_type::encoding
> _current_integer_encoding_override
;
505 unsigned int _indentation_level
;
506 const lst::abi
& _trace_abi
;
508 std::queue
<std::string
> _type_suffixes
;
510 /* Description in TSDL format. */
511 std::string _description
;
513 bool _bypass_identifier_escape
;
514 const char *_default_clock_class_name
;
517 class tsdl_trace_environment_visitor
: public lst::trace_class_environment_visitor
{
519 tsdl_trace_environment_visitor() : _environment
{"env {\n"}
523 virtual void visit(const lst::environment_field
<int64_t>& field
) override
525 _environment
+= fmt::format(" {} = {};\n", field
.name
, field
.value
);
528 virtual void visit(const lst::environment_field
<const char *>& field
) override
530 _environment
+= fmt::format(" {} = \"{}\";\n", field
.name
,
531 escape_tsdl_env_string_value(field
.value
));
534 /* Only call once. */
535 std::string
transfer_description()
537 _environment
+= "};\n\n";
538 return std::move(_environment
);
542 std::string _environment
;
546 tsdl::trace_class_visitor::trace_class_visitor(const lst::abi
& trace_abi
,
547 tsdl::append_metadata_fragment_function append_metadata_fragment
) :
548 _trace_abi
{trace_abi
},
549 _append_metadata_fragment(append_metadata_fragment
)
553 void tsdl::trace_class_visitor::append_metadata_fragment(const std::string
& fragment
) const
555 _append_metadata_fragment(fragment
);
558 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::trace_class
& trace_class
)
560 tsdl_field_visitor
packet_header_visitor(trace_class
.abi
, 1);
562 trace_class
.get_packet_header()->accept(packet_header_visitor
);
564 /* Declare type aliases, trace class, and packet header. */
565 auto trace_class_tsdl
= fmt::format(
566 "/* CTF {ctf_major}.{ctf_minor} */\n\n"
568 " major = {ctf_major};\n"
569 " minor = {ctf_minor};\n"
570 " uuid = \"{uuid}\";\n"
571 " byte_order = {byte_order};\n"
572 " packet.header := {packet_header_layout};\n"
574 fmt::arg("ctf_major", ctf_spec_major
),
575 fmt::arg("ctf_minor", ctf_spec_minor
),
576 fmt::arg("uint8_t_alignment", trace_class
.abi
.uint8_t_alignment
),
577 fmt::arg("uint16_t_alignment", trace_class
.abi
.uint16_t_alignment
),
578 fmt::arg("uint32_t_alignment", trace_class
.abi
.uint32_t_alignment
),
579 fmt::arg("uint64_t_alignment", trace_class
.abi
.uint64_t_alignment
),
580 fmt::arg("long_alignment", trace_class
.abi
.long_alignment
),
581 fmt::arg("long_size", trace_class
.abi
.long_alignment
),
582 fmt::arg("bits_per_long", trace_class
.abi
.bits_per_long
),
583 fmt::arg("uuid", lttng::utils::uuid_to_str(trace_class
.uuid
)),
584 fmt::arg("byte_order",
585 trace_class
.abi
.byte_order
== lst::byte_order::BIG_ENDIAN_
? "be" : "le"),
586 fmt::arg("packet_header_layout", packet_header_visitor
.transfer_description()));
588 /* Declare trace scope and type aliases. */
589 append_metadata_fragment(trace_class_tsdl
);
591 tsdl_trace_environment_visitor environment_visitor
;
592 trace_class
.accept(environment_visitor
);
593 append_metadata_fragment(environment_visitor
.transfer_description());
596 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::clock_class
& clock_class
)
598 auto uuid_str
= clock_class
.uuid
?
599 fmt::format(" uuid = \"{}\";\n",
600 lttng::utils::uuid_to_str(*clock_class
.uuid
)) :
603 /* Assumes a single clock that maps to specific stream class fields/roles. */
604 auto clock_class_str
= fmt::format(
606 " name = \"{name}\";\n"
609 " description = \"{description}\";\n"
610 " freq = {frequency};\n"
611 " offset = {offset};\n"
614 fmt::arg("name", clock_class
.name
),
615 fmt::arg("uuid", uuid_str
),
616 fmt::arg("description", clock_class
.description
),
617 fmt::arg("frequency", clock_class
.frequency
),
618 fmt::arg("offset", clock_class
.offset
));
620 append_metadata_fragment(clock_class_str
);
623 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::stream_class
& stream_class
)
625 auto stream_class_str
= fmt::format("stream {{\n"
626 " id = {};\n", stream_class
.id
);
628 const auto *event_header
= stream_class
.get_event_header();
630 auto event_header_visitor
= tsdl_field_visitor(
631 _trace_abi
, 1, stream_class
.default_clock_class_name
);
633 event_header
->accept(event_header_visitor
);
634 stream_class_str
+= fmt::format(" event.header := {};\n",
635 event_header_visitor
.transfer_description());
638 const auto *packet_context
= stream_class
.get_packet_context();
639 if (packet_context
) {
640 auto packet_context_visitor
= tsdl_field_visitor(
641 _trace_abi
, 1, stream_class
.default_clock_class_name
);
643 packet_context
->accept(packet_context_visitor
);
644 stream_class_str
+= fmt::format(" packet.context := {};\n",
645 packet_context_visitor
.transfer_description());
648 const auto *event_context
= stream_class
.get_event_context();
650 auto event_context_visitor
= tsdl_field_visitor(_trace_abi
, 1);
652 event_context
->accept(event_context_visitor
);
653 stream_class_str
+= fmt::format(" event.context := {};\n",
654 event_context_visitor
.transfer_description());
657 stream_class_str
+= "};\n\n";
659 append_metadata_fragment(stream_class_str
);
662 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::event_class
& event_class
)
664 auto event_class_str
= fmt::format("event {{\n"
665 " name = \"{name}\";\n"
667 " stream_id = {stream_class_id};\n"
668 " loglevel = {log_level};\n",
669 fmt::arg("name", event_class
.name
),
670 fmt::arg("id", event_class
.id
),
671 fmt::arg("stream_class_id", event_class
.stream_class_id
),
672 fmt::arg("log_level", event_class
.log_level
));
674 if (event_class
.model_emf_uri
) {
675 event_class_str
+= fmt::format(
676 " model.emf.uri = \"{}\";\n", *event_class
.model_emf_uri
);
679 auto payload_visitor
= tsdl_field_visitor(_trace_abi
, 1);
681 event_class
.payload
->accept(static_cast<lst::type_visitor
&>(payload_visitor
));
683 event_class_str
+= fmt::format(
684 " fields := {};\n}};\n\n", payload_visitor
.transfer_description());
686 append_metadata_fragment(event_class_str
);