Skip to content

Commit ec6dc77

Browse files
committed
refactor(stargate-bench): use sse-core for stream decoding
Add sse-core 0.2.3 under its Apache-2.0 license with only the std feature. Replace custom SSE framing with its bounded decoder and keep completion, usage, and output timing checks in the benchmark driver. Relates to #1817
1 parent 7858489 commit ec6dc77

8 files changed

Lines changed: 146 additions & 254 deletions

File tree

‎MODULE.bazel.lock‎

Lines changed: 18 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎NOTICE‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,4 +484,5 @@ The following third-party licenses are included in this repository:
484484
src/libraries/go/lib/vendor/sigs.k8s.io/structured-merge-diff/v6/LICENSE
485485
src/libraries/go/lib/vendor/sigs.k8s.io/yaml/LICENSE
486486
src/libraries/java/nv-boot-parent/NOTICE
487+
src/libraries/rust/stargate/crates/stargate-bench/NOTICE
487488
src/uis/nvcf-ui/NOTICE

‎src/libraries/rust/stargate/Cargo.lock‎

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/libraries/rust/stargate/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ serde = { version = "=1.0.228", features = ["derive"] }
6262
serde_json = "=1.0.150"
6363
serde_yaml_ng = "=0.10.0"
6464
sonic-rs = "=0.5.8"
65+
sse-core = { version = "=0.2.3", default-features = false, features = ["std"] }
6566
thiserror = "=2.0.18"
6667
tokio = { version = "=1.52.3", features = ["full"] }
6768
tokio-stream = "=0.1.18"

‎src/libraries/rust/stargate/crates/stargate-bench/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ rustls = { workspace = true }
4545
serde = { workspace = true }
4646
serde_json = { workspace = true }
4747
serde_yaml_ng = { workspace = true }
48+
sse-core = { workspace = true }
4849
stargate = { workspace = true }
4950
stargate-proto = { workspace = true }
5051
stargate-protocol = { workspace = true }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Stargate benchmark third-party software
2+
3+
sse-core 0.2.3
4+
Copyright (c) 2026 Max Shteimberg
5+
https://github.com/PizzasBear/sse-rs
6+
Used under the Apache License 2.0: https://www.apache.org/licenses/LICENSE-2.0

‎src/libraries/rust/stargate/crates/stargate-bench/src/driver.rs‎

Lines changed: 107 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16+
use std::num::NonZeroUsize;
1617
use std::path::Path;
1718
use std::str::FromStr;
1819
use std::sync::Arc;
@@ -21,12 +22,13 @@ use std::time::{Duration, Instant};
2122
use anyhow::{Context, ensure};
2223
use futures::StreamExt;
2324
use serde::{Deserialize, Serialize};
25+
use serde_json::Value;
26+
use sse_core::{SseDecoder, SseEvent};
2427
use tokio::sync::Semaphore;
2528

2629
use crate::manifest::{Manifest, ManifestRequest};
2730

28-
mod sse;
29-
use sse::CompletionStream;
31+
const MAX_SSE_PAYLOAD_BYTES: usize = 1024 * 1024;
3032

3133
#[derive(Debug, Clone)]
3234
pub struct DriveConfig {
@@ -221,30 +223,84 @@ async fn execute_request(
221223
);
222224
}
223225
let mut stream = response.bytes_stream();
224-
let mut output = CompletionStream::default();
226+
let mut decoder = SseDecoder::with_limit(NonZeroUsize::new(MAX_SSE_PAYLOAD_BYTES).unwrap());
225227
while let Some(chunk) = stream.next().await {
226-
let bytes = match chunk {
228+
let mut bytes = match chunk {
227229
Ok(bytes) => bytes,
228230
Err(error) => return result.finish(dispatch_time, Some(error.to_string())),
229231
};
230-
let progress = output.push(&bytes);
231-
result.observed_output_tokens = output.output_tokens();
232-
match progress {
233-
Ok(true) if result.first_output_ms.is_none() => {
234-
result.first_output_ms = Some(duration_ms(dispatch_time.elapsed()));
232+
while let Some(event) = decoder.next(&mut bytes) {
233+
let event = match event {
234+
Ok(SseEvent::Message(event)) => event,
235+
Ok(SseEvent::Retry(_)) => continue,
236+
Err(error) => return result.finish(dispatch_time, Some(error.to_string())),
237+
};
238+
match record_completion_event(&mut result, &event.data, dispatch_time) {
239+
Ok(true) => {
240+
result.ok = true;
241+
return result.finish(dispatch_time, None);
242+
}
243+
Ok(false) => {}
244+
Err(error) => return result.finish(dispatch_time, Some(error.to_string())),
235245
}
236-
Ok(_) => {}
237-
Err(error) => return result.finish(dispatch_time, Some(error.to_string())),
238246
}
239247
}
240-
if !output.is_complete() {
241-
return result.finish(
242-
dispatch_time,
243-
Some("upstream SSE response ended before [DONE]".into()),
248+
result.finish(
249+
dispatch_time,
250+
Some("upstream SSE response ended before [DONE]".into()),
251+
)
252+
}
253+
254+
fn record_completion_event(
255+
result: &mut RequestResult,
256+
data: &str,
257+
dispatch_time: Instant,
258+
) -> anyhow::Result<bool> {
259+
let data = data.trim();
260+
if data == "[DONE]" {
261+
return Ok(true);
262+
}
263+
if data.is_empty() {
264+
return Ok(false);
265+
}
266+
let value: Value = serde_json::from_str(data).context("invalid upstream SSE JSON")?;
267+
ensure!(
268+
value.get("error").is_none_or(Value::is_null),
269+
"upstream returned an SSE error event"
270+
);
271+
let generated_output = value["choices"].as_array().is_some_and(|choices| {
272+
choices.iter().any(|choice| {
273+
let delta = &choice["delta"];
274+
["content", "reasoning_content", "reasoning"]
275+
.iter()
276+
.any(|field| delta[*field].as_str().is_some_and(|text| !text.is_empty()))
277+
|| delta["tool_calls"].as_array().is_some_and(|calls| {
278+
calls.iter().any(|call| {
279+
call["function"]["arguments"]
280+
.as_str()
281+
.is_some_and(|arguments| !arguments.is_empty())
282+
})
283+
})
284+
})
285+
});
286+
if generated_output && result.first_output_ms.is_none() {
287+
result.first_output_ms = Some(duration_ms(dispatch_time.elapsed()));
288+
}
289+
if let Some(tokens) = value
290+
.pointer("/usage/completion_tokens")
291+
.or_else(|| value.get("output_tokens_so_far"))
292+
.filter(|tokens| !tokens.is_null())
293+
{
294+
result.observed_output_tokens = Some(
295+
tokens
296+
.as_u64()
297+
.context("upstream output token usage is not an unsigned integer")?,
244298
);
299+
} else if generated_output {
300+
// A prior cumulative counter does not cover later uncounted output.
301+
result.observed_output_tokens = None;
245302
}
246-
result.ok = true;
247-
result.finish(dispatch_time, None)
303+
Ok(false)
248304
}
249305

250306
fn duration_ms(duration: Duration) -> u64 {
@@ -325,12 +381,13 @@ mod tests {
325381
}
326382
}
327383

328-
async fn drive_test_response(body: &'static str) -> (RequestResult, serde_json::Value) {
384+
async fn drive_test_response(body: &str) -> (RequestResult, serde_json::Value) {
329385
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
330386
let endpoint = format!(
331387
"http://{}/v1/chat/completions",
332388
listener.local_addr().unwrap()
333389
);
390+
let body = body.to_owned();
334391
let server = tokio::spawn(async move {
335392
let (mut socket, _) = listener.accept().await.unwrap();
336393
let request = read_test_request(&mut socket).await;
@@ -362,7 +419,7 @@ mod tests {
362419

363420
#[tokio::test]
364421
async fn complete_response_records_actual_usage_and_requests_usage_reporting() {
365-
let (result, request) = drive_test_response("data: {\"choices\":[{\"delta\":{\"content\":\"answer\"}}],\"usage\":{\"completion_tokens\":2}}\n\ndata: [DONE]\n\n").await;
422+
let (result, request) = drive_test_response("\u{feff}data: {\"choices\":[{\"delta\":{\"content\":\"\u{03bb}\"}}]}\r\n\r\ndata: {\"usage\":{\"completion_tokens\":2}}\r\n\r\ndata: [DONE]\r\n\r\n").await;
366423
assert!(result.ok, "{:?}", result.error);
367424
assert_eq!(result.output_tokens, 100);
368425
assert_eq!(result.observed_output_tokens, Some(2));
@@ -384,6 +441,37 @@ mod tests {
384441
}
385442
}
386443

444+
#[tokio::test]
445+
async fn role_only_completion_keeps_output_timing_and_usage_unknown() {
446+
let (result, _) = drive_test_response(": keepalive\n\ndata: {\"choices\":[{\"delta\":{\"role\":\"assistant\",\"content\":\"\"}}]}\n\ndata: [DONE]\n\n").await;
447+
assert!(result.ok, "{:?}", result.error);
448+
assert_eq!(result.first_output_ms, None);
449+
assert_eq!(result.observed_output_tokens, None);
450+
}
451+
452+
#[tokio::test]
453+
async fn cumulative_usage_must_cover_the_last_generated_output() {
454+
let (result, _) = drive_test_response("data: {\"choices\":[{\"delta\":{\"content\":\"one\"}}],\"output_tokens_so_far\":1}\n\ndata: {\"choices\":[{\"delta\":{\"content\":\" two\"}}]}\n\ndata: [DONE]\n\n").await;
455+
assert!(result.ok, "{:?}", result.error);
456+
assert!(result.first_output_ms.is_some());
457+
assert_eq!(result.observed_output_tokens, None);
458+
}
459+
460+
#[tokio::test]
461+
async fn invalid_or_oversized_events_fail_the_request() {
462+
for data in [
463+
"{bad}".to_owned(),
464+
r#"{"error":{"message":"failed"}}"#.to_owned(),
465+
r#"{"usage":{"completion_tokens":-1}}"#.to_owned(),
466+
serde_json::json!({"choices": [{"delta": {"content": "x".repeat(MAX_SSE_PAYLOAD_BYTES)}}]}).to_string(),
467+
] {
468+
let (result, _) =
469+
drive_test_response(&format!("data: {data}\n\ndata: [DONE]\n\n")).await;
470+
assert!(!result.ok);
471+
assert!(result.error.is_some());
472+
}
473+
}
474+
387475
#[tokio::test]
388476
async fn scheduled_sleep_does_not_hold_concurrency_permit() {
389477
let listener = TcpListener::bind("127.0.0.1:0")

0 commit comments

Comments
 (0)