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 2009 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 <limits.h>
  29 #include <time.h>
  30 #include <libgen.h>
  31 #include <unistd.h>
  32 #include <strings.h>
  33 #include "filebench.h"
  34 #include "ipc.h"
  35 #include "eventgen.h"
  36 #include "utils.h"
  37 #include "fsplug.h"
  38 
  39 /* File System functions vector */
  40 fsplug_func_t *fs_functions_vec;
  41 
  42 /*
  43  * Routines to access high resolution system time, initialize and
  44  * shutdown filebench, log filebench run progress and errors, and
  45  * access system information strings.
  46  */
  47 
  48 #if !defined(sun) && defined(USE_RDTSC)
  49 /*
  50  * Lets us use the rdtsc instruction to get highres time.
  51  * Thanks to libmicro
  52  */
  53 uint64_t        cpu_hz = 0;
  54 
  55 /*
  56  * Uses the rdtsc instruction to get high resolution (cpu
  57  * clock ticks) time. Only used for non Sun compiles.
  58  */
  59 __inline__ long long
  60 rdtsc(void)
  61 {
  62         unsigned long long x;
  63         __asm__ volatile(".byte 0x0f, 0x31" : "=A" (x));
  64         return (x);
  65 }
  66 
  67 /*
  68  * Get high resolution time in nanoseconds. This is the version
  69  * used when not compiled for Sun systems. It uses rdtsc call to
  70  * get clock ticks and converts to nanoseconds
  71  */
  72 uint64_t
  73 gethrtime(void)
  74 {
  75         uint64_t hrt;
  76 
  77         /* convert to nanosecs and return */
  78         hrt = 1000000000UL * rdtsc() / cpu_hz;
  79         return (hrt);
  80 }
  81 
  82 /*
  83  * Gets CPU clock frequency in MHz from cpuinfo file.
  84  * Converts to cpu_hz and stores in cpu_hz global uint64_t.
  85  * Only used for non Sun compiles.
  86  */
  87 static uint64_t
  88 parse_cpu_hz(void)
  89 {
  90         /*
  91          * Parse the following from /proc/cpuinfo.
  92          * cpu MHz              : 2191.563
  93          */
  94         FILE *cpuinfo;
  95         double hertz = -1;
  96         uint64_t hz;
  97 
  98         if ((cpuinfo = fopen("/proc/cpuinfo", "r")) == NULL) {
  99                 filebench_log(LOG_ERROR, "open /proc/cpuinfo failed: %s",
 100                     strerror(errno));
 101                 filebench_shutdown(1);
 102         }
 103         while (!feof(cpuinfo)) {
 104                 char buffer[80];
 105 
 106                 fgets(buffer, 80, cpuinfo);
 107                 if (strlen(buffer) == 0) continue;
 108                 if (strncasecmp(buffer, "cpu MHz", 7) == 0) {
 109                         char *token = strtok(buffer, ":");
 110 
 111                         if (token != NULL) {
 112                                 token = strtok((char *)NULL, ":");
 113                                 hertz = strtod(token, NULL);
 114                         }
 115                         break;
 116                 }
 117         }
 118         hz = hertz * 1000000;
 119 
 120         return (hz);
 121 }
 122 
 123 #elif !defined(sun)
 124 
 125 /*
 126  * Get high resolution time in nanoseconds. This is the version
 127  * used if compiled for Sun systems. It calls gettimeofday
 128  * to get current time and converts it to nanoseconds.
 129  */
 130 uint64_t
 131 gethrtime(void)
 132 {
 133         struct timeval tv;
 134         uint64_t hrt;
 135 
 136         gettimeofday(&tv, NULL);
 137 
 138         hrt = (uint64_t)tv.tv_sec * 1000000000UL +
 139             (uint64_t)tv.tv_usec * 1000UL;
 140         return (hrt);
 141 }
 142 #endif
 143 
 144 /*
 145  * Main filebench initialization. Opens the random number
 146  * "device" file or shuts down the run if one is not found.
 147  * Sets the cpu clock frequency variable or shuts down the
 148  * run if one is not found.
 149  */
 150 void
 151 filebench_init(void)
 152 {
 153         fb_random_init();
 154 
 155 #if defined(USE_RDTSC) && (LINUX_PORT)
 156         cpu_hz = parse_cpu_hz();
 157         if (cpu_hz <= 0) {
 158                 filebench_log(LOG_ERROR, "Error getting CPU Mhz: %s",
 159                     strerror(errno));
 160                 filebench_shutdown(1);
 161         }
 162 #endif /* USE_RDTSC */
 163 }
 164 
 165 extern int lex_lineno;
 166 
 167 /*
 168  * Writes a message consisting of information formated by
 169  * "fmt" to the log file, dump file or stdout.  The supplied
 170  * "level" argument determines which file to write to and
 171  * what other actions to take. The level LOG_LOG writes to
 172  * the "log" file, and will open the file on the first
 173  * invocation. The level LOG_DUMP writes to the "dump" file,
 174  * and will open it on the first invocation. Other levels
 175  * print to the stdout device, with the amount of information
 176  * dependent on the error level and the current error level
 177  * setting in filebench_shm->shm_debug_level.
 178  */
 179 void filebench_log
 180 __V((int level, const char *fmt, ...))
 181 {
 182         va_list args;
 183         hrtime_t now;
 184         char line[131072];
 185         char buf[131072];
 186 
 187         if (level == LOG_FATAL)
 188                 goto fatal;
 189 
 190         /* open logfile if not already open and writing to it */
 191         if ((level == LOG_LOG) &&
 192             (filebench_shm->shm_log_fd < 0)) {
 193                 char path[MAXPATHLEN];
 194                 char *s;
 195 
 196                 (void) strcpy(path, filebench_shm->shm_fscriptname);
 197                 if ((s = strstr(path, ".f")))
 198                         *s = 0;
 199                 else
 200                         (void) strcpy(path, "filebench");
 201 
 202                 (void) strcat(path, ".csv");
 203 
 204                 filebench_shm->shm_log_fd =
 205                     open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
 206         }
 207 
 208         /*
 209          * if logfile still not open, switch to LOG_ERROR level so
 210          * it gets reported to stdout
 211          */
 212         if ((level == LOG_LOG) &&
 213             (filebench_shm->shm_log_fd < 0)) {
 214                 (void) snprintf(line, sizeof (line),  "Open logfile failed: %s",
 215                     strerror(errno));
 216                 level = LOG_ERROR;
 217         }
 218 
 219         /* open dumpfile if not already open and writing to it */
 220         if ((level == LOG_DUMP) &&
 221             (*filebench_shm->shm_dump_filename == 0))
 222                 return;
 223 
 224         if ((level == LOG_DUMP) &&
 225             (filebench_shm->shm_dump_fd < 0)) {
 226 
 227                 filebench_shm->shm_dump_fd =
 228                     open(filebench_shm->shm_dump_filename,
 229                     O_RDWR | O_CREAT | O_TRUNC, 0666);
 230         }
 231 
 232         if ((level == LOG_DUMP) &&
 233             (filebench_shm->shm_dump_fd < 0)) {
 234                 (void) snprintf(line, sizeof (line), "Open logfile failed: %s",
 235                     strerror(errno));
 236                 level = LOG_ERROR;
 237         }
 238 
 239         /* Quit if this is a LOG_ERROR messages and they are disabled */
 240         if ((filebench_shm->shm_1st_err) && (level == LOG_ERROR))
 241                 return;
 242 
 243         if (level == LOG_ERROR1) {
 244                 if (filebench_shm->shm_1st_err)
 245                         return;
 246 
 247                 /* A LOG_ERROR1 temporarily disables LOG_ERROR messages */
 248                 filebench_shm->shm_1st_err = 1;
 249                 level = LOG_ERROR;
 250         }
 251 
 252         /* Only log greater than debug setting */
 253         if ((level != LOG_DUMP) && (level != LOG_LOG) &&
 254             (level > filebench_shm->shm_debug_level))
 255                 return;
 256 
 257         now = gethrtime();
 258 
 259 fatal:
 260 
 261 #ifdef __STDC__
 262         va_start(args, fmt);
 263 #else
 264         char *fmt;
 265         va_start(args);
 266         fmt = va_arg(args, char *);
 267 #endif
 268 
 269         (void) vsprintf(line, fmt, args);
 270 
 271         va_end(args);
 272 
 273         if (level == LOG_FATAL) {
 274                 (void) fprintf(stderr, "%s\n", line);
 275                 return;
 276         }
 277 
 278         /* Serialize messages to log */
 279         (void) ipc_mutex_lock(&filebench_shm->shm_msg_lock);
 280 
 281         if (level == LOG_LOG) {
 282                 if (filebench_shm->shm_log_fd > 0) {
 283                         (void) snprintf(buf, sizeof (buf), "%s\n", line);
 284                         (void) write(filebench_shm->shm_log_fd, buf,
 285                             strlen(buf));
 286                         (void) fsync(filebench_shm->shm_log_fd);
 287                         (void) ipc_mutex_unlock(&filebench_shm->shm_msg_lock);
 288                         return;
 289                 }
 290 
 291         } else if (level == LOG_DUMP) {
 292                 if (filebench_shm->shm_dump_fd != -1) {
 293                         (void) snprintf(buf, sizeof (buf), "%s\n", line);
 294                         (void) write(filebench_shm->shm_dump_fd, buf,
 295                             strlen(buf));
 296                         (void) fsync(filebench_shm->shm_dump_fd);
 297                         (void) ipc_mutex_unlock(&filebench_shm->shm_msg_lock);
 298                         return;
 299                 }
 300 
 301         } else if (filebench_shm->shm_debug_level > LOG_INFO) {
 302                 if (level < LOG_INFO)
 303                         (void) fprintf(stderr, "%5d: ", (int)my_pid);
 304                 else
 305                         (void) fprintf(stdout, "%5d: ", (int)my_pid);
 306         }
 307 
 308         if (level < LOG_INFO) {
 309                 (void) fprintf(stderr, "%4.3f: %s",
 310                     (now - filebench_shm->shm_epoch) / FSECS,
 311                     line);
 312 
 313                 if (my_procflow == NULL)
 314                         (void) fprintf(stderr, " on line %d", lex_lineno);
 315 
 316                 (void) fprintf(stderr, "\n");
 317                 (void) fflush(stderr);
 318         } else {
 319                 (void) fprintf(stdout, "%4.3f: %s",
 320                     (now - filebench_shm->shm_epoch) / FSECS,
 321                     line);
 322                 (void) fprintf(stdout, "\n");
 323                 (void) fflush(stdout);
 324         }
 325 
 326         (void) ipc_mutex_unlock(&filebench_shm->shm_msg_lock);
 327 }
 328 
 329 /*
 330  * Stops the run and exits filebench. If filebench is
 331  * currently running a workload, calls procflow_shutdown()
 332  * to stop the run. Also closes and deletes shared memory.
 333  */
 334 void
 335 filebench_shutdown(int error) {
 336 
 337         if (error) {
 338                 filebench_log(LOG_DEBUG_IMPL, "Shutdown on error %d", error);
 339                 (void) ipc_mutex_lock(&filebench_shm->shm_procflow_lock);
 340                 if (filebench_shm->shm_f_abort == FILEBENCH_ABORT_FINI) {
 341                         (void) ipc_mutex_unlock(
 342                             &filebench_shm->shm_procflow_lock);
 343                         return;
 344                 }
 345                 filebench_shm->shm_f_abort = FILEBENCH_ABORT_ERROR;
 346                 (void) ipc_mutex_unlock(&filebench_shm->shm_procflow_lock);
 347         } else {
 348                 filebench_log(LOG_DEBUG_IMPL, "Shutdown");
 349         }
 350 
 351         procflow_shutdown();
 352 
 353         (void) unlink("/tmp/filebench_shm");
 354         ipc_ismdelete();
 355         exit(error);
 356 }
 357 
 358 /*
 359  * Put the hostname in ${hostname}. The system supplied
 360  * host name string is copied into an allocated string and
 361  * the pointer to the string is placed in the supplied
 362  * variable "var". If var->var_val.string already points to
 363  * a string, the string is freed. The routine always
 364  * returns zero (0).
 365  */
 366 var_t *
 367 host_var(var_t *var)
 368 {
 369         char hoststr[128];
 370         char *strptr;
 371 
 372         (void) gethostname(hoststr, 128);
 373         if (VAR_HAS_STRING(var) && var->var_val.string)
 374                 free(var->var_val.string);
 375 
 376         if ((strptr = fb_stralloc(hoststr)) == NULL) {
 377                 filebench_log(LOG_ERROR,
 378                     "unable to allocate string for host name");
 379                 return (NULL);
 380         }
 381 
 382         VAR_SET_STR(var, strptr);
 383         return (0);
 384 }
 385 
 386 /*
 387  * Put the date string in ${date}. The system supplied date is
 388  * copied into an allocated string and the pointer to the string
 389  * is placed in the supplied var_t's var_val.string. If
 390  * var->var_val.string already points to a string, the string
 391  * is freed. The routine returns a pointer to the supplied var_t,
 392  * unless it is unable to allocate string for the date, in which
 393  * case it returns NULL.
 394  */
 395 var_t *
 396 date_var(var_t *var)
 397 {
 398         char datestr[128];
 399         char *strptr;
 400 #ifdef HAVE_CFTIME
 401         time_t t = time(NULL);
 402 #else
 403         struct tm t;
 404 #endif
 405 
 406 #ifdef HAVE_CFTIME
 407         cftime(datestr, "%y%m%d%H" "%M", &t);
 408 #else
 409         (void) strftime(datestr, sizeof (datestr), "%y%m%d%H %M", &t);
 410 #endif
 411 
 412         if (VAR_HAS_STRING(var) && var->var_val.string)
 413                 free(var->var_val.string);
 414 
 415         if ((strptr = fb_stralloc(datestr)) == NULL) {
 416                 filebench_log(LOG_ERROR,
 417                     "unable to allocate string for date");
 418                 return (NULL);
 419         }
 420 
 421         VAR_SET_STR(var, strptr);
 422 
 423         return (var);
 424 }
 425 
 426 extern char *fscriptname;
 427 
 428 /*
 429  * Put the script name in ${script}. The path name of the script
 430  * used with this filebench run trimmed of the trailing ".f" and
 431  * all leading subdirectories. The remaining script name is
 432  * copied into the var_val.string field of the supplied variable
 433  * "var". The routine returns a pointer to the supplied var_t,
 434  * unless it is unable to allocate string space, in which case it
 435  * returns NULL.
 436  */
 437 var_t *
 438 script_var(var_t *var)
 439 {
 440         char *scriptstr;
 441         char *f = fb_stralloc(fscriptname);
 442         char *strptr;
 443 
 444         /* Trim the .f suffix */
 445         for (scriptstr = f + strlen(f) - 1; scriptstr != f; scriptstr--) {
 446                 if (*scriptstr == '.') {
 447                         *scriptstr = 0;
 448                         break;
 449                 }
 450         }
 451 
 452         if ((strptr = fb_stralloc(basename(f))) == NULL) {
 453                 filebench_log(LOG_ERROR,
 454                     "unable to allocate string for script name");
 455                 free(f);
 456                 return (NULL);
 457         }
 458 
 459         VAR_SET_STR(var, strptr);
 460         free(f);
 461 
 462         return (var);
 463 }
 464 
 465 void fb_lfs_funcvecinit(void);
 466 
 467 /*
 468  * Initialize any "plug-in" I/O function vectors. Called by each
 469  * filebench process that is forked, as the vector is relative to
 470  * its image.
 471  */
 472 void
 473 filebench_plugin_funcvecinit(void)
 474 {
 475 
 476         switch (filebench_shm->shm_filesys_type) {
 477         case LOCAL_FS_PLUG:
 478                 fb_lfs_funcvecinit();
 479                 break;
 480 
 481         case NFS3_PLUG:
 482         case NFS4_PLUG:
 483         case CIFS_PLUG:
 484                 break;
 485         default:
 486                 filebench_log(LOG_ERROR,
 487                     "filebench_plugin_funcvecinit: unknown file system");
 488                 break;
 489         }
 490 }