2 # See memcached for LICENSE
3 # Copyright 2011 Dormando (dormando@rydia.net)
7 mc_slab_mover -- example utility for slab page reassignment for memcached
11 $ mc_slab_mover --host="127.0.0.1:11211" --verbose
12 $ mc_slab_mover --host="127.0.0.1:11211" --automove
13 $ mc_slab_mover --host="127.0.0.1:11211" --sleep=60 --loops=4 --automove
17 This utility is an example implementation of an algorithm for reassigning
18 slab memory in a running memcached instance. If memcached's built-in
19 automover isn't working for you, you may use this script as an example
20 base and expand on it. We welcome modifications or alternatives on the
25 The default algorithm is simple, and may serve for a common case: over
26 time one slab may grow in use compare to others, and as evictions stop
27 in one slab and start in another it will reassign memory.
29 If a slab has the most evictions three times in a row, it will pull a page
30 from a slab which has had zero evictions three times in a row.
32 There are many traffic patterns where this does not work well. IE: If you
33 never use expirations and rely on the LRU (so all slabs always evict),
34 it will not be as likely to find source pages to move.
40 =item --host="IP:PORT"
42 The hostname to connect to. NOTE: If connection to the host breaks, script
47 How long to wait between loops for gathering stats.
51 How many loops to run before making a decision for a move.
55 Prints a formatted dump of some common statistics per loop.
59 Enables the automover, and will attempt to move memory around if it finds
66 Dormando E<lt>L<dormando@rydia.net>E<gt>
70 Licensed for use and redistribution under the same terms as Memcached itself.
80 use Data
::Dumper qw
/Dumper/;
83 my %opts = ('sleep' => 10, automove
=> 0, verbose
=> 0, loops
=> 3);
85 "host=s" => \
$opts{host
},
86 "sleep=i" => \
$opts{'sleep'},
87 "loops=i" => \
$opts{loops
},
88 "automove" => \
$opts{automove
},
89 "verbose" => \
$opts{verbose
},
92 die "Must specify at least --host='127.0.0.1:11211'" unless $opts{host
};
93 my $sock = IO
::Socket
::INET
->new(PeerAddr
=> $opts{host
},
95 die "$!\n" unless $sock;
98 my %move = (winner
=> 0, wins
=> 0);
101 print "STATS: ", Dumper
(\
%stats), "\n";
105 print "STATS: ", Dumper
(\
%stats), "\n";
111 mc_slab_ratios
--host
="127.0.0.1:11211" --verbose
--automove
112 run
`perldoc mc_slab_ratios` for full information
119 my $slabs_before = grab_stats
();
122 sleep $opts{'sleep'};
123 my $slabs_after = grab_stats
();
125 my ($totals, $sorted) = calc_results_evicted
($slabs_before, $slabs_after);
126 # my ($totals, $sorted) = calc_results_numratio($slabs_before, $slabs_after);
129 my ($num, $divisor) = @_;
130 return 0 unless $divisor;
131 return ($num / $divisor);
133 if ($opts{verbose
}) {
134 printf " %02s: %-8s (pct ) %-10s (pct ) %-6s (pct ) get_hits (pct ) cmd_set (pct )\n",
135 'sb', 'evicted', 'items', 'pages';
136 for my $slab (@
$sorted) {
137 printf " %02d: %-8d (%.2f%%) %-10s (%.4f%%) %-6d (%.2f%%) %-8d (%.3f%%) %-7d (%.2f%%)\n",
138 $slab->{slab
}, $slab->{evicted_d
},
139 $pct->($slab->{evicted_d
}, $totals->{evicted_d
}),
141 $pct->($slab->{number
}, $totals->{number
}),
142 $slab->{total_pages
},
143 $pct->($slab->{total_pages
}, $totals->{total_pages
}),
145 $pct->($slab->{get_hits_d
}, $totals->{get_hits_d
}),
147 $pct->($slab->{cmd_set_d
}, $totals->{cmd_set_d
});
151 next unless @
$sorted;
152 my $highest = $sorted->[-1];
153 $stats{$highest->{slab
}}++;
154 print " (winner: ", $highest->{slab
}, " wins: ", $stats{$highest->{slab
}}, ")\n";
155 automove_basic
($totals, $sorted) if ($opts{automove
});
157 $slabs_before = $slabs_after;
163 for my $stat (qw
/items slabs/) {
164 print $sock "stats $stat\r\n";
165 while (my $line = <$sock>) {
167 last if ($line =~ m/^END/);
168 if ($line =~ m/^STAT (?:items:)?(\d+):(\S+) (\S+)/) {
169 my ($slab, $var, $val) = ($1, $2, $3);
170 $slabs{$slab}->{$var} = $val;
178 # Really stupid algo, same as the initial algo built into memcached.
179 # If a slab "wins" most evictions 3 times in a row, pick from a slab which
180 # has had 0 evictions 3 times in a row and move it over.
182 my ($totals, $sorted) = @_;
186 my $high = $sorted->[-1];
187 return unless $high->{evicted_d
} > 0;
188 if ($move{winner
} == $high->{slab
}) {
190 $dest = $move{winner
} if $move{wins
} >= $opts{loops
};
193 $move{winner
} = $high->{slab
};
195 for my $slab (@
$sorted) {
196 my $id = $slab->{slab
};
197 if ($slab->{evicted_d
} == 0 && $slab->{total_pages
} > 2) {
198 $move{zeroes
}->{$id}++;
199 $source = $id if (!$source && $move{zeroes
}->{$id} >= $opts{loops
});
201 delete $move{zeroes
}->{$slab->{slab
}}
202 if exists $move{zeroes
}->{$slab->{slab
}};
206 if ($source && $dest) {
207 print " slabs reassign $source $dest\n";
208 print $sock "slabs reassign $source $dest\r\n";
210 print " RES: ", $res;
211 } elsif ($dest && !$source) {
212 print "FAIL: want to move memory to $dest but no valid source slab available\n";
216 # Using just the evicted stats.
217 sub calc_results_evicted
{
218 my ($slabs, $totals) = calc_slabs
(@_);
219 my @sorted = sort { $a->{evicted_d
} <=> $b->{evicted_d
} } values %$slabs;
220 return ($totals, \
@sorted);
223 # Weighted ratios of evictions vs total stored items
224 # Seems to fail as an experiment, but it tries to weight stats.
225 # In this case evictions in underused classes tend to get vastly inflated
226 sub calc_results_numratio
{
227 my ($slabs, $totals) = calc_slabs
(@_, sub {
228 my ($sb, $sa, $s) = @_;
229 if ($s->{evicted_d
}) {
230 $s->{numratio
} = $s->{evicted_d
} / $s->{number
};
231 } else { $s->{numratio
} = 0; }
233 my @sorted = sort { $a->{numratio
} <=> $b->{numratio
} } values %$slabs;
234 return ($totals, \
@sorted);
238 my ($slabs_before, $slabs_after, $code) = @_;
241 for my $id (keys %$slabs_after) {
242 my $sb = $slabs_before->{$id};
243 my $sa = $slabs_after->{$id};
244 next unless ($sb && $sa);
246 for my $key (keys %slab) {
248 if ($slab{$key} =~ m/^\d+$/) {
249 $totals{$key} += $slab{$key};
250 $slab{$key . '_d'} = $sa->{$key} - $sb->{$key};
251 $totals{$key . '_d'} += $sa->{$key} - $sb->{$key};
255 $code->($sb, $sa, \
%slab) if $code;
257 $slabs{$id} = \
%slab;
259 return (\
%slabs, \
%totals);