2 * SPDX-License-Identifier: MIT
4 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
7 #define BT_COMP_LOG_SELF_COMP (trace->fs_sink->self_comp)
8 #define BT_LOG_OUTPUT_LEVEL (trace->log_level)
9 #define BT_LOG_TAG "PLUGIN/SINK.CTF.FS/TRACE"
10 #include "logging/comp-logging.h"
12 #include <babeltrace2/babeltrace.h>
16 #include "common/assert.h"
17 #include "ctfser/ctfser.h"
19 #include "translate-trace-ir-to-ctf-ir.h"
20 #include "translate-ctf-ir-to-tsdl.h"
22 #include "fs-sink-trace.h"
23 #include "fs-sink-stream.h"
26 * Sanitizes `path` so as to:
28 * * Replace `.` subdirectories with `_`.
29 * * Replace `..` subdirectories with `__`.
30 * * Remove trailing slashes.
33 GString
*sanitize_trace_path(const char *path
)
35 GString
*san_path
= g_string_new(NULL
);
36 const char *ch
= path
;
37 bool dir_start
= true;
45 /* Start of directory */
47 g_string_append_c(san_path
, *ch
);
56 g_string_append_c(san_path
, '_');
64 g_string_append(san_path
, "__");
78 /* Not a special character */
79 g_string_append_c(san_path
, *ch
);
84 /* Remove trailing slashes */
85 while (san_path
->len
> 0 &&
86 san_path
->str
[san_path
->len
- 1] == '/') {
87 /* Remove trailing slash */
88 g_string_set_size(san_path
, san_path
->len
- 1);
91 if (san_path
->len
== 0) {
92 /* Looks like there's nothing left: just use `trace` */
93 g_string_assign(san_path
, "trace");
100 * Find a path based on `path` that doesn't exist yet. First, try `path`
101 * itself, then try with incrementing suffixes.
105 GString
*make_unique_trace_path(const char *path
)
107 GString
*unique_path
;
108 unsigned int suffix
= 0;
110 unique_path
= g_string_new(path
);
112 while (g_file_test(unique_path
->str
, G_FILE_TEST_EXISTS
)) {
113 g_string_printf(unique_path
, "%s-%u", path
, suffix
);
121 * Disable `deprecated-declarations` warnings for
122 * lttng_validate_datetime() because we're using `GTimeVal` and
123 * g_time_val_from_iso8601() which are deprecated since GLib 2.56
124 * (Babeltrace supports older versions too).
126 #pragma GCC diagnostic push
127 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
130 * Validate that the input string `datetime` is an ISO8601-compliant string (the
131 * format used by LTTng in the metadata).
135 int lttng_validate_datetime(const struct fs_sink_trace
*trace
,
136 const char *datetime
)
142 * We are using g_time_val_from_iso8601, as the safer/more modern
143 * alternative, g_date_time_new_from_iso8601, is only available in
144 * glib >= 2.56, and this is sufficient for our use case of validating
147 if (!g_time_val_from_iso8601(datetime
, &tv
)) {
148 BT_COMP_LOGI("Couldn't parse datetime as ISO 8601: date=\"%s\"", datetime
);
158 #pragma GCC diagnostic pop
161 int append_lttng_trace_path_ust_uid(const struct fs_sink_trace
*trace
,
162 GString
*path
, const bt_trace
*tc
)
167 v
= bt_trace_borrow_environment_entry_value_by_name_const(tc
, "tracer_buffering_id");
168 if (!v
|| !bt_value_is_signed_integer(v
)) {
169 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_buffering_id\"");
173 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%" PRId64
,
174 bt_value_integer_signed_get(v
));
176 v
= bt_trace_borrow_environment_entry_value_by_name_const(tc
, "architecture_bit_width");
177 if (!v
|| !bt_value_is_signed_integer(v
)) {
178 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"architecture_bit_width\"");
182 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%" PRIu64
"-bit",
183 bt_value_integer_signed_get(v
));
196 int append_lttng_trace_path_ust_pid(const struct fs_sink_trace
*trace
,
197 GString
*path
, const bt_trace
*tc
)
200 const char *datetime
;
203 v
= bt_trace_borrow_environment_entry_value_by_name_const(tc
, "procname");
204 if (!v
|| !bt_value_is_string(v
)) {
205 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"procname\"");
209 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", bt_value_string_get(v
));
211 v
= bt_trace_borrow_environment_entry_value_by_name_const(tc
, "vpid");
212 if (!v
|| !bt_value_is_signed_integer(v
)) {
213 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"vpid\"");
217 g_string_append_printf(path
, "-%" PRId64
, bt_value_integer_signed_get(v
));
219 v
= bt_trace_borrow_environment_entry_value_by_name_const(tc
, "vpid_datetime");
220 if (!v
|| !bt_value_is_string(v
)) {
221 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"vpid_datetime\"");
225 datetime
= bt_value_string_get(v
);
227 if (lttng_validate_datetime(trace
, datetime
)) {
231 g_string_append_printf(path
, "-%s", datetime
);
244 * Try to build a trace path based on environment values put in the trace
245 * environment by the LTTng tracer, starting with version 2.11.
248 GString
*make_lttng_trace_path_rel(const struct fs_sink_trace
*trace
)
251 const char *tracer_name
, *domain
, *datetime
;
252 int64_t tracer_major
, tracer_minor
;
255 path
= g_string_new(NULL
);
260 v
= bt_trace_borrow_environment_entry_value_by_name_const(
261 trace
->ir_trace
, "tracer_name");
262 if (!v
|| !bt_value_is_string(v
)) {
263 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_name\"");
267 tracer_name
= bt_value_string_get(v
);
269 if (!g_str_equal(tracer_name
, "lttng-ust")
270 && !g_str_equal(tracer_name
, "lttng-modules")) {
271 BT_COMP_LOGI("Unrecognized tracer name: name=\"%s\"", tracer_name
);
275 v
= bt_trace_borrow_environment_entry_value_by_name_const(
276 trace
->ir_trace
, "tracer_major");
277 if (!v
|| !bt_value_is_signed_integer(v
)) {
278 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_major\"");
282 tracer_major
= bt_value_integer_signed_get(v
);
284 v
= bt_trace_borrow_environment_entry_value_by_name_const(
285 trace
->ir_trace
, "tracer_minor");
286 if (!v
|| !bt_value_is_signed_integer(v
)) {
287 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_minor\"");
291 tracer_minor
= bt_value_integer_signed_get(v
);
293 if (!(tracer_major
>= 3 || (tracer_major
== 2 && tracer_minor
>= 11))) {
294 BT_COMP_LOGI("Unsupported LTTng version for automatic trace path: major=%" PRId64
", minor=%" PRId64
,
295 tracer_major
, tracer_minor
);
299 v
= bt_trace_borrow_environment_entry_value_by_name_const(
300 trace
->ir_trace
, "hostname");
301 if (!v
|| !bt_value_is_string(v
)) {
302 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_hostname\"");
306 g_string_assign(path
, bt_value_string_get(v
));
308 v
= bt_trace_borrow_environment_entry_value_by_name_const(
309 trace
->ir_trace
, "trace_name");
310 if (!v
|| !bt_value_is_string(v
)) {
311 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"trace_name\"");
315 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", bt_value_string_get(v
));
317 v
= bt_trace_borrow_environment_entry_value_by_name_const(
318 trace
->ir_trace
, "trace_creation_datetime");
319 if (!v
|| !bt_value_is_string(v
)) {
320 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"trace_creation_datetime\"");
324 datetime
= bt_value_string_get(v
);
326 if (lttng_validate_datetime(trace
, datetime
)) {
330 g_string_append_printf(path
, "-%s", datetime
);
332 v
= bt_trace_borrow_environment_entry_value_by_name_const(
333 trace
->ir_trace
, "domain");
334 if (!v
|| !bt_value_is_string(v
)) {
335 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"domain\"");
339 domain
= bt_value_string_get(v
);
340 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", domain
);
342 if (g_str_equal(domain
, "ust")) {
343 const char *tracer_buffering_scheme
;
345 v
= bt_trace_borrow_environment_entry_value_by_name_const(
346 trace
->ir_trace
, "tracer_buffering_scheme");
347 if (!v
|| !bt_value_is_string(v
)) {
348 BT_COMP_LOGI_STR("Couldn't get environment value: name=\"tracer_buffering_scheme\"");
352 tracer_buffering_scheme
= bt_value_string_get(v
);
353 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", tracer_buffering_scheme
);
355 if (g_str_equal(tracer_buffering_scheme
, "uid")) {
356 if (append_lttng_trace_path_ust_uid(trace
, path
,
360 } else if (g_str_equal(tracer_buffering_scheme
, "pid")){
361 if (append_lttng_trace_path_ust_pid(trace
, path
,
366 /* Unknown buffering scheme. */
367 BT_COMP_LOGI("Unknown buffering scheme: tracer_buffering_scheme=\"%s\"", tracer_buffering_scheme
);
370 } else if (!g_str_equal(domain
, "kernel")) {
371 /* Unknown domain. */
372 BT_COMP_LOGI("Unknown domain: domain=\"%s\"", domain
);
380 g_string_free(path
, TRUE
);
388 /* Build the relative output path for `trace`. */
391 GString
*make_trace_path_rel(const struct fs_sink_trace
*trace
)
393 GString
*path
= NULL
;
394 const char *trace_name
;
396 BT_ASSERT(!trace
->fs_sink
->assume_single_trace
);
398 /* First, try to build a path using environment fields written by LTTng. */
399 path
= make_lttng_trace_path_rel(trace
);
404 /* Otherwise, use the trace name, if available. */
405 trace_name
= bt_trace_get_name(trace
->ir_trace
);
407 path
= g_string_new(trace_name
);
411 /* Otherwise, use "trace". */
412 path
= g_string_new("trace");
419 * Compute the trace output path for `trace`, rooted at `output_base_directory`.
423 GString
*make_trace_path(const struct fs_sink_trace
*trace
, const char *output_base_directory
)
425 GString
*rel_path
= NULL
;
426 GString
*rel_path_san
= NULL
;
427 GString
*full_path
= NULL
;
428 GString
*unique_full_path
= NULL
;
430 if (trace
->fs_sink
->assume_single_trace
) {
431 /* Use output directory directly */
432 unique_full_path
= g_string_new(output_base_directory
);
433 if (!unique_full_path
) {
437 rel_path
= make_trace_path_rel(trace
);
442 rel_path_san
= sanitize_trace_path(rel_path
->str
);
447 full_path
= g_string_new(NULL
);
452 g_string_printf(full_path
, "%s" G_DIR_SEPARATOR_S
"%s",
453 output_base_directory
, rel_path_san
->str
);
455 unique_full_path
= make_unique_trace_path(full_path
->str
);
460 g_string_free(rel_path
, TRUE
);
464 g_string_free(rel_path_san
, TRUE
);
468 g_string_free(full_path
, TRUE
);
471 return unique_full_path
;
475 void fs_sink_trace_destroy(struct fs_sink_trace
*trace
)
477 GString
*tsdl
= NULL
;
485 if (trace
->ir_trace_destruction_listener_id
!= UINT64_C(-1)) {
487 * Remove the destruction listener, otherwise it could
488 * be called in the future, and its private data is this
489 * CTF FS sink trace object which won't exist anymore.
491 (void) bt_trace_remove_destruction_listener(trace
->ir_trace
,
492 trace
->ir_trace_destruction_listener_id
);
493 trace
->ir_trace_destruction_listener_id
= UINT64_C(-1);
496 if (trace
->streams
) {
497 g_hash_table_destroy(trace
->streams
);
498 trace
->streams
= NULL
;
501 tsdl
= g_string_new(NULL
);
503 translate_trace_ctf_ir_to_tsdl(trace
->trace
, tsdl
);
505 BT_ASSERT(trace
->metadata_path
);
506 fh
= fopen(trace
->metadata_path
->str
, "wb");
508 BT_COMP_LOGF_ERRNO("In trace destruction listener: "
509 "cannot open metadata file for writing",
510 ": path=\"%s\"", trace
->metadata_path
->str
);
514 len
= fwrite(tsdl
->str
, sizeof(*tsdl
->str
), tsdl
->len
, fh
);
515 if (len
!= tsdl
->len
) {
516 BT_COMP_LOGF_ERRNO("In trace destruction listener: "
517 "cannot write metadata file",
518 ": path=\"%s\"", trace
->metadata_path
->str
);
522 if (!trace
->fs_sink
->quiet
) {
523 printf("Created CTF trace `%s`.\n", trace
->path
->str
);
527 g_string_free(trace
->path
, TRUE
);
532 int ret
= fclose(fh
);
535 BT_COMP_LOGW_ERRNO("In trace destruction listener: "
536 "cannot close metadata file",
537 ": path=\"%s\"", trace
->metadata_path
->str
);
541 g_string_free(trace
->metadata_path
, TRUE
);
542 trace
->metadata_path
= NULL
;
544 fs_sink_ctf_trace_destroy(trace
->trace
);
548 g_string_free(tsdl
, TRUE
);
555 void ir_trace_destruction_listener(const bt_trace
*ir_trace
, void *data
)
557 struct fs_sink_trace
*trace
= data
;
560 * Prevent bt_trace_remove_destruction_listener() from being
561 * called in fs_sink_trace_destroy(), which is called by
562 * g_hash_table_remove() below.
564 trace
->ir_trace_destruction_listener_id
= UINT64_C(-1);
565 g_hash_table_remove(trace
->fs_sink
->traces
, ir_trace
);
569 struct fs_sink_trace
*fs_sink_trace_create(struct fs_sink_comp
*fs_sink
,
570 const bt_trace
*ir_trace
)
573 struct fs_sink_trace
*trace
= g_new0(struct fs_sink_trace
, 1);
574 bt_trace_add_listener_status trace_status
;
580 trace
->log_level
= fs_sink
->log_level
;
581 trace
->fs_sink
= fs_sink
;
582 trace
->ir_trace
= ir_trace
;
583 trace
->ir_trace_destruction_listener_id
= UINT64_C(-1);
584 trace
->trace
= translate_trace_trace_ir_to_ctf_ir(fs_sink
, ir_trace
);
589 trace
->path
= make_trace_path(trace
, fs_sink
->output_dir_path
->str
);
590 BT_ASSERT(trace
->path
);
591 ret
= g_mkdir_with_parents(trace
->path
->str
, 0755);
593 BT_COMP_LOGE_ERRNO("Cannot create directories for trace directory",
594 ": path=\"%s\"", trace
->path
->str
);
598 trace
->metadata_path
= g_string_new(trace
->path
->str
);
599 BT_ASSERT(trace
->metadata_path
);
600 g_string_append(trace
->metadata_path
, "/metadata");
601 trace
->streams
= g_hash_table_new_full(g_direct_hash
, g_direct_equal
,
602 NULL
, (GDestroyNotify
) fs_sink_stream_destroy
);
603 BT_ASSERT(trace
->streams
);
604 trace_status
= bt_trace_add_destruction_listener(ir_trace
,
605 ir_trace_destruction_listener
, trace
,
606 &trace
->ir_trace_destruction_listener_id
);
611 g_hash_table_insert(fs_sink
->traces
, (gpointer
) ir_trace
, trace
);
615 fs_sink_trace_destroy(trace
);