Update docs to clarify how to pass job options in client mode
[fio.git] / diskutil.c
1 #include <inttypes.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <sys/sysmacros.h>
7 #include <dirent.h>
8 #include <libgen.h>
9 #ifdef CONFIG_VALGRIND_DEV
10 #include <valgrind/drd.h>
11 #else
12 #define DRD_IGNORE_VAR(x) do { } while (0)
13 #endif
14
15 #include "fio.h"
16 #include "smalloc.h"
17 #include "diskutil.h"
18 #include "helper_thread.h"
19
20 static int last_majdev, last_mindev;
21 static struct disk_util *last_du;
22
23 static struct fio_sem *disk_util_sem;
24
25 static struct disk_util *__init_per_file_disk_util(struct thread_data *td,
26                 int majdev, int mindev, char *path);
27
28 static void disk_util_free(struct disk_util *du)
29 {
30         if (du == last_du)
31                 last_du = NULL;
32
33         while (!flist_empty(&du->slaves)) {
34                 struct disk_util *slave;
35
36                 slave = flist_first_entry(&du->slaves, struct disk_util, slavelist);
37                 flist_del(&slave->slavelist);
38                 slave->users--;
39         }
40
41         fio_sem_remove(du->lock);
42         free(du->sysfs_root);
43         sfree(du);
44 }
45
46 static int get_io_ticks(struct disk_util *du, struct disk_util_stat *dus)
47 {
48         char line[256];
49         FILE *f;
50         char *p;
51         int ret;
52
53         dprint(FD_DISKUTIL, "open stat file: %s\n", du->path);
54
55         f = fopen(du->path, "r");
56         if (!f)
57                 return 1;
58
59         p = fgets(line, sizeof(line), f);
60         if (!p) {
61                 fclose(f);
62                 return 1;
63         }
64
65         dprint(FD_DISKUTIL, "%s: %s", du->path, p);
66
67         ret = sscanf(p, "%"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" "
68                      "%"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" "
69                      "%*u %"SCNu64" %"SCNu64"\n",
70                      &dus->s.ios[0], &dus->s.merges[0], &dus->s.sectors[0],
71                      &dus->s.ticks[0],
72                      &dus->s.ios[1], &dus->s.merges[1], &dus->s.sectors[1],
73                      &dus->s.ticks[1],
74                      &dus->s.io_ticks, &dus->s.time_in_queue);
75         fclose(f);
76         dprint(FD_DISKUTIL, "%s: stat read ok? %d\n", du->path, ret == 10);
77         return ret != 10;
78 }
79
80 static void update_io_tick_disk(struct disk_util *du)
81 {
82         struct disk_util_stat __dus, *dus, *ldus;
83         struct timespec t;
84
85         if (!du->users)
86                 return;
87         if (get_io_ticks(du, &__dus))
88                 return;
89
90         dus = &du->dus;
91         ldus = &du->last_dus;
92
93         dus->s.sectors[0] += (__dus.s.sectors[0] - ldus->s.sectors[0]);
94         dus->s.sectors[1] += (__dus.s.sectors[1] - ldus->s.sectors[1]);
95         dus->s.ios[0] += (__dus.s.ios[0] - ldus->s.ios[0]);
96         dus->s.ios[1] += (__dus.s.ios[1] - ldus->s.ios[1]);
97         dus->s.merges[0] += (__dus.s.merges[0] - ldus->s.merges[0]);
98         dus->s.merges[1] += (__dus.s.merges[1] - ldus->s.merges[1]);
99         dus->s.ticks[0] += (__dus.s.ticks[0] - ldus->s.ticks[0]);
100         dus->s.ticks[1] += (__dus.s.ticks[1] - ldus->s.ticks[1]);
101         dus->s.io_ticks += (__dus.s.io_ticks - ldus->s.io_ticks);
102         dus->s.time_in_queue += (__dus.s.time_in_queue - ldus->s.time_in_queue);
103
104         fio_gettime(&t, NULL);
105         dus->s.msec += mtime_since(&du->time, &t);
106         memcpy(&du->time, &t, sizeof(t));
107         memcpy(&ldus->s, &__dus.s, sizeof(__dus.s));
108 }
109
110 int update_io_ticks(void)
111 {
112         struct flist_head *entry;
113         struct disk_util *du;
114         int ret = 0;
115
116         dprint(FD_DISKUTIL, "update io ticks\n");
117
118         fio_sem_down(disk_util_sem);
119
120         if (!helper_should_exit()) {
121                 flist_for_each(entry, &disk_list) {
122                         du = flist_entry(entry, struct disk_util, list);
123                         update_io_tick_disk(du);
124                 }
125         } else
126                 ret = 1;
127
128         fio_sem_up(disk_util_sem);
129         return ret;
130 }
131
132 static struct disk_util *disk_util_exists(int major, int minor)
133 {
134         struct flist_head *entry;
135         struct disk_util *du;
136
137         fio_sem_down(disk_util_sem);
138
139         flist_for_each(entry, &disk_list) {
140                 du = flist_entry(entry, struct disk_util, list);
141
142                 if (major == du->major && minor == du->minor) {
143                         fio_sem_up(disk_util_sem);
144                         return du;
145                 }
146         }
147
148         fio_sem_up(disk_util_sem);
149         return NULL;
150 }
151
152 static int get_device_numbers(char *file_name, int *maj, int *min)
153 {
154         struct stat st;
155         int majdev, mindev;
156         char tempname[PATH_MAX], *p;
157
158         if (!lstat(file_name, &st)) {
159                 if (S_ISBLK(st.st_mode)) {
160                         majdev = major(st.st_rdev);
161                         mindev = minor(st.st_rdev);
162                 } else if (S_ISCHR(st.st_mode) ||
163                            S_ISFIFO(st.st_mode)) {
164                         return -1;
165                 } else {
166                         majdev = major(st.st_dev);
167                         mindev = minor(st.st_dev);
168                 }
169         } else {
170                 /*
171                  * must be a file, open "." in that path
172                  */
173                 snprintf(tempname, FIO_ARRAY_SIZE(tempname), "%s", file_name);
174                 p = dirname(tempname);
175                 if (stat(p, &st)) {
176                         perror("disk util stat");
177                         return -1;
178                 }
179
180                 majdev = major(st.st_dev);
181                 mindev = minor(st.st_dev);
182         }
183
184         *min = mindev;
185         *maj = majdev;
186
187         return 0;
188 }
189
190 static int read_block_dev_entry(char *path, int *maj, int *min)
191 {
192         char line[256], *p;
193         FILE *f;
194
195         f = fopen(path, "r");
196         if (!f) {
197                 perror("open path");
198                 return 1;
199         }
200
201         p = fgets(line, sizeof(line), f);
202         fclose(f);
203
204         if (!p)
205                 return 1;
206
207         if (sscanf(p, "%u:%u", maj, min) != 2)
208                 return 1;
209
210         return 0;
211 }
212
213 static void find_add_disk_slaves(struct thread_data *td, char *path,
214                                  struct disk_util *masterdu)
215 {
216         DIR *dirhandle = NULL;
217         struct dirent *dirent = NULL;
218         char slavesdir[PATH_MAX], temppath[PATH_MAX], slavepath[PATH_MAX];
219         struct disk_util *slavedu = NULL;
220         int majdev, mindev;
221         ssize_t linklen;
222
223         sprintf(slavesdir, "%s/%s", path, "slaves");
224         dirhandle = opendir(slavesdir);
225         if (!dirhandle)
226                 return;
227
228         while ((dirent = readdir(dirhandle)) != NULL) {
229                 if (!strcmp(dirent->d_name, ".") ||
230                     !strcmp(dirent->d_name, ".."))
231                         continue;
232
233                 nowarn_snprintf(temppath, sizeof(temppath), "%s/%s", slavesdir,
234                                 dirent->d_name);
235                 /* Can we always assume that the slaves device entries
236                  * are links to the real directories for the slave
237                  * devices?
238                  */
239                 linklen = readlink(temppath, slavepath, PATH_MAX - 1);
240                 if (linklen < 0) {
241                         perror("readlink() for slave device.");
242                         closedir(dirhandle);
243                         return;
244                 }
245                 slavepath[linklen] = '\0';
246
247                 nowarn_snprintf(temppath, sizeof(temppath), "%s/%s/dev",
248                                 slavesdir, slavepath);
249                 if (access(temppath, F_OK) != 0)
250                         nowarn_snprintf(temppath, sizeof(temppath),
251                                         "%s/%s/device/dev", slavesdir,
252                                         slavepath);
253                 if (read_block_dev_entry(temppath, &majdev, &mindev)) {
254                         perror("Error getting slave device numbers");
255                         closedir(dirhandle);
256                         return;
257                 }
258
259                 /*
260                  * See if this maj,min already exists
261                  */
262                 slavedu = disk_util_exists(majdev, mindev);
263                 if (slavedu)
264                         continue;
265
266                 nowarn_snprintf(temppath, sizeof(temppath), "%s/%s", slavesdir,
267                                 slavepath);
268                 __init_per_file_disk_util(td, majdev, mindev, temppath);
269                 slavedu = disk_util_exists(majdev, mindev);
270
271                 /* Should probably use an assert here. slavedu should
272                  * always be present at this point. */
273                 if (slavedu) {
274                         slavedu->users++;
275                         flist_add_tail(&slavedu->slavelist, &masterdu->slaves);
276                 }
277         }
278
279         closedir(dirhandle);
280 }
281
282 static struct disk_util *disk_util_add(struct thread_data *td, int majdev,
283                                        int mindev, char *path)
284 {
285         struct disk_util *du, *__du;
286         struct flist_head *entry;
287         int l;
288
289         dprint(FD_DISKUTIL, "add maj/min %d/%d: %s\n", majdev, mindev, path);
290
291         du = smalloc(sizeof(*du));
292         if (!du)
293                 return NULL;
294
295         DRD_IGNORE_VAR(du->users);
296         memset(du, 0, sizeof(*du));
297         INIT_FLIST_HEAD(&du->list);
298         l = snprintf(du->path, sizeof(du->path), "%s/stat", path);
299         if (l < 0 || l >= sizeof(du->path)) {
300                 log_err("constructed path \"%.100s[...]/stat\" larger than buffer (%zu bytes)\n",
301                         path, sizeof(du->path) - 1);
302                 sfree(du);
303                 return NULL;
304         }
305         snprintf((char *) du->dus.name, FIO_ARRAY_SIZE(du->dus.name), "%s",
306                  basename(path));
307         du->sysfs_root = strdup(path);
308         du->major = majdev;
309         du->minor = mindev;
310         INIT_FLIST_HEAD(&du->slavelist);
311         INIT_FLIST_HEAD(&du->slaves);
312         du->lock = fio_sem_init(FIO_SEM_UNLOCKED);
313         du->users = 0;
314
315         fio_sem_down(disk_util_sem);
316
317         flist_for_each(entry, &disk_list) {
318                 __du = flist_entry(entry, struct disk_util, list);
319
320                 dprint(FD_DISKUTIL, "found %s in list\n", __du->dus.name);
321
322                 if (!strcmp((char *) du->dus.name, (char *) __du->dus.name)) {
323                         disk_util_free(du);
324                         fio_sem_up(disk_util_sem);
325                         return __du;
326                 }
327         }
328
329         dprint(FD_DISKUTIL, "add %s to list\n", du->dus.name);
330
331         fio_gettime(&du->time, NULL);
332         get_io_ticks(du, &du->last_dus);
333
334         flist_add_tail(&du->list, &disk_list);
335         fio_sem_up(disk_util_sem);
336
337         find_add_disk_slaves(td, path, du);
338         return du;
339 }
340
341 static int check_dev_match(int majdev, int mindev, char *path)
342 {
343         int major, minor;
344
345         if (read_block_dev_entry(path, &major, &minor))
346                 return 1;
347
348         if (majdev == major && mindev == minor)
349                 return 0;
350
351         return 1;
352 }
353
354 static int find_block_dir(int majdev, int mindev, char *path, int link_ok)
355 {
356         struct dirent *dir;
357         struct stat st;
358         int found = 0;
359         DIR *D;
360
361         D = opendir(path);
362         if (!D)
363                 return 0;
364
365         while ((dir = readdir(D)) != NULL) {
366                 char full_path[257];
367
368                 if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, ".."))
369                         continue;
370
371                 sprintf(full_path, "%s/%s", path, dir->d_name);
372
373                 if (!strcmp(dir->d_name, "dev")) {
374                         if (!check_dev_match(majdev, mindev, full_path)) {
375                                 found = 1;
376                                 break;
377                         }
378                 }
379
380                 if (link_ok) {
381                         if (stat(full_path, &st) == -1) {
382                                 perror("stat");
383                                 break;
384                         }
385                 } else {
386                         if (lstat(full_path, &st) == -1) {
387                                 perror("stat");
388                                 break;
389                         }
390                 }
391
392                 if (!S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))
393                         continue;
394
395                 found = find_block_dir(majdev, mindev, full_path, 0);
396                 if (found) {
397                         strcpy(path, full_path);
398                         break;
399                 }
400         }
401
402         closedir(D);
403         return found;
404 }
405
406 static struct disk_util *__init_per_file_disk_util(struct thread_data *td,
407                                                    int majdev, int mindev,
408                                                    char *path)
409 {
410         struct stat st;
411         char tmp[PATH_MAX];
412         char *p;
413
414         /*
415          * If there's a ../queue/ directory there, we are inside a partition.
416          * Check if that is the case and jump back. For loop/md/dm etc we
417          * are already in the right spot.
418          */
419         sprintf(tmp, "%s/../queue", path);
420         if (!stat(tmp, &st)) {
421                 p = dirname(path);
422                 sprintf(tmp, "%s/queue", p);
423                 if (stat(tmp, &st)) {
424                         log_err("unknown sysfs layout\n");
425                         return NULL;
426                 }
427                 snprintf(tmp, FIO_ARRAY_SIZE(tmp), "%s", p);
428                 sprintf(path, "%s", tmp);
429         }
430
431         return disk_util_add(td, majdev, mindev, path);
432 }
433
434 static struct disk_util *init_per_file_disk_util(struct thread_data *td,
435                                                  char *filename)
436 {
437
438         char foo[PATH_MAX];
439         struct disk_util *du;
440         int mindev, majdev;
441
442         if (get_device_numbers(filename, &majdev, &mindev))
443                 return NULL;
444
445         dprint(FD_DISKUTIL, "%s belongs to maj/min %d/%d\n", filename, majdev,
446                         mindev);
447
448         du = disk_util_exists(majdev, mindev);
449         if (du)
450                 return du;
451
452         /*
453          * for an fs without a device, we will repeatedly stat through
454          * sysfs which can take oodles of time for thousands of files. so
455          * cache the last lookup and compare with that before going through
456          * everything again.
457          */
458         if (mindev == last_mindev && majdev == last_majdev)
459                 return last_du;
460
461         last_mindev = mindev;
462         last_majdev = majdev;
463
464         sprintf(foo, "/sys/block");
465         if (!find_block_dir(majdev, mindev, foo, 1))
466                 return NULL;
467
468         return __init_per_file_disk_util(td, majdev, mindev, foo);
469 }
470
471 static struct disk_util *__init_disk_util(struct thread_data *td,
472                                           struct fio_file *f)
473 {
474         return init_per_file_disk_util(td, f->file_name);
475 }
476
477 void init_disk_util(struct thread_data *td)
478 {
479         struct fio_file *f;
480         unsigned int i;
481
482         if (!td->o.do_disk_util ||
483             td_ioengine_flagged(td, FIO_DISKLESSIO | FIO_NODISKUTIL))
484                 return;
485
486         for_each_file(td, f, i)
487                 f->du = __init_disk_util(td, f);
488 }
489
490 void disk_util_prune_entries(void)
491 {
492         fio_sem_down(disk_util_sem);
493
494         while (!flist_empty(&disk_list)) {
495                 struct disk_util *du;
496
497                 du = flist_first_entry(&disk_list, struct disk_util, list);
498                 flist_del(&du->list);
499                 disk_util_free(du);
500         }
501
502         last_majdev = last_mindev = -1;
503         fio_sem_up(disk_util_sem);
504         fio_sem_remove(disk_util_sem);
505 }
506
507 void setup_disk_util(void)
508 {
509         disk_util_sem = fio_sem_init(FIO_SEM_UNLOCKED);
510 }