| 1 | #! /usr/bin/perl |
|---|
| 2 | # -*- cperl -*- |
|---|
| 3 | # |
|---|
| 4 | # Copyright (C) 2009 Tomash Brechko. All rights reserved. |
|---|
| 5 | # |
|---|
| 6 | # This program is free software; you can redistribute it and/or modify |
|---|
| 7 | # it under the same terms as Perl itself, either Perl version 5.8.8 |
|---|
| 8 | # or, at your option, any later version of Perl 5 you may have |
|---|
| 9 | # available. |
|---|
| 10 | # |
|---|
| 11 | use warnings; |
|---|
| 12 | use strict; |
|---|
| 13 | |
|---|
| 14 | =head1 NAME |
|---|
| 15 | |
|---|
| 16 | ketama-distr.pl - compute relative distribution of keys. |
|---|
| 17 | |
|---|
| 18 | =head1 SYNOPSIS |
|---|
| 19 | |
|---|
| 20 | ketama-distr.pl OPTIONS |
|---|
| 21 | |
|---|
| 22 | =head1 OPTIONS |
|---|
| 23 | |
|---|
| 24 | =over |
|---|
| 25 | |
|---|
| 26 | =item C<--ketama_points, -k NUM> |
|---|
| 27 | |
|---|
| 28 | B<Required, greater than zero.> Number of ketama points per server of |
|---|
| 29 | weight 1. |
|---|
| 30 | |
|---|
| 31 | =item C<--server, -s HOST:PORT[:WEIGHT]> |
|---|
| 32 | |
|---|
| 33 | B<Two or more.> Specifies a server. May be given multiple |
|---|
| 34 | times. Default I<WEIGHT> is 1. |
|---|
| 35 | |
|---|
| 36 | =back |
|---|
| 37 | |
|---|
| 38 | =cut |
|---|
| 39 | |
|---|
| 40 | |
|---|
| 41 | use Getopt::Long qw(:config gnu_getopt); |
|---|
| 42 | use Pod::Usage; |
|---|
| 43 | |
|---|
| 44 | my %options; |
|---|
| 45 | if (! GetOptions(\%options, |
|---|
| 46 | qw(ketama_points|k=i server|s=s@)) |
|---|
| 47 | || @ARGV || grep({ not defined } @options{qw(ketama_points server)}) |
|---|
| 48 | || $options{ketama_points} <= 0 || @{$options{server}} < 2) { |
|---|
| 49 | pod2usage(1); |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | |
|---|
| 53 | use String::CRC32; |
|---|
| 54 | |
|---|
| 55 | |
|---|
| 56 | sub compute_old { |
|---|
| 57 | my ($server, $index, $prev) = @_; |
|---|
| 58 | |
|---|
| 59 | $server =~ s/:/\0/; |
|---|
| 60 | |
|---|
| 61 | my $point = crc32($server . pack("V", $index)); |
|---|
| 62 | |
|---|
| 63 | return $point; |
|---|
| 64 | } |
|---|
| 65 | |
|---|
| 66 | |
|---|
| 67 | sub compute_new { |
|---|
| 68 | my ($server, $index, $prev) = @_; |
|---|
| 69 | |
|---|
| 70 | $server =~ s/:/\0/; |
|---|
| 71 | |
|---|
| 72 | my $point = crc32($server . pack("V", $prev)); |
|---|
| 73 | |
|---|
| 74 | return $point; |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | |
|---|
| 78 | sub compute { |
|---|
| 79 | my ($compute_point) = @_; |
|---|
| 80 | |
|---|
| 81 | my @continuum; |
|---|
| 82 | |
|---|
| 83 | my $j = 0; |
|---|
| 84 | foreach my $s (@{$options{server}}) { |
|---|
| 85 | ++$j; |
|---|
| 86 | my ($server, $weight) = $s =~ /^([^:]+:[^:]+)(?::(.+))?$/; |
|---|
| 87 | |
|---|
| 88 | die "$s should be HOST:PORT" unless defined $server; |
|---|
| 89 | |
|---|
| 90 | $weight = 1 unless defined $weight; |
|---|
| 91 | |
|---|
| 92 | my $prev = 0; |
|---|
| 93 | for (my $i = 0; $i < $options{ketama_points} * $weight; ++$i) { |
|---|
| 94 | my $point = $compute_point->($server, $i, $prev); |
|---|
| 95 | push @continuum, [$point, "$j: $server"]; |
|---|
| 96 | $prev = $point; |
|---|
| 97 | } |
|---|
| 98 | } |
|---|
| 99 | |
|---|
| 100 | use sort 'stable'; |
|---|
| 101 | @continuum = sort {$a->[0] <=> $b->[0]} @continuum; |
|---|
| 102 | |
|---|
| 103 | my $prev_point = 0; |
|---|
| 104 | my $first_server = ''; |
|---|
| 105 | my %server_share; |
|---|
| 106 | foreach my $c (@continuum) { |
|---|
| 107 | $first_server = $c->[1] unless $first_server; |
|---|
| 108 | $server_share{$c->[1]} += $c->[0] - $prev_point; |
|---|
| 109 | $prev_point = $c->[0]; |
|---|
| 110 | } |
|---|
| 111 | # Wraparound case. |
|---|
| 112 | $server_share{$first_server} += 2**32 - 1 - $prev_point; |
|---|
| 113 | |
|---|
| 114 | foreach my $s (sort keys %server_share) { |
|---|
| 115 | my $share = $server_share{$s}; |
|---|
| 116 | printf("server %s total = % 10u (%.2f%%)\n", |
|---|
| 117 | $s, $share, $share * 100 / (2**32 - 1)); |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | return @continuum; |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | |
|---|
| 124 | print "Old:\n"; |
|---|
| 125 | compute(\&compute_old); |
|---|
| 126 | print "\n"; |
|---|
| 127 | print "New:\n"; |
|---|
| 128 | my $total_points = compute(\&compute_new); |
|---|
| 129 | print "\n"; |
|---|
| 130 | my $int_size = 4; |
|---|
| 131 | print "Continuum array size = ", $total_points * $int_size * 2, " bytes\n"; |
|---|