Fix: sessiond: tsdl: don't prepend underscore for stream_id
[lttng-tools.git] / src / bin / lttng-sessiond / tsdl-trace-class-visitor.cpp
1 /*
2 * Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 */
7
8 #include "clock-class.hpp"
9 #include "tsdl-trace-class-visitor.hpp"
10
11 #include <common/exception.hpp>
12 #include <common/format.hpp>
13 #include <common/make-unique.hpp>
14 #include <common/uuid.hpp>
15
16 #include <array>
17 #include <locale>
18 #include <queue>
19 #include <set>
20
21 namespace lst = lttng::sessiond::trace;
22 namespace tsdl = lttng::sessiond::tsdl;
23
24 namespace {
25 const auto ctf_spec_major = 1;
26 const auto ctf_spec_minor = 8;
27
28 /*
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.
31 */
32 const std::set<std::string> safe_tsdl_identifiers = {"stream_id"};
33
34 /*
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.
38 *
39 * Changing this behaviour to a smarter algorithm would break readers that have
40 * come to expect this initial underscore.
41 */
42 std::string escape_tsdl_identifier(const std::string& original_identifier)
43 {
44 if (original_identifier.size() == 0) {
45 LTTNG_THROW_ERROR("Invalid 0-length identifier used in trace description");
46 }
47
48 if (safe_tsdl_identifiers.find(original_identifier) != safe_tsdl_identifiers.end()) {
49 return original_identifier;
50 }
51
52 std::string new_identifier;
53 /* Optimisticly assume most identifiers are valid and allocate the same length. */
54 new_identifier.reserve(original_identifier.size());
55 new_identifier = "_";
56
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 += '_';
62 } else {
63 new_identifier += current_char;
64 }
65 }
66
67 return new_identifier;
68 }
69
70 std::string escape_tsdl_env_string_value(const std::string& original_string)
71 {
72 std::string escaped_string;
73
74 escaped_string.reserve(original_string.size());
75
76 for (const auto c : original_string) {
77 switch (c) {
78 case '\n':
79 escaped_string += "\\n";
80 break;
81 case '\\':
82 escaped_string += "\\\\";
83 break;
84 case '"':
85 escaped_string += "\"";
86 break;
87 default:
88 escaped_string += c;
89 break;
90 }
91 }
92
93 return escaped_string;
94 }
95
96 class tsdl_field_visitor : public lttng::sessiond::trace::field_visitor,
97 public lttng::sessiond::trace::type_visitor {
98 public:
99 tsdl_field_visitor(const lst::abi& abi, unsigned int indentation_level) :
100 _indentation_level{indentation_level}, _trace_abi{abi}
101 {
102 }
103
104 std::string& get_description()
105 {
106 return _description;
107 }
108
109 private:
110 virtual void visit(const lst::field& field) override final
111 {
112 /*
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.
118 */
119 _escaped_current_field_name = escape_tsdl_identifier(field.name);
120
121 field._type->accept(*this);
122 _description += " ";
123 _description += _escaped_current_field_name;
124
125 /*
126 * Some types requires suffixes to be appended (e.g. the length of arrays
127 * and sequences, the mappings of enumerations).
128 */
129 while (!_type_suffixes.empty()) {
130 _description += _type_suffixes.front();
131 _type_suffixes.pop();
132 }
133
134 _description += ";";
135 _escaped_current_field_name.clear();
136 }
137
138 virtual void visit(const lst::integer_type& type) override final
139 {
140 _description += "integer { ";
141
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));
146
147 /* Defaults to unsigned. */
148 if (type.signedness_ == lst::integer_type::signedness::SIGNED) {
149 _description += " signed = true;";
150 }
151
152 /* Defaults to 10. */
153 if (type.base_ != lst::integer_type::base::DECIMAL) {
154 unsigned int base;
155
156 switch (type.base_) {
157 case lst::integer_type::base::BINARY:
158 base = 2;
159 break;
160 case lst::integer_type::base::OCTAL:
161 base = 8;
162 break;
163 case lst::integer_type::base::HEXADECIMAL:
164 base = 16;
165 break;
166 default:
167 LTTNG_THROW_ERROR(fmt::format(
168 "Unexpected base encountered while serializing integer type to TSDL: base = {}",
169 (int) type.base_));
170 }
171
172 _description += fmt::format(" base = {};", base);
173 }
174
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";
178
179 _description += fmt::format(" byte_order = {};", byte_order_str);
180 }
181
182 if (_current_integer_encoding_override) {
183 const char *encoding_str;
184
185 switch (*_current_integer_encoding_override) {
186 case lst::string_type::encoding::ASCII:
187 encoding_str = "ASCII";
188 break;
189 case lst::string_type::encoding::UTF8:
190 encoding_str = "UTF8";
191 break;
192 default:
193 LTTNG_THROW_ERROR(fmt::format(
194 "Unexpected encoding encountered while serializing integer type to TSDL: encoding = {}",
195 (int) *_current_integer_encoding_override));
196 }
197
198 _description += fmt::format(" encoding = {};", encoding_str);
199 _current_integer_encoding_override.reset();
200 }
201
202 _description += " }";
203 }
204
205 virtual void visit(const lst::floating_point_type& type) override final
206 {
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));
212
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";
216
217 _description += fmt::format(" byte_order = {};", byte_order_str);
218 }
219
220 _description += " }";
221 }
222
223 template <class EnumerationType>
224 void visit_enumeration(const EnumerationType& type)
225 {
226 /* name follows, when applicable. */
227 _description += "enum : ";
228
229 tsdl_field_visitor integer_visitor{_trace_abi, _indentation_level};
230
231 integer_visitor.visit(static_cast<const lst::integer_type&>(type));
232 _description += integer_visitor.get_description() + " {\n";
233
234 const auto mappings_indentation_level = _indentation_level + 1;
235
236 bool first_mapping = true;
237 for (const auto& mapping : *type._mappings) {
238 if (!first_mapping) {
239 _description += ",\n";
240 }
241
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));
250 } else {
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));
257 }
258
259 first_mapping = false;
260 }
261
262 _description += "\n";
263 _description.resize(_description.size() + _indentation_level, '\t');
264 _description += "}";
265 }
266
267 virtual void visit(const lst::signed_enumeration_type& type) override final
268 {
269 visit_enumeration(type);
270 }
271
272 virtual void visit(const lst::unsigned_enumeration_type& type) override final
273 {
274 visit_enumeration(type);
275 }
276
277 virtual void visit(const lst::static_length_array_type& type) override final
278 {
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');
286 }
287
288 type.element_type->accept(*this);
289 _type_suffixes.emplace(fmt::format("[{}]", type.length));
290 }
291
292 virtual void visit(const lst::dynamic_length_array_type& type) override final
293 {
294 if (type.alignment != 0) {
295 /*
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.
300 */
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');
307 }
308
309 type.element_type->accept(*this);
310 _type_suffixes.emplace(fmt::format(
311 "[{}]", escape_tsdl_identifier(type.length_field_name)));
312 }
313
314 virtual void visit(const lst::static_length_blob_type& type) override final
315 {
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);
322
323 visit(*array);
324 }
325
326 virtual void visit(const lst::dynamic_length_blob_type& type) override final
327 {
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);
334
335 visit(*array);
336 }
337
338 virtual void visit(const lst::null_terminated_string_type& type) override final
339 {
340 /* Defaults to UTF-8. */
341 if (type.encoding_ == lst::null_terminated_string_type::encoding::ASCII) {
342 _description += "string { encoding = ASCII }";
343 } else {
344 _description += "string";
345 }
346 }
347
348 virtual void visit(const lst::structure_type& type) override final
349 {
350 _indentation_level++;
351 _description += "struct {";
352
353 for (const auto& field : type._fields) {
354 _description += "\n";
355 _description.resize(_description.size() + _indentation_level, '\t');
356 field->accept(*this);
357 }
358
359 _indentation_level--;
360 if (type._fields.size() != 0) {
361 _description += "\n";
362 _description.resize(_description.size() + _indentation_level, '\t');
363 }
364
365 _description += "};";
366 }
367
368 virtual void visit(const lst::variant_type& type) override final
369 {
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');
377 }
378
379 _indentation_level++;
380 _description += fmt::format("variant <{}> {\n", escape_tsdl_identifier(type.tag_name));
381
382 bool first_field = true;
383 for (const auto& field : type._choices) {
384 if (!first_field) {
385 _description += ",\n";
386 }
387
388 _description.resize(_description.size() + _indentation_level, '\t');
389 field->accept(*this);
390 first_field = false;
391 }
392
393 _description += "\n";
394 _description.resize(_description.size() + _indentation_level, '\t');
395 _description += "};";
396 _indentation_level--;
397 }
398
399 lst::type::cuptr create_character_type(enum lst::string_type::encoding encoding)
400 {
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);
405 }
406
407 virtual void visit(const lst::static_length_string_type& type) override final
408 {
409 /*
410 * TSDL expresses static-length strings as arrays of 8-bit integer with
411 * an encoding specified.
412 */
413 const auto char_array = lttng::make_unique<lst::static_length_array_type>(
414 type.alignment, create_character_type(type.encoding_), type.length);
415
416 visit(*char_array);
417 }
418
419 virtual void visit(const lst::dynamic_length_string_type& type) override final
420 {
421 /*
422 * TSDL expresses dynamic-length strings as arrays of 8-bit integer with
423 * an encoding specified.
424 */
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);
428
429 visit(*char_sequence);
430 }
431
432 std::string _escaped_current_field_name;
433 /*
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.
438 */
439 nonstd::optional<enum lst::string_type::encoding> _current_integer_encoding_override;
440
441 unsigned int _indentation_level;
442 const lst::abi& _trace_abi;
443
444 std::queue<std::string> _type_suffixes;
445
446 /* Description in TSDL format. */
447 std::string _description;
448 };
449 } /* namespace */
450
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)
454 {
455 }
456
457 void tsdl::trace_class_visitor::append_metadata_fragment(const std::string& fragment) const
458 {
459 _append_metadata_fragment(fragment);
460 }
461
462 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::trace_class& trace_class)
463 {
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"
474 "\n"
475 "trace {{\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"
481 " uint32_t magic;\n"
482 " uint8_t uuid[16];\n"
483 " uint32_t stream_id;\n"
484 " uint64_t stream_instance_id;\n"
485 " }};\n"
486 "}};\n\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_ ?
499 "be" :
500 "le"));
501
502 /* Declare trace scope and type aliases. */
503 append_metadata_fragment(std::move(trace_class_tsdl));
504 }
505
506 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::clock_class& clock_class)
507 {
508 auto uuid_str = clock_class.uuid ?
509 fmt::format(" uuid = \"{}\";\n",
510 lttng::utils::uuid_to_str(*clock_class.uuid)) :
511 "";
512
513 /* Assumes a single clock that maps to specific stream class fields/roles. */
514 auto clock_class_str = fmt::format(
515 "clock {{\n"
516 " name = \"{name}\";\n"
517 /* Optional uuid. */
518 "{uuid}"
519 " description = \"{description}\";\n"
520 " freq = {frequency};\n"
521 " offset = {offset};\n"
522 "}};\n"
523 "\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"
528 "\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"
533 "\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"
538 "\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"
547 "}};\n"
548 "\n"
549 "struct event_header_compact {{\n"
550 " enum : uint5_t {{ compact = 0 ... 30, extended = 31 }} id;\n"
551 " variant <id> {{\n"
552 " struct {{\n"
553 " uint27_clock_{name}_t timestamp;\n"
554 " }} compact;\n"
555 " struct {{\n"
556 " uint32_t id;\n"
557 " uint64_clock_{name}_t timestamp;\n"
558 " }} extended;\n"
559 " }} v;\n"
560 "}} align({uint32_t_alignment});\n"
561 "\n"
562 "struct event_header_large {{\n"
563 " enum : uint16_t {{ compact = 0 ... 65534, extended = 65535 }} id;\n"
564 " variant <id> {{\n"
565 " struct {{\n"
566 " uint32_clock_{name}_t timestamp;\n"
567 " }} compact;\n"
568 " struct {{\n"
569 " uint32_t id;\n"
570 " uint64_clock_{name}_t timestamp;\n"
571 " }} extended;\n"
572 " }} v;\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));
582
583 append_metadata_fragment(std::move(clock_class_str));
584 }
585
586 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::stream_class& stream_class)
587 {
588 /* Declare stream. */
589 auto stream_class_str = fmt::format("stream {{\n"
590 " id = {id};\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"));
597
598 auto context_field_visitor = tsdl_field_visitor(_trace_abi, 1);
599
600 stream_class.get_context().accept(static_cast<lst::type_visitor&>(context_field_visitor));
601
602 stream_class_str += fmt::format(" event.context := {}\n}};\n\n",
603 context_field_visitor.get_description());
604
605 append_metadata_fragment(stream_class_str);
606 }
607
608 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::event_class& event_class)
609 {
610 auto event_class_str = fmt::format("event {{\n"
611 " name = \"{name}\";\n"
612 " id = {id};\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));
619
620 if (event_class.model_emf_uri) {
621 event_class_str += fmt::format(
622 " model.emf.uri = \"{}\";\n", *event_class.model_emf_uri);
623 }
624
625 auto payload_visitor = tsdl_field_visitor(_trace_abi, 1);
626
627 event_class.payload->accept(static_cast<lst::type_visitor&>(payload_visitor));
628
629 event_class_str += fmt::format(
630 " fields := {}\n}};\n\n", payload_visitor.get_description());
631
632 append_metadata_fragment(event_class_str);
633 }
634
635 void tsdl::trace_class_visitor::environment_begin()
636 {
637 _environment += "env {\n";
638 }
639
640 void tsdl::trace_class_visitor::visit(
641 const lttng::sessiond::trace::environment_field<int64_t>& field)
642 {
643 _environment += fmt::format(" {} = {};\n", field.name, field.value);
644 }
645
646 void tsdl::trace_class_visitor::visit(
647 const lttng::sessiond::trace::environment_field<const char *>& field)
648 {
649 _environment += fmt::format(
650 " {} = \"{}\";\n", field.name, escape_tsdl_env_string_value(field.value));
651 }
652
653 void tsdl::trace_class_visitor::environment_end()
654 {
655 _environment += "};\n\n";
656 append_metadata_fragment(_environment);
657 _environment.clear();
658 }
This page took 0.047736 seconds and 5 git commands to generate.