use std::{
path::PathBuf,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
time::{Duration, Instant},
};
use serde_json::Value;
use tokio::{
sync::{mpsc, watch},
task::JoinHandle,
};
use zakura_chain::block::{self, Block};
use zakura_jsonl_trace::{JsonlTraceGuard, JsonlTracer};
use crate::zakura::{
trace::{block_sync_trace as bs_trace, BLOCK_SYNC_TABLE},
transport::ByteBudget,
ZakuraPeerId, ZakuraTrace,
};
use super::{
events::{BlockApplyResult, BlockApplyToken, BlockSyncAction},
reactor::{bs_insert_height, bs_insert_u64},
reorder::BufferedBlockBody,
sequencer::Sequencer,
sequencer_task::{
initial_view, SequencedBody, SequencerControlInput, SequencerTask, SequencerView,
},
state::{BlockSyncFrontiers, ThroughputMeter},
work_queue::WorkQueue,
};
const BENCH_ACTION_SEND_TIMEOUT: Duration = Duration::from_secs(60);
#[derive(Clone, Debug)]
pub struct BenchSubmit {
pub token: BlockApplyToken,
pub block: Arc<Block>,
}
#[derive(Copy, Clone, Debug)]
pub struct SequencerProgress {
pub verified_tip: block::Height,
pub reorder_len: u64,
pub applying_len: u64,
pub committed_blocks_per_sec: u64,
}
pub struct BenchSequencerHandle {
feeder: BenchBodyFeeder,
submissions: BenchSubmissions,
committer: BenchCommitter,
}
#[derive(Clone)]
pub struct BenchBodyFeeder {
body_input: mpsc::Sender<SequencedBody>,
body_input_bytes: Arc<AtomicU64>,
body_input_decoded_attributed_memory_bytes: Arc<AtomicU64>,
bench_peer: ZakuraPeerId,
}
pub struct BenchSubmissions {
actions: mpsc::Receiver<BlockSyncAction>,
}
pub struct BenchCommitter {
control: mpsc::UnboundedSender<SequencerControlInput>,
view: watch::Receiver<SequencerView>,
body_input_bytes: Arc<AtomicU64>,
body_input_decoded_attributed_memory_bytes: Arc<AtomicU64>,
trace: ZakuraTrace,
finalized_height: block::Height,
trace_guard: Option<JsonlTraceGuard>,
_join: JoinHandle<()>,
}
pub fn spawn_bench_sequencer(
finalized_height: block::Height,
verified_block_tip: block::Height,
verified_block_hash: block::Hash,
submit_in_flight_limit: usize,
max_inflight_bytes: u64,
trace_dir: Option<PathBuf>,
) -> BenchSequencerHandle {
let frontiers = BlockSyncFrontiers {
finalized_height,
verified_block_tip,
verified_block_hash,
};
let limit = submit_in_flight_limit.max(1);
let (trace, trace_guard) = match trace_dir {
Some(dir) => {
let guard = JsonlTracer::spawn_guard(dir);
let trace = ZakuraTrace::new(guard.tracer(), "01");
(trace, Some(guard))
}
None => (ZakuraTrace::noop(), None),
};
let sequencer = Sequencer::new(verified_block_tip, limit);
let throughput = ThroughputMeter::new(Instant::now());
let budget = ByteBudget::new(max_inflight_bytes.max(1));
let work = Arc::new(WorkQueue::new(verified_block_tip));
let (actions_tx, actions_rx) = mpsc::channel(limit + 128);
let (body_input_tx, body_input_rx) = mpsc::channel(limit);
let (control_tx, control_rx) = mpsc::unbounded_channel();
let body_input_bytes = Arc::new(AtomicU64::new(0));
let body_input_decoded_attributed_memory_bytes = Arc::new(AtomicU64::new(0));
let (view_tx, view_rx) = watch::channel(initial_view(frontiers));
let task = SequencerTask::new(
sequencer,
budget,
work,
actions_tx,
throughput,
frontiers,
body_input_rx,
control_rx,
body_input_bytes.clone(),
body_input_decoded_attributed_memory_bytes.clone(),
view_tx,
BENCH_ACTION_SEND_TIMEOUT,
trace.clone(),
);
let join = tokio::spawn(task.run());
BenchSequencerHandle {
feeder: BenchBodyFeeder {
body_input: body_input_tx,
body_input_bytes: body_input_bytes.clone(),
body_input_decoded_attributed_memory_bytes: body_input_decoded_attributed_memory_bytes
.clone(),
bench_peer: ZakuraPeerId::new(vec![0xB1; 32]).expect("32-byte bench peer id is valid"),
},
submissions: BenchSubmissions {
actions: actions_rx,
},
committer: BenchCommitter {
control: control_tx,
view: view_rx,
body_input_bytes,
body_input_decoded_attributed_memory_bytes,
trace,
finalized_height,
trace_guard,
_join: join,
},
}
}
impl BenchSequencerHandle {
pub fn into_parts(self) -> (BenchBodyFeeder, BenchSubmissions, BenchCommitter) {
(self.feeder, self.submissions, self.committer)
}
}
impl BenchBodyFeeder {
pub async fn feed_body(
&self,
height: block::Height,
hash: block::Hash,
block: Arc<Block>,
bytes: u64,
) -> bool {
let previous_block_hash = block.header.previous_block_hash;
let body = BufferedBlockBody::from_decoded_block(block, None);
let body = SequencedBody::new_queued(
height,
hash,
previous_block_hash,
body,
bytes,
self.bench_peer.clone(),
Instant::now(),
self.body_input_bytes.clone(),
self.body_input_decoded_attributed_memory_bytes.clone(),
);
self.body_input.send(body).await.is_ok()
}
}
impl BenchSubmissions {
pub async fn next_submit(&mut self) -> Option<BenchSubmit> {
while let Some(action) = self.actions.recv().await {
if let BlockSyncAction::SubmitBlock { token, block } = action {
return Some(BenchSubmit { token, block });
}
}
None
}
}
impl BenchCommitter {
pub fn apply_committed(
&self,
token: BlockApplyToken,
height: block::Height,
hash: block::Hash,
) {
let local_frontier = BlockSyncFrontiers {
finalized_height: self.finalized_height,
verified_block_tip: height,
verified_block_hash: hash,
};
let _ = self.control.send(SequencerControlInput::ApplyFinished {
token,
height,
hash,
result: BlockApplyResult::Committed,
local_frontier: Some(local_frontier),
});
}
pub fn emit_state_snapshot(&self) {
let view = *self.view.borrow();
let sequencer_input_decoded_attributed_memory_bytes = self
.body_input_decoded_attributed_memory_bytes
.load(Ordering::Relaxed);
self.trace.emit_with(BLOCK_SYNC_TABLE, |row| {
row.insert(
bs_trace::EVENT.to_string(),
Value::String(bs_trace::BLOCK_SYNC_STATE.to_string()),
);
bs_insert_height(row, bs_trace::VERIFIED_BLOCK_TIP, view.verified_tip);
bs_insert_u64(row, bs_trace::APPLYING, view.applying_len);
bs_insert_u64(row, bs_trace::REORDER, view.reorder_len);
bs_insert_u64(
row,
bs_trace::SUBMITTED_APPLIES,
view.in_flight_submission_count,
);
bs_insert_u64(row, "applying_buffered_bytes", view.applying_buffered_bytes);
bs_insert_u64(row, "reorder_buffered_bytes", view.reorder_buffered_bytes);
bs_insert_u64(
row,
bs_trace::SEQUENCER_INPUT_DECODED_ATTRIBUTED_MEMORY_BYTES,
sequencer_input_decoded_attributed_memory_bytes,
);
bs_insert_u64(
row,
bs_trace::REORDER_DECODED_ATTRIBUTED_MEMORY_BYTES,
view.reorder_decoded_attributed_memory_bytes,
);
bs_insert_u64(
row,
bs_trace::APPLYING_DECODED_ATTRIBUTED_MEMORY_BYTES,
view.applying_decoded_attributed_memory_bytes,
);
bs_insert_u64(
row,
bs_trace::ACTIVE_PIPELINE_DECODED_ATTRIBUTED_MEMORY_BYTES,
sequencer_input_decoded_attributed_memory_bytes
.saturating_add(view.reorder_decoded_attributed_memory_bytes)
.saturating_add(view.applying_decoded_attributed_memory_bytes),
);
bs_insert_u64(
row,
"retained_pipeline_wire_bytes",
super::admission::RetainedPipelineBytes {
reorder_buffered_bytes: view.reorder_buffered_bytes,
applying_buffered_bytes: view.applying_buffered_bytes,
sequencer_input_queued_bytes: self.body_input_bytes.load(Ordering::Relaxed),
}
.wire_bytes(),
);
});
}
pub async fn flush_trace(&mut self) {
if let Some(guard) = self.trace_guard.take() {
guard.shutdown().await;
}
}
pub fn progress(&self) -> SequencerProgress {
let view = *self.view.borrow();
SequencerProgress {
verified_tip: view.verified_tip,
reorder_len: view.reorder_len,
applying_len: view.applying_len,
committed_blocks_per_sec: view.committed_blocks_per_sec,
}
}
}