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