Skip to content

Commit

Permalink
Add benchmark command to farmer with the first benchmark being audit
Browse files Browse the repository at this point in the history
  • Loading branch information
nazar-pc committed Oct 16, 2023
1 parent 3b4453f commit 78b3f81
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 7 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/subspace-farmer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ blake2 = "0.10.6"
blake3 = { version = "1.4.1", default-features = false }
bytesize = "1.3.0"
clap = { version = "4.4.3", features = ["color", "derive"] }
criterion = { version = "0.5.1", default-features = false, features = ["rayon", "async"] }
derive_more = "0.99.17"
event-listener-primitives = "2.0.1"
fdlimit = "0.2"
Expand Down
5 changes: 5 additions & 0 deletions crates/subspace-farmer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ This will connect to local node and will try to solve on every slot notification

*NOTE: You need to have a `subspace-node` running before starting farmer, otherwise it will not be able to start*

### Benchmark auditing
```
target/production/subspace-farmer benchmark audit /path/to/farm
```

### Show information about the farm
```
target/production/subspace-farmer info /path/to/farm
Expand Down
1 change: 1 addition & 0 deletions crates/subspace-farmer/src/bin/subspace-farmer/commands.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) mod benchmark;
pub(crate) mod farm;
mod info;
mod scrub;
Expand Down
138 changes: 138 additions & 0 deletions crates/subspace-farmer/src/bin/subspace-farmer/commands/benchmark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use crate::PosTable;
use anyhow::anyhow;
use clap::Subcommand;
use criterion::async_executor::AsyncExecutor;
use criterion::{black_box, BatchSize, Criterion, Throughput};
use parking_lot::Mutex;
use std::fs::OpenOptions;
use std::future::Future;
use std::num::NonZeroUsize;
use std::path::PathBuf;
use subspace_core_primitives::crypto::kzg::{embedded_kzg_settings, Kzg};
use subspace_core_primitives::{Record, SolutionRange};
use subspace_erasure_coding::ErasureCoding;
use subspace_farmer::single_disk_farm::farming::{plot_audit, PlotAuditOptions};
use subspace_farmer::single_disk_farm::{SingleDiskFarm, SingleDiskFarmSummary};
use subspace_farmer_components::sector::sector_size;
use subspace_proof_of_space::Table;
use subspace_rpc_primitives::SlotInfo;
use tokio::runtime::Handle;

struct TokioAsyncExecutor(Handle);

impl AsyncExecutor for TokioAsyncExecutor {
fn block_on<T>(&self, future: impl Future<Output = T>) -> T {
tokio::task::block_in_place(|| self.0.block_on(future))
}
}

impl TokioAsyncExecutor {
fn new() -> Self {
Self(Handle::current())
}
}

/// Arguments for benchmark
#[derive(Debug, Subcommand)]
pub(crate) enum BenchmarkArgs {
/// Audit benchmark
Audit {
/// Disk farm to audit
///
/// Example:
/// /path/to/directory
disk_farm: PathBuf,
#[arg(long, default_value_t = 10)]
sample_size: usize,
},
}

pub(crate) async fn benchmark(benchmark_args: BenchmarkArgs) -> anyhow::Result<()> {
match benchmark_args {
BenchmarkArgs::Audit {
disk_farm,
sample_size,
} => audit(disk_farm, sample_size).await,
}
}

async fn audit(disk_farm: PathBuf, sample_size: usize) -> anyhow::Result<()> {
let (single_disk_farm_info, disk_farm) = match SingleDiskFarm::collect_summary(disk_farm) {
SingleDiskFarmSummary::Found { info, directory } => (info, directory),
SingleDiskFarmSummary::NotFound { directory } => {
return Err(anyhow!(
"No single disk farm info found, make sure {} is a valid path to the farm and \
process have permissions to access it",
directory.display()
));
}
SingleDiskFarmSummary::Error { directory, error } => {
return Err(anyhow!(
"Failed to open single disk farm info, make sure {} is a valid path to the farm \
and process have permissions to access it: {error}",
directory.display()
));
}
};

let sector_size = sector_size(single_disk_farm_info.pieces_in_sector());
let kzg = Kzg::new(embedded_kzg_settings());
let erasure_coding = ErasureCoding::new(
NonZeroUsize::new(Record::NUM_S_BUCKETS.next_power_of_two().ilog2() as usize).unwrap(),
)
.map_err(|error| anyhow::anyhow!(error))?;
let table_generator = Mutex::new(PosTable::generator());

let sectors_metadata = SingleDiskFarm::read_all_sectors_metadata(&disk_farm)
.map_err(|error| anyhow::anyhow!("Failed to read sectors metadata: {error}"))?;

let plot_file = OpenOptions::new()
.read(true)
.open(disk_farm.join(SingleDiskFarm::PLOT_FILE))
.map_err(|error| anyhow::anyhow!("Failed to open single disk farm: {error}"))?;
#[cfg(windows)]
let plot_mmap = unsafe { Mmap::map(plot_file)? };

let mut criterion = Criterion::default().sample_size(sample_size);
criterion
.benchmark_group("audit")
.throughput(Throughput::Bytes(
sector_size as u64 * sectors_metadata.len() as u64,
))
.bench_function("plot", |b| {
b.to_async(TokioAsyncExecutor::new()).iter_batched(
rand::random,
|global_challenge| {
let options = PlotAuditOptions::<PosTable> {
public_key: single_disk_farm_info.public_key(),
reward_address: single_disk_farm_info.public_key(),
sector_size,
slot_info: SlotInfo {
slot_number: 0,
global_challenge,
// No solution will be found, pure audit
solution_range: SolutionRange::MIN,
// No solution will be found, pure audit
voting_solution_range: SolutionRange::MIN,
},
sectors_metadata: &sectors_metadata,
kzg: &kzg,
erasure_coding: &erasure_coding,
#[cfg(not(windows))]
plot_file: &plot_file,
#[cfg(windows)]
plot_mmap: &plot_mmap,
maybe_sector_being_modified: None,
table_generator: &table_generator,
};

black_box(plot_audit(black_box(options)))
},
BatchSize::SmallInput,
)
});

criterion.final_summary();

Ok(())
}
6 changes: 6 additions & 0 deletions crates/subspace-farmer/src/bin/subspace-farmer/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type PosTable = ChiaTable;
enum Command {
/// Start a farmer, does plotting and farming
Farm(commands::farm::FarmingArgs),
/// Run various benchmarks
#[clap(subcommand)]
Benchmark(commands::benchmark::BenchmarkArgs),
/// Print information about farm and its content
Info {
/// One or more farm located at specified path.
Expand Down Expand Up @@ -77,6 +80,9 @@ async fn main() -> anyhow::Result<()> {
Command::Farm(farming_args) => {
commands::farm::farm::<PosTable>(farming_args).await?;
}
Command::Benchmark(benchmark_args) => {
commands::benchmark::benchmark(benchmark_args).await?;
}
Command::Info { disk_farms } => {
if disk_farms.is_empty() {
info!("No farm was specified, so there is nothing to do");
Expand Down
58 changes: 56 additions & 2 deletions crates/subspace-farmer/src/single_disk_farm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,8 +560,8 @@ impl Drop for SingleDiskFarm {
}

impl SingleDiskFarm {
const PLOT_FILE: &'static str = "plot.bin";
const METADATA_FILE: &'static str = "metadata.bin";
pub const PLOT_FILE: &'static str = "plot.bin";
pub const METADATA_FILE: &'static str = "metadata.bin";
const SUPPORTED_PLOT_VERSION: u8 = 0;

/// Create new single disk farm instance
Expand Down Expand Up @@ -1137,6 +1137,60 @@ impl SingleDiskFarm {
}
}

/// Read all sectors metadata
pub fn read_all_sectors_metadata(
directory: &Path,
) -> io::Result<Vec<SectorMetadataChecksummed>> {
let mut metadata_file = OpenOptions::new()
.read(true)
.open(directory.join(Self::METADATA_FILE))?;

let metadata_size = metadata_file.seek(SeekFrom::End(0))?;
let sector_metadata_size = SectorMetadataChecksummed::encoded_size();

let mut metadata_header_bytes = vec![0; PlotMetadataHeader::encoded_size()];
metadata_file.read_exact_at(&mut metadata_header_bytes, 0)?;

let metadata_header = PlotMetadataHeader::decode(&mut metadata_header_bytes.as_ref())
.map_err(|error| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to decode metadata header: {}", error),
)
})?;

if metadata_header.version != SingleDiskFarm::SUPPORTED_PLOT_VERSION {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Unsupported metadata version {}", metadata_header.version),
));
}

let mut sectors_metadata = Vec::<SectorMetadataChecksummed>::with_capacity(
((metadata_size - RESERVED_PLOT_METADATA) / sector_metadata_size as u64) as usize,
);

let mut sector_metadata_bytes = vec![0; sector_metadata_size];
for sector_index in 0..metadata_header.plotted_sector_count {
metadata_file.read_exact_at(
&mut sector_metadata_bytes,
RESERVED_PLOT_METADATA + sector_metadata_size as u64 * u64::from(sector_index),
)?;
sectors_metadata.push(
SectorMetadataChecksummed::decode(&mut sector_metadata_bytes.as_ref()).map_err(
|error| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to decode sector metadata: {}", error),
)
},
)?,
);
}

Ok(sectors_metadata)
}

/// ID of this farm
pub fn id(&self) -> &SingleDiskFarmId {
self.single_disk_farm_info.id()
Expand Down
11 changes: 6 additions & 5 deletions docs/farming.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,12 @@ There are extra commands and parameters you can use on farmer or node, use the `

Below are some helpful samples:

- `./FARMER_FILE_NAME info PATH_TO_FARM` : show information about the farm at `PATH_TO_FARM`
- `./FARMER_FILE_NAME scrub PATH_TO_FARM` : Scrub the farm to find and fix farm at `PATH_TO_FARM` corruption
- `./FARMER_FILE_NAME wipe PATH_TO_FARM` : erases everything related to farmer if data were stored in `PATH_TO_FARM`
- `./NODE_FILE_NAME --base-path NODE_DATA_PATH --chain gemini-3f ...` : start node and store data in `NODE_DATA_PATH` instead of default location
- `./NODE_FILE_NAME purge-chain --base-path NODE_DATA_PATH --chain gemini-3f` : erases data related to the node if data were stored in `NODE_DATA_PATH`
- `./FARMER_FILE_NAME benchmark audit PATH_TO_FARM`: benchmark auditing performance of the farm at `PATH_TO_FARM`
- `./FARMER_FILE_NAME info PATH_TO_FARM`: show information about the farm at `PATH_TO_FARM`
- `./FARMER_FILE_NAME scrub PATH_TO_FARM`: Scrub the farm to find and fix farm at `PATH_TO_FARM` corruption
- `./FARMER_FILE_NAME wipe PATH_TO_FARM`: erases everything related to farmer if data were stored in `PATH_TO_FARM`
- `./NODE_FILE_NAME --base-path NODE_DATA_PATH --chain gemini-3f ...`: start node and store data in `NODE_DATA_PATH` instead of default location
- `./NODE_FILE_NAME purge-chain --base-path NODE_DATA_PATH --chain gemini-3f`: erases data related to the node if data were stored in `NODE_DATA_PATH`

Examples:
```bash
Expand Down

0 comments on commit 78b3f81

Please sign in to comment.