xref: /onnv-gate/usr/src/cmd/filebench/common/misc.c (revision 9801:4a9784073e11)
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
rdtsc(void)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
gethrtime(void)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
parse_cpu_hz(void)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
gethrtime(void)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
filebench_init(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  */
filebench_log(int level,const char * fmt,...)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
filebench_shutdown(int error)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 *
host_var(var_t * var)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 *
date_var(var_t * var)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 *
script_var(var_t * var)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
filebench_plugin_funcvecinit(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 }
491