OpenSolaris_b135/cmd/filebench/common/fb_random.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <fcntl.h>
#include <math.h>
#include "filebench.h"
#include "ipc.h"
#include "gamma_dist.h"

static int urandomfd;

/*
 * Reads a 64 bit random number from the urandom "file".
 * Shuts down the run if the read fails. Otherwise returns
 * the random number after rounding it off by "round".
 * Returns 0 on success, -1 on failure.
 */
int
filebench_randomno64(uint64_t *randp, uint64_t max,
    uint64_t round, avd_t avd)
{
	uint64_t random;

	/* check for round value too large */
	if (max <= round) {
		*randp = 0;

		/* if it just fits, its ok, otherwise error */
		if (max == round)
			return (0);
		else
			return (-1);
	}

	if (avd) {

		/* get it from the variable */
		random = avd_get_int(avd);

	} else {

		/* get it from urandom */
		if (read(urandomfd, &random,
		    sizeof (uint64_t)) != sizeof (uint64_t)) {
			filebench_log(LOG_ERROR,
			    "read /dev/urandom failed: %s", strerror(errno));
			filebench_shutdown(1);
		}
	}

	/* clip with max and optionally round */
	max -= round;
	random = random / (FILEBENCH_RANDMAX64 / max);
	if (round) {
		random = random / round;
		random *= round;
	}
	if (random > max)
		random = max;

	*randp = random;
	return (0);
}


/*
 * Reads a 32 bit random number from the urandom "file".
 * Shuts down the run if the read fails. Otherwise returns
 * the random number after rounding it off by "round".
 * Returns 0 on success, -1 on failure.
 */
int
filebench_randomno32(uint32_t *randp, uint32_t max,
    uint32_t round, avd_t avd)
{
	uint32_t random;

	/* check for round value too large */
	if (max <= round) {
		*randp = 0;

		/* if it just fits, its ok, otherwise error */
		if (max == round)
			return (0);
		else
			return (-1);
	}

	if (avd) {

		/* get it from the variable */
		random = (uint32_t)avd_get_int(avd);

	} else {

		/* get it from urandom */
		if (read(urandomfd, &random,
		    sizeof (uint32_t)) != sizeof (uint32_t)) {
			filebench_log(LOG_ERROR,
			    "read /dev/urandom failed: %s", strerror(errno));
			filebench_shutdown(1);
		}
	}

	/* clip with max and optionally round */
	max -= round;
	random = random / (FILEBENCH_RANDMAX32 / max);
	if (round) {
		random = random / round;
		random *= round;
	}
	if (random > max)
		random = max;

	*randp = random;
	return (0);
}

/*
 * fetch a source random number from the pseudo random number generator:
 * erand48()
 */
static double
rand_src_rand48(unsigned short *xi)
{
	return (erand48(xi));
}

/*
 * fetch a source random number from the hardware random number device:
 * urandomfd. Convert it to a floating point probability.
 */
/* ARGSUSED */
static double
rand_src_urandom(unsigned short *xi)
{
	fbint_t randnum;

	if (read(urandomfd, &randnum,
	    sizeof (fbint_t)) != sizeof (fbint_t)) {
		filebench_log(LOG_ERROR,
		    "read /dev/urandom failed: %s", strerror(errno));
		filebench_shutdown(1);
		return (0.0);
	}

	/* convert to 0-1 probability */
	return ((double)randnum / (double)(FILEBENCH_RANDMAX64));
}

/*
 * fetch a uniformly distributed random number from the supplied
 * random object.
 */
static double
rand_uniform_get(randdist_t *rndp)
{
	double		dprob, dmin, dres, dround;

	dmin = (double)rndp->rnd_vint_min;
	dround = (double)rndp->rnd_vint_round;

	dprob = (*rndp->rnd_src)(rndp->rnd_xi);

	dres = (dprob * (2.0 * (rndp->rnd_dbl_mean - dmin))) + dmin;

	if (dround == 0.0)
		return (dres);
	else
		return (round(dres / dround) * dround);
}

/*
 * fetch a gamma distributed random number from the supplied
 * random object.
 */
static double
rand_gamma_get(randdist_t *rndp)
{
	double		dmult, dres, dmin, dround;

	dmin = (double)rndp->rnd_vint_min;
	dround = (double)rndp->rnd_vint_round;

	dmult = (rndp->rnd_dbl_mean - dmin) / rndp->rnd_dbl_gamma;

	dres = gamma_dist_knuth_src(rndp->rnd_dbl_gamma,
	    dmult, rndp->rnd_src, rndp->rnd_xi) + dmin;

	if (dround == 0.0)
		return (dres);
	else
		return (round(dres / dround) * dround);
}

/*
 * fetch a table driven random number from the supplied
 * random object.
 */
static double
rand_table_get(randdist_t *rndp)
{
	double		dprob, dprcnt, dtabres, dsclres, dmin, dround;
	int		idx;

	dmin = (double)rndp->rnd_vint_min;
	dround = (double)rndp->rnd_vint_round;

	dprob = (*rndp->rnd_src)(rndp->rnd_xi);

	dprcnt = (dprob * (double)(PF_TAB_SIZE));
	idx = (int)dprcnt;

	dtabres = (rndp->rnd_rft[idx].rf_base +
	    (rndp->rnd_rft[idx].rf_range * (dprcnt - (double)idx)));

	dsclres = (dtabres * (rndp->rnd_dbl_mean - dmin)) + dmin;

	if (dround == 0.0)
		return (dsclres);
	else
		return (round(dsclres / dround) * dround);
}

/*
 * Set the random seed in the supplied random object.
 */
static void
rand_seed_set(randdist_t *rndp)
{
	union {
		uint64_t  ll;
		uint16_t  w[4];
	} temp1;
	int  idx;

	temp1.ll = (uint64_t)avd_get_int(rndp->rnd_seed);

	for (idx = 0; idx < 3; idx++) {

#ifdef _BIG_ENDIAN
		rndp->rnd_xi[idx] = temp1.w[3-idx];
#else
		rndp->rnd_xi[idx] = temp1.w[idx];
#endif
	}
}

/*
 * Define a random entity which will contain the parameters of a random
 * distribution.
 */
randdist_t *
randdist_alloc(void)
{
	randdist_t *rndp;

	if ((rndp = (randdist_t *)ipc_malloc(FILEBENCH_RANDDIST)) == NULL) {
		filebench_log(LOG_ERROR, "Out of memory for random dist");
		return (NULL);
	}

	/* place on global list */
	rndp->rnd_next = filebench_shm->shm_rand_list;
	filebench_shm->shm_rand_list = rndp;

	return (rndp);
}

/*
 * Initializes a random distribution entity, converting avd_t
 * parameters to doubles, and converting the list of probability density
 * function table entries, if supplied, into a probablilty function table
 */
static void
randdist_init_one(randdist_t *rndp)
{
	probtabent_t	*rdte_hdp, *ptep;
	double		tablemean, tablemin;
	int		pteidx;

	/* convert parameters to doubles */
	rndp->rnd_dbl_gamma = (double)avd_get_int(rndp->rnd_gamma) / 1000.0;
	if (rndp->rnd_mean != NULL)
		rndp->rnd_dbl_mean  = (double)avd_get_int(rndp->rnd_mean);
	else
		rndp->rnd_dbl_mean = rndp->rnd_dbl_gamma;

	/* de-reference min and round amounts for later use */
	rndp->rnd_vint_min  = avd_get_int(rndp->rnd_min);
	rndp->rnd_vint_round  = avd_get_int(rndp->rnd_round);

	filebench_log(LOG_DEBUG_IMPL,
	    "init random var %s: Mean = %6.0llf, Gamma = %6.3llf, Min = %llu",
	    rndp->rnd_var->var_name, rndp->rnd_dbl_mean, rndp->rnd_dbl_gamma,
	    (u_longlong_t)rndp->rnd_vint_min);

	/* initialize distribution to apply */
	switch (rndp->rnd_type & RAND_TYPE_MASK) {
	case RAND_TYPE_UNIFORM:
		rndp->rnd_get = rand_uniform_get;
		break;

	case RAND_TYPE_GAMMA:
		rndp->rnd_get = rand_gamma_get;
		break;

	case RAND_TYPE_TABLE:
		rndp->rnd_get = rand_table_get;
		break;

	default:
		filebench_log(LOG_DEBUG_IMPL, "Random Type not Specified");
		filebench_shutdown(1);
		return;
	}

	/* initialize source of random numbers */
	if (rndp->rnd_type & RAND_SRC_GENERATOR) {
		rndp->rnd_src = rand_src_rand48;
		rand_seed_set(rndp);
	} else {
		rndp->rnd_src = rand_src_urandom;
	}

	/* any random distribution table to convert? */
	if ((rdte_hdp = rndp->rnd_probtabs) == NULL)
		return;

	/* determine random distribution max and mins and initialize table */
	pteidx = 0;
	tablemean = 0.0;
	for (ptep = rdte_hdp; ptep; ptep = ptep->pte_next) {
		double	dmin, dmax;
		int	entcnt;

		dmax = (double)avd_get_int(ptep->pte_segmax);
		dmin = (double)avd_get_int(ptep->pte_segmin);

		/* initialize table minimum on first pass */
		if (pteidx == 0)
			tablemin = dmin;

		/* update table minimum */
		if (tablemin > dmin)
			tablemin = dmin;

		entcnt = (int)avd_get_int(ptep->pte_percent);
		tablemean += (((dmin + dmax)/2.0) * (double)entcnt);

		/* populate the lookup table */

		for (; entcnt > 0; entcnt--) {
			rndp->rnd_rft[pteidx].rf_base = dmin;
			rndp->rnd_rft[pteidx].rf_range = dmax - dmin;
			pteidx++;
		}
	}

	/* check to see if probability equals 100% */
	if (pteidx != PF_TAB_SIZE)
		filebench_log(LOG_ERROR,
		    "Prob table only totals %d%%", pteidx);

	/* If table is not supplied with a mean value, set it to table mean */
	if (rndp->rnd_dbl_mean == 0.0)
		rndp->rnd_dbl_mean = (double)tablemean / (double)PF_TAB_SIZE;

	/* now normalize the entries for a min value of 0, mean of 1 */
	tablemean = (tablemean / 100.0) - tablemin;

	/* special case if really a constant value */
	if (tablemean == 0.0) {
		for (pteidx = 0; pteidx < PF_TAB_SIZE; pteidx++) {
			rndp->rnd_rft[pteidx].rf_base = 0.0;
			rndp->rnd_rft[pteidx].rf_range = 0.0;
		}
		return;
	}

	for (pteidx = 0; pteidx < PF_TAB_SIZE; pteidx++) {

		rndp->rnd_rft[pteidx].rf_base =
		    ((rndp->rnd_rft[pteidx].rf_base - tablemin) / tablemean);
		rndp->rnd_rft[pteidx].rf_range =
		    (rndp->rnd_rft[pteidx].rf_range / tablemean);
	}
}

/*
 * initialize all the random distribution entities
 */
void
randdist_init(void)
{
	randdist_t *rndp;

	for (rndp = filebench_shm->shm_rand_list; rndp; rndp = rndp->rnd_next)
		randdist_init_one(rndp);
}

/*
 * Initialize the urandom random number source
 */
void
fb_random_init(void)
{
	/* open the "urandom" random number device file */
	if ((urandomfd = open("/dev/urandom", O_RDONLY)) < 0) {
		filebench_log(LOG_ERROR, "open /dev/urandom failed: %s",
		    strerror(errno));
		filebench_shutdown(1);
	}
}