2 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 #define BT_LOG_TAG "PLUGIN-CTF-FS-SINK-TRACE"
26 #include <babeltrace/babeltrace.h>
30 #include <babeltrace/assert-internal.h>
31 #include <babeltrace/ctfser-internal.h>
33 #include "translate-trace-ir-to-ctf-ir.h"
34 #include "translate-ctf-ir-to-tsdl.h"
36 #include "fs-sink-trace.h"
37 #include "fs-sink-stream.h"
40 * Sanitizes `path` so as to:
42 * * Replace `.` subdirectories with `_`.
43 * * Replace `..` subdirectories with `__`.
44 * * Remove trailing slashes.
47 GString
*sanitize_trace_path(const char *path
)
49 GString
*san_path
= g_string_new(NULL
);
50 const char *ch
= path
;
51 bool dir_start
= true;
59 /* Start of directory */
61 g_string_append_c(san_path
, *ch
);
70 g_string_append_c(san_path
, '_');
78 g_string_append(san_path
, "__");
92 /* Not a special character */
93 g_string_append_c(san_path
, *ch
);
98 /* Remove trailing slashes */
99 while (san_path
->len
> 0 &&
100 san_path
->str
[san_path
->len
- 1] == '/') {
101 /* Remove trailing slash */
102 g_string_set_size(san_path
, san_path
->len
- 1);
105 if (san_path
->len
== 0) {
106 /* Looks like there's nothing left: just use `trace` */
107 g_string_assign(san_path
, "trace");
114 * Find a path based on `path` that doesn't exist yet. First, try `path`
115 * itself, then try with incrementing suffixes.
119 GString
*make_unique_trace_path(const char *path
)
121 GString
*unique_path
;
122 unsigned int suffix
= 0;
124 unique_path
= g_string_new(path
);
126 while (g_file_test(unique_path
->str
, G_FILE_TEST_EXISTS
)) {
127 g_string_printf(unique_path
, "%s-%u", path
, suffix
);
135 * Validate that the input string `datetime` is an ISO8601-compliant string (the
136 * format used by LTTng in the metadata).
140 int lttng_validate_datetime(const char *datetime
)
142 GTimeZone
*default_tz
;
143 GDateTime
*gdatetime
= NULL
;
146 default_tz
= g_time_zone_new_utc();
148 BT_LOGD("Failed to allocate timezone");
152 gdatetime
= g_date_time_new_from_iso8601(datetime
, default_tz
);
154 BT_LOGD("Couldn't parse datetime as iso8601: date=\"%s\"", datetime
);
162 g_time_zone_unref(default_tz
);
167 g_date_time_unref(gdatetime
);
175 int append_lttng_trace_path_ust_uid(GString
*path
, const bt_trace_class
*tc
)
180 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "tracer_buffering_id");
181 if (!v
|| !bt_value_is_integer(v
)) {
182 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_buffering_id\"");
186 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%" PRId64
, bt_value_integer_get(v
));
188 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "isa_length");
189 if (!v
|| !bt_value_is_integer(v
)) {
190 BT_LOGD_STR("Couldn't get environment value: name=\"isa_length\"");
194 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%" PRIu64
"-bit", bt_value_integer_get(v
));
207 int append_lttng_trace_path_ust_pid(GString
*path
, const bt_trace_class
*tc
)
210 const char *datetime
;
213 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "procname");
214 if (!v
|| !bt_value_is_string(v
)) {
215 BT_LOGD_STR("Couldn't get environment value: name=\"procname\"");
219 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", bt_value_string_get(v
));
221 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "vpid");
222 if (!v
|| !bt_value_is_integer(v
)) {
223 BT_LOGD_STR("Couldn't get environment value: name=\"vpid\"");
227 g_string_append_printf(path
, "-%" PRId64
, bt_value_integer_get(v
));
229 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "vpid_datetime");
230 if (!v
|| !bt_value_is_string(v
)) {
231 BT_LOGD_STR("Couldn't get environment value: name=\"vpid_datetime\"");
235 datetime
= bt_value_string_get(v
);
237 if (lttng_validate_datetime(datetime
)) {
241 g_string_append_printf(path
, "-%s", datetime
);
254 * Try to build a trace path based on environment values put in the trace
255 * environment by the LTTng tracer, starting with version 2.11.
258 GString
*make_lttng_trace_path_rel(const struct fs_sink_trace
*trace
)
260 const bt_trace_class
*tc
;
262 const char *tracer_name
, *domain
, *datetime
;
263 int64_t tracer_major
, tracer_minor
;
266 path
= g_string_new(NULL
);
271 tc
= bt_trace_borrow_class_const(trace
->ir_trace
);
273 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "tracer_name");
274 if (!v
|| !bt_value_is_string(v
)) {
275 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_name\"");
279 tracer_name
= bt_value_string_get(v
);
281 if (!g_str_equal(tracer_name
, "lttng-ust")
282 && !g_str_equal(tracer_name
, "lttng-modules")) {
283 BT_LOGD("Unrecognized tracer name: name=\"%s\"", tracer_name
);
287 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "tracer_major");
288 if (!v
|| !bt_value_is_integer(v
)) {
289 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_major\"");
293 tracer_major
= bt_value_integer_get(v
);
295 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "tracer_minor");
296 if (!v
|| !bt_value_is_integer(v
)) {
297 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_minor\"");
301 tracer_minor
= bt_value_integer_get(v
);
303 if (!(tracer_major
>= 3 || (tracer_major
== 2 && tracer_minor
>= 11))) {
304 BT_LOGD("Unsupported LTTng version for automatic trace path: major=%" PRId64
", minor=%" PRId64
,
305 tracer_major
, tracer_minor
);
309 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "hostname");
310 if (!v
|| !bt_value_is_string(v
)) {
311 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_hostname\"");
315 g_string_assign(path
, bt_value_string_get(v
));
317 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "trace_name");
318 if (!v
|| !bt_value_is_string(v
)) {
319 BT_LOGD_STR("Couldn't get environment value: name=\"trace_name\"");
323 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", bt_value_string_get(v
));
325 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "trace_creation_datetime");
326 if (!v
|| !bt_value_is_string(v
)) {
327 BT_LOGD_STR("Couldn't get environment value: name=\"trace_creation_datetime\"");
331 datetime
= bt_value_string_get(v
);
333 if (lttng_validate_datetime(datetime
)) {
337 g_string_append_printf(path
, "-%s", datetime
);
339 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "domain");
340 if (!v
|| !bt_value_is_string(v
)) {
341 BT_LOGD_STR("Couldn't get environment value: name=\"domain\"");
345 domain
= bt_value_string_get(v
);
346 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", domain
);
348 if (g_str_equal(domain
, "ust")) {
349 const char *tracer_buffering_scheme
;
351 v
= bt_trace_class_borrow_environment_entry_value_by_name_const(tc
, "tracer_buffering_scheme");
352 if (!v
|| !bt_value_is_string(v
)) {
353 BT_LOGD_STR("Couldn't get environment value: name=\"tracer_buffering_scheme\"");
357 tracer_buffering_scheme
= bt_value_string_get(v
);
358 g_string_append_printf(path
, G_DIR_SEPARATOR_S
"%s", tracer_buffering_scheme
);
360 if (g_str_equal(tracer_buffering_scheme
, "uid")) {
361 if (append_lttng_trace_path_ust_uid(path
, tc
)) {
364 } else if (g_str_equal(tracer_buffering_scheme
, "pid")){
365 if (append_lttng_trace_path_ust_pid(path
, tc
)) {
369 /* Unknown buffering scheme. */
370 BT_LOGD("Unknown buffering scheme: tracer_buffering_scheme=\"%s\"", tracer_buffering_scheme
);
373 } else if (!g_str_equal(domain
, "kernel")) {
374 /* Unknown domain. */
375 BT_LOGD("Unknown domain: domain=\"%s\"", domain
);
383 g_string_free(path
, TRUE
);
391 /* Build the relative output path for `trace`. */
394 GString
*make_trace_path_rel(const struct fs_sink_trace
*trace
)
396 GString
*path
= NULL
;
398 if (trace
->fs_sink
->assume_single_trace
) {
399 /* Use output directory directly */
400 path
= g_string_new("");
404 /* First, try to build a path using environment fields written by LTTng. */
405 path
= make_lttng_trace_path_rel(trace
);
410 /* Otherwise, use the trace name, if available. */
411 const char *trace_name
= bt_trace_get_name(trace
->ir_trace
);
413 path
= g_string_new(trace_name
);
417 /* Otherwise, use "trace". */
418 path
= g_string_new("trace");
425 * Compute the trace output path for `trace`, rooted at `output_base_directory`.
429 GString
*make_trace_path(const struct fs_sink_trace
*trace
, const char *output_base_directory
)
431 GString
*rel_path
= NULL
;
432 GString
*rel_path_san
= NULL
;
433 GString
*full_path
= NULL
;
434 GString
*unique_full_path
= NULL
;
436 rel_path
= make_trace_path_rel(trace
);
441 rel_path_san
= sanitize_trace_path(rel_path
->str
);
446 full_path
= g_string_new(NULL
);
451 g_string_printf(full_path
, "%s" G_DIR_SEPARATOR_S
"%s",
452 output_base_directory
, rel_path_san
->str
);
454 unique_full_path
= make_unique_trace_path(full_path
->str
);
458 g_string_free(rel_path
, TRUE
);
462 g_string_free(rel_path_san
, TRUE
);
466 g_string_free(full_path
, TRUE
);
469 return unique_full_path
;
473 void fs_sink_trace_destroy(struct fs_sink_trace
*trace
)
475 GString
*tsdl
= NULL
;
483 if (trace
->ir_trace_destruction_listener_id
!= UINT64_C(-1)) {
485 * Remove the destruction listener, otherwise it could
486 * be called in the future, and its private data is this
487 * CTF FS sink trace object which won't exist anymore.
489 (void) bt_trace_remove_destruction_listener(trace
->ir_trace
,
490 trace
->ir_trace_destruction_listener_id
);
491 trace
->ir_trace_destruction_listener_id
= UINT64_C(-1);
494 if (trace
->streams
) {
495 g_hash_table_destroy(trace
->streams
);
496 trace
->streams
= NULL
;
499 tsdl
= g_string_new(NULL
);
501 translate_trace_class_ctf_ir_to_tsdl(trace
->tc
, tsdl
);
502 fh
= fopen(trace
->metadata_path
->str
, "wb");
504 BT_LOGF_ERRNO("In trace destruction listener: "
505 "cannot open metadata file for writing: ",
506 ": path=\"%s\"", trace
->metadata_path
->str
);
510 len
= fwrite(tsdl
->str
, sizeof(*tsdl
->str
), tsdl
->len
, fh
);
511 if (len
!= tsdl
->len
) {
512 BT_LOGF_ERRNO("In trace destruction listener: "
513 "cannot write metadata file: ",
514 ": path=\"%s\"", trace
->metadata_path
->str
);
518 if (!trace
->fs_sink
->quiet
) {
519 printf("Created CTF trace `%s`.\n", trace
->path
->str
);
523 g_string_free(trace
->path
, TRUE
);
527 if (trace
->metadata_path
) {
528 g_string_free(trace
->metadata_path
, TRUE
);
529 trace
->metadata_path
= NULL
;
532 fs_sink_ctf_trace_class_destroy(trace
->tc
);
538 int ret
= fclose(fh
);
541 BT_LOGW_ERRNO("In trace destruction listener: "
542 "cannot close metadata file: ",
543 ": path=\"%s\"", trace
->metadata_path
->str
);
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_status trace_status
;
580 trace
->fs_sink
= fs_sink
;
581 trace
->ir_trace
= ir_trace
;
582 trace
->ir_trace_destruction_listener_id
= UINT64_C(-1);
583 trace
->tc
= translate_trace_class_trace_ir_to_ctf_ir(
584 bt_trace_borrow_class_const(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_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
);