Commit | Line | Data |
---|---|---|
da9dd521 JG |
1 | /* |
2 | * Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com> | |
3 | * Copyright (C) 2022 Simon Marchi <simon.marchi@efficios.com> | |
4 | * | |
5 | * SPDX-License-Identifier: GPL-2.0-only | |
6 | * | |
7 | */ | |
8 | ||
9 | #include "ctf2-trace-class-visitor.hpp" | |
10 | #include "clock-class.hpp" | |
11 | ||
12 | #include <common/exception.hpp> | |
13 | #include <common/format.hpp> | |
14 | ||
15 | #include <vendor/nlohmann/json.hpp> | |
16 | ||
17 | #include <algorithm> | |
18 | ||
19 | namespace lsc = lttng::sessiond::ctf2; | |
20 | namespace lst = lttng::sessiond::trace; | |
21 | ||
22 | namespace json = nlohmann; | |
23 | ||
24 | namespace { | |
25 | const unsigned int spaces_per_indent = 2; | |
26 | const std::string record_separator = "\x1e"; | |
27 | ||
28 | json::json make_json_fragment(const char *type) | |
29 | { | |
30 | return {{"type", type}}; | |
31 | } | |
32 | ||
33 | json::json to_json(const lst::field_location &location) | |
34 | { | |
35 | json::json location_array; | |
36 | ||
37 | switch (location.root_) { | |
38 | case lst::field_location::root::PACKET_HEADER: | |
39 | location_array.push_back("packet-header"); | |
40 | break; | |
41 | case lst::field_location::root::PACKET_CONTEXT: | |
42 | location_array.push_back("packet-context"); | |
43 | break; | |
44 | case lst::field_location::root::EVENT_RECORD_HEADER: | |
45 | location_array.push_back("event-record-header"); | |
46 | break; | |
47 | case lst::field_location::root::EVENT_RECORD_COMMON_CONTEXT: | |
48 | location_array.push_back("event-record-common-context"); | |
49 | break; | |
50 | case lst::field_location::root::EVENT_RECORD_SPECIFIC_CONTEXT: | |
51 | location_array.push_back("event-record-specific-context"); | |
52 | break; | |
53 | case lst::field_location::root::EVENT_RECORD_PAYLOAD: | |
54 | location_array.push_back("event-record-payload"); | |
55 | break; | |
56 | } | |
57 | ||
58 | std::copy(location.elements_.begin(), location.elements_.end(), | |
59 | std::back_inserter(location_array)); | |
60 | return location_array; | |
61 | } | |
62 | ||
63 | const char *get_role_name(lst::integer_type::role role) | |
64 | { | |
65 | switch (role) { | |
66 | case lst::integer_type::role::DEFAULT_CLOCK_TIMESTAMP: | |
67 | return "default-clock-timestamp"; | |
68 | case lst::integer_type::role::DATA_STREAM_CLASS_ID: | |
69 | return "data-stream-class-id"; | |
70 | case lst::integer_type::role::DATA_STREAM_ID: | |
71 | return "data-stream-id"; | |
72 | case lst::integer_type::role::PACKET_MAGIC_NUMBER: | |
73 | return "packet-magic-number"; | |
74 | case lst::integer_type::role::DISCARDED_EVENT_RECORD_COUNTER_SNAPSHOT: | |
75 | return "discarded-event-record-counter-snapshot"; | |
76 | case lst::integer_type::role::PACKET_CONTENT_LENGTH: | |
77 | return "packet-content-length"; | |
78 | case lst::integer_type::role::PACKET_END_DEFAULT_CLOCK_TIMESTAMP: | |
79 | return "packet-end-default-clock-timestamp"; | |
80 | case lst::integer_type::role::PACKET_SEQUENCE_NUMBER: | |
81 | return "packet-sequence-number"; | |
82 | case lst::integer_type::role::PACKET_TOTAL_LENGTH: | |
83 | return "packet-total-length"; | |
84 | case lst::integer_type::role::EVENT_RECORD_CLASS_ID: | |
85 | return "event-record-class-id"; | |
86 | default: | |
87 | abort(); | |
88 | } | |
89 | } | |
90 | ||
91 | const char *get_role_name(lst::static_length_blob_type::role role) | |
92 | { | |
93 | switch (role) { | |
94 | case lst::static_length_blob_type::role::METADATA_STREAM_UUID: | |
95 | return "metadata-stream-uuid"; | |
96 | default: | |
97 | abort(); | |
98 | } | |
99 | } | |
100 | ||
101 | namespace ctf2 { | |
102 | class trace_environment_visitor : public lst::trace_class_environment_visitor { | |
103 | public: | |
104 | trace_environment_visitor() | |
105 | { | |
106 | } | |
107 | ||
108 | virtual void visit(const lst::environment_field<int64_t>& field) override | |
109 | { | |
110 | _visit(field); | |
111 | } | |
112 | ||
113 | virtual void visit(const lst::environment_field<const char *>& field) override | |
114 | { | |
115 | _visit(field); | |
116 | } | |
117 | ||
118 | /* Only call once. */ | |
c22ded12 | 119 | json::json move_fragment() |
da9dd521 JG |
120 | { |
121 | return std::move(_environment); | |
122 | } | |
123 | ||
124 | private: | |
125 | template <class FieldType> | |
126 | void _visit(const FieldType& field) | |
127 | { | |
128 | _environment[field.name] = field.value; | |
129 | } | |
130 | ||
131 | json::json _environment; | |
132 | }; | |
133 | ||
134 | class field_visitor : public lttng::sessiond::trace::field_visitor, | |
135 | public lttng::sessiond::trace::type_visitor { | |
136 | public: | |
137 | field_visitor() | |
138 | { | |
139 | } | |
140 | ||
141 | /* Only call once. */ | |
c22ded12 | 142 | json::json move_fragment() |
da9dd521 JG |
143 | { |
144 | return std::move(_fragment); | |
145 | } | |
146 | ||
147 | private: | |
148 | virtual void visit(const lst::field& field) override final | |
149 | { | |
150 | field_visitor field_type_visitor; | |
151 | field.get_type().accept(field_type_visitor); | |
152 | ||
153 | _fragment["name"] = field.name; | |
c22ded12 | 154 | _fragment["field-class"] = field_type_visitor.move_fragment(); |
da9dd521 JG |
155 | } |
156 | ||
157 | virtual void visit(const lst::integer_type& type) override final | |
158 | { | |
159 | _fragment["type"] = type.signedness_ == lst::integer_type::signedness::SIGNED ? | |
160 | "fixed-length-signed-integer" : | |
161 | "fixed-length-unsigned-integer"; | |
162 | _fragment["length"] = type.size; | |
163 | _fragment["byte-order"] = type.byte_order == lst::byte_order::BIG_ENDIAN_ ? | |
164 | "big-endian" : | |
165 | "little-endian"; | |
166 | _fragment["alignment"] = type.alignment; | |
167 | _fragment["preferred-display-base"] = (unsigned int) type.base_; | |
168 | ||
169 | if (type.roles_.size() > 0) { | |
170 | json::json role_array = json::json::array(); | |
171 | ||
172 | for (const auto role : type.roles_) { | |
173 | role_array.push_back(get_role_name(role)); | |
174 | } | |
175 | ||
176 | _fragment["roles"] = std::move(role_array); | |
177 | } | |
178 | } | |
179 | ||
180 | virtual void visit(const lst::floating_point_type& type) override final | |
181 | { | |
182 | _fragment["type"] = "fixed-length-floating-point-number"; | |
183 | _fragment["length"] = type.exponent_digits + type.mantissa_digits; | |
184 | _fragment["byte-order"] = type.byte_order == lst::byte_order::BIG_ENDIAN_ ? | |
185 | "big-endian" : | |
186 | "little-endian"; | |
187 | _fragment["alignment"] = type.alignment; | |
188 | } | |
189 | ||
190 | template <class EnumerationType> | |
191 | void visit_enumeration(const EnumerationType& type) | |
192 | { | |
193 | _fragment["type"] = std::is_signed<typename EnumerationType::mapping::range_t::range_integer_t>::value ? | |
194 | "fixed-length-signed-enumeration" : | |
195 | "fixed-length-unsigned-enumeration"; | |
196 | _fragment["length"] = type.size; | |
197 | _fragment["byte-order"] = type.byte_order == lst::byte_order::BIG_ENDIAN_ ? | |
198 | "big-endian" : | |
199 | "little-endian"; | |
200 | _fragment["alignment"] = type.alignment; | |
201 | _fragment["preferred-display-base"] = (unsigned int) type.base_; | |
202 | ||
203 | if (type.roles_.size() > 0) { | |
204 | if (std::is_signed<typename EnumerationType::mapping::range_t:: | |
205 | range_integer_t>::value) { | |
206 | LTTNG_THROW_ERROR(fmt::format( | |
207 | "Failed to serialize {}: unexpected role", | |
208 | _fragment["type"])); | |
209 | } | |
210 | ||
211 | auto role_array = json::json::array(); | |
212 | ||
213 | for (const auto role : type.roles_) { | |
214 | role_array.push_back(get_role_name(role)); | |
215 | } | |
216 | ||
217 | _fragment["roles"] = std::move(role_array); | |
218 | } | |
219 | ||
220 | if (type.mappings_->size() < 1) { | |
221 | LTTNG_THROW_ERROR(fmt::format( | |
222 | "Failed to serialize {}: enumeration must have at least one mapping", | |
223 | _fragment["type"])); | |
224 | } | |
225 | ||
226 | json::json mappings_value; | |
227 | for (const auto &mapping : *type.mappings_) { | |
228 | mappings_value[mapping.name] = {{mapping.range.begin, mapping.range.end}}; | |
229 | } | |
230 | ||
231 | _fragment["mappings"] = std::move(mappings_value); | |
232 | } | |
233 | ||
234 | virtual void visit(const lst::signed_enumeration_type& type) override final | |
235 | { | |
236 | visit_enumeration(type); | |
237 | } | |
238 | ||
239 | virtual void visit(const lst::unsigned_enumeration_type& type) override final | |
240 | { | |
241 | visit_enumeration(type); | |
242 | } | |
243 | ||
244 | virtual void visit(const lst::static_length_array_type& type) override final | |
245 | { | |
246 | _fragment["type"] = "static-length-array"; | |
247 | ||
248 | ::ctf2::field_visitor element_visitor; | |
249 | type.element_type->accept(element_visitor); | |
c22ded12 | 250 | _fragment["element-field-class"] = element_visitor.move_fragment(); |
da9dd521 JG |
251 | |
252 | if (type.alignment != 0) { | |
253 | _fragment["minimum-alignment"] = type.alignment; | |
254 | } | |
255 | ||
256 | _fragment["length"] = type.length; | |
257 | } | |
258 | ||
259 | virtual void visit(const lst::dynamic_length_array_type& type) override final | |
260 | { | |
261 | _fragment["type"] = "dynamic-length-array"; | |
262 | ||
263 | ::ctf2::field_visitor element_visitor; | |
264 | type.element_type->accept(element_visitor); | |
c22ded12 | 265 | _fragment["element-field-class"] = element_visitor.move_fragment(); |
da9dd521 JG |
266 | |
267 | if (type.alignment != 0) { | |
268 | _fragment["minimum-alignment"] = type.alignment; | |
269 | } | |
270 | ||
271 | _fragment["length-field-location"] = to_json(type.length_field_location); | |
272 | } | |
273 | ||
274 | virtual void visit(const lst::static_length_blob_type& type) override final | |
275 | { | |
276 | _fragment["type"] = "static-length-blob"; | |
277 | _fragment["length"] = type.length_bytes; | |
278 | ||
279 | if (type.roles_.size() > 0) { | |
280 | auto role_array = json::json::array(); | |
281 | ||
282 | for (const auto role : type.roles_) { | |
283 | role_array.push_back(get_role_name(role)); | |
284 | } | |
285 | ||
286 | _fragment["roles"] = std::move(role_array); | |
287 | } | |
288 | } | |
289 | ||
290 | virtual void visit(const lst::dynamic_length_blob_type& type) override final | |
291 | { | |
292 | _fragment["type"] = "dynamic-length-blob"; | |
293 | _fragment["length-field-location"] = to_json(type.length_field_location); | |
294 | } | |
295 | ||
296 | virtual void visit(const lst::null_terminated_string_type& type | |
297 | __attribute__((unused))) override final | |
298 | { | |
299 | _fragment["type"] = "null-terminated-string"; | |
300 | } | |
301 | ||
302 | virtual void visit(const lst::structure_type& type) override final | |
303 | { | |
304 | _fragment["type"] = "structure"; | |
305 | ||
306 | if (type.alignment != 0) { | |
307 | _fragment["minimum-alignment"] = type.alignment; | |
308 | } | |
309 | ||
310 | auto member_classes_value = json::json::array(); | |
311 | for (const auto &field : type.fields_) { | |
312 | ::ctf2::field_visitor member_visitor; | |
313 | json::json member_class; | |
314 | ||
315 | field->accept(member_visitor); | |
c22ded12 | 316 | member_classes_value.emplace_back(member_visitor.move_fragment()); |
da9dd521 JG |
317 | } |
318 | ||
319 | _fragment["member-classes"] = std::move(member_classes_value); | |
320 | } | |
321 | ||
322 | template <class MappingIntegerType> | |
323 | void visit_variant(const lst::variant_type<MappingIntegerType>& type) | |
324 | { | |
325 | _fragment["type"] = "variant"; | |
326 | _fragment["selector-field-location"] = to_json(type.selector_field_location); | |
327 | ||
328 | auto options_value = json::json::array(); | |
329 | for (const auto& option : type.choices_) { | |
330 | ::ctf2::field_visitor option_visitor; | |
331 | json::json member_class; | |
332 | ||
333 | /* TODO missing selector-field-range. */ | |
334 | member_class["selector-field-ranges"] = {{option.first.range.begin, option.first.range.end}}; | |
335 | option.second->accept(option_visitor); | |
c22ded12 | 336 | member_class["field-class"] = option_visitor.move_fragment(); |
da9dd521 JG |
337 | options_value.emplace_back(std::move(member_class)); |
338 | } | |
339 | ||
340 | _fragment["options"] = std::move(options_value); | |
341 | } | |
342 | ||
343 | virtual void visit(const lst::variant_type<int64_t>& type) override final | |
344 | { | |
345 | visit_variant(type); | |
346 | } | |
347 | ||
348 | virtual void visit(const lst::variant_type<uint64_t>& type) override final | |
349 | { | |
350 | visit_variant(type); | |
351 | } | |
352 | ||
353 | virtual void visit(const lst::static_length_string_type& type) override final | |
354 | { | |
355 | _fragment["type"] = "static-length-string"; | |
356 | _fragment["length"] = type.length; | |
357 | } | |
358 | ||
359 | virtual void visit(const lst::dynamic_length_string_type& type) override final | |
360 | { | |
361 | _fragment["type"] = "dynamic-length-string"; | |
362 | _fragment["length-field-location"] = to_json(type.length_field_location); | |
363 | } | |
364 | ||
365 | json::json _fragment; | |
366 | }; | |
367 | } /* namespace ctf2 */ | |
368 | ||
369 | }; /* namespace */ | |
370 | ||
4f2da8b8 | 371 | lsc::trace_class_visitor::trace_class_visitor( |
da9dd521 | 372 | lsc::append_metadata_fragment_function append_metadata_fragment) : |
4f2da8b8 | 373 | _append_metadata_fragment(append_metadata_fragment) |
da9dd521 JG |
374 | { |
375 | } | |
376 | ||
377 | void lsc::trace_class_visitor::visit(const lst::trace_class& trace_class) | |
378 | { | |
379 | { | |
380 | auto preamble_fragment = make_json_fragment("preamble"); | |
381 | ||
382 | preamble_fragment["version"] = 2; | |
383 | preamble_fragment["uuid"] = trace_class.uuid; | |
384 | append_metadata_fragment(preamble_fragment); | |
385 | } | |
386 | ||
387 | auto trace_class_fragment = make_json_fragment("trace-class"); | |
388 | ||
389 | ::ctf2::trace_environment_visitor environment_visitor; | |
390 | trace_class.accept(environment_visitor); | |
c22ded12 | 391 | trace_class_fragment["environment"] = environment_visitor.move_fragment(); |
da9dd521 | 392 | |
4bcf2294 | 393 | const auto packet_header = trace_class.packet_header(); |
da9dd521 JG |
394 | if (packet_header) { |
395 | ::ctf2::field_visitor field_visitor; | |
396 | ||
397 | packet_header->accept(field_visitor); | |
c22ded12 | 398 | trace_class_fragment["packet-header-field-class"] = field_visitor.move_fragment(); |
da9dd521 JG |
399 | } |
400 | ||
401 | append_metadata_fragment(trace_class_fragment); | |
402 | } | |
403 | ||
404 | void lsc::trace_class_visitor::visit(const lst::clock_class& clock_class) | |
405 | { | |
406 | auto clock_class_fragment = make_json_fragment("clock-class"); | |
407 | ||
408 | json::json offset; | |
409 | offset.update({{"seconds", clock_class.offset / clock_class.frequency}, | |
410 | {"cycles", clock_class.offset % clock_class.frequency}}); | |
411 | ||
412 | clock_class_fragment.update({ | |
413 | {"name", clock_class.name}, | |
414 | {"description", clock_class.description}, | |
415 | {"frequency", clock_class.frequency}, | |
416 | {"offset", std::move(offset)}}); | |
417 | ||
418 | if (clock_class.uuid) { | |
419 | clock_class_fragment["uuid"] = *clock_class.uuid; | |
420 | } | |
421 | ||
422 | append_metadata_fragment(clock_class_fragment); | |
423 | } | |
424 | ||
425 | void lsc::trace_class_visitor::visit(const lst::stream_class& stream_class) | |
426 | { | |
427 | auto stream_class_fragment = make_json_fragment("data-stream-class"); | |
428 | ||
429 | stream_class_fragment["id"] = stream_class.id; | |
430 | if (stream_class.default_clock_class_name) { | |
431 | stream_class_fragment["default-clock-class-name"] = | |
432 | *stream_class.default_clock_class_name; | |
433 | } | |
434 | ||
4bcf2294 | 435 | const auto packet_context = stream_class.packet_context(); |
da9dd521 JG |
436 | if (packet_context) { |
437 | ::ctf2::field_visitor visitor; | |
438 | ||
439 | packet_context->accept(visitor); | |
c22ded12 | 440 | stream_class_fragment["packet-context-field-class"] = visitor.move_fragment(); |
da9dd521 JG |
441 | } |
442 | ||
4bcf2294 | 443 | const auto event_header = stream_class.event_header(); |
da9dd521 JG |
444 | if (event_header) { |
445 | ::ctf2::field_visitor visitor; | |
446 | ||
447 | event_header->accept(visitor); | |
448 | stream_class_fragment["event-record-header-field-class"] = | |
c22ded12 | 449 | visitor.move_fragment(); |
da9dd521 JG |
450 | } |
451 | ||
4bcf2294 | 452 | const auto event_context = stream_class.event_context(); |
da9dd521 JG |
453 | if (event_context) { |
454 | ::ctf2::field_visitor visitor; | |
455 | ||
456 | event_context->accept(visitor); | |
457 | stream_class_fragment["event-record-common-context-field-class"] = | |
c22ded12 | 458 | visitor.move_fragment(); |
da9dd521 JG |
459 | } |
460 | ||
461 | append_metadata_fragment(stream_class_fragment); | |
462 | } | |
463 | ||
464 | void lsc::trace_class_visitor::visit(const lst::event_class& event_class) | |
465 | { | |
466 | auto event_class_fragment = make_json_fragment("event-record-class"); | |
467 | ||
468 | event_class_fragment["id"] = event_class.id; | |
469 | event_class_fragment["data-stream-class-id"] = event_class.stream_class_id; | |
470 | event_class_fragment["name"] = event_class.name; | |
471 | ||
472 | if (event_class.payload) { | |
473 | ::ctf2::field_visitor visitor; | |
474 | ||
475 | event_class.payload->accept(visitor); | |
c22ded12 | 476 | event_class_fragment["payload-field-class"] = visitor.move_fragment(); |
da9dd521 JG |
477 | } |
478 | ||
479 | append_metadata_fragment(event_class_fragment); | |
480 | } | |
481 | ||
482 | void lsc::trace_class_visitor::append_metadata_fragment(const nlohmann::json& fragment) const | |
483 | { | |
484 | _append_metadata_fragment(record_separator + fragment.dump(spaces_per_indent).c_str()); | |
485 | } |