diff --git a/Cargo.lock b/Cargo.lock index 629ebe1b70..47117047d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12672,6 +12672,7 @@ dependencies = [ "subspace-metrics", "subspace-networking", "subspace-proof-of-space", + "subspace-proof-of-space-gpu", "subspace-rpc-primitives", "substrate-bip39 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "supports-color", diff --git a/crates/subspace-farmer/Cargo.toml b/crates/subspace-farmer/Cargo.toml index 744edab052..98dc1198cf 100644 --- a/crates/subspace-farmer/Cargo.toml +++ b/crates/subspace-farmer/Cargo.toml @@ -58,6 +58,7 @@ subspace-core-primitives = { version = "0.1.0", path = "../subspace-core-primiti subspace-metrics = { version = "0.1.0", path = "../../shared/subspace-metrics", optional = true } subspace-networking = { version = "0.1.0", path = "../subspace-networking" } subspace-proof-of-space = { version = "0.1.0", path = "../subspace-proof-of-space" } +subspace-proof-of-space-gpu = { version = "0.1.0", path = "../../shared/subspace-proof-of-space-gpu", optional = true } subspace-rpc-primitives = { version = "0.1.0", path = "../subspace-rpc-primitives" } substrate-bip39 = "0.6.0" supports-color = { version = "3.0.0", optional = true } @@ -75,6 +76,10 @@ zeroize = "1.8.1" default = ["default-library", "binary"] cluster = ["dep:async-nats"] numa = ["dep:hwlocality"] +# Only Volta+ architectures are supported (GeForce RTX 20xx consumer GPUs and newer) +cuda = ["_gpu", "subspace-proof-of-space-gpu/cuda"] +# Internal feature, shouldn't be used directly +_gpu = [] # TODO: This is a hack for https://github.com/rust-lang/cargo/issues/1982, `default-library` is what would essentially # be default, but because binary compilation will require explicit feature to be specified without `binary` feature we diff --git a/crates/subspace-farmer/src/plotter.rs b/crates/subspace-farmer/src/plotter.rs index 0dbf17fcf9..a7d87b400b 100644 --- a/crates/subspace-farmer/src/plotter.rs +++ b/crates/subspace-farmer/src/plotter.rs @@ -6,6 +6,8 @@ //! implementations without the rest of the library being aware of implementation details. pub mod cpu; +#[cfg(feature = "_gpu")] +pub mod gpu; pub mod pool; use async_trait::async_trait; diff --git a/crates/subspace-farmer/src/plotter/cpu.rs b/crates/subspace-farmer/src/plotter/cpu.rs index 5a953eaba2..f85e01c1c2 100644 --- a/crates/subspace-farmer/src/plotter/cpu.rs +++ b/crates/subspace-farmer/src/plotter/cpu.rs @@ -84,6 +84,7 @@ where PosTable: Table, { async fn has_free_capacity(&self) -> Result { + // TODO: Check available thread pools Ok(self.downloading_semaphore.available_permits() > 0) } diff --git a/crates/subspace-farmer/src/plotter/gpu.rs b/crates/subspace-farmer/src/plotter/gpu.rs index 5a953eaba2..125bfe8d89 100644 --- a/crates/subspace-farmer/src/plotter/gpu.rs +++ b/crates/subspace-farmer/src/plotter/gpu.rs @@ -1,10 +1,13 @@ -//! CPU plotter +//! GPU plotter +#[cfg(feature = "cuda")] +pub mod cuda; +mod gpu_encoders_manager; pub mod metrics; -use crate::plotter::cpu::metrics::CpuPlotterMetrics; +use crate::plotter::gpu::gpu_encoders_manager::GpuRecordsEncoderManager; +use crate::plotter::gpu::metrics::GpuPlotterMetrics; use crate::plotter::{Plotter, SectorPlottingProgress}; -use crate::thread_pool_manager::PlottingThreadPoolManager; use crate::utils::AsyncJoinOnDrop; use async_lock::Mutex as AsyncMutex; use async_trait::async_trait; @@ -13,12 +16,10 @@ use futures::channel::mpsc; use futures::stream::FuturesUnordered; use futures::{select, stream, FutureExt, Sink, SinkExt, StreamExt}; use prometheus_client::registry::Registry; -use std::any::type_name; use std::error::Error; use std::fmt; use std::future::pending; -use std::marker::PhantomData; -use std::num::NonZeroUsize; +use std::num::TryFromIntError; use std::pin::pin; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -27,11 +28,10 @@ use subspace_core_primitives::crypto::kzg::Kzg; use subspace_core_primitives::{PublicKey, SectorIndex}; use subspace_erasure_coding::ErasureCoding; use subspace_farmer_components::plotting::{ - download_sector, encode_sector, CpuRecordsEncoder, DownloadSectorOptions, EncodeSectorOptions, - PlottingError, + download_sector, encode_sector, DownloadSectorOptions, EncodeSectorOptions, PlottingError, + RecordsEncoder, }; use subspace_farmer_components::{FarmerProtocolInfo, PieceGetter}; -use subspace_proof_of_space::Table; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use tokio::task::yield_now; use tracing::{warn, Instrument}; @@ -45,12 +45,17 @@ struct Handlers { plotting_progress: Handler3, } -/// CPU plotter -pub struct CpuPlotter { +/// GPU-specific [`RecordsEncoder`] with extra APIs +pub trait GpuRecordsEncoder: RecordsEncoder + fmt::Debug + Send { + /// GPU encoder type, typically related to GPU vendor + const TYPE: &'static str; +} + +/// GPU plotter +pub struct GpuPlotter { piece_getter: PG, downloading_semaphore: Arc, - plotting_thread_pool_manager: PlottingThreadPoolManager, - record_encoding_concurrency: NonZeroUsize, + gpu_records_encoders_manager: GpuRecordsEncoderManager, global_mutex: Arc>, kzg: Kzg, erasure_coding: ErasureCoding, @@ -58,18 +63,21 @@ pub struct CpuPlotter { tasks_sender: mpsc::Sender>, _background_tasks: AsyncJoinOnDrop<()>, abort_early: Arc, - metrics: Option>, - _phantom: PhantomData, + metrics: Option>, } -impl fmt::Debug for CpuPlotter { +impl fmt::Debug for GpuPlotter +where + GRE: GpuRecordsEncoder + 'static, +{ #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("CpuPlotter").finish_non_exhaustive() + f.debug_struct(&format!("GpuPlotter[type = {}]", GRE::TYPE)) + .finish_non_exhaustive() } } -impl Drop for CpuPlotter { +impl Drop for GpuPlotter { #[inline] fn drop(&mut self) { self.abort_early.store(true, Ordering::Release); @@ -78,12 +86,13 @@ impl Drop for CpuPlotter { } #[async_trait] -impl Plotter for CpuPlotter +impl Plotter for GpuPlotter where PG: PieceGetter + Clone + Send + Sync + 'static, - PosTable: Table, + GRE: GpuRecordsEncoder + 'static, { async fn has_free_capacity(&self) -> Result { + // TODO: Check available GPU encoders Ok(self.downloading_semaphore.available_permits() > 0) } @@ -93,7 +102,7 @@ where sector_index: SectorIndex, farmer_protocol_info: FarmerProtocolInfo, pieces_in_sector: u16, - replotting: bool, + _replotting: bool, mut progress_sender: mpsc::Sender, ) { let start = Instant::now(); @@ -135,7 +144,6 @@ where sector_index, farmer_protocol_info, pieces_in_sector, - replotting, progress_sender, ) .await @@ -147,7 +155,7 @@ where sector_index: SectorIndex, farmer_protocol_info: FarmerProtocolInfo, pieces_in_sector: u16, - replotting: bool, + _replotting: bool, progress_sender: mpsc::Sender, ) -> bool { let start = Instant::now(); @@ -164,7 +172,6 @@ where sector_index, farmer_protocol_info, pieces_in_sector, - replotting, progress_sender, ) .await; @@ -173,23 +180,23 @@ where } } -impl CpuPlotter +impl GpuPlotter where PG: PieceGetter + Clone + Send + Sync + 'static, - PosTable: Table, + GRE: GpuRecordsEncoder + 'static, { - /// Create new instance - #[allow(clippy::too_many_arguments)] + /// Create new instance. + /// + /// Returns an error if empty list of encoders is provided. pub fn new( piece_getter: PG, downloading_semaphore: Arc, - plotting_thread_pool_manager: PlottingThreadPoolManager, - record_encoding_concurrency: NonZeroUsize, + gpu_records_encoders: Vec, global_mutex: Arc>, kzg: Kzg, erasure_coding: ErasureCoding, registry: Option<&mut Registry>, - ) -> Self { + ) -> Result { let (tasks_sender, mut tasks_receiver) = mpsc::channel(1); // Basically runs plotting tasks in the background and allows to abort on drop @@ -219,19 +226,19 @@ where ); let abort_early = Arc::new(AtomicBool::new(false)); + let gpu_records_encoders_manager = GpuRecordsEncoderManager::new(gpu_records_encoders)?; let metrics = registry.map(|registry| { - Arc::new(CpuPlotterMetrics::new( + Arc::new(GpuPlotterMetrics::new( registry, - type_name::(), - plotting_thread_pool_manager.thread_pool_pairs(), + GRE::TYPE, + gpu_records_encoders_manager.gpu_records_encoders(), )) }); - Self { + Ok(Self { piece_getter, downloading_semaphore, - plotting_thread_pool_manager, - record_encoding_concurrency, + gpu_records_encoders_manager, global_mutex, kzg, erasure_coding, @@ -240,8 +247,7 @@ where _background_tasks: background_tasks, abort_early, metrics, - _phantom: PhantomData, - } + }) } /// Subscribe to plotting progress notifications @@ -261,7 +267,6 @@ where sector_index: SectorIndex, farmer_protocol_info: FarmerProtocolInfo, pieces_in_sector: u16, - replotting: bool, mut progress_sender: PS, ) where PS: Sink + Unpin + Send + 'static, @@ -280,8 +285,7 @@ where let plotting_fut = { let piece_getter = self.piece_getter.clone(); - let plotting_thread_pool_manager = self.plotting_thread_pool_manager.clone(); - let record_encoding_concurrency = self.record_encoding_concurrency; + let gpu_records_encoders_manager = self.gpu_records_encoders_manager.clone(); let global_mutex = Arc::clone(&self.global_mutex); let kzg = self.kzg.clone(); let erasure_coding = self.erasure_coding.clone(); @@ -349,17 +353,11 @@ where // Plotting let (sector, plotted_sector) = { - let thread_pools = plotting_thread_pool_manager.get_thread_pools().await; + let mut records_encoder = gpu_records_encoders_manager.get_encoder().await; if let Some(metrics) = &metrics { metrics.plotting_capacity_used.inc(); } - let thread_pool = if replotting { - &thread_pools.replotting - } else { - &thread_pools.plotting - }; - // Give a chance to interrupt plotting if necessary yield_now().await; @@ -379,31 +377,22 @@ where let encoding_start = Instant::now(); let plotting_result = tokio::task::block_in_place(|| { - thread_pool.install(|| { - let mut sector = Vec::new(); - let mut generators = (0..record_encoding_concurrency.get()) - .map(|_| PosTable::generator()) - .collect::>(); - let mut records_encoder = CpuRecordsEncoder::::new( - &mut generators, - &erasure_coding, - &global_mutex, - ); - - let plotted_sector = encode_sector( - downloaded_sector, - EncodeSectorOptions { - sector_index, - sector_output: &mut sector, - records_encoder: &mut records_encoder, - abort_early: &abort_early, - }, - )?; - - Ok((sector, plotted_sector)) - }) + let mut sector = Vec::new(); + + let plotted_sector = encode_sector( + downloaded_sector, + EncodeSectorOptions { + sector_index, + sector_output: &mut sector, + records_encoder: &mut *records_encoder, + abort_early: &abort_early, + }, + )?; + + Ok((sector, plotted_sector)) }); - drop(thread_pools); + drop(records_encoder); + if let Some(metrics) = &metrics { metrics.plotting_capacity_used.dec(); } @@ -476,7 +465,7 @@ struct ProgressUpdater { public_key: PublicKey, sector_index: SectorIndex, handlers: Arc, - metrics: Option>, + metrics: Option>, } impl ProgressUpdater { diff --git a/crates/subspace-farmer/src/plotter/gpu/cuda.rs b/crates/subspace-farmer/src/plotter/gpu/cuda.rs new file mode 100644 index 0000000000..c23370ed77 --- /dev/null +++ b/crates/subspace-farmer/src/plotter/gpu/cuda.rs @@ -0,0 +1,68 @@ +//! CUDA GPU records encoder + +use crate::plotter::gpu::GpuRecordsEncoder; +use async_lock::Mutex as AsyncMutex; +use std::sync::atomic::{AtomicBool, Ordering}; +use subspace_core_primitives::{HistorySize, PieceOffset, Record, SectorId}; +use subspace_farmer_components::plotting::RecordsEncoder; +use subspace_farmer_components::sector::SectorContentsMap; +use subspace_proof_of_space_gpu::cuda::CudaDevice; + +/// CUDA implementation of [`GpuRecordsEncoder`] +#[derive(Debug)] +pub struct CudaRecordsEncoder { + cuda_device: CudaDevice, + global_mutex: AsyncMutex<()>, +} + +impl GpuRecordsEncoder for CudaRecordsEncoder { + const TYPE: &'static str = "cuda"; +} + +impl RecordsEncoder for CudaRecordsEncoder { + fn encode_records( + &mut self, + sector_id: &SectorId, + records: &mut [Record], + history_size: HistorySize, + abort_early: &AtomicBool, + ) -> Result> { + let pieces_in_sector = records + .len() + .try_into() + .map_err(|error| format!("Failed to convert pieces in sector: {error}"))?; + let mut sector_contents_map = SectorContentsMap::new(pieces_in_sector); + + for ((piece_offset, record), mut encoded_chunks_used) in (PieceOffset::ZERO..) + .zip(records.iter_mut()) + .zip(sector_contents_map.iter_record_bitfields_mut()) + { + // Take mutex briefly to make sure encoding is allowed right now + self.global_mutex.lock_blocking(); + + let seed = §or_id.derive_evaluation_seed(piece_offset, history_size); + + self.cuda_device.generate_and_encode_pospace( + seed, + record, + encoded_chunks_used.iter_mut(), + )?; + + if abort_early.load(Ordering::Relaxed) { + break; + } + } + + Ok(sector_contents_map) + } +} + +impl CudaRecordsEncoder { + /// Create new instance + pub fn new(cuda_device: CudaDevice, global_mutex: AsyncMutex<()>) -> Self { + Self { + cuda_device, + global_mutex, + } + } +} diff --git a/crates/subspace-farmer/src/plotter/gpu/gpu_encoders_manager.rs b/crates/subspace-farmer/src/plotter/gpu/gpu_encoders_manager.rs index 2a06866177..b131cea021 100644 --- a/crates/subspace-farmer/src/plotter/gpu/gpu_encoders_manager.rs +++ b/crates/subspace-farmer/src/plotter/gpu/gpu_encoders_manager.rs @@ -1,52 +1,47 @@ //! Thread pool managing utilities for plotting purposes +use crate::plotter::gpu::GpuRecordsEncoder; use event_listener::Event; use parking_lot::Mutex; -use rayon::{ThreadPool, ThreadPoolBuildError}; -use std::num::NonZeroUsize; -use std::ops::Deref; +use std::num::{NonZeroUsize, TryFromIntError}; +use std::ops::{Deref, DerefMut}; use std::sync::Arc; -/// A wrapper around thread pool pair for plotting purposes -#[derive(Debug)] -pub struct PlottingThreadPoolPair { - /// Plotting thread pool - pub plotting: ThreadPool, - /// Replotting thread pool - pub replotting: ThreadPool, -} - -#[derive(Debug)] -struct Inner { - thread_pool_pairs: Vec, -} - -/// Wrapper around [`PlottingThreadPoolPair`] that on `Drop` will return thread pool back into -/// corresponding [`PlottingThreadPoolManager`]. +/// Wrapper around [`GpuEncoder`] that on `Drop` will return thread pool back into +/// corresponding [`GpuRecordsEncoderManager`]. #[derive(Debug)] #[must_use] -pub struct PlottingThreadPoolsGuard { - inner: Arc<(Mutex, Event)>, - thread_pool_pair: Option, +pub(super) struct GpuRecordsEncoderGuard { + inner: Arc<(Mutex>, Event)>, + gpu_records_encoder: Option, } -impl Deref for PlottingThreadPoolsGuard { - type Target = PlottingThreadPoolPair; +impl Deref for GpuRecordsEncoderGuard { + type Target = GRE; #[inline] fn deref(&self) -> &Self::Target { - self.thread_pool_pair + self.gpu_records_encoder .as_ref() .expect("Value exists until `Drop`; qed") } } -impl Drop for PlottingThreadPoolsGuard { +impl DerefMut for GpuRecordsEncoderGuard { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + self.gpu_records_encoder + .as_mut() + .expect("Value exists until `Drop`; qed") + } +} + +impl Drop for GpuRecordsEncoderGuard { #[inline] fn drop(&mut self) { let (mutex, event) = &*self.inner; - mutex.lock().thread_pool_pairs.push( - self.thread_pool_pair + mutex.lock().push( + self.gpu_records_encoder .take() .expect("Happens only once in `Drop`; qed"), ); @@ -54,61 +49,53 @@ impl Drop for PlottingThreadPoolsGuard { } } -/// Plotting thread pool manager. -/// -/// This abstraction wraps a set of thread pool pairs and allows to use them one at a time. -/// -/// Each pair contains one thread pool for plotting purposes and one for replotting, this is because -/// they'll share the same set of CPU cores in most cases, and it would be inefficient to use them -/// concurrently. +/// GPU records encoder manager. /// -/// For example on machine with 64 logical cores and 4 NUMA nodes it would be recommended to create -/// 4 thread pools with 16 threads each plotting thread pool and 8 threads in each replotting thread -/// pool, which would mean work done within thread pool is tied to CPU cores dedicated for that -/// thread pool. -#[derive(Debug, Clone)] -pub struct PlottingThreadPoolManager { - inner: Arc<(Mutex, Event)>, - thread_pool_pairs: NonZeroUsize, +/// This abstraction wraps a set of GPU records encoders and allows to use them one at a time. +#[derive(Debug)] +pub(super) struct GpuRecordsEncoderManager { + inner: Arc<(Mutex>, Event)>, + gpu_records_encoders: NonZeroUsize, +} + +impl Clone for GpuRecordsEncoderManager { + fn clone(&self) -> Self { + Self { + inner: Arc::clone(&self.inner), + gpu_records_encoders: self.gpu_records_encoders, + } + } } -impl PlottingThreadPoolManager { - /// Create new thread pool manager by instantiating `thread_pools` thread pools using - /// `create_thread_pool`. +impl GpuRecordsEncoderManager +where + GRE: GpuRecordsEncoder, +{ + /// Create new instance. /// - /// `create_thread_pool` takes one argument `thread_pool_index`. - pub fn new( - create_thread_pools: C, - thread_pool_pairs: NonZeroUsize, - ) -> Result - where - C: FnMut(usize) -> Result, - { - let inner = Inner { - thread_pool_pairs: (0..thread_pool_pairs.get()) - .map(create_thread_pools) - .collect::, _>>()?, - }; + /// Returns an error if empty list of encoders is provided. + pub(super) fn new(gpu_records_encoders: Vec) -> Result { + let count = gpu_records_encoders.len().try_into()?; Ok(Self { - inner: Arc::new((Mutex::new(inner), Event::new())), - thread_pool_pairs, + inner: Arc::new((Mutex::new(gpu_records_encoders), Event::new())), + gpu_records_encoders: count, }) } - /// How many thread pool pairs are being managed here - pub fn thread_pool_pairs(&self) -> NonZeroUsize { - self.thread_pool_pairs + /// How many gpu records encoders are being managed here + pub(super) fn gpu_records_encoders(&self) -> NonZeroUsize { + self.gpu_records_encoders } /// Get one of inner thread pool pairs, will wait until one is available if needed - pub async fn get_thread_pools(&self) -> PlottingThreadPoolsGuard { + pub(super) async fn get_encoder(&self) -> GpuRecordsEncoderGuard { let (mutex, event) = &*self.inner; - let thread_pool_pair = loop { + let gpu_records_encoder = loop { let listener = event.listen(); - if let Some(thread_pool_pair) = mutex.lock().thread_pool_pairs.pop() { + if let Some(thread_pool_pair) = mutex.lock().pop() { drop(listener); // It is possible that we got here because there was the last free pair available // and in the meantime listener received notification. Just in case that was the @@ -121,9 +108,9 @@ impl PlottingThreadPoolManager { listener.await; }; - PlottingThreadPoolsGuard { + GpuRecordsEncoderGuard { inner: Arc::clone(&self.inner), - thread_pool_pair: Some(thread_pool_pair), + gpu_records_encoder: Some(gpu_records_encoder), } } } diff --git a/crates/subspace-farmer/src/plotter/gpu/metrics.rs b/crates/subspace-farmer/src/plotter/gpu/metrics.rs new file mode 100644 index 0000000000..07a689aa3b --- /dev/null +++ b/crates/subspace-farmer/src/plotter/gpu/metrics.rs @@ -0,0 +1,148 @@ +//! Metrics for GPU plotter + +use prometheus_client::metrics::counter::Counter; +use prometheus_client::metrics::gauge::Gauge; +use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; +use prometheus_client::registry::{Registry, Unit}; +use std::num::NonZeroUsize; +use std::sync::atomic::{AtomicI64, AtomicU64}; + +/// Metrics for GPU plotter +#[derive(Debug)] +pub(super) struct GpuPlotterMetrics { + pub(super) sector_downloading_time: Histogram, + pub(super) sector_encoding_time: Histogram, + pub(super) sector_plotting_time: Histogram, + pub(super) sector_downloading: Counter, + pub(super) sector_downloaded: Counter, + pub(super) sector_encoding: Counter, + pub(super) sector_encoded: Counter, + pub(super) sector_plotting: Counter, + pub(super) sector_plotted: Counter, + pub(super) sector_plotting_error: Counter, + pub(super) plotting_capacity_used: Gauge, +} + +impl GpuPlotterMetrics { + /// Create new instance + pub(super) fn new( + registry: &mut Registry, + subtype: &str, + total_capacity: NonZeroUsize, + ) -> Self { + let registry = registry + .sub_registry_with_prefix("plotter") + .sub_registry_with_label(("kind".into(), format!("gpu-{subtype}").into())); + + let sector_downloading_time = Histogram::new(exponential_buckets(0.1, 2.0, 15)); + registry.register_with_unit( + "sector_downloading_time", + "Sector downloading time", + Unit::Seconds, + sector_downloading_time.clone(), + ); + + let sector_encoding_time = Histogram::new(exponential_buckets(0.1, 2.0, 15)); + registry.register_with_unit( + "sector_encoding_time", + "Sector encoding time", + Unit::Seconds, + sector_encoding_time.clone(), + ); + + let sector_plotting_time = Histogram::new(exponential_buckets(0.1, 2.0, 15)); + registry.register_with_unit( + "sector_plotting_time", + "Sector plotting time", + Unit::Seconds, + sector_plotting_time.clone(), + ); + + let sector_downloading = Counter::default(); + registry.register_with_unit( + "sector_downloading_counter", + "Number of sectors being downloaded", + Unit::Other("Sectors".to_string()), + sector_downloading.clone(), + ); + + let sector_downloaded = Counter::default(); + registry.register_with_unit( + "sector_downloaded_counter", + "Number of downloaded sectors", + Unit::Other("Sectors".to_string()), + sector_downloaded.clone(), + ); + + let sector_encoding = Counter::default(); + registry.register_with_unit( + "sector_encoding_counter", + "Number of sectors being encoded", + Unit::Other("Sectors".to_string()), + sector_encoding.clone(), + ); + + let sector_encoded = Counter::default(); + registry.register_with_unit( + "sector_encoded_counter", + "Number of encoded sectors", + Unit::Other("Sectors".to_string()), + sector_encoded.clone(), + ); + + let sector_plotting = Counter::default(); + registry.register_with_unit( + "sector_plotting_counter", + "Number of sectors being plotted", + Unit::Other("Sectors".to_string()), + sector_plotting.clone(), + ); + + let sector_plotted = Counter::default(); + registry.register_with_unit( + "sector_plotted_counter", + "Number of plotted sectors", + Unit::Other("Sectors".to_string()), + sector_plotted.clone(), + ); + + let sector_plotting_error = Counter::default(); + registry.register_with_unit( + "sector_plotting_error_counter", + "Number of sector plotting failures", + Unit::Other("Sectors".to_string()), + sector_plotting_error.clone(), + ); + + let plotting_capacity_total = Gauge::::default(); + plotting_capacity_total.set(total_capacity.get() as i64); + registry.register_with_unit( + "plotting_capacity_total", + "Plotting capacity total", + Unit::Other("Sectors".to_string()), + plotting_capacity_total, + ); + + let plotting_capacity_used = Gauge::default(); + registry.register_with_unit( + "plotting_capacity_used", + "Plotting capacity used", + Unit::Other("Sectors".to_string()), + plotting_capacity_used.clone(), + ); + + Self { + sector_downloading_time, + sector_encoding_time, + sector_plotting_time, + sector_downloading, + sector_downloaded, + sector_encoding, + sector_encoded, + sector_plotting, + sector_plotted, + sector_plotting_error, + plotting_capacity_used, + } + } +}