mirror of
https://github.com/element-hq/synapse.git
synced 2026-11-04 09:07:22 +00:00
Rust spans will go to the same Jaeger agent as Python's, over UDP, as the `Agent.emitBatch` oneway message in the thrift compact protocol. This adds a write-only encoder for that message and the six structs in `jaeger.thrift`, with no new dependency. Spans are encoded one at a time so a reporter knows each span's size before it joins them into a batch, and `emit_batch_len` bounds a batch's size without building it. The output matches jaeger-client's own reporter byte for byte. The golden fixture is generated by `scripts-dev/generate_jaeger_thrift_fixture.py` with jaeger-client's generated thrift code and covers every tag type, logs, a follows-from reference, a 128-bit trace ID, IDs at or above 2^63 (which thrift sends negative) and long list headers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4zxygfJC93SsugxskhoJG
166 lines
5.2 KiB
Python
Executable File
166 lines
5.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
|
#
|
|
# Copyright (C) 2026 Element Creations Ltd
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# See the GNU Affero General Public License for more details:
|
|
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
|
|
|
"""Write the golden `emitBatch` message for the Rust thrift encoder's test.
|
|
|
|
Encodes a fixed batch with jaeger-client's generated thrift code and the
|
|
compact protocol, exactly as jaeger-client's UDP reporter does, and writes the
|
|
bytes to `rust/src/logging/opentracing/testdata/emit_batch.bin`. The test
|
|
`golden_emit_batch` in `rust/src/logging/opentracing/thrift.rs` builds the same
|
|
batch and compares byte for byte, so the two must change together.
|
|
|
|
Only rerun this when jaeger-client or thrift changes version, and update the
|
|
versions recorded in that test.
|
|
|
|
Usage: poetry run python scripts-dev/generate_jaeger_thrift_fixture.py
|
|
"""
|
|
|
|
import os
|
|
from importlib.metadata import version
|
|
|
|
from jaeger_client.thrift_gen.agent import Agent
|
|
from jaeger_client.thrift_gen.jaeger import ttypes
|
|
from thrift.protocol import TCompactProtocol # type: ignore[import-untyped]
|
|
from thrift.transport import TTransport # type: ignore[import-untyped]
|
|
|
|
OUTPUT = os.path.join(
|
|
os.path.dirname(__file__),
|
|
"..",
|
|
"rust",
|
|
"src",
|
|
"logging",
|
|
"opentracing",
|
|
"testdata",
|
|
"emit_batch.bin",
|
|
)
|
|
|
|
|
|
def signed(value: int) -> int:
|
|
"""jaeger-client's `id_to_int`: thrift i64 fields are signed."""
|
|
return value - (1 << 64) if value >= (1 << 63) else value
|
|
|
|
|
|
def string_tag(key: str, value: str) -> ttypes.Tag:
|
|
return ttypes.Tag(key=key, vType=ttypes.TagType.STRING, vStr=value)
|
|
|
|
|
|
def build_batch() -> ttypes.Batch:
|
|
process = ttypes.Process(
|
|
serviceName="test.server master",
|
|
tags=[
|
|
string_tag("hostname", "host-1"),
|
|
string_tag("jaeger.version", "Python-4.8.0"),
|
|
],
|
|
)
|
|
|
|
trace_id = (0x0123456789ABCDEF << 64) | 0xFEDCBA9876543210
|
|
|
|
every_type = [
|
|
string_tag("db.statement", "SELECT 1"),
|
|
ttypes.Tag(key="ratio", vType=ttypes.TagType.DOUBLE, vDouble=-1.5),
|
|
ttypes.Tag(key="sampled", vType=ttypes.TagType.BOOL, vBool=True),
|
|
ttypes.Tag(key="error", vType=ttypes.TagType.BOOL, vBool=False),
|
|
ttypes.Tag(key="db.queued", vType=ttypes.TagType.LONG, vLong=-300),
|
|
# The generated code writes `vBinary` with `writeString`, so it takes
|
|
# a `str` and sends its UTF-8 bytes.
|
|
ttypes.Tag(key="raw", vType=ttypes.TagType.BINARY, vBinary="raw-bytes"),
|
|
]
|
|
|
|
full = ttypes.Span(
|
|
traceIdLow=signed(trace_id & ((1 << 64) - 1)),
|
|
traceIdHigh=signed(trace_id >> 64),
|
|
spanId=signed(0x8000000000000001),
|
|
parentSpanId=signed(0xFFFFFFFFFFFFFFFF),
|
|
operationName="db.query",
|
|
references=[
|
|
ttypes.SpanRef(
|
|
refType=ttypes.SpanRefType.FOLLOWS_FROM,
|
|
traceIdLow=12345,
|
|
traceIdHigh=0,
|
|
spanId=67890,
|
|
)
|
|
],
|
|
flags=1,
|
|
startTime=1_760_000_000_123_456,
|
|
duration=789,
|
|
tags=every_type,
|
|
logs=[
|
|
ttypes.Log(
|
|
timestamp=1_760_000_000_123_999,
|
|
fields=[string_tag("message", "commit")],
|
|
)
|
|
],
|
|
)
|
|
|
|
# More than 14 tags, for the long list header.
|
|
many_tags = ttypes.Span(
|
|
traceIdLow=1,
|
|
traceIdHigh=0,
|
|
spanId=2,
|
|
parentSpanId=0,
|
|
operationName="many tags",
|
|
references=None,
|
|
flags=3,
|
|
startTime=1_760_000_000_000_000,
|
|
duration=0,
|
|
tags=[string_tag(f"k{i}", "v" * i) for i in range(20)],
|
|
logs=[],
|
|
)
|
|
|
|
# A minimal span, repeated so the batch's span list needs the long header.
|
|
minimal = [
|
|
ttypes.Span(
|
|
traceIdLow=7,
|
|
traceIdHigh=0,
|
|
spanId=100 + i,
|
|
parentSpanId=7,
|
|
operationName="persist.write_chunk",
|
|
references=None,
|
|
flags=1,
|
|
startTime=1_760_000_000_000_000 + i,
|
|
duration=1_000_000 * i,
|
|
tags=[],
|
|
logs=[],
|
|
)
|
|
for i in range(15)
|
|
]
|
|
|
|
return ttypes.Batch(process=process, spans=[full, many_tags, *minimal])
|
|
|
|
|
|
def main() -> None:
|
|
buffer = TTransport.TMemoryBuffer()
|
|
# `Agent.Client.__init__` starts a Tornado receive loop on the transport,
|
|
# which a memory buffer does not have. Set up only what sending needs.
|
|
client = Agent.Client.__new__(Agent.Client)
|
|
client._transport = buffer
|
|
client._oprot_factory = TCompactProtocol.TCompactProtocolFactory()
|
|
client._seqid = 0
|
|
# The client numbers its messages from 1, so this one has sequence ID 1.
|
|
client.emitBatch(build_batch())
|
|
|
|
data = buffer.getvalue()
|
|
os.makedirs(os.path.dirname(OUTPUT), exist_ok=True)
|
|
with open(OUTPUT, "wb") as f:
|
|
f.write(data)
|
|
|
|
print(
|
|
f"Wrote {len(data)} bytes to {os.path.normpath(OUTPUT)} with "
|
|
f"jaeger-client {version('jaeger-client')}, thrift {version('thrift')}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|