1use super::{
2 blocks_view::BlocksView,
3 dataframe_time_bounds::{DataFrameTimeBounds, NamedColumnsTimeBounds},
4 jit_partitions::{
5 BlockOrder, JitPartitionConfig, blocks_insert_time_range, generate_process_jit_partitions,
6 group_contiguous_block_chains, is_jit_partition_up_to_date,
7 },
8 lakehouse_context::LakehouseContext,
9 partition_cache::PartitionCache,
10 partition_source_data::{SourceDataBlocksInMemory, hash_to_object_count},
11 view::{PartitionSpec, View, ViewMetadata},
12 view_factory::{ViewFactory, ViewMaker},
13};
14use crate::{
15 lakehouse::write_partition::{PartitionRowSet, RetireMatch, write_partition_from_rows},
16 metadata::{StreamMetadata, find_process_with_latest_timing},
17 net_span_tree::make_net_span_tree,
18 net_spans_table::{NetSpanRecordBuilder, net_spans_table_schema},
19 response_writer::ResponseWriter,
20 time::{ConvertTicks, TimeRange, datetime_to_scalar, make_time_converter_from_latest_timing},
21};
22use anyhow::{Context, Result};
23use async_trait::async_trait;
24use chrono::{DateTime, Utc};
25use datafusion::logical_expr::{BinaryExpr, Expr, Operator};
26use datafusion::{arrow::datatypes::Schema, logical_expr::expr_fn::col};
27use micromegas_ingestion::data_lake_connection::DataLakeConnection;
28use micromegas_telemetry::{blob_storage::BlobStorage, types::block::BlockMetadata};
29use micromegas_tracing::prelude::*;
30use std::sync::Arc;
31use uuid::Uuid;
32
33const VIEW_SET_NAME: &str = "net_spans";
34const SCHEMA_VERSION: u8 = 2;
35const NET_STREAM_TAG: &str = "net";
36
37lazy_static::lazy_static! {
38 static ref BEGIN_TIME_COLUMN: Arc<String> = Arc::new(String::from("begin_time"));
39 static ref END_TIME_COLUMN: Arc<String> = Arc::new(String::from("end_time"));
40}
41
42#[derive(Debug)]
44pub struct NetSpansViewMaker {
45 view_factory: Arc<ViewFactory>,
46}
47
48impl NetSpansViewMaker {
49 pub fn new(view_factory: Arc<ViewFactory>) -> Self {
50 Self { view_factory }
51 }
52}
53
54impl ViewMaker for NetSpansViewMaker {
55 fn make_view(&self, view_instance_id: &str) -> Result<Arc<dyn View>> {
56 Ok(Arc::new(NetSpansView::new(
57 view_instance_id,
58 self.view_factory.clone(),
59 )?))
60 }
61
62 fn get_schema_hash(&self) -> Vec<u8> {
63 vec![SCHEMA_VERSION]
64 }
65
66 fn get_schema(&self) -> Arc<Schema> {
67 Arc::new(net_spans_table_schema())
68 }
69}
70
71#[derive(Debug)]
73pub struct NetSpansView {
74 view_set_name: Arc<String>,
75 view_instance_id: Arc<String>,
76 process_id: sqlx::types::Uuid,
77 view_factory: Arc<ViewFactory>,
78}
79
80impl NetSpansView {
81 pub fn new(view_instance_id: &str, view_factory: Arc<ViewFactory>) -> Result<Self> {
82 if view_instance_id == "global" {
83 anyhow::bail!("NetSpansView does not support global view access");
84 }
85 let process_id = Uuid::parse_str(view_instance_id).with_context(|| "Uuid::parse_str")?;
86 Ok(Self {
87 view_set_name: Arc::new(String::from(VIEW_SET_NAME)),
88 view_instance_id: Arc::new(view_instance_id.into()),
89 process_id,
90 view_factory,
91 })
92 }
93}
94
95#[span_fn]
96async fn append_net_span_tree(
97 record_builder: &mut NetSpanRecordBuilder,
98 convert_ticks: &ConvertTicks,
99 blocks: &[BlockMetadata],
100 blob_storage: Arc<BlobStorage>,
101 stream: &StreamMetadata,
102 process_id: Arc<String>,
103) -> Result<()> {
104 make_net_span_tree(
105 blocks,
106 record_builder,
107 blob_storage,
108 stream,
109 process_id,
110 convert_ticks.clone(),
111 )
112 .await
113 .with_context(|| "make_net_span_tree")
114}
115
116#[span_fn]
118async fn write_partition(
119 lake: Arc<DataLakeConnection>,
120 view_meta: ViewMetadata,
121 schema: Arc<Schema>,
122 convert_ticks: &ConvertTicks,
123 spec: &SourceDataBlocksInMemory,
124 process_id: Arc<String>,
125 same_run_ranges: &[TimeRange],
126) -> Result<()> {
127 let nb_events = hash_to_object_count(&spec.block_ids_hash)? as usize;
128 info!("nb_events: {nb_events}");
129 if spec.blocks.is_empty() {
130 anyhow::bail!("empty partition spec");
131 }
132 let insert_range =
136 blocks_insert_time_range(&spec.blocks).with_context(|| "blocks_insert_time_range")?;
137
138 let (tx, rx) = tokio::sync::mpsc::channel(1);
139 let null_response_writer = Arc::new(ResponseWriter::new(None));
140 let join_handle = spawn_with_context(write_partition_from_rows(
141 lake.clone(),
142 view_meta,
143 schema,
144 insert_range,
145 spec.block_ids_hash.clone(),
146 None,
147 RetireMatch::Overlap,
148 same_run_ranges.to_vec(),
149 rx,
150 null_response_writer,
151 ));
152
153 let build_result: Result<Option<PartitionRowSet>> = async {
154 let mut record_builder = NetSpanRecordBuilder::with_capacity(nb_events / 2);
155 let stream = spec.blocks[0].stream.clone();
156 for b in &spec.blocks {
157 anyhow::ensure!(
158 b.stream.stream_id == stream.stream_id,
159 "net_spans partition contains multiple streams ({} and {}); expected one per process",
160 stream.stream_id,
161 b.stream.stream_id,
162 );
163 }
164 for chain in group_contiguous_block_chains(&spec.blocks) {
166 append_net_span_tree(
167 &mut record_builder,
168 convert_ticks,
169 &chain,
170 lake.blob_storage.clone(),
171 &stream,
172 process_id.clone(),
173 )
174 .await?;
175 }
176 let rows_time_range = match record_builder.get_time_range() {
182 Some(range) => range,
183 None => {
184 let min_ticks = spec
185 .blocks
186 .iter()
187 .map(|b| b.block.begin_ticks)
188 .min()
189 .with_context(|| "empty partition spec")?;
190 let max_ticks = spec
191 .blocks
192 .iter()
193 .map(|b| b.block.end_ticks)
194 .max()
195 .with_context(|| "empty partition spec")?;
196 TimeRange::new(
197 convert_ticks.delta_ticks_to_time(min_ticks),
198 convert_ticks.delta_ticks_to_time(max_ticks),
199 )
200 }
201 };
202 let nb_rows = record_builder.len();
203 let rows = record_builder
204 .finish()
205 .with_context(|| "record_builder.finish()")?;
206 info!("writing {nb_rows} rows");
207 if nb_rows > 0 {
208 Ok(Some(PartitionRowSet {
209 rows_time_range,
210 rows,
211 max_sort_key_time: None,
212 }))
213 } else {
214 Ok(None)
215 }
216 }
217 .await;
218
219 match build_result {
220 Ok(Some(row_set)) => {
221 tx.send(Ok(row_set)).await?;
222 drop(tx);
223 join_handle.await??;
224 Ok(())
225 }
226 Ok(None) => {
227 drop(tx);
228 join_handle.await??;
229 Ok(())
230 }
231 Err(e) => {
232 warn!(
233 "aborting net-spans partition write for block {:?}: {e:?}",
234 spec.block_ids_hash
235 );
236 let _ = tx
237 .send(Err(anyhow::anyhow!("net-spans build aborted")))
238 .await;
239 drop(tx);
240 match join_handle.await {
241 Ok(Ok(())) => {}
242 Ok(Err(writer_err)) => {
243 debug!("net-spans writer task error during abort: {writer_err:?}");
244 }
245 Err(join_err) => {
246 warn!("net-spans writer task panicked during abort: {join_err:?}");
247 }
248 }
249 Err(e)
250 }
251 }
252}
253
254#[span_fn]
260async fn update_partition(
261 lake: Arc<DataLakeConnection>,
262 view_meta: ViewMetadata,
263 schema: Arc<Schema>,
264 convert_ticks: &ConvertTicks,
265 spec: &SourceDataBlocksInMemory,
266 process_id: Arc<String>,
267 same_run_ranges: &mut Vec<TimeRange>,
268) -> Result<()> {
269 let insert_range =
270 blocks_insert_time_range(&spec.blocks).with_context(|| "blocks_insert_time_range")?;
271 if is_jit_partition_up_to_date(
272 &lake.db_pool,
273 view_meta.clone(),
274 spec,
275 BlockOrder::EventTime,
276 )
277 .await?
278 {
279 same_run_ranges.push(insert_range);
280 return Ok(());
281 }
282 write_partition(
283 lake,
284 view_meta,
285 schema,
286 convert_ticks,
287 spec,
288 process_id,
289 same_run_ranges.as_slice(),
290 )
291 .await
292 .with_context(|| "write_partition")?;
293 same_run_ranges.push(insert_range);
294 Ok(())
295}
296
297#[async_trait]
298impl View for NetSpansView {
299 fn get_view_set_name(&self) -> Arc<String> {
300 self.view_set_name.clone()
301 }
302
303 fn get_view_instance_id(&self) -> Arc<String> {
304 self.view_instance_id.clone()
305 }
306
307 async fn make_batch_partition_spec(
308 &self,
309 _lakehouse: Arc<LakehouseContext>,
310 _existing_partitions: Arc<PartitionCache>,
311 _insert_range: TimeRange,
312 ) -> Result<Arc<dyn PartitionSpec>> {
313 anyhow::bail!("NetSpansView does not support batch partition specs")
314 }
315
316 fn get_file_schema_hash(&self) -> Vec<u8> {
317 vec![SCHEMA_VERSION]
318 }
319
320 fn get_file_schema(&self) -> Arc<Schema> {
321 Arc::new(net_spans_table_schema())
322 }
323
324 #[span_fn]
325 async fn jit_update(
326 &self,
327 lakehouse: Arc<LakehouseContext>,
328 query_range: Option<TimeRange>,
329 ) -> Result<()> {
330 let (process, last_block_end_ticks, last_block_end_time) = find_process_with_latest_timing(
331 lakehouse.clone(),
332 self.view_factory.clone(),
333 &self.process_id,
334 query_range,
335 )
336 .await
337 .with_context(|| "find_process_with_latest_timing")?;
338
339 let process = Arc::new(process);
340 let query_range =
341 query_range.unwrap_or_else(|| TimeRange::new(process.start_time, last_block_end_time));
342
343 let convert_ticks = make_time_converter_from_latest_timing(
344 &process,
345 last_block_end_ticks,
346 last_block_end_time,
347 )
348 .with_context(|| "make_time_converter_from_latest_timing")?;
349
350 let blocks_view = BlocksView::new(lakehouse.default_audience())?;
351 let config = JitPartitionConfig {
360 block_order: BlockOrder::EventTime,
361 ..Default::default()
362 };
363 let all_partitions = generate_process_jit_partitions(
364 &config,
365 lakehouse.clone(),
366 &blocks_view,
367 &query_range,
368 process.clone(),
369 NET_STREAM_TAG,
370 )
371 .await
372 .with_context(|| "generate_process_jit_partitions")?;
373
374 let process_id_str = Arc::new(self.process_id.to_string());
375 let mut same_run_ranges: Vec<TimeRange> = Vec::new();
379 for part in &all_partitions {
380 update_partition(
381 lakehouse.lake().clone(),
382 ViewMetadata {
383 view_set_name: self.get_view_set_name(),
384 view_instance_id: self.get_view_instance_id(),
385 file_schema_hash: self.get_file_schema_hash(),
386 },
387 self.get_file_schema(),
388 &convert_ticks,
389 part,
390 process_id_str.clone(),
391 &mut same_run_ranges,
392 )
393 .await
394 .with_context(|| "update_partition")?;
395 }
396 Ok(())
397 }
398
399 fn make_time_filter(&self, begin: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<Expr>> {
400 Ok(vec![
401 Expr::BinaryExpr(BinaryExpr::new(
402 col("begin_time").into(),
403 Operator::LtEq,
404 Expr::Literal(datetime_to_scalar(end), None).into(),
405 )),
406 Expr::BinaryExpr(BinaryExpr::new(
407 col("end_time").into(),
408 Operator::GtEq,
409 Expr::Literal(datetime_to_scalar(begin), None).into(),
410 )),
411 ])
412 }
413
414 fn get_time_bounds(&self) -> Arc<dyn DataFrameTimeBounds> {
415 Arc::new(NamedColumnsTimeBounds::new(
416 BEGIN_TIME_COLUMN.clone(),
417 END_TIME_COLUMN.clone(),
418 ))
419 }
420
421 fn get_update_group(&self) -> Option<i32> {
422 None
423 }
424}