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