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