sessiond: generate packet header, packet context and event header dynamically
[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 "tsdl-trace-class-visitor.hpp"
9 #include "clock-class.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 <vendor/optional.hpp>
17
18 #include <algorithm>
19 #include <array>
20 #include <locale>
21 #include <queue>
22 #include <set>
23 #include <stack>
24
25 namespace lst = lttng::sessiond::trace;
26 namespace tsdl = lttng::sessiond::tsdl;
27
28 namespace {
29 const auto ctf_spec_major = 1;
30 const auto ctf_spec_minor = 8;
31
32 /*
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.
35 */
36 const std::set<std::string> safe_tsdl_identifiers = {
37 "stream_id",
38 "packet_size",
39 "content_size",
40 "id",
41 "v",
42 "timestamp",
43 "events_discarded",
44 "packet_seq_num",
45 "timestamp_begin",
46 "timestamp_end",
47 "cpu_id",
48 "magic",
49 "uuid",
50 "stream_instance_id"
51 };
52
53 /*
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.
57 *
58 * Changing this behaviour to a smarter algorithm would break readers that have
59 * come to expect this initial underscore.
60 */
61 std::string escape_tsdl_identifier(const std::string& original_identifier)
62 {
63 if (original_identifier.size() == 0) {
64 LTTNG_THROW_ERROR("Invalid 0-length identifier used in trace description");
65 }
66
67 if (safe_tsdl_identifiers.find(original_identifier) != safe_tsdl_identifiers.end()) {
68 return original_identifier;
69 }
70
71 std::string new_identifier;
72 /* Optimisticly assume most identifiers are valid and allocate the same length. */
73 new_identifier.reserve(original_identifier.size());
74 new_identifier = "_";
75
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 += '_';
81 } else {
82 new_identifier += current_char;
83 }
84 }
85
86 return new_identifier;
87 }
88
89 std::string escape_tsdl_env_string_value(const std::string& original_string)
90 {
91 std::string escaped_string;
92
93 escaped_string.reserve(original_string.size());
94
95 for (const auto c : original_string) {
96 switch (c) {
97 case '\n':
98 escaped_string += "\\n";
99 break;
100 case '\\':
101 escaped_string += "\\\\";
102 break;
103 case '"':
104 escaped_string += "\"";
105 break;
106 default:
107 escaped_string += c;
108 break;
109 }
110 }
111
112 return escaped_string;
113 }
114
115 class tsdl_field_visitor : public lttng::sessiond::trace::field_visitor,
116 public lttng::sessiond::trace::type_visitor {
117 public:
118 tsdl_field_visitor(const lst::abi& abi,
119 unsigned int indentation_level,
120 const nonstd::optional<std::string>& in_default_clock_class_name =
121 nonstd::nullopt) :
122 _indentation_level{indentation_level},
123 _trace_abi{abi},
124 _bypass_identifier_escape{false},
125 _default_clock_class_name{in_default_clock_class_name ?
126 in_default_clock_class_name->c_str() :
127 nullptr}
128 {
129 }
130
131 std::string& get_description()
132 {
133 return _description;
134 }
135
136 private:
137 virtual void visit(const lst::field& field) override final
138 {
139 /*
140 * Hack: keep the name of the field being visited since
141 * the tracers can express sequences, variants, and arrays with an alignment
142 * constraint, which is not expressible in TSDL. To work around this limitation, an
143 * empty structure declaration is inserted when needed to express the aligment
144 * constraint. The name of this structure is generated using the field's name.
145 */
146 _current_field_name.push(_bypass_identifier_escape ?
147 field.name : escape_tsdl_identifier(field.name));
148
149 field._type->accept(*this);
150 _description += " ";
151 _description += _current_field_name.top();
152 _current_field_name.pop();
153
154 /*
155 * Some types requires suffixes to be appended (e.g. the length of arrays
156 * and sequences, the mappings of enumerations).
157 */
158 while (!_type_suffixes.empty()) {
159 _description += _type_suffixes.front();
160 _type_suffixes.pop();
161 }
162
163 _description += ";";
164 }
165
166 virtual void visit(const lst::integer_type& type) override final
167 {
168 _description += "integer { ";
169
170 /* Mandatory properties (no defaults). */
171 _description += fmt::format("size = {size}; align = {alignment};",
172 fmt::arg("size", type.size),
173 fmt::arg("alignment", type.alignment));
174
175 /* Defaults to unsigned. */
176 if (type.signedness_ == lst::integer_type::signedness::SIGNED) {
177 _description += " signed = true;";
178 }
179
180 /* Defaults to 10. */
181 if (type.base_ != lst::integer_type::base::DECIMAL) {
182 unsigned int base;
183
184 switch (type.base_) {
185 case lst::integer_type::base::BINARY:
186 base = 2;
187 break;
188 case lst::integer_type::base::OCTAL:
189 base = 8;
190 break;
191 case lst::integer_type::base::HEXADECIMAL:
192 base = 16;
193 break;
194 default:
195 LTTNG_THROW_ERROR(fmt::format(
196 "Unexpected base encountered while serializing integer type to TSDL: base = {}",
197 (int) type.base_));
198 }
199
200 _description += fmt::format(" base = {};", base);
201 }
202
203 /* Defaults to the trace's native byte order. */
204 if (type.byte_order != _trace_abi.byte_order) {
205 const auto byte_order_str = type.byte_order == lst::byte_order::BIG_ENDIAN_ ? "be" : "le";
206
207 _description += fmt::format(" byte_order = {};", byte_order_str);
208 }
209
210 if (_current_integer_encoding_override) {
211 const char *encoding_str;
212
213 switch (*_current_integer_encoding_override) {
214 case lst::string_type::encoding::ASCII:
215 encoding_str = "ASCII";
216 break;
217 case lst::string_type::encoding::UTF8:
218 encoding_str = "UTF8";
219 break;
220 default:
221 LTTNG_THROW_ERROR(fmt::format(
222 "Unexpected encoding encountered while serializing integer type to TSDL: encoding = {}",
223 (int) *_current_integer_encoding_override));
224 }
225
226 _description += fmt::format(" encoding = {};", encoding_str);
227 _current_integer_encoding_override.reset();
228 }
229
230 if (std::find(type.roles_.begin(), type.roles_.end(),
231 lst::integer_type::role::DEFAULT_CLOCK_TIMESTAMP) !=
232 type.roles_.end() ||
233 std::find(type.roles_.begin(), type.roles_.end(),
234 lst::integer_type::role::
235 PACKET_END_DEFAULT_CLOCK_TIMESTAMP) !=
236 type.roles_.end()) {
237 LTTNG_ASSERT(_default_clock_class_name);
238 _description += fmt::format(
239 " map = clock.{}.value;", _default_clock_class_name);
240 }
241
242 _description += " }";
243 }
244
245 virtual void visit(const lst::floating_point_type& type) override final
246 {
247 _description += fmt::format(
248 "floating_point {{ align = {alignment}; mant_dig = {mantissa_digits}; exp_dig = {exponent_digits};",
249 fmt::arg("alignment", type.alignment),
250 fmt::arg("mantissa_digits", type.mantissa_digits),
251 fmt::arg("exponent_digits", type.exponent_digits));
252
253 /* Defaults to the trace's native byte order. */
254 if (type.byte_order != _trace_abi.byte_order) {
255 const auto byte_order_str = type.byte_order == lst::byte_order::BIG_ENDIAN_ ? "be" : "le";
256
257 _description += fmt::format(" byte_order = {};", byte_order_str);
258 }
259
260 _description += " }";
261 }
262
263 template <class EnumerationType>
264 void visit_enumeration(const EnumerationType& type)
265 {
266 /* name follows, when applicable. */
267 _description += "enum : ";
268
269 tsdl_field_visitor integer_visitor{_trace_abi, _indentation_level};
270
271 integer_visitor.visit(static_cast<const lst::integer_type&>(type));
272 _description += integer_visitor.get_description() + " {\n";
273
274 const auto mappings_indentation_level = _indentation_level + 1;
275
276 bool first_mapping = true;
277 for (const auto& mapping : *type._mappings) {
278 if (!first_mapping) {
279 _description += ",\n";
280 }
281
282 _description.resize(_description.size() + mappings_indentation_level, '\t');
283 if (!mapping.range) {
284 _description += fmt::format("\"{}\"", mapping.name);
285 } else if (mapping.range->begin == mapping.range->end) {
286 _description += fmt::format(
287 "\"{mapping_name}\" = {mapping_value}",
288 fmt::arg("mapping_name", mapping.name),
289 fmt::arg("mapping_value", mapping.range->begin));
290 } else {
291 _description += fmt::format(
292 "\"{mapping_name}\" = {mapping_range_begin} ... {mapping_range_end}",
293 fmt::arg("mapping_name", mapping.name),
294 fmt::arg("mapping_range_begin",
295 mapping.range->begin),
296 fmt::arg("mapping_range_end", mapping.range->end));
297 }
298
299 first_mapping = false;
300 }
301
302 _description += "\n";
303 _description.resize(_description.size() + _indentation_level, '\t');
304 _description += "}";
305 }
306
307 virtual void visit(const lst::signed_enumeration_type& type) override final
308 {
309 visit_enumeration(type);
310 }
311
312 virtual void visit(const lst::unsigned_enumeration_type& type) override final
313 {
314 visit_enumeration(type);
315 }
316
317 virtual void visit(const lst::static_length_array_type& type) override final
318 {
319 if (type.alignment != 0) {
320 LTTNG_ASSERT(_current_field_name.size() > 0);
321 _description += fmt::format(
322 "struct {{ }} align({alignment}) {field_name}_padding;\n",
323 fmt::arg("alignment", type.alignment),
324 fmt::arg("field_name", _current_field_name.top()));
325 _description.resize(_description.size() + _indentation_level, '\t');
326 }
327
328 type.element_type->accept(*this);
329 _type_suffixes.emplace(fmt::format("[{}]", type.length));
330 }
331
332 virtual void visit(const lst::dynamic_length_array_type& type) override final
333 {
334 if (type.alignment != 0) {
335 /*
336 * Note that this doesn't support nested sequences. For
337 * the moment, tracers can't express those. However, we
338 * could wrap nested sequences in structures, which
339 * would allow us to express alignment constraints.
340 */
341 LTTNG_ASSERT(_current_field_name.size() > 0);
342 _description += fmt::format(
343 "struct {{ }} align({alignment}) {field_name}_padding;\n",
344 fmt::arg("alignment", type.alignment),
345 fmt::arg("field_name", _current_field_name.top()));
346 _description.resize(_description.size() + _indentation_level, '\t');
347 }
348
349 type.element_type->accept(*this);
350 _type_suffixes.emplace(fmt::format("[{}]",
351 _bypass_identifier_escape ?
352 type.length_field_name :
353 escape_tsdl_identifier(type.length_field_name)));
354 }
355
356 virtual void visit(const lst::static_length_blob_type& type) override final
357 {
358 /* This type doesn't exist in CTF 1.x, express it as a static length array of uint8_t. */
359 std::unique_ptr<const lst::type> uint8_element = lttng::make_unique<lst::integer_type>(8,
360 _trace_abi.byte_order, 8, lst::integer_type::signedness::UNSIGNED,
361 lst::integer_type::base::HEXADECIMAL);
362 const auto array = lttng::make_unique<lst::static_length_array_type>(
363 type.alignment, std::move(uint8_element), type.length_bytes);
364
365 visit(*array);
366 }
367
368 virtual void visit(const lst::dynamic_length_blob_type& type) override final
369 {
370 /* This type doesn't exist in CTF 1.x, express it as a dynamic length array of uint8_t. */
371 std::unique_ptr<const lst::type> uint8_element = lttng::make_unique<lst::integer_type>(0,
372 _trace_abi.byte_order, 8, lst::integer_type::signedness::UNSIGNED,
373 lst::integer_type::base::HEXADECIMAL);
374 const auto array = lttng::make_unique<lst::dynamic_length_array_type>(
375 type.alignment, std::move(uint8_element), type.length_field_name);
376
377 visit(*array);
378 }
379
380 virtual void visit(const lst::null_terminated_string_type& type) override final
381 {
382 /* Defaults to UTF-8. */
383 if (type.encoding_ == lst::null_terminated_string_type::encoding::ASCII) {
384 _description += "string { encoding = ASCII }";
385 } else {
386 _description += "string";
387 }
388 }
389
390 virtual void visit(const lst::structure_type& type) override final
391 {
392 _indentation_level++;
393 _description += "struct {";
394
395 const auto previous_bypass_identifier_escape = _bypass_identifier_escape;
396 _bypass_identifier_escape = false;
397 for (const auto& field : type._fields) {
398 _description += "\n";
399 _description.resize(_description.size() + _indentation_level, '\t');
400 field->accept(*this);
401 }
402
403 _bypass_identifier_escape = previous_bypass_identifier_escape;
404
405 _indentation_level--;
406 if (type._fields.size() != 0) {
407 _description += "\n";
408 _description.resize(_description.size() + _indentation_level, '\t');
409 }
410
411 _description += "}";
412 }
413
414 virtual void visit(const lst::variant_type& type) override final
415 {
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');
423 }
424
425 _indentation_level++;
426 _description += fmt::format("variant <{}> {{\n",
427 _bypass_identifier_escape ?
428 type.tag_name :
429 escape_tsdl_identifier(type.tag_name));
430
431 /*
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.
436 */
437 const auto previous_bypass_identifier_escape = _bypass_identifier_escape;
438 _bypass_identifier_escape = true;
439 for (const auto& field : type._choices) {
440 _description.resize(_description.size() + _indentation_level, '\t');
441 field->accept(*this);
442 _description += fmt::format("\n", field->name);
443 }
444
445 _bypass_identifier_escape = previous_bypass_identifier_escape;
446
447 _indentation_level--;
448 _description.resize(_description.size() + _indentation_level, '\t');
449 _description += "}";
450 }
451
452 lst::type::cuptr create_character_type(enum lst::string_type::encoding encoding)
453 {
454 _current_integer_encoding_override = encoding;
455 return lttng::make_unique<lst::integer_type>(8, _trace_abi.byte_order, 8,
456 lst::integer_type::signedness::UNSIGNED,
457 lst::integer_type::base::DECIMAL);
458 }
459
460 virtual void visit(const lst::static_length_string_type& type) override final
461 {
462 /*
463 * TSDL expresses static-length strings as arrays of 8-bit integer with
464 * an encoding specified.
465 */
466 const auto char_array = lttng::make_unique<lst::static_length_array_type>(
467 type.alignment, create_character_type(type.encoding_), type.length);
468
469 visit(*char_array);
470 }
471
472 virtual void visit(const lst::dynamic_length_string_type& type) override final
473 {
474 /*
475 * TSDL expresses dynamic-length strings as arrays of 8-bit integer with
476 * an encoding specified.
477 */
478 const auto char_sequence = lttng::make_unique<lst::dynamic_length_array_type>(
479 type.alignment, create_character_type(type.encoding_),
480 type.length_field_name);
481
482 visit(*char_sequence);
483 }
484
485 std::stack<std::string> _current_field_name;
486 /*
487 * Encoding to specify for the next serialized integer type.
488 * Since the integer_type does not allow an encoding to be specified (it is a TSDL-specific
489 * concept), this attribute is used when expressing static or dynamic length strings as
490 * arrays/sequences of bytes with an encoding.
491 */
492 nonstd::optional<enum lst::string_type::encoding> _current_integer_encoding_override;
493
494 unsigned int _indentation_level;
495 const lst::abi& _trace_abi;
496
497 std::queue<std::string> _type_suffixes;
498
499 /* Description in TSDL format. */
500 std::string _description;
501
502 bool _bypass_identifier_escape;
503 const char *_default_clock_class_name;
504 };
505 } /* namespace */
506
507 tsdl::trace_class_visitor::trace_class_visitor(const lst::abi& trace_abi,
508 tsdl::append_metadata_fragment_function append_metadata_fragment) :
509 _trace_abi{trace_abi},
510 _append_metadata_fragment(append_metadata_fragment)
511 {
512 }
513
514 void tsdl::trace_class_visitor::append_metadata_fragment(const std::string& fragment) const
515 {
516 _append_metadata_fragment(fragment);
517 }
518
519 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::trace_class& trace_class)
520 {
521 tsdl_field_visitor packet_header_visitor(trace_class.abi, 1);
522
523 trace_class.get_packet_header()->accept(packet_header_visitor);
524
525 /* Declare type aliases, trace class, and packet header. */
526 auto trace_class_tsdl = fmt::format(
527 "/* CTF {ctf_major}.{ctf_minor} */\n\n"
528 "trace {{\n"
529 " major = {ctf_major};\n"
530 " minor = {ctf_minor};\n"
531 " uuid = \"{uuid}\";\n"
532 " byte_order = {byte_order};\n"
533 " packet.header := {packet_header_layout};\n"
534 "}};\n\n",
535 fmt::arg("ctf_major", ctf_spec_major),
536 fmt::arg("ctf_minor", ctf_spec_minor),
537 fmt::arg("uint8_t_alignment", trace_class.abi.uint8_t_alignment),
538 fmt::arg("uint16_t_alignment", trace_class.abi.uint16_t_alignment),
539 fmt::arg("uint32_t_alignment", trace_class.abi.uint32_t_alignment),
540 fmt::arg("uint64_t_alignment", trace_class.abi.uint64_t_alignment),
541 fmt::arg("long_alignment", trace_class.abi.long_alignment),
542 fmt::arg("long_size", trace_class.abi.long_alignment),
543 fmt::arg("bits_per_long", trace_class.abi.bits_per_long),
544 fmt::arg("uuid", lttng::utils::uuid_to_str(trace_class.uuid)),
545 fmt::arg("byte_order",
546 trace_class.abi.byte_order == lst::byte_order::BIG_ENDIAN_ ? "be" : "le"),
547 fmt::arg("packet_header_layout", packet_header_visitor.get_description()));
548
549 /* Declare trace scope and type aliases. */
550 append_metadata_fragment(std::move(trace_class_tsdl));
551 }
552
553 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::clock_class& clock_class)
554 {
555 auto uuid_str = clock_class.uuid ?
556 fmt::format(" uuid = \"{}\";\n",
557 lttng::utils::uuid_to_str(*clock_class.uuid)) :
558 "";
559
560 /* Assumes a single clock that maps to specific stream class fields/roles. */
561 auto clock_class_str = fmt::format(
562 "clock {{\n"
563 " name = \"{name}\";\n"
564 /* Optional uuid. */
565 "{uuid}"
566 " description = \"{description}\";\n"
567 " freq = {frequency};\n"
568 " offset = {offset};\n"
569 "}};\n"
570 "\n",
571 fmt::arg("name", clock_class.name),
572 fmt::arg("uuid", uuid_str),
573 fmt::arg("description", clock_class.description),
574 fmt::arg("frequency", clock_class.frequency),
575 fmt::arg("offset", clock_class.offset));
576
577 append_metadata_fragment(std::move(clock_class_str));
578 }
579
580 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::stream_class& stream_class)
581 {
582 auto stream_class_str = fmt::format("stream {{\n"
583 " id = {};\n", stream_class.id);
584
585 const auto *event_header = stream_class.get_event_header();
586 if (event_header) {
587 auto event_header_visitor = tsdl_field_visitor(
588 _trace_abi, 1, stream_class.default_clock_class_name);
589
590 event_header->accept(event_header_visitor);
591 stream_class_str += fmt::format(" event.header := {};\n",
592 event_header_visitor.get_description());
593 }
594
595 const auto *packet_context = stream_class.get_packet_context();
596 if (packet_context) {
597 auto packet_context_visitor = tsdl_field_visitor(
598 _trace_abi, 1, stream_class.default_clock_class_name);
599
600 packet_context->accept(packet_context_visitor);
601 stream_class_str += fmt::format(" packet.context := {};\n",
602 packet_context_visitor.get_description());
603 }
604
605 const auto *event_context = stream_class.get_event_context();
606 if (event_context) {
607 auto event_context_visitor = tsdl_field_visitor(_trace_abi, 1);
608
609 event_context->accept(event_context_visitor);
610 stream_class_str += fmt::format(" event.context := {};\n",
611 event_context_visitor.get_description());
612 }
613
614 stream_class_str += "};\n\n";
615
616 append_metadata_fragment(stream_class_str);
617 }
618
619 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::event_class& event_class)
620 {
621 auto event_class_str = fmt::format("event {{\n"
622 " name = \"{name}\";\n"
623 " id = {id};\n"
624 " stream_id = {stream_class_id};\n"
625 " loglevel = {log_level};\n",
626 fmt::arg("name", event_class.name),
627 fmt::arg("id", event_class.id),
628 fmt::arg("stream_class_id", event_class.stream_class_id),
629 fmt::arg("log_level", event_class.log_level));
630
631 if (event_class.model_emf_uri) {
632 event_class_str += fmt::format(
633 " model.emf.uri = \"{}\";\n", *event_class.model_emf_uri);
634 }
635
636 auto payload_visitor = tsdl_field_visitor(_trace_abi, 1);
637
638 event_class.payload->accept(static_cast<lst::type_visitor&>(payload_visitor));
639
640 event_class_str += fmt::format(
641 " fields := {};\n}};\n\n", payload_visitor.get_description());
642
643 append_metadata_fragment(event_class_str);
644 }
645
646 void tsdl::trace_class_visitor::environment_begin()
647 {
648 _environment += "env {\n";
649 }
650
651 void tsdl::trace_class_visitor::visit(
652 const lttng::sessiond::trace::environment_field<int64_t>& field)
653 {
654 _environment += fmt::format(" {} = {};\n", field.name, field.value);
655 }
656
657 void tsdl::trace_class_visitor::visit(
658 const lttng::sessiond::trace::environment_field<const char *>& field)
659 {
660 _environment += fmt::format(
661 " {} = \"{}\";\n", field.name, escape_tsdl_env_string_value(field.value));
662 }
663
664 void tsdl::trace_class_visitor::environment_end()
665 {
666 _environment += "};\n\n";
667 append_metadata_fragment(_environment);
668 _environment.clear();
669 }
This page took 0.045443 seconds and 5 git commands to generate.