@@ -19,63 +19,111 @@ use crate::compression::lz4::Lz4Decoder;
1919use crate :: {
2020 compression:: Compression ,
2121 error:: { Error , Result } ,
22+ summary_header:: Summary ,
2223} ;
2324
25+ use tracing:: { Instrument , Span } ;
26+
2427// === Response ===
2528
2629pub ( crate ) enum Response {
2730 // Headers haven't been received yet.
2831 // `Box<_>` improves performance by reducing the size of the whole future.
29- Waiting ( ResponseFuture ) ,
32+ Waiting ( ResponseFuture , Span ) ,
3033 // Headers have been received, streaming the body.
31- Loading ( Chunks ) ,
34+ Loading ( Chunks , Span ) ,
3235}
3336
3437pub ( crate ) type ResponseFuture = Pin < Box < dyn Future < Output = Result < Chunks > > + Send > > ;
3538
3639impl Response {
37- pub ( crate ) fn new ( response : HyperResponseFuture , compression : Compression ) -> Self {
38- Self :: Waiting ( Box :: pin ( async move {
39- let response = response. await ?;
40-
41- let status = response. status ( ) ;
42- let exception_code = response. headers ( ) . get ( "X-ClickHouse-Exception-Code" ) ;
43-
44- if status == StatusCode :: OK && exception_code. is_none ( ) {
45- // More likely to be successful, start streaming.
46- // It still can fail, but we'll handle it in `DetectDbException`.
47- Ok ( Chunks :: new ( response. into_body ( ) , compression) )
48- } else {
49- // An instantly failed request.
50- Err ( collect_bad_response (
51- status,
52- exception_code
53- . and_then ( |value| value. to_str ( ) . ok ( ) )
54- . map ( |code| format ! ( "Code: {code}" ) ) ,
55- response. into_body ( ) ,
56- compression,
57- )
58- . await )
40+ pub ( crate ) fn new ( response : HyperResponseFuture , compression : Compression , span : Span ) -> Self {
41+ let inner_span = span. clone ( ) ;
42+ Self :: Waiting (
43+ Box :: pin ( async move {
44+ let response = response. await ?;
45+ if let Some ( summary_header) = response. headers ( ) . get ( "x-clickhouse-summary" ) {
46+ match serde_json:: from_slice :: < Summary > ( summary_header. as_bytes ( ) ) {
47+ Ok ( summary_header) => {
48+ if let Some ( rows) = summary_header. result_rows {
49+ inner_span. record ( "db.response.returned_rows" , rows) ;
50+ }
51+ if let Some ( rows) = summary_header. read_rows {
52+ inner_span. record ( "db.response.read_rows" , rows) ;
53+ }
54+ if let Some ( rows) = summary_header. written_rows {
55+ inner_span. record ( "db.response.written_rows" , rows) ;
56+ }
57+ if let Some ( bytes) = summary_header. read_bytes {
58+ inner_span. record ( "db.response.read_bytes" , bytes) ;
59+ }
60+ if let Some ( bytes) = summary_header. written_bytes {
61+ inner_span. record ( "db.response.written_bytes" , bytes) ;
62+ }
63+ tracing:: debug!(
64+ read_rows = summary_header. read_rows,
65+ read_bytes = summary_header. read_bytes,
66+ written_rows = summary_header. written_bytes,
67+ written_bytes = summary_header. written_rows,
68+ total_rows_to_read = summary_header. total_rows_to_read,
69+ result_rows = summary_header. result_rows,
70+ result_bytes = summary_header. result_bytes,
71+ elapsed_ns = summary_header. elapsed_ns,
72+ "finished processing query"
73+ )
74+ }
75+ Err ( e) => {
76+ tracing:: warn!(
77+ error = & e as & dyn std:: error:: Error ,
78+ ?summary_header,
79+ "invalid x-clickhouse-summary header returned" ,
80+ ) ;
81+ }
82+ }
83+ }
84+
85+ let status = response. status ( ) ;
86+ let exception_code = response. headers ( ) . get ( "X-ClickHouse-Exception-Code" ) ;
87+
88+ if status == StatusCode :: OK && exception_code. is_none ( ) {
89+ inner_span. record ( "otel.status_code" , "OK" ) ;
90+ // More likely to be successful, start streaming.
91+ // It still can fail, but we'll handle it in `DetectDbException`.
92+ Ok ( Chunks :: new ( response. into_body ( ) , compression, inner_span) )
93+ } else {
94+ inner_span. record ( "otel.status_code" , "ERROR" ) ;
95+ // An instantly failed request.
96+ Err ( collect_bad_response (
97+ status,
98+ exception_code
99+ . and_then ( |value| value. to_str ( ) . ok ( ) )
100+ . map ( |code| format ! ( "Code: {code}" ) ) ,
101+ response. into_body ( ) ,
102+ compression,
103+ )
104+ . await )
59105 }
60- } ) )
106+ } ) , span )
61107 }
62108
63109 pub ( crate ) fn into_future ( self ) -> ResponseFuture {
64110 match self {
65- Self :: Waiting ( future) => future,
66- Self :: Loading ( _) => panic ! ( "response is already streaming" ) ,
111+ Self :: Waiting ( future, span ) => Box :: pin ( future. instrument ( span ) ) ,
112+ Self :: Loading ( _, _ ) => panic ! ( "response is already streaming" ) ,
67113 }
68114 }
69115
70116 pub ( crate ) async fn finish ( & mut self ) -> Result < ( ) > {
71- let chunks = loop {
117+ let ( chunks, span ) = loop {
72118 match self {
73- Self :: Waiting ( future) => * self = Self :: Loading ( future. await ?) ,
74- Self :: Loading ( chunks) => break chunks,
119+ Self :: Waiting ( future, span) => {
120+ * self = Self :: Loading ( future. instrument ( span. clone ( ) ) . await ?, span. clone ( ) )
121+ }
122+ Self :: Loading ( chunks, span) => break ( chunks, span) ,
75123 }
76124 } ;
77125
78- while chunks. try_next ( ) . await ?. is_some ( ) { }
126+ while chunks. try_next ( ) . instrument ( span . clone ( ) ) . await ?. is_some ( ) { }
79127 Ok ( ( ) )
80128 }
81129}
@@ -153,40 +201,52 @@ pub(crate) struct Chunk {
153201
154202// * Uses `Option<_>` to make this stream fused.
155203// * Uses `Box<_>` in order to reduce the size of cursors.
156- pub ( crate ) struct Chunks ( Option < Box < DetectDbException < Decompress < IncomingStream > > > > ) ;
204+ pub ( crate ) struct Chunks {
205+ stream : Option < Box < DetectDbException < Decompress < IncomingStream > > > > ,
206+ span : Option < Span > ,
207+ }
157208
158209impl Chunks {
159- fn new ( stream : Incoming , compression : Compression ) -> Self {
210+ fn new ( stream : Incoming , compression : Compression , span : Span ) -> Self {
160211 let stream = IncomingStream ( stream) ;
161212 let stream = Decompress :: new ( stream, compression) ;
162213 let stream = DetectDbException ( stream) ;
163- Self ( Some ( Box :: new ( stream) ) )
214+ Self {
215+ stream : Some ( Box :: new ( stream) ) ,
216+ span : Some ( span) ,
217+ }
164218 }
165219
166220 pub ( crate ) fn empty ( ) -> Self {
167- Self ( None )
221+ Self {
222+ stream : None ,
223+ span : None ,
224+ }
168225 }
169226
170227 #[ cfg( feature = "futures03" ) ]
171228 pub ( crate ) fn is_terminated ( & self ) -> bool {
172- self . 0 . is_none ( )
229+ self . stream . is_none ( )
173230 }
174231}
175232
176233impl Stream for Chunks {
177234 type Item = Result < Chunk > ;
178235
179236 fn poll_next ( mut self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Option < Self :: Item > > {
237+ let guard = self . span . take ( ) . map ( |s| s. entered ( ) ) ;
180238 // We use `take()` to make the stream fused, including the case of panics.
181- if let Some ( mut stream) = self . 0 . take ( ) {
239+ if let Some ( mut stream) = self . stream . take ( ) {
182240 let res = Pin :: new ( & mut stream) . poll_next ( cx) ;
183241
184242 if matches ! ( res, Poll :: Pending | Poll :: Ready ( Some ( Ok ( _) ) ) ) {
185- self . 0 = Some ( stream) ;
243+ self . stream = Some ( stream) ;
244+ self . span = guard. map ( |g| g. exit ( ) ) ;
186245 }
187246
188247 res
189248 } else {
249+ self . span = guard. map ( |g| g. exit ( ) ) ;
190250 Poll :: Ready ( None )
191251 }
192252 }
0 commit comments