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