'equal_multi' is now the default.
Change-Id: If7e4ef33aba2982db023629590c901af5b4c3309
Signed-off-by: Michal Bloch <m.bloch@samsung.com>
# 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
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)
#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)
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;
+}
+
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);
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();
}