cpp-common/bt2c/fmt.hpp: use `wise_enum::string_type` in `EnableIfIsWiseEnum` definition
[babeltrace.git] / src / plugins / ctf / fs-sink / fs-sink-trace.cpp
1 /*
2 * SPDX-License-Identifier: MIT
3 *
4 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
5 */
6
7 #include <glib.h>
8 #include <stdio.h>
9
10 #include <babeltrace2/babeltrace.h>
11
12 #include "common/assert.h"
13
14 #include "fs-sink-ctf-meta.hpp"
15 #include "fs-sink-stream.hpp"
16 #include "fs-sink-trace.hpp"
17 #include "fs-sink.hpp"
18 #include "translate-ctf-ir-to-tsdl.hpp"
19 #include "translate-trace-ir-to-ctf-ir.hpp"
20
21 /*
22 * Sanitizes `path` so as to:
23 *
24 * * Replace `.` subdirectories with `_`.
25 * * Replace `..` subdirectories with `__`.
26 * * Remove trailing slashes.
27 */
28 static GString *sanitize_trace_path(const char *path)
29 {
30 GString *san_path = g_string_new(NULL);
31 const char *ch = path;
32 bool dir_start = true;
33
34 BT_ASSERT(san_path);
35 BT_ASSERT(path);
36
37 while (*ch != '\0') {
38 switch (*ch) {
39 case '/':
40 /* Start of directory */
41 dir_start = true;
42 g_string_append_c(san_path, *ch);
43 ch++;
44 continue;
45 case '.':
46 if (dir_start) {
47 switch (ch[1]) {
48 case '\0':
49 case '/':
50 /* `.` -> `_` */
51 g_string_append_c(san_path, '_');
52 ch++;
53 continue;
54 case '.':
55 switch (ch[2]) {
56 case '\0':
57 case '/':
58 /* `..` -> `__` */
59 g_string_append(san_path, "__");
60 ch += 2;
61 continue;
62 default:
63 break;
64 }
65 default:
66 break;
67 }
68 }
69 default:
70 break;
71 }
72
73 /* Not a special character */
74 g_string_append_c(san_path, *ch);
75 ch++;
76 dir_start = false;
77 }
78
79 /* Remove trailing slashes */
80 while (san_path->len > 0 && san_path->str[san_path->len - 1] == '/') {
81 /* Remove trailing slash */
82 g_string_set_size(san_path, san_path->len - 1);
83 }
84
85 if (san_path->len == 0) {
86 /* Looks like there's nothing left: just use `trace` */
87 g_string_assign(san_path, "trace");
88 }
89
90 return san_path;
91 }
92
93 /*
94 * Find a path based on `path` that doesn't exist yet. First, try `path`
95 * itself, then try with incrementing suffixes.
96 */
97
98 static GString *make_unique_trace_path(const char *path)
99 {
100 GString *unique_path;
101 unsigned int suffix = 0;
102
103 unique_path = g_string_new(path);
104
105 while (g_file_test(unique_path->str, G_FILE_TEST_EXISTS)) {
106 g_string_printf(unique_path, "%s-%u", path, suffix);
107 suffix++;
108 }
109
110 return unique_path;
111 }
112
113 /*
114 * Disable `deprecated-declarations` warnings for
115 * lttng_validate_datetime() because we're using `GTimeVal` and
116 * g_time_val_from_iso8601() which are deprecated since GLib 2.56
117 * (Babeltrace supports older versions too).
118 */
119 #pragma GCC diagnostic push
120 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
121
122 /*
123 * Validate that the input string `datetime` is an ISO8601-compliant string (the
124 * format used by LTTng in the metadata).
125 */
126
127 static int lttng_validate_datetime(const struct fs_sink_trace *trace, const char *datetime)
128 {
129 GTimeVal tv;
130 int ret = -1;
131
132 /*
133 * We are using g_time_val_from_iso8601, as the safer/more modern
134 * alternative, g_date_time_new_from_iso8601, is only available in
135 * glib >= 2.56, and this is sufficient for our use case of validating
136 * the format.
137 */
138 if (!g_time_val_from_iso8601(datetime, &tv)) {
139 BT_CPPLOGI_SPEC(trace->logger, "Couldn't parse datetime as ISO 8601: date=\"{}\"",
140 datetime);
141 goto end;
142 }
143
144 ret = 0;
145
146 end:
147 return ret;
148 }
149
150 #pragma GCC diagnostic pop
151
152 static int append_lttng_trace_path_ust_uid(const struct fs_sink_trace *trace, GString *path,
153 const bt_trace *tc)
154 {
155 const bt_value *v;
156 int ret;
157
158 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "tracer_buffering_id");
159 if (!v || !bt_value_is_signed_integer(v)) {
160 BT_CPPLOGI_STR_SPEC(trace->logger,
161 "Couldn't get environment value: name=\"tracer_buffering_id\"");
162 goto error;
163 }
164
165 g_string_append_printf(path, G_DIR_SEPARATOR_S "%" PRId64, bt_value_integer_signed_get(v));
166
167 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "architecture_bit_width");
168 if (!v || !bt_value_is_signed_integer(v)) {
169 BT_CPPLOGI_STR_SPEC(trace->logger,
170 "Couldn't get environment value: name=\"architecture_bit_width\"");
171 goto error;
172 }
173
174 g_string_append_printf(path, G_DIR_SEPARATOR_S "%" PRIu64 "-bit",
175 bt_value_integer_signed_get(v));
176
177 ret = 0;
178 goto end;
179
180 error:
181 ret = -1;
182
183 end:
184 return ret;
185 }
186
187 static int append_lttng_trace_path_ust_pid(const struct fs_sink_trace *trace, GString *path,
188 const bt_trace *tc)
189 {
190 const bt_value *v;
191 const char *datetime;
192 int ret;
193
194 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "procname");
195 if (!v || !bt_value_is_string(v)) {
196 BT_CPPLOGI_STR_SPEC(trace->logger, "Couldn't get environment value: name=\"procname\"");
197 goto error;
198 }
199
200 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", bt_value_string_get(v));
201
202 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "vpid");
203 if (!v || !bt_value_is_signed_integer(v)) {
204 BT_CPPLOGI_STR_SPEC(trace->logger, "Couldn't get environment value: name=\"vpid\"");
205 goto error;
206 }
207
208 g_string_append_printf(path, "-%" PRId64, bt_value_integer_signed_get(v));
209
210 v = bt_trace_borrow_environment_entry_value_by_name_const(tc, "vpid_datetime");
211 if (!v || !bt_value_is_string(v)) {
212 BT_CPPLOGI_STR_SPEC(trace->logger,
213 "Couldn't get environment value: name=\"vpid_datetime\"");
214 goto error;
215 }
216
217 datetime = bt_value_string_get(v);
218
219 if (lttng_validate_datetime(trace, datetime)) {
220 goto error;
221 }
222
223 g_string_append_printf(path, "-%s", datetime);
224
225 ret = 0;
226 goto end;
227
228 error:
229 ret = -1;
230
231 end:
232 return ret;
233 }
234
235 /*
236 * Try to build a trace path based on environment values put in the trace
237 * environment by the LTTng tracer, starting with version 2.11.
238 */
239 static GString *make_lttng_trace_path_rel(const struct fs_sink_trace *trace)
240 {
241 const bt_value *v;
242 const char *tracer_name, *domain, *datetime;
243 int64_t tracer_major, tracer_minor;
244 GString *path;
245
246 path = g_string_new(NULL);
247 if (!path) {
248 goto error;
249 }
250
251 v = bt_trace_borrow_environment_entry_value_by_name_const(trace->ir_trace, "tracer_name");
252 if (!v || !bt_value_is_string(v)) {
253 BT_CPPLOGI_STR_SPEC(trace->logger, "Couldn't get environment value: name=\"tracer_name\"");
254 goto error;
255 }
256
257 tracer_name = bt_value_string_get(v);
258
259 if (!g_str_equal(tracer_name, "lttng-ust") && !g_str_equal(tracer_name, "lttng-modules")) {
260 BT_CPPLOGI_SPEC(trace->logger, "Unrecognized tracer name: name=\"{}\"", tracer_name);
261 goto error;
262 }
263
264 v = bt_trace_borrow_environment_entry_value_by_name_const(trace->ir_trace, "tracer_major");
265 if (!v || !bt_value_is_signed_integer(v)) {
266 BT_CPPLOGI_STR_SPEC(trace->logger, "Couldn't get environment value: name=\"tracer_major\"");
267 goto error;
268 }
269
270 tracer_major = bt_value_integer_signed_get(v);
271
272 v = bt_trace_borrow_environment_entry_value_by_name_const(trace->ir_trace, "tracer_minor");
273 if (!v || !bt_value_is_signed_integer(v)) {
274 BT_CPPLOGI_STR_SPEC(trace->logger, "Couldn't get environment value: name=\"tracer_minor\"");
275 goto error;
276 }
277
278 tracer_minor = bt_value_integer_signed_get(v);
279
280 if (!(tracer_major >= 3 || (tracer_major == 2 && tracer_minor >= 11))) {
281 BT_CPPLOGI_SPEC(trace->logger,
282 "Unsupported LTTng version for automatic trace path: major={}, minor={}",
283 tracer_major, tracer_minor);
284 goto error;
285 }
286
287 v = bt_trace_borrow_environment_entry_value_by_name_const(trace->ir_trace, "hostname");
288 if (!v || !bt_value_is_string(v)) {
289 BT_CPPLOGI_STR_SPEC(trace->logger,
290 "Couldn't get environment value: name=\"tracer_hostname\"");
291 goto error;
292 }
293
294 g_string_assign(path, bt_value_string_get(v));
295
296 v = bt_trace_borrow_environment_entry_value_by_name_const(trace->ir_trace, "trace_name");
297 if (!v || !bt_value_is_string(v)) {
298 BT_CPPLOGI_STR_SPEC(trace->logger, "Couldn't get environment value: name=\"trace_name\"");
299 goto error;
300 }
301
302 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", bt_value_string_get(v));
303
304 v = bt_trace_borrow_environment_entry_value_by_name_const(trace->ir_trace,
305 "trace_creation_datetime");
306 if (!v || !bt_value_is_string(v)) {
307 BT_CPPLOGI_STR_SPEC(trace->logger,
308 "Couldn't get environment value: name=\"trace_creation_datetime\"");
309 goto error;
310 }
311
312 datetime = bt_value_string_get(v);
313
314 if (lttng_validate_datetime(trace, datetime)) {
315 goto error;
316 }
317
318 g_string_append_printf(path, "-%s", datetime);
319
320 v = bt_trace_borrow_environment_entry_value_by_name_const(trace->ir_trace, "domain");
321 if (!v || !bt_value_is_string(v)) {
322 BT_CPPLOGI_STR_SPEC(trace->logger, "Couldn't get environment value: name=\"domain\"");
323 goto error;
324 }
325
326 domain = bt_value_string_get(v);
327 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", domain);
328
329 if (g_str_equal(domain, "ust")) {
330 const char *tracer_buffering_scheme;
331
332 v = bt_trace_borrow_environment_entry_value_by_name_const(trace->ir_trace,
333 "tracer_buffering_scheme");
334 if (!v || !bt_value_is_string(v)) {
335 BT_CPPLOGI_STR_SPEC(trace->logger,
336 "Couldn't get environment value: name=\"tracer_buffering_scheme\"");
337 goto error;
338 }
339
340 tracer_buffering_scheme = bt_value_string_get(v);
341 g_string_append_printf(path, G_DIR_SEPARATOR_S "%s", tracer_buffering_scheme);
342
343 if (g_str_equal(tracer_buffering_scheme, "uid")) {
344 if (append_lttng_trace_path_ust_uid(trace, path, trace->ir_trace)) {
345 goto error;
346 }
347 } else if (g_str_equal(tracer_buffering_scheme, "pid")) {
348 if (append_lttng_trace_path_ust_pid(trace, path, trace->ir_trace)) {
349 goto error;
350 }
351 } else {
352 /* Unknown buffering scheme. */
353 BT_CPPLOGI_SPEC(trace->logger,
354 "Unknown buffering scheme: tracer_buffering_scheme=\"{}\"",
355 tracer_buffering_scheme);
356 goto error;
357 }
358 } else if (!g_str_equal(domain, "kernel")) {
359 /* Unknown domain. */
360 BT_CPPLOGI_SPEC(trace->logger, "Unknown domain: domain=\"{}\"", domain);
361 goto error;
362 }
363
364 goto end;
365
366 error:
367 if (path) {
368 g_string_free(path, TRUE);
369 path = NULL;
370 }
371
372 end:
373 return path;
374 }
375
376 /* Build the relative output path for `trace`. */
377
378 static GString *make_trace_path_rel(const struct fs_sink_trace *trace)
379 {
380 GString *path = NULL;
381 const char *trace_name;
382
383 BT_ASSERT(!trace->fs_sink->assume_single_trace);
384
385 /* First, try to build a path using environment fields written by LTTng. */
386 path = make_lttng_trace_path_rel(trace);
387 if (path) {
388 goto end;
389 }
390
391 /* Otherwise, use the trace name, if available. */
392 trace_name = bt_trace_get_name(trace->ir_trace);
393 if (trace_name) {
394 path = g_string_new(trace_name);
395 goto end;
396 }
397
398 /* Otherwise, use "trace". */
399 path = g_string_new("trace");
400
401 end:
402 return path;
403 }
404
405 /*
406 * Compute the trace output path for `trace`, rooted at `output_base_directory`.
407 */
408
409 static GString *make_trace_path(const struct fs_sink_trace *trace,
410 const char *output_base_directory)
411 {
412 GString *rel_path = NULL;
413 GString *rel_path_san = NULL;
414 GString *full_path = NULL;
415 GString *unique_full_path = NULL;
416
417 if (trace->fs_sink->assume_single_trace) {
418 /* Use output directory directly */
419 unique_full_path = g_string_new(output_base_directory);
420 if (!unique_full_path) {
421 goto end;
422 }
423 } else {
424 rel_path = make_trace_path_rel(trace);
425 if (!rel_path) {
426 goto end;
427 }
428
429 rel_path_san = sanitize_trace_path(rel_path->str);
430 if (!rel_path_san) {
431 goto end;
432 }
433
434 full_path = g_string_new(NULL);
435 if (!full_path) {
436 goto end;
437 }
438
439 g_string_printf(full_path, "%s" G_DIR_SEPARATOR_S "%s", output_base_directory,
440 rel_path_san->str);
441
442 unique_full_path = make_unique_trace_path(full_path->str);
443 }
444
445 end:
446 if (rel_path) {
447 g_string_free(rel_path, TRUE);
448 }
449
450 if (rel_path_san) {
451 g_string_free(rel_path_san, TRUE);
452 }
453
454 if (full_path) {
455 g_string_free(full_path, TRUE);
456 }
457
458 return unique_full_path;
459 }
460
461 void fs_sink_trace_destroy(struct fs_sink_trace *trace)
462 {
463 GString *tsdl = NULL;
464 FILE *fh = NULL;
465 size_t len;
466
467 if (!trace) {
468 goto end;
469 }
470
471 if (trace->ir_trace_destruction_listener_id != UINT64_C(-1)) {
472 /*
473 * Remove the destruction listener, otherwise it could
474 * be called in the future, and its private data is this
475 * CTF FS sink trace object which won't exist anymore.
476 */
477 (void) bt_trace_remove_destruction_listener(trace->ir_trace,
478 trace->ir_trace_destruction_listener_id);
479 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
480 }
481
482 if (trace->streams) {
483 g_hash_table_destroy(trace->streams);
484 trace->streams = NULL;
485 }
486
487 tsdl = g_string_new(NULL);
488 BT_ASSERT(tsdl);
489 translate_trace_ctf_ir_to_tsdl(trace->trace, tsdl);
490
491 BT_ASSERT(trace->metadata_path);
492 fh = fopen(trace->metadata_path->str, "wb");
493 if (!fh) {
494 BT_CPPLOGF_ERRNO_SPEC(trace->logger,
495 "In trace destruction listener: "
496 "cannot open metadata file for writing",
497 ": path=\"{}\"", trace->metadata_path->str);
498 bt_common_abort();
499 }
500
501 len = fwrite(tsdl->str, sizeof(*tsdl->str), tsdl->len, fh);
502 if (len != tsdl->len) {
503 BT_CPPLOGF_ERRNO_SPEC(trace->logger,
504 "In trace destruction listener: "
505 "cannot write metadata file",
506 ": path=\"{}\"", trace->metadata_path->str);
507 bt_common_abort();
508 }
509
510 if (!trace->fs_sink->quiet) {
511 printf("Created CTF trace `%s`.\n", trace->path->str);
512 }
513
514 if (trace->path) {
515 g_string_free(trace->path, TRUE);
516 trace->path = NULL;
517 }
518
519 if (fh) {
520 int ret = fclose(fh);
521
522 if (ret != 0) {
523 BT_CPPLOGW_ERRNO_SPEC(trace->logger,
524 "In trace destruction listener: "
525 "cannot close metadata file",
526 ": path=\"{}\"", trace->metadata_path->str);
527 }
528 }
529
530 g_string_free(trace->metadata_path, TRUE);
531 trace->metadata_path = NULL;
532
533 fs_sink_ctf_trace_destroy(trace->trace);
534 trace->trace = NULL;
535 delete trace;
536
537 g_string_free(tsdl, TRUE);
538
539 end:
540 return;
541 }
542
543 static void ir_trace_destruction_listener(const bt_trace *ir_trace, void *data)
544 {
545 struct fs_sink_trace *trace = (fs_sink_trace *) data;
546
547 /*
548 * Prevent bt_trace_remove_destruction_listener() from being
549 * called in fs_sink_trace_destroy(), which is called by
550 * g_hash_table_remove() below.
551 */
552 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
553 g_hash_table_remove(trace->fs_sink->traces, ir_trace);
554 }
555
556 struct fs_sink_trace *fs_sink_trace_create(struct fs_sink_comp *fs_sink, const bt_trace *ir_trace)
557 {
558 int ret;
559 fs_sink_trace *trace = new fs_sink_trace {fs_sink->logger};
560 bt_trace_add_listener_status trace_status;
561
562 trace->fs_sink = fs_sink;
563 trace->ir_trace = ir_trace;
564 trace->ir_trace_destruction_listener_id = UINT64_C(-1);
565 trace->trace = translate_trace_trace_ir_to_ctf_ir(fs_sink, ir_trace);
566 if (!trace->trace) {
567 goto error;
568 }
569
570 trace->path = make_trace_path(trace, fs_sink->output_dir_path->str);
571 BT_ASSERT(trace->path);
572 ret = g_mkdir_with_parents(trace->path->str, 0755);
573 if (ret) {
574 BT_CPPLOGE_ERRNO_SPEC(trace->logger, "Cannot create directories for trace directory",
575 ": path=\"{}\"", trace->path->str);
576 goto error;
577 }
578
579 trace->metadata_path = g_string_new(trace->path->str);
580 BT_ASSERT(trace->metadata_path);
581 g_string_append(trace->metadata_path, "/metadata");
582 trace->streams = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
583 (GDestroyNotify) fs_sink_stream_destroy);
584 BT_ASSERT(trace->streams);
585 trace_status = bt_trace_add_destruction_listener(ir_trace, ir_trace_destruction_listener, trace,
586 &trace->ir_trace_destruction_listener_id);
587 if (trace_status) {
588 goto error;
589 }
590
591 g_hash_table_insert(fs_sink->traces, (gpointer) ir_trace, trace);
592 goto end;
593
594 error:
595 fs_sink_trace_destroy(trace);
596 trace = NULL;
597
598 end:
599 return trace;
600 }
This page took 0.044845 seconds and 4 git commands to generate.