Add `sink.text.details`, a testing textual sink which prints details
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Sat, 8 Jun 2019 00:26:34 +0000 (20:26 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Mon, 17 Jun 2019 20:56:59 +0000 (16:56 -0400)
This patch adds a new sink component class, `sink.text.details`. Such
a sink component prints all the messages and their details, including
the full metadata objects, in a deterministic way.

You can test it easily like this:

    babeltrace2 /path/to/trace -c sink.text.details

The purpose of this sink is to use it for regression testing using the
following approach:

1. Run the `babeltrace2` program with a `sink.text.details` component,
   providing a known input trace to the graph, for example a CTF trace
   with `src.ctf.fs`. The graph can include one or more filters with
   specific parameters to test them specifically.

2. Inspect the output with your human eyes and brain. Verify that
   everything is as expected. It is possible that, for a given test,
   some elements of the output are less important (for example, the
   metadata objects or the message times): there are component
   parameters to remove parts of the output to reduce the noise.

3. Save the output of 2. to a file.

4. Create a test which runs 1. with the same input and compares its
   output to the file saved in 3. Any regression will show up as a
   difference between both inputs.

Step 2. is particularly important because this is where you validate
that the output shows what you expect from the graph. To make this
easier, I tried to make the output format of `sink.text.details` as
human-readable as possible:

* The component optionally colorizes the output. This is the default
  when the terminal supports it. There's no color codes by default when
  you redirect the output.

* The component honors the preferred display base of integer fields and
  prepends the typical `0x` and `0` prefixes.

* The component inserts digit separators to make it easier to read large
  numbers, for example `102,354,895,784,619` and `0x7f4d:d04f:c636`.

* The component does not print the metadata objects interlaced with the
  data and message objects. I found that this was noisy, for example
  showing all the integer field class details for each integer field
  when what you really want is to inspect the event payloads from one
  message to the other.

  Instead, I chose to print trace classes, stream classes, and event
  classes only once, and before the message which is associated to them.
  This typically occurs before a stream beginning message, but for event
  classes, it can also occur before an event message, as a source can
  add event classes to an existing stream class.

* The component assigns a unique ID to each trace object to follow the
  messages associated to a specific stream more easily, as many streams
  can share the same ID, across difference traces or even with the same
  trace, when they have different stream classes.

  The component prints this unique trace ID, as well as the stream
  class and stream IDs, before each message type as such
  (`{Trace 1, Stream class ID 0, Stream ID 2}` in the following
  example):

      [102,354,895,784,619 cycles, 1,441,852,773,172,610,636 ns from origin]
      {Trace 1, Stream class ID 0, Stream ID 2}
      Event `lttng_ust_statedump:build_id` (Class ID 56):
        Common context:
          vpid: 17,868
          ip: 0x7f4d:d04f:c636
        Payload:
          baddr: 0x7f4d:d02d:1000
          ...

  A unique trace ID is never reused for two different traces within the
  lifetime of a `sink.text.details` component.

  You can track all the messages of a given stream with this unique
  tuple. For example, you can find the last packet beginning message for
  the packet of a given event message having unique trace ID 0, stream
  class ID 2, and stream ID 5 by finding the last

      {Trace 0, Stream class ID 2, Stream ID 5}
      Packet beginning

  string.

  This makes it possible to avoid redundancy and reduce textual noise in
  the printed messages. For example, the component does not print the
  packet context field for each event message, but only for the packet
  beginning message.

In order to make step 4. continue to work in the future and really test
regressions, the output of step 1. must always be the same
(deterministic). For `sink.text.details`, this means:

* The component does not print run-time addresses.

* The component sorts mappings of enumeration field classes (by label),
  and ranges within such mappings, before it prints them.

* The component sorts event classes of stream classes (by ID) before
  it prints them.

* The component sorts stream classes of trace classes (by ID) before
  it prints them.

* The component sorts streams of traces (by ID and class ID) before
  it prints them.

* The component sorts environment entries of trace classes (by name)
  before it prints them.

To make step 3. easier, the component does not print trailing spaces
or trailing empty lines.

A `sink.text.details` component can also be used for support if we want
all the details of a given graph's output.

The available component parameters are:

`color`:
  `never`, `auto`, or `always` (just like `sink.text.pretty`).

  Default: `auto`

`with-metadata`:
  True to print metadata objects (only the first time they are
  encountered).

  Default: true

`with-time`:
  True to print message times.

  Default: true

`with-trace-class-name`:
  True to print trace class names.

  Default: true

`with-trace-name`:
  True to print trace names.

  Default: true

`with-stream-class-name`:
  True to print stream class names.

  Default: true

`with-stream-name`:
  True to print stream names.

  Default: true

`with-uuid`:
  True to print UUIDs.

  Default: true

`compact`:
  True to make the output compact (one message per line, no property
  names).

  Default: false

This patch does not add the facilities to create tests easily using the
CLI and a `sink.text.details` component: this is reserved for a future
patch.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Change-Id: I9cc39cdfe44436974600388342029a2e4e2acb76
Reviewed-on: https://review.lttng.org/c/babeltrace/+/1412
Tested-by: jenkins
Reviewed-by: Simon Marchi <simon.marchi@efficios.com>
14 files changed:
configure.ac
src/cli/babeltrace2.c
src/plugins/text/Makefile.am
src/plugins/text/details/Makefile.am [new file with mode: 0644]
src/plugins/text/details/colors.h [new file with mode: 0644]
src/plugins/text/details/details.c [new file with mode: 0644]
src/plugins/text/details/details.h [new file with mode: 0644]
src/plugins/text/details/logging.c [new file with mode: 0644]
src/plugins/text/details/logging.h [new file with mode: 0644]
src/plugins/text/details/obj-lifetime-mgmt.c [new file with mode: 0644]
src/plugins/text/details/obj-lifetime-mgmt.h [new file with mode: 0644]
src/plugins/text/details/write.c [new file with mode: 0644]
src/plugins/text/details/write.h [new file with mode: 0644]
src/plugins/text/plugin.c

index e79321e941e39b99407f544b600f736e7870e37a..dfbe00c6ac67774beed024ec7c189311e44865da 100644 (file)
@@ -771,6 +771,7 @@ AC_CONFIG_FILES([
        src/plugins/text/dmesg/Makefile
        src/plugins/text/Makefile
        src/plugins/text/pretty/Makefile
+       src/plugins/text/details/Makefile
        src/plugins/utils/counter/Makefile
        src/plugins/utils/dummy/Makefile
        src/plugins/utils/Makefile
index b4285e5b09185cf0be00a1e1241539ebfd358274..4f786c2e3d6ebad141c22b0aa599b0a0a0ede92e 100644 (file)
@@ -64,6 +64,7 @@ static const char* log_level_env_var_names[] = {
        "BABELTRACE_PYTHON_BT2_LOG_LEVEL",
        "BABELTRACE_SINK_CTF_FS_LOG_LEVEL",
        "BABELTRACE_SINK_TEXT_PRETTY_LOG_LEVEL",
+       "BABELTRACE_SINK_TEXT_DETAILS_LOG_LEVEL",
        "BABELTRACE_SRC_CTF_FS_LOG_LEVEL",
        "BABELTRACE_SRC_CTF_LTTNG_LIVE_LOG_LEVEL",
        "BABELTRACE_SRC_TEXT_DMESG_LOG_LEVEL",
index f71791071afb9bd2584e70aac7cc4008bcdde55d..27560ad4528c08e67ac03306f7b474a7d8cb881b 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS = pretty dmesg
+SUBDIRS = pretty dmesg details
 
 plugindir = "$(PLUGINSDIR)"
 plugin_LTLIBRARIES = babeltrace-plugin-text.la
@@ -10,7 +10,8 @@ babeltrace_plugin_text_la_LDFLAGS = \
 
 babeltrace_plugin_text_la_LIBADD = \
        pretty/libbabeltrace2-plugin-text-pretty-cc.la \
-       dmesg/libbabeltrace2-plugin-text-dmesg-cc.la
+       dmesg/libbabeltrace2-plugin-text-dmesg-cc.la \
+       details/libbabeltrace2-plugin-text-details-cc.la
 
 if !ENABLE_BUILT_IN_PLUGINS
 babeltrace_plugin_text_la_LIBADD += \
diff --git a/src/plugins/text/details/Makefile.am b/src/plugins/text/details/Makefile.am
new file mode 100644 (file)
index 0000000..fefae10
--- /dev/null
@@ -0,0 +1,9 @@
+AM_CPPFLAGS += -I$(top_srcdir)/plugins
+
+noinst_LTLIBRARIES = libbabeltrace2-plugin-text-details-cc.la
+libbabeltrace2_plugin_text_details_cc_la_SOURCES = \
+       details.c details.h \
+       write.c write.h \
+       obj-lifetime-mgmt.c obj-lifetime-mgmt.h \
+       colors.h \
+       logging.c logging.h
diff --git a/src/plugins/text/details/colors.h b/src/plugins/text/details/colors.h
new file mode 100644 (file)
index 0000000..5b142f5
--- /dev/null
@@ -0,0 +1,246 @@
+#ifndef BABELTRACE_PLUGINS_TEXT_DETAILS_COLORS_H
+#define BABELTRACE_PLUGINS_TEXT_DETAILS_COLORS_H
+
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "common/common.h"
+
+#include "write.h"
+
+static inline
+const char *color_reset(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_RESET;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_bold(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_BOLD;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_fg_default(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_FG_DEFAULT;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_fg_red(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_FG_RED;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_fg_green(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_FG_GREEN;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_fg_yellow(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_FG_YELLOW;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_fg_blue(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_FG_BLUE;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_fg_magenta(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_FG_MAGENTA;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_fg_cyan(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_FG_CYAN;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_fg_light_gray(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_FG_LIGHT_GRAY;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_bg_default(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_BG_DEFAULT;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_bg_red(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_BG_RED;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_bg_green(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_BG_GREEN;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_bg_yellow(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_BG_YELLOW;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_bg_blue(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_BG_BLUE;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_bg_magenta(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_BG_MAGENTA;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_bg_cyan(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_BG_CYAN;
+       }
+
+       return code;
+}
+
+static inline
+const char *color_bg_light_gray(struct details_write_ctx *ctx)
+{
+       const char *code = "";
+
+       if (ctx->details_comp->cfg.with_color) {
+               code = BT_COMMON_COLOR_BG_LIGHT_GRAY;
+       }
+
+       return code;
+}
+
+#endif /* BABELTRACE_PLUGINS_TEXT_DETAILS_COLORS_H */
diff --git a/src/plugins/text/details/details.c b/src/plugins/text/details/details.c
new file mode 100644 (file)
index 0000000..3fcd466
--- /dev/null
@@ -0,0 +1,525 @@
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_TAG "PLUGIN-TEXT-DETAILS-SINK"
+#include "logging.h"
+
+#include <babeltrace2/babeltrace.h>
+
+#include "common/common.h"
+#include "common/assert.h"
+#include "details.h"
+#include "write.h"
+
+#define LOG_WRONG_PARAM_TYPE(_name, _value, _exp_type)                 \
+       do {                                                            \
+               BT_LOGE("Wrong `%s` parameter type: type=%s, "          \
+                       "expected-type=%s",                             \
+                       (_name), bt_common_value_type_string(           \
+                               bt_value_get_type(_value)),             \
+                       bt_common_value_type_string(_exp_type));        \
+       } while (0)
+
+static
+const char * const in_port_name = "in";
+
+static
+const char * const color_param_name = "color";
+
+static
+const char * const with_metadata_param_name = "with-metadata";
+
+static
+const char * const with_time_param_name = "with-time";
+
+static
+const char * const with_trace_class_name_param_name = "with-trace-class-name";
+
+static
+const char * const with_trace_name_param_name = "with-trace-name";
+
+static
+const char * const with_stream_class_name_param_name = "with-stream-class-name";
+
+static
+const char * const with_stream_name_param_name = "with-stream-name";
+
+static
+const char * const with_uuid_param_name = "with-uuid";
+
+static
+const char * const compact_param_name = "compact";
+
+BT_HIDDEN
+void details_destroy_details_trace_class_meta(
+               struct details_trace_class_meta *details_tc_meta)
+{
+       if (!details_tc_meta) {
+               goto end;
+       }
+
+       if (details_tc_meta->objects) {
+               g_hash_table_destroy(details_tc_meta->objects);
+               details_tc_meta->objects = NULL;
+       }
+
+       g_free(details_tc_meta);
+
+end:
+       return;
+}
+
+BT_HIDDEN
+struct details_trace_class_meta *details_create_details_trace_class_meta(void)
+{
+       struct details_trace_class_meta *details_tc_meta =
+               g_new0(struct details_trace_class_meta, 1);
+
+       if (!details_tc_meta) {
+               goto end;
+       }
+
+       details_tc_meta->objects = g_hash_table_new(
+               g_direct_hash, g_direct_equal);
+       if (!details_tc_meta->objects) {
+               details_destroy_details_trace_class_meta(details_tc_meta);
+               details_tc_meta = NULL;
+               goto end;
+       }
+
+       details_tc_meta->tc_destruction_listener_id = UINT64_C(-1);
+
+end:
+       return details_tc_meta;
+}
+
+static
+void destroy_details_comp(struct details_comp *details_comp)
+{
+       GHashTableIter iter;
+       gpointer key, value;
+
+       if (!details_comp) {
+               goto end;
+       }
+
+       if (details_comp->meta) {
+               /*
+                * Remove trace class destruction listeners, because
+                * otherwise, when they are called, `details_comp`
+                * (their user data) won't exist anymore (we're
+                * destroying it here).
+                */
+               g_hash_table_iter_init(&iter, details_comp->meta);
+
+               while (g_hash_table_iter_next(&iter, &key, &value)) {
+                       struct details_trace_class_meta *details_tc_meta =
+                               value;
+
+                       if (details_tc_meta->tc_destruction_listener_id !=
+                                       UINT64_C(-1)) {
+                               bt_trace_class_remove_destruction_listener(
+                                       (const void *) key,
+                                       details_tc_meta->tc_destruction_listener_id);
+                       }
+               }
+
+               g_hash_table_destroy(details_comp->meta);
+               details_comp->meta = NULL;
+       }
+
+       if (details_comp->traces) {
+               /*
+                * Remove trace destruction listeners, because
+                * otherwise, when they are called, `details_comp` won't
+                * exist anymore (we're destroying it here).
+                */
+               g_hash_table_iter_init(&iter, details_comp->traces);
+
+               while (g_hash_table_iter_next(&iter, &key, &value)) {
+                       struct details_trace *details_trace = value;
+
+                       bt_trace_remove_destruction_listener(
+                               (const void *) key,
+                               details_trace->trace_destruction_listener_id);
+               }
+
+               g_hash_table_destroy(details_comp->traces);
+               details_comp->traces = NULL;
+       }
+
+       if (details_comp->str) {
+               g_string_free(details_comp->str, TRUE);
+               details_comp->str = NULL;
+       }
+
+       BT_SELF_COMPONENT_PORT_INPUT_MESSAGE_ITERATOR_PUT_REF_AND_RESET(
+               details_comp->msg_iter);
+       g_free(details_comp);
+
+end:
+       return;
+}
+
+static
+struct details_comp *create_details_comp(void)
+{
+       struct details_comp *details_comp = g_new0(struct details_comp, 1);
+
+       if (!details_comp) {
+               goto error;
+       }
+
+       details_comp->meta = g_hash_table_new_full(g_direct_hash,
+               g_direct_equal, NULL,
+               (GDestroyNotify) details_destroy_details_trace_class_meta);
+       if (!details_comp->meta) {
+               goto error;
+       }
+
+       details_comp->traces = g_hash_table_new_full(g_direct_hash,
+               g_direct_equal, NULL, g_free);
+       if (!details_comp->traces) {
+               goto error;
+       }
+
+       details_comp->str = g_string_new(NULL);
+       if (!details_comp->str) {
+               goto error;
+       }
+
+       goto end;
+
+error:
+       destroy_details_comp(details_comp);
+       details_comp = NULL;
+
+end:
+       return details_comp;
+}
+
+BT_HIDDEN
+void details_finalize(bt_self_component_sink *comp)
+{
+       struct details_comp *details_comp;
+
+       BT_ASSERT(comp);
+       details_comp = bt_self_component_get_data(
+               bt_self_component_sink_as_self_component(comp));
+       BT_ASSERT(details_comp);
+       destroy_details_comp(details_comp);
+}
+
+static
+int configure_bool_opt(struct details_comp *details_comp,
+               const bt_value *params, const char *param_name,
+               bool default_value, bool *opt_value)
+{
+       int ret = 0;
+       const bt_value *value;
+
+       *opt_value = default_value;
+       value = bt_value_map_borrow_entry_value_const(params, param_name);
+       if (value) {
+               if (!bt_value_is_bool(value)) {
+                       LOG_WRONG_PARAM_TYPE(param_name, value,
+                               BT_VALUE_TYPE_BOOL);
+                       ret = -1;
+                       goto end;
+               }
+
+               *opt_value = (bool) bt_value_bool_get(value);
+       }
+
+end:
+       return ret;
+}
+
+static
+int configure_details_comp(struct details_comp *details_comp,
+               const bt_value *params)
+{
+       int ret = 0;
+       const bt_value *value;
+       const char *str;
+
+       /* Colorize output? */
+       details_comp->cfg.with_color = bt_common_colors_supported();
+       value = bt_value_map_borrow_entry_value_const(params, color_param_name);
+       if (value) {
+               if (!bt_value_is_string(value)) {
+                       LOG_WRONG_PARAM_TYPE(color_param_name, value,
+                               BT_VALUE_TYPE_STRING);
+                       goto error;
+               }
+
+               str = bt_value_string_get(value);
+
+               if (strcmp(str, "never") == 0) {
+                       details_comp->cfg.with_color = false;
+               } else if (strcmp(str, "auto") == 0) {
+                       details_comp->cfg.with_color =
+                               bt_common_colors_supported();
+               } else if (strcmp(str, "always") == 0) {
+                       details_comp->cfg.with_color = true;
+               } else {
+                       BT_LOGE("Invalid `%s` parameter: unknown value "
+                               "(expecting `never`, `auto`, or `always`): "
+                               "value=\"%s\"", color_param_name, str);
+                       goto error;
+               }
+       }
+
+       /* With metadata objects? */
+       ret = configure_bool_opt(details_comp, params, with_metadata_param_name,
+               true, &details_comp->cfg.with_meta);
+       if (ret) {
+               goto error;
+       }
+
+       /* Compact? */
+       ret = configure_bool_opt(details_comp, params, compact_param_name,
+               false, &details_comp->cfg.compact);
+       if (ret) {
+               goto error;
+       }
+
+       /* With time? */
+       ret = configure_bool_opt(details_comp, params, with_time_param_name,
+               true, &details_comp->cfg.with_time);
+       if (ret) {
+               goto error;
+       }
+
+       /* With trace class name? */
+       ret = configure_bool_opt(details_comp, params,
+               with_trace_class_name_param_name,
+               true, &details_comp->cfg.with_trace_class_name);
+       if (ret) {
+               goto error;
+       }
+
+       /* With trace name? */
+       ret = configure_bool_opt(details_comp, params,
+               with_trace_name_param_name,
+               true, &details_comp->cfg.with_trace_name);
+       if (ret) {
+               goto error;
+       }
+
+       /* With stream class name? */
+       ret = configure_bool_opt(details_comp, params,
+               with_stream_class_name_param_name,
+               true, &details_comp->cfg.with_stream_class_name);
+       if (ret) {
+               goto error;
+       }
+
+       /* With stream name? */
+       ret = configure_bool_opt(details_comp, params,
+               with_stream_name_param_name,
+               true, &details_comp->cfg.with_stream_name);
+       if (ret) {
+               goto error;
+       }
+
+       /* With UUID? */
+       ret = configure_bool_opt(details_comp, params,
+               with_uuid_param_name, true, &details_comp->cfg.with_uuid);
+       if (ret) {
+               goto error;
+       }
+
+       goto end;
+
+error:
+       ret = -1;
+
+end:
+       return ret;
+}
+
+static
+void log_configuration(bt_self_component_sink *comp,
+               struct details_comp *details_comp)
+{
+       BT_LOGI("Configuration for `sink.text.details` component `%s`:",
+               bt_component_get_name(bt_self_component_as_component(
+                       bt_self_component_sink_as_self_component(comp))));
+       BT_LOGI("  Colorize output: %d", details_comp->cfg.with_color);
+       BT_LOGI("  Compact: %d", details_comp->cfg.compact);
+       BT_LOGI("  With metadata: %d", details_comp->cfg.with_meta);
+       BT_LOGI("  With time: %d", details_comp->cfg.with_time);
+       BT_LOGI("  With trace class name: %d",
+               details_comp->cfg.with_trace_class_name);
+       BT_LOGI("  With trace name: %d", details_comp->cfg.with_trace_name);
+       BT_LOGI("  With stream class name: %d",
+               details_comp->cfg.with_stream_class_name);
+       BT_LOGI("  With stream name: %d", details_comp->cfg.with_stream_name);
+       BT_LOGI("  With UUID: %d", details_comp->cfg.with_uuid);
+}
+
+BT_HIDDEN
+bt_self_component_status details_init(bt_self_component_sink *comp,
+               const bt_value *params,
+               __attribute__((unused)) void *init_method_data)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       struct details_comp *details_comp = NULL;
+
+       status = bt_self_component_sink_add_input_port(comp, in_port_name,
+               NULL, NULL);
+       if (status != BT_SELF_COMPONENT_STATUS_OK) {
+               goto error;
+       }
+
+       details_comp = create_details_comp();
+       if (!details_comp) {
+               status = BT_SELF_COMPONENT_STATUS_NOMEM;
+               goto error;
+       }
+
+       if (configure_details_comp(details_comp, params)) {
+               BT_LOGE_STR("Failed to configure component.");
+               goto error;
+       }
+
+       log_configuration(comp, details_comp);
+       bt_self_component_set_data(
+               bt_self_component_sink_as_self_component(comp), details_comp);
+       goto end;
+
+error:
+       if (status == BT_SELF_COMPONENT_STATUS_OK) {
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+       }
+
+       destroy_details_comp(details_comp);
+
+end:
+       return status;
+}
+
+BT_HIDDEN
+bt_self_component_status details_graph_is_configured(
+               bt_self_component_sink *comp)
+{
+       bt_self_component_status status = BT_SELF_COMPONENT_STATUS_OK;
+       bt_self_component_port_input_message_iterator *iterator;
+       struct details_comp *details_comp;
+       bt_self_component_port_input *in_port;
+
+       details_comp = bt_self_component_get_data(
+               bt_self_component_sink_as_self_component(comp));
+       BT_ASSERT(details_comp);
+       in_port = bt_self_component_sink_borrow_input_port_by_name(comp,
+               in_port_name);
+       if (!bt_port_is_connected(bt_port_input_as_port_const(
+                       bt_self_component_port_input_as_port_input(in_port)))) {
+               BT_LOGE("Single input port is not connected: "
+                       "port-name=\"%s\"", in_port_name);
+               status = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       }
+
+       iterator = bt_self_component_port_input_message_iterator_create(
+               bt_self_component_sink_borrow_input_port_by_name(comp,
+                       in_port_name));
+       if (!iterator) {
+               status = BT_SELF_COMPONENT_STATUS_NOMEM;
+               goto end;
+       }
+
+       BT_SELF_COMPONENT_PORT_INPUT_MESSAGE_ITERATOR_MOVE_REF(
+               details_comp->msg_iter, iterator);
+
+end:
+       return status;
+}
+
+BT_HIDDEN
+bt_self_component_status details_consume(bt_self_component_sink *comp)
+{
+       bt_self_component_status ret = BT_SELF_COMPONENT_STATUS_OK;
+       bt_message_array_const msgs;
+       uint64_t count;
+       struct details_comp *details_comp;
+       bt_message_iterator_status it_ret;
+       uint64_t i;
+
+       details_comp = bt_self_component_get_data(
+               bt_self_component_sink_as_self_component(comp));
+       BT_ASSERT(details_comp);
+       BT_ASSERT(details_comp->msg_iter);
+
+       /* Consume messages */
+       it_ret = bt_self_component_port_input_message_iterator_next(
+               details_comp->msg_iter, &msgs, &count);
+       switch (it_ret) {
+       case BT_MESSAGE_ITERATOR_STATUS_OK:
+               ret = BT_SELF_COMPONENT_STATUS_OK;
+
+               for (i = 0; i < count; i++) {
+                       int print_ret = details_write_message(details_comp,
+                               msgs[i]);
+
+                       if (print_ret) {
+                               for (; i < count; i++) {
+                                       /* Put all remaining messages */
+                                       bt_message_put_ref(msgs[i]);
+                               }
+
+                               ret = BT_SELF_COMPONENT_STATUS_ERROR;
+                               goto end;
+                       }
+
+                       /* Print output buffer to standard output and flush */
+                       if (details_comp->str->len > 0) {
+                               printf("%s", details_comp->str->str);
+                               fflush(stdout);
+                               details_comp->printed_something = true;
+                       }
+
+                       /* Put this message */
+                       bt_message_put_ref(msgs[i]);
+               }
+
+               break;
+       case BT_MESSAGE_ITERATOR_STATUS_AGAIN:
+               ret = BT_SELF_COMPONENT_STATUS_AGAIN;
+               goto end;
+       case BT_MESSAGE_ITERATOR_STATUS_END:
+               ret = BT_SELF_COMPONENT_STATUS_END;
+               goto end;
+       case BT_MESSAGE_ITERATOR_STATUS_ERROR:
+               ret = BT_SELF_COMPONENT_STATUS_ERROR;
+               goto end;
+       case BT_MESSAGE_ITERATOR_STATUS_NOMEM:
+               ret = BT_SELF_COMPONENT_STATUS_NOMEM;
+               goto end;
+       default:
+               abort();
+       }
+
+end:
+       return ret;
+}
diff --git a/src/plugins/text/details/details.h b/src/plugins/text/details/details.h
new file mode 100644 (file)
index 0000000..41f6acf
--- /dev/null
@@ -0,0 +1,182 @@
+#ifndef BABELTRACE_PLUGINS_TEXT_DETAILS_DETAILS_H
+#define BABELTRACE_PLUGINS_TEXT_DETAILS_DETAILS_H
+
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <glib.h>
+#include <babeltrace2/babeltrace.h>
+#include <stdbool.h>
+
+/*
+ * This structure contains a hash table which maps trace IR stream class
+ * and event class addresses to whether or not they have been printed
+ * already during the lifetime of the component.
+ *
+ * It is safe to keep the addresses (weak references) in this hash table
+ * as long as the trace class to which the structure is associated
+ * exists because it's not possible to remove stream classes from a
+ * trace class and event classes from a stream class.
+ */
+struct details_trace_class_meta {
+       /*
+        * Stream class or event class address (`const void *`) ->
+        * `guint` (as a pointer)
+        *
+        * This acts as a set in fact; we don't care about the values,
+        * but we put 1 so that we can use g_hash_table_lookup() to know
+        * whether or not the hash table contains a given key
+        * (g_hash_table_lookup() returns `NULL` when not found, but
+        * also when the value is `NULL`).
+        */
+       GHashTable *objects;
+
+       /*
+        * Trace class destruction listener ID (`UINT64_C(-1)` if
+        * there's no listener ID.
+        */
+       uint64_t tc_destruction_listener_id;
+};
+
+/*
+ * An entry of the `traces` hash table of a
+ * `struct details_comp` structure.
+ */
+struct details_trace {
+       /* Unique ID of this trace within the lifetime of the component */
+       uint64_t unique_id;
+
+       /*
+        * Trace destruction listener ID (`UINT64_C(-1)` if there's no
+        * listener ID.
+        */
+       uint64_t trace_destruction_listener_id;
+};
+
+/* A `sink.text.details` component */
+struct details_comp {
+       /* Component's configuration */
+       struct {
+               /* Write metadata objects */
+               bool with_meta;
+
+               /*
+                * Compact mode: each line is a single message, and
+                * there are no extended message properties and
+                * event/packet fields. `with_meta` can still be true in
+                * compact mode, printing the full metadata objects, but
+                * making the messages compact.
+                */
+               bool compact;
+
+               /* Colorize output */
+               bool with_color;
+
+               /* Write message's time */
+               bool with_time;
+
+               /* Write trace class's name */
+               bool with_trace_class_name;
+
+               /* Write trace's name */
+               bool with_trace_name;
+
+               /* Write stream class's name */
+               bool with_stream_class_name;
+
+               /* Write stream's name */
+               bool with_stream_name;
+
+               /* Write UUID */
+               bool with_uuid;
+       } cfg;
+
+       /*
+        * `const bt_trace_class *` (weak) ->
+        * `struct details_trace_class_meta *` (owned by this)
+        *
+        * The key (trace class object) is weak. An entry is added, if
+        * `cfg.with_meta` above is true, when first encountering a
+        * trace class. An entry is removed when a trace class is
+        * destroyed or when the component is finalized.
+        */
+       GHashTable *meta;
+
+       /*
+        * `const bt_trace *` (weak) ->
+        * `struct details_trace *` (owner by this)
+        *
+        * This hash table associates a trace object to a unique ID
+        * within the lifetime of this component. This is used to easily
+        * follow the messages of a given trace/stream when reading the
+        * text output of the component. We cannot use the actual stream
+        * ID properties for this because many streams can share the
+        * same ID (with different stream classes or different traces).
+        *
+        * When adding an entry, the unique ID to use is
+        * `next_unique_trace_id`.
+        *
+        * An entry is added when first encountering a trace. An entry
+        * is removed when a trace is destroyed or when the component is
+        * finalized.
+        */
+       GHashTable *traces;
+       uint32_t next_unique_trace_id;
+
+       /* Upstream message iterator */
+       bt_self_component_port_input_message_iterator *msg_iter;
+
+       /*
+        * True if this component printed something. This is used to
+        * prepend a newline to the next message string instead of
+        * appending it so that the last printed message is not followed
+        * with an empty line.
+        */
+       bool printed_something;
+
+       /* Current message's output buffer */
+       GString *str;
+};
+
+BT_HIDDEN
+bt_self_component_status details_init(
+               bt_self_component_sink *component,
+               const bt_value *params, void *init_method_data);
+
+BT_HIDDEN
+void details_finalize(bt_self_component_sink *component);
+
+BT_HIDDEN
+bt_self_component_status details_graph_is_configured(
+               bt_self_component_sink *comp);
+
+BT_HIDDEN
+bt_self_component_status details_consume(bt_self_component_sink *component);
+
+BT_HIDDEN
+void details_destroy_details_trace_class_meta(
+               struct details_trace_class_meta *details_trace_class_meta);
+
+BT_HIDDEN
+struct details_trace_class_meta *details_create_details_trace_class_meta(void);
+
+#endif /* BABELTRACE_PLUGINS_TEXT_DETAILS_DETAILS_H */
diff --git a/src/plugins/text/details/logging.c b/src/plugins/text/details/logging.c
new file mode 100644 (file)
index 0000000..6198267
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_OUTPUT_LEVEL bt_plugin_text_details_log_level
+#include "logging/log.h"
+
+BT_LOG_INIT_LOG_LEVEL(bt_plugin_text_details_log_level,
+       "BABELTRACE_SINK_TEXT_DETAILS_LOG_LEVEL");
diff --git a/src/plugins/text/details/logging.h b/src/plugins/text/details/logging.h
new file mode 100644 (file)
index 0000000..6c881b2
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef BABELTRACE_PLUGINS_TEXT_DETAILS_LOGGING_H
+#define BABELTRACE_PLUGINS_TEXT_DETAILS_LOGGING_H
+
+/*
+ * Copyright (c) 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_OUTPUT_LEVEL bt_plugin_text_details_log_level
+#include "logging/log.h"
+
+BT_LOG_LEVEL_EXTERN_SYMBOL(bt_plugin_text_details_log_level);
+
+#endif /* BABELTRACE_PLUGINS_TEXT_DETAILS_LOGGING_H */
diff --git a/src/plugins/text/details/obj-lifetime-mgmt.c b/src/plugins/text/details/obj-lifetime-mgmt.c
new file mode 100644 (file)
index 0000000..fc74904
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_TAG "PLUGIN-TEXT-DETAILS-SINK-OBJ-LIFETIME-MGMT"
+#include "logging.h"
+
+#include <babeltrace2/babeltrace.h>
+
+#include "common/common.h"
+#include "common/assert.h"
+#include "compat/glib.h"
+
+#include "details.h"
+#include "write.h"
+#include "obj-lifetime-mgmt.h"
+
+static
+void trace_class_destruction_listener(const bt_trace_class *tc, void *data)
+{
+       struct details_comp *details_comp = data;
+
+       BT_ASSERT(details_comp);
+       BT_ASSERT(details_comp->meta);
+
+       /* Remove from hash table, which also destroys the value */
+       g_hash_table_remove(details_comp->meta, tc);
+}
+
+static
+struct details_trace_class_meta *borrow_trace_class_meta(
+               struct details_write_ctx *ctx, const bt_trace_class *tc)
+{
+       struct details_trace_class_meta *details_tc_meta;
+
+       BT_ASSERT(ctx->details_comp->cfg.with_meta);
+       BT_ASSERT(ctx->details_comp->meta);
+       details_tc_meta = g_hash_table_lookup(ctx->details_comp->meta, tc);
+       if (!details_tc_meta) {
+               /* Not found: create one */
+               details_tc_meta = details_create_details_trace_class_meta();
+               if (!details_tc_meta) {
+                       goto error;
+               }
+
+               /* Register trace class destruction listener */
+               if (bt_trace_class_add_destruction_listener(tc,
+                               trace_class_destruction_listener,
+                               ctx->details_comp,
+                               &details_tc_meta->tc_destruction_listener_id)) {
+                       goto error;
+               }
+
+               /* Insert into hash table (becomes the owner) */
+               g_hash_table_insert(ctx->details_comp->meta, (void *) tc,
+                       details_tc_meta);
+       }
+
+       goto end;
+
+error:
+       details_destroy_details_trace_class_meta(details_tc_meta);
+       details_tc_meta = NULL;
+
+end:
+       return details_tc_meta;
+}
+
+BT_HIDDEN
+bool details_need_to_write_meta_object(struct details_write_ctx *ctx,
+               const bt_trace_class *tc, const void *obj)
+{
+       bool need_to_write;
+       struct details_trace_class_meta *details_tc_meta;
+
+       if (!ctx->details_comp->cfg.with_meta) {
+               need_to_write = false;
+               goto end;
+       }
+
+       BT_ASSERT(ctx->details_comp->meta);
+       details_tc_meta = g_hash_table_lookup(ctx->details_comp->meta, tc);
+       BT_ASSERT(details_tc_meta);
+       need_to_write =
+               g_hash_table_lookup(details_tc_meta->objects, obj) == NULL;
+
+end:
+       return need_to_write;
+}
+
+BT_HIDDEN
+void details_did_write_meta_object(struct details_write_ctx *ctx,
+               const bt_trace_class *tc, const void *obj)
+{
+       struct details_trace_class_meta *details_tc_meta;
+
+       BT_ASSERT(ctx->details_comp->cfg.with_meta);
+       details_tc_meta = borrow_trace_class_meta(ctx, tc);
+       BT_ASSERT(details_tc_meta);
+       g_hash_table_insert(details_tc_meta->objects, (gpointer) obj,
+               GUINT_TO_POINTER(1));
+}
+
+BT_HIDDEN
+bool details_need_to_write_trace_class(struct details_write_ctx *ctx,
+               const bt_trace_class *tc)
+{
+       struct details_trace_class_meta *details_tc_meta;
+       bool need_to_write;
+
+       if (!ctx->details_comp->cfg.with_meta) {
+               need_to_write = false;
+               goto end;
+       }
+
+       BT_ASSERT(ctx->details_comp->meta);
+       details_tc_meta = g_hash_table_lookup(ctx->details_comp->meta, tc);
+       need_to_write = details_tc_meta == NULL;
+
+end:
+       return need_to_write;
+}
+
+BT_HIDDEN
+int details_did_write_trace_class(struct details_write_ctx *ctx,
+               const bt_trace_class *tc)
+{
+       int ret = 0;
+       struct details_trace_class_meta *details_tc_meta;
+
+       BT_ASSERT(ctx->details_comp->cfg.with_meta);
+
+       /* borrow_trace_class_meta() creates an entry if none exists */
+       details_tc_meta = borrow_trace_class_meta(ctx, tc);
+       if (!details_tc_meta) {
+               ret = -1;
+       }
+
+       return ret;
+}
+
+static
+void trace_destruction_listener(const bt_trace *trace, void *data)
+{
+       struct details_comp *details_comp = data;
+
+       BT_ASSERT(details_comp);
+       BT_ASSERT(details_comp->traces);
+
+       /* Remove from hash table, which also destroys the value */
+       g_hash_table_remove(details_comp->traces, trace);
+}
+
+static
+struct details_trace *create_details_trace(uint64_t unique_id)
+{
+       struct details_trace *details_trace = g_new0(struct details_trace, 1);
+
+       if (!details_trace) {
+               goto end;
+       }
+
+       details_trace->unique_id = unique_id;
+       details_trace->trace_destruction_listener_id = UINT64_C(-1);
+
+end:
+       return details_trace;
+}
+
+
+BT_HIDDEN
+int details_trace_unique_id(struct details_write_ctx *ctx,
+               const bt_trace *trace, uint64_t *unique_id)
+{
+       int ret = 0;
+       struct details_trace *details_trace = NULL;
+
+       BT_ASSERT(unique_id);
+       BT_ASSERT(ctx->details_comp->traces);
+       if (!bt_g_hash_table_contains(ctx->details_comp->traces,
+                       trace)) {
+               /* Not found: create one */
+               *unique_id = ctx->details_comp->next_unique_trace_id;
+               details_trace = create_details_trace(*unique_id);
+               if (!details_trace) {
+                       goto error;
+               }
+
+               ctx->details_comp->next_unique_trace_id++;
+
+               /* Register trace destruction listener if there's none */
+               if (bt_trace_add_destruction_listener(trace,
+                               trace_destruction_listener,
+                               ctx->details_comp,
+                               &details_trace->trace_destruction_listener_id)) {
+                       goto error;
+               }
+
+               BT_ASSERT(details_trace->trace_destruction_listener_id !=
+                       UINT64_C(-1));
+
+               /* Move to hash table */
+               g_hash_table_insert(ctx->details_comp->traces, (gpointer) trace,
+                       details_trace);
+               details_trace = NULL;
+       } else {
+               /* Found */
+               details_trace = g_hash_table_lookup(
+                       ctx->details_comp->traces, trace);
+               *unique_id = details_trace->unique_id;
+               details_trace = NULL;
+       }
+
+       goto end;
+
+error:
+       ret = -1;
+
+end:
+       if (details_trace) {
+               g_free(details_trace);
+       }
+
+       return ret;
+}
diff --git a/src/plugins/text/details/obj-lifetime-mgmt.h b/src/plugins/text/details/obj-lifetime-mgmt.h
new file mode 100644 (file)
index 0000000..163f3e8
--- /dev/null
@@ -0,0 +1,71 @@
+#ifndef BABELTRACE_PLUGINS_TEXT_DETAILS_OBJ_LIFETIME_MGMT_H
+#define BABELTRACE_PLUGINS_TEXT_DETAILS_OBJ_LIFETIME_MGMT_H
+
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <glib.h>
+#include <babeltrace2/babeltrace.h>
+#include <stdbool.h>
+
+#include "details.h"
+#include "write.h"
+
+/*
+ * Returns whether or not stream class or event class `obj`, which
+ * belongs to `tc`, needs to be written.
+ */
+BT_HIDDEN
+bool details_need_to_write_meta_object(struct details_write_ctx *ctx,
+               const bt_trace_class *tc, const void *obj);
+
+/*
+ * Marks stream class or event class `obj`, which belongs to `tc`, as
+ * written.
+ */
+BT_HIDDEN
+void details_did_write_meta_object(struct details_write_ctx *ctx,
+               const bt_trace_class *tc, const void *obj);
+
+/*
+ * Returns whether or not trace class `tc` needs to be written.
+ */
+BT_HIDDEN
+bool details_need_to_write_trace_class(struct details_write_ctx *ctx,
+               const bt_trace_class *tc);
+
+/*
+ * Marks trace class `tc` as written.
+ */
+BT_HIDDEN
+int details_did_write_trace_class(struct details_write_ctx *ctx,
+               const bt_trace_class *tc);
+
+/*
+ * Writes the unique trace ID of `trace` to `*unique_id`, allocating a
+ * new unique ID if none exists.
+ */
+BT_HIDDEN
+int details_trace_unique_id(struct details_write_ctx *ctx,
+               const bt_trace *trace, uint64_t *unique_id);
+
+#endif /* BABELTRACE_PLUGINS_TEXT_DETAILS_OBJ_LIFETIME_MGMT_H */
diff --git a/src/plugins/text/details/write.c b/src/plugins/text/details/write.c
new file mode 100644 (file)
index 0000000..8fabe0b
--- /dev/null
@@ -0,0 +1,2219 @@
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define BT_LOG_TAG "PLUGIN-TEXT-DETAILS-SINK-WRITE"
+#include "logging.h"
+
+#include <babeltrace2/babeltrace.h>
+
+#include "common/assert.h"
+#include "common/common.h"
+#include "details.h"
+#include "write.h"
+#include "obj-lifetime-mgmt.h"
+#include "colors.h"
+
+static inline
+const char *plural(uint64_t value)
+{
+       return value == 1 ? "" : "s";
+}
+
+static inline
+void incr_indent_by(struct details_write_ctx *ctx, unsigned int value)
+{
+       BT_ASSERT(ctx);
+       ctx->indent_level += value;
+}
+
+static inline
+void incr_indent(struct details_write_ctx *ctx)
+{
+       incr_indent_by(ctx, 2);
+}
+
+static inline
+void decr_indent_by(struct details_write_ctx *ctx, unsigned int value)
+{
+       BT_ASSERT(ctx);
+       BT_ASSERT(ctx->indent_level >= value);
+       ctx->indent_level -= value;
+}
+
+static inline
+void decr_indent(struct details_write_ctx *ctx)
+{
+       decr_indent_by(ctx, 2);
+}
+
+static inline
+void format_uint(char *buf, uint64_t value, unsigned int base)
+{
+       const char *spec = "%" PRIu64;
+       char *buf_start = buf;
+       unsigned int digits_per_group = 3;
+       char sep = ',';
+       bool sep_digits = true;
+
+       switch (base) {
+       case 2:
+       case 16:
+               /* TODO: Support binary format */
+               spec = "%" PRIx64;
+               strcpy(buf, "0x");
+               buf_start = buf + 2;
+               digits_per_group = 4;
+               sep = ':';
+               break;
+       case 8:
+               spec = "%" PRIo64;
+               strcpy(buf, "0");
+               buf_start = buf + 1;
+               sep = ':';
+               break;
+       case 10:
+               if (value <= 9999) {
+                       /*
+                        * Do not insert digit separators for numbers
+                        * under 10,000 as it looks weird.
+                        */
+                       sep_digits = false;
+               }
+
+               break;
+       default:
+               abort();
+       }
+
+       sprintf(buf_start, spec, value);
+
+       if (sep_digits) {
+               bt_common_sep_digits(buf_start, digits_per_group, sep);
+       }
+}
+
+static inline
+void format_int(char *buf, int64_t value, unsigned int base)
+{
+       const char *spec = "%" PRIu64;
+       char *buf_start = buf;
+       unsigned int digits_per_group = 3;
+       char sep = ',';
+       bool sep_digits = true;
+       uint64_t abs_value = value < 0 ? (uint64_t) -value : (uint64_t) value;
+
+       if (value < 0) {
+               buf[0] = '-';
+               buf_start++;
+       }
+
+       switch (base) {
+       case 2:
+       case 16:
+               /* TODO: Support binary format */
+               spec = "%" PRIx64;
+               strcpy(buf_start, "0x");
+               buf_start += 2;
+               digits_per_group = 4;
+               sep = ':';
+               break;
+       case 8:
+               spec = "%" PRIo64;
+               strcpy(buf_start, "0");
+               buf_start++;
+               sep = ':';
+               break;
+       case 10:
+               if (value >= -9999 && value <= 9999) {
+                       /*
+                        * Do not insert digit separators for numbers
+                        * over -10,000 and under 10,000 as it looks
+                        * weird.
+                        */
+                       sep_digits = false;
+               }
+
+               break;
+       default:
+               abort();
+       }
+
+       sprintf(buf_start, spec, abs_value);
+
+       if (sep_digits) {
+               bt_common_sep_digits(buf_start, digits_per_group, sep);
+       }
+}
+
+static inline
+void write_nl(struct details_write_ctx *ctx)
+{
+       BT_ASSERT(ctx);
+       g_string_append_c(ctx->str, '\n');
+}
+
+static inline
+void write_sp(struct details_write_ctx *ctx)
+{
+       BT_ASSERT(ctx);
+       g_string_append_c(ctx->str, ' ');
+}
+
+static inline
+void write_indent(struct details_write_ctx *ctx)
+{
+       uint64_t i;
+
+       BT_ASSERT(ctx);
+
+       for (i = 0; i < ctx->indent_level; i++) {
+               write_sp(ctx);
+       }
+}
+
+static inline
+void write_compound_member_name(struct details_write_ctx *ctx, const char *name)
+{
+       write_indent(ctx);
+       g_string_append_printf(ctx->str, "%s%s%s:",
+               color_fg_cyan(ctx), name, color_reset(ctx));
+}
+
+static inline
+void write_array_index(struct details_write_ctx *ctx, uint64_t index)
+{
+       char buf[32];
+
+       write_indent(ctx);
+       format_uint(buf, index, 10);
+       g_string_append_printf(ctx->str, "%s[%s]%s:",
+               color_fg_cyan(ctx), buf, color_reset(ctx));
+}
+
+static inline
+void write_obj_type_name(struct details_write_ctx *ctx, const char *name)
+{
+       g_string_append_printf(ctx->str, "%s%s%s%s",
+               color_fg_yellow(ctx), color_bold(ctx), name, color_reset(ctx));
+}
+
+static inline
+void write_prop_name(struct details_write_ctx *ctx, const char *prop_name)
+{
+       g_string_append_printf(ctx->str, "%s%s%s",
+               color_fg_magenta(ctx), prop_name, color_reset(ctx));
+}
+
+static inline
+void write_str_prop_value(struct details_write_ctx *ctx, const char *value)
+{
+       g_string_append_printf(ctx->str, "%s%s%s",
+               color_bold(ctx), value, color_reset(ctx));
+}
+
+static inline
+void write_uint_str_prop_value(struct details_write_ctx *ctx, const char *value)
+{
+       write_str_prop_value(ctx, value);
+}
+
+static inline
+void write_uint_prop_value(struct details_write_ctx *ctx, uint64_t value)
+{
+       char buf[32];
+
+       format_uint(buf, value, 10);
+       write_uint_str_prop_value(ctx, buf);
+}
+
+static inline
+void write_int_prop_value(struct details_write_ctx *ctx, int64_t value)
+{
+       char buf[32];
+
+       format_int(buf, value, 10);
+       write_uint_str_prop_value(ctx, buf);
+}
+
+static inline
+void write_float_prop_value(struct details_write_ctx *ctx, double value)
+{
+       g_string_append_printf(ctx->str, "%s%f%s",
+               color_bold(ctx), value, color_reset(ctx));
+}
+
+static inline
+void write_str_prop_line(struct details_write_ctx *ctx, const char *prop_name,
+               const char *prop_value)
+{
+       BT_ASSERT(prop_value);
+       write_indent(ctx);
+       write_prop_name(ctx, prop_name);
+       g_string_append(ctx->str, ": ");
+       write_str_prop_value(ctx, prop_value);
+       write_nl(ctx);
+}
+
+static inline
+void write_uint_prop_line(struct details_write_ctx *ctx, const char *prop_name,
+               uint64_t prop_value)
+{
+       write_indent(ctx);
+       write_prop_name(ctx, prop_name);
+       g_string_append(ctx->str, ": ");
+       write_uint_prop_value(ctx, prop_value);
+       write_nl(ctx);
+}
+
+static inline
+void write_int_prop_line(struct details_write_ctx *ctx, const char *prop_name,
+               int64_t prop_value)
+{
+       write_indent(ctx);
+       write_prop_name(ctx, prop_name);
+       g_string_append(ctx->str, ": ");
+       write_int_prop_value(ctx, prop_value);
+       write_nl(ctx);
+}
+
+static inline
+void write_int_str_prop_value(struct details_write_ctx *ctx, const char *value)
+{
+       write_str_prop_value(ctx, value);
+}
+
+static inline
+void write_bool_prop_line(struct details_write_ctx *ctx, const char *prop_name,
+               bt_bool prop_value)
+{
+       const char *str;
+
+       write_indent(ctx);
+       write_prop_name(ctx, prop_name);
+       g_string_append_printf(ctx->str, ": %s", color_bold(ctx));
+
+       if (prop_value) {
+               g_string_append(ctx->str, color_fg_green(ctx));
+               str = "Yes";
+       } else {
+               g_string_append(ctx->str, color_fg_red(ctx));
+               str = "No";
+       }
+
+       g_string_append_printf(ctx->str, "%s%s\n", str, color_reset(ctx));
+}
+
+static inline
+void write_uuid_prop_line(struct details_write_ctx *ctx, const char *prop_name,
+               bt_uuid uuid)
+{
+       BT_ASSERT(uuid);
+       write_indent(ctx);
+       write_prop_name(ctx, prop_name);
+       g_string_append_printf(ctx->str,
+               ": %s%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x%s\n",
+               color_bold(ctx),
+               (unsigned int) uuid[0],
+               (unsigned int) uuid[1],
+               (unsigned int) uuid[2],
+               (unsigned int) uuid[3],
+               (unsigned int) uuid[4],
+               (unsigned int) uuid[5],
+               (unsigned int) uuid[6],
+               (unsigned int) uuid[7],
+               (unsigned int) uuid[8],
+               (unsigned int) uuid[9],
+               (unsigned int) uuid[10],
+               (unsigned int) uuid[11],
+               (unsigned int) uuid[12],
+               (unsigned int) uuid[13],
+               (unsigned int) uuid[14],
+               (unsigned int) uuid[15],
+               color_reset(ctx));
+}
+
+static
+void write_int_field_class_props(struct details_write_ctx *ctx,
+               const bt_field_class *fc, bool close)
+{
+       g_string_append_printf(ctx->str, "(%s%" PRIu64 "-bit%s, Base ",
+               color_bold(ctx),
+               bt_field_class_integer_get_field_value_range(fc),
+               color_reset(ctx));
+
+       switch (bt_field_class_integer_get_preferred_display_base(fc)) {
+       case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_BINARY:
+               write_uint_prop_value(ctx, 2);
+               break;
+       case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_OCTAL:
+               write_uint_prop_value(ctx, 8);
+               break;
+       case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL:
+               write_uint_prop_value(ctx, 10);
+               break;
+       case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL:
+               write_uint_prop_value(ctx, 16);
+               break;
+       default:
+               abort();
+       }
+
+       if (close) {
+               g_string_append(ctx->str, ")");
+       }
+}
+
+struct enum_field_class_mapping_range {
+       union {
+               uint64_t u;
+               int64_t i;
+       } lower;
+
+       union {
+               uint64_t u;
+               int64_t i;
+       } upper;
+};
+
+struct enum_field_class_mapping {
+       /* Weak */
+       const char *label;
+
+       /* Array of `struct enum_field_class_mapping_range` */
+       GArray *ranges;
+};
+
+static
+gint compare_enum_field_class_mappings(struct enum_field_class_mapping **a,
+               struct enum_field_class_mapping **b)
+{
+       return strcmp((*a)->label, (*b)->label);
+}
+
+static
+gint compare_enum_field_class_mapping_ranges_signed(
+               struct enum_field_class_mapping_range *a,
+               struct enum_field_class_mapping_range *b)
+{
+
+       if (a->lower.i < b->lower.i) {
+               return -1;
+       } else if (a->lower.i > b->lower.i) {
+               return 1;
+       } else {
+               if (a->upper.i < b->upper.i) {
+                       return -1;
+               } else if (a->upper.i > b->upper.i) {
+                       return 1;
+               } else {
+                       return 0;
+               }
+       }
+}
+
+static
+gint compare_enum_field_class_mapping_ranges_unsigned(
+               struct enum_field_class_mapping_range *a,
+               struct enum_field_class_mapping_range *b)
+{
+       if (a->lower.u < b->lower.u) {
+               return -1;
+       } else if (a->lower.u > b->lower.u) {
+               return 1;
+       } else {
+               if (a->upper.u < b->upper.u) {
+                       return -1;
+               } else if (a->upper.u > b->upper.u) {
+                       return 1;
+               } else {
+                       return 0;
+               }
+       }
+}
+
+static
+void destroy_enum_field_class_mapping(struct enum_field_class_mapping *mapping)
+{
+       if (mapping->ranges) {
+               g_array_free(mapping->ranges, TRUE);
+               mapping->ranges = NULL;
+       }
+
+       g_free(mapping);
+}
+
+static
+void write_enum_field_class_mapping_range(struct details_write_ctx *ctx,
+               struct enum_field_class_mapping_range *range, bool is_signed)
+{
+       g_string_append(ctx->str, "[");
+
+       if (is_signed) {
+               write_int_prop_value(ctx, range->lower.i);
+       } else {
+               write_int_prop_value(ctx, range->lower.u);
+       }
+
+       g_string_append(ctx->str, ", ");
+
+       if (is_signed) {
+               write_int_prop_value(ctx, range->upper.i);
+       } else {
+               write_int_prop_value(ctx, range->upper.u);
+       }
+
+       g_string_append(ctx->str, "]");
+}
+
+static
+void write_enum_field_class_mappings(struct details_write_ctx *ctx,
+               const bt_field_class *fc)
+{
+       GPtrArray *mappings;
+       uint64_t i;
+       uint64_t range_i;
+       bool is_signed = bt_field_class_get_type(fc) ==
+               BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION;
+
+       mappings = g_ptr_array_new_with_free_func(
+               (GDestroyNotify) destroy_enum_field_class_mapping);
+       BT_ASSERT(mappings);
+
+       /*
+        * Copy field class's mappings to our own arrays and structures
+        * to sort them.
+        */
+       for (i = 0; i < bt_field_class_enumeration_get_mapping_count(fc); i++) {
+               const void *fc_mapping;
+               struct enum_field_class_mapping *mapping = g_new0(
+                       struct enum_field_class_mapping, 1);
+
+               BT_ASSERT(mapping);
+               mapping->ranges = g_array_new(FALSE, TRUE,
+                       sizeof(struct enum_field_class_mapping_range));
+               BT_ASSERT(mapping->ranges);
+
+               if (is_signed) {
+                       fc_mapping = bt_field_class_signed_enumeration_borrow_mapping_by_index_const(
+                               fc, i);
+               } else {
+                       fc_mapping = bt_field_class_unsigned_enumeration_borrow_mapping_by_index_const(
+                               fc, i);
+               }
+
+               mapping->label = bt_field_class_enumeration_mapping_get_label(
+                       bt_field_class_signed_enumeration_mapping_as_mapping_const(
+                               fc_mapping));
+
+               for (range_i = 0;
+                               range_i < bt_field_class_enumeration_mapping_get_range_count(
+                                       bt_field_class_signed_enumeration_mapping_as_mapping_const(fc_mapping));
+                               range_i++) {
+                       struct enum_field_class_mapping_range range;
+
+                       if (is_signed) {
+                               bt_field_class_signed_enumeration_mapping_get_range_by_index(
+                                       fc_mapping, range_i,
+                                       &range.lower.i, &range.upper.i);
+                       } else {
+                               bt_field_class_unsigned_enumeration_mapping_get_range_by_index(
+                                       fc_mapping, range_i,
+                                       &range.lower.u, &range.upper.u);
+                       }
+
+                       g_array_append_val(mapping->ranges, range);
+               }
+
+               g_ptr_array_add(mappings, mapping);
+       }
+
+       /* Sort mappings, and for each mapping, sort ranges */
+       g_ptr_array_sort(mappings,
+               (GCompareFunc) compare_enum_field_class_mappings);
+
+       for (i = 0; i < mappings->len; i++) {
+               struct enum_field_class_mapping *mapping = mappings->pdata[i];
+
+               if (is_signed) {
+                       g_array_sort(mapping->ranges,
+                               (GCompareFunc)
+                                       compare_enum_field_class_mapping_ranges_signed);
+               } else {
+                       g_array_sort(mapping->ranges,
+                               (GCompareFunc)
+                                       compare_enum_field_class_mapping_ranges_unsigned);
+               }
+       }
+
+       /* Write mappings */
+       for (i = 0; i < mappings->len; i++) {
+               struct enum_field_class_mapping *mapping = mappings->pdata[i];
+
+               write_nl(ctx);
+               write_compound_member_name(ctx, mapping->label);
+
+               if (mapping->ranges->len == 1) {
+                       /* Single one: write on same line */
+                       write_sp(ctx);
+                       write_enum_field_class_mapping_range(ctx,
+                               &g_array_index(mapping->ranges,
+                                       struct enum_field_class_mapping_range,
+                                       0), is_signed);
+                       continue;
+               }
+
+               incr_indent(ctx);
+
+               for (range_i = 0; range_i < mapping->ranges->len; range_i++) {
+                       write_nl(ctx);
+                       write_indent(ctx);
+                       write_enum_field_class_mapping_range(ctx,
+                               &g_array_index(mapping->ranges,
+                                       struct enum_field_class_mapping_range,
+                                       range_i), is_signed);
+               }
+
+               decr_indent(ctx);
+       }
+
+       g_ptr_array_free(mappings, TRUE);
+}
+
+static
+void write_field_path(struct details_write_ctx *ctx,
+               const bt_field_path *field_path)
+{
+       uint64_t i;
+
+       g_string_append_c(ctx->str, '[');
+
+       switch (bt_field_path_get_root_scope(field_path)) {
+       case BT_SCOPE_PACKET_CONTEXT:
+               write_str_prop_value(ctx, "Packet context");
+               break;
+       case BT_SCOPE_EVENT_COMMON_CONTEXT:
+               write_str_prop_value(ctx, "Event common context");
+               break;
+       case BT_SCOPE_EVENT_SPECIFIC_CONTEXT:
+               write_str_prop_value(ctx, "Event specific context");
+               break;
+       case BT_SCOPE_EVENT_PAYLOAD:
+               write_str_prop_value(ctx, "Event payload");
+               break;
+       default:
+               abort();
+       }
+
+       g_string_append(ctx->str, ": ");
+
+       for (i = 0; i < bt_field_path_get_item_count(field_path); i++) {
+               const bt_field_path_item *fp_item =
+                       bt_field_path_borrow_item_by_index_const(field_path, i);
+
+               if (i != 0) {
+                       g_string_append(ctx->str, ", ");
+               }
+
+               switch (bt_field_path_item_get_type(fp_item)) {
+               case BT_FIELD_PATH_ITEM_TYPE_INDEX:
+                       write_uint_prop_value(ctx,
+                               bt_field_path_item_index_get_index(fp_item));
+                       break;
+               case BT_FIELD_PATH_ITEM_TYPE_CURRENT_ARRAY_ELEMENT:
+                       write_str_prop_value(ctx, "<current>");
+                       break;
+               default:
+                       abort();
+               }
+       }
+
+       g_string_append_c(ctx->str, ']');
+}
+
+static
+void write_field_class(struct details_write_ctx *ctx, const bt_field_class *fc,
+               const char *name)
+{
+       uint64_t i;
+       const char *type;
+       bt_field_class_type fc_type = bt_field_class_get_type(fc);
+
+       /* Write field class's name */
+       if (name) {
+               write_compound_member_name(ctx, name);
+               write_sp(ctx);
+       }
+
+       /* Write field class's type */
+       switch (fc_type) {
+       case BT_FIELD_CLASS_TYPE_UNSIGNED_INTEGER:
+               type = "Unsigned integer";
+               break;
+       case BT_FIELD_CLASS_TYPE_SIGNED_INTEGER:
+               type = "Signed integer";
+               break;
+       case BT_FIELD_CLASS_TYPE_UNSIGNED_ENUMERATION:
+               type = "Unsigned enumeration";
+               break;
+       case BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION:
+               type = "Signed enumeration";
+               break;
+       case BT_FIELD_CLASS_TYPE_REAL:
+               type = "Real";
+               break;
+       case BT_FIELD_CLASS_TYPE_STRING:
+               type = "String";
+               break;
+       case BT_FIELD_CLASS_TYPE_STRUCTURE:
+               type = "Structure";
+               break;
+       case BT_FIELD_CLASS_TYPE_STATIC_ARRAY:
+               type = "Static array";
+               break;
+       case BT_FIELD_CLASS_TYPE_DYNAMIC_ARRAY:
+               type = "Dynamic array";
+               break;
+       case BT_FIELD_CLASS_TYPE_VARIANT:
+               type = "Variant";
+               break;
+       default:
+               abort();
+       }
+
+       g_string_append_printf(ctx->str, "%s%s%s",
+               color_fg_blue(ctx), type, color_reset(ctx));
+
+       /* Write field class's properties */
+       switch (fc_type) {
+       case BT_FIELD_CLASS_TYPE_UNSIGNED_INTEGER:
+       case BT_FIELD_CLASS_TYPE_SIGNED_INTEGER:
+               write_sp(ctx);
+               write_int_field_class_props(ctx, fc, true);
+               break;
+       case BT_FIELD_CLASS_TYPE_UNSIGNED_ENUMERATION:
+       case BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION:
+       {
+               uint64_t mapping_count =
+                       bt_field_class_enumeration_get_mapping_count(fc);
+
+               write_sp(ctx);
+               write_int_field_class_props(ctx, fc, false);
+               g_string_append(ctx->str, ", ");
+               write_uint_prop_value(ctx, mapping_count);
+               g_string_append_printf(ctx->str, " mapping%s)",
+                       plural(mapping_count));
+
+               if (mapping_count > 0) {
+                       g_string_append_c(ctx->str, ':');
+                       incr_indent(ctx);
+                       write_enum_field_class_mappings(ctx, fc);
+                       decr_indent(ctx);
+               }
+
+               break;
+       }
+       case BT_FIELD_CLASS_TYPE_REAL:
+               if (bt_field_class_real_is_single_precision(fc)) {
+                       g_string_append(ctx->str, " (Single precision)");
+               } else {
+                       g_string_append(ctx->str, " (Double precision)");
+               }
+
+               break;
+       case BT_FIELD_CLASS_TYPE_STRUCTURE:
+       {
+               uint64_t member_count =
+                       bt_field_class_structure_get_member_count(fc);
+
+               g_string_append(ctx->str, " (");
+               write_uint_prop_value(ctx, member_count);
+               g_string_append_printf(ctx->str, " member%s)",
+                       plural(member_count));
+
+               if (member_count > 0) {
+                       g_string_append_c(ctx->str, ':');
+                       incr_indent(ctx);
+
+                       for (i = 0; i < member_count; i++) {
+                               const bt_field_class_structure_member *member =
+                                       bt_field_class_structure_borrow_member_by_index_const(
+                                               fc, i);
+
+                               write_nl(ctx);
+                               write_field_class(ctx,
+                                       bt_field_class_structure_member_borrow_field_class_const(member),
+                                       bt_field_class_structure_member_get_name(member));
+                       }
+
+                       decr_indent(ctx);
+               }
+
+               break;
+       }
+       case BT_FIELD_CLASS_TYPE_STATIC_ARRAY:
+       case BT_FIELD_CLASS_TYPE_DYNAMIC_ARRAY:
+               if (fc_type == BT_FIELD_CLASS_TYPE_STATIC_ARRAY) {
+                       g_string_append(ctx->str, " (Length ");
+                       write_uint_prop_value(ctx,
+                               bt_field_class_static_array_get_length(fc));
+                       g_string_append_c(ctx->str, ')');
+               } else {
+                       const bt_field_path *length_field_path =
+                               bt_field_class_dynamic_array_borrow_length_field_path_const(
+                                       fc);
+
+                       if (length_field_path) {
+                               g_string_append(ctx->str, " (Length field path ");
+                               write_field_path(ctx, length_field_path);
+                               g_string_append_c(ctx->str, ')');
+                       }
+               }
+
+               g_string_append_c(ctx->str, ':');
+               write_nl(ctx);
+               incr_indent(ctx);
+               write_field_class(ctx,
+                       bt_field_class_array_borrow_element_field_class_const(fc),
+                       "Element");
+               decr_indent(ctx);
+               break;
+       case BT_FIELD_CLASS_TYPE_VARIANT:
+       {
+               uint64_t option_count =
+                       bt_field_class_variant_get_option_count(fc);
+               const bt_field_path *sel_field_path =
+                       bt_field_class_variant_borrow_selector_field_path_const(
+                               fc);
+
+               g_string_append(ctx->str, " (");
+               write_uint_prop_value(ctx, option_count);
+               g_string_append_printf(ctx->str, " option%s, ",
+                       plural(option_count));
+
+               if (sel_field_path) {
+                       g_string_append(ctx->str, "Selector field path ");
+                       write_field_path(ctx, sel_field_path);
+               }
+
+               g_string_append_c(ctx->str, ')');
+
+               if (option_count > 0) {
+                       g_string_append_c(ctx->str, ':');
+                       incr_indent(ctx);
+
+                       for (i = 0; i < option_count; i++) {
+                               const bt_field_class_variant_option *option =
+                                       bt_field_class_variant_borrow_option_by_index_const(
+                                               fc, i);
+
+                               write_nl(ctx);
+                               write_field_class(ctx,
+                                       bt_field_class_variant_option_borrow_field_class_const(option),
+                                       bt_field_class_variant_option_get_name(option));
+                       }
+
+                       decr_indent(ctx);
+               }
+
+               break;
+       }
+       default:
+               break;
+       }
+}
+
+static
+void write_root_field_class(struct details_write_ctx *ctx, const char *name,
+               const bt_field_class *fc)
+{
+       BT_ASSERT(name);
+       BT_ASSERT(fc);
+       write_indent(ctx);
+       write_prop_name(ctx, name);
+       g_string_append(ctx->str, ": ");
+       write_field_class(ctx, fc, NULL);
+       write_nl(ctx);
+}
+
+static
+void write_event_class(struct details_write_ctx *ctx, const bt_event_class *ec)
+{
+       const char *name = bt_event_class_get_name(ec);
+       const char *emf_uri;
+       const bt_field_class *fc;
+       bt_event_class_log_level log_level;
+
+       write_indent(ctx);
+       write_obj_type_name(ctx, "Event class");
+
+       /* Write name and ID */
+       if (name) {
+               g_string_append_printf(ctx->str, " `%s%s%s`",
+                       color_fg_green(ctx), name, color_reset(ctx));
+       }
+
+       g_string_append(ctx->str, " (ID ");
+       write_uint_prop_value(ctx, bt_event_class_get_id(ec));
+       g_string_append(ctx->str, "):\n");
+
+       /* Write properties */
+       incr_indent(ctx);
+
+       /* Write log level */
+       if (bt_event_class_get_log_level(ec, &log_level) ==
+                       BT_PROPERTY_AVAILABILITY_AVAILABLE) {
+               const char *ll_str = NULL;
+
+               switch (log_level) {
+               case BT_EVENT_CLASS_LOG_LEVEL_EMERGENCY:
+                       ll_str = "Emergency";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_ALERT:
+                       ll_str = "Alert";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_CRITICAL:
+                       ll_str = "Critical";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_ERROR:
+                       ll_str = "Error";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_WARNING:
+                       ll_str = "Warning";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_NOTICE:
+                       ll_str = "Notice";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_INFO:
+                       ll_str = "Info";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_SYSTEM:
+                       ll_str = "Debug (system)";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_PROGRAM:
+                       ll_str = "Debug (program)";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_PROCESS:
+                       ll_str = "Debug (process)";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_MODULE:
+                       ll_str = "Debug (module)";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_UNIT:
+                       ll_str = "Debug (unit)";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_FUNCTION:
+                       ll_str = "Debug (function)";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG_LINE:
+                       ll_str = "Debug (line)";
+                       break;
+               case BT_EVENT_CLASS_LOG_LEVEL_DEBUG:
+                       ll_str = "Debug";
+                       break;
+               default:
+                       abort();
+               }
+
+               write_str_prop_line(ctx, "Log level", ll_str);
+       }
+
+       /* Write EMF URI */
+       emf_uri = bt_event_class_get_emf_uri(ec);
+       if (emf_uri) {
+               write_str_prop_line(ctx, "EMF URI", emf_uri);
+       }
+
+       /* Write specific context field class */
+       fc = bt_event_class_borrow_specific_context_field_class_const(ec);
+       if (fc) {
+               write_root_field_class(ctx, "Specific context field class", fc);
+       }
+
+       /* Write payload field class */
+       fc = bt_event_class_borrow_payload_field_class_const(ec);
+       if (fc) {
+               write_root_field_class(ctx, "Payload field class", fc);
+       }
+
+       decr_indent(ctx);
+}
+
+static
+void write_clock_class_prop_lines(struct details_write_ctx *ctx,
+               const bt_clock_class *cc)
+{
+       int64_t offset_seconds;
+       uint64_t offset_cycles;
+       const char *str;
+
+       str = bt_clock_class_get_name(cc);
+       if (str) {
+               write_str_prop_line(ctx, "Name", str);
+       }
+
+       str = bt_clock_class_get_description(cc);
+       if (str) {
+               write_str_prop_line(ctx, "Description", str);
+       }
+
+       write_uint_prop_line(ctx, "Frequency (Hz)",
+               bt_clock_class_get_frequency(cc));
+       write_uint_prop_line(ctx, "Precision (cycles)",
+               bt_clock_class_get_precision(cc));
+       bt_clock_class_get_offset(cc, &offset_seconds, &offset_cycles);
+       write_int_prop_line(ctx, "Offset (s)", offset_seconds);
+       write_uint_prop_line(ctx, "Offset (cycles)", offset_cycles);
+       write_bool_prop_line(ctx, "Origin is Unix epoch",
+               bt_clock_class_origin_is_unix_epoch(cc));
+
+       if (ctx->details_comp->cfg.with_uuid) {
+               bt_uuid uuid = bt_clock_class_get_uuid(cc);
+
+               if (uuid) {
+                       write_uuid_prop_line(ctx, "UUID", uuid);
+               }
+       }
+}
+
+static
+gint compare_event_classes(const bt_event_class **a, const bt_event_class **b)
+{
+       uint64_t id_a = bt_event_class_get_id(*a);
+       uint64_t id_b = bt_event_class_get_id(*b);
+
+       if (id_a < id_b) {
+               return -1;
+       } else if (id_a > id_b) {
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
+static
+void write_stream_class(struct details_write_ctx *ctx,
+               const bt_stream_class *sc)
+{
+       const bt_field_class *fc;
+       GPtrArray *event_classes = g_ptr_array_new();
+       uint64_t i;
+
+       write_indent(ctx);
+       write_obj_type_name(ctx, "Stream class");
+
+       /* Write name and ID */
+       if (ctx->details_comp->cfg.with_stream_class_name) {
+               const char *name = bt_stream_class_get_name(sc);
+
+               if (name) {
+                       g_string_append(ctx->str, " `");
+                       write_str_prop_value(ctx, name);
+                       g_string_append(ctx->str, "`");
+               }
+       }
+
+       g_string_append(ctx->str, " (ID ");
+       write_uint_prop_value(ctx, bt_stream_class_get_id(sc));
+       g_string_append(ctx->str, "):\n");
+
+       /* Write properties */
+       incr_indent(ctx);
+
+       /* Write configuration */
+       write_bool_prop_line(ctx,
+               "Packets have beginning default clock snapshot",
+               bt_stream_class_packets_have_beginning_default_clock_snapshot(sc));
+       write_bool_prop_line(ctx,
+               "Packets have end default clock snapshot",
+               bt_stream_class_packets_have_end_default_clock_snapshot(sc));
+       write_bool_prop_line(ctx,
+               "Supports discarded events",
+               bt_stream_class_supports_discarded_events(sc));
+       write_bool_prop_line(ctx,
+               "Discarded events have default clock snapshots",
+               bt_stream_class_discarded_events_have_default_clock_snapshots(sc));
+       write_bool_prop_line(ctx,
+               "Supports discarded packets",
+               bt_stream_class_supports_discarded_packets(sc));
+       write_bool_prop_line(ctx,
+               "Discarded packets have default clock snapshots",
+               bt_stream_class_discarded_packets_have_default_clock_snapshots(sc));
+
+       /* Write default clock class */
+       if (bt_stream_class_borrow_default_clock_class_const(sc)) {
+               write_indent(ctx);
+               write_prop_name(ctx, "Default clock class");
+               g_string_append_c(ctx->str, ':');
+               write_nl(ctx);
+               incr_indent(ctx);
+               write_clock_class_prop_lines(ctx,
+                       bt_stream_class_borrow_default_clock_class_const(sc));
+               decr_indent(ctx);
+       }
+
+       fc = bt_stream_class_borrow_packet_context_field_class_const(sc);
+       if (fc) {
+               write_root_field_class(ctx, "Packet context field class", fc);
+       }
+
+       fc = bt_stream_class_borrow_event_common_context_field_class_const(sc);
+       if (fc) {
+               write_root_field_class(ctx, "Event common context field class",
+                       fc);
+       }
+
+       for (i = 0; i < bt_stream_class_get_event_class_count(sc); i++) {
+               g_ptr_array_add(event_classes,
+                       (gpointer) bt_stream_class_borrow_event_class_by_index_const(
+                               sc, i));
+       }
+
+       g_ptr_array_sort(event_classes, (GCompareFunc) compare_event_classes);
+
+       for (i = 0; i < event_classes->len; i++) {
+               write_event_class(ctx, event_classes->pdata[i]);
+       }
+
+       decr_indent(ctx);
+       g_ptr_array_free(event_classes, TRUE);
+}
+
+static
+gint compare_stream_classes(const bt_stream_class **a, const bt_stream_class **b)
+{
+       uint64_t id_a = bt_stream_class_get_id(*a);
+       uint64_t id_b = bt_stream_class_get_id(*b);
+
+       if (id_a < id_b) {
+               return -1;
+       } else if (id_a > id_b) {
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
+static
+gint compare_strings(const char **a, const char **b)
+{
+       return strcmp(*a, *b);
+}
+
+static
+void write_trace_class(struct details_write_ctx *ctx, const bt_trace_class *tc)
+{
+       GPtrArray *stream_classes = g_ptr_array_new();
+       GPtrArray *env_names = g_ptr_array_new();
+       uint64_t env_count;
+       uint64_t i;
+       bool printed_prop = false;
+
+       write_indent(ctx);
+       write_obj_type_name(ctx, "Trace class");
+
+       /* Write name */
+       if (ctx->details_comp->cfg.with_trace_class_name) {
+               const char *name = bt_trace_class_get_name(tc);
+
+               if (name) {
+                       g_string_append(ctx->str, " `");
+                       write_str_prop_value(ctx, name);
+                       g_string_append(ctx->str, "`");
+               }
+       }
+
+       /* Write properties */
+       incr_indent(ctx);
+
+       if (ctx->details_comp->cfg.with_uuid) {
+               bt_uuid uuid = bt_trace_class_get_uuid(tc);
+
+               if (uuid) {
+                       if (!printed_prop) {
+                               g_string_append(ctx->str, ":\n");
+                               printed_prop = true;
+                       }
+
+                       write_uuid_prop_line(ctx, "UUID", uuid);
+               }
+       }
+
+       /* Write environment */
+       env_count = bt_trace_class_get_environment_entry_count(tc);
+       if (env_count > 0) {
+               if (!printed_prop) {
+                       g_string_append(ctx->str, ":\n");
+                       printed_prop = true;
+               }
+
+               write_indent(ctx);
+               write_prop_name(ctx, "Environment");
+               g_string_append(ctx->str, " (");
+               write_uint_prop_value(ctx, env_count);
+               g_string_append_printf(ctx->str, " entr%s):",
+                       env_count == 1 ? "y" : "ies");
+               write_nl(ctx);
+               incr_indent(ctx);
+
+               for (i = 0; i < env_count; i++) {
+                       const char *name;
+                       const bt_value *value;
+
+                       bt_trace_class_borrow_environment_entry_by_index_const(
+                               tc, i, &name, &value);
+                       g_ptr_array_add(env_names, (gpointer) name);
+               }
+
+               g_ptr_array_sort(env_names, (GCompareFunc) compare_strings);
+
+               for (i = 0; i < env_names->len; i++) {
+                       const char *name = env_names->pdata[i];
+                       const bt_value *value =
+                               bt_trace_class_borrow_environment_entry_value_by_name_const(
+                                       tc, name);
+
+                       BT_ASSERT(value);
+                       write_compound_member_name(ctx, name);
+                       write_sp(ctx);
+
+                       if (bt_value_get_type(value) ==
+                                       BT_VALUE_TYPE_SIGNED_INTEGER) {
+                               write_int_prop_value(ctx,
+                                       bt_value_signed_integer_get(value));
+                       } else if (bt_value_get_type(value) ==
+                                       BT_VALUE_TYPE_STRING) {
+                               write_str_prop_value(ctx,
+                                       bt_value_string_get(value));
+                       } else {
+                               abort();
+                       }
+
+                       write_nl(ctx);
+               }
+
+               decr_indent(ctx);
+       }
+
+       for (i = 0; i < bt_trace_class_get_stream_class_count(tc); i++) {
+               g_ptr_array_add(stream_classes,
+                       (gpointer) bt_trace_class_borrow_stream_class_by_index_const(
+                               tc, i));
+       }
+
+       g_ptr_array_sort(stream_classes, (GCompareFunc) compare_stream_classes);
+
+       if (stream_classes->len > 0) {
+               if (!printed_prop) {
+                       g_string_append(ctx->str, ":\n");
+                       printed_prop = true;
+               }
+       }
+
+       for (i = 0; i < stream_classes->len; i++) {
+               write_stream_class(ctx, stream_classes->pdata[i]);
+       }
+
+       decr_indent(ctx);
+
+       if (!printed_prop) {
+               write_nl(ctx);
+       }
+
+       g_ptr_array_free(stream_classes, TRUE);
+       g_ptr_array_free(env_names, TRUE);
+}
+
+static
+int try_write_meta(struct details_write_ctx *ctx, const bt_trace_class *tc,
+               const bt_stream_class *sc, const bt_event_class *ec)
+{
+       int ret = 0;
+
+       BT_ASSERT(tc);
+
+       if (details_need_to_write_trace_class(ctx, tc)) {
+               uint64_t sc_i;
+
+               if (ctx->details_comp->cfg.compact &&
+                               ctx->details_comp->printed_something) {
+                       /*
+                        * There are no empty line between messages in
+                        * compact mode, so write one here to decouple
+                        * the trace class from the next message.
+                        */
+                       write_nl(ctx);
+               }
+
+               /*
+                * write_trace_class() also writes all its stream
+                * classes their event classes, so we don't need to
+                * rewrite `sc`.
+                */
+               write_trace_class(ctx, tc);
+               write_nl(ctx);
+
+               /*
+                * Mark this trace class as written, as well as all
+                * its stream classes and their event classes.
+                */
+               ret = details_did_write_trace_class(ctx, tc);
+               if (ret) {
+                       goto end;
+               }
+
+               for (sc_i = 0; sc_i < bt_trace_class_get_stream_class_count(tc);
+                               sc_i++) {
+                       uint64_t ec_i;
+                       const bt_stream_class *tc_sc =
+                               bt_trace_class_borrow_stream_class_by_index_const(
+                                       tc, sc_i);
+
+                       details_did_write_meta_object(ctx, tc, tc_sc);
+
+                       for (ec_i = 0; ec_i <
+                                       bt_stream_class_get_event_class_count(tc_sc);
+                                       ec_i++) {
+                               details_did_write_meta_object(ctx, tc,
+                                       bt_stream_class_borrow_event_class_by_index_const(
+                                               tc_sc, ec_i));
+                       }
+               }
+
+               goto end;
+       }
+
+       if (sc && details_need_to_write_meta_object(ctx, tc, sc)) {
+               uint64_t ec_i;
+
+               BT_ASSERT(tc);
+
+               if (ctx->details_comp->cfg.compact &&
+                               ctx->details_comp->printed_something) {
+                       /*
+                        * There are no empty line between messages in
+                        * compact mode, so write one here to decouple
+                        * the stream class from the next message.
+                        */
+                       write_nl(ctx);
+               }
+
+               /*
+                * write_stream_class() also writes all its event
+                * classes, so we don't need to rewrite `ec`.
+                */
+               write_stream_class(ctx, sc);
+               write_nl(ctx);
+
+               /*
+                * Mark this stream class as written, as well as all its
+                * event classes.
+                */
+               details_did_write_meta_object(ctx, tc, sc);
+
+               for (ec_i = 0; ec_i <
+                               bt_stream_class_get_event_class_count(sc);
+                               ec_i++) {
+                       details_did_write_meta_object(ctx, tc,
+                               bt_stream_class_borrow_event_class_by_index_const(
+                                       sc, ec_i));
+               }
+
+               goto end;
+       }
+
+       if (ec && details_need_to_write_meta_object(ctx, tc, ec)) {
+               BT_ASSERT(sc);
+
+               if (ctx->details_comp->cfg.compact &&
+                               ctx->details_comp->printed_something) {
+                       /*
+                        * There are no empty line between messages in
+                        * compact mode, so write one here to decouple
+                        * the event class from the next message.
+                        */
+                       write_nl(ctx);
+               }
+
+               write_event_class(ctx, ec);
+               write_nl(ctx);
+               details_did_write_meta_object(ctx, tc, ec);
+               goto end;
+       }
+
+end:
+       return ret;
+}
+
+static
+void write_time_str(struct details_write_ctx *ctx, const char *str)
+{
+       if (!ctx->details_comp->cfg.with_time) {
+               goto end;
+       }
+
+       g_string_append_printf(ctx->str, "[%s%s%s%s]",
+               color_bold(ctx), color_fg_blue(ctx), str, color_reset(ctx));
+
+       if (ctx->details_comp->cfg.compact) {
+               write_sp(ctx);
+       } else {
+               write_nl(ctx);
+       }
+
+end:
+       return;
+}
+
+static
+void write_time(struct details_write_ctx *ctx, const bt_clock_snapshot *cs)
+{
+       bt_clock_snapshot_status status;
+       int64_t ns_from_origin;
+       char buf[32];
+
+       if (!ctx->details_comp->cfg.with_time) {
+               goto end;
+       }
+
+       format_uint(buf, bt_clock_snapshot_get_value(cs), 10);
+       g_string_append_printf(ctx->str, "[%s%s%s%s%s",
+               color_bold(ctx), color_fg_blue(ctx), buf,
+               color_reset(ctx),
+               ctx->details_comp->cfg.compact ? "" : " cycles");
+       status = bt_clock_snapshot_get_ns_from_origin(cs, &ns_from_origin);
+       if (status == BT_CLOCK_SNAPSHOT_STATUS_OK) {
+               format_int(buf, ns_from_origin, 10);
+               g_string_append_printf(ctx->str, "%s %s%s%s%s%s",
+                       ctx->details_comp->cfg.compact ? "" : ",",
+                       color_bold(ctx), color_fg_blue(ctx), buf,
+                       color_reset(ctx),
+                       ctx->details_comp->cfg.compact ? "" : " ns from origin");
+       }
+
+       g_string_append(ctx->str, "]");
+
+       if (ctx->details_comp->cfg.compact) {
+               write_sp(ctx);
+       } else {
+               write_nl(ctx);
+       }
+
+end:
+       return;
+}
+
+static
+int write_message_follow_tag(struct details_write_ctx *ctx,
+               const bt_stream *stream)
+{
+       int ret;
+       uint64_t unique_trace_id;
+       const bt_stream_class *sc = bt_stream_borrow_class_const(stream);
+       const bt_trace *trace = bt_stream_borrow_trace_const(stream);
+
+       ret = details_trace_unique_id(ctx, trace, &unique_trace_id);
+       if (ret) {
+               goto end;
+       }
+
+       if (ctx->details_comp->cfg.compact) {
+               g_string_append_printf(ctx->str,
+                       "%s{%s%" PRIu64 " %" PRIu64 " %" PRIu64 "%s%s}%s ",
+                       color_fg_cyan(ctx), color_bold(ctx),
+                       unique_trace_id, bt_stream_class_get_id(sc),
+                       bt_stream_get_id(stream),
+                       color_reset(ctx), color_fg_cyan(ctx), color_reset(ctx));
+       } else {
+               g_string_append_printf(ctx->str,
+                       "%s{Trace %s%" PRIu64 "%s%s, Stream class ID %s%" PRIu64 "%s%s, Stream ID %s%" PRIu64 "%s%s}%s\n",
+                       color_fg_cyan(ctx),
+                       color_bold(ctx), unique_trace_id,
+                       color_reset(ctx), color_fg_cyan(ctx),
+                       color_bold(ctx), bt_stream_class_get_id(sc),
+                       color_reset(ctx), color_fg_cyan(ctx),
+                       color_bold(ctx), bt_stream_get_id(stream),
+                       color_reset(ctx), color_fg_cyan(ctx),
+                       color_reset(ctx));
+       }
+
+end:
+       return ret;
+}
+
+static
+void write_field(struct details_write_ctx *ctx, const bt_field *field,
+               const char *name)
+{
+       uint64_t i;
+       bt_field_class_type fc_type = bt_field_get_class_type(field);
+       const bt_field_class *fc;
+       char buf[64];
+
+       /* Write field's name */
+       if (name) {
+               write_compound_member_name(ctx, name);
+       }
+
+       /* Write field's value */
+       switch (fc_type) {
+       case BT_FIELD_CLASS_TYPE_UNSIGNED_INTEGER:
+       case BT_FIELD_CLASS_TYPE_UNSIGNED_ENUMERATION:
+       case BT_FIELD_CLASS_TYPE_SIGNED_INTEGER:
+       case BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION:
+       {
+               unsigned int fmt_base;
+               bt_field_class_integer_preferred_display_base base;
+
+               fc = bt_field_borrow_class_const(field);
+               base = bt_field_class_integer_get_preferred_display_base(fc);
+
+               switch (base) {
+               case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL:
+                       fmt_base = 10;
+                       break;
+               case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_OCTAL:
+                       fmt_base = 8;
+                       break;
+               case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_BINARY:
+                       fmt_base = 2;
+                       break;
+               case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL:
+                       fmt_base = 16;
+                       break;
+               default:
+                       abort();
+               }
+
+               if (fc_type == BT_FIELD_CLASS_TYPE_UNSIGNED_INTEGER ||
+                               fc_type == BT_FIELD_CLASS_TYPE_UNSIGNED_ENUMERATION) {
+                       format_uint(buf,
+                               bt_field_unsigned_integer_get_value(field),
+                               fmt_base);
+                       write_sp(ctx);
+                       write_uint_str_prop_value(ctx, buf);
+               } else {
+                       format_int(buf,
+                               bt_field_signed_integer_get_value(field),
+                               fmt_base);
+                       write_sp(ctx);
+                       write_int_str_prop_value(ctx, buf);
+               }
+
+               break;
+       }
+       case BT_FIELD_CLASS_TYPE_REAL:
+               write_sp(ctx);
+               write_float_prop_value(ctx, bt_field_real_get_value(field));
+               break;
+       case BT_FIELD_CLASS_TYPE_STRING:
+               write_sp(ctx);
+               write_str_prop_value(ctx, bt_field_string_get_value(field));
+               break;
+       case BT_FIELD_CLASS_TYPE_STRUCTURE:
+       {
+               uint64_t member_count;
+
+               fc = bt_field_borrow_class_const(field);
+               member_count = bt_field_class_structure_get_member_count(fc);
+
+               if (member_count > 0) {
+                       incr_indent(ctx);
+
+                       for (i = 0; i < member_count; i++) {
+                               const bt_field_class_structure_member *member =
+                                       bt_field_class_structure_borrow_member_by_index_const(
+                                               fc, i);
+                               const bt_field *member_field =
+                                       bt_field_structure_borrow_member_field_by_index_const(
+                                               field, i);
+
+                               write_nl(ctx);
+                               write_field(ctx, member_field,
+                                       bt_field_class_structure_member_get_name(member));
+                       }
+
+                       decr_indent(ctx);
+               } else {
+                       g_string_append(ctx->str, " Empty");
+               }
+
+               break;
+       }
+       case BT_FIELD_CLASS_TYPE_STATIC_ARRAY:
+       case BT_FIELD_CLASS_TYPE_DYNAMIC_ARRAY:
+       {
+               uint64_t length = bt_field_array_get_length(field);
+
+               if (length == 0) {
+                       g_string_append(ctx->str, " Empty");
+               } else {
+                       g_string_append(ctx->str, " Length ");
+                       write_uint_prop_value(ctx, length);
+                       g_string_append_c(ctx->str, ':');
+               }
+
+               incr_indent(ctx);
+
+               for (i = 0; i < length; i++) {
+                       const bt_field *elem_field =
+                               bt_field_array_borrow_element_field_by_index_const(
+                                       field, i);
+
+                       write_nl(ctx);
+                       write_array_index(ctx, i);
+                       write_field(ctx, elem_field, NULL);
+               }
+
+               decr_indent(ctx);
+               break;
+       }
+       case BT_FIELD_CLASS_TYPE_VARIANT:
+               write_field(ctx,
+                       bt_field_variant_borrow_selected_option_field_const(
+                               field), NULL);
+               break;
+       default:
+               abort();
+       }
+}
+
+static
+void write_root_field(struct details_write_ctx *ctx, const char *name,
+               const bt_field *field)
+{
+       BT_ASSERT(name);
+       BT_ASSERT(field);
+       write_indent(ctx);
+       write_prop_name(ctx, name);
+       g_string_append(ctx->str, ":");
+       write_field(ctx, field, NULL);
+       write_nl(ctx);
+}
+
+static
+int write_event_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       int ret = 0;
+       const bt_event *event = bt_message_event_borrow_event_const(msg);
+       const bt_stream *stream = bt_event_borrow_stream_const(event);
+       const bt_event_class *ec = bt_event_borrow_class_const(event);
+       const bt_stream_class *sc = bt_event_class_borrow_stream_class_const(ec);
+       const bt_trace_class *tc = bt_stream_class_borrow_trace_class_const(sc);
+       const char *ec_name;
+       const bt_field *field;
+
+       ret = try_write_meta(ctx, tc, sc, ec);
+       if (ret) {
+               goto end;
+       }
+
+       /* Write time */
+       if (bt_stream_class_borrow_default_clock_class_const(sc)) {
+               write_time(ctx,
+                       bt_message_event_borrow_default_clock_snapshot_const(
+                               msg));
+       }
+
+       /* Write follow tag for message */
+       ret = write_message_follow_tag(ctx, stream);
+       if (ret) {
+               goto end;
+       }
+
+       /* Write object's basic properties */
+       write_obj_type_name(ctx, "Event");
+       ec_name = bt_event_class_get_name(ec);
+       if (ec_name) {
+               g_string_append_printf(ctx->str, " `%s%s%s`",
+                       color_fg_green(ctx), ec_name, color_reset(ctx));
+       }
+
+       g_string_append(ctx->str, " (");
+
+       if (!ctx->details_comp->cfg.compact) {
+               g_string_append(ctx->str, "Class ID ");
+       }
+
+       write_uint_prop_value(ctx, bt_event_class_get_id(ec));
+       g_string_append(ctx->str, ")");
+
+       if (ctx->details_comp->cfg.compact) {
+               write_nl(ctx);
+               goto end;
+       }
+
+       /* Write fields */
+       g_string_append(ctx->str, ":\n");
+       incr_indent(ctx);
+       field = bt_event_borrow_common_context_field_const(event);
+       if (field) {
+               write_root_field(ctx, "Common context", field);
+       }
+
+       field = bt_event_borrow_specific_context_field_const(event);
+       if (field) {
+               write_root_field(ctx, "Specific context", field);
+       }
+
+       field = bt_event_borrow_payload_field_const(event);
+       if (field) {
+               write_root_field(ctx, "Payload", field);
+       }
+
+       decr_indent(ctx);
+
+end:
+
+       return ret;
+}
+
+static
+gint compare_streams(const bt_stream **a, const bt_stream **b)
+{
+       uint64_t id_a = bt_stream_get_id(*a);
+       uint64_t id_b = bt_stream_get_id(*b);
+
+       if (id_a < id_b) {
+               return -1;
+       } else if (id_a > id_b) {
+               return 1;
+       } else {
+               const bt_stream_class *a_sc = bt_stream_borrow_class_const(*a);
+               const bt_stream_class *b_sc = bt_stream_borrow_class_const(*b);
+               uint64_t a_sc_id = bt_stream_class_get_id(a_sc);
+               uint64_t b_sc_id = bt_stream_class_get_id(b_sc);
+
+               if (a_sc_id < b_sc_id) {
+                       return -1;
+               } else if (a_sc_id > b_sc_id) {
+                       return 1;
+               } else {
+                       return 0;
+               }
+       }
+}
+
+static
+void write_trace(struct details_write_ctx *ctx, const bt_trace *trace)
+{
+       const char *name;
+       const bt_trace_class *tc = bt_trace_borrow_class_const(trace);
+       GPtrArray *streams = g_ptr_array_new();
+       uint64_t i;
+       bool printed_prop = false;
+
+       write_indent(ctx);
+       write_obj_type_name(ctx, "Trace");
+
+       /* Write name */
+       if (ctx->details_comp->cfg.with_trace_name) {
+               name = bt_trace_get_name(trace);
+               if (name) {
+                       g_string_append(ctx->str, " `");
+                       write_str_prop_value(ctx, name);
+                       g_string_append(ctx->str, "`");
+               }
+       }
+
+       /* Write properties */
+       incr_indent(ctx);
+
+       if (ctx->details_comp->cfg.with_trace_class_name) {
+               name = bt_trace_class_get_name(tc);
+               if (name) {
+                       if (!printed_prop) {
+                               g_string_append(ctx->str, ":\n");
+                               printed_prop = true;
+                       }
+
+                       write_str_prop_line(ctx, "Class name", name);
+               }
+       }
+
+       if (ctx->details_comp->cfg.with_uuid) {
+               bt_uuid uuid = bt_trace_class_get_uuid(tc);
+
+               if (uuid) {
+                       if (!printed_prop) {
+                               g_string_append(ctx->str, ":\n");
+                               printed_prop = true;
+                       }
+
+                       write_uuid_prop_line(ctx, "Class UUID", uuid);
+               }
+       }
+
+       for (i = 0; i < bt_trace_get_stream_count(trace); i++) {
+               g_ptr_array_add(streams,
+                       (gpointer) bt_trace_borrow_stream_by_index_const(
+                               trace, i));
+       }
+
+       g_ptr_array_sort(streams, (GCompareFunc) compare_streams);
+
+       if (streams->len > 0 && !printed_prop) {
+               g_string_append(ctx->str, ":\n");
+               printed_prop = true;
+       }
+
+       for (i = 0; i < streams->len; i++) {
+               const bt_stream *stream = streams->pdata[i];
+
+               write_indent(ctx);
+               write_obj_type_name(ctx, "Stream");
+               g_string_append(ctx->str, " (ID ");
+               write_uint_prop_value(ctx, bt_stream_get_id(stream));
+               g_string_append(ctx->str, ", Class ID ");
+               write_uint_prop_value(ctx, bt_stream_class_get_id(
+                       bt_stream_borrow_class_const(stream)));
+               g_string_append(ctx->str, ")");
+               write_nl(ctx);
+       }
+
+       decr_indent(ctx);
+
+       if (!printed_prop) {
+               write_nl(ctx);
+       }
+
+       g_ptr_array_free(streams, TRUE);
+}
+
+static
+int write_stream_beginning_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       int ret = 0;
+       const bt_stream *stream =
+               bt_message_stream_beginning_borrow_stream_const(msg);
+       const bt_trace *trace = bt_stream_borrow_trace_const(stream);
+       const bt_stream_class *sc = bt_stream_borrow_class_const(stream);
+       const bt_trace_class *tc = bt_stream_class_borrow_trace_class_const(sc);
+       const char *name;
+
+       ret = try_write_meta(ctx, tc, sc, NULL);
+       if (ret) {
+               goto end;
+       }
+
+       /* Write follow tag for message */
+       ret = write_message_follow_tag(ctx, stream);
+       if (ret) {
+               goto end;
+       }
+
+       /* Write stream properties */
+       write_obj_type_name(ctx, "Stream beginning");
+
+       if (ctx->details_comp->cfg.compact) {
+               write_nl(ctx);
+               goto end;
+       }
+
+       g_string_append(ctx->str, ":\n");
+       incr_indent(ctx);
+
+       if (ctx->details_comp->cfg.with_stream_name) {
+               name = bt_stream_get_name(stream);
+               if (name) {
+                       write_str_prop_line(ctx, "Name", name);
+               }
+       }
+
+       if (ctx->details_comp->cfg.with_stream_class_name) {
+               name = bt_stream_class_get_name(sc);
+               if (name) {
+                       write_str_prop_line(ctx, "Class name", name);
+               }
+       }
+
+       write_trace(ctx, trace);
+       decr_indent(ctx);
+
+end:
+       return ret;
+}
+
+static
+int write_stream_end_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       int ret = 0;
+       const bt_stream *stream =
+               bt_message_stream_end_borrow_stream_const(msg);
+
+       /* Write follow tag for message */
+       ret = write_message_follow_tag(ctx, stream);
+       if (ret) {
+               goto end;
+       }
+
+       /* Write stream properties */
+       write_obj_type_name(ctx, "Stream end\n");
+
+end:
+       return ret;
+}
+
+static
+int write_stream_activity_beginning_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       int ret = 0;
+       const bt_stream *stream =
+               bt_message_stream_activity_beginning_borrow_stream_const(msg);
+       bt_message_stream_activity_clock_snapshot_state cs_state;
+       const bt_clock_snapshot *cs = NULL;
+
+       /* Write time */
+       cs_state = bt_message_stream_activity_beginning_borrow_default_clock_snapshot_const(
+               msg, &cs);
+       switch (cs_state) {
+       case BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_KNOWN:
+               BT_ASSERT(cs);
+               write_time(ctx, cs);
+               break;
+       case BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_UNKNOWN:
+               write_time_str(ctx, "Unknown");
+               break;
+       case BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_INFINITE:
+               write_time_str(ctx, "-Infinity");
+               break;
+       default:
+               abort();
+       }
+
+       /* Write follow tag for message */
+       ret = write_message_follow_tag(ctx, stream);
+       if (ret) {
+               goto end;
+       }
+
+       write_obj_type_name(ctx, "Stream activity beginning");
+       write_nl(ctx);
+
+end:
+       return ret;
+}
+
+static
+int write_stream_activity_end_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       int ret = 0;
+       const bt_stream *stream =
+               bt_message_stream_activity_end_borrow_stream_const(msg);
+       bt_message_stream_activity_clock_snapshot_state cs_state;
+       const bt_clock_snapshot *cs = NULL;
+
+       /* Write time */
+       cs_state = bt_message_stream_activity_end_borrow_default_clock_snapshot_const(
+               msg, &cs);
+       switch (cs_state) {
+       case BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_KNOWN:
+               BT_ASSERT(cs);
+               write_time(ctx, cs);
+               break;
+       case BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_UNKNOWN:
+               write_time_str(ctx, "Unknown");
+               break;
+       case BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_INFINITE:
+               write_time_str(ctx, "+Infinity");
+               break;
+       default:
+               abort();
+       }
+
+       /* Write follow tag for message */
+       ret = write_message_follow_tag(ctx, stream);
+       if (ret) {
+               goto end;
+       }
+
+       write_obj_type_name(ctx, "Stream activity end");
+       write_nl(ctx);
+
+end:
+       return ret;
+}
+
+static
+int write_packet_beginning_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       int ret = 0;
+       const bt_packet *packet =
+               bt_message_packet_beginning_borrow_packet_const(msg);
+       const bt_stream *stream = bt_packet_borrow_stream_const(packet);
+       const bt_stream_class *sc = bt_stream_borrow_class_const(stream);
+       const bt_field *field;
+
+       /* Write time */
+       if (bt_stream_class_packets_have_beginning_default_clock_snapshot(sc)) {
+               write_time(ctx,
+                       bt_message_packet_beginning_borrow_default_clock_snapshot_const(
+                               msg));
+       }
+
+       /* Write follow tag for message */
+       ret = write_message_follow_tag(ctx, stream);
+       if (ret) {
+               goto end;
+       }
+
+       write_obj_type_name(ctx, "Packet beginning");
+
+       if (ctx->details_comp->cfg.compact) {
+               write_nl(ctx);
+               goto end;
+       }
+
+       /* Write field */
+       g_string_append(ctx->str, ":\n");
+       incr_indent(ctx);
+       field = bt_packet_borrow_context_field_const(packet);
+       if (field) {
+               write_root_field(ctx, "Context", field);
+       }
+
+       decr_indent(ctx);
+
+end:
+       return ret;
+}
+
+static
+int write_discarded_items_message(struct details_write_ctx *ctx,
+               const char *name, const bt_stream *stream,
+               const bt_clock_snapshot *beginning_cs,
+               const bt_clock_snapshot *end_cs, uint64_t count)
+{
+       int ret = 0;
+
+       /* Write times */
+       if (beginning_cs) {
+               write_time(ctx, beginning_cs);
+               BT_ASSERT(end_cs);
+               write_time(ctx, end_cs);
+       }
+
+       /* Write follow tag for message */
+       ret = write_message_follow_tag(ctx, stream);
+       if (ret) {
+               goto end;
+       }
+
+       write_obj_type_name(ctx, "Discarded ");
+       write_obj_type_name(ctx, name);
+
+       /* Write count */
+       if (count == UINT64_C(-1)) {
+               write_nl(ctx);
+               goto end;
+       }
+
+       g_string_append(ctx->str, " (");
+       write_uint_prop_value(ctx, count);
+       g_string_append_printf(ctx->str, " %s)\n", name);
+
+end:
+       return ret;
+}
+
+static
+int write_discarded_events_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       const bt_stream *stream = bt_message_discarded_events_borrow_stream_const(
+               msg);
+       const bt_stream_class *sc = bt_stream_borrow_class_const(stream);
+       const bt_clock_snapshot *beginning_cs = NULL;
+       const bt_clock_snapshot *end_cs = NULL;
+       uint64_t count;
+
+       if (bt_stream_class_discarded_events_have_default_clock_snapshots(sc)) {
+               beginning_cs =
+                       bt_message_discarded_events_borrow_beginning_default_clock_snapshot_const(
+                               msg);
+               end_cs =
+                       bt_message_discarded_events_borrow_end_default_clock_snapshot_const(
+                               msg);
+       }
+
+       if (bt_message_discarded_events_get_count(msg, &count) !=
+                       BT_PROPERTY_AVAILABILITY_AVAILABLE) {
+               count = UINT64_C(-1);
+       }
+
+       return write_discarded_items_message(ctx, "events", stream,
+               beginning_cs, end_cs, count);
+}
+
+static
+int write_discarded_packets_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       const bt_stream *stream = bt_message_discarded_packets_borrow_stream_const(
+               msg);
+       const bt_stream_class *sc = bt_stream_borrow_class_const(stream);
+       const bt_clock_snapshot *beginning_cs = NULL;
+       const bt_clock_snapshot *end_cs = NULL;
+       uint64_t count;
+
+       if (bt_stream_class_discarded_packets_have_default_clock_snapshots(sc)) {
+               beginning_cs =
+                       bt_message_discarded_packets_borrow_beginning_default_clock_snapshot_const(
+                               msg);
+               end_cs =
+                       bt_message_discarded_packets_borrow_end_default_clock_snapshot_const(
+                               msg);
+       }
+
+       if (bt_message_discarded_packets_get_count(msg, &count) !=
+                       BT_PROPERTY_AVAILABILITY_AVAILABLE) {
+               count = UINT64_C(-1);
+       }
+
+       return write_discarded_items_message(ctx, "packets", stream,
+               beginning_cs, end_cs, count);
+}
+
+static
+int write_packet_end_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       int ret = 0;
+       const bt_packet *packet =
+               bt_message_packet_end_borrow_packet_const(msg);
+       const bt_stream *stream = bt_packet_borrow_stream_const(packet);
+       const bt_stream_class *sc = bt_stream_borrow_class_const(stream);
+
+       /* Write time */
+       if (bt_stream_class_packets_have_end_default_clock_snapshot(sc)) {
+               write_time(ctx,
+                       bt_message_packet_end_borrow_default_clock_snapshot_const(
+                               msg));
+       }
+
+       /* Write follow tag for message */
+       ret = write_message_follow_tag(ctx, stream);
+       if (ret) {
+               goto end;
+       }
+
+       write_obj_type_name(ctx, "Packet end");
+       write_nl(ctx);
+
+end:
+       return ret;
+}
+
+static
+int write_message_iterator_inactivity_message(struct details_write_ctx *ctx,
+               const bt_message *msg)
+{
+       int ret = 0;
+       const bt_clock_snapshot *cs =
+               bt_message_message_iterator_inactivity_borrow_default_clock_snapshot_const(
+                       msg);
+
+       /* Write time */
+       write_time(ctx, cs);
+       write_obj_type_name(ctx, "Message iterator inactivity");
+
+       if (ctx->details_comp->cfg.compact) {
+               write_nl(ctx);
+               goto end;
+       }
+
+       /* Write clock class properties */
+       g_string_append(ctx->str, ":\n");
+       incr_indent(ctx);
+       write_indent(ctx);
+       write_prop_name(ctx, "Clock class");
+       g_string_append_c(ctx->str, ':');
+       write_nl(ctx);
+       incr_indent(ctx);
+       write_clock_class_prop_lines(ctx,
+               bt_clock_snapshot_borrow_clock_class_const(cs));
+       decr_indent(ctx);
+
+end:
+       return ret;
+}
+
+BT_HIDDEN
+int details_write_message(struct details_comp *details_comp,
+               const bt_message *msg)
+{
+       int ret = 0;
+       struct details_write_ctx ctx = {
+               .details_comp = details_comp,
+               .str = details_comp->str,
+               .indent_level = 0,
+       };
+
+       /* Reset output buffer */
+       g_string_assign(details_comp->str, "");
+
+       if (details_comp->printed_something && !details_comp->cfg.compact) {
+               write_nl(&ctx);
+       }
+
+       switch (bt_message_get_type(msg)) {
+       case BT_MESSAGE_TYPE_EVENT:
+               ret = write_event_message(&ctx, msg);
+               break;
+       case BT_MESSAGE_TYPE_MESSAGE_ITERATOR_INACTIVITY:
+               ret = write_message_iterator_inactivity_message(&ctx, msg);
+               break;
+       case BT_MESSAGE_TYPE_STREAM_BEGINNING:
+               ret = write_stream_beginning_message(&ctx, msg);
+               break;
+       case BT_MESSAGE_TYPE_STREAM_END:
+               ret = write_stream_end_message(&ctx, msg);
+               break;
+       case BT_MESSAGE_TYPE_PACKET_BEGINNING:
+               ret = write_packet_beginning_message(&ctx, msg);
+               break;
+       case BT_MESSAGE_TYPE_PACKET_END:
+               ret = write_packet_end_message(&ctx, msg);
+               break;
+       case BT_MESSAGE_TYPE_STREAM_ACTIVITY_BEGINNING:
+               ret = write_stream_activity_beginning_message(&ctx, msg);
+               break;
+       case BT_MESSAGE_TYPE_STREAM_ACTIVITY_END:
+               ret = write_stream_activity_end_message(&ctx, msg);
+               break;
+       case BT_MESSAGE_TYPE_DISCARDED_EVENTS:
+               ret = write_discarded_events_message(&ctx, msg);
+               break;
+       case BT_MESSAGE_TYPE_DISCARDED_PACKETS:
+               ret = write_discarded_packets_message(&ctx, msg);
+               break;
+       default:
+               abort();
+       }
+
+       return ret;
+}
diff --git a/src/plugins/text/details/write.h b/src/plugins/text/details/write.h
new file mode 100644 (file)
index 0000000..0f811a9
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef BABELTRACE_PLUGINS_TEXT_DETAILS_WRITE_H
+#define BABELTRACE_PLUGINS_TEXT_DETAILS_WRITE_H
+
+/*
+ * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <glib.h>
+#include <babeltrace2/babeltrace.h>
+#include <stdbool.h>
+
+#include "details.h"
+
+/* Writing context */
+struct details_write_ctx {
+       /* Weak */
+       struct details_comp *details_comp;
+
+       /* Weak (belongs to `details_comp` above) */
+       GString *str;
+
+       /* Current indentation level (number of actual spaces) */
+       unsigned int indent_level;
+};
+
+/*
+ * Writes the message `msg` to the component's output buffer
+ * (`details_comp->str`).
+ */
+BT_HIDDEN
+int details_write_message(struct details_comp *details_comp,
+               const bt_message *msg);
+
+#endif /* BABELTRACE_PLUGINS_TEXT_DETAILS_WRITE_H */
index 17f814b9aa24994873797f40c259096fba949c24..0c474d30f432cf3e483aa0571eed3f283a6eedcf 100644 (file)
@@ -23,6 +23,7 @@
 #include <babeltrace2/babeltrace.h>
 #include "pretty/pretty.h"
 #include "dmesg/dmesg.h"
+#include "details/details.h"
 
 #ifndef BT_BUILT_IN_PLUGINS
 BT_PLUGIN_MODULE();
@@ -56,3 +57,12 @@ BT_PLUGIN_SOURCE_COMPONENT_CLASS_MESSAGE_ITERATOR_SEEK_BEGINNING_METHOD(dmesg,
        dmesg_msg_iter_seek_beginning);
 BT_PLUGIN_SOURCE_COMPONENT_CLASS_MESSAGE_ITERATOR_CAN_SEEK_BEGINNING_METHOD(dmesg,
        dmesg_msg_iter_can_seek_beginning);
+
+/* details sink */
+BT_PLUGIN_SINK_COMPONENT_CLASS(details, details_consume);
+BT_PLUGIN_SINK_COMPONENT_CLASS_INIT_METHOD(details, details_init);
+BT_PLUGIN_SINK_COMPONENT_CLASS_FINALIZE_METHOD(details, details_finalize);
+BT_PLUGIN_SINK_COMPONENT_CLASS_GRAPH_IS_CONFIGURED_METHOD(details,
+       details_graph_is_configured);
+BT_PLUGIN_SINK_COMPONENT_CLASS_DESCRIPTION(details,
+       "Print messages with details.");
This page took 0.104454 seconds and 4 git commands to generate.