2 * Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com>
4 * SPDX-License-Identifier: GPL-2.0-only
8 #include "clock-class.hpp"
9 #include "tsdl-trace-class-visitor.hpp"
11 #include <common/exception.hpp>
12 #include <common/format.hpp>
13 #include <common/make-unique.hpp>
14 #include <common/uuid.hpp>
21 namespace lst
= lttng::sessiond::trace
;
22 namespace tsdl
= lttng::sessiond::tsdl
;
25 const auto ctf_spec_major
= 1;
26 const auto ctf_spec_minor
= 8;
29 * Although the CTF v1.8 specification recommends ignoring any leading underscore, Some readers,
30 * such as Babeltrace 1.x, expect special identifiers without a prepended underscore.
32 const std::set
<std::string
> safe_tsdl_identifiers
= {"stream_id"};
35 * A previous implementation always prepended '_' to the identifiers in order to
36 * side-step the problem of escaping TSDL keywords and ensuring identifiers
37 * started with an alphabetic character.
39 * Changing this behaviour to a smarter algorithm would break readers that have
40 * come to expect this initial underscore.
42 std::string
escape_tsdl_identifier(const std::string
& original_identifier
)
44 if (original_identifier
.size() == 0) {
45 LTTNG_THROW_ERROR("Invalid 0-length identifier used in trace description");
48 if (safe_tsdl_identifiers
.find(original_identifier
) != safe_tsdl_identifiers
.end()) {
49 return original_identifier
;
52 std::string new_identifier
;
53 /* Optimisticly assume most identifiers are valid and allocate the same length. */
54 new_identifier
.reserve(original_identifier
.size());
57 /* Replace illegal characters by '_'. */
58 std::locale c_locale
{"C"};
59 for (const auto current_char
: original_identifier
) {
60 if (!std::isalnum(current_char
, c_locale
) && current_char
!= '_') {
61 new_identifier
+= '_';
63 new_identifier
+= current_char
;
67 return new_identifier
;
70 std::string
escape_tsdl_env_string_value(const std::string
& original_string
)
72 std::string escaped_string
;
74 escaped_string
.reserve(original_string
.size());
76 for (const auto c
: original_string
) {
79 escaped_string
+= "\\n";
82 escaped_string
+= "\\\\";
85 escaped_string
+= "\"";
93 return escaped_string
;
96 class tsdl_field_visitor
: public lttng::sessiond::trace::field_visitor
,
97 public lttng::sessiond::trace::type_visitor
{
99 tsdl_field_visitor(const lst::abi
& abi
, unsigned int indentation_level
) :
100 _indentation_level
{indentation_level
}, _trace_abi
{abi
}
104 std::string
& get_description()
110 virtual void visit(const lst::field
& field
) override final
113 * Hack: keep the name of the field being visited since
114 * the tracers can express sequences, variants, and arrays with an alignment
115 * constraint, which is not expressible in TSDL. To work around this limitation, an
116 * empty structure declaration is inserted when needed to express the aligment
117 * constraint. The name of this structure is generated using the field's name.
119 _escaped_current_field_name
= escape_tsdl_identifier(field
.name
);
121 field
._type
->accept(*this);
123 _description
+= _escaped_current_field_name
;
126 * Some types requires suffixes to be appended (e.g. the length of arrays
127 * and sequences, the mappings of enumerations).
129 while (!_type_suffixes
.empty()) {
130 _description
+= _type_suffixes
.front();
131 _type_suffixes
.pop();
135 _escaped_current_field_name
.clear();
138 virtual void visit(const lst::integer_type
& type
) override final
140 _description
+= "integer { ";
142 /* Mandatory properties (no defaults). */
143 _description
+= fmt::format("size = {size}; align = {alignment};",
144 fmt::arg("size", type
.size
),
145 fmt::arg("alignment", type
.alignment
));
147 /* Defaults to unsigned. */
148 if (type
.signedness_
== lst::integer_type::signedness::SIGNED
) {
149 _description
+= " signed = true;";
152 /* Defaults to 10. */
153 if (type
.base_
!= lst::integer_type::base::DECIMAL
) {
156 switch (type
.base_
) {
157 case lst::integer_type::base::BINARY
:
160 case lst::integer_type::base::OCTAL
:
163 case lst::integer_type::base::HEXADECIMAL
:
167 LTTNG_THROW_ERROR(fmt::format(
168 "Unexpected base encountered while serializing integer type to TSDL: base = {}",
172 _description
+= fmt::format(" base = {};", base
);
175 /* Defaults to the trace's native byte order. */
176 if (type
.byte_order
!= _trace_abi
.byte_order
) {
177 const auto byte_order_str
= type
.byte_order
== lst::byte_order::BIG_ENDIAN_
? "be" : "le";
179 _description
+= fmt::format(" byte_order = {};", byte_order_str
);
182 if (_current_integer_encoding_override
) {
183 const char *encoding_str
;
185 switch (*_current_integer_encoding_override
) {
186 case lst::string_type::encoding::ASCII
:
187 encoding_str
= "ASCII";
189 case lst::string_type::encoding::UTF8
:
190 encoding_str
= "UTF8";
193 LTTNG_THROW_ERROR(fmt::format(
194 "Unexpected encoding encountered while serializing integer type to TSDL: encoding = {}",
195 (int) *_current_integer_encoding_override
));
198 _description
+= fmt::format(" encoding = {};", encoding_str
);
199 _current_integer_encoding_override
.reset();
202 _description
+= " }";
205 virtual void visit(const lst::floating_point_type
& type
) override final
207 _description
+= fmt::format(
208 "floating_point {{ align = {alignment}; mant_dig = {mantissa_digits}; exp_dig = {exponent_digits};",
209 fmt::arg("alignment", type
.alignment
),
210 fmt::arg("mantissa_digits", type
.mantissa_digits
),
211 fmt::arg("exponent_digits", type
.exponent_digits
));
213 /* Defaults to the trace's native byte order. */
214 if (type
.byte_order
!= _trace_abi
.byte_order
) {
215 const auto byte_order_str
= type
.byte_order
== lst::byte_order::BIG_ENDIAN_
? "be" : "le";
217 _description
+= fmt::format(" byte_order = {};", byte_order_str
);
220 _description
+= " }";
223 template <class EnumerationType
>
224 void visit_enumeration(const EnumerationType
& type
)
226 /* name follows, when applicable. */
227 _description
+= "enum : ";
229 tsdl_field_visitor integer_visitor
{_trace_abi
, _indentation_level
};
231 integer_visitor
.visit(static_cast<const lst::integer_type
&>(type
));
232 _description
+= integer_visitor
.get_description() + " {\n";
234 const auto mappings_indentation_level
= _indentation_level
+ 1;
236 bool first_mapping
= true;
237 for (const auto& mapping
: *type
._mappings
) {
238 if (!first_mapping
) {
239 _description
+= ",\n";
242 _description
.resize(_description
.size() + mappings_indentation_level
, '\t');
243 if (!mapping
.range
) {
244 _description
+= fmt::format("\"{}\"", mapping
.name
);
245 } else if (mapping
.range
->begin
== mapping
.range
->end
) {
246 _description
+= fmt::format(
247 "\"{mapping_name}\" = {mapping_value}",
248 fmt::arg("mapping_name", mapping
.name
),
249 fmt::arg("mapping_value", mapping
.range
->begin
));
251 _description
+= fmt::format(
252 "\"{mapping_name}\" = {mapping_range_begin} ... {mapping_range_end}",
253 fmt::arg("mapping_name", mapping
.name
),
254 fmt::arg("mapping_range_begin",
255 mapping
.range
->begin
),
256 fmt::arg("mapping_range_end", mapping
.range
->end
));
259 first_mapping
= false;
262 _description
+= "\n";
263 _description
.resize(_description
.size() + _indentation_level
, '\t');
267 virtual void visit(const lst::signed_enumeration_type
& type
) override final
269 visit_enumeration(type
);
272 virtual void visit(const lst::unsigned_enumeration_type
& type
) override final
274 visit_enumeration(type
);
277 virtual void visit(const lst::static_length_array_type
& type
) override final
279 if (type
.alignment
!= 0) {
280 LTTNG_ASSERT(_escaped_current_field_name
.size() > 0);
281 _description
+= fmt::format(
282 "struct {{ }} align({alignment}) {field_name}_padding;\n",
283 fmt::arg("alignment", type
.alignment
),
284 fmt::arg("field_name", _escaped_current_field_name
));
285 _description
.resize(_description
.size() + _indentation_level
, '\t');
288 type
.element_type
->accept(*this);
289 _type_suffixes
.emplace(fmt::format("[{}]", type
.length
));
292 virtual void visit(const lst::dynamic_length_array_type
& type
) override final
294 if (type
.alignment
!= 0) {
296 * Note that this doesn't support nested sequences. For
297 * the moment, tracers can't express those. However, we
298 * could wrap nested sequences in structures, which
299 * would allow us to express alignment constraints.
301 LTTNG_ASSERT(_escaped_current_field_name
.size() > 0);
302 _description
+= fmt::format(
303 "struct {{ }} align({alignment}) {field_name}_padding;\n",
304 fmt::arg("alignment", type
.alignment
),
305 fmt::arg("field_name", _escaped_current_field_name
));
306 _description
.resize(_description
.size() + _indentation_level
, '\t');
309 type
.element_type
->accept(*this);
310 _type_suffixes
.emplace(fmt::format(
311 "[{}]", escape_tsdl_identifier(type
.length_field_name
)));
314 virtual void visit(const lst::static_length_blob_type
& type
) override final
316 /* This type doesn't exist in CTF 1.x, express it as a static length array of uint8_t. */
317 std::unique_ptr
<const lst::type
> uint8_element
= lttng::make_unique
<lst::integer_type
>(8,
318 _trace_abi
.byte_order
, 8, lst::integer_type::signedness::UNSIGNED
,
319 lst::integer_type::base::HEXADECIMAL
);
320 const auto array
= lttng::make_unique
<lst::static_length_array_type
>(
321 type
.alignment
, std::move(uint8_element
), type
.length_bytes
);
326 virtual void visit(const lst::dynamic_length_blob_type
& type
) override final
328 /* This type doesn't exist in CTF 1.x, express it as a dynamic length array of uint8_t. */
329 std::unique_ptr
<const lst::type
> uint8_element
= lttng::make_unique
<lst::integer_type
>(0,
330 _trace_abi
.byte_order
, 8, lst::integer_type::signedness::UNSIGNED
,
331 lst::integer_type::base::HEXADECIMAL
);
332 const auto array
= lttng::make_unique
<lst::dynamic_length_array_type
>(
333 type
.alignment
, std::move(uint8_element
), type
.length_field_name
);
338 virtual void visit(const lst::null_terminated_string_type
& type
) override final
340 /* Defaults to UTF-8. */
341 if (type
.encoding_
== lst::null_terminated_string_type::encoding::ASCII
) {
342 _description
+= "string { encoding = ASCII }";
344 _description
+= "string";
348 virtual void visit(const lst::structure_type
& type
) override final
350 _indentation_level
++;
351 _description
+= "struct {";
353 for (const auto& field
: type
._fields
) {
354 _description
+= "\n";
355 _description
.resize(_description
.size() + _indentation_level
, '\t');
356 field
->accept(*this);
359 _indentation_level
--;
360 if (type
._fields
.size() != 0) {
361 _description
+= "\n";
362 _description
.resize(_description
.size() + _indentation_level
, '\t');
365 _description
+= "};";
368 virtual void visit(const lst::variant_type
& type
) override final
370 if (type
.alignment
!= 0) {
371 LTTNG_ASSERT(_escaped_current_field_name
.size() > 0);
372 _description
+= fmt::format(
373 "struct {{ }} align({alignment}) {field_name}_padding;\n",
374 fmt::arg("alignment", type
.alignment
),
375 fmt::arg("field_name", _escaped_current_field_name
));
376 _description
.resize(_description
.size() + _indentation_level
, '\t');
379 _indentation_level
++;
380 _description
+= fmt::format("variant <{}> {\n", escape_tsdl_identifier(type
.tag_name
));
382 bool first_field
= true;
383 for (const auto& field
: type
._choices
) {
385 _description
+= ",\n";
388 _description
.resize(_description
.size() + _indentation_level
, '\t');
389 field
->accept(*this);
393 _description
+= "\n";
394 _description
.resize(_description
.size() + _indentation_level
, '\t');
395 _description
+= "};";
396 _indentation_level
--;
399 lst::type::cuptr
create_character_type(enum lst::string_type::encoding encoding
)
401 _current_integer_encoding_override
= encoding
;
402 return lttng::make_unique
<lst::integer_type
>(8, _trace_abi
.byte_order
, 8,
403 lst::integer_type::signedness::UNSIGNED
,
404 lst::integer_type::base::DECIMAL
);
407 virtual void visit(const lst::static_length_string_type
& type
) override final
410 * TSDL expresses static-length strings as arrays of 8-bit integer with
411 * an encoding specified.
413 const auto char_array
= lttng::make_unique
<lst::static_length_array_type
>(
414 type
.alignment
, create_character_type(type
.encoding_
), type
.length
);
419 virtual void visit(const lst::dynamic_length_string_type
& type
) override final
422 * TSDL expresses dynamic-length strings as arrays of 8-bit integer with
423 * an encoding specified.
425 const auto char_sequence
= lttng::make_unique
<lst::dynamic_length_array_type
>(
426 type
.alignment
, create_character_type(type
.encoding_
),
427 type
.length_field_name
);
429 visit(*char_sequence
);
432 std::string _escaped_current_field_name
;
434 * Encoding to specify for the next serialized integer type.
435 * Since the integer_type does not allow an encoding to be specified (it is a TSDL-specific
436 * concept), this attribute is used when expressing static or dynamic length strings as
437 * arrays/sequences of bytes with an encoding.
439 nonstd::optional
<enum lst::string_type::encoding
> _current_integer_encoding_override
;
441 unsigned int _indentation_level
;
442 const lst::abi
& _trace_abi
;
444 std::queue
<std::string
> _type_suffixes
;
446 /* Description in TSDL format. */
447 std::string _description
;
451 tsdl::trace_class_visitor::trace_class_visitor(const lst::abi
& trace_abi
,
452 tsdl::append_metadata_fragment_function append_metadata_fragment
) :
453 _trace_abi
{trace_abi
}, _append_metadata_fragment(append_metadata_fragment
)
457 void tsdl::trace_class_visitor::append_metadata_fragment(const std::string
& fragment
) const
459 _append_metadata_fragment(fragment
);
462 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::trace_class
& trace_class
)
464 /* Declare type aliases, trace class, and packet header. */
465 auto trace_class_tsdl
= fmt::format(
466 "/* CTF {ctf_major}.{ctf_minor} */\n\n"
467 "typealias integer {{ size = 8; align = {uint8_t_alignment}; signed = false; }} := uint8_t;\n"
468 "typealias integer {{ size = 16; align = {uint16_t_alignment}; signed = false; }} := uint16_t;\n"
469 "typealias integer {{ size = 32; align = {uint32_t_alignment}; signed = false; }} := uint32_t;\n"
470 "typealias integer {{ size = 64; align = {uint64_t_alignment}; signed = false; }} := uint64_t;\n"
471 "typealias integer {{ size = {bits_per_long}; align = {long_alignment}; signed = false; }} := unsigned long;\n"
472 "typealias integer {{ size = 5; align = 1; signed = false; }} := uint5_t;\n"
473 "typealias integer {{ size = 27; align = 1; signed = false; }} := uint27_t;\n"
476 " major = {ctf_major};\n"
477 " minor = {ctf_minor};\n"
478 " uuid = \"{uuid}\";\n"
479 " byte_order = {byte_order};\n"
480 " packet.header := struct {{\n"
482 " uint8_t uuid[16];\n"
483 " uint32_t stream_id;\n"
484 " uint64_t stream_instance_id;\n"
487 fmt::arg("ctf_major", ctf_spec_major
),
488 fmt::arg("ctf_minor", ctf_spec_minor
),
489 fmt::arg("uint8_t_alignment", trace_class
.abi
.uint8_t_alignment
),
490 fmt::arg("uint16_t_alignment", trace_class
.abi
.uint16_t_alignment
),
491 fmt::arg("uint32_t_alignment", trace_class
.abi
.uint32_t_alignment
),
492 fmt::arg("uint64_t_alignment", trace_class
.abi
.uint64_t_alignment
),
493 fmt::arg("long_alignment", trace_class
.abi
.long_alignment
),
494 fmt::arg("long_size", trace_class
.abi
.long_alignment
),
495 fmt::arg("bits_per_long", trace_class
.abi
.bits_per_long
),
496 fmt::arg("uuid", lttng::utils::uuid_to_str(trace_class
.uuid
)),
497 fmt::arg("byte_order",
498 trace_class
.abi
.byte_order
== lst::byte_order::BIG_ENDIAN_
?
502 /* Declare trace scope and type aliases. */
503 append_metadata_fragment(std::move(trace_class_tsdl
));
506 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::clock_class
& clock_class
)
508 auto uuid_str
= clock_class
.uuid
?
509 fmt::format(" uuid = \"{}\";\n",
510 lttng::utils::uuid_to_str(*clock_class
.uuid
)) :
513 /* Assumes a single clock that maps to specific stream class fields/roles. */
514 auto clock_class_str
= fmt::format(
516 " name = \"{name}\";\n"
519 " description = \"{description}\";\n"
520 " freq = {frequency};\n"
521 " offset = {offset};\n"
524 "typealias integer {{\n"
525 " size = 27; align = 1; signed = false;\n"
526 " map = clock.{name}.value;\n"
527 "}} := uint27_clock_{name}_t;\n"
529 "typealias integer {{\n"
530 " size = 32; align = {uint32_t_alignment}; signed = false;\n"
531 " map = clock.{name}.value;\n"
532 "}} := uint32_clock_{name}_t;\n"
534 "typealias integer {{\n"
535 " size = 64; align = {uint64_t_alignment}; signed = false;\n"
536 " map = clock.{name}.value;\n"
537 "}} := uint64_clock_{name}_t;\n"
539 "struct packet_context {{\n"
540 " uint64_clock_{name}_t timestamp_begin;\n"
541 " uint64_clock_{name}_t timestamp_end;\n"
542 " uint64_t content_size;\n"
543 " uint64_t packet_size;\n"
544 " uint64_t packet_seq_num;\n"
545 " unsigned long events_discarded;\n"
546 " uint32_t cpu_id;\n"
549 "struct event_header_compact {{\n"
550 " enum : uint5_t {{ compact = 0 ... 30, extended = 31 }} id;\n"
553 " uint27_clock_{name}_t timestamp;\n"
557 " uint64_clock_{name}_t timestamp;\n"
560 "}} align({uint32_t_alignment});\n"
562 "struct event_header_large {{\n"
563 " enum : uint16_t {{ compact = 0 ... 65534, extended = 65535 }} id;\n"
566 " uint32_clock_{name}_t timestamp;\n"
570 " uint64_clock_{name}_t timestamp;\n"
573 "}} align({uint16_t_alignment});\n\n",
574 fmt::arg("name", clock_class
.name
),
575 fmt::arg("uuid", uuid_str
),
576 fmt::arg("description", clock_class
.description
),
577 fmt::arg("frequency", clock_class
.frequency
),
578 fmt::arg("offset", clock_class
.offset
),
579 fmt::arg("uint16_t_alignment", _trace_abi
.uint16_t_alignment
),
580 fmt::arg("uint32_t_alignment", _trace_abi
.uint32_t_alignment
),
581 fmt::arg("uint64_t_alignment", _trace_abi
.uint64_t_alignment
));
583 append_metadata_fragment(std::move(clock_class_str
));
586 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::stream_class
& stream_class
)
588 /* Declare stream. */
589 auto stream_class_str
= fmt::format("stream {{\n"
591 " event.header := {header_type};\n"
592 " packet.context := struct packet_context;\n",
593 fmt::arg("id", stream_class
.id
),
594 fmt::arg("header_type", stream_class
.header_type_
== lst::stream_class::header_type::COMPACT
?
595 "struct event_header_compact" :
596 "struct event_header_large"));
598 auto context_field_visitor
= tsdl_field_visitor(_trace_abi
, 1);
600 stream_class
.get_context().accept(static_cast<lst::type_visitor
&>(context_field_visitor
));
602 stream_class_str
+= fmt::format(" event.context := {}\n}};\n\n",
603 context_field_visitor
.get_description());
605 append_metadata_fragment(stream_class_str
);
608 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::event_class
& event_class
)
610 auto event_class_str
= fmt::format("event {{\n"
611 " name = \"{name}\";\n"
613 " stream_id = {stream_class_id};\n"
614 " loglevel = {log_level};\n",
615 fmt::arg("name", event_class
.name
),
616 fmt::arg("id", event_class
.id
),
617 fmt::arg("stream_class_id", event_class
.stream_class_id
),
618 fmt::arg("log_level", event_class
.log_level
));
620 if (event_class
.model_emf_uri
) {
621 event_class_str
+= fmt::format(
622 " model.emf.uri = \"{}\";\n", *event_class
.model_emf_uri
);
625 auto payload_visitor
= tsdl_field_visitor(_trace_abi
, 1);
627 event_class
.payload
->accept(static_cast<lst::type_visitor
&>(payload_visitor
));
629 event_class_str
+= fmt::format(
630 " fields := {}\n}};\n\n", payload_visitor
.get_description());
632 append_metadata_fragment(event_class_str
);
635 void tsdl::trace_class_visitor::environment_begin()
637 _environment
+= "env {\n";
640 void tsdl::trace_class_visitor::visit(
641 const lttng::sessiond::trace::environment_field
<int64_t>& field
)
643 _environment
+= fmt::format(" {} = {};\n", field
.name
, field
.value
);
646 void tsdl::trace_class_visitor::visit(
647 const lttng::sessiond::trace::environment_field
<const char *>& field
)
649 _environment
+= fmt::format(
650 " {} = \"{}\";\n", field
.name
, escape_tsdl_env_string_value(field
.value
));
653 void tsdl::trace_class_visitor::environment_end()
655 _environment
+= "};\n\n";
656 append_metadata_fragment(_environment
);
657 _environment
.clear();