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 
  33 static 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  */
  41 int
  42 filebench_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  */
  95 int
  96 filebench_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  */
 146 static double
 147 rand_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 */
 157 static double
 158 rand_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  */
 178 static double
 179 rand_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  */
 200 static double
 201 rand_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  */
 223 static double
 224 rand_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  */
 251 static void
 252 rand_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  */
 276 randdist_t *
 277 randdist_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  */
 298 static void
 299 randdist_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  */
 416 void
 417 randdist_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  */
 428 void
 429 fb_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 }