QoS: add some new distributions, change default 16/240816/2
authorMichal Bloch <m.bloch@samsung.com>
Tue, 11 Aug 2020 15:14:39 +0000 (17:14 +0200)
committerMichal Bloch <m.bloch@samsung.com>
Wed, 12 Aug 2020 12:52:14 +0000 (14:52 +0200)
'equal_multi' is now the default.

Change-Id: If7e4ef33aba2982db023629590c901af5b4c3309
Signed-off-by: Michal Bloch <m.bloch@samsung.com>
configs/15-qos.conf
src/logger/logger.c
src/logger/qos.c
src/logger/qos.h
src/tests/qos_distributions.c

index 4cb7e3f..9c4a0da 100644 (file)
@@ -36,9 +36,35 @@ qos_threshold_reapply_logs=5
 #              4/8/12/16 logs respectively (reaching threshold 40)
 #              their limits will be 5/10/15/20 (if max is 50).
 #
-#       "equal" (default) : max limit is distributed equally.
+#       "equal" : max limit is distributed equally.
 #              For example if there are 4 clients, and they sent
 #              4/8/12/16 logs respectively (reaching threshold 40)
 #              their limits will be 13/13/13/13 (if max is 50).
-qos_method=equal
+#
+#       "equal_dual" : any client under the "equal" limit
+#                      can log freely, any remaining throughput is
+#                      distributed equally among the rest
+#              For example if there are 3 clients, and they sent
+#              20/35/60 logs (max throughput 100), they will get
+#              --/40/40 limits respectively: 20 < 33, remaining 80
+#               gets split equally
+#
+#       "equal_multi" (default) : similar to the above, but any client
+#              not reaching its limit is also unlimited, increasing it.
+#              For example if there are 3 clients, and they sent
+#              20/35/60 logs (max throughput 100), they will get
+#              --/--/45 limits respectively: 20 < 33, but then the
+#              remaining average becomes 40 (like in dual) and 35 < 40.
+#
+#       "proportional_talmud" : Talmudic contested garment rule.
+#              It interpolates between equal shares and equal dropped
+#              logs depending on throughput (it's complicated so I
+#              would recommend googling). Some classic examples:
+#               100/200/300 logs, max throughput 100: 33/ 33/ 33
+#               100/200/300 logs, max throughput 200: 50/ 75/ 75
+#               100/200/300 logs, max throughput 300: 50/100/150
+#               100/200/300 logs, max throughput 400: 50/125/225
+#               100/200/300 logs, max throughput 500: 66/166/266
+#
+qos_method=equal_multi
 
index 16e9031..078d694 100644 (file)
@@ -2693,9 +2693,12 @@ int prepare_config_data(struct logger_config_data *data)
                const char *name;
        } const qos_methods[] = {
                { qos_distribution_proportional_raw   , "proportional_raw"    },
+               { qos_distribution_proportional_talmud, "proportional_talmud" },
                { qos_distribution_equal              , "equal"               },
+               { qos_distribution_equal_dual         , "equal_dual"          },
+               { qos_distribution_equal_multi        , "equal_multi"         },
        };
-       qos_distribution_func = qos_distribution_proportional_raw; // default
+       qos_distribution_func = qos_distribution_equal_multi; // default
        const char *const qos_method = log_config_get(&conf, "qos_method");
        if (qos_method)
                for (int i = 0; i < NELEMS(qos_methods); ++i)
index d43bb19..c2c0bbb 100644 (file)
@@ -14,6 +14,8 @@
 
 #include "qos.h"
 
+#include <stdlib.h>
+
 void (*qos_distribution_func)(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count);
 
 static inline int divide_rounding_upwards(int dividend, int divisor)
@@ -39,3 +41,110 @@ void qos_distribution_equal(struct qos_module *qos, struct metrics_pid_aggr_info
                infos[i].count = equal_limit_for_everybody;
 }
 
+
+void qos_distribution_equal_dual(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count)
+{
+       /* Anything below the "equal" threshold is unlimited.
+        * The ones above this limit get leftovers distributed
+        * among them, equally as well. */
+
+       const int equal_threshold = qos->max_throughput / count; // rounded down to prevent negative values after subtraction
+       int remaining_throughput = qos->max_throughput;
+       int count_above_threshold = count;
+
+       for (int i = 0; i < count; ++i) {
+               if (infos[i].count >= equal_threshold)
+                       continue;
+
+               remaining_throughput -= infos[i].count;
+               -- count_above_threshold;
+       }
+
+       const int higher_threshold = divide_rounding_upwards(remaining_throughput, count_above_threshold);
+       for (int i = 0; i < count; ++i)
+               if (infos[i].count >= equal_threshold)
+                       infos[i].count = higher_threshold;
+               else
+                       infos[i].count = -1;
+}
+
+void qos_distribution_equal_multi(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count)
+{
+       /* Similar to 'dual', except anything not reaching the higher
+        * threshold also gets an unlimited pass (which increases the
+        * threshold again, potentially propagating it even more) */
+
+       int threshold = qos->max_throughput / count;
+       int remaining_throughput = qos->max_throughput;
+       int count_above_threshold = count;
+
+       int prev_threshold = 0;
+       int below_current_threshold;
+       do {
+               below_current_threshold = 0;
+               for (int i = 0; i < count; ++i) {
+                       if (infos[i].count >= threshold)
+                               continue;
+                       if (infos[i].count < prev_threshold)
+                               continue;
+
+                       remaining_throughput -= infos[i].count;
+                       -- count_above_threshold;
+                       ++ below_current_threshold;
+               }
+               prev_threshold = threshold;
+               threshold = divide_rounding_upwards(remaining_throughput, count_above_threshold);
+       } while (below_current_threshold > 0);
+
+       for (int i = 0; i < count; ++i)
+               if (infos[i].count >= threshold)
+                       infos[i].count = threshold;
+               else
+                       infos[i].count = -1;
+}
+
+void qos_distribution_proportional_talmud(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count)
+{
+       /* As proscribed by the talmudic contested garment rule.
+        * Probably makes little sense given how our QoS works but
+        * it's interesting and should work really well if we ever
+        * switch to the "preset limits" design that the feature
+        * was originally supposed to follow. */
+
+       if (qos->max_throughput > metrics_get_total(qos->log_metrics))
+               return;
+
+       inline int sort_by_count(const void *vlhs, const void *vrhs) {
+               const struct metrics_pid_aggr_info *lhs = vlhs, *rhs = vrhs;
+               return lhs->count - rhs->count;
+       }
+       qsort(infos, count, sizeof(*infos), sort_by_count);
+
+       int remaining_count = count;
+       int sum_level = 0;
+       bool const upper_half = qos->max_throughput > metrics_get_total(qos->log_metrics) / 2;
+       int total = upper_half
+               ? metrics_get_total(qos->log_metrics) - qos->max_throughput
+               : qos->max_throughput
+       ;
+
+       for (int i = 0; i < count; ++i) {
+               int const level_diff = (infos[i].count / 2) - sum_level;
+               int const total_diff = level_diff * remaining_count;
+               if (total_diff >= total)
+                       break;
+
+               total -= total_diff;
+               sum_level += level_diff;
+               infos[i].count = upper_half ? infos[i].count - sum_level : sum_level;
+               -- remaining_count;
+       }
+
+       if (remaining_count == 0)
+               return;
+
+       const int final_level = sum_level + (total / remaining_count);
+       for (int i = count - remaining_count; i < count; ++i)
+               infos[i].count =  upper_half ? infos[i].count - final_level : final_level;
+}
+
index b1eb766..2d5848e 100644 (file)
@@ -39,5 +39,8 @@ struct qos_module {
 extern void (*qos_distribution_func)(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count);
 
 void qos_distribution_equal(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count);
+void qos_distribution_equal_dual(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count);
+void qos_distribution_equal_multi(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count);
 void qos_distribution_proportional_raw(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count);
+void qos_distribution_proportional_talmud(struct qos_module *qos, struct metrics_pid_aggr_info *infos, int count);
 
index a381ee1..a5008dc 100644 (file)
@@ -45,8 +45,127 @@ void test_equal()
        assert(i1[3].count == 50);
 }
 
+void test_equal_dual()
+{
+       struct metrics_pid_aggr_info i1 [] =
+               {{ .count =  20, }
+               ,{ .count =  85, }
+               ,{ .count = 120, }
+       };
+       total_logs = 20 + 85 + 120;
+
+       qos_distribution_equal_dual(&(struct qos_module) { .max_throughput = 200 }, i1, 3);
+       assert(i1[0].count == -1); //  20 <  200/3
+       assert(i1[1].count == 90); //  85 >= 200/3, 90 == (200-20)/2
+       assert(i1[2].count == 90); // 120 >= 200/3, 90 == (200-20)/2
+}
+
+void test_equal_multi()
+{
+       struct metrics_pid_aggr_info i1 [] =
+               {{ .count =  30, }
+               ,{ .count =  95, }
+               ,{ .count = 120, }
+       };
+       total_logs = 30 + 95 + 120;
+
+       qos_distribution_equal_multi(&(struct qos_module) { .max_throughput = 200 }, i1, 3);
+       assert(i1[0].count == -1); //  30 <  200/3
+       assert(i1[1].count == 85); //  95 >= 200/3, and  95 >= (200-30)/2
+       assert(i1[2].count == 85); // 120 >= 200/3, and 120 >= (200-30)/2
+
+
+       struct metrics_pid_aggr_info i2 [] =
+               {{ .count =  20, }
+               ,{ .count =  85, }
+               ,{ .count = 120, }
+       };
+       total_logs = 20 + 85 + 120;
+
+       qos_distribution_equal_multi(&(struct qos_module) { .max_throughput = 200 }, i2, 3);
+       assert(i2[0].count == -1); //  20 <  200/3
+       assert(i2[1].count == -1); //  85 >= 200/3, but  85 <  (200-20)/2
+       assert(i2[2].count == 95); // 120 >= 200/3, and 120 >= (200-20)/2, and 120 >= (200-20-85)/1
+}
+
+void test_proportional_talmud()
+{
+       struct metrics_pid_aggr_info i1 [] =
+               {{ .count =  50, }
+               ,{ .count = 100, }
+       };
+       total_logs = 50 + 100;
+
+       qos_distribution_proportional_talmud(&(struct qos_module) { .max_throughput = 100 }, i1, 2);
+       assert(i1[0].count == 25);
+       assert(i1[1].count == 75);
+
+       struct metrics_pid_aggr_info i2 [] =
+               {{ .count = 100, }
+               ,{ .count = 200, }
+               ,{ .count = 300, }
+       };
+       total_logs = 100 + 200 + 300;
+
+       qos_distribution_proportional_talmud(&(struct qos_module) { .max_throughput = 100 }, i2, 3);
+       assert(i2[0].count == 33);
+       assert(i2[1].count == 33);
+       assert(i2[2].count == 33);
+
+       struct metrics_pid_aggr_info i3 [] =
+               {{ .count = 100, }
+               ,{ .count = 200, }
+               ,{ .count = 300, }
+       };
+       total_logs = 100 + 200 + 300;
+
+       qos_distribution_proportional_talmud(&(struct qos_module) { .max_throughput = 200 }, i3, 3);
+       assert(i3[0].count == 50);
+       assert(i3[1].count == 75);
+       assert(i3[2].count == 75);
+
+       struct metrics_pid_aggr_info i4 [] =
+               {{ .count = 100, }
+               ,{ .count = 200, }
+               ,{ .count = 300, }
+       };
+       total_logs = 100 + 200 + 300;
+
+       qos_distribution_proportional_talmud(&(struct qos_module) { .max_throughput = 300 }, i4, 3);
+       assert(i4[0].count == 50);
+       assert(i4[1].count == 100);
+       assert(i4[2].count == 150);
+
+       struct metrics_pid_aggr_info i5 [] =
+               {{ .count = 100, }
+               ,{ .count = 200, }
+               ,{ .count = 300, }
+       };
+       total_logs = 100 + 200 + 300;
+
+       qos_distribution_proportional_talmud(&(struct qos_module) { .max_throughput = 400 }, i5, 3);
+       assert(i5[0].count == 50);
+       assert(i5[1].count == 125);
+       assert(i5[2].count == 225);
+
+       struct metrics_pid_aggr_info i6 [] =
+               {{ .count = 10, }
+               ,{ .count = 20, }
+               ,{ .count = 30, }
+       };
+       total_logs = 10 + 20 + 30;
+
+       qos_distribution_proportional_talmud(&(struct qos_module) { .max_throughput = 100 }, i6, 3);
+       assert(i6[0].count == 10);
+       assert(i6[1].count == 20);
+       assert(i6[2].count == 30);
+}
+
 int main()
 {
        test_proportional_raw();
        test_equal();
+       test_equal_dual();
+       test_equal_multi();
+       test_proportional_talmud();
 }