Mirror of the lttng-dev mailing list
 help / color / mirror / Atom feed
* babeltrace 2.1.2: sink.ctf.fs does not deduplicate identical clocks
@ 2026-08-04 15:27 MOESSBAUER, Felix via lttng-dev
  2026-08-06 18:45 ` Philippe Proulx via lttng-dev
  0 siblings, 1 reply; 2+ messages in thread
From: MOESSBAUER, Felix via lttng-dev @ 2026-08-04 15:27 UTC (permalink / raw)
  To: MOESSBAUER, Felix via lttng-dev

[-- Attachment #1: Type: text/plain, Size: 1695 bytes --]

Hi,

while porting the bt2-ftrace-to-ctf [1] plugin to babeltrace 2.1, I
noticed that the sink.ctf.fs creates invalid clock definitions if
multiple stream-classes share the same clock-class. In this case, the
clock definition is added multiple times to the CTF 2 metadata,
resulting in the following assertion on read-back:

ERROR:    [Babeltrace CLI] (babeltrace2.c:2705)
  Command-line error: retcode=1
CAUSED BY [Source auto-discovery] (autodisc/autodisc.c:493)
  babeltrace.support-info query failed.
CAUSED BY [libbabeltrace2] (lib/graph/query-executor.c:233)
  Component class's "query" method failed: query-exec-
addr=0x55cba00c12e0, cc-addr=0x55cba00b4100, cc-type=SOURCE, cc-
name="fs", cc-partial-descr="Read CTF traces from
  the file sy", cc-is-frozen=0, cc-so-handle-addr=0x55cba00b1930, cc-
so-handle-path="<...>/build/babeltrace-plugin-ctf.so",
  object="babeltrace.support-info", params-addr=0x55cba00cd470, params-
type=MAP, params-element-count=2, log-level=WARNING
CAUSED BY ['source.ctf.fs'] (plugins/ctf/common/src/metadata/json/ctf-
2-metadata-stream-parser.cpp:130)
  [1:1 @ 8512 bytes] Invalid fragment #12.
CAUSED BY ['source.ctf.fs'] (plugins/ctf/common/src/metadata/json/ctf-
2-metadata-stream-parser.cpp:367)
  [1:1 @ 17024 bytes] Duplicate clock class fragment with ID `local`.

When running with MIP=0, the CTF 1.8 data does not have this issue (or
the reader ignores the duplicated definitions).

[1] https://github.com/siemens/bt2-ftrace-to-ctf

Attached you will find an AI generated reproducer using just the
babeltrace2 Python bindings.

Best regards,
Felix Moessbauer

-- 
Siemens AG
Linux Expert Center
Friedrich-Ludwig-Bauer-Str. 3
85748 Garching, Germany


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: repro.py --]
[-- Type: text/x-python3; name="repro.py", Size: 4650 bytes --]

#!/usr/bin/env python3
#
# Minimal, self-contained reproducer for a babeltrace2 CTF-2 sink bug.
#
# A single trace that contains two data stream classes sharing ONE clock class
# is written to CTF 2 by sink.ctf.fs. The sink emits one `clock-class` metadata
# fragment PER stream class (both with the same id, here "the_clock") instead
# of emitting the shared clock class only once. Reading the resulting trace
# back then fails with:
#
#   Duplicate clock class fragment with ID `the_clock`.
#
# This depends only on babeltrace2 and its bundled `ctf`/`utils` plugins; no
# external trace files or third-party plugins are needed.
#
# Usage:
#   python3 repro.py <output-dir>
#
# The script writes the trace in a child process (so sink.ctf.fs flushes the
# metadata file on component teardown), then reads it back in the parent.
import os
import subprocess
import sys

import bt2


class TheIter(bt2._UserMessageIterator):
    def __init__(self, config, port):
        stream = port.user_data
        self._msgs = [
            self._create_stream_beginning_message(stream),
            self._create_stream_end_message(stream),
        ]
        self._i = 0

    def __next__(self):
        if self._i >= len(self._msgs):
            raise StopIteration
        msg = self._msgs[self._i]
        self._i += 1
        return msg


@bt2.plugin_component_class
class TheSource(bt2._UserSourceComponent, message_iterator_class=TheIter):
    """One trace, two stream classes, ONE shared clock class."""

    def __init__(self, config, params, obj):
        tc = self._create_trace_class(assigns_automatic_stream_class_id=False)
        cc = self._create_clock_class(frequency=1000000000, name="the_clock")
        sc0 = tc.create_stream_class(
            id=0,
            default_clock_class=cc,
            supports_packets=True,
            assigns_automatic_stream_id=False,
        )
        sc1 = tc.create_stream_class(
            id=1,
            default_clock_class=cc,
            supports_packets=True,
            assigns_automatic_stream_id=False,
        )
        trace = tc()
        self._add_output_port("out0", trace.create_stream(sc0, id=0))
        self._add_output_port("out1", trace.create_stream(sc1, id=1))


def write_trace(out_dir):
    ctf = bt2.find_plugin("ctf")
    utils = bt2.find_plugin("utils")
    sink_cls = ctf.sink_component_classes["fs"]

    # CTF 2 requires MIP 1. The ctf.fs sink has a single input port, so mux
    # the two source ports into it.
    graph = bt2.Graph(mip_version=1)
    src = graph.add_component(TheSource, "src")
    muxer = graph.add_component(utils.filter_component_classes["muxer"], "mux")
    sink = graph.add_component(
        sink_cls, "sink", params={"path": out_dir, "ctf-version": "2"}
    )
    out_ports = list(src.output_ports.values())
    graph.connect_ports(out_ports[0], list(muxer.input_ports.values())[0])
    graph.connect_ports(out_ports[1], list(muxer.input_ports.values())[1])
    graph.connect_ports(
        list(muxer.output_ports.values())[0],
        list(sink.input_ports.values())[0],
    )
    graph.run()


def read_trace(trace_dir):
    for _ in bt2.TraceCollectionMessageIterator(trace_dir):
        pass


def main():
    if len(sys.argv) < 2:
        print("usage: repro.py <output-dir>", file=sys.stderr)
        return 2

    # Internal entry points, invoked as child processes.
    if sys.argv[1] == "--write":
        write_trace(sys.argv[2])
        return 0
    if sys.argv[1] == "--read":
        read_trace(sys.argv[2])
        return 0

    out_dir = sys.argv[1]
    trace_dir = os.path.join(out_dir, "trace")

    # Write in a child process so the sink flushes the metadata on teardown.
    subprocess.run([sys.executable, __file__, "--write", out_dir], check=True)
    print("wrote CTF-2 trace to", trace_dir)

    with open(os.path.join(trace_dir, "metadata"), "rb") as f:
        n_clock = f.read().count(b'"type":"clock-class"')
    print("clock-class fragments written to metadata:", n_clock)

    # Read back with the CLI for a clear, unwrapped error message.
    proc = subprocess.run(
        ["babeltrace2", trace_dir],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.PIPE,
        text=True,
    )
    if proc.returncode != 0:
        print("\nREAD-BACK FAILED (bug reproduced):", file=sys.stderr)
        for line in proc.stderr.splitlines():
            if "Duplicate clock class" in line or "Invalid fragment" in line:
                print("  " + line.split("] ", 1)[-1], file=sys.stderr)
        return 1

    print("read-back OK (bug NOT reproduced)")
    return 0


if __name__ == "__main__":
    sys.exit(main())

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-08-06 18:46 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-04 15:27 babeltrace 2.1.2: sink.ctf.fs does not deduplicate identical clocks MOESSBAUER, Felix via lttng-dev
2026-08-06 18:45 ` Philippe Proulx via lttng-dev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox