|
0 |
# PyEClib Companion tool
|
|
1 |
# Goal: When defining an EC pool, help cluster admin make an informed choice between available EC implementations. Generate sample swift.conf + swift-ring-builder hints.
|
|
2 |
#
|
|
3 |
# Suggested features:
|
|
4 |
#
|
|
5 |
# List the "EC types" - EC algorithms
|
|
6 |
# List implementations of each EC type available on the platform (dumb-software-only, software with SIMD acceleration, specialized hardware, etc).
|
|
7 |
# Benchmark each algorithm with possible implementation and display performance numbers.
|
|
8 |
# Generate sample EC policy entry (for inclusion in swift.conf) for the best performing algorithm + implementation. (And optionally provide swift-ring-builder hints).
|
|
9 |
# Suggested EC policy entry format:
|
|
10 |
#
|
|
11 |
# ======== swift.conf ============
|
|
12 |
# [storage-policy:10]
|
|
13 |
# type = erasure_coding
|
|
14 |
# name = ec_rs_cauchy_orig_12_2
|
|
15 |
# ec_type = rs_cauchy_orig
|
|
16 |
# ec_k = 12
|
|
17 |
# ec_m = 2
|
|
18 |
# ============================
|
|
19 |
#
|
|
20 |
# (ec_type values are one of those available within PyEClib)
|
|
21 |
|
|
22 |
#
|
|
23 |
# User input: Num data, num parity, average file size
|
|
24 |
# Output: Ordered list of options and their corresponding conf entries (limit 10)
|
|
25 |
#
|
|
26 |
|
|
27 |
import pyeclib
|
|
28 |
from pyeclib.ec_iface import ECDriver
|
|
29 |
import random
|
|
30 |
import string
|
|
31 |
import sys
|
|
32 |
import os
|
|
33 |
import argparse
|
|
34 |
import time
|
|
35 |
import math
|
|
36 |
|
|
37 |
class Timer:
|
|
38 |
def __init__(self):
|
|
39 |
self.start_time = 0
|
|
40 |
self.end_time = 0
|
|
41 |
|
|
42 |
def reset(self):
|
|
43 |
self.start_time = 0
|
|
44 |
self.end_time = 0
|
|
45 |
|
|
46 |
def start(self):
|
|
47 |
self.start_time = time.time()
|
|
48 |
|
|
49 |
def stop(self):
|
|
50 |
self.end_time = time.time()
|
|
51 |
|
|
52 |
def curr_delta(self):
|
|
53 |
return self.end_time - self.start_time
|
|
54 |
|
|
55 |
def stop_and_return(self):
|
|
56 |
self.end_time = time.time()
|
|
57 |
return self.curr_delta()
|
|
58 |
|
|
59 |
def nCr(n,r):
|
|
60 |
f = math.factorial
|
|
61 |
return f(n) / f(r) / f(n-r)
|
|
62 |
|
|
63 |
class ECScheme:
|
|
64 |
def __init__(self, k, m, w, type):
|
|
65 |
self.k = k
|
|
66 |
self.m = m
|
|
67 |
self.w = w
|
|
68 |
self.type = type
|
|
69 |
|
|
70 |
def __str__(self):
|
|
71 |
return "k=%d m=%d w=%d type=%s" % (self.k, self.m, self.w, self.type)
|
|
72 |
|
|
73 |
valid_flat_xor_3 = [(6,6), (7,6), (8,6), (9,6), (10,6), (11,6), (12,6), (13,6), (14,6), (15,6)]
|
|
74 |
valid_flat_xor_4 = [(6,6), (7,6), (8,6), (9,6), (10,6), (11,6), (12,6), (13,6), (14,6), (15,6), (16,6), (17,6), (18,6), (19,6), (20,6)]
|
|
75 |
|
|
76 |
def get_viable_schemes(max_num_frags, minimum_rate, avg_stripe_size, fault_tolerance):
|
|
77 |
|
|
78 |
list_of_schemes = []
|
|
79 |
|
|
80 |
#
|
|
81 |
# Get min_k from (minimum_rate * max_num_frags)
|
|
82 |
#
|
|
83 |
min_k = int(math.ceil(minimum_rate * max_num_frags))
|
|
84 |
|
|
85 |
#
|
|
86 |
# Get min_m from the fault tolerance
|
|
87 |
#
|
|
88 |
min_m = fault_tolerance
|
|
89 |
|
|
90 |
#
|
|
91 |
# Is not information theoretically possible
|
|
92 |
#
|
|
93 |
if (min_k + min_m) > max_num_frags:
|
|
94 |
return list_of_schemes
|
|
95 |
|
|
96 |
#
|
|
97 |
# Iterate over EC(k, max_num_frags-k) k \in [min_k, n-min_m]
|
|
98 |
#
|
|
99 |
for k in range(min_k, max_num_frags-min_m):
|
|
100 |
#
|
|
101 |
# RS(k, max_num_frags-k) is trivial, just add it (w=[8,16,32] for vand_rs)
|
|
102 |
#
|
|
103 |
for w in [8, 16, 32]:
|
|
104 |
list_of_schemes.append(ECScheme(k, max_num_frags-k, w, "rs_vand_%d" % w))
|
|
105 |
|
|
106 |
for w in [4, 8]:
|
|
107 |
list_of_schemes.append(ECScheme(k, max_num_frags-k, w, "rs_cauchy_orig_%d" % w))
|
|
108 |
|
|
109 |
#
|
|
110 |
# The XOR codes are a little tricker (only check if fault_tolerance = 2 or 3)
|
|
111 |
#
|
|
112 |
# Constraint for 2: k <= (m choose 2)
|
|
113 |
# Constraint for 3: k <= (m choose 3)
|
|
114 |
#
|
|
115 |
# The '3' flat_xor_3 (and '4' in flat_xor_4) refers to the Hamming distance,
|
|
116 |
# which means the code guarantees the reconstruction of any 2 lost fragments
|
|
117 |
# (or 3 in the case of flat_xor_4).
|
|
118 |
#
|
|
119 |
# So, only consider the XOR code if the fault_tolerance matches and
|
|
120 |
# the additional constraint is met
|
|
121 |
#
|
|
122 |
if fault_tolerance == 2:
|
|
123 |
max_k = nCr(max_num_frags-k, 2)
|
|
124 |
if k <= max_k and (k, max_num_frags-k) in valid_flat_xor_3:
|
|
125 |
list_of_schemes.append(ECScheme(k, max_num_frags-k, 0, "flat_xor_3"))
|
|
126 |
|
|
127 |
if fault_tolerance == 3:
|
|
128 |
max_k = nCr(max_num_frags-k, 3)
|
|
129 |
if k <= max_k and (k, max_num_frags-k) in valid_flat_xor_4:
|
|
130 |
list_of_schemes.append(ECScheme(k, max_num_frags-k, 0, "flat_xor_4"))
|
|
131 |
|
|
132 |
return list_of_schemes
|
|
133 |
|
|
134 |
|
|
135 |
parser = argparse.ArgumentParser(description='PyECLib tool to evaluate viable EC options, benchmark them and report results with the appropriate conf entries.')
|
|
136 |
parser.add_argument('-n', type=int, help='max number of fragments')
|
|
137 |
parser.add_argument('-f', type=int, help='fault tolerance')
|
|
138 |
parser.add_argument('-r', type=float, help='minimum coding rate (num_data / num_data+num_parity)')
|
|
139 |
parser.add_argument('-s', type=int, help='average stripe size')
|
|
140 |
parser.add_argument('-l', type=int, help='set limit on number of entries returned (default = 10)', default=10, )
|
|
141 |
|
|
142 |
args = parser.parse_args(sys.argv[1:])
|
|
143 |
|
|
144 |
MB=1024*1024
|
|
145 |
|
|
146 |
# Generate a buffer of size 's'
|
|
147 |
if args.s > 10*MB:
|
|
148 |
print "s must be smaller than 10 MB."
|
|
149 |
sys.exit(1)
|
|
150 |
|
|
151 |
# Instantiate the timer
|
|
152 |
timer = Timer()
|
|
153 |
|
|
154 |
return_limit = args.l
|
|
155 |
|
|
156 |
schemes = get_viable_schemes(args.n, args.r, args.s, args.f)
|
|
157 |
|
|
158 |
# Results will be List[(type, throughput)]
|
|
159 |
results = []
|
|
160 |
|
|
161 |
# Num iterations
|
|
162 |
num_iterations=10
|
|
163 |
|
|
164 |
for scheme in schemes:
|
|
165 |
print scheme
|
|
166 |
|
|
167 |
# Generate a new string for each test
|
|
168 |
file_str = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(args.s))
|
|
169 |
|
|
170 |
try:
|
|
171 |
ec_driver = ECDriver("pyeclib.core.ECPyECLibDriver", k=scheme.k, m=scheme.m, type=scheme.type)
|
|
172 |
except Exception as e:
|
|
173 |
print "Scheme %s is not defined (%s)." % (scheme, e)
|
|
174 |
continue
|
|
175 |
|
|
176 |
timer.start()
|
|
177 |
|
|
178 |
for i in range(num_iterations):
|
|
179 |
ec_driver.encode(file_str)
|
|
180 |
|
|
181 |
duration = timer.stop_and_return()
|
|
182 |
|
|
183 |
results.append((scheme, duration))
|
|
184 |
|
|
185 |
timer.reset()
|
|
186 |
|
|
187 |
results.sort(lambda x,y: (int)((1000*x[1]) - (1000*y[1])))
|
|
188 |
|
|
189 |
for i in range(len(results)):
|
|
190 |
if i > return_limit:
|
|
191 |
break
|
|
192 |
|
|
193 |
print "\n\nPerf Rank #%d:" % i
|
|
194 |
print " ======== To Use this Policy, Copy and Paste Text (not including this header and footer) to Swift Conf ========"
|
|
195 |
print " type = erasure_coding"
|
|
196 |
print " name = %s_%d_%d" % (results[i][0].type, results[i][0].k, results[i][0].m)
|
|
197 |
print " ec_type = %s" % results[i][0].type
|
|
198 |
print " ec_k = %s" % results[i][0].k
|
|
199 |
print " ec_m = %s" % results[i][0].m
|
|
200 |
print " =============================================================================================================="
|
|
201 |
results[i]
|
|
202 |
|