xref: /onnv-gate/usr/src/cmd/filebench/common/misc.c (revision 6084:d5f45b4dae7e)
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 #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 	/* Quit if this is a LOG_ERROR messages and they are disabled */
335 	if ((filebench_shm->shm_1st_err) && (level == LOG_ERROR))
336 		return;
337 
338 	if (level == LOG_ERROR1) {
339 		if (filebench_shm->shm_1st_err)
340 			return;
341 
342 		/* A LOG_ERROR1 temporarily disables LOG_ERROR messages */
343 		filebench_shm->shm_1st_err = 1;
344 		level = LOG_ERROR;
345 	}
346 
347 	/* Only log greater than debug setting */
348 	if ((level != LOG_DUMP) && (level != LOG_LOG) &&
349 	    (level > filebench_shm->debug_level))
350 		return;
351 
352 	now = gethrtime();
353 
354 fatal:
355 
356 #ifdef __STDC__
357 	va_start(args, fmt);
358 #else
359 	char *fmt;
360 	va_start(args);
361 	fmt = va_arg(args, char *);
362 #endif
363 
364 	(void) vsprintf(line, fmt, args);
365 
366 	va_end(args);
367 
368 	if (level == LOG_FATAL) {
369 		(void) fprintf(stdout, "%s\n", line);
370 		return;
371 	}
372 
373 	/* Serialize messages to log */
374 	(void) ipc_mutex_lock(&filebench_shm->msg_lock);
375 
376 	if (level == LOG_LOG) {
377 		if (filebench_shm->log_fd > 0) {
378 			(void) snprintf(buf, sizeof (buf), "%s\n", line);
379 			(void) write(filebench_shm->log_fd, buf, strlen(buf));
380 			(void) fsync(filebench_shm->log_fd);
381 		}
382 
383 	} else if (level == LOG_DUMP) {
384 		if (filebench_shm->dump_fd != -1) {
385 			(void) snprintf(buf, sizeof (buf), "%s\n", line);
386 			(void) write(filebench_shm->dump_fd, buf, strlen(buf));
387 			(void) fsync(filebench_shm->dump_fd);
388 		}
389 
390 	} else if (filebench_shm->debug_level > LOG_INFO) {
391 		(void) fprintf(stdout, "%5d: %4.3f: %s",
392 		    (int)my_pid, (now - filebench_shm->epoch) / FSECS,
393 		    line);
394 	} else {
395 		(void) fprintf(stdout, "%4.3f: %s",
396 		    (now - filebench_shm->epoch) / FSECS,
397 		    line);
398 	}
399 
400 	if (level == LOG_ERROR) {
401 		if (my_procflow == NULL)
402 			(void) fprintf(stdout, " on line %d", lex_lineno);
403 	}
404 
405 	if ((level != LOG_LOG) && (level != LOG_DUMP)) {
406 		(void) fprintf(stdout, "\n");
407 		(void) fflush(stdout);
408 	}
409 
410 	(void) ipc_mutex_unlock(&filebench_shm->msg_lock);
411 }
412 
413 /*
414  * Stops the run and exits filebench. If filebench is
415  * currently running a workload, calls procflow_shutdown()
416  * to stop the run. Also closes and deletes shared memory.
417  */
418 void
419 filebench_shutdown(int error) {
420 	filebench_log(LOG_DEBUG_IMPL, "Shutdown");
421 	(void) unlink("/tmp/filebench_shm");
422 	if (filebench_shm->shm_running)
423 		procflow_shutdown();
424 	filebench_shm->f_abort = 1;
425 	ipc_ismdelete();
426 	exit(error);
427 }
428 
429 /*
430  * Put the hostname in ${hostname}. The system supplied
431  * host name string is copied into an allocated string and
432  * the pointer to the string is placed in the supplied
433  * variable "var". If var->var_string already points to
434  * a string, the string is freed. The routine always
435  * returns zero (0).
436  */
437 var_t *
438 host_var(var_t *var)
439 {
440 	char hoststr[128];
441 
442 	(void) gethostname(hoststr, 128);
443 	if (var->var_string)
444 		free(var->var_string);
445 	var->var_string = fb_stralloc(hoststr);
446 	return (0);
447 }
448 
449 /*
450  * Put the date string in ${date}. The system supplied date is
451  * copied into an allocated string and the pointer to the string
452  * is placed in the supplied var_t's var_string. If
453  * var->var_string already points to a string, the string
454  * is freed. The routine always returns a pointer to the
455  * supplied var_t.
456  */
457 var_t *
458 date_var(var_t *var)
459 {
460 	char datestr[128];
461 #ifdef HAVE_CFTIME
462 	time_t t = time(NULL);
463 #else
464 	struct tm t;
465 #endif
466 
467 #ifdef HAVE_CFTIME
468 	cftime(datestr, "%y%m%d%H" "%M", &t);
469 #else
470 	(void) strftime(datestr, sizeof (datestr), "%y%m%d%H %M", &t);
471 #endif
472 
473 	if (var->var_string)
474 		free(var->var_string);
475 	var->var_string = fb_stralloc(datestr);
476 
477 	return (var);
478 }
479 
480 extern char *fscriptname;
481 
482 /*
483  * Put the script name in ${script}. The path name of the script
484  * used with this filebench run trimmed of the trailing ".f" and
485  * all leading subdirectories. The remaining script name is
486  * copied into the var_string field of the supplied variable
487  * "var". The routine always returns a pointer to the supplied
488  * var_t.
489  */
490 var_t *
491 script_var(var_t *var)
492 {
493 	char *scriptstr;
494 	char *f = fb_stralloc(fscriptname);
495 
496 	/* Trim the .f suffix */
497 	for (scriptstr = f + strlen(f) - 1; scriptstr != f; scriptstr--) {
498 		if (*scriptstr == '.') {
499 			*scriptstr = 0;
500 			break;
501 		}
502 	}
503 
504 	var->var_string = fb_stralloc(basename(f));
505 	free(f);
506 
507 	return (var);
508 }
509