From 3cfb26e281c78f9dfc890ab42fba22909f650547 Mon Sep 17 00:00:00 2001 From: Michael Whittaker Date: Sun, 7 Feb 2021 12:19:08 -0800 Subject: [PATCH] Tied up examples. --- examples/case_study.py | 6 + examples/examples.py | 71 ------ examples/paper.py | 5 + examples/plot_load_distribution.py | 26 +- examples/plot_node_loads.py | 24 +- examples/plot_workload_distribution.py | 22 +- examples/tutorial.py | 316 +++++++++++++------------ 7 files changed, 227 insertions(+), 243 deletions(-) delete mode 100644 examples/examples.py diff --git a/examples/case_study.py b/examples/case_study.py index a18ea76..17f25ba 100644 --- a/examples/case_study.py +++ b/examples/case_study.py @@ -1,3 +1,8 @@ +""" +This script contains the code used in the case study of our paper +(https://mwhittaker.github.io/publications/quoracle.pdf). +""" + # See https://stackoverflow.com/a/19521297/3187068 import matplotlib matplotlib.use('pdf') @@ -8,6 +13,7 @@ from quoracle import * import datetime import matplotlib.pyplot as plt + def main() -> None: def seconds(x: int) -> datetime.timedelta: return datetime.timedelta(seconds=x) diff --git a/examples/examples.py b/examples/examples.py deleted file mode 100644 index a8b581e..0000000 --- a/examples/examples.py +++ /dev/null @@ -1,71 +0,0 @@ -from quoracle import * - - -def load(qs: QuorumSystem, fr: float, f: int) -> float: - try: - return qs.load(read_fraction=fr, f=f) - except ValueError: - return float('inf') - - -def main(): - a = Node('a') - b = Node('b') - c = Node('c') - d = Node('d') - e = Node('e') - - reads_examples = [ - # 1 node. - a, - - # 2 nodes. - choose(1, [a, b]), - choose(2, [a, b]), - - # 3 nodes. - choose(1, [a, b, c]), - choose(2, [a, b, c]), - choose(3, [a, b, c]), - - # 4 nodes. - a*b + c*d, - (a+b)*(c+d), - choose(1, [a, b, c, d]), - choose(2, [a, b, c, d]), - choose(3, [a, b, c, d]), - choose(4, [a, b, c, d]), - - # 5 nodes. - a*b + a*c*e + d*e + d*c*b, - a*b + c*d*e, - (a+b) * (c+d+e), - (a+b) * (c+d+e), - a + b*c + d*e, - a * (b+c) * (d+e), - choose(1, [a, b, c, d, e]), - choose(2, [a, b, c, d, e]), - choose(3, [a, b, c, d, e]), - choose(4, [a, b, c, d, e]), - choose(5, [a, b, c, d, e]), - ] - - fs = [0, 1, 2] - frs = [0, 0.25, 0.5, 0.75, 1] - header = (['Quorum System', 'n', 'Dup Free?', 'Read Resilience', - 'Write Resilience', 'Resilience'] + - [f'f={f},fr={fr}' for f in fs for fr in frs]) - print(';'.join(header)) - - for reads in reads_examples: - qs = QuorumSystem(reads=reads) - data = ([reads, len(qs.nodes()), qs.dup_free(), qs.read_resilience(), - qs.write_resilience(), qs.resilience()]+ - ['{:.4f}'.format(load(qs, fr, f=f)) - for f in [0, 1, 2] - for fr in [0, 0.25, 0.5, 0.75, 1]]) - print(';'.join(str(x) for x in data)) - - -if __name__ == '__main__': - main() diff --git a/examples/paper.py b/examples/paper.py index 46524fb..eae2363 100644 --- a/examples/paper.py +++ b/examples/paper.py @@ -1,3 +1,8 @@ +""" +This script contains the code used in our paper +(https://mwhittaker.github.io/publications/quoracle.pdf). +""" + from quoracle import * import datetime diff --git a/examples/plot_load_distribution.py b/examples/plot_load_distribution.py index fe3baca..ff980c4 100644 --- a/examples/plot_load_distribution.py +++ b/examples/plot_load_distribution.py @@ -1,9 +1,11 @@ from quoracle import * +import argparse import matplotlib import matplotlib.pyplot as plt +import os.path -def main(): +def main(output_directory: str): a = Node('a', capacity=100) b = Node('b', capacity=200) c = Node('c', capacity=100) @@ -18,34 +20,40 @@ def main(): } for name, qs in quorum_systems.items(): - d = {0.0: 1, 0.1: 1, 0.2: 1, 0.3: 1, 0.4: 1, 0.5: 1, - 0.6: 1, 0.7: 1, 0.8: 1, 0.9: 1, 1.0: 1} + dist = {0.0: 1., 0.1: 1., 0.2: 1., 0.3: 1., 0.4: 1., 0.5: 1., + 0.6: 1., 0.7: 1., 0.8: 1., 0.9: 1., 1.0: 1.} fig, axes = plt.subplots(3, 4, figsize=(6 * 2, 4 * 2), sharey='all') axes_iter = (axes[row][col] for row in range(3) for col in range(4)) - for fr in d.keys(): + for fr in dist.keys(): sigma = qs.strategy(read_fraction=fr) ax = next(axes_iter) plot_load_distribution_on(ax, sigma, nodes) ax.set_title(f'Optimized For\nRead Fraction = {fr}') ax.set_xlabel('Read Fraction') ax.grid() - # ax.legend() - sigma = qs.strategy(read_fraction=d) + sigma = qs.strategy(read_fraction=dist) ax = next(axes_iter) plot_load_distribution_on(ax, sigma, nodes) ax.set_title('Optimized For\nUniform Read Fraction') ax.set_xlabel('Read Fraction') ax.grid() - # ax.legend() axes[0][0].set_ylabel('Load') axes[1][0].set_ylabel('Load') axes[2][0].set_ylabel('Load') fig.tight_layout() - fig.savefig(f'{name}.pdf') + output_filename = os.path.join(output_directory, f'{name}.pdf') + fig.savefig(output_filename) + print(f'Wrote figure to "{output_filename}".') if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument('--output', + type=str, + default='.', + help='Output directory') + args = parser.parse_args() + main(args.output) diff --git a/examples/plot_node_loads.py b/examples/plot_node_loads.py index af1eba8..2103713 100644 --- a/examples/plot_node_loads.py +++ b/examples/plot_node_loads.py @@ -1,10 +1,17 @@ +""" +This script shows how to use plot_node_load_on, plot_node_utilization, and +plot_node_throughput to plot the load, utilization, and throughput of nodes in +a read-write quorum system. +""" + from quoracle import * +import argparse import datetime import matplotlib import matplotlib.pyplot as plt -def main(): +def main(output_filename: str) -> None: a = Node('a', write_capacity=1000, read_capacity=10000) b = Node('b', write_capacity=500, read_capacity=5000) c = Node('c', write_capacity=1000, read_capacity=10000) @@ -31,11 +38,18 @@ def main(): ax[0][2].set_title('Paths') ax[0][3].set_title(f'Opt {opt.reads}') ax[0][0].set_ylabel('Load') - ax[1][0].set_ylabel('Utilization at Peak Throughput') - ax[2][0].set_ylabel('Throughput at Peak Throughput') + ax[1][0].set_ylabel('Utilization') + ax[2][0].set_ylabel('Throughput') fig.tight_layout() - fig.savefig('node_loads.pdf') + fig.savefig(output_filename) + print(f'Wrote figure to "{output_filename}".') if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument('--output', + type=str, + default='node_loads.pdf', + help='Output filename') + args = parser.parse_args() + main(args.output) diff --git a/examples/plot_workload_distribution.py b/examples/plot_workload_distribution.py index c60b13e..71f4f95 100644 --- a/examples/plot_workload_distribution.py +++ b/examples/plot_workload_distribution.py @@ -1,3 +1,11 @@ +""" +In this script, we generate a strategy sigma that is optimal for a distribution +of read fractions. We plot this strategy's capacity as a function of read +fraction and compare it to other strategies optimized for specific points in +this distribution. This plot was used in our paper +(https://mwhittaker.github.io/publications/quoracle.pdf). +""" + # See https://stackoverflow.com/a/19521297/3187068 import matplotlib matplotlib.use('pdf') @@ -5,12 +13,13 @@ font = {'size': 8} matplotlib.rc('font', **font) from quoracle import * +import argparse import itertools import matplotlib import matplotlib.pyplot as plt -def main(): +def main(output_filename: str) -> None: a = Node('a', write_capacity=100, read_capacity=200) b = Node('b', write_capacity=100, read_capacity=200) c = Node('c', write_capacity=50, read_capacity=100) @@ -44,8 +53,15 @@ def main(): ax.set_xticks([0, 0.25, 0.5, 0.75, 1]) ax.grid() fig.tight_layout() - fig.savefig(f'workload_distribution.pdf') + fig.savefig(output_filename) + print(f'Wrote figure to "{output_filename}".') if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument('--output', + type=str, + default='workload_distribution.pdf', + help='Output filename') + args = parser.parse_args() + main(args.output) diff --git a/examples/tutorial.py b/examples/tutorial.py index 7d136c8..72414f8 100644 --- a/examples/tutorial.py +++ b/examples/tutorial.py @@ -1,197 +1,203 @@ -## Quorum Systems from quoracle import * -a = Node('a') -b = Node('b') -c = Node('c') -d = Node('d') -e = Node('e') -f = Node('f') -grid = QuorumSystem(reads=a*b*c + d*e*f) +def main() -> None: + ## Quorum Systems + a = Node('a') + b = Node('b') + c = Node('c') + d = Node('d') + e = Node('e') + f = Node('f') -for r in grid.read_quorums(): - print(r) + grid = QuorumSystem(reads=a*b*c + d*e*f) -for w in grid.write_quorums(): - print(w) + for r in grid.read_quorums(): + print(r) -QuorumSystem(writes=(a + b + c) * (d + e + f)) + for w in grid.write_quorums(): + print(w) -QuorumSystem(reads=a*b*c + d*e*f, writes=(a + b + c) * (d + e + f)) + QuorumSystem(writes=(a + b + c) * (d + e + f)) -# QuorumSystem(reads=a+b+c, writes=d+e+f) -# ValueError: Not all read quorums intersect all write quorums + QuorumSystem(reads=a*b*c + d*e*f, writes=(a + b + c) * (d + e + f)) -print(grid.is_read_quorum({'a', 'b', 'c'})) # True -print(grid.is_read_quorum({'a', 'b', 'c', 'd'})) # True -print(grid.is_read_quorum({'a', 'b', 'd'})) # False + # QuorumSystem(reads=a+b+c, writes=d+e+f) + # ValueError: Not all read quorums intersect all write quorums -print(grid.is_write_quorum({'a', 'd'})) # True -print(grid.is_write_quorum({'a', 'd', 'd'})) # True -print(grid.is_write_quorum({'a', 'b'})) # False + print(grid.is_read_quorum({'a', 'b', 'c'})) # True + print(grid.is_read_quorum({'a', 'b', 'c', 'd'})) # True + print(grid.is_read_quorum({'a', 'b', 'd'})) # False -## Resilience -print(grid.read_resilience()) # 1 -print(grid.write_resilience()) # 2 -print(grid.resilience()) # 1 + print(grid.is_write_quorum({'a', 'd'})) # True + print(grid.is_write_quorum({'a', 'd', 'd'})) # True + print(grid.is_write_quorum({'a', 'b'})) # False -## Strategies -# The read quorum strategy. -sigma_r = { - frozenset({'a', 'b', 'c'}): 2., - frozenset({'d', 'e', 'f'}): 1., -} + ## Resilience + print(grid.read_resilience()) # 1 + print(grid.write_resilience()) # 2 + print(grid.resilience()) # 1 -# The write quorum strategy. -sigma_w = { - frozenset({'a', 'd'}): 1., - frozenset({'b', 'e'}): 1., - frozenset({'c', 'f'}): 1., -} -strategy = grid.make_strategy(sigma_r, sigma_w) + ## Strategies + # The read quorum strategy. + sigma_r = { + frozenset({'a', 'b', 'c'}): 2., + frozenset({'d', 'e', 'f'}): 1., + } -print(strategy.get_read_quorum()) -print(strategy.get_read_quorum()) -print(strategy.get_read_quorum()) -print(strategy.get_read_quorum()) -print(strategy.get_write_quorum()) -print(strategy.get_write_quorum()) -print(strategy.get_write_quorum()) -print(strategy.get_write_quorum()) + # The write quorum strategy. + sigma_w = { + frozenset({'a', 'd'}): 1., + frozenset({'b', 'e'}): 1., + frozenset({'c', 'f'}): 1., + } + strategy = grid.make_strategy(sigma_r, sigma_w) -## Load and Capacity -print(strategy.load(read_fraction=1)) # 2/3 + print(strategy.get_read_quorum()) + print(strategy.get_read_quorum()) + print(strategy.get_read_quorum()) + print(strategy.get_read_quorum()) + print(strategy.get_write_quorum()) + print(strategy.get_write_quorum()) + print(strategy.get_write_quorum()) + print(strategy.get_write_quorum()) -print(strategy.load(write_fraction=1)) # 1/3 + ## Load and Capacity + print(strategy.load(read_fraction=1)) # 2/3 -print(strategy.load(read_fraction=0.25)) # 5/12 + print(strategy.load(write_fraction=1)) # 1/3 -print(strategy.node_load(a, read_fraction=0.25)) # 5/12 -print(strategy.node_load(b, read_fraction=0.25)) # 5/12 -print(strategy.node_load(c, read_fraction=0.25)) # 5/12 -print(strategy.node_load(d, read_fraction=0.25)) # 1/3 -print(strategy.node_load(e, read_fraction=0.25)) # 1/3 -print(strategy.node_load(f, read_fraction=0.25)) # 1/3 + print(strategy.load(read_fraction=0.25)) # 5/12 -strategy = grid.strategy(read_fraction=0.25) -print(strategy) -print(strategy.load(read_fraction=0.25)) # 3/8 + print(strategy.node_load(a, read_fraction=0.25)) # 5/12 + print(strategy.node_load(b, read_fraction=0.25)) # 5/12 + print(strategy.node_load(c, read_fraction=0.25)) # 5/12 + print(strategy.node_load(d, read_fraction=0.25)) # 1/3 + print(strategy.node_load(e, read_fraction=0.25)) # 1/3 + print(strategy.node_load(f, read_fraction=0.25)) # 1/3 -print(strategy.load(read_fraction=0)) # 1/3 -print(strategy.load(read_fraction=0.5)) # 5/12 -print(strategy.load(read_fraction=1)) # 1/2 + strategy = grid.strategy(read_fraction=0.25) + print(strategy) + print(strategy.load(read_fraction=0.25)) # 3/8 -print(grid.load(read_fraction=0.25)) # 3/8 + print(strategy.load(read_fraction=0)) # 1/3 + print(strategy.load(read_fraction=0.5)) # 5/12 + print(strategy.load(read_fraction=1)) # 1/2 -print(grid.capacity(read_fraction=0.25)) # 8/3 + print(grid.load(read_fraction=0.25)) # 3/8 -## Workload Distributions -distribution = {0.1: 0.5, 0.75: 0.5} -strategy = grid.strategy(read_fraction=distribution) -print(strategy.load(read_fraction=distribution)) # 0.404 + print(grid.capacity(read_fraction=0.25)) # 8/3 -## Heterogeneous Node -a = Node('a', capacity=1000) -b = Node('b', capacity=500) -c = Node('c', capacity=1000) -d = Node('d', capacity=500) -e = Node('e', capacity=1000) -f = Node('f', capacity=500) + ## Workload Distributions + distribution = {0.1: 0.5, 0.75: 0.5} + strategy = grid.strategy(read_fraction=distribution) + print(strategy.load(read_fraction=distribution)) # 0.404 -grid = QuorumSystem(reads=a*b*c + d*e*f) -strategy = grid.strategy(read_fraction=0.75) -print(strategy.load(read_fraction=0.75)) # 0.00075 -print(strategy.capacity(read_fraction=0.75)) # 1333 + ## Heterogeneous Node + a = Node('a', capacity=1000) + b = Node('b', capacity=500) + c = Node('c', capacity=1000) + d = Node('d', capacity=500) + e = Node('e', capacity=1000) + f = Node('f', capacity=500) -a = Node('a', write_capacity=1000, read_capacity=10000) -b = Node('b', write_capacity=500, read_capacity=5000) -c = Node('c', write_capacity=1000, read_capacity=10000) -d = Node('d', write_capacity=500, read_capacity=5000) -e = Node('e', write_capacity=1000, read_capacity=10000) -f = Node('f', write_capacity=500, read_capacity=5000) + grid = QuorumSystem(reads=a*b*c + d*e*f) + strategy = grid.strategy(read_fraction=0.75) + print(strategy.load(read_fraction=0.75)) # 0.00075 + print(strategy.capacity(read_fraction=0.75)) # 1333 -grid = QuorumSystem(reads=a*b*c + d*e*f) -print(grid.capacity(read_fraction=1)) # 10,000 -print(grid.capacity(read_fraction=0.5)) # 3913 -print(grid.capacity(read_fraction=0)) # 2000 + a = Node('a', write_capacity=1000, read_capacity=10000) + b = Node('b', write_capacity=500, read_capacity=5000) + c = Node('c', write_capacity=1000, read_capacity=10000) + d = Node('d', write_capacity=500, read_capacity=5000) + e = Node('e', write_capacity=1000, read_capacity=10000) + f = Node('f', write_capacity=500, read_capacity=5000) -## f-resilient Strategies -strategy = grid.strategy(read_fraction=0.5, f=1) + grid = QuorumSystem(reads=a*b*c + d*e*f) + print(grid.capacity(read_fraction=1)) # 10,000 + print(grid.capacity(read_fraction=0.5)) # 3913 + print(grid.capacity(read_fraction=0)) # 2000 -print(strategy.get_read_quorum()) -print(strategy.get_write_quorum()) + ## f-resilient Strategies + strategy = grid.strategy(read_fraction=0.5, f=1) -print(grid.capacity(write_fraction=1, f=0)) -print(grid.capacity(write_fraction=1, f=1)) + print(strategy.get_read_quorum()) + print(strategy.get_write_quorum()) -write2 = QuorumSystem(writes=choose(2, [a, b, c, d, e])) -print(write2.capacity(write_fraction=1, f=0)) -print(write2.capacity(write_fraction=1, f=1)) + print(grid.capacity(write_fraction=1, f=0)) + print(grid.capacity(write_fraction=1, f=1)) -## Latency -import datetime + write2 = QuorumSystem(writes=choose(2, [a, b, c, d, e])) + print(write2.capacity(write_fraction=1, f=0)) + print(write2.capacity(write_fraction=1, f=1)) -def seconds(x: int) -> datetime.timedelta: - return datetime.timedelta(seconds=x) + ## Latency + import datetime -a = Node('a', write_capacity=1000, read_capacity=10000, latency=seconds(1)) -b = Node('b', write_capacity=500, read_capacity=5000, latency=seconds(2)) -c = Node('c', write_capacity=1000, read_capacity=10000, latency=seconds(3)) -d = Node('d', write_capacity=500, read_capacity=5000, latency=seconds(4)) -e = Node('e', write_capacity=1000, read_capacity=10000, latency=seconds(5)) -f = Node('f', write_capacity=500, read_capacity=5000, latency=seconds(6)) -grid = QuorumSystem(reads=a*b*c + d*e*f) + def seconds(x: int) -> datetime.timedelta: + return datetime.timedelta(seconds=x) -sigma = grid.strategy(read_fraction=0.5, optimize='latency') -print(sigma) + a = Node('a', write_capacity=1000, read_capacity=10000, latency=seconds(1)) + b = Node('b', write_capacity=500, read_capacity=5000, latency=seconds(2)) + c = Node('c', write_capacity=1000, read_capacity=10000, latency=seconds(3)) + d = Node('d', write_capacity=500, read_capacity=5000, latency=seconds(4)) + e = Node('e', write_capacity=1000, read_capacity=10000, latency=seconds(5)) + f = Node('f', write_capacity=500, read_capacity=5000, latency=seconds(6)) + grid = QuorumSystem(reads=a*b*c + d*e*f) -print(sigma.latency(read_fraction=1)) -print(sigma.latency(read_fraction=0)) -print(sigma.latency(read_fraction=0.5)) + sigma = grid.strategy(read_fraction=0.5, optimize='latency') + print(sigma) -print(grid.latency(read_fraction=0.5, optimize='latency')) + print(sigma.latency(read_fraction=1)) + print(sigma.latency(read_fraction=0)) + print(sigma.latency(read_fraction=0.5)) -sigma = grid.strategy(read_fraction=0.5, - optimize='latency', - load_limit=1/1500) -print(sigma) -print(sigma.capacity(read_fraction=0.5)) -print(sigma.latency(read_fraction=0.5)) + print(grid.latency(read_fraction=0.5, optimize='latency')) -sigma = grid.strategy(read_fraction=0.5, - optimize='load', - latency_limit=seconds(4)) -print(sigma) -print(sigma.capacity(read_fraction=0.5)) -print(sigma.latency(read_fraction=0.5)) + sigma = grid.strategy(read_fraction=0.5, + optimize='latency', + load_limit=1/1500) + print(sigma) + print(sigma.capacity(read_fraction=0.5)) + print(sigma.latency(read_fraction=0.5)) -# grid.strategy(read_fraction=0.5, -# optimize='load', -# latency_limit=seconds(1)) -# quoracle.quorum_system.NoStrategyFoundError: no strategy satisfies the given constraints + sigma = grid.strategy(read_fraction=0.5, + optimize='load', + latency_limit=seconds(4)) + print(sigma) + print(sigma.capacity(read_fraction=0.5)) + print(sigma.latency(read_fraction=0.5)) -## Network Load -sigma = grid.strategy(read_fraction=0.5, optimize='network') -print(sigma) -print(sigma.network_load(read_fraction=0.5)) -print(grid.network_load(read_fraction=0.5, optimize='network')) -sigma = grid.strategy(read_fraction=0.5, - optimize='network', - load_limit=1/2000, - latency_limit=seconds(4)) + # grid.strategy(read_fraction=0.5, + # optimize='load', + # latency_limit=seconds(1)) + # quoracle.quorum_system.NoStrategyFoundError: no strategy satisfies the given constraints -## Search -qs, sigma = search(nodes=[a, b, c, d, e, f], - resilience=1, - f=1, - read_fraction=0.75, - optimize='load', - latency_limit=seconds(4), - network_limit=4, - timeout=seconds(60)) -print(qs) -print(sigma) -print(sigma.capacity(read_fraction=0.75)) -print(sigma.latency(read_fraction=0.75)) -print(sigma.network_load(read_fraction=0.75)) + ## Network Load + sigma = grid.strategy(read_fraction=0.5, optimize='network') + print(sigma) + print(sigma.network_load(read_fraction=0.5)) + print(grid.network_load(read_fraction=0.5, optimize='network')) + sigma = grid.strategy(read_fraction=0.5, + optimize='network', + load_limit=1/2000, + latency_limit=seconds(4)) + + ## Search + qs, sigma = search(nodes=[a, b, c, d, e, f], + resilience=1, + f=1, + read_fraction=0.75, + optimize='load', + latency_limit=seconds(4), + network_limit=4, + timeout=seconds(60)) + print(qs) + print(sigma) + print(sigma.capacity(read_fraction=0.75)) + print(sigma.latency(read_fraction=0.75)) + print(sigma.network_load(read_fraction=0.75)) + + +if __name__ == '__main__': + main()