diff --git a/src/frontend/src/instance/jaeger.rs b/src/frontend/src/instance/jaeger.rs index a742dd69d4..980fd5d6f5 100644 --- a/src/frontend/src/instance/jaeger.rs +++ b/src/frontend/src/instance/jaeger.rs @@ -130,7 +130,13 @@ impl JaegerQueryHandler for Instance { .await?) } - async fn get_trace(&self, ctx: QueryContextRef, trace_id: &str) -> ServerResult { + async fn get_trace( + &self, + ctx: QueryContextRef, + trace_id: &str, + start_time: Option, + end_time: Option, + ) -> ServerResult { // It's equivalent to // // ``` @@ -139,13 +145,25 @@ impl JaegerQueryHandler for Instance { // FROM // {db}.{trace_table} // WHERE - // trace_id = '{trace_id}' + // trace_id = '{trace_id}' AND + // timestamp >= {start_time} AND + // timestamp <= {end_time} // ORDER BY // timestamp DESC // ```. let selects = vec![wildcard()]; - let filters = vec![col(TRACE_ID_COLUMN).eq(lit(trace_id))]; + let mut filters = vec![col(TRACE_ID_COLUMN).eq(lit(trace_id))]; + + if let Some(start_time) = start_time { + // Microseconds to nanoseconds. + filters.push(col(TIMESTAMP_COLUMN).gt_eq(lit_timestamp_nano(start_time * 1_000))); + } + + if let Some(end_time) = end_time { + // Microseconds to nanoseconds. + filters.push(col(TIMESTAMP_COLUMN).lt_eq(lit_timestamp_nano(end_time * 1_000))); + } Ok(query_trace_table( ctx, diff --git a/src/servers/src/http/jaeger.rs b/src/servers/src/http/jaeger.rs index 9d59c9ae29..dc102b66d7 100644 --- a/src/servers/src/http/jaeger.rs +++ b/src/servers/src/http/jaeger.rs @@ -403,7 +403,10 @@ pub async fn handle_get_trace( .with_label_values(&[&db, "/api/traces"]) .start_timer(); - let output = match handler.get_trace(query_ctx, &trace_id).await { + let output = match handler + .get_trace(query_ctx, &trace_id, query_params.start, query_params.end) + .await + { Ok(output) => output, Err(err) => { return handle_query_error( diff --git a/src/servers/src/query_handler.rs b/src/servers/src/query_handler.rs index 2e365c9b47..da7d5c38b3 100644 --- a/src/servers/src/query_handler.rs +++ b/src/servers/src/query_handler.rs @@ -203,7 +203,13 @@ pub trait JaegerQueryHandler { ) -> Result; /// Get trace by trace id. It's used for `/api/traces/{trace_id}` API. - async fn get_trace(&self, ctx: QueryContextRef, trace_id: &str) -> Result; + async fn get_trace( + &self, + ctx: QueryContextRef, + trace_id: &str, + start_time: Option, + end_time: Option, + ) -> Result; /// Find traces by query params. It's used for `/api/traces` API. async fn find_traces( diff --git a/tests-integration/tests/http.rs b/tests-integration/tests/http.rs index dad071d5d4..616c54145c 100644 --- a/tests-integration/tests/http.rs +++ b/tests-integration/tests/http.rs @@ -4542,7 +4542,7 @@ pub async fn test_jaeger_query_api_for_trace_v1(store_type: StorageType) { let expected: Value = serde_json::from_str(expected).unwrap(); assert_eq!(resp, expected); - // Test `/api/traces/{trace_id}` API. + // Test `/api/traces/{trace_id}` API without start and end. let res = client .get("/v1/jaeger/api/traces/5611dce1bc9ebed65352d99a027b08ea") .header("x-greptime-trace-table-name", trace_table_name) @@ -4658,6 +4658,122 @@ pub async fn test_jaeger_query_api_for_trace_v1(store_type: StorageType) { let expected: Value = serde_json::from_str(expected).unwrap(); assert_eq!(resp, expected); + // Test `/api/traces/{trace_id}` API with start and end in microseconds. + let res = client + .get("/v1/jaeger/api/traces/5611dce1bc9ebed65352d99a027b08ea?start=1738726754492421&end=1738726754642422") + .header("x-greptime-trace-table-name", trace_table_name) + .send() + .await; + assert_eq!(StatusCode::OK, res.status()); + let expected = r#"{ + "data": [ + { + "traceID": "5611dce1bc9ebed65352d99a027b08ea", + "spans": [ + { + "traceID": "5611dce1bc9ebed65352d99a027b08ea", + "spanID": "ffa03416a7b9ea48", + "operationName": "access-redis", + "references": [], + "startTime": 1738726754492422, + "duration": 100000, + "tags": [ + { + "key": "net.peer.ip", + "type": "string", + "value": "1.2.3.4" + }, + { + "key": "operation.type", + "type": "string", + "value": "access-redis" + }, + { + "key": "otel.scope.name", + "type": "string", + "value": "test-jaeger-query-api" + }, + { + "key": "otel.scope.version", + "type": "string", + "value": "1.0.0" + }, + { + "key": "peer.service", + "type": "string", + "value": "test-jaeger-query-api" + }, + { + "key": "span.kind", + "type": "string", + "value": "server" + } + ], + "logs": [], + "processID": "p1" + }, + { + "traceID": "5611dce1bc9ebed65352d99a027b08ea", + "spanID": "008421dbbd33a3e9", + "operationName": "access-mysql", + "references": [], + "startTime": 1738726754492421, + "duration": 100000, + "tags": [ + { + "key": "net.peer.ip", + "type": "string", + "value": "1.2.3.4" + }, + { + "key": "operation.type", + "type": "string", + "value": "access-mysql" + }, + { + "key": "otel.scope.name", + "type": "string", + "value": "test-jaeger-query-api" + }, + { + "key": "otel.scope.version", + "type": "string", + "value": "1.0.0" + }, + { + "key": "peer.service", + "type": "string", + "value": "test-jaeger-query-api" + }, + { + "key": "span.kind", + "type": "string", + "value": "server" + } + ], + "logs": [], + "processID": "p1" + } + ], + "processes": { + "p1": { + "serviceName": "test-jaeger-query-api", + "tags": [] + } + } + } + ], + "total": 0, + "limit": 0, + "offset": 0, + "errors": [] +} +"#; + + let resp: Value = serde_json::from_str(&res.text().await).unwrap(); + let expected: Value = serde_json::from_str(expected).unwrap(); + assert_eq!(resp, expected); + // Test `/api/traces` API. let res = client .get("/v1/jaeger/api/traces?service=test-jaeger-query-api&operation=access-mysql&start=1738726754492421&end=1738726754642422&tags=%7B%22operation.type%22%3A%22access-mysql%22%7D")