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::DynProperty,
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) {
205 self.dropped[priority as usize].fetch_add(1, Ordering::Relaxed);
206 }
207
208 fn try_push(&self, item: QueuedItem) -> bool {
211 let current = self.queue_bytes.load(Ordering::Relaxed).max(0) as usize;
212 let should_drop = match item.priority {
213 UploadPriority::Metadata => false,
214 UploadPriority::Traces => current >= self.soft_bytes,
215 UploadPriority::Logs | UploadPriority::Metrics => current >= self.hard_bytes,
216 };
217 if should_drop {
218 self.note_dropped(item.priority);
219 return false;
220 }
221 let mut guard = self.queues.lock().unwrap();
229 if self.shutdown.load(Ordering::SeqCst) {
230 drop(guard);
231 self.note_dropped(item.priority);
238 return false;
239 }
240 self.queue_bytes
241 .fetch_add(item.bytes as isize, Ordering::Relaxed);
242 self.queue_count.fetch_add(1, Ordering::Relaxed);
243 guard[item.priority as usize].push_back(item);
244 self.notify.notify_all();
245 true
246 }
247
248 fn pop_highest(&self) -> Option<QueuedItem> {
251 let mut guard = self.queues.lock().unwrap();
252 for deque in guard.iter_mut() {
253 if let Some(item) = deque.pop_front() {
254 drop(guard);
255 self.queue_bytes
256 .fetch_sub(item.bytes as isize, Ordering::Relaxed);
257 self.queue_count.fetch_sub(1, Ordering::Relaxed);
258 return Some(item);
259 }
260 }
261 None
262 }
263
264 fn wake(&self) {
269 let _guard = self.queues.lock().unwrap();
270 self.notify.notify_all();
271 }
272}
273
274struct WakeOnDrop {
279 queue: Arc<SharedQueue>,
280 _permit: Option<OwnedSemaphorePermit>,
281}
282
283impl Drop for WakeOnDrop {
284 fn drop(&mut self) {
285 drop(self._permit.take());
293 self.queue.wake();
294 }
295}
296
297pub struct HttpSinkConfig {
305 pub max_queue_bytes: usize,
308 pub hard_queue_bytes: usize,
313 pub max_in_flight_requests: usize,
316 pub request_timeout: Duration,
328 pub retry_by_priority: [RetryStrategy; NUM_PRIORITIES],
331}
332
333impl HttpSinkConfig {
334 pub const DEFAULT_MAX_QUEUE_BYTES: usize = 128 * 1024 * 1024;
336 pub const DEFAULT_HARD_QUEUE_BYTES: usize = 256 * 1024 * 1024;
338 pub const DEFAULT_MAX_IN_FLIGHT_REQUESTS: usize = 3;
340 pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
342
343 pub fn default_retry_by_priority() -> [RetryStrategy; NUM_PRIORITIES] {
347 [
348 ExponentialBackoff::from_millis(10).take(10), ExponentialBackoff::from_millis(10).take(5), ExponentialBackoff::from_millis(10).take(2), ExponentialBackoff::from_millis(10).take(1), ]
353 }
354}
355
356impl Default for HttpSinkConfig {
357 fn default() -> Self {
358 Self {
359 max_queue_bytes: Self::DEFAULT_MAX_QUEUE_BYTES,
360 hard_queue_bytes: Self::DEFAULT_HARD_QUEUE_BYTES,
361 max_in_flight_requests: Self::DEFAULT_MAX_IN_FLIGHT_REQUESTS,
362 request_timeout: Self::DEFAULT_REQUEST_TIMEOUT,
363 retry_by_priority: Self::default_retry_by_priority(),
364 }
365 }
366}
367
368struct WorkerShared {
373 client: reqwest::Client,
374 addr: String,
375 process_info: Mutex<Option<Arc<ProcessInfo>>>,
376 decorator: Arc<dyn RequestDecorator>,
377 retry_by_priority: [RetryStrategy; NUM_PRIORITIES],
378}
379
380struct WorkerConfig {
383 max_in_flight_requests: usize,
384 request_timeout: Duration,
385 retry_by_priority: [RetryStrategy; NUM_PRIORITIES],
386}
387
388pub struct HttpEventSink {
389 thread: Option<std::thread::JoinHandle<()>>,
390 queue: Arc<SharedQueue>,
391 pending_stream_meta: Mutex<HashMap<uuid::Uuid, Arc<StreamInfo>>>,
392 shutdown_complete: Arc<(Mutex<bool>, std::sync::Condvar)>,
393}
394
395impl Drop for HttpEventSink {
396 fn drop(&mut self) {
397 if !self.signal_shutdown_and_wait() {
403 eprintln!(
404 "Warning: telemetry thread did not shut down within the timeout, abandoning it"
405 );
406 return;
407 }
408
409 if let Some(handle) = self.thread.take()
410 && let Err(e) = handle.join()
411 {
412 eprintln!("Warning: telemetry thread join failed: {:?}", e);
414 }
415 }
416}
417
418impl HttpEventSink {
419 pub fn new(
433 addr_server: &str,
434 config: HttpSinkConfig,
435 make_decorator: Box<dyn FnOnce() -> Arc<dyn RequestDecorator> + Send>,
436 ) -> Self {
437 let addr = addr_server.to_owned();
438 let queue = Arc::new(SharedQueue::new(
439 config.max_queue_bytes,
440 config.hard_queue_bytes,
441 ));
442 let thread_queue = queue.clone();
443 let worker_config = WorkerConfig {
444 max_in_flight_requests: config.max_in_flight_requests,
445 request_timeout: config.request_timeout,
446 retry_by_priority: config.retry_by_priority,
447 };
448 let shutdown_complete = Arc::new((Mutex::new(false), std::sync::Condvar::new()));
449 let thread_shutdown_complete = shutdown_complete.clone();
450 Self {
451 thread: Some(std::thread::spawn(move || {
452 Self::thread_proc(
453 addr,
454 thread_queue,
455 worker_config,
456 make_decorator,
457 thread_shutdown_complete,
458 );
459 })),
460 queue,
461 pending_stream_meta: Mutex::new(HashMap::new()),
462 shutdown_complete,
463 }
464 }
465
466 fn signal_shutdown_and_wait(&self) -> bool {
471 self.queue.shutdown.store(true, Ordering::SeqCst);
472 self.queue.wake();
473
474 let (lock, cvar) = &*self.shutdown_complete;
475 let completed = lock.lock().unwrap();
476 let (completed, result) = cvar
477 .wait_timeout_while(completed, SHUTDOWN_WAIT_TIMEOUT, |&mut c| !c)
478 .unwrap();
479 drop(completed);
480 !result.timed_out()
481 }
482
483 fn flush_pending_stream_meta(&self, stream_id: uuid::Uuid) {
484 if let Some(stream_info) = self.pending_stream_meta.lock().unwrap().remove(&stream_id) {
485 self.queue.try_push(QueuedItem {
486 priority: UploadPriority::Metadata,
487 bytes: 0,
488 payload: Payload::Stream(stream_info),
489 });
490 }
491 }
492
493 #[span_fn]
494 async fn push_process(
495 client: &reqwest::Client,
496 root_path: &str,
497 process_info: Arc<ProcessInfo>,
498 retry_strategy: RetryStrategy,
499 decorator: &dyn RequestDecorator,
500 ) -> Result<(), IngestionClientError> {
501 debug!("sending process {process_info:?}");
502 let url = format!("{root_path}/ingestion/insert_process");
503 let body: bytes::Bytes = encode_cbor(&*process_info)
504 .map_err(|e| IngestionClientError::Permanent(format!("encoding process: {e}")))?
505 .into();
506 tokio_retry2::Retry::spawn(retry_strategy, || async {
507 let mut request = client.post(&url).body(body.clone()).build().map_err(|e| {
508 IngestionClientError::Permanent(format!("building request: {e}")).into_retry()
509 })?;
510
511 if let Err(e) = decorator.decorate(&mut request).await {
512 debug!("request decorator: {e:?}");
513 return Err(
514 IngestionClientError::Transient(format!("decorating request: {e}"))
515 .into_retry(),
516 );
517 }
518
519 let response = client.execute(request).await.map_err(|e| {
520 IngestionClientError::Transient(format!("network error: {e}")).into_retry()
521 })?;
522
523 let status = response.status();
524 match status.as_u16() {
525 200..=299 => Ok(()),
526 400..=499 => {
527 let body = response.text().await.unwrap_or_default();
528 warn!("insert_process client error ({status}): {body}");
529 Err(IngestionClientError::Permanent(body).into_retry())
530 }
531 500..=599 => {
532 let body = response.text().await.unwrap_or_default();
533 debug!("insert_process server error ({status}): {body}");
534 Err(IngestionClientError::Transient(format!("{status}: {body}")).into_retry())
535 }
536 _ => {
537 let body = response.text().await.unwrap_or_default();
538 warn!("insert_process unexpected status ({status}): {body}");
539 Err(IngestionClientError::Permanent(format!("{status}: {body}")).into_retry())
540 }
541 }
542 })
543 .await
544 }
545
546 #[span_fn]
547 async fn push_stream(
548 client: &reqwest::Client,
549 root_path: &str,
550 stream_info: Arc<StreamInfo>,
551 retry_strategy: RetryStrategy,
552 decorator: &dyn RequestDecorator,
553 ) -> Result<(), IngestionClientError> {
554 let url = format!("{root_path}/ingestion/insert_stream");
555 let body: bytes::Bytes = encode_cbor(&*stream_info)
556 .map_err(|e| IngestionClientError::Permanent(format!("encoding stream: {e}")))?
557 .into();
558 tokio_retry2::Retry::spawn(retry_strategy, || async {
559 let mut request = client.post(&url).body(body.clone()).build().map_err(|e| {
560 IngestionClientError::Permanent(format!("building request: {e}")).into_retry()
561 })?;
562
563 if let Err(e) = decorator.decorate(&mut request).await {
564 debug!("request decorator: {e:?}");
565 return Err(
566 IngestionClientError::Transient(format!("decorating request: {e}"))
567 .into_retry(),
568 );
569 }
570
571 let response = client.execute(request).await.map_err(|e| {
572 IngestionClientError::Transient(format!("network error: {e}")).into_retry()
573 })?;
574
575 let status = response.status();
576 match status.as_u16() {
577 200..=299 => Ok(()),
578 400..=499 => {
579 let body = response.text().await.unwrap_or_default();
580 warn!("insert_stream client error ({status}): {body}");
581 Err(IngestionClientError::Permanent(body).into_retry())
582 }
583 500..=599 => {
584 let body = response.text().await.unwrap_or_default();
585 debug!("insert_stream server error ({status}): {body}");
586 Err(IngestionClientError::Transient(format!("{status}: {body}")).into_retry())
587 }
588 _ => {
589 let body = response.text().await.unwrap_or_default();
590 warn!("insert_stream unexpected status ({status}): {body}");
591 Err(IngestionClientError::Permanent(format!("{status}: {body}")).into_retry())
592 }
593 }
594 })
595 .await
596 }
597
598 #[span_fn]
599 async fn push_block(
600 client: &reqwest::Client,
601 root_path: &str,
602 buffer: &dyn StreamBlock,
603 retry_strategy: RetryStrategy,
604 decorator: &dyn RequestDecorator,
605 process_info: &ProcessInfo,
606 ) -> Result<(), IngestionClientError> {
607 trace!("push_block");
608 let encoded_block: bytes::Bytes = buffer
609 .encode_bin(process_info)
610 .map_err(|e| IngestionClientError::Permanent(format!("encoding block: {e}")))?
611 .into();
612
613 let url = format!("{root_path}/ingestion/insert_block");
614
615 tokio_retry2::Retry::spawn(retry_strategy, || async {
616 let mut request = client
617 .post(&url)
618 .body(encoded_block.clone())
619 .build()
620 .map_err(|e| {
621 IngestionClientError::Permanent(format!("building request: {e}")).into_retry()
622 })?;
623
624 if let Err(e) = decorator.decorate(&mut request).await {
625 debug!("request decorator: {e:?}");
626 return Err(
627 IngestionClientError::Transient(format!("decorating request: {e}"))
628 .into_retry(),
629 );
630 }
631
632 trace!("push_block: executing request");
633
634 let response = client.execute(request).await.map_err(|e| {
635 IngestionClientError::Transient(format!("network error: {e}")).into_retry()
636 })?;
637
638 let status = response.status();
639 match status.as_u16() {
640 200..=299 => Ok(()),
641 400..=499 => {
642 let body = response.text().await.unwrap_or_default();
643 warn!("insert_block client error ({status}): {body}");
644 Err(IngestionClientError::Permanent(body).into_retry())
645 }
646 500..=599 => {
647 let body = response.text().await.unwrap_or_default();
648 debug!("insert_block server error ({status}): {body}");
649 Err(IngestionClientError::Transient(format!("{status}: {body}")).into_retry())
650 }
651 _ => {
652 let body = response.text().await.unwrap_or_default();
653 warn!("insert_block unexpected status ({status}): {body}");
654 Err(IngestionClientError::Permanent(format!("{status}: {body}")).into_retry())
655 }
656 }
657 })
658 .await
659 }
660
661 fn spawn_item(
669 shared: &Arc<WorkerShared>,
670 queue: &Arc<SharedQueue>,
671 item: QueuedItem,
672 join_set: &mut tokio::task::JoinSet<()>,
673 permit: Option<OwnedSemaphorePermit>,
674 ) {
675 queue.in_flight_count.fetch_add(1, Ordering::Relaxed);
680 if let Payload::Process(ref info) = item.payload {
681 *shared.process_info.lock().unwrap() = Some(info.clone());
682 }
683 let shared = shared.clone();
684 let queue = queue.clone();
685 let retry_strategy = shared.retry_by_priority[item.priority as usize].clone();
686 join_set.spawn(async move {
687 let _wake_on_drop = WakeOnDrop {
692 queue,
693 _permit: permit,
694 };
695 match item.payload {
696 Payload::Process(info) => {
697 if let Err(e) = Self::push_process(
698 &shared.client,
699 &shared.addr,
700 info,
701 retry_strategy,
702 shared.decorator.as_ref(),
703 )
704 .await
705 {
706 error!("error sending process: {e}");
707 }
708 }
709 Payload::Stream(stream_info) => {
710 if let Err(e) = Self::push_stream(
711 &shared.client,
712 &shared.addr,
713 stream_info,
714 retry_strategy,
715 shared.decorator.as_ref(),
716 )
717 .await
718 {
719 error!("error sending stream: {e}");
720 }
721 }
722 Payload::Block { block, kind } => {
723 let maybe_process_info = shared.process_info.lock().unwrap().clone();
724 if let Some(process_info) = maybe_process_info {
725 if let Err(e) = Self::push_block(
726 &shared.client,
727 &shared.addr,
728 block.as_ref(),
729 retry_strategy,
730 shared.decorator.as_ref(),
731 &process_info,
732 )
733 .await
734 {
735 error!("error sending {kind}: {e}");
736 }
737 } else {
738 error!("trying to send blocks before Startup message");
739 }
740 }
741 }
742 });
743 }
744
745 async fn run(
746 addr: String,
747 queue: Arc<SharedQueue>,
748 config: WorkerConfig,
749 make_decorator: Box<dyn FnOnce() -> Arc<dyn RequestDecorator> + Send>,
750 shutdown_complete: Arc<(Mutex<bool>, std::sync::Condvar)>,
751 ) {
752 let client = match reqwest::Client::builder()
753 .pool_idle_timeout(Some(core::time::Duration::from_secs(2)))
754 .timeout(config.request_timeout)
755 .build()
756 {
757 Ok(client) => client,
758 Err(e) => {
759 error!("Error creating http client: {e:?}");
760 return;
761 }
762 };
763 if let Some(process_id) = micromegas_tracing::dispatch::process_id() {
765 let cpu_tracing_enabled =
766 micromegas_tracing::dispatch::cpu_tracing_enabled().unwrap_or(false);
767 info!("process_id={process_id}, cpu_tracing_enabled={cpu_tracing_enabled}");
768 }
769 let shared = Arc::new(WorkerShared {
770 client,
771 addr,
772 process_info: Mutex::new(None),
773 decorator: make_decorator(),
774 retry_by_priority: config.retry_by_priority,
775 });
776 let semaphore = Arc::new(Semaphore::new(config.max_in_flight_requests.max(1)));
777 let mut join_set: tokio::task::JoinSet<()> = tokio::task::JoinSet::new();
778 let flusher = FlushMonitor::default();
779
780 loop {
781 if queue.shutdown.load(Ordering::SeqCst) {
782 break;
783 }
784
785 loop {
786 let permit = match Arc::clone(&semaphore).try_acquire_owned() {
787 Ok(permit) => permit,
788 Err(_) => break,
789 };
790 match queue.pop_highest() {
791 Some(item) => {
792 Self::spawn_item(&shared, &queue, item, &mut join_set, Some(permit))
793 }
794 None => {
795 drop(permit);
796 break;
797 }
798 }
799 }
800 while let Some(result) = join_set.try_join_next() {
802 queue.in_flight_count.fetch_sub(1, Ordering::Relaxed);
803 if let Err(e) = result {
804 error!("telemetry send task panicked: {e}");
805 }
806 }
807
808 let timeout = Duration::from_secs(max(0, flusher.time_to_flush_seconds()) as u64);
812 {
813 let guard = queue.queues.lock().unwrap();
814 let empty = guard.iter().all(VecDeque::is_empty);
815 let permit_available = semaphore.available_permits() > 0;
816 if !queue.shutdown.load(Ordering::SeqCst) && (empty || !permit_available) {
817 let _ = queue.notify.wait_timeout(guard, timeout);
818 }
819 }
820 flusher.tick();
821 report_dropped_items(&queue);
822 }
823
824 debug!("received shutdown signal, flushing remaining data");
825 let mut drained = 0;
828 while let Some(item) = queue.pop_highest() {
829 drained += 1;
830 Self::spawn_item(&shared, &queue, item, &mut join_set, None);
831 }
832 while let Some(result) = join_set.join_next().await {
833 queue.in_flight_count.fetch_sub(1, Ordering::Relaxed);
834 if let Err(e) = result {
835 error!("telemetry send task panicked: {e}");
836 }
837 }
838 debug!("telemetry thread shutdown complete, drained {drained} remaining items");
839 report_dropped_items(&queue);
843
844 let (lock, cvar) = &*shutdown_complete;
845 let mut completed = lock.lock().unwrap();
846 *completed = true;
847 cvar.notify_all();
848 }
849
850 fn thread_proc(
851 addr: String,
852 queue: Arc<SharedQueue>,
853 config: WorkerConfig,
854 make_decorator: Box<dyn FnOnce() -> Arc<dyn RequestDecorator> + Send>,
855 shutdown_complete: Arc<(Mutex<bool>, std::sync::Condvar)>,
856 ) {
857 let tokio_runtime = match tokio::runtime::Runtime::new() {
859 Ok(rt) => rt,
860 Err(e) => {
861 error!("Failed to create tokio runtime for telemetry: {e}");
862 return;
863 }
864 };
865 tokio_runtime.block_on(Self::run(
866 addr,
867 queue,
868 config,
869 make_decorator,
870 shutdown_complete,
871 ));
872 }
873}
874
875impl EventSink for HttpEventSink {
876 fn on_startup(&self, process_info: Arc<ProcessInfo>) {
877 self.queue.try_push(QueuedItem {
878 priority: UploadPriority::Metadata,
879 bytes: 0,
880 payload: Payload::Process(process_info),
881 });
882 }
883
884 fn on_shutdown(&self) {
885 self.signal_shutdown_and_wait();
889 }
890
891 fn on_log_enabled(&self, _metadata: &LogMetadata) -> bool {
892 true
894 }
895
896 fn on_log(
897 &self,
898 _metadata: &LogMetadata,
899 _properties: &[DynProperty<'_>],
900 _time: i64,
901 _args: fmt::Arguments<'_>,
902 ) {
903 }
904
905 fn on_init_log_stream(&self, log_stream: &LogStream) {
906 self.pending_stream_meta.lock().unwrap().insert(
907 log_stream.stream_id(),
908 Arc::new(make_stream_info(log_stream)),
909 );
910 }
911
912 fn on_process_log_block(&self, log_block: Arc<LogBlock>) {
913 self.flush_pending_stream_meta(log_block.stream_id);
914 let bytes = log_block.len_bytes();
915 self.queue.try_push(QueuedItem {
916 priority: UploadPriority::Logs,
917 bytes,
918 payload: Payload::Block {
919 block: log_block,
920 kind: "log block",
921 },
922 });
923 }
924
925 fn on_init_metrics_stream(&self, metrics_stream: &MetricsStream) {
926 self.pending_stream_meta.lock().unwrap().insert(
927 metrics_stream.stream_id(),
928 Arc::new(make_stream_info(metrics_stream)),
929 );
930 }
931
932 fn on_process_metrics_block(&self, metrics_block: Arc<MetricsBlock>) {
933 self.flush_pending_stream_meta(metrics_block.stream_id);
934 let bytes = metrics_block.len_bytes();
935 self.queue.try_push(QueuedItem {
936 priority: UploadPriority::Metrics,
937 bytes,
938 payload: Payload::Block {
939 block: metrics_block,
940 kind: "metrics block",
941 },
942 });
943 }
944
945 fn on_init_thread_stream(&self, thread_stream: &ThreadStream) {
946 self.pending_stream_meta.lock().unwrap().insert(
947 thread_stream.stream_id(),
948 Arc::new(make_stream_info(thread_stream)),
949 );
950 }
951
952 fn on_process_thread_block(&self, thread_block: Arc<ThreadBlock>) {
953 self.flush_pending_stream_meta(thread_block.stream_id);
954 let bytes = thread_block.len_bytes();
955 self.queue.try_push(QueuedItem {
956 priority: UploadPriority::Traces,
957 bytes,
958 payload: Payload::Block {
959 block: thread_block,
960 kind: "thread block",
961 },
962 });
963 }
964
965 fn on_init_image_stream(&self, stream: &ImageStream) {
966 self.pending_stream_meta
967 .lock()
968 .unwrap()
969 .insert(stream.stream_id(), Arc::new(make_stream_info(stream)));
970 }
971
972 fn on_process_image_block(&self, block: Arc<ImageBlock>) {
973 self.flush_pending_stream_meta(block.stream_id);
974 let bytes = block.len_bytes();
975 self.queue.try_push(QueuedItem {
976 priority: UploadPriority::Traces,
977 bytes,
978 payload: Payload::Block {
979 block,
980 kind: "image block",
981 },
982 });
983 }
984
985 fn is_busy(&self) -> bool {
986 let queued = self.queue.queue_count.load(Ordering::Relaxed);
987 debug_assert!(queued >= 0, "queue_count went negative: {queued}");
988 let in_flight = self.queue.in_flight_count.load(Ordering::Relaxed);
989 debug_assert!(in_flight >= 0, "in_flight_count went negative: {in_flight}");
990 queued > 0 || in_flight > 0
991 }
992}