micromegas_analytics/lakehouse/
thread_spans_view.rs1use super::{
2 blocks_view::BlocksView,
3 dataframe_time_bounds::{DataFrameTimeBounds, NamedColumnsTimeBounds},
4 jit_partitions::{
5 JitPartitionConfig, generate_stream_jit_partitions, is_jit_partition_up_to_date,
6 },
7 lakehouse_context::LakehouseContext,
8 partition_cache::PartitionCache,
9 partition_source_data::{SourceDataBlocksInMemory, hash_to_object_count},
10 view::{PartitionSpec, ScanSortColumn, View, ViewMetadata},
11 view_factory::{ViewFactory, ViewMaker},
12};
13use crate::{
14 call_tree::make_call_tree,
15 lakehouse::write_partition::{PartitionRowSet, write_partition_from_rows},
16 metadata::{find_process_with_latest_timing, find_stream_from_view},
17 response_writer::ResponseWriter,
18 span_table::{SpanRecordBuilder, get_spans_schema},
19 time::{ConvertTicks, TimeRange, datetime_to_scalar, make_time_converter_from_latest_timing},
20};
21use anyhow::{Context, Result};
22use async_trait::async_trait;
23use chrono::{DateTime, Utc};
24use datafusion::logical_expr::{BinaryExpr, Expr, Operator};
25use datafusion::{arrow::datatypes::Schema, logical_expr::expr_fn::col};
26use micromegas_ingestion::data_lake_connection::DataLakeConnection;
27use micromegas_telemetry::{blob_storage::BlobStorage, types::block::BlockMetadata};
28use micromegas_tracing::prelude::*;
29use std::sync::Arc;
30use uuid::Uuid;
31
32const VIEW_SET_NAME: &str = "thread_spans";
33const SCHEMA_VERSION: u8 = 1;
34lazy_static::lazy_static! {
35 static ref MIN_TIME_COLUMN: Arc<String> = Arc::new( String::from("begin"));
36 static ref MAX_TIME_COLUMN: Arc<String> = Arc::new( String::from("end"));
37}
38
39#[derive(Debug)]
41pub struct ThreadSpansViewMaker {
42 view_factory: Arc<ViewFactory>,
43}
44
45impl ThreadSpansViewMaker {
46 pub fn new(view_factory: Arc<ViewFactory>) -> Self {
47 Self { view_factory }
48 }
49}
50
51impl ViewMaker for ThreadSpansViewMaker {
52 fn make_view(&self, stream_id: &str) -> Result<Arc<dyn View>> {
53 Ok(Arc::new(ThreadSpansView::new(
54 stream_id,
55 self.view_factory.clone(),
56 )?))
57 }
58
59 fn get_schema_hash(&self) -> Vec<u8> {
60 vec![SCHEMA_VERSION]
61 }
62
63 fn get_schema(&self) -> Arc<Schema> {
64 Arc::new(get_spans_schema())
65 }
66}
67
68#[derive(Debug)]
70pub struct ThreadSpansView {
71 view_set_name: Arc<String>,
72 view_instance_id: Arc<String>,
73 stream_id: sqlx::types::Uuid,
74 view_factory: Arc<ViewFactory>,
75}
76
77impl ThreadSpansView {
78 pub fn new(view_instance_id: &str, view_factory: Arc<ViewFactory>) -> Result<Self> {
79 if view_instance_id == "global" {
80 anyhow::bail!("the global view is not implemented for thread spans");
81 }
82
83 Ok(Self {
84 view_set_name: Arc::new(String::from(VIEW_SET_NAME)),
85 view_instance_id: Arc::new(String::from(view_instance_id)),
86 stream_id: Uuid::parse_str(view_instance_id).with_context(|| "Uuid::parse_str")?,
87 view_factory,
88 })
89 }
90}
91
92#[span_fn]
93async fn append_call_tree(
94 record_builder: &mut SpanRecordBuilder,
95 convert_ticks: &ConvertTicks,
96 blocks: &[BlockMetadata],
97 blob_storage: Arc<BlobStorage>,
98 stream: &crate::metadata::StreamMetadata,
99) -> Result<()> {
100 let call_tree = make_call_tree(
101 blocks,
102 convert_ticks.delta_ticks_to_ns(blocks[0].begin_ticks),
103 convert_ticks.delta_ticks_to_ns(blocks[blocks.len() - 1].end_ticks),
104 None,
105 blob_storage,
106 convert_ticks.clone(),
107 stream,
108 )
109 .await
110 .with_context(|| "make_call_tree")?;
111 record_builder
112 .append_call_tree(&call_tree)
113 .with_context(|| "adding call tree to span record builder")?;
114 Ok(())
115}
116
117#[span_fn]
119async fn write_partition(
120 lake: Arc<DataLakeConnection>,
121 view_meta: ViewMetadata,
122 schema: Arc<Schema>,
123 convert_ticks: &ConvertTicks,
124 spec: &SourceDataBlocksInMemory,
125) -> Result<()> {
126 let nb_events = hash_to_object_count(&spec.block_ids_hash)? as usize;
127 info!("nb_events: {nb_events}");
128 if spec.blocks.is_empty() {
129 anyhow::bail!("empty partition spec");
130 }
131 let min_insert_time = spec.blocks[0].block.insert_time;
134 let max_insert_time = spec.blocks[spec.blocks.len() - 1].block.insert_time;
135
136 let (tx, rx) = tokio::sync::mpsc::channel(1);
137 let null_response_writer = Arc::new(ResponseWriter::new(None));
138 let join_handle = spawn_with_context(write_partition_from_rows(
139 lake.clone(),
140 view_meta,
141 schema,
142 TimeRange::new(min_insert_time, max_insert_time),
143 spec.block_ids_hash.clone(),
144 None,
145 rx,
146 null_response_writer,
147 ));
148
149 let build_result: Result<PartitionRowSet> = async {
150 let mut record_builder = SpanRecordBuilder::with_capacity(nb_events / 2);
151 let mut blocks_to_process = vec![];
152 let mut last_end = None;
153 for block in &spec.blocks {
154 if block.block.begin_ticks == last_end.unwrap_or(block.block.begin_ticks) {
155 last_end = Some(block.block.end_ticks);
156 blocks_to_process.push(block.block.clone());
157 } else {
158 append_call_tree(
159 &mut record_builder,
160 convert_ticks,
161 &blocks_to_process,
162 lake.blob_storage.clone(),
163 &block.stream,
164 )
165 .await?;
166 last_end = Some(block.block.end_ticks);
167 blocks_to_process = vec![block.block.clone()];
168 }
169 }
170 if !blocks_to_process.is_empty() {
171 append_call_tree(
172 &mut record_builder,
173 convert_ticks,
174 &blocks_to_process,
175 lake.blob_storage.clone(),
176 &spec.blocks[0].stream,
177 )
178 .await?;
179 }
180 let min_time_row = convert_ticks.delta_ticks_to_time(spec.blocks[0].block.begin_ticks);
181 let max_time_row =
182 convert_ticks.delta_ticks_to_time(spec.blocks[spec.blocks.len() - 1].block.end_ticks);
183 let rows = record_builder
184 .finish()
185 .with_context(|| "record_builder.finish()")?;
186 info!("writing {} rows", rows.num_rows());
187 Ok(PartitionRowSet {
188 rows_time_range: TimeRange::new(min_time_row, max_time_row),
189 rows,
190 })
191 }
192 .await;
193
194 match build_result {
195 Ok(row_set) => {
196 tx.send(Ok(row_set)).await?;
197 drop(tx);
198 join_handle.await??;
199 Ok(())
200 }
201 Err(e) => {
202 warn!(
203 "aborting thread-spans partition write for block {:?}: {e:?}",
204 spec.block_ids_hash
205 );
206 let _ = tx
207 .send(Err(anyhow::anyhow!("thread-spans build aborted")))
208 .await;
209 drop(tx);
210 match join_handle.await {
211 Ok(Ok(())) => {}
212 Ok(Err(writer_err)) => {
213 debug!("thread-spans writer task error during abort: {writer_err:?}");
214 }
215 Err(join_err) => {
216 warn!("thread-spans writer task panicked during abort: {join_err:?}");
217 }
218 }
219 Err(e)
220 }
221 }
222}
223#[span_fn]
225async fn update_partition(
226 lake: Arc<DataLakeConnection>,
227 view_meta: ViewMetadata,
228 schema: Arc<Schema>,
229 convert_ticks: &ConvertTicks,
230 spec: &SourceDataBlocksInMemory,
231) -> Result<()> {
232 if is_jit_partition_up_to_date(&lake.db_pool, view_meta.clone(), spec).await? {
233 return Ok(());
234 }
235 write_partition(lake, view_meta, schema, convert_ticks, spec)
236 .await
237 .with_context(|| "write_partition")?;
238
239 Ok(())
240}
241
242#[async_trait]
243impl View for ThreadSpansView {
244 fn get_view_set_name(&self) -> Arc<String> {
245 self.view_set_name.clone()
246 }
247
248 fn get_view_instance_id(&self) -> Arc<String> {
249 self.view_instance_id.clone()
250 }
251
252 async fn make_batch_partition_spec(
253 &self,
254 _lakehouse: Arc<LakehouseContext>,
255 _existing_partitions: Arc<PartitionCache>,
256 _insert_range: TimeRange,
257 ) -> Result<Arc<dyn PartitionSpec>> {
258 anyhow::bail!("not implemented")
259 }
260
261 fn get_file_schema_hash(&self) -> Vec<u8> {
262 vec![SCHEMA_VERSION]
263 }
264
265 fn get_file_schema(&self) -> Arc<Schema> {
266 Arc::new(get_spans_schema())
267 }
268
269 #[span_fn]
270 async fn jit_update(
271 &self,
272 lakehouse: Arc<LakehouseContext>,
273 query_range: Option<TimeRange>,
274 ) -> Result<()> {
275 let Some(query_range) = query_range else {
276 anyhow::bail!("query range mandatory for thread spans view");
277 };
278 let stream = Arc::new(
279 find_stream_from_view(
280 lakehouse.clone(),
281 self.view_factory.clone(),
282 &self.stream_id,
283 None,
284 )
285 .await
286 .with_context(|| "find_stream_from_view")?,
287 );
288 let (process, last_block_end_ticks, last_block_end_time) = find_process_with_latest_timing(
289 lakehouse.clone(),
290 self.view_factory.clone(),
291 &stream.process_id,
292 None,
293 )
294 .await
295 .with_context(|| "find_process_with_latest_timing")?;
296 let process = Arc::new(process);
297 let convert_ticks = make_time_converter_from_latest_timing(
298 &process,
299 last_block_end_ticks,
300 last_block_end_time,
301 )
302 .with_context(|| "make_time_converter_from_latest_timing")?;
303 let blocks_view = BlocksView::new()?;
304 let partitions = generate_stream_jit_partitions(
305 &JitPartitionConfig::default(),
306 lakehouse.clone(),
307 &blocks_view,
308 &query_range,
309 stream.clone(),
310 process.clone(),
311 )
312 .await
313 .with_context(|| "generate_stream_jit_partitions")?;
314 for part in &partitions {
315 update_partition(
316 lakehouse.lake().clone(),
317 ViewMetadata {
318 view_set_name: self.get_view_set_name(),
319 view_instance_id: self.get_view_instance_id(),
320 file_schema_hash: self.get_file_schema_hash(),
321 },
322 self.get_file_schema(),
323 &convert_ticks,
324 part,
325 )
326 .await
327 .with_context(|| "update_partition")?;
328 }
329 Ok(())
330 }
331
332 fn make_time_filter(&self, begin: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<Expr>> {
333 Ok(vec![
334 Expr::BinaryExpr(BinaryExpr::new(
335 col("begin").into(),
336 Operator::LtEq,
337 Expr::Literal(datetime_to_scalar(end), None).into(),
338 )),
339 Expr::BinaryExpr(BinaryExpr::new(
340 col("end").into(),
341 Operator::GtEq,
342 Expr::Literal(datetime_to_scalar(begin), None).into(),
343 )),
344 ])
345 }
346
347 fn get_time_bounds(&self) -> Arc<dyn DataFrameTimeBounds> {
348 Arc::new(NamedColumnsTimeBounds::new(
349 MIN_TIME_COLUMN.clone(),
350 MAX_TIME_COLUMN.clone(),
351 ))
352 }
353
354 fn get_update_group(&self) -> Option<i32> {
355 None
356 }
357
358 fn get_scan_output_ordering(&self) -> Vec<ScanSortColumn> {
359 vec![ScanSortColumn {
360 column: MIN_TIME_COLUMN.clone(),
361 descending: false,
362 }]
363 }
364}