2 * SPDX-License-Identifier: MIT
4 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
10 #include <babeltrace2/babeltrace.h>
12 #include "common/assert.h"
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"
22 * Sanitizes `path` so as to:
24 * * Replace `.` subdirectories with `_`.
25 * * Replace `..` subdirectories with `__`.
26 * * Remove trailing slashes.
28 static GString
*sanitize_trace_path(const char *path
)
30 GString
*san_path
= g_string_new(NULL
);
31 const char *ch
= path
;
32 bool dir_start
= true;
40 /* Start of directory */
42 g_string_append_c(san_path
, *ch
);
51 g_string_append_c(san_path
, '_');
59 g_string_append(san_path
, "__");
73 /* Not a special character */
74 g_string_append_c(san_path
, *ch
);
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);
85 if (san_path
->len
== 0) {
86 /* Looks like there's nothing left: just use `trace` */
87 g_string_assign(san_path
, "trace");
94 * Find a path based on `path` that doesn't exist yet. First, try `path`
95 * itself, then try with incrementing suffixes.
98 static GString
*make_unique_trace_path(const char *path
)
100 GString
*unique_path
;
101 unsigned int suffix
= 0;
103 unique_path
= g_string_new(path
);
105 while (g_file_test(unique_path
->str
, G_FILE_TEST_EXISTS
)) {
106 g_string_printf(unique_path
, "%s-%u", path
, suffix
);
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).
119 #pragma GCC diagnostic push
120 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
123 * Validate that the input string `datetime` is an ISO8601-compliant string (the
124 * format used by LTTng in the metadata).
127 static int lttng_validate_datetime(const struct fs_sink_trace
*trace
, const char *datetime
)
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
138 if (!g_time_val_from_iso8601(datetime
, &tv
)) {
139 BT_CPPLOGI_SPEC(trace
->logger
, "Couldn't parse datetime as ISO 8601: date=\"{}\"",
150 #pragma GCC diagnostic pop
152 static int append_lttng_trace_path_ust_uid(const struct fs_sink_trace
*trace
, GString
*path
,
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\"");
165 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%" PRId64
, bt_value_integer_signed_get(v
));
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\"");
174 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%" PRIu64
"-bit",
175 bt_value_integer_signed_get(v
));
187 static int append_lttng_trace_path_ust_pid(const struct fs_sink_trace
*trace
, GString
*path
,
191 const char *datetime
;
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\"");
200 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", bt_value_string_get(v
));
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\"");
208 g_string_append_printf(path
, "-%" PRId64
, bt_value_integer_signed_get(v
));
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\"");
217 datetime
= bt_value_string_get(v
);
219 if (lttng_validate_datetime(trace
, datetime
)) {
223 g_string_append_printf(path
, "-%s", datetime
);
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.
239 static GString
*make_lttng_trace_path_rel(const struct fs_sink_trace
*trace
)
242 const char *tracer_name
, *domain
, *datetime
;
243 int64_t tracer_major
, tracer_minor
;
246 path
= g_string_new(NULL
);
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\"");
257 tracer_name
= bt_value_string_get(v
);
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
);
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\"");
270 tracer_major
= bt_value_integer_signed_get(v
);
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\"");
278 tracer_minor
= bt_value_integer_signed_get(v
);
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
);
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\"");
294 g_string_assign(path
, bt_value_string_get(v
));
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\"");
302 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", bt_value_string_get(v
));
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\"");
312 datetime
= bt_value_string_get(v
);
314 if (lttng_validate_datetime(trace
, datetime
)) {
318 g_string_append_printf(path
, "-%s", datetime
);
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\"");
326 domain
= bt_value_string_get(v
);
327 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", domain
);
329 if (g_str_equal(domain
, "ust")) {
330 const char *tracer_buffering_scheme
;
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\"");
340 tracer_buffering_scheme
= bt_value_string_get(v
);
341 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", tracer_buffering_scheme
);
343 if (g_str_equal(tracer_buffering_scheme
, "uid")) {
344 if (append_lttng_trace_path_ust_uid(trace
, path
, trace
->ir_trace
)) {
347 } else if (g_str_equal(tracer_buffering_scheme
, "pid")) {
348 if (append_lttng_trace_path_ust_pid(trace
, path
, trace
->ir_trace
)) {
352 /* Unknown buffering scheme. */
353 BT_CPPLOGI_SPEC(trace
->logger
,
354 "Unknown buffering scheme: tracer_buffering_scheme=\"{}\"",
355 tracer_buffering_scheme
);
358 } else if (!g_str_equal(domain
, "kernel")) {
359 /* Unknown domain. */
360 BT_CPPLOGI_SPEC(trace
->logger
, "Unknown domain: domain=\"{}\"", domain
);
368 g_string_free(path
, TRUE
);
376 /* Build the relative output path for `trace`. */
378 static GString
*make_trace_path_rel(const struct fs_sink_trace
*trace
)
380 GString
*path
= NULL
;
381 const char *trace_name
;
383 BT_ASSERT(!trace
->fs_sink
->assume_single_trace
);
385 /* First, try to build a path using environment fields written by LTTng. */
386 path
= make_lttng_trace_path_rel(trace
);
391 /* Otherwise, use the trace name, if available. */
392 trace_name
= bt_trace_get_name(trace
->ir_trace
);
394 path
= g_string_new(trace_name
);
398 /* Otherwise, use "trace". */
399 path
= g_string_new("trace");
406 * Compute the trace output path for `trace`, rooted at `output_base_directory`.
409 static GString
*make_trace_path(const struct fs_sink_trace
*trace
,
410 const char *output_base_directory
)
412 GString
*rel_path
= NULL
;
413 GString
*rel_path_san
= NULL
;
414 GString
*full_path
= NULL
;
415 GString
*unique_full_path
= NULL
;
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
) {
424 rel_path
= make_trace_path_rel(trace
);
429 rel_path_san
= sanitize_trace_path(rel_path
->str
);
434 full_path
= g_string_new(NULL
);
439 g_string_printf(full_path
, "%s" G_DIR_SEPARATOR_S
"%s", output_base_directory
,
442 unique_full_path
= make_unique_trace_path(full_path
->str
);
447 g_string_free(rel_path
, TRUE
);
451 g_string_free(rel_path_san
, TRUE
);
455 g_string_free(full_path
, TRUE
);
458 return unique_full_path
;
461 void fs_sink_trace_destroy(struct fs_sink_trace
*trace
)
463 GString
*tsdl
= NULL
;
471 if (trace
->ir_trace_destruction_listener_id
!= UINT64_C(-1)) {
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.
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);
482 if (trace
->streams
) {
483 g_hash_table_destroy(trace
->streams
);
484 trace
->streams
= NULL
;
487 tsdl
= g_string_new(NULL
);
489 translate_trace_ctf_ir_to_tsdl(trace
->trace
, tsdl
);
491 BT_ASSERT(trace
->metadata_path
);
492 fh
= fopen(trace
->metadata_path
->str
, "wb");
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
);
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
);
510 if (!trace
->fs_sink
->quiet
) {
511 printf("Created CTF trace `%s`.\n", trace
->path
->str
);
515 g_string_free(trace
->path
, TRUE
);
520 int ret
= fclose(fh
);
523 BT_CPPLOGW_ERRNO_SPEC(trace
->logger
,
524 "In trace destruction listener: "
525 "cannot close metadata file",
526 ": path=\"{}\"", trace
->metadata_path
->str
);
530 g_string_free(trace
->metadata_path
, TRUE
);
531 trace
->metadata_path
= NULL
;
533 fs_sink_ctf_trace_destroy(trace
->trace
);
537 g_string_free(tsdl
, TRUE
);
543 static void ir_trace_destruction_listener(const bt_trace
*ir_trace
, void *data
)
545 struct fs_sink_trace
*trace
= (fs_sink_trace
*) data
;
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.
552 trace
->ir_trace_destruction_listener_id
= UINT64_C(-1);
553 g_hash_table_remove(trace
->fs_sink
->traces
, ir_trace
);
556 struct fs_sink_trace
*fs_sink_trace_create(struct fs_sink_comp
*fs_sink
, const bt_trace
*ir_trace
)
559 fs_sink_trace
*trace
= new fs_sink_trace
{fs_sink
->logger
};
560 bt_trace_add_listener_status trace_status
;
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
);
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);
574 BT_CPPLOGE_ERRNO_SPEC(trace
->logger
, "Cannot create directories for trace directory",
575 ": path=\"{}\"", trace
->path
->str
);
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
);
591 g_hash_table_insert(fs_sink
->traces
, (gpointer
) ir_trace
, trace
);
595 fs_sink_trace_destroy(trace
);