libmultipath: fix memory issue in path_latency prio
[multipath-tools/.git] / libmultipath / prioritizers / path_latency.c
1 /*
2  * (C) Copyright HUAWEI Technology Corp. 2017, All Rights Reserved.
3  *
4  * path_latency.c
5  *
6  * Prioritizer for device mapper multipath, where the corresponding priority
7  * values of specific paths are provided by a latency algorithm. And the
8  * latency algorithm is dependent on arguments("io_num" and "base_num").
9  *
10  * The principle of the algorithm as follows:
11  * 1. By sending a certain number "io_num" of read IOs to the current path
12  *    continuously, the IOs' average latency can be calculated.
13  * 2. Max value and min value of average latency are constant. According to
14  *    the average latency of each path and the "base_num" of logarithmic
15  *    scale, the priority "rc" of each path can be provided.
16  *
17  * Author(s): Yang Feng <philip.yang@huawei.com>
18  * Revised:   Guan Junxiong <guanjunxiong@huawei.com>
19  *
20  * This file is released under the GPL version 2, or any later version.
21  */
22
23 #define _GNU_SOURCE
24 #include <stdio.h>
25 #include <math.h>
26 #include <ctype.h>
27 #include <time.h>
28 #include <fcntl.h>
29 #include <sys/ioctl.h>
30 #include <linux/fs.h>
31 #include <unistd.h>
32
33 #include "debug.h"
34 #include "prio.h"
35 #include "structs.h"
36 #include "util.h"
37 #include "time-util.h"
38
39 #define pp_pl_log(prio, fmt, args...) condlog(prio, "path_latency prio: " fmt, ##args)
40
41 #define MAX_IO_NUM              200
42 #define MIN_IO_NUM              20
43 #define DEF_IO_NUM              100
44
45 #define MAX_BASE_NUM            10
46 #define MIN_BASE_NUM            1.1
47 // This is 10**(1/4). 4 prio steps correspond to a factor of 10.
48 #define DEF_BASE_NUM            1.77827941004
49
50 #define MAX_AVG_LATENCY         100000000.      /* Unit: us */
51 #define MIN_AVG_LATENCY         1.              /* Unit: us */
52
53 #define DEFAULT_PRIORITY        0
54
55 #define USEC_PER_SEC            1000000LL
56 #define NSEC_PER_USEC           1000LL
57
58 #define DEF_BLK_SIZE            4096
59
60 static int prepare_directio_read(int fd, int *blksz, char **pbuf,
61                 int *restore_flags)
62 {
63         unsigned long pgsize = getpagesize();
64         long flags;
65
66         if (ioctl(fd, BLKBSZGET, blksz) < 0) {
67                 pp_pl_log(3,"catnnot get blocksize, set default");
68                 *blksz = DEF_BLK_SIZE;
69         }
70         if (posix_memalign((void **)pbuf, pgsize, *blksz))
71                 return -1;
72
73         flags = fcntl(fd, F_GETFL);
74         if (flags < 0)
75                 goto free_out;
76         if (!(flags & O_DIRECT)) {
77                 flags |= O_DIRECT;
78                 if (fcntl(fd, F_SETFL, flags) < 0)
79                         goto free_out;
80                 *restore_flags = 1;
81         }
82
83         return 0;
84
85 free_out:
86         free(*pbuf);
87
88         return -1;
89 }
90
91 static void cleanup_directio_read(int fd, char *buf, int restore_flags)
92 {
93         long flags;
94
95         free(buf);
96
97         if (!restore_flags)
98                 return;
99         if ((flags = fcntl(fd, F_GETFL)) >= 0) {
100                 int ret __attribute__ ((unused));
101                 flags &= ~O_DIRECT;
102                 /* No point in checking for errors */
103                 ret = fcntl(fd, F_SETFL, flags);
104         }
105 }
106
107 static int do_directio_read(int fd, unsigned int timeout, char *buf, int sz)
108 {
109         fd_set read_fds;
110         struct timeval tm = { .tv_sec = timeout };
111         int ret;
112         int num_read;
113
114         if (lseek(fd, 0, SEEK_SET) == -1)
115                 return -1;
116         FD_ZERO(&read_fds);
117         FD_SET(fd, &read_fds);
118         ret = select(fd+1, &read_fds, NULL, NULL, &tm);
119         if (ret <= 0)
120                 return -1;
121         num_read = read(fd, buf, sz);
122         if (num_read != sz)
123                 return -1;
124
125         return 0;
126 }
127
128 int check_args_valid(int io_num, double base_num)
129 {
130         if ((io_num < MIN_IO_NUM) || (io_num > MAX_IO_NUM)) {
131                 pp_pl_log(0, "args io_num is outside the valid range");
132                 return 0;
133         }
134
135         if ((base_num < MIN_BASE_NUM) || (base_num > MAX_BASE_NUM)) {
136                 pp_pl_log(0, "args base_num is outside the valid range");
137                 return 0;
138         }
139
140         return 1;
141 }
142
143 /*
144  * In multipath.conf, args form: io_num=n base_num=m. For example, args are
145  * "io_num=20 base_num=10", this function can get io_num value 20 and
146  * base_num value 10.
147  */
148 static int get_ionum_and_basenum(char *args, int *ionum, double *basenum)
149 {
150         char split_char[] = " \t";
151         char *arg, *temp;
152         char *str, *str_inval;
153         int i;
154         int flag_io = 0, flag_base = 0;
155
156         if ((args == NULL) || (ionum == NULL) || (basenum == NULL)) {
157                 pp_pl_log(0, "args string is NULL");
158                 return 0;
159         }
160
161         arg = temp = STRDUP(args);
162         if (!arg)
163                 return 0;
164
165         for (i = 0; i < 2; i++) {
166                 str = get_next_string(&temp, split_char);
167                 if (!str)
168                         goto out;
169                 if (!strncmp(str, "io_num=", 7) && strlen(str) > 7) {
170                         *ionum = (int)strtoul(str + 7, &str_inval, 10);
171                         if (str == str_inval)
172                                 goto out;
173                         flag_io = 1;
174                 }
175                 else if (!strncmp(str, "base_num=", 9) && strlen(str) > 9) {
176                         *basenum = strtod(str + 9, &str_inval);
177                         if (str == str_inval)
178                                 goto out;
179                         flag_base = 1;
180                 }
181         }
182
183         if (!flag_io || !flag_base)
184                 goto out;
185         if (check_args_valid(*ionum, *basenum) == 0)
186                 goto out;
187
188         FREE(arg);
189         return 1;
190 out:
191         FREE(arg);
192         return 0;
193 }
194
195 /*
196  * Do not scale the prioriy in a certain range such as [0, 1024]
197  * because scaling will eliminate the effect of base_num.
198  */
199 int calcPrio(double lg_avglatency, double lg_maxavglatency,
200                 double lg_minavglatency)
201 {
202         if (lg_avglatency <= lg_minavglatency)
203                 return lg_maxavglatency - lg_minavglatency;
204
205         if (lg_avglatency >= lg_maxavglatency)
206                 return 0;
207
208         return lg_maxavglatency - lg_avglatency;
209 }
210
211 int getprio(struct path *pp, char *args, unsigned int timeout)
212 {
213         int rc, temp;
214         int io_num = 0;
215         double base_num = 0;
216         double lg_avglatency, lg_maxavglatency, lg_minavglatency;
217         double standard_deviation;
218         double lg_toldelay = 0;
219         int blksize;
220         char *buf;
221         int restore_flags = 0;
222         double lg_base;
223         double sum_squares = 0;
224
225         if (pp->fd < 0)
226                 return -1;
227
228         if (get_ionum_and_basenum(args, &io_num, &base_num) == 0) {
229                 io_num = DEF_IO_NUM;
230                 base_num = DEF_BASE_NUM;
231                 pp_pl_log(0, "%s: fails to get path_latency args, set default:"
232                                 "io_num=%d base_num=%.3lf",
233                                 pp->dev, io_num, base_num);
234         }
235
236         lg_base = log(base_num);
237         lg_maxavglatency = log(MAX_AVG_LATENCY) / lg_base;
238         lg_minavglatency = log(MIN_AVG_LATENCY) / lg_base;
239
240         if (prepare_directio_read(pp->fd, &blksize, &buf, &restore_flags) < 0)
241                 return PRIO_UNDEF;
242
243         temp = io_num;
244         while (temp-- > 0) {
245                 struct timespec tv_before, tv_after, tv_diff;
246                 double diff, reldiff;
247
248                 (void)clock_gettime(CLOCK_MONOTONIC, &tv_before);
249
250                 if (do_directio_read(pp->fd, timeout, buf, blksize)) {
251                         pp_pl_log(0, "%s: path down", pp->dev);
252                         cleanup_directio_read(pp->fd, buf, restore_flags);
253                         return -1;
254                 }
255
256                 (void)clock_gettime(CLOCK_MONOTONIC, &tv_after);
257
258                 timespecsub(&tv_after, &tv_before, &tv_diff);
259                 diff = tv_diff.tv_sec * 1000 * 1000 + tv_diff.tv_nsec / 1000;
260
261                 if (diff == 0)
262                         /*
263                          * Avoid taking log(0).
264                          * This unlikely case is treated as minimum -
265                          * the sums don't increase
266                          */
267                         continue;
268
269                 /* we scale by lg_base here */
270                 reldiff = log(diff) / lg_base;
271
272                 /*
273                  * We assume that the latency complies with Log-normal
274                  * distribution. The logarithm of latency is in normal
275                  * distribution.
276                  */
277                 lg_toldelay += reldiff;
278                 sum_squares += reldiff * reldiff;
279         }
280
281         cleanup_directio_read(pp->fd, buf, restore_flags);
282
283         lg_avglatency = lg_toldelay / (long long)io_num;
284
285         if (lg_avglatency > lg_maxavglatency) {
286                 pp_pl_log(2,
287                           "%s: average latency (%lld us) is outside the thresold (%lld us)",
288                           pp->dev, (long long)pow(base_num, lg_avglatency),
289                           (long long)MAX_AVG_LATENCY);
290                 return DEFAULT_PRIORITY;
291         }
292
293         standard_deviation = sqrt((sum_squares - lg_toldelay * lg_avglatency)
294                                   / (io_num - 1));
295
296         rc = calcPrio(lg_avglatency, lg_maxavglatency, lg_minavglatency);
297
298         pp_pl_log(3, "%s: latency avg=%.2e uncertainty=%.1f prio=%d\n",
299                   pp->dev, exp(lg_avglatency * lg_base),
300                   exp(standard_deviation * lg_base), rc);
301
302         return rc;
303 }