1use super::channel_resolver::BoxCloneSyncChannel;
2use super::errors::{map_flight_to_datafusion_error, map_status_to_datafusion_error};
3use super::generated::worker as pb;
4use super::metrics_proto::metrics_set_proto_to_df;
5use crate::common::serialize_uuid;
6use crate::grpc::generated::worker::FlightAppMetadata;
7use crate::grpc::on_drop_stream::on_drop_stream;
8use crate::{
9 BytesMetricExt, CoordinatorToWorkerMsg, DISTRIBUTED_DATAFUSION_TASK_ID_LABEL,
10 DistributedConfig, ExecuteTaskRequest, FirstLatencyMetric, GetWorkerInfoRequest,
11 GetWorkerInfoResponse, LatencyMetricExt, LoadInfo, MaxLatencyMetric, MaybeEncoded,
12 MinLatencyMetric, P50LatencyMetric, P95LatencyMetric, ProducerHead, SetPlanRequest, TaskKey,
13 TaskMetrics, WorkUnitBatch, WorkUnitFeedDeclaration, WorkUnitMsg, WorkerChannel,
14 WorkerToCoordinatorMsg,
15};
16use arrow_flight::FlightData;
17use arrow_flight::decode::FlightRecordBatchStream;
18use arrow_flight::error::FlightError;
19use async_trait::async_trait;
20use datafusion::arrow::array::RecordBatch;
21use datafusion::common::instant::Instant;
22use datafusion::common::runtime::SpawnedTask;
23use datafusion::common::{DataFusionError, Result};
24use datafusion::execution::TaskContext;
25use datafusion::execution::memory_pool::MemoryConsumer;
26use datafusion::physical_expr_common::metrics::{Count, Label, MetricBuilder, MetricValue, Time};
27use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
28use futures::stream::BoxStream;
29use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
30use http::{Extensions, HeaderMap};
31use pin_project::{pin_project, pinned_drop};
32use prost::Message;
33use std::borrow::Cow;
34use std::pin::Pin;
35use std::sync::Arc;
36use std::sync::atomic::{AtomicUsize, Ordering};
37use std::task::{Context, Poll};
38use std::time::{Duration, SystemTime, UNIX_EPOCH};
39use tokio::sync::Notify;
40use tokio::sync::mpsc::UnboundedSender;
41use tokio_stream::wrappers::UnboundedReceiverStream;
42use tokio_util::sync::CancellationToken;
43use tonic::metadata::MetadataMap;
44use tonic::{Request, Status};
45
46#[async_trait]
47impl WorkerChannel for pb::worker_service_client::WorkerServiceClient<BoxCloneSyncChannel> {
48 async fn coordinator_channel(
49 &mut self,
50 headers: HeaderMap,
51 set_plan_request: SetPlanRequest,
52 c2w_stream: BoxStream<'static, CoordinatorToWorkerMsg>,
53 metrics: ExecutionPlanMetricsSet,
54 ctx: &Arc<TaskContext>,
55 ) -> Result<BoxStream<'static, Result<WorkerToCoordinatorMsg>>> {
56 let set_plan_request = encode_set_plan_request(set_plan_request, ctx)?;
57 let plan_bytes_sent = set_plan_request.plan_proto.len();
58 let input_stream = futures::stream::once(async move {
59 pb::CoordinatorToWorkerMsg {
60 inner: Some(pb::coordinator_to_worker_msg::Inner::SetPlanRequest(
61 set_plan_request,
62 )),
63 }
64 })
65 .chain(c2w_stream.map(encode_coordinator_to_worker_msg));
66
67 let output_stream = self
68 .coordinator_channel(Request::from_parts(
69 MetadataMap::from_headers(headers),
70 Extensions::default(),
71 input_stream,
72 ))
73 .boxed()
74 .await
75 .map_err(map_status_to_datafusion_error)?
76 .into_inner()
77 .map_err(map_status_to_datafusion_error)
78 .map(|msg| decode_worker_to_coordinator_msg(msg?))
79 .boxed();
80
81 MetricBuilder::new(&metrics)
82 .with_label(Label::new(DISTRIBUTED_DATAFUSION_TASK_ID_LABEL, "0"))
83 .bytes_counter("plan_bytes_sent")
84 .add_bytes(plan_bytes_sent);
85
86 Ok(output_stream)
87 }
88
89 async fn execute_task(
90 &mut self,
91 headers: HeaderMap,
92 request: ExecuteTaskRequest,
93 metrics: ExecutionPlanMetricsSet,
94 ctx: &Arc<TaskContext>,
95 ) -> Result<Vec<BoxStream<'static, Result<RecordBatch>>>> {
96 let d_cfg = DistributedConfig::from_session_config(ctx.session_config())?;
97 let buffer_budget_bytes = d_cfg.worker_connection_buffer_budget_bytes;
98
99 let memory_reservation =
102 Arc::new(MemoryConsumer::new("WorkerConnection").register(ctx.memory_pool()));
103 let memory_reservation_clone = Arc::clone(&memory_reservation);
104
105 let mut curr_max_mem = 0;
107 let max_mem_used = MetricBuilder::new(&metrics).global_gauge("max_mem_used");
108 let bytes_transferred = MetricBuilder::new(&metrics).bytes_counter("bytes_transferred");
110 let msg_count = MetricBuilder::new(&metrics).global_counter("msg_count");
111 let mut latency_metrics = NetworkLatencyMetrics::new(&metrics);
113 let elapsed_compute = Time::new();
115 let elapsed_compute_clone = elapsed_compute.clone();
116 MetricBuilder::new(&metrics).build(MetricValue::ElapsedCompute(elapsed_compute.clone()));
117
118 let target_partition_range = request.target_partition_start..request.target_partition_end;
119 let request = pb::ExecuteTaskRequest {
120 task_key: Some(encode_task_key(request.task_key)),
121 target_partition_start: request.target_partition_start as u64,
122 target_partition_end: request.target_partition_end as u64,
123 producer_head: Some(encode_producer_head(request.producer_head, ctx)?),
124 };
125 let metadata = MetadataMap::from_headers(headers);
126
127 let mut per_partition_tx = Vec::with_capacity(target_partition_range.len());
134 let mut per_partition_rx = Vec::with_capacity(target_partition_range.len());
135 for _partition in target_partition_range.clone() {
136 let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<WorkerMsg>();
137 per_partition_tx.push(tx);
138 per_partition_rx.push(rx);
139 }
140
141 let mem_available_notify = Arc::new(Notify::new());
142 let mem_available_notify_for_task = Arc::clone(&mem_available_notify);
143
144 let first_poll_notify = Arc::new(Notify::new());
145 let first_poll_notify_for_task = Arc::clone(&first_poll_notify);
146
147 let cancel_token = CancellationToken::new();
150 let cancel = cancel_token.clone();
151
152 let mut self_clone = self.clone();
153 let request_for_task = request.clone();
154 let metadata_for_task = metadata.clone();
155
156 let task = SpawnedTask::spawn(async move {
160 tokio::select! {
161 biased;
162 _ = cancel.cancelled() => {
163 let _ = self_clone.execute_task(Request::from_parts(
168 metadata_for_task,
169 Extensions::default(),
170 request_for_task,
171 )).await;
172 return
173 },
174 _ = first_poll_notify_for_task.notified() => {}
175 }
176
177 let request = Request::from_parts(
178 metadata_for_task,
179 Extensions::default(),
180 request_for_task,
181 );
182 let mut interleaved_stream = match self_clone.execute_task(request).await {
183 Ok(v) => v.into_inner(),
184 Err(err) => return fanout(&per_partition_tx, err),
185 };
186
187 loop {
188 while memory_reservation.size() >= buffer_budget_bytes {
195 tokio::select! {
196 biased;
197 _ = cancel.cancelled() => return,
198 _ = mem_available_notify_for_task.notified() => {}
199 }
200 }
201
202 let flight_data = tokio::select! {
204 biased;
205 _ = cancel.cancelled() => return,
206 msg = interleaved_stream.next() => {
207 match msg {
208 Some(Ok(v)) => v,
209 Some(Err(err)) => return fanout(&per_partition_tx, err),
210 None => return, }
212 }
213 };
214
215 let msg_received_time = SystemTime::now();
217
218 let flight_metadata = match FlightAppMetadata::decode(flight_data.app_metadata.as_ref()) {
219 Ok(v) => v,
220 Err(err) => {
221 return fanout(&per_partition_tx, Status::internal(err.to_string()));
222 }
223 };
224
225 let sent_time = UNIX_EPOCH + Duration::from_nanos(flight_metadata.created_timestamp_unix_nanos);
227 if flight_metadata.created_timestamp_unix_nanos > 0
228 && let Ok(delta) = msg_received_time.duration_since(sent_time) {
229 latency_metrics.add_duration(delta);
230 }
231
232 let partition = flight_metadata.partition as usize;
233 let Some(sender_i) = partition.checked_sub(target_partition_range.start) else {
237 let msg = format!(
238 "Received partition {partition} in Flight metadata, but available partitions are {target_partition_range:?}"
239 );
240 return fanout(&per_partition_tx, Status::internal(msg));
241 };
242
243 let Some(o_tx) = per_partition_tx.get(sender_i) else {
244 let msg = format!(
245 "Received partition {partition} in Flight metadata, but available partitions are {target_partition_range:?}"
246 );
247 return fanout(&per_partition_tx, Status::internal(msg));
248 };
249
250 let size = flight_data.encoded_len();
255 memory_reservation.grow(size);
256
257 msg_count.add(1);
259 bytes_transferred.add_bytes(size);
260 let curr_mem = memory_reservation.size();
261 if curr_mem > curr_max_mem {
262 curr_max_mem = curr_mem;
263 max_mem_used.set(curr_max_mem);
264 }
265
266 if o_tx.send(Ok((flight_data, flight_metadata))).is_err() {
267 memory_reservation.shrink(size);
272 continue;
273 };
274 }
275 }.with_elapsed_compute(elapsed_compute));
276
277 let task = Arc::new(task);
278 let not_consumed_streams = Arc::new(AtomicUsize::new(per_partition_rx.len()));
279
280 let mut result = Vec::with_capacity(per_partition_rx.len());
281 for partition_receiver in per_partition_rx {
282 let task = Arc::clone(&task);
283 let cancel_token = cancel_token.clone();
284
285 let first_poll_notify = Arc::clone(&first_poll_notify);
286 let stream = async move {
287 first_poll_notify.notify_one();
288 UnboundedReceiverStream::new(partition_receiver)
289 }
290 .flatten_stream();
291
292 let stream = stream.map_err(|err| FlightError::Tonic(Box::new(err)));
293 let reservation = Arc::clone(&memory_reservation_clone);
294 let mem_available_notify = Arc::clone(&mem_available_notify);
295 let stream = stream.map_ok(move |(data, _meta)| {
296 reservation.shrink(data.encoded_len());
297 mem_available_notify.notify_one();
299 let _ = &task; data
301 });
302 let stream = FlightRecordBatchStream::new_from_flight_data(stream);
303 let stream = stream.map_err(map_flight_to_datafusion_error);
304 let stream = stream.with_elapsed_compute(elapsed_compute_clone.clone());
305
306 let not_consumed_streams = Arc::clone(¬_consumed_streams);
308 result.push(
309 on_drop_stream(stream, move || {
310 let remaining_streams = not_consumed_streams.fetch_sub(1, Ordering::SeqCst) - 1;
311 if remaining_streams == 0 {
312 cancel_token.cancel();
313 }
314 })
315 .boxed(),
316 );
317 }
318
319 Ok(result)
320 }
321
322 async fn get_worker_info(
323 &mut self,
324 _request: GetWorkerInfoRequest,
325 ) -> Result<GetWorkerInfoResponse> {
326 let response = self
327 .get_worker_info(pb::GetWorkerInfoRequest {})
328 .await
329 .map_err(map_status_to_datafusion_error)?
330 .into_inner();
331 Ok(GetWorkerInfoResponse {
332 version: response.version,
333 })
334 }
335}
336
337type WorkerMsg = Result<(FlightData, FlightAppMetadata), Status>;
338
339struct NetworkLatencyMetrics {
340 metrics: ExecutionPlanMetricsSet,
341 values: Option<NetworkLatencyMetricValues>,
342}
343
344impl NetworkLatencyMetrics {
345 fn new(metrics: &ExecutionPlanMetricsSet) -> Self {
346 Self {
347 metrics: metrics.clone(),
348 values: None,
349 }
350 }
351
352 fn add_duration(&mut self, duration: Duration) {
353 self.values
354 .get_or_insert_with(|| NetworkLatencyMetricValues::new(&self.metrics))
355 .add_duration(duration);
356 }
357}
358
359struct NetworkLatencyMetricValues {
360 min_latency: MinLatencyMetric,
361 max_latency: MaxLatencyMetric,
362 p50_latency: P50LatencyMetric,
363 p95_latency: P95LatencyMetric,
364 first_latency: FirstLatencyMetric,
365 sum_latency: Time,
366 latency_count: Count,
367}
368
369impl NetworkLatencyMetricValues {
370 fn new(metrics: &ExecutionPlanMetricsSet) -> Self {
371 let min_latency = MetricBuilder::new(metrics).min_latency("network_latency_min");
372 let max_latency = MetricBuilder::new(metrics).max_latency("network_latency_max");
373 let p50_latency = MetricBuilder::new(metrics).p50_latency("network_latency_p50");
374 let p95_latency = MetricBuilder::new(metrics).p95_latency("network_latency_p95");
375 let first_latency = MetricBuilder::new(metrics).first_latency("network_latency_first");
376 let sum_latency = Time::new();
377 MetricBuilder::new(metrics).build(MetricValue::Time {
378 name: Cow::Borrowed("network_latency_sum"),
379 time: sum_latency.clone(),
380 });
381 let latency_count = MetricBuilder::new(metrics).counter("network_latency_count", 0);
382
383 Self {
384 min_latency,
385 max_latency,
386 p50_latency,
387 p95_latency,
388 first_latency,
389 sum_latency,
390 latency_count,
391 }
392 }
393
394 fn add_duration(&self, duration: Duration) {
395 self.min_latency.add_duration(duration);
396 self.max_latency.add_duration(duration);
397 self.p50_latency.add_duration(duration);
398 self.p95_latency.add_duration(duration);
399 self.first_latency.add_duration(duration);
400 self.sum_latency.add_duration(duration);
401 self.latency_count.add(1);
402 }
403}
404
405pub(super) fn encode_producer_head(
406 head: ProducerHead,
407 ctx: &Arc<TaskContext>,
408) -> Result<pb::execute_task_request::ProducerHead> {
409 Ok(match head {
410 ProducerHead::None => pb::execute_task_request::ProducerHead::None(pb::NoneHead {}),
411 ProducerHead::BroadcastExec { output_partitions } => {
412 pb::execute_task_request::ProducerHead::Broadcast(pb::BroadcastExecHead {
413 output_partitions: output_partitions as u64,
414 })
415 }
416 ProducerHead::RepartitionExec { partitioning } => {
417 pb::execute_task_request::ProducerHead::Repartition(pb::RepartitionExecHead {
418 partitioning: partitioning.encode(ctx)?,
419 })
420 }
421 })
422}
423
424fn encode_coordinator_to_worker_msg(msg: CoordinatorToWorkerMsg) -> pb::CoordinatorToWorkerMsg {
425 pb::CoordinatorToWorkerMsg {
426 inner: Some(match msg {
427 CoordinatorToWorkerMsg::WorkUnitBatch(batch) => {
428 pb::coordinator_to_worker_msg::Inner::WorkUnitBatch(encode_work_unit_batch(batch))
429 }
430 CoordinatorToWorkerMsg::WorkUnitEos => {
431 pb::coordinator_to_worker_msg::Inner::WorkUnitEos(true)
432 }
433 }),
434 }
435}
436
437fn encode_set_plan_request(
438 request: SetPlanRequest,
439 ctx: &Arc<TaskContext>,
440) -> Result<pb::SetPlanRequest> {
441 let plan_proto = request.plan.encode(ctx)?;
442 Ok(pb::SetPlanRequest {
443 task_key: Some(encode_task_key(request.task_key)),
444 task_count: request.task_count as u64,
445 plan_proto,
446 work_unit_feed_declarations: request
447 .work_unit_feed_declarations
448 .into_iter()
449 .map(encode_work_unit_feed_declaration)
450 .collect(),
451 target_worker_url: request.target_worker_url.to_string(),
452 query_start_time_ns: request.query_start_time_ns as u64,
453 })
454}
455
456fn encode_work_unit_batch(batch: WorkUnitBatch) -> pb::WorkUnitBatch {
457 pb::WorkUnitBatch {
458 batch: batch.batch.into_iter().map(encode_work_unit).collect(),
459 }
460}
461
462fn encode_work_unit(work_unit: WorkUnitMsg) -> pb::WorkUnit {
463 pb::WorkUnit {
464 id: serialize_uuid(&work_unit.id),
465 partition: work_unit.partition as u64,
466 body: match work_unit.body {
467 MaybeEncoded::Encoded(body) => body,
468 MaybeEncoded::Decoded(body) => body.encode_to_bytes(),
469 },
470 created_timestamp_unix_nanos: work_unit.created_timestamp_unix_nanos as u64,
471 sent_timestamp_unix_nanos: work_unit.sent_timestamp_unix_nanos as u64,
472 received_timestamp_unix_nanos: work_unit.received_timestamp_unix_nanos as u64,
473 processed_timestamp_unix_nanos: work_unit.processed_timestamp_unix_nanos as u64,
474 }
475}
476
477fn encode_work_unit_feed_declaration(
478 declaration: WorkUnitFeedDeclaration,
479) -> pb::set_plan_request::WorkUnitFeedDeclaration {
480 pb::set_plan_request::WorkUnitFeedDeclaration {
481 id: serialize_uuid(&declaration.id),
482 partitions: declaration.partitions as u64,
483 }
484}
485
486fn encode_task_key(task_key: TaskKey) -> pb::TaskKey {
487 pb::TaskKey {
488 query_id: serialize_uuid(&task_key.query_id),
489 stage_id: task_key.stage_id as u64,
490 task_number: task_key.task_number as u64,
491 }
492}
493
494fn decode_worker_to_coordinator_msg(
495 msg: pb::WorkerToCoordinatorMsg,
496) -> Result<WorkerToCoordinatorMsg> {
497 Ok(
498 match msg
499 .inner
500 .ok_or_else(|| missing("WorkerToCoordinatorMsg.inner"))?
501 {
502 pb::worker_to_coordinator_msg::Inner::TaskMetrics(task_metrics) => {
503 WorkerToCoordinatorMsg::TaskMetrics(decode_task_metrics(task_metrics)?)
504 }
505 pb::worker_to_coordinator_msg::Inner::LoadInfo(load_info) => {
506 WorkerToCoordinatorMsg::LoadInfo(decode_load_info(load_info))
507 }
508 pb::worker_to_coordinator_msg::Inner::LoadInfoEos(_) => {
509 WorkerToCoordinatorMsg::LoadInfoEos
510 }
511 },
512 )
513}
514
515fn decode_task_metrics(task_metrics: pb::TaskMetrics) -> Result<TaskMetrics> {
516 Ok(TaskMetrics {
517 pre_order_plan_metrics: task_metrics
518 .pre_order_plan_metrics
519 .into_iter()
520 .map(|metrics_set| metrics_set_proto_to_df(&metrics_set))
521 .collect::<Result<_>>()?,
522 task_metrics: metrics_set_proto_to_df(
523 &task_metrics
524 .task_metrics
525 .ok_or_else(|| missing("task_metrics"))?,
526 )?,
527 })
528}
529
530fn decode_load_info(load_info: pb::LoadInfo) -> LoadInfo {
531 LoadInfo {
532 partition: load_info.partition as usize,
533 rows_ready: load_info.rows_ready as usize,
534 per_column_bytes_ready: load_info
535 .per_column_bytes_ready
536 .into_iter()
537 .map(|bytes| bytes as usize)
538 .collect(),
539 per_column_ndv_percentage: load_info.per_column_ndv_percentage,
540 per_column_null_percentage: load_info.per_column_null_percentage,
541 rows_pulled_from_leaf: load_info.rows_pulled_from_leaf as usize,
542 reached_eos: load_info.reached_eos,
543 }
544}
545
546fn missing(field: &'static str) -> DataFusionError {
547 DataFusionError::Internal(format!("Missing field '{field}'"))
548}
549
550fn fanout(o_txs: &[UnboundedSender<WorkerMsg>], err: Status) {
551 for o_tx in o_txs {
552 let _ = o_tx.send(Err(err.clone()));
553 }
554}
555
556pub fn create_worker_client(channel: BoxCloneSyncChannel) -> Box<dyn WorkerChannel> {
590 Box::new(
591 pb::worker_service_client::WorkerServiceClient::new(channel)
592 .max_decoding_message_size(usize::MAX)
593 .max_encoding_message_size(usize::MAX),
594 )
595}
596
597trait ElapsedComputeFutureExt: Future + Sized {
598 fn with_elapsed_compute(self, elapsed_compute: Time) -> ElapsedComputeFuture<Self>;
599}
600
601trait ElapsedComputeStreamExt: Stream + Sized {
602 fn with_elapsed_compute(self, elapsed_compute: Time) -> ElapsedComputeStream<Self>;
603}
604
605impl<O, F: Future<Output = O>> ElapsedComputeFutureExt for F {
606 fn with_elapsed_compute(self, elapsed_compute: Time) -> ElapsedComputeFuture<Self> {
607 ElapsedComputeFuture {
608 inner: self,
609 curr: Duration::default(),
610 elapsed_compute,
611 }
612 }
613}
614
615impl<O, S: Stream<Item = O>> ElapsedComputeStreamExt for S {
616 fn with_elapsed_compute(self, elapsed_compute: Time) -> ElapsedComputeStream<Self> {
617 ElapsedComputeStream {
618 inner: self,
619 curr: Duration::default(),
620 elapsed_compute,
621 }
622 }
623}
624
625#[pin_project(PinnedDrop)]
626struct ElapsedComputeStream<T> {
627 #[pin]
628 inner: T,
629 curr: Duration,
630 elapsed_compute: Time,
631}
632
633#[pinned_drop]
636impl<T> PinnedDrop for ElapsedComputeStream<T> {
637 fn drop(self: Pin<&mut Self>) {
638 if self.curr > Duration::default() {
639 let self_projected = self.project();
640 self_projected
641 .elapsed_compute
642 .add_duration(*self_projected.curr);
643 }
644 }
645}
646
647impl<O, F: Stream<Item = O>> Stream for ElapsedComputeStream<F> {
648 type Item = O;
649
650 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
651 let self_projected = self.project();
652 let start = Instant::now();
653 let result = self_projected.inner.poll_next(cx);
654 *self_projected.curr += start.elapsed();
655 if result.is_ready() {
656 self_projected
657 .elapsed_compute
658 .add_duration(*self_projected.curr);
659 *self_projected.curr = Duration::default();
660 }
661 result
662 }
663}
664
665#[pin_project(PinnedDrop)]
666struct ElapsedComputeFuture<T> {
667 #[pin]
668 inner: T,
669 curr: Duration,
670 elapsed_compute: Time,
671}
672
673#[pinned_drop]
676impl<T> PinnedDrop for ElapsedComputeFuture<T> {
677 fn drop(self: Pin<&mut Self>) {
678 if self.curr > Duration::default() {
679 let self_projected = self.project();
680 self_projected
681 .elapsed_compute
682 .add_duration(*self_projected.curr);
683 }
684 }
685}
686
687impl<O, F: Future<Output = O>> Future for ElapsedComputeFuture<F> {
688 type Output = O;
689
690 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
691 let self_projected = self.project();
692 let start = Instant::now();
693 let result = self_projected.inner.poll(cx);
694 *self_projected.curr += start.elapsed();
695 if result.is_ready() {
696 self_projected
697 .elapsed_compute
698 .add_duration(*self_projected.curr);
699 *self_projected.curr = Duration::default();
700 }
701 result
702 }
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708 use futures::StreamExt;
709 use futures::stream::unfold;
710
711 #[tokio::test]
712 async fn elapsed_compute_future() {
713 async fn cheap() {
714 tokio::time::sleep(Duration::from_millis(1)).await;
715 }
716
717 async fn expensive() {
718 let mut _count = 0f64;
719 for i in 0..100000 {
720 tokio::task::yield_now().await;
721 _count /= i as f64
722 }
723 }
724
725 let cheap_time = Time::new();
726 cheap().with_elapsed_compute(cheap_time.clone()).await;
727 println!("cheap future: {}", cheap_time.value());
728
729 let expensive_time = Time::new();
730 expensive()
731 .with_elapsed_compute(expensive_time.clone())
732 .await;
733 println!("expensive future: {}", expensive_time.value());
734
735 assert!(expensive_time.value() > cheap_time.value());
736 }
737
738 #[tokio::test]
739 async fn elapsed_compute_stream() {
740 fn cheap() -> impl Stream<Item = i64> {
741 unfold(0i64, |state| async move {
742 if state < 10 {
743 tokio::time::sleep(Duration::from_micros(10)).await;
744 Some((state, state + 1))
745 } else {
746 None
747 }
748 })
749 }
750
751 fn expensive() -> impl Stream<Item = i64> {
752 unfold(0i64, |state| async move {
753 if state < 10 {
754 let mut _count = 0f64;
756 for i in 1..100000 {
757 _count += (i as f64).sqrt();
758 }
759 tokio::task::yield_now().await;
760 Some((state, state + 1))
761 } else {
762 None
763 }
764 })
765 }
766
767 let cheap_time = Time::new();
768 cheap()
769 .with_elapsed_compute(cheap_time.clone())
770 .collect::<Vec<_>>()
771 .await;
772 println!("cheap future: {}", cheap_time.value());
773
774 let expensive_time = Time::new();
775 expensive()
776 .with_elapsed_compute(expensive_time.clone())
777 .collect::<Vec<_>>()
778 .await;
779 println!("expensive future: {}", expensive_time.value());
780
781 assert!(expensive_time.value() > cheap_time.value());
782 }
783}