1use std::collections::BTreeMap;
11use std::convert::Infallible;
12use std::ops::Rem;
13use std::sync::Arc;
14use std::time::Duration;
15
16use differential_dataflow::{AsCollection, Hashable};
17use futures::StreamExt;
18use itertools::Itertools;
19use mz_ore::cast::CastFrom;
20use mz_ore::iter::IteratorExt;
21use mz_ore::now::NowFn;
22use mz_repr::{Diff, GlobalId, Row};
23use mz_storage_types::errors::DataflowError;
24use mz_storage_types::sources::load_generator::{
25 Event, Generator, KeyValueLoadGenerator, LoadGenerator, LoadGeneratorOutput,
26 LoadGeneratorSourceConnection,
27};
28use mz_storage_types::sources::{MzOffset, SourceExportDetails, SourceTimestamp};
29use mz_timely_util::builder_async::{
30 Event as AsyncEvent, OperatorBuilder as AsyncOperatorBuilder, PressOnDropButton,
31};
32use mz_timely_util::containers::stack::FueledBuilder;
33use timely::container::CapacityContainerBuilder;
34use timely::dataflow::channels::pact::Pipeline;
35use timely::dataflow::operators::core::Partition;
36use timely::dataflow::{Scope, StreamVec};
37use timely::progress::{Antichain, Timestamp};
38use tokio::time::{Instant, interval_at};
39
40use crate::healthcheck::{HealthStatusMessage, HealthStatusUpdate, StatusNamespace};
41use crate::source::types::{
42 FuelSize, Probe, ResumeUppers, SignaledFuture, SourceRender, StackedCollection,
43};
44use crate::source::{RawSourceCreationConfig, SourceMessage};
45
46mod auction;
47mod clock;
48mod counter;
49mod datums;
50mod key_value;
51mod marketing;
52mod tpch;
53
54pub use auction::Auction;
55pub use clock::Clock;
56pub use counter::Counter;
57pub use datums::Datums;
58pub use tpch::Tpch;
59
60use self::marketing::Marketing;
61
62enum GeneratorKind {
63 Simple {
64 generator: Box<dyn Generator>,
65 tick_micros: Option<u64>,
66 as_of: u64,
67 up_to: u64,
68 },
69 KeyValue(KeyValueLoadGenerator),
70}
71
72impl GeneratorKind {
73 fn new(g: &LoadGenerator, tick_micros: Option<u64>, as_of: u64, up_to: u64) -> Self {
74 match g {
75 LoadGenerator::Auction => GeneratorKind::Simple {
76 generator: Box::new(Auction {}),
77 tick_micros,
78 as_of,
79 up_to,
80 },
81 LoadGenerator::Clock => GeneratorKind::Simple {
82 generator: Box::new(Clock {
83 tick_ms: tick_micros
84 .map(Duration::from_micros)
85 .unwrap_or(Duration::from_secs(1))
86 .as_millis()
87 .try_into()
88 .expect("reasonable tick interval"),
89 as_of_ms: as_of,
90 }),
91 tick_micros,
92 as_of,
93 up_to,
94 },
95 LoadGenerator::Counter { max_cardinality } => GeneratorKind::Simple {
96 generator: Box::new(Counter {
97 max_cardinality: max_cardinality.clone(),
98 }),
99 tick_micros,
100 as_of,
101 up_to,
102 },
103 LoadGenerator::Datums => GeneratorKind::Simple {
104 generator: Box::new(Datums {}),
105 tick_micros,
106 as_of,
107 up_to,
108 },
109 LoadGenerator::Marketing => GeneratorKind::Simple {
110 generator: Box::new(Marketing {}),
111 tick_micros,
112 as_of,
113 up_to,
114 },
115 LoadGenerator::Tpch {
116 count_supplier,
117 count_part,
118 count_customer,
119 count_orders,
120 count_clerk,
121 } => GeneratorKind::Simple {
122 generator: Box::new(Tpch {
123 count_supplier: *count_supplier,
124 count_part: *count_part,
125 count_customer: *count_customer,
126 count_orders: *count_orders,
127 count_clerk: *count_clerk,
128 tick: Duration::from_micros(tick_micros.unwrap_or(0)),
131 }),
132 tick_micros,
133 as_of,
134 up_to,
135 },
136 LoadGenerator::KeyValue(kv) => GeneratorKind::KeyValue(kv.clone()),
137 }
138 }
139
140 fn render<'scope>(
141 self,
142 scope: Scope<'scope, MzOffset>,
143 config: &RawSourceCreationConfig,
144 committed_uppers: impl futures::Stream<Item = ResumeUppers<MzOffset>> + 'static,
145 start_signal: impl std::future::Future<Output = ()> + 'static,
146 ) -> (
147 BTreeMap<
148 GlobalId,
149 StackedCollection<'scope, MzOffset, Result<SourceMessage, DataflowError>>,
150 >,
151 StreamVec<'scope, MzOffset, Infallible>,
152 StreamVec<'scope, MzOffset, HealthStatusMessage>,
153 Vec<PressOnDropButton>,
154 ) {
155 let mut output_map = BTreeMap::new();
157 let mut idx_to_exportid = BTreeMap::new();
159 output_map.insert(LoadGeneratorOutput::Default, Vec::new());
163 for (idx, (export_id, export)) in config.source_exports.iter().enumerate() {
164 let output_type = match &export.details {
165 SourceExportDetails::LoadGenerator(details) => details.output,
166 SourceExportDetails::None => continue,
168 _ => panic!("unexpected source export details: {:?}", export.details),
169 };
170 output_map
171 .entry(output_type)
172 .or_insert_with(Vec::new)
173 .push(idx);
174 idx_to_exportid.insert(idx, export_id.clone());
175 }
176
177 match self {
178 GeneratorKind::Simple {
179 tick_micros,
180 as_of,
181 up_to,
182 generator,
183 } => render_simple_generator(
184 generator,
185 tick_micros,
186 as_of.into(),
187 up_to.into(),
188 scope,
189 config,
190 committed_uppers,
191 output_map,
192 ),
193 GeneratorKind::KeyValue(kv) => key_value::render(
194 kv,
195 scope,
196 config.clone(),
197 committed_uppers,
198 start_signal,
199 output_map,
200 idx_to_exportid,
201 ),
202 }
203 }
204}
205
206impl SourceRender for LoadGeneratorSourceConnection {
207 type Time = MzOffset;
208
209 const STATUS_NAMESPACE: StatusNamespace = StatusNamespace::Generator;
210
211 fn render<'scope>(
212 self,
213 scope: Scope<'scope, MzOffset>,
214 config: &RawSourceCreationConfig,
215 committed_uppers: impl futures::Stream<Item = ResumeUppers<MzOffset>> + 'static,
216 start_signal: impl std::future::Future<Output = ()> + 'static,
217 ) -> (
218 BTreeMap<
219 GlobalId,
220 StackedCollection<'scope, MzOffset, Result<SourceMessage, DataflowError>>,
221 >,
222 StreamVec<'scope, MzOffset, HealthStatusMessage>,
223 StreamVec<'scope, MzOffset, Probe<MzOffset>>,
224 Vec<PressOnDropButton>,
225 ) {
226 let generator_kind = GeneratorKind::new(
227 &self.load_generator,
228 self.tick_micros,
229 self.as_of,
230 self.up_to,
231 );
232 let (updates, progress, health, button) =
233 generator_kind.render(scope, config, committed_uppers, start_signal);
234
235 let probe_stream = synthesize_probes(
236 config.id,
237 progress,
238 config.timestamp_interval,
239 config.now_fn.clone(),
240 );
241
242 (updates, health, probe_stream, button)
243 }
244}
245
246fn render_simple_generator<'scope>(
247 generator: Box<dyn Generator>,
248 tick_micros: Option<u64>,
249 as_of: MzOffset,
250 up_to: MzOffset,
251 scope: Scope<'scope, MzOffset>,
252 config: &RawSourceCreationConfig,
253 committed_uppers: impl futures::Stream<Item = ResumeUppers<MzOffset>> + 'static,
254 output_map: BTreeMap<LoadGeneratorOutput, Vec<usize>>,
255) -> (
256 BTreeMap<GlobalId, StackedCollection<'scope, MzOffset, Result<SourceMessage, DataflowError>>>,
257 StreamVec<'scope, MzOffset, Infallible>,
258 StreamVec<'scope, MzOffset, HealthStatusMessage>,
259 Vec<PressOnDropButton>,
260) {
261 let mut builder = AsyncOperatorBuilder::new(config.name.clone(), scope.clone());
262
263 let (data_output, stream) = builder.new_output::<FueledBuilder<
264 CapacityContainerBuilder<
265 Vec<(
266 (usize, Result<SourceMessage, DataflowError>),
267 MzOffset,
268 Diff,
269 )>,
270 >,
271 >>();
272 let export_ids: Vec<_> = config.source_exports.keys().copied().collect();
273 let partition_count = u64::cast_from(export_ids.len());
274 let data_streams: Vec<_> = stream.partition::<CapacityContainerBuilder<_>, _, _>(
275 partition_count,
276 |((output, data), time, diff): (
277 (usize, Result<SourceMessage, DataflowError>),
278 MzOffset,
279 Diff,
280 )| {
281 let output = u64::cast_from(output);
282 (output, (data, time, diff))
283 },
284 );
285 let mut data_collections = BTreeMap::new();
286 for (id, data_stream) in export_ids.iter().zip_eq(data_streams) {
287 data_collections.insert(*id, data_stream.as_collection());
288 }
289
290 let (_progress_output, progress_stream) = builder.new_output::<CapacityContainerBuilder<_>>();
291 let (health_output, health_stream) = builder.new_output::<CapacityContainerBuilder<_>>();
292
293 let busy_signal = Arc::clone(&config.busy_signal);
294 let source_resume_uppers = config.source_resume_uppers.clone();
295 let is_active_worker = config.responsible_for(());
299 let worker_count = config.worker_count;
300 let generator_worker_index =
301 (config.worker_id + worker_count - config.responsible_worker(())) % worker_count;
302 let source_statistics = config.statistics.clone();
303 let button = builder.build(move |caps| {
304 SignaledFuture::new(busy_signal, async move {
305 let [mut cap, mut progress_cap, health_cap] = caps.try_into().unwrap();
306
307 let mut health_cap = is_active_worker.then_some(health_cap);
309
310 if !is_active_worker {
311 for stats in source_statistics.values() {
313 stats.set_offset_known(0);
314 stats.set_offset_committed(0);
315 }
316 }
317
318 let resume_upper = Antichain::from_iter(
319 source_resume_uppers
320 .values()
321 .flat_map(|f| f.iter().map(MzOffset::decode_row)),
322 );
323
324 let Some(resume_offset) = resume_upper.into_option() else {
325 return;
326 };
327
328 let now_fn = mz_ore::now::SYSTEM_TIME.clone();
329
330 let start_instant = {
331 let now_millis = now_fn();
338 let now_instant = Instant::now();
339 let delay_millis = tick_micros
340 .map(|tick_micros| tick_micros / 1000)
341 .filter(|tick_millis| *tick_millis > 0)
342 .map(|tick_millis| tick_millis - now_millis.rem(tick_millis))
343 .unwrap_or(0);
344 now_instant + Duration::from_millis(delay_millis)
345 };
346 let tick = Duration::from_micros(tick_micros.unwrap_or(1_000_000));
347 let mut tick_interval = interval_at(start_instant, tick);
348
349 let mut rows = generator.by_seed_for_worker(
350 now_fn,
351 None,
352 resume_offset,
353 generator_worker_index,
354 worker_count,
355 );
356
357 let mut committed_uppers = std::pin::pin!(committed_uppers);
358
359 let mut offsets_committed: BTreeMap<_, _> = if resume_offset.offset == 0 {
361 source_statistics.keys().map(|id| (*id, 0)).collect()
362 } else {
363 BTreeMap::new()
364 };
365
366 while let Some((output_type, event)) = rows.next() {
367 match event {
368 Event::Message(mut offset, (value, diff)) => {
369 if offset <= as_of {
371 offset = as_of;
372 }
373
374 if offset >= up_to {
380 continue;
381 }
382
383 let _ = progress_cap.try_downgrade(&(offset + 1));
387
388 let outputs = match output_map.get(&output_type) {
389 Some(outputs) => outputs,
390 None => continue,
392 };
393
394 let message: Result<SourceMessage, DataflowError> = Ok(SourceMessage {
395 key: Row::default(),
396 value,
397 metadata: Row::default(),
398 });
399
400 if resume_offset <= offset {
404 for (&output, message) in outputs.iter().repeat_clone(message) {
405 let update = ((output, message), offset, diff);
406 let size = update.fuel_size();
407 data_output.give_fueled(&cap, update, size).await;
408 }
409 }
410 }
411 Event::Progress(Some(offset)) => {
412 if resume_offset <= offset && health_cap.is_some() {
413 let health_cap = health_cap.take().expect("known to exist");
414 let export_ids = export_ids.iter().copied();
415 for id in export_ids.map(Some).chain(std::iter::once(None)) {
416 health_output.give(
417 &health_cap,
418 HealthStatusMessage {
419 id,
420 namespace: StatusNamespace::Generator,
421 update: HealthStatusUpdate::running(),
422 },
423 );
424 }
425 }
426
427 if offset >= up_to {
429 break;
430 }
431
432 if offset <= as_of {
435 continue;
436 }
437
438 cap.downgrade(&offset);
439 let _ = progress_cap.try_downgrade(&offset);
440
441 if resume_offset < offset {
447 loop {
448 tokio::select! {
449 _tick = tick_interval.tick() => {
450 break;
451 }
452 Some(uppers) = committed_uppers.next() => {
453 for (id, frontier) in uppers.exports {
454 if let Some(offset) = frontier.into_option() {
455 offsets_committed.insert(id, offset.offset);
458 }
459 }
460 }
461 }
462 }
463
464 for (id, offset_committed) in &offsets_committed {
468 if let Some(stats) =
469 source_statistics.get(id).filter(|_| is_active_worker)
470 {
471 stats.set_offset_committed(*offset_committed);
472 stats.set_offset_known(*offset_committed);
477 }
478 }
479 }
480 }
481 Event::Progress(None) => return,
482 }
483 }
484 })
485 });
486
487 (
488 data_collections,
489 progress_stream,
490 health_stream,
491 vec![button.press_on_drop()],
492 )
493}
494
495fn synthesize_probes<'scope, T: timely::progress::Timestamp>(
501 source_id: GlobalId,
502 progress: StreamVec<'scope, T, Infallible>,
503 interval: Duration,
504 now_fn: NowFn,
505) -> StreamVec<'scope, T, Probe<T>> {
506 let scope = progress.scope();
507
508 let active_worker = usize::cast_from(source_id.hashed()) % scope.peers();
509 let is_active_worker = active_worker == scope.index();
510
511 let mut op = AsyncOperatorBuilder::new("synthesize_probes".into(), scope);
512 let (output, output_stream) = op.new_output::<CapacityContainerBuilder<_>>();
513 let mut input = op.new_input_for(progress, Pipeline, &output);
514
515 op.build(|caps| async move {
516 if !is_active_worker {
517 return;
518 }
519
520 let [cap] = caps.try_into().expect("one capability per output");
521
522 let mut ticker = super::probe::Ticker::new(move || interval, now_fn.clone());
523
524 let minimum_frontier = Antichain::from_elem(Timestamp::minimum());
525 let mut frontier = minimum_frontier.clone();
526 loop {
527 tokio::select! {
528 event = input.next() => match event {
529 Some(AsyncEvent::Progress(progress)) => frontier = progress,
530 Some(AsyncEvent::Data(..)) => unreachable!(),
531 None => break,
532 },
533 probe_ts = ticker.tick(), if frontier != minimum_frontier => {
538 let probe = Probe {
539 probe_ts,
540 upstream_frontier: frontier.clone(),
541 };
542 output.give(&cap, probe);
543 }
544 }
545 }
546
547 let probe = Probe {
548 probe_ts: now_fn().into(),
549 upstream_frontier: Antichain::new(),
550 };
551 output.give(&cap, probe);
552 });
553
554 output_stream
555}