use crate::encode::Encoder;
use crate::metrics::LaneCounters;
use crate::plan::EventPlan;
use spate_core::checkpoint::{AckIssuer, AckRef};
use spate_core::error::SourceError;
use spate_core::record::{PartitionId, RawPayload};
use spate_core::source::{LaneId, PayloadBatch, SourceLane};
use std::io::Write;
use std::ops::Range;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug)]
pub(crate) struct Shared {
pub(crate) exhausted: Box<[AtomicBool]>,
pub(crate) paused: Box<[AtomicBool]>,
pub(crate) remaining: Box<[AtomicU64]>,
pub(crate) open: Box<[AtomicU64]>,
}
impl Shared {
pub(crate) fn new(partitions: usize, budgets: Option<&[u64]>) -> Shared {
let flags = || (0..partitions).map(|_| AtomicBool::new(false)).collect();
Shared {
exhausted: flags(),
paused: flags(),
remaining: (0..partitions)
.map(|i| AtomicU64::new(budgets.map_or(0, |b| b[i])))
.collect(),
open: (0..partitions).map(|_| AtomicU64::new(0)).collect(),
}
}
}
#[derive(Debug)]
struct Item {
key: Range<usize>,
value: Range<usize>,
offset: i64,
timestamp_ms: i64,
}
pub(crate) struct LaneParts {
pub(crate) id: LaneId,
pub(crate) index: usize,
pub(crate) issuer: AckIssuer,
pub(crate) plan: EventPlan,
pub(crate) encoder: Arc<Encoder>,
pub(crate) counters: Option<LaneCounters>,
pub(crate) shared: Arc<Shared>,
pub(crate) budget: u64,
pub(crate) tick_interval: Duration,
pub(crate) events_per_tick: usize,
}
#[derive(Debug)]
pub struct DatagenLane {
id: LaneId,
partition: PartitionId,
index: usize,
issuer: AckIssuer,
plan: EventPlan,
encoder: Arc<Encoder>,
counters: Option<LaneCounters>,
shared: Arc<Shared>,
budget: u64,
emitted: u64,
next_offset: i64,
tick_interval: Duration,
events_per_tick: usize,
next_tick: Instant,
tick_budget: usize,
arena: Vec<u8>,
items: Vec<Item>,
}
impl DatagenLane {
pub(crate) fn new(parts: LaneParts) -> DatagenLane {
DatagenLane {
id: parts.id,
partition: PartitionId(parts.index as u32),
index: parts.index,
issuer: parts.issuer,
plan: parts.plan,
encoder: parts.encoder,
counters: parts.counters,
shared: parts.shared,
budget: parts.budget,
emitted: 0,
next_offset: 0,
tick_interval: parts.tick_interval,
events_per_tick: parts.events_per_tick,
next_tick: Instant::now(),
tick_budget: 0,
arena: Vec::new(),
items: Vec::new(),
}
}
fn rate_gate(&mut self, timeout: Duration) -> Option<usize> {
if self.tick_interval.is_zero() {
return Some(usize::MAX);
}
if self.tick_budget > 0 {
return Some(self.tick_budget);
}
let now = Instant::now();
if now < self.next_tick {
park(Duration::min(self.next_tick - now, timeout));
return None;
}
let mut next = self.next_tick.checked_add(self.tick_interval);
if next.is_some_and(|at| at <= now) {
if let Some(counters) = &self.counters {
counters.tick_overruns.increment(1);
}
next = now.checked_add(self.tick_interval);
}
let Some(next) = next else {
park(timeout);
return None;
};
self.next_tick = next;
if let Some(counters) = &self.counters {
counters.ticks.increment(1);
}
self.tick_budget = self.events_per_tick;
Some(self.tick_budget)
}
fn fill(&mut self, count: usize) -> Result<(), SourceError> {
self.arena.clear();
self.items.clear();
let mut generated = [0u64; 3];
for _ in 0..count {
let (event, timestamp_ms) = self.plan.next();
let key_start = self.arena.len();
let _ = write!(self.arena, "{}", event.order_id());
let key = key_start..self.arena.len();
let value_start = self.arena.len();
self.encoder.encode(&event, &mut self.arena)?;
let value = value_start..self.arena.len();
generated[crate::metrics::kind(&event)] += 1;
self.items.push(Item {
key,
value,
offset: self.next_offset,
timestamp_ms,
});
self.next_offset += 1;
self.emitted += 1;
}
if let Some(counters) = &self.counters {
counters.add_generated(generated);
}
self.shared.remaining[self.index]
.store(self.budget.saturating_sub(self.emitted), Ordering::Release);
self.shared.open[self.index].store(self.plan.open_orders(), Ordering::Release);
Ok(())
}
}
impl SourceLane for DatagenLane {
type Batch<'a> = DatagenBatch<'a>;
fn id(&self) -> LaneId {
self.id
}
fn partition(&self) -> PartitionId {
self.partition
}
fn poll(
&mut self,
max_records: usize,
timeout: Duration,
) -> Result<Option<DatagenBatch<'_>>, SourceError> {
if self.emitted >= self.budget {
self.shared.exhausted[self.index].store(true, Ordering::Release);
park(timeout);
return Ok(None);
}
if self.shared.paused[self.index].load(Ordering::Acquire) || max_records == 0 {
park(timeout);
return Ok(None);
}
let Some(quota) = self.rate_gate(timeout) else {
return Ok(None);
};
let count = quota
.min(max_records)
.min(usize::try_from(self.budget - self.emitted).unwrap_or(usize::MAX));
self.fill(count)?;
self.tick_budget = self.tick_budget.saturating_sub(count);
let last_offset = self.next_offset - 1;
Ok(Some(DatagenBatch {
arena: &self.arena,
items: &self.items,
next: 0,
partition: self.partition,
ack: self.issuer.issue(self.partition, last_offset),
}))
}
}
#[derive(Debug)]
pub struct DatagenBatch<'a> {
arena: &'a [u8],
items: &'a [Item],
next: usize,
partition: PartitionId,
ack: AckRef,
}
impl<'a> PayloadBatch<'a> for DatagenBatch<'a> {
fn next_payload(&mut self) -> Option<RawPayload<'a>> {
let item = self.items.get(self.next)?;
self.next += 1;
Some(RawPayload {
bytes: &self.arena[item.value.clone()],
key: Some(&self.arena[item.key.clone()]),
partition: self.partition,
offset: item.offset,
timestamp_ms: item.timestamp_ms,
})
}
fn ack(&self) -> &AckRef {
&self.ack
}
}
pub(crate) fn park(how_long: Duration) {
if !how_long.is_zero() {
std::thread::sleep(how_long);
}
}