1*99fc043aSdormando#! /usr/bin/perl 2*99fc043aSdormando# See memcached for LICENSE 3*99fc043aSdormando# Copyright 2011 Dormando ([email protected]) 4*99fc043aSdormando 5*99fc043aSdormando=head1 NAME 6*99fc043aSdormando 7*99fc043aSdormandomc_slab_mover -- example utility for slab page reassignment for memcached 8*99fc043aSdormando 9*99fc043aSdormando=head1 SYNOPSIS 10*99fc043aSdormando 11*99fc043aSdormando $ mc_slab_mover --host="127.0.0.1:11211" --verbose 12*99fc043aSdormando $ mc_slab_mover --host="127.0.0.1:11211" --automove 13*99fc043aSdormando $ mc_slab_mover --host="127.0.0.1:11211" --sleep=60 --loops=4 --automove 14*99fc043aSdormando 15*99fc043aSdormando=head1 DESCRIPTION 16*99fc043aSdormando 17*99fc043aSdormandoThis utility is an example implementation of an algorithm for reassigning 18*99fc043aSdormandoslab memory in a running memcached instance. If memcached's built-in 19*99fc043aSdormandoautomover isn't working for you, you may use this script as an example 20*99fc043aSdormandobase and expand on it. We welcome modifications or alternatives on the 21*99fc043aSdormandomailing list. 22*99fc043aSdormando 23*99fc043aSdormando=head1 ALGORITHM 24*99fc043aSdormando 25*99fc043aSdormandoThe default algorithm is simple, and may serve for a common case: over 26*99fc043aSdormandotime one slab may grow in use compare to others, and as evictions stop 27*99fc043aSdormandoin one slab and start in another it will reassign memory. 28*99fc043aSdormando 29*99fc043aSdormandoIf a slab has the most evictions three times in a row, it will pull a page 30*99fc043aSdormandofrom a slab which has had zero evictions three times in a row. 31*99fc043aSdormando 32*99fc043aSdormandoThere are many traffic patterns where this does not work well. IE: If you 33*99fc043aSdormandonever use expirations and rely on the LRU (so all slabs always evict), 34*99fc043aSdormandoit will not be as likely to find source pages to move. 35*99fc043aSdormando 36*99fc043aSdormando=head1 OPTIONS 37*99fc043aSdormando 38*99fc043aSdormando=over 39*99fc043aSdormando 40*99fc043aSdormando=item --host="IP:PORT" 41*99fc043aSdormando 42*99fc043aSdormandoThe hostname to connect to. NOTE: If connection to the host breaks, script 43*99fc043aSdormandowill stop. 44*99fc043aSdormando 45*99fc043aSdormando=item --sleep=10 46*99fc043aSdormando 47*99fc043aSdormandoHow long to wait between loops for gathering stats. 48*99fc043aSdormando 49*99fc043aSdormando=item --loops=3 50*99fc043aSdormando 51*99fc043aSdormandoHow many loops to run before making a decision for a move. 52*99fc043aSdormando 53*99fc043aSdormando=item --verbose 54*99fc043aSdormando 55*99fc043aSdormandoPrints a formatted dump of some common statistics per loop. 56*99fc043aSdormando 57*99fc043aSdormando=item --automove 58*99fc043aSdormando 59*99fc043aSdormandoEnables the automover, and will attempt to move memory around if it finds 60*99fc043aSdormandoviable candidates. 61*99fc043aSdormando 62*99fc043aSdormando=back 63*99fc043aSdormando 64*99fc043aSdormando=head1 AUTHOR 65*99fc043aSdormando 66*99fc043aSdormandoDormando E<lt>L<[email protected]>E<gt> 67*99fc043aSdormando 68*99fc043aSdormando=head1 LICENSE 69*99fc043aSdormando 70*99fc043aSdormandoLicensed for use and redistribution under the same terms as Memcached itself. 71*99fc043aSdormando 72*99fc043aSdormando=cut 73*99fc043aSdormando 74*99fc043aSdormandouse warnings; 75*99fc043aSdormandouse strict; 76*99fc043aSdormando 77*99fc043aSdormandouse IO::Socket::INET; 78*99fc043aSdormando 79*99fc043aSdormandouse FindBin; 80*99fc043aSdormandouse Data::Dumper qw/Dumper/; 81*99fc043aSdormandouse Getopt::Long; 82*99fc043aSdormando 83*99fc043aSdormandomy %opts = ('sleep' => 10, automove => 0, verbose => 0, loops => 3); 84*99fc043aSdormandoGetOptions( 85*99fc043aSdormando "host=s" => \$opts{host}, 86*99fc043aSdormando "sleep=i" => \$opts{'sleep'}, 87*99fc043aSdormando "loops=i" => \$opts{loops}, 88*99fc043aSdormando "automove" => \$opts{automove}, 89*99fc043aSdormando "verbose" => \$opts{verbose}, 90*99fc043aSdormando ) or usage(); 91*99fc043aSdormando 92*99fc043aSdormandodie "Must specify at least --host='127.0.0.1:11211'" unless $opts{host}; 93*99fc043aSdormandomy $sock = IO::Socket::INET->new(PeerAddr => $opts{host}, 94*99fc043aSdormando Timeout => 3); 95*99fc043aSdormandodie "$!\n" unless $sock; 96*99fc043aSdormando 97*99fc043aSdormandomy %stats = (); 98*99fc043aSdormandomy %move = (winner => 0, wins => 0); 99*99fc043aSdormando 100*99fc043aSdormando$SIG{INT} = sub { 101*99fc043aSdormando print "STATS: ", Dumper(\%stats), "\n"; 102*99fc043aSdormando exit; 103*99fc043aSdormando}; 104*99fc043aSdormando$SIG{USR1} = sub { 105*99fc043aSdormando print "STATS: ", Dumper(\%stats), "\n"; 106*99fc043aSdormando}; 107*99fc043aSdormandorun(); 108*99fc043aSdormando 109*99fc043aSdormandosub usage { 110*99fc043aSdormando print qq{Usage: 111*99fc043aSdormando mc_slab_ratios --host="127.0.0.1:11211" --verbose --automove 112*99fc043aSdormando run `perldoc mc_slab_ratios` for full information 113*99fc043aSdormando 114*99fc043aSdormando}; 115*99fc043aSdormando exit 1; 116*99fc043aSdormando} 117*99fc043aSdormando 118*99fc043aSdormandosub run { 119*99fc043aSdormando my $slabs_before = grab_stats(); 120*99fc043aSdormando 121*99fc043aSdormando while (1) { 122*99fc043aSdormando sleep $opts{'sleep'}; 123*99fc043aSdormando my $slabs_after = grab_stats(); 124*99fc043aSdormando 125*99fc043aSdormando my ($totals, $sorted) = calc_results_evicted($slabs_before, $slabs_after); 126*99fc043aSdormando# my ($totals, $sorted) = calc_results_numratio($slabs_before, $slabs_after); 127*99fc043aSdormando 128*99fc043aSdormando my $pct = sub { 129*99fc043aSdormando my ($num, $divisor) = @_; 130*99fc043aSdormando return 0 unless $divisor; 131*99fc043aSdormando return ($num / $divisor); 132*99fc043aSdormando }; 133*99fc043aSdormando if ($opts{verbose}) { 134*99fc043aSdormando printf " %02s: %-8s (pct ) %-10s (pct ) %-6s (pct ) get_hits (pct ) cmd_set (pct )\n", 135*99fc043aSdormando 'sb', 'evicted', 'items', 'pages'; 136*99fc043aSdormando for my $slab (@$sorted) { 137*99fc043aSdormando printf " %02d: %-8d (%.2f%%) %-10s (%.4f%%) %-6d (%.2f%%) %-8d (%.3f%%) %-7d (%.2f%%)\n", 138*99fc043aSdormando $slab->{slab}, $slab->{evicted_d}, 139*99fc043aSdormando $pct->($slab->{evicted_d}, $totals->{evicted_d}), 140*99fc043aSdormando $slab->{number}, 141*99fc043aSdormando $pct->($slab->{number}, $totals->{number}), 142*99fc043aSdormando $slab->{total_pages}, 143*99fc043aSdormando $pct->($slab->{total_pages}, $totals->{total_pages}), 144*99fc043aSdormando $slab->{get_hits_d}, 145*99fc043aSdormando $pct->($slab->{get_hits_d}, $totals->{get_hits_d}), 146*99fc043aSdormando $slab->{cmd_set_d}, 147*99fc043aSdormando $pct->($slab->{cmd_set_d}, $totals->{cmd_set_d}); 148*99fc043aSdormando } 149*99fc043aSdormando } 150*99fc043aSdormando 151*99fc043aSdormando next unless @$sorted; 152*99fc043aSdormando my $highest = $sorted->[-1]; 153*99fc043aSdormando $stats{$highest->{slab}}++; 154*99fc043aSdormando print " (winner: ", $highest->{slab}, " wins: ", $stats{$highest->{slab}}, ")\n"; 155*99fc043aSdormando automove_basic($totals, $sorted) if ($opts{automove}); 156*99fc043aSdormando 157*99fc043aSdormando $slabs_before = $slabs_after; 158*99fc043aSdormando } 159*99fc043aSdormando} 160*99fc043aSdormando 161*99fc043aSdormandosub grab_stats { 162*99fc043aSdormando my %slabs = (); 163*99fc043aSdormando for my $stat (qw/items slabs/) { 164*99fc043aSdormando print $sock "stats $stat\r\n"; 165*99fc043aSdormando while (my $line = <$sock>) { 166*99fc043aSdormando chomp $line; 167*99fc043aSdormando last if ($line =~ m/^END/); 168*99fc043aSdormando if ($line =~ m/^STAT (?:items:)?(\d+):(\S+) (\S+)/) { 169*99fc043aSdormando my ($slab, $var, $val) = ($1, $2, $3); 170*99fc043aSdormando $slabs{$slab}->{$var} = $val; 171*99fc043aSdormando } 172*99fc043aSdormando } 173*99fc043aSdormando } 174*99fc043aSdormando 175*99fc043aSdormando return \%slabs; 176*99fc043aSdormando} 177*99fc043aSdormando 178*99fc043aSdormando# Really stupid algo, same as the initial algo built into memcached. 179*99fc043aSdormando# If a slab "wins" most evictions 3 times in a row, pick from a slab which 180*99fc043aSdormando# has had 0 evictions 3 times in a row and move it over. 181*99fc043aSdormandosub automove_basic { 182*99fc043aSdormando my ($totals, $sorted) = @_; 183*99fc043aSdormando 184*99fc043aSdormando my $source = 0; 185*99fc043aSdormando my $dest = 0; 186*99fc043aSdormando my $high = $sorted->[-1]; 187*99fc043aSdormando return unless $high->{evicted_d} > 0; 188*99fc043aSdormando if ($move{winner} == $high->{slab}) { 189*99fc043aSdormando $move{wins}++; 190*99fc043aSdormando $dest = $move{winner} if $move{wins} >= $opts{loops}; 191*99fc043aSdormando } else { 192*99fc043aSdormando $move{wins} = 1; 193*99fc043aSdormando $move{winner} = $high->{slab}; 194*99fc043aSdormando } 195*99fc043aSdormando for my $slab (@$sorted) { 196*99fc043aSdormando my $id = $slab->{slab}; 197*99fc043aSdormando if ($slab->{evicted_d} == 0 && $slab->{total_pages} > 2) { 198*99fc043aSdormando $move{zeroes}->{$id}++; 199*99fc043aSdormando $source = $id if (!$source && $move{zeroes}->{$id} >= $opts{loops}); 200*99fc043aSdormando } else { 201*99fc043aSdormando delete $move{zeroes}->{$slab->{slab}} 202*99fc043aSdormando if exists $move{zeroes}->{$slab->{slab}}; 203*99fc043aSdormando } 204*99fc043aSdormando } 205*99fc043aSdormando 206*99fc043aSdormando if ($source && $dest) { 207*99fc043aSdormando print " slabs reassign $source $dest\n"; 208*99fc043aSdormando print $sock "slabs reassign $source $dest\r\n"; 209*99fc043aSdormando my $res = <$sock>; 210*99fc043aSdormando print " RES: ", $res; 211*99fc043aSdormando } elsif ($dest && !$source) { 212*99fc043aSdormando print "FAIL: want to move memory to $dest but no valid source slab available\n"; 213*99fc043aSdormando } 214*99fc043aSdormando} 215*99fc043aSdormando 216*99fc043aSdormando# Using just the evicted stats. 217*99fc043aSdormandosub calc_results_evicted { 218*99fc043aSdormando my ($slabs, $totals) = calc_slabs(@_); 219*99fc043aSdormando my @sorted = sort { $a->{evicted_d} <=> $b->{evicted_d} } values %$slabs; 220*99fc043aSdormando return ($totals, \@sorted); 221*99fc043aSdormando} 222*99fc043aSdormando 223*99fc043aSdormando# Weighted ratios of evictions vs total stored items 224*99fc043aSdormando# Seems to fail as an experiment, but it tries to weight stats. 225*99fc043aSdormando# In this case evictions in underused classes tend to get vastly inflated 226*99fc043aSdormandosub calc_results_numratio { 227*99fc043aSdormando my ($slabs, $totals) = calc_slabs(@_, sub { 228*99fc043aSdormando my ($sb, $sa, $s) = @_; 229*99fc043aSdormando if ($s->{evicted_d}) { 230*99fc043aSdormando $s->{numratio} = $s->{evicted_d} / $s->{number}; 231*99fc043aSdormando } else { $s->{numratio} = 0; } 232*99fc043aSdormando }); 233*99fc043aSdormando my @sorted = sort { $a->{numratio} <=> $b->{numratio} } values %$slabs; 234*99fc043aSdormando return ($totals, \@sorted); 235*99fc043aSdormando} 236*99fc043aSdormando 237*99fc043aSdormandosub calc_slabs { 238*99fc043aSdormando my ($slabs_before, $slabs_after, $code) = @_; 239*99fc043aSdormando my %slabs = (); 240*99fc043aSdormando my %totals = (); 241*99fc043aSdormando for my $id (keys %$slabs_after) { 242*99fc043aSdormando my $sb = $slabs_before->{$id}; 243*99fc043aSdormando my $sa = $slabs_after->{$id}; 244*99fc043aSdormando next unless ($sb && $sa); 245*99fc043aSdormando my %slab = %$sa; 246*99fc043aSdormando for my $key (keys %slab) { 247*99fc043aSdormando # Add totals, diffs 248*99fc043aSdormando if ($slab{$key} =~ m/^\d+$/) { 249*99fc043aSdormando $totals{$key} += $slab{$key}; 250*99fc043aSdormando $slab{$key . '_d'} = $sa->{$key} - $sb->{$key}; 251*99fc043aSdormando $totals{$key . '_d'} += $sa->{$key} - $sb->{$key}; 252*99fc043aSdormando } 253*99fc043aSdormando } 254*99fc043aSdormando # External code 255*99fc043aSdormando $code->($sb, $sa, \%slab) if $code; 256*99fc043aSdormando $slab{slab} = $id; 257*99fc043aSdormando $slabs{$id} = \%slab; 258*99fc043aSdormando } 259*99fc043aSdormando return (\%slabs, \%totals); 260*99fc043aSdormando} 261