File Coverage

blib/lib/Cache/Memcached/libmemcached.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Cache::Memcached::libmemcached;
2              
3             require bytes;
4 18     18   57332 use strict;
  18         40  
  18         571  
5 18     18   90 use warnings;
  18         28  
  18         655  
6              
7 0           use Memcached::libmemcached 1.001701, qw(
8             MEMCACHED_CALLBACK_PREFIX_KEY
9             MEMCACHED_PREFIX_KEY_MAX_SIZE
10 18     18   30364 );
  0            
11             use base qw(Memcached::libmemcached);
12              
13             use Carp qw(croak carp);
14             use Scalar::Util qw(weaken);
15             use Storable ();
16              
17             our $VERSION = '0.04001';
18              
19             use constant HAVE_ZLIB => eval { require Compress::Zlib } && !$@;
20             use constant F_STORABLE => 1;
21             use constant F_COMPRESS => 2;
22             use constant OPTIMIZE => $ENV{PERL_LIBMEMCACHED_OPTIMIZE} ? 1 : 0;
23              
24             my %behavior;
25              
26             BEGIN
27             {
28             # Make sure to load bytes.pm if HAVE_ZLIB is enabled
29             if (HAVE_ZLIB) {
30             require bytes;
31             }
32              
33             # accessors
34             foreach my $field (qw(compress_enable compress_threshold compress_savings)) {
35             eval sprintf(<<" EOSUB", $field, $field, $field, $field);
36             sub set_%s { \$_[0]->{%s} = \$_[1] }
37             sub get_%s { \$_[0]->{%s} }
38             EOSUB
39             die if $@;
40             }
41             # for Cache::Memcached compatibility
42             sub enable_compress { shift->set_compress_enable(@_) }
43              
44             # XXX this should be done via subclasses
45             if (OPTIMIZE) {
46             # If the optimize flag is enabled, we do not support master key
47             # generation, cause we really care about the speed.
48             foreach my $method (qw(get set add replace prepend append cas delete)) {
49             eval <<" EOSUB";
50             sub $method {
51             shift->SUPER::memcached_${method}(\@_)
52             }
53             EOSUB
54             die if $@;
55             }
56             } else {
57             # Regular case.
58             # Mental note. We only do this cause while we're faster than
59             # Cache::Memcached::Fast, *even* when the above optimization isn't
60             # toggled.
61             foreach my $method (qw(get set add replace prepend append cas delete)) {
62             eval <<" EOSUB";
63             sub $method {
64             my \$self = shift;
65             my \$key = shift;
66             return \$self->SUPER::memcached_${method}(\$key, \@_)
67             unless ref \$key;
68             (my \$master_key, \$key) = @\$key;
69             if (\$master_key) {
70             \$self->SUPER::memcached_${method}_by_key(\$master_key, \$key, \@_);
71             } else {
72             \$self->SUPER::memcached_${method}(\$key, \@_);
73             }
74             }
75             EOSUB
76             die if $@;
77             }
78             }
79              
80             # Create get_*/is_*/set_* methods for some libmemcached behaviors.
81             # We only do this for some because there are many and it's easy for
82             # the user to use memcached_behavior_set() etc directly.
83             #
84             %behavior = (
85             # non-boolean behaviors that are renamed (to be more descriptive)
86             distribution_method => [ 0, 'distribution' ],
87             hashing_algorithm => [ 0, 'hash' ],
88             # boolean behaviors that are not renamed:
89             no_block => [ 1 ],
90             binary_protocol => [ 1 ],
91             );
92              
93             while ( my ($method, $field_info) = each %behavior ) {
94             my $is_bool = $field_info->[0];
95             my $field = $field_info->[1] || $method;
96              
97             my $behavior = "Memcached::libmemcached::MEMCACHED_BEHAVIOR_\U$field";
98             warn "$behavior doesn't exist\n" # sanity check
99             unless do { no strict 'refs'; defined &$behavior };
100              
101             my ($set, $get) = ("set_$method", "get_$method");
102             $get = "is_$method" if $is_bool;
103             my $code = "sub $set { \$_[0]->memcached_behavior_set($behavior(), \$_[1]) }\n"
104             . "sub $get { \$_[0]->memcached_behavior_get($behavior()) }";
105             eval $code;
106             die "$@ while executing $code" if $@;
107             }
108              
109             }
110              
111             sub import
112             {
113             my $class = shift;
114             Memcached::libmemcached->export_to_level(1, undef, @_) ;
115             }
116              
117             sub new
118             {
119             my $class = shift;
120             my %args = %{ shift || {} };
121              
122             my $self = $class->SUPER::new();
123              
124             $self->trace_level(delete $args{debug}) if exists $args{debug};
125              
126             $self->namespace(delete $args{namespace})
127             if exists $args{namespace};
128              
129             $self->{compress_threshold} = delete $args{compress_threshold};
130             # Add support for Cache::Memcache::Fast's compress_ratio
131             $self->{compress_savingsS} = delete $args{compress_savings} || 0.20;
132             $self->{compress_enable} =
133             exists $args{compress_enable} ? delete $args{compress_enable} : 1;
134              
135             # servers
136             $args{servers} || croak "No servers specified";
137             $self->set_servers(delete $args{servers});
138              
139             # old-style behavior options (see behavior_ block below)
140             foreach my $option (qw(no_block hashing_algorithm distribution_method binary_protocol)) {
141             my $behavior = $behavior{$option}->[1] || $option;
142             $args{"behavior_$behavior"} = delete $args{$option} if exists $args{$option};
143             }
144              
145             # allow any libmemcached behavior to be set via args to new()
146             for my $name (grep { /^behavior_/ } keys %args) {
147             my $value = delete $args{$name};
148             my $behavior = "Memcached::libmemcached::MEMCACHED_\U$name";
149             no strict 'refs';
150             if (not defined &$behavior) {
151             carp "$name ($behavior) isn't available"; # sanity check
152             next;
153             }
154             $self->memcached_behavior_set(&$behavior(), $value);
155             }
156              
157             delete $args{readonly};
158             delete $args{no_rehash};
159              
160             carp "Unrecognised options: @{[ sort keys %args ]}"
161             if %args;
162              
163             # Set compression/serialization callbacks
164             $self->set_callback_coderefs(
165             # Closures so we have reference to $self
166             $self->_mk_callbacks()
167             );
168              
169             # behavior options
170             foreach my $option (qw(no_block hashing_algorithm distribution_method binary_protocol)) {
171             my $method = "set_$option";
172             $self->$method( $args{$option} ) if exists $args{$option};
173             }
174              
175             return $self;
176             }
177              
178             sub namespace {
179             my $self = shift;
180              
181             my $old_namespace = $self->memcached_callback_get(MEMCACHED_CALLBACK_PREFIX_KEY);
182             if (@_) {
183             my $namespace = shift;
184             $self->memcached_callback_set(MEMCACHED_CALLBACK_PREFIX_KEY, $namespace)
185             or carp $self->errstr;
186             }
187              
188             return $old_namespace;
189             }
190              
191             sub set_servers
192             {
193             my $self = shift;
194             my $servers = shift || [];
195              
196             # $self->{servers} = []; # for compatibility with Cache::Memcached
197              
198             # XXX should delete any existing servers from libmemcached
199             foreach my $server (@$servers) {
200             $self->server_add($server);
201             }
202             }
203              
204             sub server_add
205             {
206             my $self = shift;
207             my $server = shift
208             or Carp::confess("server not specified");
209              
210             my $weight = 0;
211             if (ref $server eq 'ARRAY') {
212             my @ary = @$server;
213             $server = shift @ary;
214             $weight = shift @ary || 0 if @ary;
215             }
216             elsif (ref $server eq 'HASH') { # Cache::Memcached::Fast
217             my $h = $server;
218             $server = $h->{address};
219             $weight = $h->{weight} if exists $h->{weight};
220             # noreply is not supported
221             }
222              
223             if ($server =~ /^([^:]+):([^:]+)$/) {
224             my ($hostname, $port) = ($1, $2);
225             $self->memcached_server_add_with_weight($hostname, $port, $weight);
226             } else {
227             $self->memcached_server_add_unix_socket_with_weight( $server, $weight );
228             }
229              
230             # for compatibility with Cache::Memcached
231             # push @{$self->{servers}}, $server;
232             }
233              
234              
235             sub _mk_callbacks
236             {
237             my $self = shift;
238              
239             weaken($self);
240             my $inflate = sub {
241             my ($key, $flags) = @_;
242             if ($flags & F_COMPRESS) {
243             if (! HAVE_ZLIB) {
244             croak("Data for $key is compressed, but we have no Compress::Zlib");
245             }
246             $_ = Compress::Zlib::memGunzip($_);
247             }
248              
249             if ($flags & F_STORABLE) {
250             $_ = Storable::thaw($_);
251             }
252             return ();
253             };
254              
255             my $deflate = sub {
256             # Check if we have a complex structure
257             if (ref $_) {
258             $_ = Storable::nfreeze($_);
259             $_[1] |= F_STORABLE;
260             }
261              
262             # Check if we need compression
263             if (HAVE_ZLIB && $self->{compress_enable} && $self->{compress_threshold}) {
264             # Find the byte length
265             my $length = bytes::length($_);
266             if ($length > $self->{compress_threshold}) {
267             my $tmp = Compress::Zlib::memGzip($_);
268             if (bytes::length($tmp) / $length < 1 - $self->{compress_savingsS}) {
269             $_ = $tmp;
270             $_[1] |= F_COMPRESS;
271             }
272             }
273             }
274             return ();
275             };
276             return ($deflate, $inflate);
277             }
278              
279             sub incr
280             {
281             my $self = shift;
282             my $key = shift;
283             my $offset = shift || 1;
284             my $val = 0;
285             $self->memcached_increment($key, $offset, $val) || return undef;
286             return $val;
287             }
288              
289             sub decr
290             {
291             my $self = shift;
292             my $key = shift;
293             my $offset = shift || 1;
294             my $val = 0;
295             $self->memcached_decrement($key, $offset, $val) || return undef;
296             return $val;
297             }
298              
299              
300             sub flush_all
301             {
302             $_[0]->memcached_flush(0);
303             }
304              
305             *remove = \&delete;
306              
307             sub disconnect_all {
308             $_[0]->memcached_quit();
309             }
310              
311              
312             sub server_versions {
313             my $self = shift;
314             my %versions;
315             # XXX not optimal, libmemcached knows these values without having to send a stats request
316             $self->walk_stats('', sub {
317             my ($key, $value, $hostport) = @_;
318             $versions{$hostport} = $value if $key eq 'version';
319             return;
320             });
321             return \%versions;
322             }
323              
324              
325             sub stats
326             {
327             my $self = shift;
328             my ($stats_args) = @_;
329              
330             # http://github.com/memcached/memcached/blob/master/doc/protocol.txt
331             $stats_args = [ $stats_args ]
332             if $stats_args and not ref $stats_args;
333             $stats_args ||= [ '' ];
334              
335             # stats keys that aren't matched by the prefix and suffix regexes below
336             # but which we want to accumulate in totals
337             my %total_misc_keys = map { ($_ => 1) } qw(
338             bytes evictions
339             connection_structures curr_connections total_connections
340             );
341              
342             my %h;
343             for my $type (@$stats_args) {
344              
345             my $code = sub {
346             my ($key, $value, $hostport) = @_;
347              
348             # XXX - This is hardcoded in the callback cause r139 in perl-memcached
349             # removed the magic of "misc"
350             $type ||= 'misc';
351             $h{hosts}{$hostport}{$type}{$key} = $value;
352             #warn "$_ ($key, $value, $hostport, $type)\n";
353              
354             # accumulate overall totals for some items
355             if ($type eq 'misc') {
356             if ($total_misc_keys{$key}
357             or $key =~ /^(?:cmd|bytes)_/ # prefixes
358             or $key =~ /_(?:hits|misses|errors|yields|badval|items|read|written)$/ # suffixes
359             ) {
360             $h{total}{$key} += $value;
361             }
362             }
363             elsif ($type eq 'malloc' or $type eq 'sizes') {
364             $h{total}{"${type}_$key"} += $value;
365             }
366             return;
367             };
368              
369             $self->walk_stats($type, $code);
370             }
371              
372             return \%h;
373             }
374              
375             # for compatability with Cache::Memcached and Cache::Memcached::Managed 0.20:
376             # https://rt.cpan.org/Ticket/Display.html?id=62512
377             # sub sock_to_host { undef }
378             # sub get_sock { undef }
379             # sub forget_dead_hosts { undef }
380              
381             1;
382              
383             __END__