Skip to content

Support CTF 2 LTTng traces - #141

Open
Hani Nemati (Nemati) wants to merge 1 commit into
developfrom
user/hanemati/lttng-ctf2-support
Open

Hani Nemati (Nemati) wants to merge 1 commit into
developfrom
user/hanemati/lttng-ctf2-support

Conversation

@Nemati

Copy link
Copy Markdown
Contributor

LTTng 2.15+ records CTF 2 (JSON metadata) by default, which the TSDL parser could not read. Add a CTF 2 metadata parser that maps CTF 2 field classes onto the existing descriptor model, and select the parser from the metadata content.

Also fix issues that CTF 2 / multi-channel LTTng traces exposed:

  • Accept any _ stream file, not only chan*
  • Event ids are unique per stream: look up event descriptors and generic event kinds by (stream, id)
  • Look up streams by id and read the packet context after determining the stream id
  • Decode explicitly big-endian whole-byte integers
  • Read the event-specific context when one is defined

LTTng 2.15+ records CTF 2 (JSON metadata) by default, which the TSDL parser
could not read. Add a CTF 2 metadata parser that maps CTF 2 field classes onto
the existing descriptor model, and select the parser from the metadata content.

Also fix issues that CTF 2 / multi-channel LTTng traces exposed:
- Accept any <channel>_<cpu> stream file, not only chan*
- Event ids are unique per stream: look up event descriptors and generic
  event kinds by (stream, id)
- Look up streams by id and read the packet context after determining the
  stream id
- Decode explicitly big-endian whole-byte integers
- Read the event-specific context when one is defined

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@jhpohovey John Pohovey (jhpohovey) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of these comments are small style and clarity things; though there are two or three that revolve around logic correctness that I think you should take a quick look at first and let me know if I am thinking nonsense or not

Comment on lines 64 to +69
ReadPacketHeader();
ReadPacketContext();

// The packet context layout depends on the stream, which is identified in the packet header.
this.DetermineStreamIndex();

ReadPacketContext();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So thinking out this logic -- this likely would have been previously a latent bug when reading packet metadata? given that the when that streamId is updated controls data read within ReadPacketContext().

Mostly just confirming here that the streamId needs to be updated to the current packet here, and there isn't some desired behavior for off-by-one or similar.

Comment on lines +241 to +259
if (GetValue(clockClass, "offset-from-origin") is Dictionary<string, object> offset)
{
// The descriptor expects the full offset in clock cycles.
decimal cycles = 0;
if (TryGetNumber(offset, "seconds", out var seconds))
{
cycles += decimal.Parse(seconds.Text, CultureInfo.InvariantCulture) * frequency;
}

if (TryGetNumber(offset, "cycles", out var offsetCycles))
{
cycles += decimal.Parse(offsetCycles.Text, CultureInfo.InvariantCulture);
}

if (cycles > 0)
{
bag.AddValue("offset", decimal.ToUInt64(cycles).ToString(CultureInfo.InvariantCulture));
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in the data block, while cycles is required to be non-negative, seconds doesn't have such a constraint (e.g., to be an offset chronologically preceding the origin clock).

Is the intention here that we throw out clock offsets where the origin is more than 1 second forward in time relative to the offset?

nit: i also think it would be nice to link https://diamon.org/ctf/#clock-offset here

Comment on lines +213 to +216
private static CtfClockDescriptor CreateClock(Dictionary<string, object> clockClass)
{
// CTF 2 data stream classes refer to clock classes by id; older drafts only had a name.
string clockName = GetString(clockClass, "id") ?? GetRequiredString(clockClass, "name");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider linking in https://diamon.org/ctf/#cc-frag

return this.metadataBuilder;
}

private void AddTraceClass(Dictionary<string, object> traceClass, Dictionary<string, object> preamble)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider link in https://diamon.org/ctf/#tc-frag

return new CtfClockDescriptor(bag);
}

private void AddDataStreamClass(Dictionary<string, object> dataStreamClass)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider linking https://diamon.org/ctf/#dsc-frag

Comment on lines +12 to +14
/// <summary>
/// CTF 2 variable-length (unsigned or signed LEB128) integer.
/// </summary>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public override int Align => 8;

/// <inheritdoc />
public override CtfFieldValue Read(IPacketReader reader, CtfFieldValue parent = null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: While read is true, i'd lean towards having a public Read call an internal DecodeLEB128, which would self-explain what the below algorithm is doing.

namespace CtfPlayback.Metadata
{
/// <summary>
/// Helpers for reading a metadata stream, which may be plain text or packetized (see https://diamon.org/ctf/#spec7.1).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://diamon.org/ctf/#spec7.1 isn't a real link

Likely meant https://diamon.org/ctf/#metadata-stream?

/// </summary>
internal static class CtfMetadataText
{
private const uint PacketMagic = 0x75d11d57;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs a comment i think to just note that this is not an abritrary value here, but defined in the spec: https://diamon.org/ctf/files/CTF2-PMETA-1.0.html

There is a different magic number for event an data packets as opposed to this metadata packet.

int h2 = id.GetHashCode();
return ((h1 << 5) + h1) ^ h2;
int h3 = streamId.GetHashCode();
return ((((h1 << 5) + h1) ^ h2) * 31) ^ h3;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this specific computation? arbitrary? why not use a SHA256 or MD5?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if to simply retain + expand past behavior, that seems generally reasonable.

@jhpohovey John Pohovey (jhpohovey) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving pending resolutions of whichever above

private const uint PacketMagic = 0x75d11d57;

// magic(4) + uuid(16) + checksum(4) + content_size(4) + packet_size(4) + 5 single-byte fields
private const int PacketHeaderSize = 37;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CTF2-PMETA-1.0 defines a minimum 44-byte metadata packet header, not 37 bytes: after the five legacy single-byte fields it adds 3 reserved bytes and a 32-bit header-size field. With a compliant packet, starting the JSON at byte 37 leaves seven binary header bytes before the record separator, so CTF 2 detection/parsing fails. The new packetized-metadata test currently constructs the same nonconforming 37-byte header and therefore masks this. Could this read the CTF 2 declared header size (while retaining the 37-byte CTF 1 path)? See https://diamon.org/ctf/files/CTF2-PMETA-1.0.html

{
if (metadata.Length < PacketHeaderSize || BitConverter.ToUInt32(metadata, 0) != PacketMagic)
{
return Encoding.UTF8.GetString(metadata);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The packet metadata specification permits the magic and following integer fields in either byte order; the magic's encoding indicates which order to use. On our little-endian runtime, BitConverter.ToUInt32() will not recognize a valid big-endian magic and this falls through to decoding the binary packet as UTF-8. Please detect both magic encodings and use the detected byte order for content_size, packet_size, and the CTF 2 header-size field.

default:
throw new CtfMetadataException("Only 32-bit and 64-bit CTF 2 floating point numbers are supported.");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This accepts big-endian floating-point field classes, but CtfFloatingPointDescriptor.Read() passes the source bytes directly to BitConverter without applying its ByteOrder property. A valid big-endian float therefore produces a silently incorrect value. Could we either implement the byte swap in the descriptor (and test it) or reject big-endian floating-point classes here until they are supported?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Valid traces can be misdecoded due to trace-level event collisions, metadata endianness, field-location origins, and signed 64-bit handling.

Review effort: Balanced
Findings: 3 High severity · 1 Medium severity

Open (4)
What changed in this PR

Adds CTF 2 metadata and multi-channel LTTng trace support while retaining CTF 1.8 compatibility.

Changes:

  • Adds JSON metadata parsing with automatic CTF version detection.
  • Makes stream and event lookup stream-aware.
  • Expands stream discovery, decoding, tests, and documentation.
File Description
LTTngDataExtUnitTest/​LTTngCtf2UnitTest.cs Adds end-to-end CTF 2 tests.
LTTngDataExtensions/​DataOutputTypes/​LTTngGenericEvent.cs Keys event kinds by stream.
LTTngCds/​CtfExtensions/​ZipArchiveInput/​LTTngZipArchiveInput.cs Expands ZIP stream discovery.
LTTngCds/​CtfExtensions/​LTTngStreamFiles.cs Recognizes channel/CPU filenames.
LTTngCds/​CtfExtensions/​LTTngPlaybackCustomization.cs Selects parsers and performs stream-aware lookup.
LTTngCds/​CtfExtensions/​LTTngMetadata.cs Indexes events by stream and ID.
LTTngCds/​CtfExtensions/​FolderInput/​LTTngFolderInput.cs Expands folder stream discovery.
LTTngCds/​CtfExtensions/​Descriptors/​EventDescriptor.cs Supports event-specific contexts.
LTTngCds/​CookerData/​LTTngEvent.cs Exposes stream IDs.
LinuxTraceLogCapture.md Documents CTF 2 and performance counters.
CtfUnitTest/​Ctf2MetadataTests.cs Tests CTF 2 parsing and decoding.
CtfPlayback/​Metadata/​Types/​CtfIntegerDescriptor.cs Adds big-endian integer handling.
CtfPlayback/​Metadata/​CtfVersionDetectingMetadataParser.cs Detects CTF metadata versions.
CtfPlayback/​Metadata/​CtfMetadataText.cs Reads plain or packetized metadata.
CtfPlayback/​Metadata/​CtfMetadataExtensions.cs Looks up streams by ID.
CtfPlayback/​Metadata/​Ctf2/​Ctf2VariableLengthIntegerDescriptor.cs Decodes LEB128 integers.
CtfPlayback/​Metadata/​Ctf2/​Ctf2MetadataParser.cs Maps CTF 2 metadata to descriptors.
CtfPlayback/​Metadata/​Ctf2/​Ctf2Json.cs Adds a metadata JSON reader.
CtfPlayback/​EventStreams/​Interfaces/​ICtfEvent.cs Adds stream identity to events.
CtfPlayback/​EventStreams/​CtfPacket.cs Selects streams before reading contexts.
CtfPlayback/​EventStreams/​CtfEvent.cs Uses stream-aware descriptors and contexts.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +622 to +625
private string GetFieldLocation(Dictionary<string, object> fieldClass, string property, BuildContext context)
{
return string.Join(".", GetFieldLocationPath(fieldClass, property).Select(context.MapName));
}
/// </summary>
internal static string Read(byte[] metadata)
{
if (metadata.Length < PacketHeaderSize || BitConverter.ToUInt32(metadata, 0) != PacketMagic)
private class Key
{
private string domain;
private uint streamId;
// if the byte order is "be" or "network", then it is big endian
// if the byte order is "le", then it is little endian
// Only explicitly big endian, whole-byte integers are handled for now (e.g. IPv4 addresses in network order).
if (this.IsExplicitlyBigEndian && (this.Size % 8) == 0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants