1pub mod metrics;
4
5use crate::plotter::cpu::metrics::CpuPlotterMetrics;
6use crate::plotter::{Plotter, SectorPlottingProgress};
7use crate::thread_pool_manager::PlottingThreadPoolManager;
8use crate::utils::AsyncJoinOnDrop;
9use ab_core_primitives::ed25519::Ed25519PublicKey;
10use ab_core_primitives::sectors::SectorIndex;
11use ab_core_primitives::solutions::ShardCommitmentHash;
12use ab_data_retrieval::piece_getter::PieceGetter;
13use ab_erasure_coding::ErasureCoding;
14use ab_farmer_components::FarmerProtocolInfo;
15use ab_farmer_components::plotting::{
16 CpuRecordsEncoder, DownloadSectorOptions, EncodeSectorOptions, PlottingError, download_sector,
17 encode_sector, write_sector,
18};
19use ab_proof_of_space::Table;
20use async_lock::{Mutex as AsyncMutex, Semaphore, SemaphoreGuardArc};
21use async_trait::async_trait;
22use bytes::Bytes;
23use event_listener_primitives::{Bag, HandlerId};
24use futures::channel::mpsc;
25use futures::stream::FuturesUnordered;
26use futures::{FutureExt, Sink, SinkExt, StreamExt, select, stream};
27use prometheus_client::registry::Registry;
28use std::any::type_name;
29use std::error::Error;
30use std::future::pending;
31use std::marker::PhantomData;
32use std::num::NonZeroUsize;
33use std::pin::pin;
34use std::sync::Arc as StdArc;
35use std::sync::atomic::{AtomicBool, Ordering};
36use std::task::Poll;
37use std::time::Instant;
38use std::{fmt, iter};
39use tokio::task::yield_now;
40use tracing::{Instrument, warn};
41use triomphe::Arc;
42
43pub type HandlerFn3<A, B, C> = StdArc<dyn Fn(&A, &B, &C) + Send + Sync + 'static>;
45type Handler3<A, B, C> = Bag<HandlerFn3<A, B, C>, A, B, C>;
46
47#[derive(Default, Debug)]
48struct Handlers {
49 plotting_progress: Handler3<Ed25519PublicKey, SectorIndex, SectorPlottingProgress>,
50}
51
52pub struct CpuPlotter<PG, PosTable> {
54 piece_getter: PG,
55 downloading_semaphore: StdArc<Semaphore>,
56 plotting_thread_pool_manager: PlottingThreadPoolManager,
57 record_encoding_concurrency: NonZeroUsize,
58 global_mutex: Arc<AsyncMutex<()>>,
59 erasure_coding: ErasureCoding,
60 handlers: Arc<Handlers>,
61 tasks_sender: mpsc::Sender<AsyncJoinOnDrop<()>>,
62 _background_tasks: AsyncJoinOnDrop<()>,
63 abort_early: Arc<AtomicBool>,
64 metrics: Option<Arc<CpuPlotterMetrics>>,
65 _phantom: PhantomData<PosTable>,
66}
67
68impl<PG, PosTable> fmt::Debug for CpuPlotter<PG, PosTable> {
69 #[inline]
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 f.debug_struct("CpuPlotter").finish_non_exhaustive()
72 }
73}
74
75impl<PG, PosTable> Drop for CpuPlotter<PG, PosTable> {
76 #[inline]
77 fn drop(&mut self) {
78 self.abort_early.store(true, Ordering::Release);
79 self.tasks_sender.close_channel();
80 }
81}
82
83#[async_trait]
84impl<PG, PosTable> Plotter for CpuPlotter<PG, PosTable>
85where
86 PG: PieceGetter + Clone + Send + Sync + 'static,
87 PosTable: Table,
88{
89 async fn has_free_capacity(&self) -> Result<bool, String> {
90 Ok(self.downloading_semaphore.try_acquire().is_some())
91 }
92
93 async fn plot_sector(
94 &self,
95 public_key: Ed25519PublicKey,
96 shard_commitments_root: ShardCommitmentHash,
97 sector_index: SectorIndex,
98 farmer_protocol_info: FarmerProtocolInfo,
99 pieces_in_sector: u16,
100 replotting: bool,
101 progress_sender: mpsc::Sender<SectorPlottingProgress>,
102 ) {
103 let start = Instant::now();
104
105 let downloading_permit = self.downloading_semaphore.acquire_arc().await;
108
109 self.plot_sector_internal(
110 start,
111 downloading_permit,
112 public_key,
113 shard_commitments_root,
114 sector_index,
115 farmer_protocol_info,
116 pieces_in_sector,
117 replotting,
118 progress_sender,
119 )
120 .await;
121 }
122
123 async fn try_plot_sector(
124 &self,
125 public_key: Ed25519PublicKey,
126 shard_commitments_root: ShardCommitmentHash,
127 sector_index: SectorIndex,
128 farmer_protocol_info: FarmerProtocolInfo,
129 pieces_in_sector: u16,
130 replotting: bool,
131 progress_sender: mpsc::Sender<SectorPlottingProgress>,
132 ) -> bool {
133 let start = Instant::now();
134
135 let Some(downloading_permit) = self.downloading_semaphore.try_acquire_arc() else {
136 return false;
137 };
138
139 self.plot_sector_internal(
140 start,
141 downloading_permit,
142 public_key,
143 shard_commitments_root,
144 sector_index,
145 farmer_protocol_info,
146 pieces_in_sector,
147 replotting,
148 progress_sender,
149 )
150 .await;
151
152 true
153 }
154}
155
156impl<PG, PosTable> CpuPlotter<PG, PosTable>
157where
158 PG: PieceGetter + Clone + Send + Sync + 'static,
159 PosTable: Table,
160{
161 pub fn new(
163 piece_getter: PG,
164 downloading_semaphore: StdArc<Semaphore>,
165 plotting_thread_pool_manager: PlottingThreadPoolManager,
166 record_encoding_concurrency: NonZeroUsize,
167 global_mutex: Arc<AsyncMutex<()>>,
168 erasure_coding: ErasureCoding,
169 registry: Option<&mut Registry>,
170 ) -> Self {
171 let (tasks_sender, mut tasks_receiver) = mpsc::channel(1);
172
173 let background_tasks = AsyncJoinOnDrop::new(
175 tokio::spawn(async move {
176 let background_tasks = FuturesUnordered::new();
177 let mut background_tasks = pin!(background_tasks);
178 background_tasks.push(AsyncJoinOnDrop::new(tokio::spawn(pending::<()>()), true));
180
181 loop {
182 select! {
183 maybe_background_task = tasks_receiver.next().fuse() => {
184 let Some(background_task) = maybe_background_task else {
185 break;
186 };
187
188 background_tasks.push(background_task);
189 },
190 _ = background_tasks.select_next_some() => {
191 }
193 }
194 }
195 }),
196 true,
197 );
198
199 let abort_early = Arc::new(AtomicBool::new(false));
200 let metrics = registry.map(|registry| {
201 Arc::new(CpuPlotterMetrics::new(
202 registry,
203 type_name::<PosTable>(),
204 plotting_thread_pool_manager.thread_pool_pairs(),
205 ))
206 });
207
208 Self {
209 piece_getter,
210 downloading_semaphore,
211 plotting_thread_pool_manager,
212 record_encoding_concurrency,
213 global_mutex,
214 erasure_coding,
215 handlers: Arc::default(),
216 tasks_sender,
217 _background_tasks: background_tasks,
218 abort_early,
219 metrics,
220 _phantom: PhantomData,
221 }
222 }
223
224 pub fn on_plotting_progress(
226 &self,
227 callback: HandlerFn3<Ed25519PublicKey, SectorIndex, SectorPlottingProgress>,
228 ) -> HandlerId {
229 self.handlers.plotting_progress.add(callback)
230 }
231
232 #[expect(clippy::too_many_arguments, reason = "Internal API")]
233 async fn plot_sector_internal<PS>(
234 &self,
235 start: Instant,
236 downloading_permit: SemaphoreGuardArc,
237 public_key: Ed25519PublicKey,
238 shard_commitments_root: ShardCommitmentHash,
239 sector_index: SectorIndex,
240 farmer_protocol_info: FarmerProtocolInfo,
241 pieces_in_sector: u16,
242 replotting: bool,
243 mut progress_sender: PS,
244 ) where
245 PS: Sink<SectorPlottingProgress> + Unpin + Send + 'static,
246 PS::Error: Error,
247 {
248 if let Some(metrics) = &self.metrics {
249 metrics.sector_plotting.inc();
250 }
251
252 let progress_updater = ProgressUpdater {
253 public_key,
254 sector_index,
255 handlers: Arc::clone(&self.handlers),
256 metrics: self.metrics.clone(),
257 };
258
259 let plotting_fut = {
260 let piece_getter = self.piece_getter.clone();
261 let plotting_thread_pool_manager = self.plotting_thread_pool_manager.clone();
262 let record_encoding_concurrency = self.record_encoding_concurrency;
263 let global_mutex = Arc::clone(&self.global_mutex);
264 let erasure_coding = self.erasure_coding.clone();
265 let abort_early = Arc::clone(&self.abort_early);
266 let metrics = self.metrics.clone();
267
268 async move {
269 let downloaded_sector = {
271 if !progress_updater
272 .update_progress_and_events(
273 &mut progress_sender,
274 SectorPlottingProgress::Downloading,
275 )
276 .await
277 {
278 return;
279 }
280
281 global_mutex.lock().await;
283
284 let downloading_start = Instant::now();
285 let public_key_hash = &public_key.hash();
286
287 let downloaded_sector_fut = download_sector(DownloadSectorOptions {
288 public_key_hash,
289 shard_commitments_root: &shard_commitments_root,
290 sector_index,
291 piece_getter: &piece_getter,
292 farmer_protocol_info,
293 erasure_coding: &erasure_coding,
294 pieces_in_sector,
295 });
296
297 let downloaded_sector = match downloaded_sector_fut.await {
298 Ok(downloaded_sector) => downloaded_sector,
299 Err(error) => {
300 warn!(%error, "Failed to download sector");
301
302 progress_updater
303 .update_progress_and_events(
304 &mut progress_sender,
305 SectorPlottingProgress::Error {
306 error: format!("Failed to download sector: {error}"),
307 },
308 )
309 .await;
310
311 return;
312 }
313 };
314
315 if !progress_updater
316 .update_progress_and_events(
317 &mut progress_sender,
318 SectorPlottingProgress::Downloaded(downloading_start.elapsed()),
319 )
320 .await
321 {
322 return;
323 }
324
325 downloaded_sector
326 };
327
328 let (sector, plotted_sector) = {
330 let thread_pools = plotting_thread_pool_manager.get_thread_pools().await;
331 if let Some(metrics) = &metrics {
332 metrics.plotting_capacity_used.inc();
333 }
334
335 yield_now().await;
337
338 if !progress_updater
339 .update_progress_and_events(
340 &mut progress_sender,
341 SectorPlottingProgress::Encoding,
342 )
343 .await
344 {
345 if let Some(metrics) = &metrics {
346 metrics.plotting_capacity_used.dec();
347 }
348 return;
349 }
350
351 let encoding_start = Instant::now();
352
353 let plotting_result = tokio::task::block_in_place(move || {
354 let thread_pool = if replotting {
355 &thread_pools.replotting
356 } else {
357 &thread_pools.plotting
358 };
359
360 let encoded_sector = thread_pool.install(|| {
361 let generator = PosTable::generator();
363 let generators = iter::repeat_n(
364 generator.clone(),
365 record_encoding_concurrency.get(),
366 )
367 .collect::<Vec<_>>();
368 let mut records_encoder = CpuRecordsEncoder::<PosTable>::new(
369 &generators,
370 &erasure_coding,
371 &global_mutex,
372 );
373
374 encode_sector(
375 downloaded_sector,
376 EncodeSectorOptions {
377 sector_index,
378 records_encoder: &mut records_encoder,
379 abort_early: &abort_early,
380 },
381 )
382 })?;
383
384 if abort_early.load(Ordering::Acquire) {
385 return Err(PlottingError::AbortEarly);
386 }
387
388 drop(thread_pools);
389
390 let mut sector = Vec::new();
391
392 write_sector(&encoded_sector, &mut sector)?;
393
394 Ok((sector, encoded_sector.plotted_sector))
395 });
396
397 if let Some(metrics) = &metrics {
398 metrics.plotting_capacity_used.dec();
399 }
400
401 match plotting_result {
402 Ok(plotting_result) => {
403 if !progress_updater
404 .update_progress_and_events(
405 &mut progress_sender,
406 SectorPlottingProgress::Encoded(encoding_start.elapsed()),
407 )
408 .await
409 {
410 return;
411 }
412
413 plotting_result
414 }
415 Err(PlottingError::AbortEarly) => {
416 return;
417 }
418 Err(error) => {
419 progress_updater
420 .update_progress_and_events(
421 &mut progress_sender,
422 SectorPlottingProgress::Error {
423 error: format!("Failed to encode sector: {error}"),
424 },
425 )
426 .await;
427
428 return;
429 }
430 }
431 };
432
433 progress_updater
434 .update_progress_and_events(
435 &mut progress_sender,
436 SectorPlottingProgress::Finished {
437 plotted_sector,
438 time: start.elapsed(),
439 sector: Box::pin({
440 let mut sector = Some(Ok(Bytes::from(sector)));
441
442 stream::poll_fn(move |_cx| {
443 let _downloading_permit = &downloading_permit;
445
446 Poll::Ready(sector.take())
447 })
448 }),
449 },
450 )
451 .await;
452 }
453 };
454
455 let plotting_task =
457 AsyncJoinOnDrop::new(tokio::spawn(plotting_fut.in_current_span()), true);
458 if let Err(error) = self.tasks_sender.clone().send(plotting_task).await {
459 warn!(%error, "Failed to send plotting task");
460
461 let progress = SectorPlottingProgress::Error {
462 error: format!("Failed to send plotting task: {error}"),
463 };
464
465 self.handlers
466 .plotting_progress
467 .call_simple(&public_key, §or_index, &progress);
468 }
469 }
470}
471
472struct ProgressUpdater {
473 public_key: Ed25519PublicKey,
474 sector_index: SectorIndex,
475 handlers: Arc<Handlers>,
476 metrics: Option<Arc<CpuPlotterMetrics>>,
477}
478
479impl ProgressUpdater {
480 async fn update_progress_and_events<PS>(
482 &self,
483 progress_sender: &mut PS,
484 progress: SectorPlottingProgress,
485 ) -> bool
486 where
487 PS: Sink<SectorPlottingProgress> + Unpin,
488 PS::Error: Error,
489 {
490 if let Some(metrics) = &self.metrics {
491 #[expect(
492 clippy::rest_pattern_accessible_field,
493 reason = "Just counting occurrences"
494 )]
495 match &progress {
496 SectorPlottingProgress::Downloading => {
497 metrics.sector_downloading.inc();
498 }
499 SectorPlottingProgress::Downloaded(time) => {
500 metrics.sector_downloading_time.observe(time.as_secs_f64());
501 metrics.sector_downloaded.inc();
502 }
503 SectorPlottingProgress::Encoding => {
504 metrics.sector_encoding.inc();
505 }
506 SectorPlottingProgress::Encoded(time) => {
507 metrics.sector_encoding_time.observe(time.as_secs_f64());
508 metrics.sector_encoded.inc();
509 }
510 SectorPlottingProgress::Finished { time, .. } => {
511 metrics.sector_plotting_time.observe(time.as_secs_f64());
512 metrics.sector_plotted.inc();
513 }
514 SectorPlottingProgress::Error { .. } => {
515 metrics.sector_plotting_error.inc();
516 }
517 }
518 }
519 self.handlers.plotting_progress.call_simple(
520 &self.public_key,
521 &self.sector_index,
522 &progress,
523 );
524
525 if let Err(error) = progress_sender.send(progress).await {
526 warn!(%error, "Failed to send progress update");
527
528 false
529 } else {
530 true
531 }
532 }
533}