ac984d7b7d3c7cffc1852026a3afae7fd67a26b4
[awesomized/libmemcached] / memcached / scripts / mc_slab_mover
1 #! /usr/bin/perl
2 # See memcached for LICENSE
3 # Copyright 2011 Dormando (dormando@rydia.net)
4
5 =head1 NAME
6
7 mc_slab_mover -- example utility for slab page reassignment for memcached
8
9 =head1 SYNOPSIS
10
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
14
15 =head1 DESCRIPTION
16
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
21 mailing list.
22
23 =head1 ALGORITHM
24
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.
28
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.
31
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.
35
36 =head1 OPTIONS
37
38 =over
39
40 =item --host="IP:PORT"
41
42 The hostname to connect to. NOTE: If connection to the host breaks, script
43 will stop.
44
45 =item --sleep=10
46
47 How long to wait between loops for gathering stats.
48
49 =item --loops=3
50
51 How many loops to run before making a decision for a move.
52
53 =item --verbose
54
55 Prints a formatted dump of some common statistics per loop.
56
57 =item --automove
58
59 Enables the automover, and will attempt to move memory around if it finds
60 viable candidates.
61
62 =back
63
64 =head1 AUTHOR
65
66 Dormando E<lt>L<dormando@rydia.net>E<gt>
67
68 =head1 LICENSE
69
70 Licensed for use and redistribution under the same terms as Memcached itself.
71
72 =cut
73
74 use warnings;
75 use strict;
76
77 use IO::Socket::INET;
78
79 use FindBin;
80 use Data::Dumper qw/Dumper/;
81 use Getopt::Long;
82
83 my %opts = ('sleep' => 10, automove => 0, verbose => 0, loops => 3);
84 GetOptions(
85 "host=s" => \$opts{host},
86 "sleep=i" => \$opts{'sleep'},
87 "loops=i" => \$opts{loops},
88 "automove" => \$opts{automove},
89 "verbose" => \$opts{verbose},
90 ) or usage();
91
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},
94 Timeout => 3);
95 die "$!\n" unless $sock;
96
97 my %stats = ();
98 my %move = (winner => 0, wins => 0);
99
100 $SIG{INT} = sub {
101 print "STATS: ", Dumper(\%stats), "\n";
102 exit;
103 };
104 $SIG{USR1} = sub {
105 print "STATS: ", Dumper(\%stats), "\n";
106 };
107 run();
108
109 sub usage {
110 print qq{Usage:
111 mc_slab_ratios --host="127.0.0.1:11211" --verbose --automove
112 run `perldoc mc_slab_ratios` for full information
113
114 };
115 exit 1;
116 }
117
118 sub run {
119 my $slabs_before = grab_stats();
120
121 while (1) {
122 sleep $opts{'sleep'};
123 my $slabs_after = grab_stats();
124
125 my ($totals, $sorted) = calc_results_evicted($slabs_before, $slabs_after);
126 # my ($totals, $sorted) = calc_results_numratio($slabs_before, $slabs_after);
127
128 my $pct = sub {
129 my ($num, $divisor) = @_;
130 return 0 unless $divisor;
131 return ($num / $divisor);
132 };
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}),
140 $slab->{number},
141 $pct->($slab->{number}, $totals->{number}),
142 $slab->{total_pages},
143 $pct->($slab->{total_pages}, $totals->{total_pages}),
144 $slab->{get_hits_d},
145 $pct->($slab->{get_hits_d}, $totals->{get_hits_d}),
146 $slab->{cmd_set_d},
147 $pct->($slab->{cmd_set_d}, $totals->{cmd_set_d});
148 }
149 }
150
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});
156
157 $slabs_before = $slabs_after;
158 }
159 }
160
161 sub grab_stats {
162 my %slabs = ();
163 for my $stat (qw/items slabs/) {
164 print $sock "stats $stat\r\n";
165 while (my $line = <$sock>) {
166 chomp $line;
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;
171 }
172 }
173 }
174
175 return \%slabs;
176 }
177
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.
181 sub automove_basic {
182 my ($totals, $sorted) = @_;
183
184 my $source = 0;
185 my $dest = 0;
186 my $high = $sorted->[-1];
187 return unless $high->{evicted_d} > 0;
188 if ($move{winner} == $high->{slab}) {
189 $move{wins}++;
190 $dest = $move{winner} if $move{wins} >= $opts{loops};
191 } else {
192 $move{wins} = 1;
193 $move{winner} = $high->{slab};
194 }
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});
200 } else {
201 delete $move{zeroes}->{$slab->{slab}}
202 if exists $move{zeroes}->{$slab->{slab}};
203 }
204 }
205
206 if ($source && $dest) {
207 print " slabs reassign $source $dest\n";
208 print $sock "slabs reassign $source $dest\r\n";
209 my $res = <$sock>;
210 print " RES: ", $res;
211 } elsif ($dest && !$source) {
212 print "FAIL: want to move memory to $dest but no valid source slab available\n";
213 }
214 }
215
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);
221 }
222
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; }
232 });
233 my @sorted = sort { $a->{numratio} <=> $b->{numratio} } values %$slabs;
234 return ($totals, \@sorted);
235 }
236
237 sub calc_slabs {
238 my ($slabs_before, $slabs_after, $code) = @_;
239 my %slabs = ();
240 my %totals = ();
241 for my $id (keys %$slabs_after) {
242 my $sb = $slabs_before->{$id};
243 my $sa = $slabs_after->{$id};
244 next unless ($sb && $sa);
245 my %slab = %$sa;
246 for my $key (keys %slab) {
247 # Add totals, diffs
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};
252 }
253 }
254 # External code
255 $code->($sb, $sa, \%slab) if $code;
256 $slab{slab} = $id;
257 $slabs{$id} = \%slab;
258 }
259 return (\%slabs, \%totals);
260 }