"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