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