"Fossies" - the Fresh Open Source Software Archive

Member "memcached-1.6.15/scripts/memcached-automove" (21 Feb 2022, 8793 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
   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 
   25 # TODO:
   26 # - age adjuster function
   27 #   - by ratio of get_hits
   28 #   - by ratio of chunk size
   29 
   30 args = parser.parse_args()
   31 
   32 host, port = args.host.split(':')
   33 
   34 def window_check_count(history, sid, key):
   35     total = 0
   36     for window in history['w']:
   37         s = window.get(sid)
   38         if s and s.get(key):
   39             total += s.get(key) > 0 and 1 or 0
   40     return total
   41 
   42 def window_check_sum(history, sid, key):
   43     total = 0
   44     for window in history['w']:
   45         s = window.get(sid)
   46         if s and s.get(key):
   47             total += s.get(key)
   48     return total
   49 
   50 def determine_move(history, diffs, totals):
   51     """ Figure out of a page move is in order.
   52 
   53     - if > 2.5 pages of free space without free chunks reducing for N trials,
   54       and no evictions for N trials, free to global.
   55     - use ratio of how far apart age can be between slab classes
   56     - TODO: if get_hits exists, allowable distance in age from the *youngest* slab
   57       class is based on the percentage of get_hits the class gets, against the
   58       factored max distance, ie:
   59       1% max delta. youngest is 900, oldest allowable is 900+90
   60       if class has 30% of get_hits, it can be 930
   61     - youngest evicting slab class gets page moved to it, if outside ratio max
   62     - use age as average over window. smooths over items falling out of WARM.
   63       also gives a little weight: if still evicting, a few more pages than
   64       necessary may be moved, pulling the classes closer together. Hopefully
   65       avoiding ping-ponging.
   66     """
   67     # rotate windows
   68     history['w'].append({})
   69     if (len(history['w']) > args.window):
   70         history['w'].pop(0)
   71     w = history['w'][-1]
   72     oldest = (-1, 0)
   73     youngest = (-1, sys.maxsize)
   74     decision = (-1, -1)
   75     for sid, slab in diffs.items():
   76 
   77         w[sid] = {}
   78         if 'evicted_d' not in slab or 'total_pages_d' not in slab:
   79             continue
   80         # mark this window as dirty if total pages increases or evictions
   81         # happened
   82         if slab['total_pages_d'] > 0:
   83             w[sid]['dirty'] = 1
   84         if slab['evicted_d'] > 0:
   85             w[sid]['dirty'] = 1
   86             w[sid]['ev'] = slab['evicted_d'] / totals['evicted_d']
   87         w[sid]['age'] = slab['age']
   88         age = window_check_sum(history, sid, 'age') / len(history['w'])
   89 
   90         # if > 2.5 pages free, and not dirty, reassign to global page pool and
   91         # break.
   92         if slab['free_chunks'] > slab['chunks_per_page'] * 2.5:
   93             if window_check_sum(history, sid, 'dirty') == 0:
   94                 decision = (sid, 0)
   95                 break
   96 
   97         # are we the oldest slab class? (and a valid target)
   98         if age > oldest[1] and slab['total_pages'] > 2:
   99             oldest = (sid, age)
  100 
  101         # are we the youngest evicting slab class?
  102         ev_total = window_check_count(history, sid, 'ev')
  103         ev_total_sum = window_check_sum(history, sid, 'ev') / args.window
  104         window_min = args.window / 2
  105         if args.verbose:
  106             print("sid {} age {} ev_total {} window_min {} ev_total_sum {}".format(sid, age, ev_total, window_min, ev_total_sum))
  107         # If youngest and evicted in more than 50% of the window interval, or more than 25% of the total evictions in the window
  108         if age < youngest[1] and ( ev_total > window_min or ev_total_sum > 0.25 ):
  109             youngest = (sid, age)
  110             #if args.verbose:
  111             #    print("window: {} range: {}".format(ev_total, window_min))
  112 
  113     # is the youngest slab class too young?
  114     if youngest[0] != -1 and oldest[0] != -1:
  115         if args.verbose:
  116             print("old:   [class: {}] [age: {:.2f}]\nyoung: [class: {}] [age: {:.2f}]".format(
  117                 int(oldest[0]), oldest[1], int(youngest[0]), youngest[1]))
  118         if youngest[1] < oldest[1] * args.ratio and w[youngest[0]].get('ev'):
  119             decision = (oldest[0], youngest[0])
  120 
  121     if (len(history['w']) >= args.window):
  122         return decision
  123     return (-1, -1)
  124 
  125 
  126 def run_move(s, decision):
  127     s.write("slabs reassign " + str(decision[0]) + " " + str(decision[1]) + "\r\n")
  128     line = s.readline().rstrip()
  129     if args.verbose:
  130         print("move result:", line)
  131 
  132 
  133 def diff_stats(before, after):
  134     """ fills out "diffs" as deltas between before/after,
  135     and "totals" as the sum of all slab classes.
  136     "_d" postfix to keys means the delta between before/after.
  137     non-postfix keys are total as of 'after's reading.
  138     """
  139     diffs = {}
  140     totals = {}
  141     for slabid in after.keys():
  142         sb = before.get(slabid)
  143         sa = after.get(slabid)
  144         if not (sb and sa):
  145             continue
  146         slab = sa.copy()
  147         for k in sa.keys():
  148             if k not in sb:
  149                 continue
  150             if k not in totals:
  151                 totals[k] = 0
  152                 totals[k + '_d'] = 0
  153             if k + '_d' not in slab:
  154                 slab[k + '_d'] = 0
  155             if re.search(r"^\d+$", sa[k]):
  156                 totals[k] += int(sa[k])
  157                 slab[k] = int(sa[k])
  158                 slab[k + '_d'] = int(sa[k]) - int(sb[k])
  159                 totals[k + '_d'] += int(sa[k]) - int(sb[k])
  160         slab['slab'] = slabid
  161         diffs[slabid] = slab
  162     return (diffs, totals)
  163 
  164 
  165 def read_stats(s):
  166     slabs = {}
  167     for statcmd in ['items', 'slabs']:
  168         #print("stat cmd: " + statcmd)
  169         # FIXME: Formatting
  170         s.write("stats " + statcmd + "\r\n")
  171         while True:
  172             line = s.readline().rstrip()
  173             if line.startswith("END"):
  174                 break
  175 
  176             m = re.match(r"^STAT (?:items:)?(\d+):(\S+) (\S+)", line)
  177             if m:
  178                 (slab, var, val) = m.groups()
  179                 if slab not in slabs:
  180                     slabs[slab] = {}
  181                 slabs[slab][var] = val
  182             #print("line: " + line)
  183     return slabs
  184 
  185 
  186 def pct(num, divisor):
  187     if not divisor:
  188         return 0
  189     return (num / divisor)
  190 
  191 
  192 def show_detail(diffs, totals):
  193     """ just a pretty printer for some extra data """
  194     print("\n  {:2s}: {:8s} (pct  ) {:10s} (pct    ) {:6s} (pct)   {:6s}".format('sb',
  195                 'evicted', 'items', 'pages', 'age'))
  196 
  197     for sid, slab in diffs.items():
  198         if 'evicted_d' not in slab:
  199             continue
  200         print("  {:2d}: {:8d} ({:.2f}%) {:10d} ({:.4f}%) {:6d} ({:.2f}%) {:6d}".format(
  201               int(sid), slab['evicted_d'], pct(slab['evicted_d'], totals['evicted_d']),
  202               slab['number'], pct(slab['number'], totals['number']),
  203               slab['total_pages'], pct(slab['total_pages'],
  204               totals['total_pages']),
  205               slab['age']))
  206 
  207 
  208 stats_pre = {}
  209 history = { 'w': [{}] }
  210 while True:
  211     try:
  212         with socket.create_connection((host, port), 5) as c:
  213             s = c.makefile(mode="rw", buffering=1)
  214             s.write("slabs automove 0\r\n")
  215             print(s.readline().rstrip())
  216             while True:
  217                 stats_post = read_stats(s)
  218                 (diffs, totals) = diff_stats(stats_pre, stats_post)
  219                 if args.verbose:
  220                     show_detail(diffs, totals)
  221                 decision = determine_move(history, diffs, totals)
  222                 if decision[0] != -1 and decision[1] != -1:
  223                     print("moving page from, to:", decision)
  224                     if args.automove:
  225                         run_move(s, decision)
  226 
  227                 # Minimize sleeping if we just moved a page to global pool.
  228                 # Improves responsiveness during flushes/quick changes.
  229                 if decision[1] == 0:
  230                     sleep(0.05)
  231                 else:
  232                     sleep(args.sleep)
  233                 stats_pre = stats_post
  234     except:
  235         err = sys.exc_info()
  236         print("disconnected:", err[0], err[1])
  237         traceback.print_exc()
  238         stats_pre = {}
  239         history = { 'w': [{}] }
  240         sleep(args.sleep)
  241