xref: /onnv-gate/usr/src/cmd/filebench/common/misc.c (revision 5184:da60d2b4a9e2)
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, "%5ld: %4.3f: %s",
379 		    pid, (now - filebench_shm->epoch) / FSECS,
380 		    line);
381 
382 	} else {
383 		(void) fprintf(stdout, "%4.3f: %s",
384 		    (now - filebench_shm->epoch) / FSECS,
385 		    line);
386 	}
387 
388 	if (level == LOG_ERROR) {
389 		(void) fprintf(stdout, " on line %d", lex_lineno);
390 	}
391 
392 	if ((level != LOG_LOG) && (level != LOG_DUMP)) {
393 		(void) fprintf(stdout, "\n");
394 		(void) fflush(stdout);
395 	}
396 
397 	(void) ipc_mutex_unlock(&filebench_shm->msg_lock);
398 }
399 
400 /*
401  * Stops the run and exits filebench. If filebench is
402  * currently running a workload, calls procflow_shutdown()
403  * to stop the run. Also closes and deletes shared memory.
404  */
405 void
406 filebench_shutdown(int error) {
407 	filebench_log(LOG_DEBUG_IMPL, "Shutdown");
408 	(void) unlink("/tmp/filebench_shm");
409 	if (filebench_shm->allrunning)
410 		procflow_shutdown();
411 	filebench_shm->f_abort = 1;
412 	ipc_ismdelete();
413 	exit(error);
414 }
415 
416 /*
417  * Put the hostname in ${hostname}. The system supplied
418  * host name string is copied into an allocated string and
419  * the pointer to the string is placed in the supplied
420  * variable "var". If var->var_string already points to
421  * a string, the string is freed. The routine always
422  * returns zero (0).
423  */
424 var_t *
425 host_var(var_t *var)
426 {
427 	char hoststr[128];
428 
429 	(void) gethostname(hoststr, 128);
430 	if (var->var_string)
431 		free(var->var_string);
432 	var->var_string = fb_stralloc(hoststr);
433 	return (0);
434 }
435 
436 /*
437  * Put the date string in ${date}. The system supplied date is
438  * copied into an allocated string and the pointer to the string
439  * is placed in the supplied var_t's var_string. If
440  * var->var_string already points to a string, the string
441  * is freed. The routine always returns a pointer to the
442  * supplied var_t.
443  */
444 var_t *
445 date_var(var_t *var)
446 {
447 	char datestr[128];
448 #ifdef HAVE_CFTIME
449 	time_t t = time(NULL);
450 #else
451 	struct tm t;
452 #endif
453 
454 #ifdef HAVE_CFTIME
455 	cftime(datestr, "%y%m%d%H" "%M", &t);
456 #else
457 	(void) strftime(datestr, sizeof (datestr), "%y%m%d%H %M", &t);
458 #endif
459 
460 	if (var->var_string)
461 		free(var->var_string);
462 	var->var_string = fb_stralloc(datestr);
463 
464 	return (var);
465 }
466 
467 extern char *fscriptname;
468 
469 /*
470  * Put the script name in ${script}. The path name of the script
471  * used with this filebench run trimmed of the trailing ".f" and
472  * all leading subdirectories. The remaining script name is
473  * copied into the var_string field of the supplied variable
474  * "var". The routine always returns a pointer to the supplied
475  * var_t.
476  */
477 var_t *
478 script_var(var_t *var)
479 {
480 	char *scriptstr;
481 	char *f = fb_stralloc(fscriptname);
482 
483 	/* Trim the .f suffix */
484 	for (scriptstr = f + strlen(f) - 1; scriptstr != f; scriptstr--) {
485 		if (*scriptstr == '.') {
486 			*scriptstr = 0;
487 			break;
488 		}
489 	}
490 
491 	var->var_string = fb_stralloc(basename(f));
492 	free(f);
493 
494 	return (var);
495 }
496