"Fossies" - the Fresh Open Source Software Archive 
Member "memcached-1.6.15/scripts/memcached-automove-extstore" (16 Jul 2020, 12122 Bytes) of package /linux/www/memcached-1.6.15.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
1 #!/usr/bin/python3
2 # Copyright 2017 Facebook.
3 # Licensed under the same terms as memcached itself.
4
5 import argparse
6 import socket
7 import sys
8 import re
9 import traceback
10 from time import sleep, time
11
12 parser = argparse.ArgumentParser(description="daemon for rebalancing slabs")
13 parser.add_argument("--host", help="host to connect to",
14 default="localhost:11211", metavar="HOST:PORT")
15 parser.add_argument("-s", "--sleep", help="seconds between runs",
16 type=int, default="1")
17 parser.add_argument("-v", "--verbose", action="store_true")
18 parser.add_argument("-a", "--automove", action="store_true", default=False,
19 help="enable automatic page rebalancing")
20 parser.add_argument("-w", "--window", type=int, default="30",
21 help="rolling window size for decision history")
22 parser.add_argument("-r", "--ratio", type=float, default=0.8,
23 help="ratio limiting distance between low/high class ages")
24 parser.add_argument("-f", "--free", type=float, default=0.005,
25 help="free chunks/pages buffer ratio")
26 parser.add_argument("-z", "--size", type=int, default=512,
27 help="item size cutoff for storage")
28
29 args = parser.parse_args()
30
31 host, port = args.host.split(':')
32
33 MIN_PAGES_FOR_SOURCE = 2
34 MIN_PAGES_FOR_RECLAIM = 2.5
35 MIN_PAGES_FREE = 1.5
36 MEMCHECK_PERIOD = 60
37
38 def window_check(history, sid, key):
39 total = 0
40 for window in history['w']:
41 s = window.get(sid)
42 if s and s.get(key):
43 total += s.get(key)
44 return total
45
46 def window_key_check(history, key):
47 total = 0
48 for window in history['w']:
49 v = window.get(key)
50 if v:
51 total += v
52 return total
53
54
55 def determine_move(history, stats, diffs, memfree):
56 """ Figure out of a page move is in order.
57
58 - Use as much memory as possible to hold items, reducing the load on
59 flash.
60 - tries to keep the global free page pool inbetween poolmin/poolmax.
61 - avoids flapping as much as possible:
62 - only pull pages off of a class if it hasn't recently evicted or allocated pages.
63 - only pull pages off if a sufficient number of free chunks are available.
64 - if global pool is below minimum remove pages from oldest large class.
65 - if global pool is above maximum, move pages to youngest large class.
66 - extstore manages a desired number of free chunks in each slab class.
67 - automover adjusts above limits once per minute based on current sizes.
68 - if youngest is below the age ratio limit of oldest, move a page to it.
69 """
70 # rotate windows
71 history['w'].append({})
72 if (len(history['w']) > args.window):
73 history['w'].pop(0)
74 w = history['w'][-1]
75 oldest = (-1, 0)
76 youngest = (-1, sys.maxsize)
77 too_free = False
78 # Most bytes free
79 decision = (-1, -1)
80 if int(stats['slab_global_page_pool']) < memfree[0] / 2:
81 w['slab_pool_low'] = 1
82 if int(stats['slab_global_page_pool']) > memfree[0]:
83 w['slab_pool_high'] = 1
84 if args.verbose:
85 print("global pool: [{}]".format(stats['slab_global_page_pool']))
86
87 pool_low = window_key_check(history, 'slab_pool_low')
88 for sid, slab in diffs.items():
89 small_slab = False
90 free_enough = False
91 # Only balance larger slab classes
92 if slab['chunk_size'] < args.size:
93 small_slab = True
94
95 w[sid] = {}
96 if 'evicted_d' not in slab or 'total_pages_d' not in slab:
97 continue
98 # mark this window as dirty if total pages increases or evictions
99 # happened
100 if slab['total_pages_d'] > 0:
101 w[sid]['dirty'] = 1
102 if slab['evicted_d'] > 0:
103 w[sid]['dirty'] = 1
104 w[sid]['ev'] = 1
105 if slab['free_chunks'] > memfree[sid]:
106 free_enough = True
107 if memfree[sid] > 0 and slab['free_chunks'] > (memfree[sid] * 2):
108 w[sid]['excess_free'] = 1
109 w[sid]['age'] = slab['age']
110 age = window_check(history, sid, 'age') / len(history['w'])
111
112 # if > 2.5 pages free, and not dirty, reassign to global page pool
113 if slab['free_chunks'] > slab['chunks_per_page'] * MIN_PAGES_FOR_RECLAIM and too_free == False:
114 dirt = window_check(history, sid, 'dirty')
115 excess = window_check(history, sid, 'excess_free')
116 if small_slab == True and dirt == 0:
117 # If we're a small slab, don't hang on to free memory forever.
118 decision = (sid, 0)
119 too_free = True
120 elif small_slab == False and dirt == 0 \
121 and excess >= len(history['w']):
122 decision = (sid, 0)
123 too_free = True
124
125 # are we the oldest slab class? (and a valid target)
126 # don't consider for young if we've recently given it unused memory
127 if small_slab == False:
128 if age > oldest[1] and slab['total_pages'] > MIN_PAGES_FOR_SOURCE:
129 oldest = (sid, age)
130 if age < youngest[1] and slab['total_pages'] > 0 \
131 and window_check(history, sid, 'excess_free') < len(history['w']) \
132 and not (window_check(history, sid, 'relaxed') and free_enough):
133 youngest = (sid, age)
134
135
136 if w.get('slab_pool_high') and youngest[0] != -1:
137 # if global pool is too high, feed youngest large class.
138 if slab['free_chunks'] <= memfree[youngest[0]]:
139 decision = (0, youngest[0])
140 w[youngest[0]]['relaxed'] = 1
141 elif too_free == False and pool_low and oldest[0] != -1:
142 # if pool is too low, take from oldest large class.
143 if args.verbose:
144 print("oldest: [class: {}] [age: {:.2f}]".format(int(oldest[0]), oldest[1]))
145 decision = (oldest[0], 0)
146 elif too_free == False and youngest[0] != -1 and oldest[0] != -1 and youngest[0] != oldest[0]:
147 # youngest is outside of the tolerance ratio, move a page around.
148 if args.verbose:
149 print("old: [class: {}] [age: {:.2f}]\nyoung: [class: {}] [age: {:.2f}]".format(
150 int(oldest[0]), oldest[1], int(youngest[0]), youngest[1]))
151
152 slab = diffs[youngest[0]]
153 #print("F:{} L:{} Y:{} R:{}".format(slab['free_chunks'], memfree[youngest[0]], int(youngest[1]), int(oldest[1] * args.ratio)))
154 if youngest[1] < oldest[1] * args.ratio:
155 w[youngest[0]]['relaxed'] = 1
156 if slab['free_chunks'] <= memfree[youngest[0]]:
157 decision = (0, youngest[0])
158
159 if (len(history['w']) >= args.window):
160 return decision
161 return (-1, -1)
162
163
164 def run_move(s, decision):
165 s.write("slabs reassign " + str(decision[0]) + " " + str(decision[1]) + "\r\n")
166 line = s.readline().rstrip()
167 if args.verbose:
168 print("move result:", line)
169
170
171 def diff_stats(before, after):
172 """ fills out "diffs" as deltas between before/after,
173 and "totals" as the sum of all slab classes.
174 "_d" postfix to keys means the delta between before/after.
175 non-postfix keys are total as of 'after's reading.
176 """
177 diffs = {}
178 totals = {}
179 for slabid in after.keys():
180 sb = before.get(slabid)
181 sa = after.get(slabid)
182 if not (sb and sa):
183 continue
184 slab = sa.copy()
185 for k in sa.keys():
186 if k not in sb:
187 continue
188 if k not in totals:
189 totals[k] = 0
190 totals[k + '_d'] = 0
191 if k + '_d' not in slab:
192 slab[k + '_d'] = 0
193 if re.search(r"^\d+$", sa[k]):
194 totals[k] += int(sa[k])
195 slab[k] = int(sa[k])
196 slab[k + '_d'] = int(sa[k]) - int(sb[k])
197 totals[k + '_d'] += int(sa[k]) - int(sb[k])
198 slab['slab'] = slabid
199 diffs[slabid] = slab
200 return (diffs, totals)
201
202
203 def read_slab_stats(s):
204 slabs = {}
205 for statcmd in ['items', 'slabs']:
206 #print("stat cmd: " + statcmd)
207 # FIXME: Formatting
208 s.write("stats " + statcmd + "\r\n")
209 while True:
210 line = s.readline().rstrip()
211 if line.startswith("END"):
212 break
213
214 m = re.match(r"^STAT (?:items:)?(\d+):(\S+) (\S+)", line)
215 if m:
216 (slab, var, val) = m.groups()
217 if slab not in slabs:
218 slabs[slab] = {}
219 slabs[slab][var] = val
220 #print("line: " + line)
221 return slabs
222
223
224 # HACK: lets look at 'evictions' being nonzero to indicate memory filled at some point.
225 def read_stats(s):
226 stats = {}
227 s.write("stats\r\n")
228 while True:
229 line = s.readline().rstrip()
230 if line.startswith("END"):
231 break
232
233 m = re.match(r"^STAT (\S+) (\S+)", line)
234 if m:
235 (key, val) = m.groups()
236 stats[key] = val
237 return stats
238
239
240 def pct(num, divisor):
241 if not divisor:
242 return 0
243 return (num / divisor)
244
245
246 def show_detail(diffs, totals):
247 """ just a pretty printer for some extra data """
248 print("\n {:2s}: {:8s} (pct ) {:10s} (pct ) {:6s} (pct) {:6s}".format('sb',
249 'evicted', 'items', 'pages', 'age'))
250
251 for sid, slab in diffs.items():
252 if 'evicted_d' not in slab:
253 continue
254 print(" {:2d}: {:8d} ({:.2f}%) {:10d} ({:.4f}%) {:6d} ({:.2f}%) {:6d}".format(
255 int(sid), slab['evicted_d'], pct(slab['evicted_d'], totals['evicted_d']),
256 slab['number'], pct(slab['number'], totals['number']),
257 slab['total_pages'], pct(slab['total_pages'],
258 totals['total_pages']),
259 slab['age']))
260
261 def memfree_check(s, diffs, totals):
262 info = {}
263 # manage about this many free chunks in each slab class.
264 for sid, slab in diffs.items():
265 if sid == 0:
266 continue
267 hold_free = int((slab['used_chunks'] + slab['free_chunks']) * args.free)
268 # Hold a minimum of 1.5 pages so page moves are unlikely to lose items.
269 if slab['chunks_per_page'] * MIN_PAGES_FREE > hold_free:
270 hold_free = int(slab['chunks_per_page'] * MIN_PAGES_FREE)
271 info[sid] = hold_free
272 # TODO: only adjust if different?
273 s.write("extstore free_memchunks {} {}\r\n".format(sid, hold_free))
274 s.readline()
275
276 # how many pages to leave in the global pool.
277 info[0] = int(totals['total_pages'] * args.free)
278 return info
279
280
281 stats_pre = {}
282 history = { 'w': [{}] }
283 memfree = { 0: 2 }
284 last_memfree_check = 0
285 while True:
286 try:
287 with socket.create_connection((host, port), 5) as c:
288 s = c.makefile(mode="rw", buffering=1)
289 s.write("slabs automove 0\r\n")
290 print(s.readline().rstrip())
291 while True:
292 stats_post = read_slab_stats(s)
293 stats = read_stats(s)
294 (diffs, totals) = diff_stats(stats_pre, stats_post)
295 #if args.verbose:
296 # show_detail(diffs, totals)
297 if int(stats['evictions']) > 0:
298 if (last_memfree_check < time() - 60) and totals.get('total_pages'):
299 memfree = memfree_check(s, diffs, totals)
300 last_memfree_check = time()
301 decision = (-1, -1)
302 decision = determine_move(history, stats, diffs, memfree)
303 if int(decision[0]) > 0 and int(decision[1]) >= 0:
304 print("moving page from, to:", decision)
305 if args.automove:
306 run_move(s, decision)
307
308 # Minimize sleeping if we just moved a page to global pool.
309 # Improves responsiveness during flushes/quick changes.
310 if decision[1] == 0:
311 continue
312 else:
313 sleep(args.sleep)
314 stats_pre = stats_post
315 except:
316 err = sys.exc_info()
317 print("disconnected:", err[0], err[1])
318 traceback.print_exc()
319 stats_pre = {}
320 history = { 'w': [{}] }
321 sleep(args.sleep)
322