stat: make add lat percentile functions inline
[fio.git] / engines / exec.c
1 /*
2  * Exec engine
3  *
4  * Doesn't transfer any data, merely run 3rd party tools
5  *
6  */
7 #include "../fio.h"
8 #include "../optgroup.h"
9 #include <signal.h>
10
11 struct exec_options {
12         void *pad;
13         char *program;
14         char *arguments;
15         int grace_time;
16         unsigned int std_redirect;
17         pid_t pid;
18 };
19
20 static struct fio_option options[] = {
21         {
22                 .name = "program",
23                 .lname = "Program",
24                 .type = FIO_OPT_STR_STORE,
25                 .off1 = offsetof(struct exec_options, program),
26                 .help = "Program to execute",
27                 .category = FIO_OPT_C_ENGINE,
28                 .group = FIO_OPT_G_INVALID,
29         },
30         {
31                 .name = "arguments",
32                 .lname = "Arguments",
33                 .type = FIO_OPT_STR_STORE,
34                 .off1 = offsetof(struct exec_options, arguments),
35                 .help = "Arguments to pass",
36                 .category = FIO_OPT_C_ENGINE,
37                 .group = FIO_OPT_G_INVALID,
38         },
39         {
40                 .name = "grace_time",
41                 .lname = "Grace time",
42                 .type = FIO_OPT_INT,
43                 .minval = 0,
44                 .def = "1",
45                 .off1 = offsetof(struct exec_options, grace_time),
46                 .help = "Grace time before sending a SIGKILL",
47                 .category = FIO_OPT_C_ENGINE,
48                 .group = FIO_OPT_G_INVALID,
49         },
50         {
51                 .name = "std_redirect",
52                 .lname = "Std redirect",
53                 .type = FIO_OPT_BOOL,
54                 .def = "1",
55                 .off1 = offsetof(struct exec_options, std_redirect),
56                 .help = "Redirect stdout & stderr to files",
57                 .category = FIO_OPT_C_ENGINE,
58                 .group = FIO_OPT_G_INVALID,
59         },
60         {
61                 .name = NULL,
62         },
63 };
64
65 char *str_replace(char *orig, const char *rep, const char *with)
66 {
67         /*
68          * Replace a substring by another.
69          *
70          * Returns the new string if occurences were found
71          * Returns orig if no occurence is found
72          */
73         char *result, *insert, *tmp;
74         int len_rep, len_with, len_front, count;
75
76         /* sanity checks and initialization */
77         if (!orig || !rep)
78                 return orig;
79
80         len_rep = strlen(rep);
81         if (len_rep == 0)
82                 return orig;
83
84         if (!with)
85                 with = "";
86         len_with = strlen(with);
87
88         insert = orig;
89         for (count = 0; (tmp = strstr(insert, rep)); ++count) {
90                 insert = tmp + len_rep;
91         }
92
93         tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);
94
95         if (!result)
96                 return orig;
97
98         while (count--) {
99                 insert = strstr(orig, rep);
100                 len_front = insert - orig;
101                 tmp = strncpy(tmp, orig, len_front) + len_front;
102                 tmp = strcpy(tmp, with) + len_with;
103                 orig += len_front + len_rep;
104         }
105         strcpy(tmp, orig);
106         return result;
107 }
108
109 char *expand_variables(struct thread_options *o, char *arguments)
110 {
111         char str[16];
112         char *expanded_runtime, *expanded_name;
113         snprintf(str, sizeof(str), "%lld", o->timeout / 1000000);
114
115         /* %r is replaced by the runtime in seconds */
116         expanded_runtime = str_replace(arguments, "%r", str);
117
118         /* %n is replaced by the name of the running job */
119         expanded_name = str_replace(expanded_runtime, "%n", o->name);
120
121         free(expanded_runtime);
122         return expanded_name;
123 }
124
125 static int exec_background(struct thread_options *o, struct exec_options *eo)
126 {
127         char *outfilename = NULL, *errfilename = NULL;
128         int outfd = 0, errfd = 0;
129         pid_t pid;
130         char *expanded_arguments = NULL;
131         /* For the arguments splitting */
132         char **arguments_array = NULL;
133         char *p;
134         char *exec_cmd = NULL;
135         size_t arguments_nb_items = 0, q;
136
137         if (asprintf(&outfilename, "%s.stdout", o->name) < 0)
138                 return -1;
139
140         if (asprintf(&errfilename, "%s.stderr", o->name) < 0) {
141                 free(outfilename);
142                 return -1;
143         }
144
145         /* If we have variables in the arguments, let's expand them */
146         expanded_arguments = expand_variables(o, eo->arguments);
147
148         if (eo->std_redirect) {
149                 log_info("%s : Saving output of %s %s : stdout=%s stderr=%s\n",
150                          o->name, eo->program, expanded_arguments, outfilename,
151                          errfilename);
152
153                 /* Creating the stderr & stdout output files */
154                 outfd = open(outfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
155                 if (outfd < 0) {
156                         log_err("fio: cannot open output file %s : %s\n",
157                                 outfilename, strerror(errno));
158                         free(outfilename);
159                         free(errfilename);
160                         free(expanded_arguments);
161                         return -1;
162                 }
163
164                 errfd = open(errfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
165                 if (errfd < 0) {
166                         log_err("fio: cannot open output file %s : %s\n",
167                                 errfilename, strerror(errno));
168                         free(outfilename);
169                         free(errfilename);
170                         free(expanded_arguments);
171                         close(outfd);
172                         return -1;
173                 }
174         } else {
175                 log_info("%s : Running %s %s\n",
176                          o->name, eo->program, expanded_arguments);
177         }
178
179         pid = fork();
180
181         /* We are on the control thread (parent side of the fork */
182         if (pid > 0) {
183                 eo->pid = pid;
184                 if (eo->std_redirect) {
185                         /* The output file is for the client side of the fork */
186                         close(outfd);
187                         close(errfd);
188                         free(outfilename);
189                         free(errfilename);
190                 }
191                 free(expanded_arguments);
192                 return 0;
193         }
194
195         /* If the fork failed */
196         if (pid < 0) {
197                 log_err("fio: forking failed %s \n", strerror(errno));
198                 if (eo->std_redirect) {
199                         close(outfd);
200                         close(errfd);
201                         free(outfilename);
202                         free(errfilename);
203                 }
204                 free(expanded_arguments);
205                 return -1;
206         }
207
208         /* We are in the worker (child side of the fork) */
209         if (pid == 0) {
210                 if (eo->std_redirect) {
211                         /* replace stdout by the output file we create */
212                         dup2(outfd, 1);
213                         /* replace stderr by the output file we create */
214                         dup2(errfd, 2);
215                         close(outfd);
216                         close(errfd);
217                         free(outfilename);
218                         free(errfilename);
219                 }
220
221                 /*
222                  * Let's split the command line into a null terminated array to
223                  * be passed to the exec'd program.
224                  * But don't asprintf expanded_arguments if NULL as it would be
225                  * converted to a '(null)' argument, while we want no arguments
226                  * at all.
227                  */
228                 if (expanded_arguments != NULL) {
229                         if (asprintf(&exec_cmd, "%s %s", eo->program, expanded_arguments) < 0) {
230                                 free(expanded_arguments);
231                                 return -1;
232                         }
233                 } else {
234                         if (asprintf(&exec_cmd, "%s", eo->program) < 0)
235                                 return -1;
236                 }
237
238                 /*
239                  * Let's build an argv array to based on the program name and
240                  * arguments
241                  */
242                 p = exec_cmd;
243                 for (;;) {
244                         p += strspn(p, " ");
245
246                         if (!(q = strcspn(p, " ")))
247                                 break;
248
249                         if (q) {
250                                 arguments_array =
251                                     realloc(arguments_array,
252                                             (arguments_nb_items +
253                                              1) * sizeof(char *));
254                                 arguments_array[arguments_nb_items] =
255                                     malloc(q + 1);
256                                 strncpy(arguments_array[arguments_nb_items], p,
257                                         q);
258                                 arguments_array[arguments_nb_items][q] = 0;
259                                 arguments_nb_items++;
260                                 p += q;
261                         }
262                 }
263
264                 /* Adding a null-terminated item to close the list */
265                 arguments_array =
266                     realloc(arguments_array,
267                             (arguments_nb_items + 1) * sizeof(char *));
268                 arguments_array[arguments_nb_items] = NULL;
269
270                 /*
271                  * Replace the fio program from the child fork by the target
272                  * program
273                  */
274                 execvp(arguments_array[0], arguments_array);
275         }
276         /* We never reach this place */
277         /* Let's free the malloc'ed structures to make static checkers happy */
278         if (expanded_arguments)
279                 free(expanded_arguments);
280         if (arguments_array)
281                 free(arguments_array);
282         return 0;
283 }
284
285 static enum fio_q_status
286 fio_exec_queue(struct thread_data *td, struct io_u fio_unused * io_u)
287 {
288         struct thread_options *o = &td->o;
289         struct exec_options *eo = td->eo;
290
291         /* Let's execute the program the first time we get queued */
292         if (eo->pid == -1) {
293                 exec_background(o, eo);
294         } else {
295                 /*
296                  * The program is running in background, let's check on a
297                  * regular basis
298                  * if the time is over and if we need to stop the tool
299                  */
300                 usleep(o->thinktime);
301                 if (utime_since_now(&td->start) > o->timeout) {
302                         /* Let's stop the child */
303                         kill(eo->pid, SIGTERM);
304                         /*
305                          * Let's give grace_time (1 sec by default) to the 3rd
306                          * party tool to stop
307                          */
308                         sleep(eo->grace_time);
309                 }
310         }
311
312         return FIO_Q_COMPLETED;
313 }
314
315 static int fio_exec_init(struct thread_data *td)
316 {
317         struct thread_options *o = &td->o;
318         struct exec_options *eo = td->eo;
319         int td_previous_state;
320
321         eo->pid = -1;
322
323         if (!eo->program) {
324                 td_vmsg(td, EINVAL,
325                         "no program is defined, it is mandatory to define one",
326                         "exec");
327                 return 1;
328         }
329
330         log_info("%s : program=%s, arguments=%s\n",
331                  td->o.name, eo->program, eo->arguments);
332
333         /* Saving the current thread state */
334         td_previous_state = td->runstate;
335
336         /*
337          * Reporting that we are preparing the engine
338          * This is useful as the qsort() calibration takes time
339          * This prevents the job from starting before init is completed
340          */
341         td_set_runstate(td, TD_SETTING_UP);
342
343         /*
344          * set thinktime_sleep and thinktime_spin appropriately
345          */
346         o->thinktime_blocks = 1;
347         o->thinktime_blocks_type = THINKTIME_BLOCKS_TYPE_COMPLETE;
348         o->thinktime_spin = 0;
349         /* 50ms pause when waiting for the program to complete */
350         o->thinktime = 50000;
351
352         o->nr_files = o->open_files = 1;
353
354         /* Let's restore the previous state. */
355         td_set_runstate(td, td_previous_state);
356         return 0;
357 }
358
359 static void fio_exec_cleanup(struct thread_data *td)
360 {
361         struct exec_options *eo = td->eo;
362         /* Send a sigkill to ensure the job is well terminated */
363         if (eo->pid > 0)
364                 kill(eo->pid, SIGKILL);
365 }
366
367 static int
368 fio_exec_open(struct thread_data fio_unused * td,
369               struct fio_file fio_unused * f)
370 {
371         return 0;
372 }
373
374 static struct ioengine_ops ioengine = {
375         .name = "exec",
376         .version = FIO_IOOPS_VERSION,
377         .queue = fio_exec_queue,
378         .init = fio_exec_init,
379         .cleanup = fio_exec_cleanup,
380         .open_file = fio_exec_open,
381         .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NOIO,
382         .options = options,
383         .option_struct_size = sizeof(struct exec_options),
384 };
385
386 static void fio_init fio_exec_register(void)
387 {
388         register_ioengine(&ioengine);
389 }
390
391 static void fio_exit fio_exec_unregister(void)
392 {
393         unregister_ioengine(&ioengine);
394 }