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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <stdio.h> 29 #include <fcntl.h> 30 #include <limits.h> 31 #include <time.h> 32 #include <libgen.h> 33 #include <unistd.h> 34 #include <strings.h> 35 #include "filebench.h" 36 #include "ipc.h" 37 #include "eventgen.h" 38 #include "utils.h" 39 40 /* 41 * Routines to access high resolution system time, initialize and 42 * shutdown filebench, obtain system generated random numbers from 43 * "urandom", log filebench run progress and errors, and access system 44 * information strings. 45 */ 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 printf("CPU Mhz %9.6f, sysconf:%ld\n", hertz, sysconf(_SC_CLK_TCK)); 119 hz = hertz * 1000000; 120 121 return (hz); 122 } 123 124 #elif !defined(sun) 125 126 /* 127 * Get high resolution time in nanoseconds. This is the version 128 * used if compiled for Sun systems. It calls gettimeofday 129 * to get current time and converts it to nanoseconds. 130 */ 131 uint64_t 132 gethrtime(void) 133 { 134 struct timeval tv; 135 uint64_t hrt; 136 137 gettimeofday(&tv, NULL); 138 139 hrt = (uint64_t)tv.tv_sec * 1000000000UL + 140 (uint64_t)tv.tv_usec * 1000UL; 141 return (hrt); 142 } 143 #endif 144 145 static int urandomfd; 146 147 /* 148 * Main filebench initialization. Opens the random number 149 * "device" file or shuts down the run if one is not found. 150 * Sets the cpu clock frequency variable or shuts down the 151 * run if one is not found. 152 */ 153 void 154 filebench_init(void) 155 { 156 /* open the "urandom" random number device file */ 157 if ((urandomfd = open("/dev/urandom", O_RDONLY)) < 0) { 158 filebench_log(LOG_ERROR, "open /dev/urandom failed: %s", 159 strerror(errno)); 160 filebench_shutdown(1); 161 } 162 #if defined(USE_RDTSC) && (LINUX_PORT) 163 cpu_hz = parse_cpu_hz(); 164 if (cpu_hz <= 0) { 165 filebench_log(LOG_ERROR, "Error getting CPU Mhz: %s", 166 strerror(errno)); 167 filebench_shutdown(1); 168 } 169 #endif /* USE_RDTSC */ 170 171 } 172 173 /* 174 * Reads a 64 bit random number from the urandom "file". 175 * Shuts down the run if the read fails. Otherwise returns 176 * the random number after rounding it off by "round". 177 * Returns 0 on success, -1 on failure. 178 */ 179 int 180 filebench_randomno64(uint64_t *randp, uint64_t max, uint64_t round) 181 { 182 uint64_t random; 183 184 /* check for round value too large */ 185 if (max <= round) { 186 *randp = 0; 187 188 /* if it just fits, its ok, otherwise error */ 189 if (max == round) 190 return (0); 191 else 192 return (-1); 193 } 194 195 if (read(urandomfd, &random, 196 sizeof (uint64_t)) != sizeof (uint64_t)) { 197 filebench_log(LOG_ERROR, "read /dev/urandom failed: %s", 198 strerror(errno)); 199 filebench_shutdown(1); 200 } 201 202 /* clip with max and optionally round */ 203 max -= round; 204 random = random / (FILEBENCH_RANDMAX64 / max); 205 if (round) { 206 random = random / round; 207 random *= round; 208 } 209 if (random > max) 210 random = max; 211 212 *randp = random; 213 return (0); 214 } 215 216 217 /* 218 * Reads a 32 bit random number from the urandom "file". 219 * Shuts down the run if the read fails. Otherwise returns 220 * the random number after rounding it off by "round". 221 * Returns 0 on success, -1 on failure. 222 */ 223 int 224 filebench_randomno32(uint32_t *randp, uint32_t max, uint32_t round) 225 { 226 uint32_t random; 227 228 /* check for round value too large */ 229 if (max <= round) { 230 *randp = 0; 231 232 /* if it just fits, its ok, otherwise error */ 233 if (max == round) 234 return (0); 235 else 236 return (-1); 237 } 238 239 if (read(urandomfd, &random, 240 sizeof (uint32_t)) != sizeof (uint32_t)) { 241 filebench_log(LOG_ERROR, "read /dev/urandom failed: %s", 242 strerror(errno)); 243 filebench_shutdown(1); 244 } 245 246 /* clip with max and optionally round */ 247 max -= round; 248 random = random / (FILEBENCH_RANDMAX32 / max); 249 if (round) { 250 random = random / round; 251 random *= round; 252 } 253 if (random > max) 254 random = max; 255 256 *randp = random; 257 return (0); 258 } 259 260 extern int lex_lineno; 261 262 /* 263 * Writes a message consisting of information formated by 264 * "fmt" to the log file, dump file or stdout. The supplied 265 * "level" argument determines which file to write to and 266 * what other actions to take. The level LOG_LOG writes to 267 * the "log" file, and will open the file on the first 268 * invocation. The level LOG_DUMP writes to the "dump" file, 269 * and will open it on the first invocation. Other levels 270 * print to the stdout device, with the amount of information 271 * dependent on the error level and the current error level 272 * setting in filebench_shm->debug_level. 273 */ 274 void filebench_log 275 __V((int level, const char *fmt, ...)) 276 { 277 va_list args; 278 hrtime_t now; 279 char line[131072]; 280 char buf[131072]; 281 282 if (level == LOG_FATAL) 283 goto fatal; 284 285 /* open logfile if not already open and writing to it */ 286 if ((level == LOG_LOG) && 287 (filebench_shm->log_fd < 0)) { 288 char path[MAXPATHLEN]; 289 char *s; 290 291 (void) strcpy(path, filebench_shm->fscriptname); 292 if ((s = strstr(path, ".f"))) 293 *s = 0; 294 else 295 (void) strcpy(path, "filebench"); 296 297 (void) strcat(path, ".csv"); 298 299 filebench_shm->log_fd = 300 open(path, O_RDWR | O_CREAT | O_TRUNC, 0666); 301 } 302 303 /* 304 * if logfile still not open, switch to LOG_ERROR level so 305 * it gets reported to stdout 306 */ 307 if ((level == LOG_LOG) && 308 (filebench_shm->log_fd < 0)) { 309 (void) snprintf(line, sizeof (line), "Open logfile failed: %s", 310 strerror(errno)); 311 level = LOG_ERROR; 312 } 313 314 /* open dumpfile if not already open and writing to it */ 315 if ((level == LOG_DUMP) && 316 (*filebench_shm->dump_filename == 0)) 317 return; 318 319 if ((level == LOG_DUMP) && 320 (filebench_shm->dump_fd < 0)) { 321 322 filebench_shm->dump_fd = 323 open(filebench_shm->dump_filename, 324 O_RDWR | O_CREAT | O_TRUNC, 0666); 325 } 326 327 if ((level == LOG_DUMP) && 328 (filebench_shm->dump_fd < 0)) { 329 (void) snprintf(line, sizeof (line), "Open logfile failed: %s", 330 strerror(errno)); 331 level = LOG_ERROR; 332 } 333 334 /* Only log greater than debug setting */ 335 if ((level != LOG_DUMP) && (level != LOG_LOG) && 336 (level > filebench_shm->debug_level)) 337 return; 338 339 now = gethrtime(); 340 341 fatal: 342 343 #ifdef __STDC__ 344 va_start(args, fmt); 345 #else 346 char *fmt; 347 va_start(args); 348 fmt = va_arg(args, char *); 349 #endif 350 351 (void) vsprintf(line, fmt, args); 352 353 va_end(args); 354 355 if (level == LOG_FATAL) { 356 (void) fprintf(stdout, "%s\n", line); 357 return; 358 } 359 360 /* Serialize messages to log */ 361 (void) ipc_mutex_lock(&filebench_shm->msg_lock); 362 363 if (level == LOG_LOG) { 364 if (filebench_shm->log_fd > 0) { 365 (void) snprintf(buf, sizeof (buf), "%s\n", line); 366 (void) write(filebench_shm->log_fd, buf, strlen(buf)); 367 (void) fsync(filebench_shm->log_fd); 368 } 369 370 } else if (level == LOG_DUMP) { 371 if (filebench_shm->dump_fd != -1) { 372 (void) snprintf(buf, sizeof (buf), "%s\n", line); 373 (void) write(filebench_shm->dump_fd, buf, strlen(buf)); 374 (void) fsync(filebench_shm->dump_fd); 375 } 376 377 } else if (filebench_shm->debug_level > LOG_INFO) { 378 (void) fprintf(stdout, "%5d: %4.3f: %s", 379 (int)pid, (now - filebench_shm->epoch) / FSECS, 380 line); 381 } else { 382 (void) fprintf(stdout, "%4.3f: %s", 383 (now - filebench_shm->epoch) / FSECS, 384 line); 385 } 386 387 if (level == LOG_ERROR) { 388 (void) fprintf(stdout, " on line %d", lex_lineno); 389 } 390 391 if ((level != LOG_LOG) && (level != LOG_DUMP)) { 392 (void) fprintf(stdout, "\n"); 393 (void) fflush(stdout); 394 } 395 396 (void) ipc_mutex_unlock(&filebench_shm->msg_lock); 397 } 398 399 /* 400 * Stops the run and exits filebench. If filebench is 401 * currently running a workload, calls procflow_shutdown() 402 * to stop the run. Also closes and deletes shared memory. 403 */ 404 void 405 filebench_shutdown(int error) { 406 filebench_log(LOG_DEBUG_IMPL, "Shutdown"); 407 (void) unlink("/tmp/filebench_shm"); 408 if (filebench_shm->allrunning) 409 procflow_shutdown(); 410 filebench_shm->f_abort = 1; 411 ipc_ismdelete(); 412 exit(error); 413 } 414 415 /* 416 * Put the hostname in ${hostname}. The system supplied 417 * host name string is copied into an allocated string and 418 * the pointer to the string is placed in the supplied 419 * variable "var". If var->var_string already points to 420 * a string, the string is freed. The routine always 421 * returns zero (0). 422 */ 423 var_t * 424 host_var(var_t *var) 425 { 426 char hoststr[128]; 427 428 (void) gethostname(hoststr, 128); 429 if (var->var_string) 430 free(var->var_string); 431 var->var_string = fb_stralloc(hoststr); 432 return (0); 433 } 434 435 /* 436 * Put the date string in ${date}. The system supplied date is 437 * copied into an allocated string and the pointer to the string 438 * is placed in the supplied var_t's var_string. If 439 * var->var_string already points to a string, the string 440 * is freed. The routine always returns a pointer to the 441 * supplied var_t. 442 */ 443 var_t * 444 date_var(var_t *var) 445 { 446 char datestr[128]; 447 #ifdef HAVE_CFTIME 448 time_t t = time(NULL); 449 #else 450 struct tm t; 451 #endif 452 453 #ifdef HAVE_CFTIME 454 cftime(datestr, "%y%m%d%H" "%M", &t); 455 #else 456 (void) strftime(datestr, sizeof (datestr), "%y%m%d%H %M", &t); 457 #endif 458 459 if (var->var_string) 460 free(var->var_string); 461 var->var_string = fb_stralloc(datestr); 462 463 return (var); 464 } 465 466 extern char *fscriptname; 467 468 /* 469 * Put the script name in ${script}. The path name of the script 470 * used with this filebench run trimmed of the trailing ".f" and 471 * all leading subdirectories. The remaining script name is 472 * copied into the var_string field of the supplied variable 473 * "var". The routine always returns a pointer to the supplied 474 * var_t. 475 */ 476 var_t * 477 script_var(var_t *var) 478 { 479 char *scriptstr; 480 char *f = fb_stralloc(fscriptname); 481 482 /* Trim the .f suffix */ 483 for (scriptstr = f + strlen(f) - 1; scriptstr != f; scriptstr--) { 484 if (*scriptstr == '.') { 485 *scriptstr = 0; 486 break; 487 } 488 } 489 490 var->var_string = fb_stralloc(basename(f)); 491 free(f); 492 493 return (var); 494 } 495