/**
 * SPDX-License-Identifier: GPL-2.0 only
 *
 * Copyright (c) 2025 Sandisk Corporation or its affiliates.
 */
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include "lib/pow2.h"
#include "fio.h"
#include "file.h"
#include "sprandom.h"

/*
 * Model for Estimating Steady-State Data Distribution in SSDs
 *
 * This model estimates the distribution of valid data across a flash drive
 * in a steady state. It is based on the key insight from Desnoyers' research,
 * which establishes a relationship between data validity and the physical
 * space it occupies.
 *
 * P. Desnoyers, "Analytic Models of SSD Write Performance,"
 * ACM Transactions on Storage,
 * vol. 8, no. 2, pp. 1–18, Jun. 2012, doi: 10.1145/2133360.2133364.
 *
 * The Core Principle
 * ==================
 *
 * The fundamental concept is that for a drive in a steady state, the product
 * of a block's validity and the fraction of drive space occupied by such
 * blocks is constant.
 *
 * Key Equation (1): i * f(i) = k
 *
 * Where:
 * - i: The number of valid pages in a block.
 * - f(i): The fraction of the drive composed of blocks with 'i' valid pages.
 * - k: A constant for the drive.
 *
 * This implies that for any two validity levels i and j: i * f(i) = j * f(j).
 * In other words, regions with lower validity (more invalid data) must
 * occupy proportionally more physical space than regions with high validity.
 *
 *
 * Modeling Steps
 * ==============
 * The model is built by following these steps:
 *
 * 1. Normalize Validity & Relate to Write Amplification (WA)
 * We normalize 'i' into a validity fraction:
 *
 *      valid_frac(i) = i / num_pages_per_region
 *
 * A greedy garbage collection (GC) algorithm reclaims the block with the
 * lowest validity. The validity of this GC block (`valid_frac_gc`) is
 * determined by the drive's WA:
 *
 *      valid_frac_gc = 1 - (1 / WA)
 *
 * 2. Determine Write Amplification (WA) from Over-Provisioning (OP)
 * The WA can be calculated from the drive's OP. A simple approximation
 * is often sufficient for most cases:
 *
 *      WA ≈ 0.5 / OP + 0.7
 *
 * Note: The precise formula from Desnoyers uses
 *      alpha = T/U
 * where
 *      OP = alpha - 1
 *
 * in the equation:
 *                   alpha
 *      WA = ----------------------------
 *           (alpha + W(-alpha*e^-alpha)
 *
 * with W being the Lambert W function).
 *
 * 3. Define the Distribution Curve
 *
 * Using the steady-state principle, we can find the relative size f(i) of a
 * region given its validity (`valid_frac_i`) by comparing it to the GC block.
 *
 *       valid_frac(i) * f(i) = valid_frac_gc * f_gc
 *
 * By defining the base size f_gc = 1, we get a simple relationship:
 *
 *       f(i) = valid_frac_gc / valid_frac(i)
 *
 * This formula defines a curve where points are spaced equally by validity.
 *
 * 4. Resample for Equal-Sized Regions
 *
 * The final step is to make the model practical. We take the curve defined
 * above and resample it to get points that are equally spaced by region
 * size f(i). This resampling gives the expected validity for each
 * equal-sized region of the drive, completing the model.
 */

#define PCT_PRECISION 10000

static inline double *d_alloc(size_t n)
{
	return calloc(n, sizeof(double));
}

struct point {
	double x;
	double y;
};

static inline struct point *p_alloc(size_t n)
{
	return calloc(n, sizeof(struct point));
}

static void print_d_array(const char *hdr, double *darray, size_t len)
{
	struct buf_output out;
	int i;

	buf_output_init(&out);

	log_buf(&out, "[");
	for (i = 0; i < len - 1; i++)
		log_buf(&out, "%.2f, ", darray[i]);

	log_buf(&out, "%.2f]\n", darray[len - 1]);
	if (hdr)
		dprint(FD_SPRANDOM, "%s: ", hdr);

	dprint(FD_SPRANDOM, "%s", out.buf);
	buf_output_free(&out);
}

static void print_d_points(struct point *parray, size_t len)
{
	struct buf_output out;
	unsigned int i;

	buf_output_init(&out);

	log_buf(&out, "[");
	for (i = 0; i < len - 1; i++)
		log_buf(&out, "(%.2f %.2f), ", parray[i].x, parray[i].y);

	log_buf(&out, "(%.2f %.2f)]\n", parray[len - 1].x, parray[len - 1].y);
	dprint(FD_SPRANDOM, "%s", out.buf);
	buf_output_free(&out);
}

/* Comparison function for qsort to sort points by x-value */
static int compare_points(const void *a, const void *b)
{
	/* Cast void pointers to struct point pointers */
	const struct point *point_a = (const struct point *)a;
	const struct point *point_b = (const struct point *)b;

	if (point_a->x < point_b->x)
		return -1;

	if (point_a->x > point_b->x)
		return 1;

	return 0;
}

/**
 * reverse - Reverses the elements of a double array in place.
 * @arr: pointer to the array of doubles to be reversed.
 * @size: number of elements in the array.
 */
static void reverse(double arr[], size_t size)
{
	size_t left = 0;
	size_t right = size - 1;

	if (size <= 1)
		return;

	while (left < right) {
		double temp = arr[left];
		arr[left] = arr[right];
		arr[right] = temp;
		left++;
		right--;
	}
}

/**
 * linspace - Generates a linearly spaced array of doubles.
 * @start: The starting value of the sequence.
 * @end: The ending value of the sequence.
 * @num: The number of elements to generate.
 *
 * Allocates and returns an array of @num doubles, linearly spaced
 * between @start and @end (inclusive). If @num is 0, returns NULL.
 * If @num is 1, the array contains only @start.
 *
 * Return: allocated array, or NULL on allocation failure or if @num is 0.
 */
static double *linspace(double start, double end, unsigned int num)
{
	double *arr;
	unsigned int i;
	double step;

	if (num == 0)
		return NULL;

	dprint(FD_SPRANDOM, "linespace start=%0.2f end=%0.2f num=%d\n",
	       start, end, num);

	arr = d_alloc(num);
	if (arr == NULL)
		return NULL;

	if (num == 1) {
		arr[0] = start;
		return arr;
	}

	/* Calculate step size */
	step = (end - start) / ((double)num - 1.0);

	for (i = 0; i < num; i++)
		arr[i] = start + (double)i * step;

	return arr;
}

/**
 * linear_interp - Performs linear interpolation or extrapolation.
 * @new_x: The x-value at which to interpolate.
 * @x_arr: Array of x-values (must be sorted in strictly increasing order).
 * @y_arr: Array of y-values corresponding to x_arr.
 * @num: Number of points in x_arr and y_arr.
 *
 * Returns the interpolated y-value at new_x using linear interpolation
 * between the points in x_arr and y_arr. If new_x is outside the range
 * of x_arr, returns the nearest endpoint's y-value (extrapolation).
 * Handles edge cases for zero or one point, and avoids division by zero
 * if two x-values are nearly identical.
 */
static double linear_interp(double new_x, const double *x_arr,
			    const double *y_arr, unsigned int num)
{
	unsigned int i;
	double x1, y1, x2, y2;

	if (num == 0)
		return 0.0;

	if (num == 1)
		return y_arr[0]; /* If only one point, return its y-value */

	/* Handle extrapolation outside the range */
	if (new_x <= x_arr[0])
		return y_arr[0];

	if (new_x >= x_arr[num - 1])
		return y_arr[num - 1];

	/* Find the interval [x_arr[i], x_arr[i + 1]] that contains new_x */
	for (i = 0; i < num - 1; i++) {
		if (new_x >= x_arr[i] && new_x <= x_arr[i + 1]) {
			x1 = x_arr[i];
			y1 = y_arr[i];
			x2 = x_arr[i + 1];
			y2 = y_arr[i + 1];

			/* Avoid division by zero if x values are identical
			 * Using a small epsilon for float comparison
			 * Return y1 if x1 and x2 are almost identical
			 */
			if (fabs(x2 - x1) < 1e-9)
				return y1;

			return y1 + (y2 - y1) * ((new_x - x1) / (x2 - x1));
		}
	}
	/* Should not reach here if new_x is within bounds
	 * and x_arr is strictly increasing
	 */
	return 0.0;
}

/**
 * sample_curve_equally_on_x - Resamples a curve at equally spaced x-values.
 * @points: array of input points (must have strictly increasing x-values).
 * @num: Number of input points.
 * @num_resampled: number of points to resample to.
 * @resampled_points: An output array of resampled points.
 *
 * Sorts the input points by x-value, checks for strictly increasing x-values,
 * and generates a new set of points with x-values equally spaced between the
 * minimum and maximum x of the input. Uses linear interpolation to compute
 * corresponding y-values.
 * Note: The function allocates memory for the output array.
 *
 * Return: 0 on success, negative error code on failure.
 */
static int sample_curve_equally_on_x(struct point *points, unsigned int num,
				     unsigned int num_resampled,
				     struct point **resampled_points)
{
	double *x_orig = (double *)0;
	double *y_orig = (double *)0;
	double *new_x_arr = (double *)0;
	struct point *new_points_arr = (struct point *)0;
	unsigned int i;
	int ret = 0;

	if (points == NULL || resampled_points == NULL)
		return -EINVAL;

	if (num == 0) {
		log_err("fio: original points array cannot be empty.\n");
		return -EINVAL;
	}

	if (num_resampled == 0) {
		*resampled_points = NULL;
		return 0;
	}

	qsort(points, num, sizeof(struct point), compare_points);

	/* Check if x-values are strictly increasing and sort them */
	for (i = 0; i < num - 1; i++) {
		if (points[i+1].x <= points[i].x) {
			log_err("fio: x-values must be strictly increasing.\n");
			ret = -EINVAL;
			goto cleanup;
		}
	}

	/* 2. Extract x and y into separate arrays for interpolation */
	x_orig = d_alloc(num);
	y_orig = d_alloc(num);
	if (x_orig == NULL || y_orig == NULL) {
		log_err("fio: Memory allocation failed for x_orig or y_orig.\n");
		ret = -ENOMEM;
		goto cleanup;
	}
	for (i = 0; i < num; i++) {
		x_orig[i] = points[i].x;
		y_orig[i] = points[i].y;
	}

	/* 4. Generate new_x values using linspace */
	new_x_arr = linspace(x_orig[0], x_orig[num - 1], num_resampled);
	if (new_x_arr == NULL) {
		ret = -ENOMEM;
		goto cleanup;
	}

	/* 5. Allocate memory for new resampled points */
	new_points_arr = p_alloc(num_resampled);
	if (new_points_arr == NULL) {
		log_err("fio: Memory allocation failed for new_points_arr.\n");
		ret = -ENOMEM;
		goto cleanup;
	}

	/* 6. Perform linear interpolation for each new_x to get new_y */
	for (i = 0; i < num_resampled; i++) {
		new_points_arr[i].x = new_x_arr[i];
		new_points_arr[i].y = linear_interp(new_x_arr[i], x_orig, y_orig, num);
	}

	*resampled_points = new_points_arr;

cleanup:
	free(x_orig);
	free(y_orig);
	free(new_x_arr);

	return ret;
}

/**
 * compute_waf - Compute the write amplification factor (WAF)
 * @over_provisioning: The over-provisioning ratio (0 < over_provisioning < 1)
 *
 * write amplification approximation equation
 *
 *                   0.5
 *     WAF = ------------------ + 0.7
 *           over_provisioning
 *
 * Return: The computed write amplification factor as a double.
 */
static inline double compute_waf(double over_provisioning)
{
	return 0.5 / over_provisioning + 0.7;
}

/**
 * compute_gc_validity - validity of the block selected for GC (garbage collector)
 *
 * @waf: The Write Amplification Factor, must be greater than 1.0.
 *
 * Return: The computed gavalidity;
 */
static inline double compute_gc_validity(double waf)
{
	assert(waf > 1.0); /* Ensure WAF is greater than 1.0 */
	return 1.0 - (double)1.0 / waf;
}

/**
 * compute_validity_dist - Computes a resampled validity distribution for regions.
 * @n_regions: Number of regions to divide the distribution into.
 * @over_provisioning: Over-provisioning factor used to calculate WAF and validity.
 *
 * Calculates the validity distribution across a specified number of regions,
 * based on the write amplification factor (WAF) and over-provisioning.
 * Steps:
 * - Allocates and fills arrays for:
 *   - validity distribution
 *   - block ratios
 *   - accumulated ratios
 * - Constructs a set of points representing the curve.
 * - Resamples the curve to ensure equal spacing along the x-axis.
 * - Reverses the resulting validity distribution before returning.
 *
 * Note: The function allocates memory for the validity distribution array.
 *
 * Return: resampled and reversed validity distribution array or NULL on error.
 */
static double *compute_validity_dist(unsigned int n_regions, double over_provisioning)
{
	double waf = compute_waf(over_provisioning);
	double validity = compute_gc_validity(waf);
	double *validity_distribution = NULL;
	double *blocks_ratio = NULL;
	double *acc_ratio = NULL;
	double acc;
	unsigned int i;
	struct point *points = NULL;
	struct point *points_resampled = NULL;
	int ret;

	if (n_regions == 0) {
		log_err("fio: requires at least one region");
		goto out;
	}

	/*
	 * Use linspace to get equally distributed validity values,
	 * along the y-axis of the curve we want to generate.
	 */
	validity_distribution = linspace(1.0, validity, n_regions);

	blocks_ratio = d_alloc(n_regions);
	if (blocks_ratio == NULL) {
		log_err("fio: memory allocation failed for linspace.\n");
		goto out;
	}

	for (i = 0; i < n_regions; i++)
		blocks_ratio[i] = 1.0 / validity_distribution[i];

	acc_ratio = d_alloc(n_regions);
	if (acc_ratio == NULL) {
		log_err("fio: memory allocation failed for linspace_c.\n");
		goto out;
	}

	acc = 0.0;
	for (i = 0; i < n_regions; i++) {
		acc_ratio[i] = acc + blocks_ratio[i];
		acc = acc_ratio[i];
	}

	print_d_array("validity_distribution", validity_distribution, n_regions);
	print_d_array("blocks ratio", blocks_ratio, n_regions);
	print_d_array("accumulated ratio:", acc_ratio, n_regions);

	points = p_alloc(n_regions);

	for (i = 0; i < n_regions; i++) {
		points[i].x = acc_ratio[i];
		points[i].y = validity_distribution[i];
	}
	print_d_points(points, n_regions);

	/*
	 * Use linspace again to get uniformly distributed x-values,
	 * and then interpolate the curve to find the validity at those
	 * uniformly distributed x-values.
	 */
	ret = sample_curve_equally_on_x(points, n_regions, n_regions,
					&points_resampled);

	if (ret == 0) {
		print_d_points(points_resampled, n_regions);
	} else {
		log_err("fio: failed to resample curve. Error code: %d\n", ret);
		free(validity_distribution);
		validity_distribution = NULL;
		goto out;
	}

	for (i = 0; i < n_regions; i++)
		validity_distribution[i] = points_resampled[i].y;

	print_d_array("validity resampled", validity_distribution, n_regions);

out:
	free(points);
	free(points_resampled);
	free(blocks_ratio);
	free(acc_ratio);

	reverse(validity_distribution, n_regions);

	return validity_distribution;
}

/**
 * Calculate the physical size based on logical size and over-provisioning
 *
 * @over_provisioning:   over provisioning factor (e.g. 0.2 for 20%)
 * @logical_sz:          Logical size in bytes
 * @align_bs:            Block size for alignment in bytes
 *
 * return: Physical size in bytes, including over-provisioning and aligned to align_bs
 */
static uint64_t sprandom_physical_size(double over_provisioning, uint64_t logical_sz,
				       uint64_t align_bs)
{
	uint64_t size;

	size = logical_sz + ceil((double)logical_sz * over_provisioning);
	return (size + (align_bs - 1)) & ~(align_bs - 1);
}

/**
 * estimate_inv_capacity - Estimates the invalid capacity of a region.
 * @region_cnt: number of offsets in the region.
 * @validity: invalidation ration in the regions (between 0 and 1).
 *
 * Calculates the expected number of invalidion in regions, adding a margin
 * of 6 standard deviations to account for statistical variation.
 *
 * Returns: Estimated invalid capacity
 */
static uint64_t estimate_inv_capacity(uint64_t region_cnt, double validity)
{
	double sigma = sqrt((double)region_cnt * validity * (1.0 - validity));
	return (uint64_t)ceil(region_cnt * (1.0 - validity) + 6.0 * sigma);
}

/**
 * sprandom_setup - Initialize and configure sprandom_info structure.
 * @spr_info: Pointer to sprandom_info structure to be initialized.
 * @logical_size: Logical size of the storage region.
 * @align_bs: Alignment block size.
 *
 * Calculates physical size and region parameters based on logical size,
 * alignment, and over-provisioning. Allocates and initializes validity
 * distribution and invalid percentage arrays for regions. Precomputes
 * invalid buffer capacity and allocates buffer. Sets up region size,
 * write counts, and resets region/phase counters.
 *
 * Returns 0 on success, enagative value on failure.
 */
static int sprandom_setup(struct sprandom_info *spr_info, uint64_t logical_size,
			  uint64_t align_bs)
{
	double over_provisioning = spr_info->over_provisioning;
	uint64_t physical_size;
	uint64_t region_sz;
	uint64_t region_write_count;
	double *validity_dist;
	size_t invalid_capacity;
	size_t total_alloc = 0;
	char bytes2str_buf[40];
	int i;

	physical_size = sprandom_physical_size(over_provisioning,
					       logical_size, align_bs);

	validity_dist = compute_validity_dist(spr_info->num_regions,
					      spr_info->over_provisioning);
	if (!validity_dist)
		return -ENOMEM;

	/* Initialize validity_distribution */
	print_d_array("validity resampled:", validity_dist, spr_info->num_regions);

	/* Precompute invalidity percentage array */
	spr_info->invalid_pct = calloc(spr_info->num_regions,
				       sizeof(spr_info->invalid_pct[0]));
	if (!spr_info->invalid_pct)
		goto err;

	total_alloc += spr_info->num_regions * sizeof(spr_info->invalid_pct[0]);

	for (i = 0; i < spr_info->num_regions; i++) {
		double inv = (1.0 - validity_dist[i]) * (double)PCT_PRECISION;
		spr_info->invalid_pct[i] = (int)round(inv);
	}

	region_sz = physical_size / spr_info->num_regions;
	region_write_count = region_sz / align_bs;

	invalid_capacity = estimate_inv_capacity(region_write_count,
						 validity_dist[0]);
	spr_info->invalid_capacity = invalid_capacity;

	spr_info->invalid_buf = pcb_alloc(invalid_capacity);

	total_alloc += invalid_capacity * sizeof(uint64_t);

	spr_info->region_sz = region_sz;
	spr_info->invalid_count[0] = 0;
	spr_info->invalid_count[1] = 0;
	spr_info->curr_phase = 0;
	spr_info->current_region = 0;
	spr_info->region_write_count = region_write_count;
	spr_info->writes_remaining = region_write_count;

	/* Display overall allocation */
	dprint(FD_SPRANDOM, "Summary:\n");
	dprint(FD_SPRANDOM, "  logical_size:      %"PRIu64": %s\n",
		logical_size,
		bytes2str_simple(bytes2str_buf, sizeof(bytes2str_buf), logical_size));
	dprint(FD_SPRANDOM, "  physical_size:     %"PRIu64": %s\n",
		physical_size,
		bytes2str_simple(bytes2str_buf, sizeof(bytes2str_buf), physical_size));
	dprint(FD_SPRANDOM, "  op: %02f\n", spr_info->over_provisioning);
	dprint(FD_SPRANDOM, "  region_size:        %"PRIu64"\n", region_sz);
	dprint(FD_SPRANDOM, "  num_regions:        %u\n", spr_info->num_regions);
	dprint(FD_SPRANDOM, "  region_write_count: %"PRIu64"\n", region_write_count);
	dprint(FD_SPRANDOM, "  invalid_capacity:   %zu\n", invalid_capacity);
	dprint(FD_SPRANDOM, "  dynamic memory:     %zu: %s\n",
		total_alloc,
		bytes2str_simple(bytes2str_buf, sizeof(bytes2str_buf), total_alloc));

	free(validity_dist);
	return 0;
err:
	free(validity_dist);
	free(spr_info->invalid_pct);
	return -ENOMEM;
}

/**
 * sprandom_add_with_probability - Adds an offset to the invalid buffer with
 * a probability.
 *
 * @info: sprandom_info structure containing random state and buffers.
 * @offset: The offset value to potentially add to the invalid buffer.
 * @phase: The current phase index for invalid count tracking.
 *
 * Generates a random value and, based on the current region's invalid percentage,
 * decides whether to add the offset to the invalid buffer.
 * If the buffer is full, ogs an error and asserts failure.
 */
static void sprandom_add_with_probability(struct sprandom_info *info,
					  uint64_t offset, unsigned int phase)
{

	int v = rand_between(info->rand_state, 0, PCT_PRECISION);

	if (v <= info->invalid_pct[info->current_region]) {
		if (pcb_space_available(info->invalid_buf)) {
			pcb_push_staged(info->invalid_buf, offset);
			info->invalid_count[phase]++;
		} else {
			dprint(FD_SPRANDOM, "pcb buffer would be overriten\n");
			assert(false);
		}
	}
}

static void dprint_invalidation(const struct sprandom_info *info)
{
	uint32_t phase = info->curr_phase;
	double inv = 0;
	double inv_act; /* actually invalidation percentage */

	inv_act = (double)info->invalid_count[phase] / (double)info->region_write_count;
	if (info->current_region > 0)
		inv = (double)info->invalid_pct[info->current_region - 1] / PCT_PRECISION;

	dprint(FD_SPRANDOM, "Invalidation[%d] %"PRIu64" %zu %.04f %.04f\n",
		info->current_region,
		info->region_write_count,
		info->invalid_count[phase],
		inv, inv_act);
}

/**
 * sprandom_get_next_offset - Generate the next write offset for a region,
 * managing invalidation, and region transitions.
 *
 * @info: sprandom_info structure containing state and configuration.
 * @f: fio file associated with the ssd device.
 * @b: block offset to store the next write offset.
 *
 * Generates offsets to write a region and saves a fraction of the offsets
 * in a two phase circular buffer.
 * When transitioning to the next region (phase is flipped),it first writes
 * all saved offsets to achieve the desired fraction of invalid blocks in the
 * previous region. The remainder of the current region is then filled with
 * new offsets.
 *
 * Returns:
 *   0 if a valid offset is found and stored in @b,
 *   1 if no more offsets are available (end of regions or LFSR exhausted).
 */
int sprandom_get_next_offset(struct sprandom_info *info, struct fio_file *f, uint64_t *b)
{
	uint64_t offset = 0;
	uint32_t phase = info->curr_phase;

	/* replay invalidation */
	if (pcb_pop(info->invalid_buf, &offset)) {
		sprandom_add_with_probability(info, offset,  phase ^ 1);
		dprint(FD_SPRANDOM, "Write %"PRIu64" over %d\n",
		       offset, info->current_region);
		goto out;
	}

	/* Move to next region */
	if (info->writes_remaining == 0) {
		if (info->current_region >= info->num_regions) {
			dprint(FD_SPRANDOM, "End: Last Region %d cur%d\n",
			       info->current_region, info->num_regions);
			return 1;
		}

		dprint_invalidation(info);

		info->invalid_count[phase] = 0;

		info->current_region++;
		phase ^= 1;
		info->writes_remaining = info->region_write_count -
						info->invalid_count[phase];
		info->curr_phase = phase;
		pcb_commit(info->invalid_buf);
	}

	/* Fetch new offset */
	if (lfsr_next(&f->lfsr, &offset)) {
		dprint(FD_SPRANDOM, "End: LFSR exhausted %d [%zu] [%zu]\n",
			info->current_region,
			info->invalid_count[phase],
			info->invalid_count[phase ^ 1]);

		dprint_invalidation(info);

		return 1;
	}

	if (info->writes_remaining > 0)
		info->writes_remaining--;

	sprandom_add_with_probability(info, offset,  phase ^ 1);
	dprint(FD_SPRANDOM, "Write %"PRIu64" lfsr %d\n", offset, info->current_region);
out:
	*b = offset;
	return 0;
}

/**
 * sprandom_init - initialize sprandom info
 * @td: fio thread data
 * @f: fio file associated with the ssd device.
 *
 * Sets up the sprandom_info structure for the given file according:
 * region count, over-provisioning, and file/device size.
 *
 * Return: 0 on success, negative error code on failure.
 */
int sprandom_init(struct thread_data *td, struct fio_file *f)
{
	struct sprandom_info *info = NULL;
	double over_provisioning;
	uint64_t logical_size;
	uint64_t align_bs = td->o.bs[DDIR_WRITE];
	int ret;

	if (!td->o.sprandom)
		return 0;

	if (!is_power_of_2(align_bs)) {
		log_err("fio: sprandom: bs [%"PRIu64"] should be power of 2",
			align_bs);
		return -EINVAL;
	}

	info = calloc(1, sizeof(*info));
	if (!info)
		return -ENOMEM;

	logical_size = min(f->real_file_size, f->io_size);
	over_provisioning = td->o.spr_over_provisioning.u.f;
	info->num_regions = td->o.spr_num_regions;
	info->over_provisioning = over_provisioning;
	td->o.io_size = sprandom_physical_size(over_provisioning,
					       logical_size, align_bs);
	info->rand_state = &td->sprandom_state;
	ret = sprandom_setup(info, logical_size, align_bs);
	if (ret)
		goto err;

	f->spr_info = info;
	return 0;
err:
	free(info);
	return ret;
}

/**
 * sprandom_free - Frees resources associated with a sprandom_info structure.
 * @info: Pointer to the sprandom_info structure to be freed.
 *
 * Releases memory allocated for validity_dist, invalid_buf, and the spr_info
 * structure itself. Does nothing if @spr_info is NULL.
 */
void sprandom_free(struct sprandom_info *info)
{
	if (!info)
		return;

	free(info->invalid_pct);
	free(info->invalid_buf);
	free(info);
}
