1use micromegas_telemetry::stream_info::StreamInfo;
2use micromegas_telemetry::wire_format::encode_cbor;
3use micromegas_tracing::{
4 event::{EventSink, TracingBlock},
5 flush_monitor::FlushMonitor,
6 images::{ImageBlock, ImageStream},
7 logs::{LogBlock, LogMetadata, LogStream},
8 metrics::{MetricsBlock, MetricsStream},
9 prelude::*,
10 property_set::Property,
11 spans::{ThreadBlock, ThreadStream},
12};
13use std::{
14 cmp::max,
15 collections::{HashMap, VecDeque},
16 fmt,
17 sync::{
18 Arc, Condvar, Mutex,
19 atomic::{AtomicBool, AtomicIsize, AtomicU64, Ordering},
20 },
21 time::Duration,
22};
23use tokio::sync::{OwnedSemaphorePermit, Semaphore};
24use tokio_retry2::{RetryError, strategy::ExponentialBackoff};
25
26use crate::request_decorator::RequestDecorator;
27use crate::stream_block::StreamBlock;
28use crate::stream_info::make_stream_info;
29
30type RetryStrategy = core::iter::Take<ExponentialBackoff>;
32
33#[derive(Clone, Debug)]
40enum IngestionClientError {
41 Transient(String),
43 Permanent(String),
45}
46
47impl std::fmt::Display for IngestionClientError {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self {
50 IngestionClientError::Transient(msg) => write!(f, "transient error: {msg}"),
51 IngestionClientError::Permanent(msg) => write!(f, "permanent error: {msg}"),
52 }
53 }
54}
55
56impl std::error::Error for IngestionClientError {}
57
58impl IngestionClientError {
59 fn into_retry(self) -> RetryError<Self> {
60 match self {
61 IngestionClientError::Transient(_) => RetryError::transient(self),
62 IngestionClientError::Permanent(_) => RetryError::permanent(self),
63 }
64 }
65}
66
67#[derive(Copy, Clone, Debug, PartialEq, Eq)]
74pub enum UploadPriority {
75 Metadata = 0,
77 Logs = 1,
79 Metrics = 2,
81 Traces = 3,
83}
84
85const NUM_PRIORITIES: usize = 4;
86
87const SHUTDOWN_WAIT_TIMEOUT: Duration = Duration::from_secs(5);
90
91fn report_dropped_items(queue: &SharedQueue) {
101 for priority in [
102 UploadPriority::Metadata,
103 UploadPriority::Logs,
104 UploadPriority::Metrics,
105 UploadPriority::Traces,
106 ] {
107 let count = queue.dropped[priority as usize].swap(0, Ordering::Relaxed);
108 if count == 0 {
109 continue;
110 }
111 warn!("dropped {count} telemetry items, priority={priority:?}");
112 match priority {
113 UploadPriority::Metadata => {
114 imetric!("telemetry_dropped_metadata", "count", count);
115 }
116 UploadPriority::Logs => {
117 imetric!("telemetry_dropped_logs", "count", count);
118 }
119 UploadPriority::Metrics => {
120 imetric!("telemetry_dropped_metrics", "count", count);
121 }
122 UploadPriority::Traces => {
123 imetric!("telemetry_dropped_traces", "count", count);
124 }
125 }
126 }
127}
128
129enum Payload {
130 Process(Arc<ProcessInfo>),
131 Stream(Arc<StreamInfo>),
132 Block {
133 block: Arc<dyn StreamBlock + Send + Sync>,
134 kind: &'static str,
135 },
136}
137
138struct QueuedItem {
139 priority: UploadPriority,
140 bytes: usize,
142 payload: Payload,
143}
144
145struct SharedQueue {
155 queues: Mutex<[VecDeque<QueuedItem>; NUM_PRIORITIES]>,
156 queue_bytes: AtomicIsize,
157 queue_count: AtomicIsize,
158 in_flight_count: AtomicIsize,
164 notify: Condvar,
165 shutdown: AtomicBool,
166 soft_bytes: usize,
167 hard_bytes: usize,
168 dropped: [AtomicU64; NUM_PRIORITIES],
173}
174
175impl SharedQueue {
176 fn new(soft_bytes: usize, hard_bytes: usize) -> Self {
177 Self {
178 queues: Mutex::new([
179 VecDeque::new(),
180 VecDeque::new(),
181 VecDeque::new(),
182 VecDeque::new(),
183 ]),
184 queue_bytes: AtomicIsize::new(0),
185 queue_count: AtomicIsize::new(0),
186 in_flight_count: AtomicIsize::new(0),
187 notify: Condvar::new(),
188 shutdown: AtomicBool::new(false),
189 soft_bytes,
190 hard_bytes: hard_bytes.max(soft_bytes),
191 dropped: [
192 AtomicU64::new(0),
193 AtomicU64::new(0),
194 AtomicU64::new(0),
195 AtomicU64::new(0),
196 ],
197 }
198 }
199
200 fn note_dropped(&self, priority: UploadPriority) {
204 self.dropped[priority as usize].fetch_add(1, Ordering::Relaxed);
205 }
206
207 fn try_push(&self, item: QueuedItem) -> bool {
210 let current = self.queue_bytes.load(Ordering::Relaxed).max(0) as usize;
211 let should_drop = match item.priority {
212 UploadPriority::Metadata => false,
213 UploadPriority::Traces => current >= self.soft_bytes,
214 UploadPriority::Logs | UploadPriority::Metrics => current >= self.hard_bytes,
215 };
216 if should_drop {
217 self.note_dropped(item.priority);
218 return false;
219 }
220 let mut guard = self.queues.lock().unwrap();
228 if self.shutdown.load(Ordering::SeqCst) {
229 drop(guard);
230 self.note_dropped(item.priority);
237 return false;
238 }
239 self.queue_bytes
240 .fetch_add(item.bytes as isize, Ordering::Relaxed);
241 self.queue_count.fetch_add(1, Ordering::Relaxed);
242 guard[item.priority as usize].push_back(item);
243 self.notify.notify_all();
244 true
245 }
246
247 fn pop_highest(&self) -> Option<QueuedItem> {
250 let mut guard = self.queues.lock().unwrap();
251 for deque in guard.iter_mut() {
252 if let Some(item) = deque.pop_front() {
253 drop(guard);
254 self.queue_bytes
255 .fetch_sub(item.bytes as isize, Ordering::Relaxed);
256 self.queue_count.fetch_sub(1, Ordering::Relaxed);
257 return Some(item);
258 }
259 }
260 None
261 }
262
263 fn wake(&self) {
268 let _guard = self.queues.lock().unwrap();
269 self.notify.notify_all();
270 }
271}
272
273struct WakeOnDrop {
278 queue: Arc<SharedQueue>,
279 _permit: Option<OwnedSemaphorePermit>,
280}
281
282impl Drop for WakeOnDrop {
283 fn drop(&mut self) {
284 drop(self._permit.take());
292 self.queue.wake();
293 }
294}
295
296pub struct HttpSinkConfig {
304 pub max_queue_bytes: usize,
307 pub hard_queue_bytes: usize,
312 pub max_in_flight_requests: usize,
315 pub request_timeout: Duration,
327 pub retry_by_priority: [RetryStrategy; NUM_PRIORITIES],
330}
331
332impl HttpSinkConfig {
333 pub const DEFAULT_MAX_QUEUE_BYTES: usize = 128 * 1024 * 1024;
335 pub const DEFAULT_HARD_QUEUE_BYTES: usize = 256 * 1024 * 1024;
337 pub const DEFAULT_MAX_IN_FLIGHT_REQUESTS: usize = 3;
339 pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
341
342 pub fn default_retry_by_priority() -> [RetryStrategy; NUM_PRIORITIES] {
346 [
347 ExponentialBackoff::from_millis(10).take(10), ExponentialBackoff::from_millis(10).take(5), ExponentialBackoff::from_millis(10).take(2), ExponentialBackoff::from_millis(10).take(1), ]
352 }
353}
354
355impl Default for HttpSinkConfig {
356 fn default() -> Self {
357 Self {
358 max_queue_bytes: Self::DEFAULT_MAX_QUEUE_BYTES,
359 hard_queue_bytes: Self::DEFAULT_HARD_QUEUE_BYTES,
360 max_in_flight_requests: Self::DEFAULT_MAX_IN_FLIGHT_REQUESTS,
361 request_timeout: Self::DEFAULT_REQUEST_TIMEOUT,
362 retry_by_priority: Self::default_retry_by_priority(),
363 }
364 }
365}
366
367struct WorkerShared {
372 client: reqwest::Client,
373 addr: String,
374 process_info: Mutex<Option<Arc<ProcessInfo>>>,
375 decorator: Arc<dyn RequestDecorator>,
376 retry_by_priority: [RetryStrategy; NUM_PRIORITIES],
377}
378
379struct WorkerConfig {
382 max_in_flight_requests: usize,
383 request_timeout: Duration,
384 retry_by_priority: [RetryStrategy; NUM_PRIORITIES],
385}
386
387pub struct HttpEventSink {
388 thread: Option<std::thread::JoinHandle<()>>,
389 queue: Arc<SharedQueue>,
390 pending_stream_meta: Mutex<HashMap<uuid::Uuid, Arc<StreamInfo>>>,
391 shutdown_complete: Arc<(Mutex<bool>, std::sync::Condvar)>,
392}
393
394impl Drop for HttpEventSink {
395 fn drop(&mut self) {
396 if !self.signal_shutdown_and_wait() {
402 eprintln!(
403 "Warning: telemetry thread did not shut down within the timeout, abandoning it"
404 );
405 return;
406 }
407
408 if let Some(handle) = self.thread.take()
409 && let Err(e) = handle.join()
410 {
411 eprintln!("Warning: telemetry thread join failed: {:?}", e);
413 }
414 }
415}
416
417impl HttpEventSink {
418 pub fn new(
432 addr_server: &str,
433 config: HttpSinkConfig,
434 make_decorator: Box<dyn FnOnce() -> Arc<dyn RequestDecorator> + Send>,
435 ) -> Self {
436 let addr = addr_server.to_owned();
437 let queue = Arc::new(SharedQueue::new(
438 config.max_queue_bytes,
439 config.hard_queue_bytes,
440 ));
441 let thread_queue = queue.clone();
442 let worker_config = WorkerConfig {
443 max_in_flight_requests: config.max_in_flight_requests,
444 request_timeout: config.request_timeout,
445 retry_by_priority: config.retry_by_priority,
446 };
447 let shutdown_complete = Arc::new((Mutex::new(false), std::sync::Condvar::new()));
448 let thread_shutdown_complete = shutdown_complete.clone();
449 Self {
450 thread: Some(std::thread::spawn(move || {
451 Self::thread_proc(
452 addr,
453 thread_queue,
454 worker_config,
455 make_decorator,
456 thread_shutdown_complete,
457 );
458 })),
459 queue,
460 pending_stream_meta: Mutex::new(HashMap::new()),
461 shutdown_complete,
462 }
463 }
464
465 fn signal_shutdown_and_wait(&self) -> bool {
470 self.queue.shutdown.store(true, Ordering::SeqCst);
471 self.queue.wake();
472
473 let (lock, cvar) = &*self.shutdown_complete;
474 let completed = lock.lock().unwrap();
475 let (completed, result) = cvar
476 .wait_timeout_while(completed, SHUTDOWN_WAIT_TIMEOUT, |&mut c| !c)
477 .unwrap();
478 drop(completed);
479 !result.timed_out()
480 }
481
482 fn flush_pending_stream_meta(&self, stream_id: uuid::Uuid) {
483 if let Some(stream_info) = self.pending_stream_meta.lock().unwrap().remove(&stream_id) {
484 self.queue.try_push(QueuedItem {
485 priority: UploadPriority::Metadata,
486 bytes: 0,
487 payload: Payload::Stream(stream_info),
488 });
489 }
490 }
491
492 #[span_fn]
493 async fn push_process(
494 client: &reqwest::Client,
495 root_path: &str,
496 process_info: Arc<ProcessInfo>,
497 retry_strategy: RetryStrategy,
498 decorator: &dyn RequestDecorator,
499 ) -> Result<(), IngestionClientError> {
500 debug!("sending process {process_info:?}");
501 let url = format!("{root_path}/ingestion/insert_process");
502 let body: bytes::Bytes = encode_cbor(&*process_info)
503 .map_err(|e| IngestionClientError::Permanent(format!("encoding process: {e}")))?
504 .into();
505 tokio_retry2::Retry::spawn(retry_strategy, || async {
506 let mut request = client.post(&url).body(body.clone()).build().map_err(|e| {
507 IngestionClientError::Permanent(format!("building request: {e}")).into_retry()
508 })?;
509
510 if let Err(e) = decorator.decorate(&mut request).await {
511 debug!("request decorator: {e:?}");
512 return Err(
513 IngestionClientError::Transient(format!("decorating request: {e}"))
514 .into_retry(),
515 );
516 }
517
518 let response = client.execute(request).await.map_err(|e| {
519 IngestionClientError::Transient(format!("network error: {e}")).into_retry()
520 })?;
521
522 let status = response.status();
523 match status.as_u16() {
524 200..=299 => Ok(()),
525 400..=499 => {
526 let body = response.text().await.unwrap_or_default();
527 warn!("insert_process client error ({status}): {body}");
528 Err(IngestionClientError::Permanent(body).into_retry())
529 }
530 500..=599 => {
531 let body = response.text().await.unwrap_or_default();
532 debug!("insert_process server error ({status}): {body}");
533 Err(IngestionClientError::Transient(format!("{status}: {body}")).into_retry())
534 }
535 _ => {
536 let body = response.text().await.unwrap_or_default();
537 warn!("insert_process unexpected status ({status}): {body}");
538 Err(IngestionClientError::Permanent(format!("{status}: {body}")).into_retry())
539 }
540 }
541 })
542 .await
543 }
544
545 #[span_fn]
546 async fn push_stream(
547 client: &reqwest::Client,
548 root_path: &str,
549 stream_info: Arc<StreamInfo>,
550 retry_strategy: RetryStrategy,
551 decorator: &dyn RequestDecorator,
552 ) -> Result<(), IngestionClientError> {
553 let url = format!("{root_path}/ingestion/insert_stream");
554 let body: bytes::Bytes = encode_cbor(&*stream_info)
555 .map_err(|e| IngestionClientError::Permanent(format!("encoding stream: {e}")))?
556 .into();
557 tokio_retry2::Retry::spawn(retry_strategy, || async {
558 let mut request = client.post(&url).body(body.clone()).build().map_err(|e| {
559 IngestionClientError::Permanent(format!("building request: {e}")).into_retry()
560 })?;
561
562 if let Err(e) = decorator.decorate(&mut request).await {
563 debug!("request decorator: {e:?}");
564 return Err(
565 IngestionClientError::Transient(format!("decorating request: {e}"))
566 .into_retry(),
567 );
568 }
569
570 let response = client.execute(request).await.map_err(|e| {
571 IngestionClientError::Transient(format!("network error: {e}")).into_retry()
572 })?;
573
574 let status = response.status();
575 match status.as_u16() {
576 200..=299 => Ok(()),
577 400..=499 => {
578 let body = response.text().await.unwrap_or_default();
579 warn!("insert_stream client error ({status}): {body}");
580 Err(IngestionClientError::Permanent(body).into_retry())
581 }
582 500..=599 => {
583 let body = response.text().await.unwrap_or_default();
584 debug!("insert_stream server error ({status}): {body}");
585 Err(IngestionClientError::Transient(format!("{status}: {body}")).into_retry())
586 }
587 _ => {
588 let body = response.text().await.unwrap_or_default();
589 warn!("insert_stream unexpected status ({status}): {body}");
590 Err(IngestionClientError::Permanent(format!("{status}: {body}")).into_retry())
591 }
592 }
593 })
594 .await
595 }
596
597 #[span_fn]
598 async fn push_block(
599 client: &reqwest::Client,
600 root_path: &str,
601 buffer: &dyn StreamBlock,
602 retry_strategy: RetryStrategy,
603 decorator: &dyn RequestDecorator,
604 process_info: &ProcessInfo,
605 ) -> Result<(), IngestionClientError> {
606 trace!("push_block");
607 let encoded_block: bytes::Bytes = buffer
608 .encode_bin(process_info)
609 .map_err(|e| IngestionClientError::Permanent(format!("encoding block: {e}")))?
610 .into();
611
612 let url = format!("{root_path}/ingestion/insert_block");
613
614 tokio_retry2::Retry::spawn(retry_strategy, || async {
615 let mut request = client
616 .post(&url)
617 .body(encoded_block.clone())
618 .build()
619 .map_err(|e| {
620 IngestionClientError::Permanent(format!("building request: {e}")).into_retry()
621 })?;
622
623 if let Err(e) = decorator.decorate(&mut request).await {
624 debug!("request decorator: {e:?}");
625 return Err(
626 IngestionClientError::Transient(format!("decorating request: {e}"))
627 .into_retry(),
628 );
629 }
630
631 trace!("push_block: executing request");
632
633 let response = client.execute(request).await.map_err(|e| {
634 IngestionClientError::Transient(format!("network error: {e}")).into_retry()
635 })?;
636
637 let status = response.status();
638 match status.as_u16() {
639 200..=299 => Ok(()),
640 400..=499 => {
641 let body = response.text().await.unwrap_or_default();
642 warn!("insert_block client error ({status}): {body}");
643 Err(IngestionClientError::Permanent(body).into_retry())
644 }
645 500..=599 => {
646 let body = response.text().await.unwrap_or_default();
647 debug!("insert_block server error ({status}): {body}");
648 Err(IngestionClientError::Transient(format!("{status}: {body}")).into_retry())
649 }
650 _ => {
651 let body = response.text().await.unwrap_or_default();
652 warn!("insert_block unexpected status ({status}): {body}");
653 Err(IngestionClientError::Permanent(format!("{status}: {body}")).into_retry())
654 }
655 }
656 })
657 .await
658 }
659
660 fn spawn_item(
668 shared: &Arc<WorkerShared>,
669 queue: &Arc<SharedQueue>,
670 item: QueuedItem,
671 join_set: &mut tokio::task::JoinSet<()>,
672 permit: Option<OwnedSemaphorePermit>,
673 ) {
674 queue.in_flight_count.fetch_add(1, Ordering::Relaxed);
679 if let Payload::Process(ref info) = item.payload {
680 *shared.process_info.lock().unwrap() = Some(info.clone());
681 }
682 let shared = shared.clone();
683 let queue = queue.clone();
684 let retry_strategy = shared.retry_by_priority[item.priority as usize].clone();
685 join_set.spawn(async move {
686 let _wake_on_drop = WakeOnDrop {
691 queue,
692 _permit: permit,
693 };
694 match item.payload {
695 Payload::Process(info) => {
696 if let Err(e) = Self::push_process(
697 &shared.client,
698 &shared.addr,
699 info,
700 retry_strategy,
701 shared.decorator.as_ref(),
702 )
703 .await
704 {
705 error!("error sending process: {e}");
706 }
707 }
708 Payload::Stream(stream_info) => {
709 if let Err(e) = Self::push_stream(
710 &shared.client,
711 &shared.addr,
712 stream_info,
713 retry_strategy,
714 shared.decorator.as_ref(),
715 )
716 .await
717 {
718 error!("error sending stream: {e}");
719 }
720 }
721 Payload::Block { block, kind } => {
722 let maybe_process_info = shared.process_info.lock().unwrap().clone();
723 if let Some(process_info) = maybe_process_info {
724 if let Err(e) = Self::push_block(
725 &shared.client,
726 &shared.addr,
727 block.as_ref(),
728 retry_strategy,
729 shared.decorator.as_ref(),
730 &process_info,
731 )
732 .await
733 {
734 error!("error sending {kind}: {e}");
735 }
736 } else {
737 error!("trying to send blocks before Startup message");
738 }
739 }
740 }
741 });
742 }
743
744 async fn run(
745 addr: String,
746 queue: Arc<SharedQueue>,
747 config: WorkerConfig,
748 make_decorator: Box<dyn FnOnce() -> Arc<dyn RequestDecorator> + Send>,
749 shutdown_complete: Arc<(Mutex<bool>, std::sync::Condvar)>,
750 ) {
751 let client = match reqwest::Client::builder()
752 .pool_idle_timeout(Some(core::time::Duration::from_secs(2)))
753 .timeout(config.request_timeout)
754 .build()
755 {
756 Ok(client) => client,
757 Err(e) => {
758 error!("Error creating http client: {e:?}");
759 return;
760 }
761 };
762 if let Some(process_id) = micromegas_tracing::dispatch::process_id() {
764 let cpu_tracing_enabled =
765 micromegas_tracing::dispatch::cpu_tracing_enabled().unwrap_or(false);
766 info!("process_id={process_id}, cpu_tracing_enabled={cpu_tracing_enabled}");
767 }
768 let shared = Arc::new(WorkerShared {
769 client,
770 addr,
771 process_info: Mutex::new(None),
772 decorator: make_decorator(),
773 retry_by_priority: config.retry_by_priority,
774 });
775 let semaphore = Arc::new(Semaphore::new(config.max_in_flight_requests.max(1)));
776 let mut join_set: tokio::task::JoinSet<()> = tokio::task::JoinSet::new();
777 let flusher = FlushMonitor::default();
778
779 loop {
780 if queue.shutdown.load(Ordering::SeqCst) {
781 break;
782 }
783
784 loop {
785 let permit = match Arc::clone(&semaphore).try_acquire_owned() {
786 Ok(permit) => permit,
787 Err(_) => break,
788 };
789 match queue.pop_highest() {
790 Some(item) => {
791 Self::spawn_item(&shared, &queue, item, &mut join_set, Some(permit))
792 }
793 None => {
794 drop(permit);
795 break;
796 }
797 }
798 }
799 while let Some(result) = join_set.try_join_next() {
801 queue.in_flight_count.fetch_sub(1, Ordering::Relaxed);
802 if let Err(e) = result {
803 error!("telemetry send task panicked: {e}");
804 }
805 }
806
807 let timeout = Duration::from_secs(max(0, flusher.time_to_flush_seconds()) as u64);
811 {
812 let guard = queue.queues.lock().unwrap();
813 let empty = guard.iter().all(VecDeque::is_empty);
814 let permit_available = semaphore.available_permits() > 0;
815 if !queue.shutdown.load(Ordering::SeqCst) && (empty || !permit_available) {
816 let _ = queue.notify.wait_timeout(guard, timeout);
817 }
818 }
819 flusher.tick();
820 report_dropped_items(&queue);
821 }
822
823 debug!("received shutdown signal, flushing remaining data");
824 let mut drained = 0;
827 while let Some(item) = queue.pop_highest() {
828 drained += 1;
829 Self::spawn_item(&shared, &queue, item, &mut join_set, None);
830 }
831 while let Some(result) = join_set.join_next().await {
832 queue.in_flight_count.fetch_sub(1, Ordering::Relaxed);
833 if let Err(e) = result {
834 error!("telemetry send task panicked: {e}");
835 }
836 }
837 debug!("telemetry thread shutdown complete, drained {drained} remaining items");
838 report_dropped_items(&queue);
842
843 let (lock, cvar) = &*shutdown_complete;
844 let mut completed = lock.lock().unwrap();
845 *completed = true;
846 cvar.notify_all();
847 }
848
849 fn thread_proc(
850 addr: String,
851 queue: Arc<SharedQueue>,
852 config: WorkerConfig,
853 make_decorator: Box<dyn FnOnce() -> Arc<dyn RequestDecorator> + Send>,
854 shutdown_complete: Arc<(Mutex<bool>, std::sync::Condvar)>,
855 ) {
856 let tokio_runtime = match tokio::runtime::Runtime::new() {
858 Ok(rt) => rt,
859 Err(e) => {
860 error!("Failed to create tokio runtime for telemetry: {e}");
861 return;
862 }
863 };
864 tokio_runtime.block_on(Self::run(
865 addr,
866 queue,
867 config,
868 make_decorator,
869 shutdown_complete,
870 ));
871 }
872}
873
874impl EventSink for HttpEventSink {
875 fn on_startup(&self, process_info: Arc<ProcessInfo>) {
876 self.queue.try_push(QueuedItem {
877 priority: UploadPriority::Metadata,
878 bytes: 0,
879 payload: Payload::Process(process_info),
880 });
881 }
882
883 fn on_shutdown(&self) {
884 self.signal_shutdown_and_wait();
888 }
889
890 fn on_log_enabled(&self, _metadata: &LogMetadata) -> bool {
891 true
893 }
894
895 fn on_log(
896 &self,
897 _metadata: &LogMetadata,
898 _properties: &[Property],
899 _time: i64,
900 _args: fmt::Arguments<'_>,
901 ) {
902 }
903
904 fn on_init_log_stream(&self, log_stream: &LogStream) {
905 self.pending_stream_meta.lock().unwrap().insert(
906 log_stream.stream_id(),
907 Arc::new(make_stream_info(log_stream)),
908 );
909 }
910
911 fn on_process_log_block(&self, log_block: Arc<LogBlock>) {
912 self.flush_pending_stream_meta(log_block.stream_id);
913 let bytes = log_block.len_bytes();
914 self.queue.try_push(QueuedItem {
915 priority: UploadPriority::Logs,
916 bytes,
917 payload: Payload::Block {
918 block: log_block,
919 kind: "log block",
920 },
921 });
922 }
923
924 fn on_init_metrics_stream(&self, metrics_stream: &MetricsStream) {
925 self.pending_stream_meta.lock().unwrap().insert(
926 metrics_stream.stream_id(),
927 Arc::new(make_stream_info(metrics_stream)),
928 );
929 }
930
931 fn on_process_metrics_block(&self, metrics_block: Arc<MetricsBlock>) {
932 self.flush_pending_stream_meta(metrics_block.stream_id);
933 let bytes = metrics_block.len_bytes();
934 self.queue.try_push(QueuedItem {
935 priority: UploadPriority::Metrics,
936 bytes,
937 payload: Payload::Block {
938 block: metrics_block,
939 kind: "metrics block",
940 },
941 });
942 }
943
944 fn on_init_thread_stream(&self, thread_stream: &ThreadStream) {
945 self.pending_stream_meta.lock().unwrap().insert(
946 thread_stream.stream_id(),
947 Arc::new(make_stream_info(thread_stream)),
948 );
949 }
950
951 fn on_process_thread_block(&self, thread_block: Arc<ThreadBlock>) {
952 self.flush_pending_stream_meta(thread_block.stream_id);
953 let bytes = thread_block.len_bytes();
954 self.queue.try_push(QueuedItem {
955 priority: UploadPriority::Traces,
956 bytes,
957 payload: Payload::Block {
958 block: thread_block,
959 kind: "thread block",
960 },
961 });
962 }
963
964 fn on_init_image_stream(&self, stream: &ImageStream) {
965 self.pending_stream_meta
966 .lock()
967 .unwrap()
968 .insert(stream.stream_id(), Arc::new(make_stream_info(stream)));
969 }
970
971 fn on_process_image_block(&self, block: Arc<ImageBlock>) {
972 self.flush_pending_stream_meta(block.stream_id);
973 let bytes = block.len_bytes();
974 self.queue.try_push(QueuedItem {
975 priority: UploadPriority::Traces,
976 bytes,
977 payload: Payload::Block {
978 block,
979 kind: "image block",
980 },
981 });
982 }
983
984 fn is_busy(&self) -> bool {
985 let queued = self.queue.queue_count.load(Ordering::Relaxed);
986 debug_assert!(queued >= 0, "queue_count went negative: {queued}");
987 let in_flight = self.queue.in_flight_count.load(Ordering::Relaxed);
988 debug_assert!(in_flight >= 0, "in_flight_count went negative: {in_flight}");
989 queued > 0 || in_flight > 0
990 }
991}