[SGLang Tracing] Add pd disaggregation mooncake backend tracing (#23755)

Co-authored-by: Mu Huai <tianbowen.tbw@antgroup.com>
Co-authored-by: Shangming Cai <csmthu@gmail.com>
This commit is contained in:
Feng Su
2026-06-03 16:43:29 +08:00
committed by GitHub
co-authored by Mu Huai Shangming Cai
parent 73b53e7a87
commit e67810bea7
9 changed files with 270 additions and 14 deletions
@@ -814,6 +814,12 @@ Please consult the documentation below and [server_args.py](https://github.com/s
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>False</code></td> <td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>False</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>bool flag (set to enable)</td> <td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>bool flag (set to enable)</td>
</tr> </tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}><code>--trace-modules</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Select the components to trace. Available options are 'request' and 'mooncake'. Format: &lt;module1 name&gt;,&lt;module2 name&gt;,......</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>request</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Type: str</td>
</tr>
<tr> <tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}>`--otlp-traces-endpoint`</td> <td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}>`--otlp-traces-endpoint`</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Config opentelemetry collector endpoint if --enable-trace is set. format: &lt;ip&gt;:&lt;port&gt;</td> <td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Config opentelemetry collector endpoint if --enable-trace is set. format: &lt;ip&gt;:&lt;port&gt;</td>
@@ -3,11 +3,16 @@ import dataclasses
import struct import struct
import threading import threading
from collections import deque from collections import deque
from typing import List, Optional, Tuple from typing import List, Optional, Tuple, Union
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
from sglang.srt.observability.trace import (
TraceNullContext,
TraceReqContext,
)
@dataclasses.dataclass @dataclasses.dataclass
class TransferKVChunk: class TransferKVChunk:
@@ -20,6 +25,9 @@ class TransferKVChunk:
prefill_aux_index: Optional[int] prefill_aux_index: Optional[int]
state_indices: Optional[List] state_indices: Optional[List]
chunk_id: Optional[int] = None chunk_id: Optional[int] = None
trace_ctx: Union[TraceReqContext, TraceNullContext] = dataclasses.field(
default_factory=TraceNullContext
)
def pack_list_of_buffers(buffers: List[bytes]) -> bytes: def pack_list_of_buffers(buffers: List[bytes]) -> bytes:
@@ -8,7 +8,7 @@ import struct
import threading import threading
import time import time
from collections import defaultdict from collections import defaultdict
from typing import List, Optional, Tuple from typing import List, Optional, Tuple, Union
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
@@ -41,6 +41,16 @@ from sglang.srt.disaggregation.mooncake.utils import (
from sglang.srt.disaggregation.utils import DisaggregationMode from sglang.srt.disaggregation.utils import DisaggregationMode
from sglang.srt.distributed.parallel_state import get_mooncake_transfer_engine from sglang.srt.distributed.parallel_state import get_mooncake_transfer_engine
from sglang.srt.environ import envs from sglang.srt.environ import envs
from sglang.srt.observability.mooncake_trace import (
MooncakeRequestStage,
mooncake_trace_func,
mooncake_trace_slice,
)
from sglang.srt.observability.trace import (
TraceNullContext,
TraceReqContext,
trace_set_thread_info,
)
from sglang.srt.server_args import ServerArgs from sglang.srt.server_args import ServerArgs
from sglang.srt.utils.network import NetworkAddress from sglang.srt.utils.network import NetworkAddress
@@ -163,6 +173,7 @@ class MooncakeKVManager(CommonKVManager):
self.init_engine() self.init_engine()
self.register_buffer_to_engine() self.register_buffer_to_engine()
self.enable_staging = envs.SGLANG_DISAGG_STAGING_BUFFER.get() self.enable_staging = envs.SGLANG_DISAGG_STAGING_BUFFER.get()
self.enable_trace = server_args.enable_trace
if self.disaggregation_mode == DisaggregationMode.PREFILL: if self.disaggregation_mode == DisaggregationMode.PREFILL:
self.start_prefill_thread() self.start_prefill_thread()
self.session_failures = defaultdict(int) self.session_failures = defaultdict(int)
@@ -208,6 +219,7 @@ class MooncakeKVManager(CommonKVManager):
if self.enable_staging and self._staging_ctx.buffers if self.enable_staging and self._staging_ctx.buffers
else None else None
), ),
i,
), ),
daemon=True, daemon=True,
).start() ).start()
@@ -1136,12 +1148,26 @@ class MooncakeKVManager(CommonKVManager):
queue: FastQueue, queue: FastQueue,
executor: concurrent.futures.ThreadPoolExecutor, executor: concurrent.futures.ThreadPoolExecutor,
staging_buffer=None, staging_buffer=None,
worker_index=0,
): ):
staging_strategy = None staging_strategy = None
if self.enable_trace:
trace_set_thread_info(
f"mooncake transfer worker {worker_index}",
tp_rank=self.attn_tp_rank,
dp_rank=self.attn_dp_rank,
)
while True: while True:
try: try:
kv_chunk: TransferKVChunk = queue.get() kv_chunk: TransferKVChunk = queue.get()
if self.enable_trace:
kv_chunk.trace_ctx.rebuild_thread_context()
kv_chunk.trace_ctx.trace_slice_start(
MooncakeRequestStage.MOONCAKE_WORKER_SEND.stage_name,
MooncakeRequestStage.MOONCAKE_WORKER_SEND.level,
)
if ( if (
self.enable_staging self.enable_staging
and staging_strategy is None and staging_strategy is None
@@ -1165,6 +1191,7 @@ class MooncakeKVManager(CommonKVManager):
# the chunk is re-enqueued and we break out of the req loop to retry later. # the chunk is re-enqueued and we break out of the req loop to retry later.
staging_deferred = False staging_deferred = False
for req in reqs_to_be_processed: for req in reqs_to_be_processed:
start_ts = time.perf_counter()
if not req.is_dummy: if not req.is_dummy:
# Early exit if the request has failed # Early exit if the request has failed
with self.session_lock: with self.session_lock:
@@ -1305,6 +1332,20 @@ class MooncakeKVManager(CommonKVManager):
if kv_chunk.is_last_chunk and req.room in self.request_status: if kv_chunk.is_last_chunk and req.room in self.request_status:
self.update_status(req.room, KVPoll.Success) self.update_status(req.room, KVPoll.Success)
if self.enable_trace:
mooncake_trace_slice(
kv_chunk.trace_ctx,
MooncakeRequestStage.MOONCAKE_WORKER_SEND_SESSION,
start_ts,
)
if self.enable_trace:
kv_chunk.trace_ctx.trace_slice_end(
MooncakeRequestStage.MOONCAKE_WORKER_SEND.stage_name,
MooncakeRequestStage.MOONCAKE_WORKER_SEND.level,
thread_finish_flag=True,
)
if staging_deferred: if staging_deferred:
continue continue
@@ -1455,6 +1496,7 @@ class MooncakeKVManager(CommonKVManager):
is_last_chunk: bool, is_last_chunk: bool,
aux_index: Optional[int] = None, aux_index: Optional[int] = None,
state_indices: Optional[List] = None, state_indices: Optional[List] = None,
trace_ctx: Optional[Union[TraceReqContext, TraceNullContext]] = None,
): ):
assert self.disaggregation_mode == DisaggregationMode.PREFILL assert self.disaggregation_mode == DisaggregationMode.PREFILL
assert not is_last_chunk or (is_last_chunk and aux_index is not None) assert not is_last_chunk or (is_last_chunk and aux_index is not None)
@@ -1481,6 +1523,9 @@ class MooncakeKVManager(CommonKVManager):
session_port_sum = sum(int(session.rsplit(":", 1)[1]) for session in dst_infos) session_port_sum = sum(int(session.rsplit(":", 1)[1]) for session in dst_infos)
shard_idx = session_port_sum % len(self.transfer_queues) shard_idx = session_port_sum % len(self.transfer_queues)
if trace_ctx is None:
trace_ctx = TraceNullContext()
self.transfer_queues[shard_idx].put( self.transfer_queues[shard_idx].put(
TransferKVChunk( TransferKVChunk(
room=bootstrap_room, room=bootstrap_room,
@@ -1489,6 +1534,7 @@ class MooncakeKVManager(CommonKVManager):
is_last_chunk=is_last_chunk, is_last_chunk=is_last_chunk,
prefill_aux_index=aux_index, prefill_aux_index=aux_index,
state_indices=state_indices, state_indices=state_indices,
trace_ctx=trace_ctx,
) )
) )
@@ -1584,7 +1630,9 @@ class MooncakeKVSender(CommonKVSender):
super().__init__(mgr, bootstrap_addr, bootstrap_room, dest_tp_ranks, pp_rank) super().__init__(mgr, bootstrap_addr, bootstrap_room, dest_tp_ranks, pp_rank)
self.conclude_state = None self.conclude_state = None
self.init_time = time.time() self.init_time = time.time()
self._init_trace_ctx()
@mooncake_trace_func(MooncakeRequestStage.MOONCAKE_SEND)
def send( def send(
self, self,
kv_indices: npt.NDArray[np.int32], kv_indices: npt.NDArray[np.int32],
@@ -1602,6 +1650,7 @@ class MooncakeKVSender(CommonKVSender):
kv_indices, kv_indices,
index_slice, index_slice,
False, False,
trace_ctx=self.trace_ctx.copy_for_thread(),
) )
else: else:
self.kv_mgr.add_transfer_request( self.kv_mgr.add_transfer_request(
@@ -1611,6 +1660,7 @@ class MooncakeKVSender(CommonKVSender):
True, True,
aux_index=self.aux_index, aux_index=self.aux_index,
state_indices=state_indices, state_indices=state_indices,
trace_ctx=self.trace_ctx.copy_for_thread(),
) )
self._record_transfer_indices(kv_indices, state_indices) self._record_transfer_indices(kv_indices, state_indices)
@@ -1619,6 +1669,7 @@ class MooncakeKVSender(CommonKVSender):
status = self.kv_mgr.check_status(self.bootstrap_room) status = self.kv_mgr.check_status(self.bootstrap_room)
if status in (KVPoll.Success, KVPoll.Failed): if status in (KVPoll.Success, KVPoll.Failed):
self.conclude_state = status self.conclude_state = status
self.trace_ctx.trace_req_finish()
elif status == KVPoll.Bootstrapping: elif status == KVPoll.Bootstrapping:
timeout_result = self._check_bootstrap_timeout() timeout_result = self._check_bootstrap_timeout()
if timeout_result is not None: if timeout_result is not None:
@@ -1641,6 +1692,26 @@ class MooncakeKVSender(CommonKVSender):
) )
raise KVTransferError(self.bootstrap_room, failure_reason) raise KVTransferError(self.bootstrap_room, failure_reason)
def _init_trace_ctx(self):
if self.kv_mgr.enable_trace:
self.trace_ctx = TraceReqContext(
rid=str(hex(self.bootstrap_room)),
bootstrap_room=self.bootstrap_room,
role="Sender",
module_name="mooncake",
)
if not self.trace_ctx.tracing_enable:
self.trace_ctx = TraceNullContext()
else:
self.trace_ctx = TraceNullContext()
self.trace_ctx.trace_req_start()
def abort(self):
super().abort()
self.trace_ctx.abort(abort_info={"reason": "Aborted"})
self.trace_ctx.trace_req_finish()
class MooncakeKVReceiver(CommonKVReceiver): class MooncakeKVReceiver(CommonKVReceiver):
def __init__( def __init__(
@@ -0,0 +1,68 @@
import time
from typing import Union
from sglang.srt.observability.req_time_stats import (
RequestStageConfig,
convert_time_to_realtime_ns,
)
from sglang.srt.observability.trace import TraceNullContext, TraceReqContext
class MooncakeRequestStage:
MOONCAKE_SEND = RequestStageConfig(
"mooncake_send",
level=1,
)
MOONCAKE_RECV = RequestStageConfig(
"mooncake_recv",
level=1,
)
MOONCAKE_WORKER_SEND = RequestStageConfig(
"mooncake_worker_send",
level=1,
)
MOONCAKE_WORKER_SEND_SESSION = RequestStageConfig(
"mooncake_worker_send_session",
level=2,
)
MOONCAKE_WORKER_RECV = RequestStageConfig(
"mooncake_worker_recv",
level=1,
)
def mooncake_trace_slice(
trace_ctx: Union[TraceReqContext, TraceNullContext],
stage: RequestStageConfig,
start_ts: float,
thread_finish_flag=False,
):
if trace_ctx is None:
return
if not trace_ctx.tracing_enable:
return
start_ts = convert_time_to_realtime_ns(start_ts)
trace_ctx.trace_slice_start(stage.stage_name, stage.level, start_ts)
trace_ctx.trace_slice_end(
stage.stage_name,
stage.level,
thread_finish_flag=thread_finish_flag,
)
def mooncake_trace_func(stage: RequestStageConfig):
def decorator(func):
def wrapper(self, *args, **kwargs):
if self.trace_ctx is None:
return func(self, *args, **kwargs)
start_ts = convert_time_to_realtime_ns(time.perf_counter())
self.trace_ctx.trace_slice_start(stage.stage_name, stage.level, start_ts)
ret = func(self, *args, **kwargs)
self.trace_ctx.trace_slice_end(stage.stage_name, stage.level)
return ret
return wrapper
return decorator
+71 -3
View File
@@ -22,10 +22,14 @@ import threading
import time import time
import uuid import uuid
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, List, Mapping, Optional from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional
from sglang.srt.server_args import get_global_server_args
from sglang.srt.utils import get_int_env_var from sglang.srt.utils import get_int_env_var
if TYPE_CHECKING:
from sglang.srt.server_args import ServerArgs
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
opentelemetry_imported = False opentelemetry_imported = False
opentelemetry_initialized = False opentelemetry_initialized = False
@@ -137,7 +141,7 @@ if hasattr(time, "time_ns"):
get_cur_time_ns = lambda: int(time.time_ns()) get_cur_time_ns = lambda: int(time.time_ns())
def __get_host_id() -> str: def _get_host_id() -> str:
""" """
In distributed tracing systems, obtain a unique node identifier In distributed tracing systems, obtain a unique node identifier
and inject it into all subsequently generated spans and inject it into all subsequently generated spans
@@ -237,7 +241,7 @@ def trace_set_thread_info(
return return
threads_info[pid] = TraceThreadInfo( threads_info[pid] = TraceThreadInfo(
host_id=__get_host_id(), host_id=_get_host_id(),
pid=pid, pid=pid,
thread_label=thread_label, thread_label=thread_label,
tp_rank=tp_rank, tp_rank=tp_rank,
@@ -259,6 +263,10 @@ class TraceReqContext:
self.trace_level = global_trace_level self.trace_level = global_trace_level
self.tracing_enable: bool = opentelemetry_initialized and self.trace_level > 0 self.tracing_enable: bool = opentelemetry_initialized and self.trace_level > 0
server_args: ServerArgs = get_global_server_args()
if module_name not in server_args.trace_modules.split(","):
self.tracing_enable = False
if not self.tracing_enable: if not self.tracing_enable:
return return
@@ -386,6 +394,66 @@ class TraceReqContext:
) )
self.events_cache = [] self.events_cache = []
def copy_for_thread(self) -> "TraceReqContext":
"""
Create a copy of this context for use in another thread.
The copy shares the same root_span_context but has its own thread_context.
This is useful for propagating trace context across threads (e.g., worker threads).
Usage:
# Sender (main thread)
trace_ctx_copy = trace_ctx.copy_for_thread()
queue.put(TransferKVChunk(..., trace_ctx=trace_ctx_copy))
# Receiver (worker thread)
kv_chunk = queue.get()
kv_chunk.trace_ctx.rebuild_thread_context()
"""
# Fast path: not tracing
if not self.tracing_enable or not self.root_span_context:
return TraceNullContext()
# Extract prev_span_context from current thread state
prev_span_context = self.last_span_context
if self.thread_context and self.thread_context.cur_slice_stack:
cur_slice = self.thread_context.cur_slice_stack[0]
if cur_slice.span:
prev_span_context = cur_slice.span.get_span_context()
# Create new instance with shared state
copied = TraceReqContext.__new__(TraceReqContext)
copied.tracing_enable = self.tracing_enable
copied.rid = self.rid
copied.bootstrap_room = self.bootstrap_room
copied.start_time_ns = self.start_time_ns
copied.role = self.role
copied.trace_level = self.trace_level
copied.module_name = self.module_name
copied.is_copy = True # Mark as copy
copied.pid = self.pid
# thread_context is None, will be rebuilt via rebuild_thread_context()
copied.thread_context = None
copied.root_span = None
# Share root_span_context (already a context, no need to serialize)
copied.root_span_context = self.root_span_context
# Set prev_span_context for linking spans
if prev_span_context:
copied.last_span_context = trace.span.SpanContext(
trace_id=prev_span_context.trace_id,
span_id=prev_span_context.span_id,
is_remote=True,
)
else:
copied.last_span_context = None
copied.events_cache = []
return copied
def rebuild_thread_context(self, ts: Optional[int] = None): def rebuild_thread_context(self, ts: Optional[int] = None):
if not self.tracing_enable: if not self.tracing_enable:
return return
+8
View File
@@ -494,6 +494,7 @@ class ServerArgs:
forward_pass_metrics_worker_id: str = "" forward_pass_metrics_worker_id: str = ""
forward_pass_metrics_ipc_name: Optional[str] = None forward_pass_metrics_ipc_name: Optional[str] = None
enable_trace: bool = False enable_trace: bool = False
trace_modules: str = "request"
otlp_traces_endpoint: str = "localhost:4317" otlp_traces_endpoint: str = "localhost:4317"
# RequestMetricsExporter configuration # RequestMetricsExporter configuration
@@ -5258,6 +5259,13 @@ class ServerArgs:
action="store_true", action="store_true",
help="Enable opentelemetry trace", help="Enable opentelemetry trace",
) )
parser.add_argument(
"--trace-modules",
type=str,
default="request",
help="Select the components to trace. Available options are 'request' and 'mooncake'. Format: <module1 name>,<module2 name>,...",
)
parser.add_argument( parser.add_argument(
"--otlp-traces-endpoint", "--otlp-traces-endpoint",
type=str, type=str,
+2 -1
View File
@@ -178,7 +178,8 @@ def build_otel_span_tree(otel_spans):
for span in otel_spans: for span in otel_spans:
parent_span_id = span.get("parentSpanId", "") parent_span_id = span.get("parentSpanId", "")
if span.get("attributes", {}).get("module") == "sglang::request": module_name = span.get("attributes", {}).get("module", "")
if module_name == "sglang::request" or module_name == "sglang::mooncake":
root_spans.append(span) root_spans.append(span)
elif parent_span_id in span_id_map: elif parent_span_id in span_id_map:
parent_span = span_id_map[parent_span_id] parent_span = span_id_map[parent_span_id]
@@ -15,6 +15,7 @@ from urllib.parse import urlparse
import requests import requests
from sglang.srt.observability.mooncake_trace import MooncakeRequestStage
from sglang.srt.observability.req_time_stats import RequestStage from sglang.srt.observability.req_time_stats import RequestStage
from sglang.srt.utils import kill_process_tree from sglang.srt.utils import kill_process_tree
from sglang.test.ci.ci_register import register_cuda_ci from sglang.test.ci.ci_register import register_cuda_ci
@@ -78,6 +79,8 @@ class TestTraceDisaggregation(CustomTestCase):
"--enable-trace", "--enable-trace",
"--otlp-traces-endpoint", "--otlp-traces-endpoint",
"localhost:4317", "localhost:4317",
"--trace-modules",
"request,mooncake",
] ]
prefill_args += cls.transfer_backend + cls.rdma_devices prefill_args += cls.transfer_backend + cls.rdma_devices
cls.process_prefill = popen_launch_pd_server( cls.process_prefill = popen_launch_pd_server(
@@ -186,9 +189,7 @@ class TestTraceDisaggregation(CustomTestCase):
def test_disaggregation_transfer_spans(self): def test_disaggregation_transfer_spans(self):
"""Test that disaggregation produces PREFILL_TRANSFER_KV_CACHE and DECODE_TRANSFERRED spans.""" """Test that disaggregation produces PREFILL_TRANSFER_KV_CACHE and DECODE_TRANSFERRED spans."""
# Set trace level # Set trace level
response = requests.get(f"{self.prefill_url}/set_trace_level?level=1") response = requests.get(f"{self.prefill_url}/set_trace_level?level=2")
self.assertEqual(response.status_code, 200)
response = requests.get(f"{self.decode_url}/set_trace_level?level=1")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.collector.clear() self.collector.clear()
@@ -221,13 +222,14 @@ class TestTraceDisaggregation(CustomTestCase):
# Check for transfer-related spans # Check for transfer-related spans
self.assertTrue( self.assertTrue(
self.collector.has_any_span( self.collector.has_all_spans(
[ [
RequestStage.PREFILL_TRANSFER_KV_CACHE.stage_name, RequestStage.PREFILL_TRANSFER_KV_CACHE.stage_name,
RequestStage.DECODE_TRANSFERRED.stage_name, RequestStage.DECODE_TRANSFERRED.stage_name,
MooncakeRequestStage.MOONCAKE_WORKER_SEND.stage_name,
] ]
), ),
f"Expected disaggregation transfer spans, got {sorted(span_names)}", f"Expected all disaggregation transfer spans, got {sorted(span_names)}",
) )
@@ -8,7 +8,7 @@ register_cpu_ci(est_time=6, suite="base-a-test-cpu")
import threading import threading
import unittest import unittest
from unittest.mock import patch from unittest.mock import MagicMock, patch
import sglang.srt.observability.trace as mod import sglang.srt.observability.trace as mod
from sglang.srt.observability.trace import ( from sglang.srt.observability.trace import (
@@ -38,7 +38,7 @@ except ImportError:
_has_otel = False _has_otel = False
# Access the private module-level function (avoid name mangling inside classes). # Access the private module-level function (avoid name mangling inside classes).
_get_host_id = getattr(mod, "__get_host_id") _get_host_id = getattr(mod, "_get_host_id")
class TestTraceFunctions(unittest.TestCase): class TestTraceFunctions(unittest.TestCase):
@@ -195,12 +195,25 @@ class TestProcessTracingInit(unittest.TestCase):
mod.opentelemetry_imported = orig mod.opentelemetry_imported = orig
def _mock_get_global_server_args():
"""Return a mock ServerArgs for tests that create TraceReqContext."""
mock = MagicMock()
mock.trace_modules = ""
return mock
class TestTraceReqContextDisabled(unittest.TestCase): class TestTraceReqContextDisabled(unittest.TestCase):
def setUp(self): def setUp(self):
self.orig = mod.opentelemetry_initialized self.orig = mod.opentelemetry_initialized
mod.opentelemetry_initialized = False mod.opentelemetry_initialized = False
self._sa_patcher = patch(
"sglang.srt.observability.trace.get_global_server_args",
side_effect=_mock_get_global_server_args,
)
self._sa_patcher.start()
def tearDown(self): def tearDown(self):
self._sa_patcher.stop()
mod.opentelemetry_initialized = self.orig mod.opentelemetry_initialized = self.orig
def test_init_disabled(self): def test_init_disabled(self):
@@ -246,13 +259,24 @@ class TestTraceReqContextEnabled(unittest.TestCase):
self.orig_threads = mod.threads_info.copy() self.orig_threads = mod.threads_info.copy()
self.orig_level = mod.global_trace_level self.orig_level = mod.global_trace_level
# Reset OTel global TracerProvider so set_tracer_provider works each test
otel_trace._TRACER_PROVIDER_SET_ONCE._done = False
otel_trace._TRACER_PROVIDER = None
self.provider = TracerProvider() self.provider = TracerProvider()
otel_trace.set_tracer_provider(self.provider) otel_trace.set_tracer_provider(self.provider)
mod.opentelemetry_initialized = True mod.opentelemetry_initialized = True
mod.tracer = otel_trace.get_tracer("test") mod.tracer = otel_trace.get_tracer("test")
mod.global_trace_level = 3 mod.global_trace_level = 3
self._sa_patcher = patch(
"sglang.srt.observability.trace.get_global_server_args",
side_effect=_mock_get_global_server_args,
)
self._sa_patcher.start()
def tearDown(self): def tearDown(self):
self._sa_patcher.stop()
mod.opentelemetry_initialized = self.orig_initialized mod.opentelemetry_initialized = self.orig_initialized
mod.tracer = self.orig_tracer mod.tracer = self.orig_tracer
mod.threads_info.clear() mod.threads_info.clear()
@@ -272,7 +296,7 @@ class TestTraceReqContextEnabled(unittest.TestCase):
def test_full_lifecycle(self): def test_full_lifecycle(self):
"""Start → slice_start → slice_end → finish.""" """Start → slice_start → slice_end → finish."""
ctx = TraceReqContext(rid="req-1", role="unified", module_name="test") ctx = TraceReqContext(rid="req-1", role="unified")
self.assertTrue(ctx.tracing_enable) self.assertTrue(ctx.tracing_enable)
ctx.trace_req_start(ts=1000) ctx.trace_req_start(ts=1000)