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, Error: Error> + Unpin + Send + 'static,
246 {
247 if let Some(metrics) = &self.metrics {
248 metrics.sector_plotting.inc();
249 }
250
251 let progress_updater = ProgressUpdater {
252 public_key,
253 sector_index,
254 handlers: Arc::clone(&self.handlers),
255 metrics: self.metrics.clone(),
256 };
257
258 let plotting_fut = {
259 let piece_getter = self.piece_getter.clone();
260 let plotting_thread_pool_manager = self.plotting_thread_pool_manager.clone();
261 let record_encoding_concurrency = self.record_encoding_concurrency;
262 let global_mutex = Arc::clone(&self.global_mutex);
263 let erasure_coding = self.erasure_coding.clone();
264 let abort_early = Arc::clone(&self.abort_early);
265 let metrics = self.metrics.clone();
266
267 async move {
268 let downloaded_sector = {
270 if !progress_updater
271 .update_progress_and_events(
272 &mut progress_sender,
273 SectorPlottingProgress::Downloading,
274 )
275 .await
276 {
277 return;
278 }
279
280 global_mutex.lock().await;
282
283 let downloading_start = Instant::now();
284 let public_key_hash = &public_key.hash();
285
286 let downloaded_sector_fut = download_sector(DownloadSectorOptions {
287 public_key_hash,
288 shard_commitments_root: &shard_commitments_root,
289 sector_index,
290 piece_getter: &piece_getter,
291 farmer_protocol_info,
292 erasure_coding: &erasure_coding,
293 pieces_in_sector,
294 });
295
296 let downloaded_sector = match downloaded_sector_fut.await {
297 Ok(downloaded_sector) => downloaded_sector,
298 Err(error) => {
299 warn!(%error, "Failed to download sector");
300
301 progress_updater
302 .update_progress_and_events(
303 &mut progress_sender,
304 SectorPlottingProgress::Error {
305 error: format!("Failed to download sector: {error}"),
306 },
307 )
308 .await;
309
310 return;
311 }
312 };
313
314 if !progress_updater
315 .update_progress_and_events(
316 &mut progress_sender,
317 SectorPlottingProgress::Downloaded(downloading_start.elapsed()),
318 )
319 .await
320 {
321 return;
322 }
323
324 downloaded_sector
325 };
326
327 let (sector, plotted_sector) = {
329 let thread_pools = plotting_thread_pool_manager.get_thread_pools().await;
330 if let Some(metrics) = &metrics {
331 metrics.plotting_capacity_used.inc();
332 }
333
334 yield_now().await;
336
337 if !progress_updater
338 .update_progress_and_events(
339 &mut progress_sender,
340 SectorPlottingProgress::Encoding,
341 )
342 .await
343 {
344 if let Some(metrics) = &metrics {
345 metrics.plotting_capacity_used.dec();
346 }
347 return;
348 }
349
350 let encoding_start = Instant::now();
351
352 let plotting_result = tokio::task::block_in_place(move || {
353 let thread_pool = if replotting {
354 &thread_pools.replotting
355 } else {
356 &thread_pools.plotting
357 };
358
359 let encoded_sector = thread_pool.install(|| {
360 let generator = PosTable::generator();
362 let generators = iter::repeat_n(
363 generator.clone(),
364 record_encoding_concurrency.get(),
365 )
366 .collect::<Vec<_>>();
367 let mut records_encoder = CpuRecordsEncoder::<PosTable>::new(
368 &generators,
369 &erasure_coding,
370 &global_mutex,
371 );
372
373 encode_sector(
374 downloaded_sector,
375 EncodeSectorOptions {
376 sector_index,
377 records_encoder: &mut records_encoder,
378 abort_early: &abort_early,
379 },
380 )
381 })?;
382
383 drop(thread_pools);
384
385 let mut sector = Vec::new();
386
387 write_sector(&encoded_sector, &mut sector)?;
388
389 Ok((sector, encoded_sector.plotted_sector))
390 });
391
392 if let Some(metrics) = &metrics {
393 metrics.plotting_capacity_used.dec();
394 }
395
396 match plotting_result {
397 Ok(plotting_result) => {
398 if !progress_updater
399 .update_progress_and_events(
400 &mut progress_sender,
401 SectorPlottingProgress::Encoded(encoding_start.elapsed()),
402 )
403 .await
404 {
405 return;
406 }
407
408 plotting_result
409 }
410 Err(PlottingError::AbortEarly) => {
411 return;
412 }
413 Err(error) => {
414 progress_updater
415 .update_progress_and_events(
416 &mut progress_sender,
417 SectorPlottingProgress::Error {
418 error: format!("Failed to encode sector: {error}"),
419 },
420 )
421 .await;
422
423 return;
424 }
425 }
426 };
427
428 progress_updater
429 .update_progress_and_events(
430 &mut progress_sender,
431 SectorPlottingProgress::Finished {
432 plotted_sector,
433 time: start.elapsed(),
434 sector: Box::pin({
435 let mut sector = Some(Ok(Bytes::from(sector)));
436
437 stream::poll_fn(move |_cx| {
438 let _downloading_permit = &downloading_permit;
440
441 Poll::Ready(sector.take())
442 })
443 }),
444 },
445 )
446 .await;
447 }
448 };
449
450 let plotting_task =
452 AsyncJoinOnDrop::new(tokio::spawn(plotting_fut.in_current_span()), true);
453 if let Err(error) = self.tasks_sender.clone().send(plotting_task).await {
454 warn!(%error, "Failed to send plotting task");
455
456 let progress = SectorPlottingProgress::Error {
457 error: format!("Failed to send plotting task: {error}"),
458 };
459
460 self.handlers
461 .plotting_progress
462 .call_simple(&public_key, §or_index, &progress);
463 }
464 }
465}
466
467struct ProgressUpdater {
468 public_key: Ed25519PublicKey,
469 sector_index: SectorIndex,
470 handlers: Arc<Handlers>,
471 metrics: Option<Arc<CpuPlotterMetrics>>,
472}
473
474impl ProgressUpdater {
475 async fn update_progress_and_events<PS>(
477 &self,
478 progress_sender: &mut PS,
479 progress: SectorPlottingProgress,
480 ) -> bool
481 where
482 PS: Sink<SectorPlottingProgress, Error: Error> + Unpin,
483 {
484 if let Some(metrics) = &self.metrics {
485 #[expect(
486 clippy::rest_pattern_accessible_field,
487 reason = "Just counting occurrences"
488 )]
489 match &progress {
490 SectorPlottingProgress::Downloading => {
491 metrics.sector_downloading.inc();
492 }
493 SectorPlottingProgress::Downloaded(time) => {
494 metrics.sector_downloading_time.observe(time.as_secs_f64());
495 metrics.sector_downloaded.inc();
496 }
497 SectorPlottingProgress::Encoding => {
498 metrics.sector_encoding.inc();
499 }
500 SectorPlottingProgress::Encoded(time) => {
501 metrics.sector_encoding_time.observe(time.as_secs_f64());
502 metrics.sector_encoded.inc();
503 }
504 SectorPlottingProgress::Finished { time, .. } => {
505 metrics.sector_plotting_time.observe(time.as_secs_f64());
506 metrics.sector_plotted.inc();
507 }
508 SectorPlottingProgress::Error { .. } => {
509 metrics.sector_plotting_error.inc();
510 }
511 }
512 }
513 self.handlers.plotting_progress.call_simple(
514 &self.public_key,
515 &self.sector_index,
516 &progress,
517 );
518
519 if let Err(error) = progress_sender.send(progress).await {
520 warn!(%error, "Failed to send progress update");
521
522 false
523 } else {
524 true
525 }
526 }
527}