1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <stdio.h>
27#include <fcntl.h>
28#include <math.h>
29#include "filebench.h"
30#include "ipc.h"
31#include "gamma_dist.h"
32
33static int urandomfd;
34
35/*
36 * Reads a 64 bit random number from the urandom "file".
37 * Shuts down the run if the read fails. Otherwise returns
38 * the random number after rounding it off by "round".
39 * Returns 0 on success, -1 on failure.
40 */
41int
42filebench_randomno64(uint64_t *randp, uint64_t max,
43    uint64_t round, avd_t avd)
44{
45	uint64_t random;
46
47	/* check for round value too large */
48	if (max <= round) {
49		*randp = 0;
50
51		/* if it just fits, its ok, otherwise error */
52		if (max == round)
53			return (0);
54		else
55			return (-1);
56	}
57
58	if (avd) {
59
60		/* get it from the variable */
61		random = avd_get_int(avd);
62
63	} else {
64
65		/* get it from urandom */
66		if (read(urandomfd, &random,
67		    sizeof (uint64_t)) != sizeof (uint64_t)) {
68			filebench_log(LOG_ERROR,
69			    "read /dev/urandom failed: %s", strerror(errno));
70			filebench_shutdown(1);
71		}
72	}
73
74	/* clip with max and optionally round */
75	max -= round;
76	random = random / (FILEBENCH_RANDMAX64 / max);
77	if (round) {
78		random = random / round;
79		random *= round;
80	}
81	if (random > max)
82		random = max;
83
84	*randp = random;
85	return (0);
86}
87
88
89/*
90 * Reads a 32 bit random number from the urandom "file".
91 * Shuts down the run if the read fails. Otherwise returns
92 * the random number after rounding it off by "round".
93 * Returns 0 on success, -1 on failure.
94 */
95int
96filebench_randomno32(uint32_t *randp, uint32_t max,
97    uint32_t round, avd_t avd)
98{
99	uint32_t random;
100
101	/* check for round value too large */
102	if (max <= round) {
103		*randp = 0;
104
105		/* if it just fits, its ok, otherwise error */
106		if (max == round)
107			return (0);
108		else
109			return (-1);
110	}
111
112	if (avd) {
113
114		/* get it from the variable */
115		random = (uint32_t)avd_get_int(avd);
116
117	} else {
118
119		/* get it from urandom */
120		if (read(urandomfd, &random,
121		    sizeof (uint32_t)) != sizeof (uint32_t)) {
122			filebench_log(LOG_ERROR,
123			    "read /dev/urandom failed: %s", strerror(errno));
124			filebench_shutdown(1);
125		}
126	}
127
128	/* clip with max and optionally round */
129	max -= round;
130	random = random / (FILEBENCH_RANDMAX32 / max);
131	if (round) {
132		random = random / round;
133		random *= round;
134	}
135	if (random > max)
136		random = max;
137
138	*randp = random;
139	return (0);
140}
141
142/*
143 * fetch a source random number from the pseudo random number generator:
144 * erand48()
145 */
146static double
147rand_src_rand48(unsigned short *xi)
148{
149	return (erand48(xi));
150}
151
152/*
153 * fetch a source random number from the hardware random number device:
154 * urandomfd. Convert it to a floating point probability.
155 */
156/* ARGSUSED */
157static double
158rand_src_urandom(unsigned short *xi)
159{
160	fbint_t randnum;
161
162	if (read(urandomfd, &randnum,
163	    sizeof (fbint_t)) != sizeof (fbint_t)) {
164		filebench_log(LOG_ERROR,
165		    "read /dev/urandom failed: %s", strerror(errno));
166		filebench_shutdown(1);
167		return (0.0);
168	}
169
170	/* convert to 0-1 probability */
171	return ((double)randnum / (double)(FILEBENCH_RANDMAX64));
172}
173
174/*
175 * fetch a uniformly distributed random number from the supplied
176 * random object.
177 */
178static double
179rand_uniform_get(randdist_t *rndp)
180{
181	double		dprob, dmin, dres, dround;
182
183	dmin = (double)rndp->rnd_vint_min;
184	dround = (double)rndp->rnd_vint_round;
185
186	dprob = (*rndp->rnd_src)(rndp->rnd_xi);
187
188	dres = (dprob * (2.0 * (rndp->rnd_dbl_mean - dmin))) + dmin;
189
190	if (dround == 0.0)
191		return (dres);
192	else
193		return (round(dres / dround) * dround);
194}
195
196/*
197 * fetch a gamma distributed random number from the supplied
198 * random object.
199 */
200static double
201rand_gamma_get(randdist_t *rndp)
202{
203	double		dmult, dres, dmin, dround;
204
205	dmin = (double)rndp->rnd_vint_min;
206	dround = (double)rndp->rnd_vint_round;
207
208	dmult = (rndp->rnd_dbl_mean - dmin) / rndp->rnd_dbl_gamma;
209
210	dres = gamma_dist_knuth_src(rndp->rnd_dbl_gamma,
211	    dmult, rndp->rnd_src, rndp->rnd_xi) + dmin;
212
213	if (dround == 0.0)
214		return (dres);
215	else
216		return (round(dres / dround) * dround);
217}
218
219/*
220 * fetch a table driven random number from the supplied
221 * random object.
222 */
223static double
224rand_table_get(randdist_t *rndp)
225{
226	double		dprob, dprcnt, dtabres, dsclres, dmin, dround;
227	int		idx;
228
229	dmin = (double)rndp->rnd_vint_min;
230	dround = (double)rndp->rnd_vint_round;
231
232	dprob = (*rndp->rnd_src)(rndp->rnd_xi);
233
234	dprcnt = (dprob * (double)(PF_TAB_SIZE));
235	idx = (int)dprcnt;
236
237	dtabres = (rndp->rnd_rft[idx].rf_base +
238	    (rndp->rnd_rft[idx].rf_range * (dprcnt - (double)idx)));
239
240	dsclres = (dtabres * (rndp->rnd_dbl_mean - dmin)) + dmin;
241
242	if (dround == 0.0)
243		return (dsclres);
244	else
245		return (round(dsclres / dround) * dround);
246}
247
248/*
249 * Set the random seed in the supplied random object.
250 */
251static void
252rand_seed_set(randdist_t *rndp)
253{
254	union {
255		uint64_t  ll;
256		uint16_t  w[4];
257	} temp1;
258	int  idx;
259
260	temp1.ll = (uint64_t)avd_get_int(rndp->rnd_seed);
261
262	for (idx = 0; idx < 3; idx++) {
263
264#ifdef _BIG_ENDIAN
265		rndp->rnd_xi[idx] = temp1.w[3-idx];
266#else
267		rndp->rnd_xi[idx] = temp1.w[idx];
268#endif
269	}
270}
271
272/*
273 * Define a random entity which will contain the parameters of a random
274 * distribution.
275 */
276randdist_t *
277randdist_alloc(void)
278{
279	randdist_t *rndp;
280
281	if ((rndp = (randdist_t *)ipc_malloc(FILEBENCH_RANDDIST)) == NULL) {
282		filebench_log(LOG_ERROR, "Out of memory for random dist");
283		return (NULL);
284	}
285
286	/* place on global list */
287	rndp->rnd_next = filebench_shm->shm_rand_list;
288	filebench_shm->shm_rand_list = rndp;
289
290	return (rndp);
291}
292
293/*
294 * Initializes a random distribution entity, converting avd_t
295 * parameters to doubles, and converting the list of probability density
296 * function table entries, if supplied, into a probablilty function table
297 */
298static void
299randdist_init_one(randdist_t *rndp)
300{
301	probtabent_t	*rdte_hdp, *ptep;
302	double		tablemean, tablemin;
303	int		pteidx;
304
305	/* convert parameters to doubles */
306	rndp->rnd_dbl_gamma = (double)avd_get_int(rndp->rnd_gamma) / 1000.0;
307	if (rndp->rnd_mean != NULL)
308		rndp->rnd_dbl_mean  = (double)avd_get_int(rndp->rnd_mean);
309	else
310		rndp->rnd_dbl_mean = rndp->rnd_dbl_gamma;
311
312	/* de-reference min and round amounts for later use */
313	rndp->rnd_vint_min  = avd_get_int(rndp->rnd_min);
314	rndp->rnd_vint_round  = avd_get_int(rndp->rnd_round);
315
316	filebench_log(LOG_DEBUG_IMPL,
317	    "init random var %s: Mean = %6.0llf, Gamma = %6.3llf, Min = %llu",
318	    rndp->rnd_var->var_name, rndp->rnd_dbl_mean, rndp->rnd_dbl_gamma,
319	    (u_longlong_t)rndp->rnd_vint_min);
320
321	/* initialize distribution to apply */
322	switch (rndp->rnd_type & RAND_TYPE_MASK) {
323	case RAND_TYPE_UNIFORM:
324		rndp->rnd_get = rand_uniform_get;
325		break;
326
327	case RAND_TYPE_GAMMA:
328		rndp->rnd_get = rand_gamma_get;
329		break;
330
331	case RAND_TYPE_TABLE:
332		rndp->rnd_get = rand_table_get;
333		break;
334
335	default:
336		filebench_log(LOG_DEBUG_IMPL, "Random Type not Specified");
337		filebench_shutdown(1);
338		return;
339	}
340
341	/* initialize source of random numbers */
342	if (rndp->rnd_type & RAND_SRC_GENERATOR) {
343		rndp->rnd_src = rand_src_rand48;
344		rand_seed_set(rndp);
345	} else {
346		rndp->rnd_src = rand_src_urandom;
347	}
348
349	/* any random distribution table to convert? */
350	if ((rdte_hdp = rndp->rnd_probtabs) == NULL)
351		return;
352
353	/* determine random distribution max and mins and initialize table */
354	pteidx = 0;
355	tablemean = 0.0;
356	for (ptep = rdte_hdp; ptep; ptep = ptep->pte_next) {
357		double	dmin, dmax;
358		int	entcnt;
359
360		dmax = (double)avd_get_int(ptep->pte_segmax);
361		dmin = (double)avd_get_int(ptep->pte_segmin);
362
363		/* initialize table minimum on first pass */
364		if (pteidx == 0)
365			tablemin = dmin;
366
367		/* update table minimum */
368		if (tablemin > dmin)
369			tablemin = dmin;
370
371		entcnt = (int)avd_get_int(ptep->pte_percent);
372		tablemean += (((dmin + dmax)/2.0) * (double)entcnt);
373
374		/* populate the lookup table */
375
376		for (; entcnt > 0; entcnt--) {
377			rndp->rnd_rft[pteidx].rf_base = dmin;
378			rndp->rnd_rft[pteidx].rf_range = dmax - dmin;
379			pteidx++;
380		}
381	}
382
383	/* check to see if probability equals 100% */
384	if (pteidx != PF_TAB_SIZE)
385		filebench_log(LOG_ERROR,
386		    "Prob table only totals %d%%", pteidx);
387
388	/* If table is not supplied with a mean value, set it to table mean */
389	if (rndp->rnd_dbl_mean == 0.0)
390		rndp->rnd_dbl_mean = (double)tablemean / (double)PF_TAB_SIZE;
391
392	/* now normalize the entries for a min value of 0, mean of 1 */
393	tablemean = (tablemean / 100.0) - tablemin;
394
395	/* special case if really a constant value */
396	if (tablemean == 0.0) {
397		for (pteidx = 0; pteidx < PF_TAB_SIZE; pteidx++) {
398			rndp->rnd_rft[pteidx].rf_base = 0.0;
399			rndp->rnd_rft[pteidx].rf_range = 0.0;
400		}
401		return;
402	}
403
404	for (pteidx = 0; pteidx < PF_TAB_SIZE; pteidx++) {
405
406		rndp->rnd_rft[pteidx].rf_base =
407		    ((rndp->rnd_rft[pteidx].rf_base - tablemin) / tablemean);
408		rndp->rnd_rft[pteidx].rf_range =
409		    (rndp->rnd_rft[pteidx].rf_range / tablemean);
410	}
411}
412
413/*
414 * initialize all the random distribution entities
415 */
416void
417randdist_init(void)
418{
419	randdist_t *rndp;
420
421	for (rndp = filebench_shm->shm_rand_list; rndp; rndp = rndp->rnd_next)
422		randdist_init_one(rndp);
423}
424
425/*
426 * Initialize the urandom random number source
427 */
428void
429fb_random_init(void)
430{
431	/* open the "urandom" random number device file */
432	if ((urandomfd = open("/dev/urandom", O_RDONLY)) < 0) {
433		filebench_log(LOG_ERROR, "open /dev/urandom failed: %s",
434		    strerror(errno));
435		filebench_shutdown(1);
436	}
437}
438