micromegas_analytics/lakehouse/otel/
spans_view.rs1use super::spans_block_processor::OtelSpansBlockProcessor;
13use super::spans_table::otel_spans_table_schema;
14use crate::lakehouse::{
15 batch_update::PartitionCreationStrategy,
16 block_partition_spec::{BlockProcessor, BlockProcessorMap},
17 blocks_view::BlocksView,
18 dataframe_time_bounds::{DataFrameTimeBounds, NamedColumnsTimeBounds},
19 jit_partitions::{
20 BlockOrder, JitPartitionConfig, find_up_to_date_partitions,
21 generate_process_jit_partitions, write_partition_from_blocks,
22 },
23 lakehouse_context::LakehouseContext,
24 partition_cache::PartitionCache,
25 view::{PartitionSpec, View, ViewMetadata},
26 view_factory::ViewMaker,
27};
28use crate::{
29 metadata::find_process,
30 time::{TimeRange, datetime_to_scalar},
31};
32use anyhow::{Context, Result};
33use async_trait::async_trait;
34use chrono::{DateTime, TimeDelta, Utc};
35use datafusion::{
36 arrow::datatypes::Schema,
37 logical_expr::{BinaryExpr, Expr, Operator, col},
38};
39use micromegas_ingestion::web_ingestion_service::FORMAT_OTLP_TRACES;
40use micromegas_tracing::prelude::*;
41use std::collections::HashMap;
42use std::sync::Arc;
43use uuid::Uuid;
44
45const VIEW_SET_NAME: &str = "otel_spans";
46const SCHEMA_VERSION: u8 = 1;
47lazy_static::lazy_static! {
48 static ref TIME_COLUMN: Arc<String> = Arc::new(String::from("start_time"));
49 static ref END_TIME_COLUMN: Arc<String> = Arc::new(String::from("end_time"));
50}
51
52#[derive(Debug)]
54pub struct OtelSpansViewMaker {}
55
56impl ViewMaker for OtelSpansViewMaker {
57 fn make_view(&self, view_instance_id: &str) -> Result<Arc<dyn View>> {
58 Ok(Arc::new(OtelSpansView::new(view_instance_id)?))
59 }
60
61 fn get_schema_hash(&self) -> Vec<u8> {
62 vec![SCHEMA_VERSION]
63 }
64
65 fn get_schema(&self) -> Arc<Schema> {
66 Arc::new(otel_spans_table_schema())
67 }
68}
69
70#[derive(Debug)]
71pub struct OtelSpansView {
72 view_set_name: Arc<String>,
73 view_instance_id: Arc<String>,
74 process_id: sqlx::types::Uuid,
75}
76
77impl OtelSpansView {
78 pub fn new(view_instance_id: &str) -> Result<Self> {
79 if view_instance_id == "global" {
80 anyhow::bail!("OtelSpansView does not support global view access");
81 }
82 let process_id = Uuid::parse_str(view_instance_id).with_context(|| "Uuid::parse_str")?;
83 Ok(Self {
84 view_set_name: Arc::new(String::from(VIEW_SET_NAME)),
85 view_instance_id: Arc::new(view_instance_id.into()),
86 process_id,
87 })
88 }
89}
90
91#[async_trait]
92impl View for OtelSpansView {
93 fn get_view_set_name(&self) -> Arc<String> {
94 self.view_set_name.clone()
95 }
96
97 fn get_view_instance_id(&self) -> Arc<String> {
98 self.view_instance_id.clone()
99 }
100
101 async fn make_batch_partition_spec(
102 &self,
103 _lakehouse: Arc<LakehouseContext>,
104 _existing_partitions: Arc<PartitionCache>,
105 _insert_range: TimeRange,
106 ) -> Result<Arc<dyn PartitionSpec>> {
107 anyhow::bail!("OtelSpansView does not support batch partition specs");
108 }
109
110 fn get_file_schema_hash(&self) -> Vec<u8> {
111 vec![SCHEMA_VERSION]
112 }
113
114 fn get_file_schema(&self) -> Arc<Schema> {
115 Arc::new(otel_spans_table_schema())
116 }
117
118 #[span_fn]
119 async fn jit_update(
120 &self,
121 lakehouse: Arc<LakehouseContext>,
122 query_range: Option<TimeRange>,
123 ) -> Result<()> {
124 let process = Arc::new(
125 find_process(
126 &lakehouse.lake().db_pool,
127 &self.process_id,
128 &lakehouse.default_audience(),
129 )
130 .await
131 .with_context(|| "find_process")?,
132 );
133 let query_range =
134 query_range.unwrap_or_else(|| TimeRange::new(process.start_time, chrono::Utc::now()));
135
136 let blocks_view = BlocksView::new(lakehouse.default_audience())?;
137 let all_partitions = generate_process_jit_partitions(
138 &JitPartitionConfig::default(),
139 lakehouse.clone(),
140 &blocks_view,
141 &query_range,
142 process.clone(),
143 "trace",
144 )
145 .await
146 .with_context(|| "generate_process_jit_partitions")?;
147 let view_meta = ViewMetadata {
148 view_set_name: self.get_view_set_name(),
149 view_instance_id: self.get_view_instance_id(),
150 file_schema_hash: self.get_file_schema_hash(),
151 };
152
153 let mut block_processors: BlockProcessorMap = HashMap::new();
154 block_processors.insert(
155 FORMAT_OTLP_TRACES,
156 Arc::new(OtelSpansBlockProcessor {}) as Arc<dyn BlockProcessor>,
157 );
158 let block_processors = Arc::new(block_processors);
159
160 let up_to_date = find_up_to_date_partitions(
161 &lakehouse.lake().db_pool,
162 view_meta.clone(),
163 BlockOrder::InsertTime,
164 &all_partitions,
165 )
166 .await
167 .with_context(|| "find_up_to_date_partitions")?;
168 for (part, up_to_date) in all_partitions.into_iter().zip(up_to_date) {
169 if !up_to_date {
170 write_partition_from_blocks(
171 lakehouse.lake().clone(),
172 view_meta.clone(),
173 self.get_file_schema(),
174 part,
175 block_processors.clone(),
176 )
177 .await
178 .with_context(|| "write_partition_from_blocks")?;
179 }
180 }
181 Ok(())
182 }
183
184 fn make_time_filter(&self, begin: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<Expr>> {
185 Ok(vec![
190 Expr::BinaryExpr(BinaryExpr::new(
191 col("start_time").into(),
192 Operator::LtEq,
193 Expr::Literal(datetime_to_scalar(end), None).into(),
194 )),
195 Expr::BinaryExpr(BinaryExpr::new(
196 col("end_time").into(),
197 Operator::GtEq,
198 Expr::Literal(datetime_to_scalar(begin), None).into(),
199 )),
200 ])
201 }
202
203 fn get_time_bounds(&self) -> Arc<dyn DataFrameTimeBounds> {
204 Arc::new(NamedColumnsTimeBounds::new(
205 TIME_COLUMN.clone(),
206 END_TIME_COLUMN.clone(),
207 ))
208 }
209
210 fn get_update_group(&self) -> Option<i32> {
211 None }
213
214 fn get_max_partition_time_delta(&self, _strategy: &PartitionCreationStrategy) -> TimeDelta {
215 TimeDelta::hours(1)
216 }
217}