libmultipath: path latency: fix default base num
[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
38 #define pp_pl_log(prio, fmt, args...) condlog(prio, "path_latency prio: " fmt, ##args)
39
40 #define MAX_IO_NUM              200
41 #define MIN_IO_NUM              20
42 #define DEF_IO_NUM              100
43
44 #define MAX_BASE_NUM            10
45 #define MIN_BASE_NUM            1.1
46 // This is 10**(1/4). 4 prio steps correspond to a factor of 10.
47 #define DEF_BASE_NUM            1.77827941004
48
49 #define MAX_AVG_LATENCY         100000000.      /* Unit: us */
50 #define MIN_AVG_LATENCY         1.              /* Unit: us */
51
52 #define DEFAULT_PRIORITY        0
53
54 #define USEC_PER_SEC            1000000LL
55 #define NSEC_PER_USEC           1000LL
56
57 #define DEF_BLK_SIZE            4096
58
59 static double lg_path_latency[MAX_IO_NUM];
60
61 static inline long long timeval_to_us(const struct timespec *tv)
62 {
63         return ((long long)tv->tv_sec * USEC_PER_SEC) +
64             (tv->tv_nsec / NSEC_PER_USEC);
65 }
66
67 static int prepare_directio_read(int fd, int *blksz, char **pbuf,
68                 int *restore_flags)
69 {
70         unsigned long pgsize = getpagesize();
71         long flags;
72
73         if (ioctl(fd, BLKBSZGET, blksz) < 0) {
74                 pp_pl_log(3,"catnnot get blocksize, set default");
75                 *blksz = DEF_BLK_SIZE;
76         }
77         if (posix_memalign((void **)pbuf, pgsize, *blksz))
78                 return -1;
79
80         flags = fcntl(fd, F_GETFL);
81         if (flags < 0)
82                 goto free_out;
83         if (!(flags & O_DIRECT)) {
84                 flags |= O_DIRECT;
85                 if (fcntl(fd, F_SETFL, flags) < 0)
86                         goto free_out;
87                 *restore_flags = 1;
88         }
89
90         return 0;
91
92 free_out:
93         free(*pbuf);
94
95         return -1;
96 }
97
98 static void cleanup_directio_read(int fd, char *buf, int restore_flags)
99 {
100         long flags;
101
102         free(buf);
103
104         if (!restore_flags)
105                 return;
106         if ((flags = fcntl(fd, F_GETFL)) >= 0) {
107                 int ret __attribute__ ((unused));
108                 flags &= ~O_DIRECT;
109                 /* No point in checking for errors */
110                 ret = fcntl(fd, F_SETFL, flags);
111         }
112 }
113
114 static int do_directio_read(int fd, unsigned int timeout, char *buf, int sz)
115 {
116         fd_set read_fds;
117         struct timeval tm = { .tv_sec = timeout };
118         int ret;
119         int num_read;
120
121         if (lseek(fd, 0, SEEK_SET) == -1)
122                 return -1;
123         FD_ZERO(&read_fds);
124         FD_SET(fd, &read_fds);
125         ret = select(fd+1, &read_fds, NULL, NULL, &tm);
126         if (ret <= 0)
127                 return -1;
128         num_read = read(fd, buf, sz);
129         if (num_read != sz)
130                 return -1;
131
132         return 0;
133 }
134
135 int check_args_valid(int io_num, double base_num)
136 {
137         if ((io_num < MIN_IO_NUM) || (io_num > MAX_IO_NUM)) {
138                 pp_pl_log(0, "args io_num is outside the valid range");
139                 return 0;
140         }
141
142         if ((base_num < MIN_BASE_NUM) || (base_num > MAX_BASE_NUM)) {
143                 pp_pl_log(0, "args base_num is outside the valid range");
144                 return 0;
145         }
146
147         return 1;
148 }
149
150 /*
151  * In multipath.conf, args form: io_num=n base_num=m. For example, args are
152  * "io_num=20 base_num=10", this function can get io_num value 20 and
153  * base_num value 10.
154  */
155 static int get_ionum_and_basenum(char *args, int *ionum, double *basenum)
156 {
157         char split_char[] = " \t";
158         char *arg, *temp;
159         char *str, *str_inval;
160         int i;
161         int flag_io = 0, flag_base = 0;
162
163         if ((args == NULL) || (ionum == NULL) || (basenum == NULL)) {
164                 pp_pl_log(0, "args string is NULL");
165                 return 0;
166         }
167
168         arg = temp = STRDUP(args);
169         if (!arg)
170                 return 0;
171
172         for (i = 0; i < 2; i++) {
173                 str = get_next_string(&temp, split_char);
174                 if (!str)
175                         goto out;
176                 if (!strncmp(str, "io_num=", 7) && strlen(str) > 7) {
177                         *ionum = (int)strtoul(str + 7, &str_inval, 10);
178                         if (str == str_inval)
179                                 goto out;
180                         flag_io = 1;
181                 }
182                 else if (!strncmp(str, "base_num=", 9) && strlen(str) > 9) {
183                         *basenum = strtod(str + 9, &str_inval);
184                         if (str == str_inval)
185                                 goto out;
186                         flag_base = 1;
187                 }
188         }
189
190         if (!flag_io || !flag_base)
191                 goto out;
192         if (check_args_valid(*ionum, *basenum) == 0)
193                 goto out;
194
195         FREE(arg);
196         return 1;
197 out:
198         FREE(arg);
199         return 0;
200 }
201
202 double calc_standard_deviation(double *lg_path_latency, int size,
203                                   double lg_avglatency)
204 {
205         int index;
206         double sum = 0;
207
208         for (index = 0; index < size; index++) {
209                 sum += (lg_path_latency[index] - lg_avglatency) *
210                         (lg_path_latency[index] - lg_avglatency);
211         }
212
213         sum /= (size - 1);
214
215         return sqrt(sum);
216 }
217
218 /*
219  * Do not scale the prioriy in a certain range such as [0, 1024]
220  * because scaling will eliminate the effect of base_num.
221  */
222 int calcPrio(double lg_avglatency, double lg_maxavglatency,
223                 double lg_minavglatency)
224 {
225         if (lg_avglatency <= lg_minavglatency)
226                 return lg_maxavglatency - lg_minavglatency;
227
228         if (lg_avglatency >= lg_maxavglatency)
229                 return 0;
230
231         return lg_maxavglatency - lg_avglatency;
232 }
233
234 int getprio(struct path *pp, char *args, unsigned int timeout)
235 {
236         int rc, temp;
237         int index = 0;
238         int io_num = 0;
239         double base_num = 0;
240         double lg_avglatency, lg_maxavglatency, lg_minavglatency;
241         double standard_deviation;
242         double lg_toldelay = 0;
243         long long before, after;
244         struct timespec tv;
245         int blksize;
246         char *buf;
247         int restore_flags = 0;
248         double lg_base;
249         long long sum_latency = 0;
250         long long arith_mean_lat;
251
252         if (pp->fd < 0)
253                 return -1;
254
255         if (get_ionum_and_basenum(args, &io_num, &base_num) == 0) {
256                 io_num = DEF_IO_NUM;
257                 base_num = DEF_BASE_NUM;
258                 pp_pl_log(0, "%s: fails to get path_latency args, set default:"
259                                 "io_num=%d base_num=%.3lf",
260                                 pp->dev, io_num, base_num);
261         }
262
263         memset(lg_path_latency, 0, sizeof(lg_path_latency));
264         lg_base = log(base_num);
265         lg_maxavglatency = log(MAX_AVG_LATENCY) / lg_base;
266         lg_minavglatency = log(MIN_AVG_LATENCY) / lg_base;
267
268         prepare_directio_read(pp->fd, &blksize, &buf, &restore_flags);
269
270         temp = io_num;
271         while (temp-- > 0) {
272                 (void)clock_gettime(CLOCK_MONOTONIC, &tv);
273                 before = timeval_to_us(&tv);
274
275                 if (do_directio_read(pp->fd, timeout, buf, blksize)) {
276                         pp_pl_log(0, "%s: path down", pp->dev);
277                         cleanup_directio_read(pp->fd, buf, restore_flags);
278                         return -1;
279                 }
280
281                 (void)clock_gettime(CLOCK_MONOTONIC, &tv);
282                 after = timeval_to_us(&tv);
283                 /*
284                  * We assume that the latency complies with Log-normal
285                  * distribution. The logarithm of latency is in normal
286                  * distribution.
287                  */
288                 lg_path_latency[index] = log(after - before) / lg_base;
289                 lg_toldelay += lg_path_latency[index++];
290                 sum_latency += after - before;
291         }
292
293         cleanup_directio_read(pp->fd, buf, restore_flags);
294
295         lg_avglatency = lg_toldelay / (long long)io_num;
296         arith_mean_lat = sum_latency / (long long)io_num;
297         pp_pl_log(4, "%s: arithmetic mean latency is (%lld us), geometric mean latency is (%lld us)",
298                         pp->dev, arith_mean_lat,
299                         (long long)pow(base_num, lg_avglatency));
300
301         if (lg_avglatency > lg_maxavglatency) {
302                 pp_pl_log(0,
303                           "%s: average latency (%lld us) is outside the thresold (%lld us)",
304                           pp->dev, (long long)pow(base_num, lg_avglatency),
305                           (long long)MAX_AVG_LATENCY);
306                 return DEFAULT_PRIORITY;
307         }
308
309         standard_deviation = calc_standard_deviation(lg_path_latency,
310                         index, lg_avglatency);
311         /*
312          * In calPrio(), we let prio y = f(x) = log(max, base) - log (x, base);
313          * So if we want to let the priority of the latency outside 2 standard
314          * deviations can be distinguished from the latency inside 2 standard
315          * deviation, in others words at most 95% are the same and at least 5%
316          * are different according interval estimation of normal distribution,
317          * we should warn the user to set the base_num to be smaller if the
318          * log(x_threshold, base) is small than 2 standard deviation.
319          * x_threshold is derived from:
320          * y + 1 = f(x) + 1 = f(x) + log(base, base), so x_threadshold =
321          * base_num; Note that we only can compare the logarithm of x_threshold
322          * with the standard deviation because the standard deviation is derived
323          * from logarithm of latency.
324          *
325          * therefore , we recommend the base_num to meet the condition :
326          * 1 <= 2 * standard_deviation
327          */
328         pp_pl_log(5, "%s: standard deviation for logarithm of latency = %.6f",
329                         pp->dev, standard_deviation);
330         if (standard_deviation <= 0.5)
331                 pp_pl_log(3, "%s: the base_num(%.3lf) is too big to distinguish different priority "
332                           "of two far-away latency. It is recommend to be set smaller",
333                           pp->dev, base_num);
334         /*
335          * If the standard deviation is too large , we should also warn the user
336          */
337
338         if (standard_deviation > 4)
339                 pp_pl_log(3, "%s: the base_num(%.3lf) is too small to avoid noise disturbance "
340                           ".It is recommend to be set larger",
341                           pp->dev, base_num);
342
343
344         rc = calcPrio(lg_avglatency, lg_maxavglatency, lg_minavglatency);
345
346         return rc;
347 }