xref: /memcached-1.4.29/scripts/mc_slab_mover (revision 8c1c18ed)
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