xref: /memcached-1.4.29/scripts/mc_slab_mover (revision 8c1c18ed)
1#! /usr/bin/perl
2# See memcached for LICENSE
3# Copyright 2011 Dormando ([email protected])
4
5=head1 NAME
6
7mc_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
17This utility is an example implementation of an algorithm for reassigning
18slab memory in a running memcached instance. If memcached's built-in
19automover isn't working for you, you may use this script as an example
20base and expand on it. We welcome modifications or alternatives on the
21mailing list.
22
23=head1 ALGORITHM
24
25The default algorithm is simple, and may serve for a common case: over
26time one slab may grow in use compare to others, and as evictions stop
27in one slab and start in another it will reassign memory.
28
29If a slab has the most evictions three times in a row, it will pull a page
30from a slab which has had zero evictions three times in a row.
31
32There are many traffic patterns where this does not work well. IE: If you
33never use expirations and rely on the LRU (so all slabs always evict),
34it 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
42The hostname to connect to. NOTE: If connection to the host breaks, script
43will stop.
44
45=item --sleep=10
46
47How long to wait between loops for gathering stats.
48
49=item --loops=3
50
51How many loops to run before making a decision for a move.
52
53=item --verbose
54
55Prints a formatted dump of some common statistics per loop.
56
57=item --automove
58
59Enables the automover, and will attempt to move memory around if it finds
60viable candidates.
61
62=back
63
64=head1 AUTHOR
65
66Dormando E<lt>L<[email protected]>E<gt>
67
68=head1 LICENSE
69
70Licensed for use and redistribution under the same terms as Memcached itself.
71
72=cut
73
74use warnings;
75use strict;
76
77use IO::Socket::INET;
78
79use FindBin;
80use Data::Dumper qw/Dumper/;
81use Getopt::Long;
82
83my %opts = ('sleep' => 10, automove => 0, verbose => 0, loops => 3);
84GetOptions(
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
92die "Must specify at least --host='127.0.0.1:11211'" unless $opts{host};
93my $sock = IO::Socket::INET->new(PeerAddr => $opts{host},
94                                 Timeout  => 3);
95die "$!\n" unless $sock;
96
97my %stats = ();
98my %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};
107run();
108
109sub 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
118sub 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
161sub 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.
181sub 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.
217sub 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
226sub 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
237sub 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}
261