t/slabs-mover.t 418 lines View on github.com → Search inside
1#!/usr/bin/env perl23use strict;4use warnings;5use Test::More;6use FindBin qw($Bin);7use lib "$Bin/lib";8use MemcachedTest;9use Data::Dumper qw/Dumper/;1011# Enable manual slab reassign, cap at 6 slabs12# Items above 16kb are chunked13# disable LRU crawler so we can be sure to move expired items14my $server = new_memcached('-o no_lru_crawler,slab_reassign,slab_automove=0,slab_chunk_max=16 -m 12');15my $sock = $server->sock;1617{18    subtest 'automove window' => \&test_automove_window;19    subtest 'syntax' => \&test_syntax;20    subtest 'fill and move pages' => \&test_fill;21    subtest 'chunked items' => \&test_chunked;22    subtest 'test locked' => \&test_locked;23    subtest 'expired items' => \&test_expired;24    # Need clean memory for the reflock tests25    print $sock "flush_all\r\n";26    is(scalar <$sock>, "OK\r\n", "flushed items before reflock test");27    wait_for_stat("curr_items", 0);28    subtest 'reflocked items' => \&test_reflocked;29    # test reflocked chunked items (ensure busy_deletes)30}3132# If I still remembered perl I'd do that dynamic comparator thing33sub wait_for_stat {34    my $stat = shift;35    my $amt = shift; # 0 is valid3637    my $stats_a;38    my $to_sleep = 0.01;39    for my $cnt (1 .. 500) {40        $stats_a = mem_stats($sock);41        last if ($stats_a->{$stat} == $amt);42        sleep $to_sleep;43        $to_sleep += $cnt / 100;44        $to_sleep = 0.2 if $to_sleep > 0.2;45        #print STDERR "SLEEPING: $to_sleep STAT: ", $stats_a->{$stat}, " LAST BUSY: ", $stats_a->{slab_reassign_last_busy_status}, "\n";46    }47    return $stats_a;48}4950sub wait_for_stat_incr {51    my $stats = shift;52    my $stat = shift;53    my $amt = shift; # 0 is valid5455    my $stats_a;56    my $to_sleep = 0.01;57    for my $cnt (1 .. 500) {58        $stats_a = mem_stats($sock);59        last if ($stats_a->{$stat} > $stats->{$stat}+$amt);60        sleep $to_sleep;61        $to_sleep += $cnt / 100;62        $to_sleep = 0.2 if $to_sleep > 0.2;63        #print STDERR "SLEEPING: $to_sleep STAT: ", $stats_a->{$stat}, " LAST BUSY: ", $stats_a->{slab_reassign_last_busy_status}, "\n";64        #print STDERR Dumper(map { $_ => $stats_a->{$_} } sort keys %$stats_a), "\n";65    }66    return $stats_a;67}6869sub find_largest_clsid {70    my $s = mem_stats($sock, 'slabs');71    my $sid = 0;72    my $total_pages = 0;73    # Find the highest ID to source from.74    for my $k (keys %$s) {75        next unless $k =~ m/^(\d+):total_pages/;76        if ($s->{$k} > $total_pages) {77            $sid = $1;78            $total_pages = $s->{$k};79        }80    }81    return $sid;82}8384# TODO: mc metric for automove runs so we can loop for that instead of blind85# sleep.86sub test_automove_window {87    print $sock "slabs automove 1\r\n";88    is(scalar <$sock>, "OK\r\n", "automover on");89    print $sock "slabs automove window 0\r\n";90    is(scalar <$sock>, "CLIENT_ERROR automove window too low or too high\r\n", "invalid window");9192    sleep 4;93    print $sock "version\r\n";94    like(scalar <$sock>, qr/VERSION/, "daemon still alive");9596    print $sock "slabs automove window 67108864\r\n";97    is(scalar <$sock>, "CLIENT_ERROR automove window too low or too high\r\n", "invalid window");98    sleep 4;99100    print $sock "version\r\n";101    like(scalar <$sock>, qr/VERSION/, "daemon still alive");102103    print $sock "slabs automove 0\r\n";104    is(scalar <$sock>, "OK\r\n", "automover off");105}106107# NOTE: Can't validate reflocked items in an integration test since we leak108# the memory and cannot de-ref an unlinked item.109# TODO: test reflocked chunked items as well110sub test_reflocked {111    my $size = 9000;112    my $bigdata = 'x' x $size;113    my $stats;114    my $count = 1;115    $stats = mem_stats($sock);116    for my $c (1 .. 10000) {117        my $exp = $c % 2 == 1 ? "T0" : "T240";118        print $sock "ms rfoo$c $size $exp\r\n", $bigdata, "\r\n";119        is(scalar <$sock>, "HD\r\n", "stored big key: $c [$exp]");120        my $s_after = mem_stats($sock);121        last if ($s_after->{evictions} > $stats->{evictions});122        $count++;123    }124125    # delete one page worth so we have memory to work with to move pages126    # non-destructively.127    my $todelete = int((1024 * 1024) / $size)+1;128    for (0 .. $todelete) {129        # delete from the newest since we move the oldest page.130        # encourages chunk rescues.131        my $i = $count - $_;132        print $sock "delete rfoo$i\r\n";133        is(scalar <$sock>, "DELETED\r\n", "deleted rfoo$i");134    }135136    # attempt to reflock a bunch so we hit both expired and active rescue137    for (2 .. $count - ($todelete+1)) {138        print $sock "debugitem ref rfoo$_\r\n";139        is(scalar <$sock>, "OK\r\n", "reflocked rfoo$_");140    }141142    mem_move_time($sock, 480);143144    my $sid = find_largest_clsid($sock);145146    $stats = mem_stats($sock);147    print $sock "slabs reassign $sid 0\r\n";148    is(scalar <$sock>, "OK\r\n", "reassign started");149    my $stats_a = wait_for_stat_incr($stats, "slab_reassign_busy_items", 500);150151    cmp_ok($stats_a->{slab_reassign_busy_items}, '>', $stats->{slab_reassign_busy_items}+500, "page mover busy");152    is($stats_a->{slab_reassign_last_busy_status}, "busy_floating", "recorded busy status correctly");153    # TODO: rescue counter is only updated after a page completes moving.154    #cmp_ok($stats_a->{slab_reassign_rescues}, '>', $stats->{slab_reassign_rescues}+50, "page mover rescued data");155    cmp_ok($stats_a->{slabs_moved}, '==', $stats->{slabs_moved}, "no page moved");156157}158159# TODO: reflock some items so we hit the "expired but reflocked" path160# TODO: use debugtime to move clock and avoid having to sleep.161# - also so we can reflock some items without having a race condition by162# setting the expire time out a minute/hour.163sub test_expired {164    my $size = 9000;165    my $bigdata = 'x' x $size;166    my $stats;167    my $count = 1;168    $stats = mem_stats($sock);169    for my $c (1 .. 10000) {170        my $exp = $c % 2 == 1 ? "T0" : "T30";171        print $sock "ms efoo$c $size $exp\r\n", $bigdata, "\r\n";172        is(scalar <$sock>, "HD\r\n", "stored big key");173        my $s_after = mem_stats($sock);174        last if ($s_after->{evictions} > $stats->{evictions});175        $count++;176    }177178    mem_move_time($sock, 60);179180    my $sid = find_largest_clsid($sock);181182    $stats = mem_stats($sock);183    empty_class(mem_stats($sock), $sid);184185    my $stats_a = mem_stats($sock);186    # TODO: there's no counter for "expired items reaped"187    # if we get here we at least didn't crash though.188}189190# use debugitem command to deliberately hang the page mover191# NOTE: test is slightly flaky since we have to pick an item to lock that's in192# the page we want to move. this won't normally change but could if you193# re-order the tests.194sub test_locked {195    my $size = 9000;196    my $bigdata = 'x' x $size;197    my $stats;198    my $count = 1;199    $stats = mem_stats($sock);200    for (1 .. 10000) {201        print $sock "set lfoo$_ 0 0 $size\r\n", $bigdata, "\r\n";202        is(scalar <$sock>, "STORED\r\n", "stored big key");203        my $s_after = mem_stats($sock);204        last if ($s_after->{evictions} > $stats->{evictions});205        $count++;206    }207208    my $s = mem_stats($sock, 'slabs');209    my $sid = 0;210    my $total_pages = 0;211    # Find the highest ID to source from.212    for my $k (keys %$s) {213        next unless $k =~ m/^(\d+):total_pages/;214        if ($s->{$k} > $total_pages) {215            $sid = $1;216            $total_pages = $s->{$k};217        }218    }219220    print $sock "debugitem lock lfoo50\r\n";221    is(scalar <$sock>, "OK\r\n", "locked item");222223    $stats = mem_stats($sock);224    print $sock "slabs reassign $sid 0\r\n";225    is(scalar <$sock>, "OK\r\n", "reassign started");226    my $stats_a = wait_for_stat_incr($stats, "slab_reassign_busy_items", 500);227228    cmp_ok($stats_a->{slab_reassign_busy_items}, '>', $stats->{slab_reassign_busy_items}+500, "page mover busy");229    cmp_ok($stats_a->{slabs_moved}, '==', $stats->{slabs_moved}, "no page moved");230    is($stats_a->{slab_reassign_running}, 1, "reassign stuck running");231232    print $sock "debugitem unlock lfoo50\r\n";233    is(scalar <$sock>, "OK\r\n", "unlocked item");234235    $stats_a = wait_for_stat_incr($stats, "slabs_moved", 0);236    cmp_ok($stats_a->{slabs_moved}, '>', $stats->{slabs_moved}, "page moved");237    is($stats_a->{slab_reassign_running}, 0, "reassign stopped running");238239    empty_class(mem_stats($sock), $sid);240    $stats = mem_stats($sock);241    cmp_ok($stats->{slab_global_page_pool}, '>', 5, "pages back in global pool");242}243244sub test_chunked {245    # Patterned value so we can tell if chunks get corrupted246    my $value;247    my $size = 30000;248    {249        my @chars = ("C".."Z");250        for (1 .. $size) {251            $value .= $chars[rand @chars];252        }253    }254255    my $stats = mem_stats($sock);256257    my $count = 1;258    while (1) {259        print $sock "set cfoo$count 0 0 $size\r\n$value\r\n";260        is(scalar <$sock>, "STORED\r\n", "stored chunked item");261        my $s_after = mem_stats($sock);262        last if ($s_after->{evictions} > $stats->{evictions});263        $count++;264    }265266    # hold stats to compare evictions later267    $stats = mem_stats($sock);268    my $s = mem_stats($sock, 'slabs');269    my $sid = 0;270    # Find the highest ID to source from.271    for my $k (keys %$s) {272        next unless $k =~ m/^(\d+):/;273        $sid = $1 if $1 > $sid;274    }275276    cmp_ok($s->{"$sid:total_pages"}, '>', 5, "most pages in high class");277    cmp_ok($s->{"$sid:used_chunks"}, '>', 500, "many chunks in use");278279    # move one page while full to force evictions280    print $sock "slabs reassign $sid 0\r\n";281    my $stats_a = wait_for_stat_incr($stats, "slabs_moved", 0);282    cmp_ok($stats_a->{evictions}, '>', $stats->{evictions}, "page move caused some evictions: " . ($stats_a->{evictions} - $stats->{evictions}));283284    # delete at least a page worth so we can test rescuing data285    $s = mem_stats($sock, 'slabs');286    cmp_ok($s->{"$sid:free_chunks"}, '<', 5, "few free chunks available to start");287    cmp_ok($stats->{slab_reassign_chunk_rescues}, '<', 1, 'few chunk rescues happened');288    my $todelete = int((1024 * 1024) / $size)+1;289    for (0 .. $todelete) {290        # delete from the newest since we move the oldest page.291        # encourages chunk rescues.292        my $i = $count - $_;293        print $sock "delete cfoo$i\r\n";294        is(scalar <$sock>, "DELETED\r\n", "deleted cfoo$i");295    }296297    $stats = mem_stats($sock);298    print $sock "slabs reassign $sid 0\r\n";299    $stats_a = wait_for_stat_incr($stats, "slabs_moved", 0);300301    cmp_ok($stats_a->{slab_reassign_chunk_rescues}, '>', 50, 'more chunk rescues happened');302    cmp_ok($stats_a->{slab_reassign_busy_deletes}, '==', $stats->{slab_reassign_busy_deletes}, 'no busy deletes');303304    # TODO: fetch all items back and check value305306    empty_class(mem_stats($sock), $sid);307    $stats = mem_stats($sock);308    cmp_ok($stats->{slab_global_page_pool}, '>', 6, "pages back in global pool");309}310311# Fill test, no chunked items.312sub test_fill {313    my $size = 15000;314    my $bigdata = 'x' x $size;315    my $stats;316    for (1 .. 10000) {317        print $sock "set bfoo$_ 0 0 $size\r\n", $bigdata, "\r\n";318        is(scalar <$sock>, "STORED\r\n", "stored big key");319        $stats = mem_stats($sock);320        last if ($stats->{evictions} != 0);321    }322323    # fill a smaller slab too324    $size = 2000;325    my $smalldata = 'y' x $size;326    for (1 .. 10000) {327        print $sock "set sfoo$_ 0 0 $size\r\n", $smalldata, "\r\n";328        is(scalar <$sock>, "STORED\r\n", "stored small key");329        my $nstats = mem_stats($sock);330        last if ($stats->{evictions} < $nstats->{evictions});331    }332333    my $slabs_before = mem_stats($sock, "slabs");334    #print STDERR Dumper($slabs_before), "\n\n";335    # Find our two slab classes.336    # low, high337    my @classes = sort map { /(\d+):/ } grep {/total_pages/} keys %$slabs_before;338    is(scalar @classes, 2, "right number of active classes");339    my $cls_small = $classes[0];340    my $cls_big = $classes[1];341342    $stats = mem_stats($sock);343    is($stats->{slabs_moved}, 0, "no slabs moved before testing");344    is($stats->{evictions}, 2, "only two total evictions before testing");345    print $sock "slabs reassign $cls_big $cls_small\r\n";346    is(scalar <$sock>, "OK\r\n", "slab rebalancer started: $cls_big -> $cls_small");347348    $stats = wait_for_stat_incr($stats, "slabs_moved", 0);349350    isnt($stats->{slabs_moved}, 0, "slab moved within time limit");351    my $slabs_after = mem_stats($sock, "slabs");352    isnt($slabs_before->{"$cls_small:total_pages"}, $slabs_after->{"$cls_small:total_pages"},353        "slab $cls_small pagecount changed");354    isnt($slabs_before->{"$cls_big:total_pages"}, $slabs_after->{"$cls_big:total_pages"},355        "slab $cls_big pagecount changed");356    cmp_ok($stats->{slab_reassign_busy_nomem}, '>', 1, 'busy looped due to lack of memory');357    cmp_ok($stats->{evictions}, '>', 10, 'ran normal evictions to move page');358    # inline reclaim and other stats might be nonzero: evicted, then tried to359    # allocate memory and it was from the page we intended to move.360    # not intentionally causing that here.361362    # Move another page363    my $stats_after;364    print $sock "slabs reassign $cls_big $cls_small\r\n";365    is(scalar <$sock>, "OK\r\n", "slab rebalancer started: $cls_big -> $cls_small");366367    $stats_after = wait_for_stat_incr($stats, "slabs_moved", 0);368369    cmp_ok($stats_after->{slabs_moved}, '>', $stats->{slabs_moved}, 'moved another page');370371    # move all possible pages back to global372    empty_class(mem_stats($sock), $cls_big);373    empty_class(mem_stats($sock), $cls_small);374    $stats = mem_stats($sock);375    is($stats->{slab_global_page_pool}, 10, "pages back in global pool");376377    #print STDERR Dumper(map { $_ => $stats->{$_} } sort keys %$stats), "\n";378}379380sub empty_class {381    my $stats = shift;382    my $cls = shift;383    my $stats_after = $stats;384    while (1) {385        $stats = $stats_after;386        print $sock "slabs reassign $cls 0\r\n";387        my $res = <$sock>;388        if ($res =~ m/NOSPARE/) {389            pass("NOSPARE received while moving pages");390            last;391        }392        is($res, "OK\r\n", "slab rebalancer started: $cls -> 0");393        $stats_after = wait_for_stat_incr($stats, "slabs_moved", 0);394    }395}396397sub test_syntax {398    my $stats = mem_stats($sock, ' settings');399    is($stats->{slab_reassign}, "yes");400401    print $sock "slabs reassign invalid1 invalid2\r\n";402    is(scalar <$sock>, "CLIENT_ERROR bad command line format\r\n");403404    print $sock "slabs reassign 5\r\n";405    is(scalar <$sock>, "ERROR\r\n");406407    print $sock "slabs reassign 1 1\r\n";408    is(scalar <$sock>, "SAME src and dst class are identical\r\n");409410    print $sock "slabs reassign -5 70\r\n";411    is(scalar <$sock>, "BADCLASS invalid src or dst class id\r\n");412413    print $sock "slabs reassign 2 1\r\n";414    is(scalar <$sock>, "NOSPARE source class has no spare pages\r\n");415}416417done_testing();

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings