"Fossies" - the Fresh Open Source Software Archive 
Member "memcached-1.6.15/t/binary-extstore.t" (30 Mar 2022, 17783 Bytes) of package /linux/www/memcached-1.6.15.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Perl source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the latest
Fossies "Diffs" side-by-side code changes report for "binary-extstore.t":
1.6.14_vs_1.6.15.
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use Test::More;
6 use FindBin qw($Bin);
7 use lib "$Bin/lib";
8 use MemcachedTest;
9 use Data::Dumper qw/Dumper/;
10
11 my $ext_path;
12
13 if (!supports_extstore()) {
14 plan skip_all => 'extstore not enabled';
15 exit 0;
16 }
17
18 $ext_path = "/tmp/extstore.$$";
19
20 my $server = new_memcached("-m 64 -U 0 -o ext_page_size=8,ext_wbuf_size=2,ext_threads=1,ext_io_depth=2,ext_item_size=512,ext_item_age=2,ext_recache_rate=10000,ext_max_frag=0.9,ext_path=$ext_path:64m,no_lru_crawler,slab_automove=0,ext_max_sleep=100000");
21 ok($server, "started the server");
22
23 # Based almost 100% off testClient.py which is:
24 # Copyright (c) 2007 Dustin Sallings <dustin@spy.net>
25
26 # Command constants
27 use constant CMD_GET => 0x00;
28 use constant CMD_SET => 0x01;
29 use constant CMD_ADD => 0x02;
30 use constant CMD_REPLACE => 0x03;
31 use constant CMD_DELETE => 0x04;
32 use constant CMD_INCR => 0x05;
33 use constant CMD_DECR => 0x06;
34 use constant CMD_QUIT => 0x07;
35 use constant CMD_FLUSH => 0x08;
36 use constant CMD_GETQ => 0x09;
37 use constant CMD_NOOP => 0x0A;
38 use constant CMD_VERSION => 0x0B;
39 use constant CMD_GETK => 0x0C;
40 use constant CMD_GETKQ => 0x0D;
41 use constant CMD_APPEND => 0x0E;
42 use constant CMD_PREPEND => 0x0F;
43 use constant CMD_STAT => 0x10;
44 use constant CMD_SETQ => 0x11;
45 use constant CMD_ADDQ => 0x12;
46 use constant CMD_REPLACEQ => 0x13;
47 use constant CMD_DELETEQ => 0x14;
48 use constant CMD_INCREMENTQ => 0x15;
49 use constant CMD_DECREMENTQ => 0x16;
50 use constant CMD_QUITQ => 0x17;
51 use constant CMD_FLUSHQ => 0x18;
52 use constant CMD_APPENDQ => 0x19;
53 use constant CMD_PREPENDQ => 0x1A;
54 use constant CMD_TOUCH => 0x1C;
55 use constant CMD_GAT => 0x1D;
56 use constant CMD_GATQ => 0x1E;
57 use constant CMD_GATK => 0x23;
58 use constant CMD_GATKQ => 0x24;
59
60 # REQ and RES formats are divided even though they currently share
61 # the same format, since they _could_ differ in the future.
62 use constant REQ_PKT_FMT => "CCnCCnNNNN";
63 use constant RES_PKT_FMT => "CCnCCnNNNN";
64 use constant INCRDECR_PKT_FMT => "NNNNN";
65 use constant MIN_RECV_BYTES => length(pack(RES_PKT_FMT));
66 use constant REQ_MAGIC => 0x80;
67 use constant RES_MAGIC => 0x81;
68
69 my $mc = MC::Client->new;
70
71 # Wait until all items have flushed
72 sub wait_for_ext {
73 my $sum = 1;
74 while ($sum != 0) {
75 my %s = $mc->stats("items");
76 $sum = 0;
77 for my $key (keys %s) {
78 if ($key =~ m/items:(\d+):number/) {
79 # Ignore classes which can contain extstore items
80 next if $1 < 3;
81 $sum += $s{$key};
82 }
83 }
84 sleep 1 if $sum != 0;
85 }
86 }
87
88 my $check = sub {
89 my ($key, $orig_flags, $orig_val) = @_;
90 my ($flags, $val, $cas) = $mc->get($key);
91 is($flags, $orig_flags, "Flags is set properly");
92 ok($val eq $orig_val || $val == $orig_val, $val . " = " . $orig_val);
93 };
94
95 my $set = sub {
96 my ($key, $exp, $orig_flags, $orig_value) = @_;
97 $mc->set($key, $orig_value, $orig_flags, $exp);
98 $check->($key, $orig_flags, $orig_value);
99 };
100
101 my $empty = sub {
102 my $key = shift;
103 my $rv =()= eval { $mc->get($key) };
104 is($rv, 0, "Didn't get a result from get");
105 ok($@->not_found, "We got a not found error when we expected one");
106 };
107
108 my $delete = sub {
109 my ($key, $when) = @_;
110 $mc->delete($key, $when);
111 $empty->($key);
112 };
113
114 my $value;
115 my $bigvalue;
116 {
117 my @chars = ("C".."Z");
118 for (1 .. 20000) {
119 $value .= $chars[rand @chars];
120 }
121 for (1 .. 800000) {
122 $bigvalue .= $chars[rand @chars];
123 }
124 }
125
126 # diag "small object";
127 $set->('x', 10, 19, "somevalue");
128
129 # check extstore counters
130 {
131 my %stats = $mc->stats('');
132 is($stats{extstore_objects_written}, 0);
133 }
134
135 # diag "Delete";
136 #$delete->('x');
137
138 # diag "Flush";
139 #$empty->('y');
140
141 # fill some larger objects
142 {
143 my $keycount = 1000;
144 for (1 .. $keycount) {
145 $set->("nfoo$_", 0, 19, $value);
146 }
147 # wait for a flush
148 wait_for_ext();
149 # value returns for one flushed object.
150 $check->('nfoo1', 19, $value);
151
152 # check extstore counters
153 my %stats = $mc->stats('');
154 cmp_ok($stats{extstore_page_allocs}, '>', 0, 'at least one page allocated');
155 cmp_ok($stats{extstore_objects_written}, '>', $keycount / 2, 'some objects written');
156 cmp_ok($stats{extstore_bytes_written}, '>', length($value) * 2, 'some bytes written');
157 cmp_ok($stats{get_extstore}, '>', 0, 'one object was fetched');
158 cmp_ok($stats{extstore_objects_read}, '>', 0, 'one object read');
159 cmp_ok($stats{extstore_bytes_read}, '>', length($value), 'some bytes read');
160 # Test multiget
161 my $rv = $mc->get_multi(qw(nfoo2 nfoo3 noexist));
162 is($rv->{nfoo2}->[1], $value, 'multiget nfoo2');
163 is($rv->{nfoo3}->[1], $value, 'multiget nfoo2');
164
165 # Remove half of the keys for the next test.
166 for (1 .. $keycount) {
167 next unless $_ % 2 == 0;
168 $delete->("nfoo$_");
169 }
170
171 my %stats2 = $mc->stats('');
172 cmp_ok($stats{extstore_bytes_used}, '>', $stats2{extstore_bytes_used},
173 'bytes used dropped after deletions');
174 cmp_ok($stats{extstore_objects_used}, '>', $stats2{extstore_objects_used},
175 'objects used dropped after deletions');
176 is($stats2{badcrc_from_extstore}, 0, 'CRC checks successful');
177
178 # delete the rest
179 for (1 .. $keycount) {
180 next unless $_ % 2 == 1;
181 $delete->("nfoo$_");
182 }
183 }
184
185 # check evictions and misses
186 {
187 my $keycount = 1000;
188 for (1 .. $keycount) {
189 $set->("mfoo$_", 0, 19, $value);
190 }
191 wait_for_ext();
192 for ($keycount .. ($keycount*3)) {
193 $set->("mfoo$_", 0, 19, $value);
194 }
195 wait_for_ext();
196 # FIXME: Need to sample through a few values, or fix eviction to be
197 # more accurate. On 32bit systems some pages unused to this point get
198 # filled after the first few items, then the eviction algo pulls those
199 # pages since they have the lowest version number, leaving older objects
200 # in memory and evicting newer ones.
201 for (1 .. ($keycount*3)) {
202 next unless $_ % 100 == 0;
203 eval { $mc->get("mfoo$_"); };
204 }
205
206 my %s = $mc->stats('');
207 cmp_ok($s{extstore_objects_evicted}, '>', 0);
208 cmp_ok($s{miss_from_extstore}, '>', 0);
209 }
210
211 # store and re-fetch a chunked value
212 {
213 my %stats = $mc->stats('');
214 $set->("bigvalue", 0, 0, $bigvalue);
215 wait_for_ext();
216 $check->("bigvalue", 0, $bigvalue);
217 my %stats2 = $mc->stats('');
218
219 cmp_ok($stats2{extstore_objects_written}, '>',
220 $stats{extstore_objects_written}, "a large value flushed");
221 }
222
223 # ensure ASCII can still fetch the chunked value.
224 {
225 my $ns = $server->new_sock;
226
227 my %s1 = $mc->stats('');
228 mem_get_is($ns, "bigvalue", $bigvalue);
229 print $ns "extstore recache_rate 1\r\n";
230 is(scalar <$ns>, "OK\r\n", "recache rate upped");
231 for (1..3) {
232 mem_get_is($ns, "bigvalue", $bigvalue);
233 $check->('bigvalue', 0, $bigvalue);
234 }
235 my %s2 = $mc->stats('');
236 cmp_ok($s2{recache_from_extstore}, '>', $s1{recache_from_extstore},
237 'a new recache happened');
238
239 }
240
241 done_testing();
242
243 END {
244 unlink $ext_path if $ext_path;
245 }
246 # ######################################################################
247 # Test ends around here.
248 # ######################################################################
249
250 package MC::Client;
251
252 use strict;
253 use warnings;
254 use fields qw(socket);
255 use IO::Socket::INET;
256
257 sub new {
258 my $self = shift;
259 my ($s) = @_;
260 $s = $server unless defined $s;
261 my $sock = $s->sock;
262 $self = fields::new($self);
263 $self->{socket} = $sock;
264 return $self;
265 }
266
267 sub build_command {
268 my $self = shift;
269 die "Not enough args to send_command" unless @_ >= 4;
270 my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
271
272 $extra_header = '' unless defined $extra_header;
273 my $keylen = length($key);
274 my $vallen = length($val);
275 my $extralen = length($extra_header);
276 my $datatype = 0; # field for future use
277 my $reserved = 0; # field for future use
278 my $totallen = $keylen + $vallen + $extralen;
279 my $ident_hi = 0;
280 my $ident_lo = 0;
281
282 if ($cas) {
283 $ident_hi = int($cas / 2 ** 32);
284 $ident_lo = int($cas % 2 ** 32);
285 }
286
287 my $msg = pack(::REQ_PKT_FMT, ::REQ_MAGIC, $cmd, $keylen, $extralen,
288 $datatype, $reserved, $totallen, $opaque, $ident_hi,
289 $ident_lo);
290 my $full_msg = $msg . $extra_header . $key . $val;
291 return $full_msg;
292 }
293
294 sub send_command {
295 my $self = shift;
296 die "Not enough args to send_command" unless @_ >= 4;
297 my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
298
299 my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas);
300
301 my $sent = 0;
302 my $data_len = length($full_msg);
303 while ($sent < $data_len) {
304 my $sent_bytes = $self->{socket}->syswrite($full_msg,
305 $data_len - $sent > MemcachedTest::MAX_READ_WRITE_SIZE ?
306 MemcachedTest::MAX_READ_WRITE_SIZE : ($data_len - $sent),
307 $sent);
308 last if ($sent_bytes <= 0);
309 $sent += $sent_bytes;
310 }
311 die("Send failed: $!") unless $data_len;
312
313 if($sent != length($full_msg)) {
314 die("only sent $sent of " . length($full_msg) . " bytes");
315 }
316 }
317
318 sub flush_socket {
319 my $self = shift;
320 $self->{socket}->flush;
321 }
322
323 # Send a silent command and ensure it doesn't respond.
324 sub send_silent {
325 my $self = shift;
326 die "Not enough args to send_silent" unless @_ >= 4;
327 my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
328
329 $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
330 $self->send_command(::CMD_NOOP, '', '', $opaque + 1);
331
332 my ($ropaque, $data) = $self->_handle_single_response;
333 Test::More::is($ropaque, $opaque + 1);
334 }
335
336 sub silent_mutation {
337 my $self = shift;
338 my ($cmd, $key, $value) = @_;
339
340 $empty->($key);
341 my $extra = pack "NN", 82, 0;
342 $mc->send_silent($cmd, $key, $value, 7278552, $extra, 0);
343 $check->($key, 82, $value);
344 }
345
346 sub _handle_single_response {
347 my $self = shift;
348 my $myopaque = shift;
349
350 my $hdr = "";
351 while(::MIN_RECV_BYTES - length($hdr) > 0) {
352 $self->{socket}->sysread(my $response, ::MIN_RECV_BYTES - length($hdr));
353 $hdr .= $response;
354 }
355 Test::More::is(length($hdr), ::MIN_RECV_BYTES, "Expected read length");
356
357 my ($magic, $cmd, $keylen, $extralen, $datatype, $status, $remaining,
358 $opaque, $ident_hi, $ident_lo) = unpack(::RES_PKT_FMT, $hdr);
359 Test::More::is($magic, ::RES_MAGIC, "Got proper response magic");
360
361 my $cas = ($ident_hi * 2 ** 32) + $ident_lo;
362
363 return ($opaque, '', $cas, 0) if($remaining == 0);
364
365 # fetch the value
366 my $rv="";
367 while($remaining - length($rv) > 0) {
368 $self->{socket}->sysread(my $buf, $remaining - length($rv));
369 $rv .= $buf;
370 }
371 if(length($rv) != $remaining) {
372 my $found = length($rv);
373 die("Expected $remaining bytes, got $found");
374 }
375 if (defined $myopaque) {
376 Test::More::is($opaque, $myopaque, "Expected opaque");
377 } else {
378 Test::More::pass("Implicit pass since myopaque is undefined");
379 }
380
381 if ($status) {
382 die MC::Error->new($status, $rv);
383 }
384
385 return ($opaque, $rv, $cas, $keylen);
386 }
387
388 sub _do_command {
389 my $self = shift;
390 die unless @_ >= 3;
391 my ($cmd, $key, $val, $extra_header, $cas) = @_;
392
393 $extra_header = '' unless defined $extra_header;
394 my $opaque = int(rand(2**32));
395 $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
396 my (undef, $rv, $rcas) = $self->_handle_single_response($opaque);
397 return ($rv, $rcas);
398 }
399
400 sub _incrdecr_header {
401 my $self = shift;
402 my ($amt, $init, $exp) = @_;
403
404 my $amt_hi = int($amt / 2 ** 32);
405 my $amt_lo = int($amt % 2 ** 32);
406
407 my $init_hi = int($init / 2 ** 32);
408 my $init_lo = int($init % 2 ** 32);
409
410 my $extra_header = pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi,
411 $init_lo, $exp);
412
413 return $extra_header;
414 }
415
416 sub _incrdecr_cas {
417 my $self = shift;
418 my ($cmd, $key, $amt, $init, $exp) = @_;
419
420 my ($data, $rcas) = $self->_do_command($cmd, $key, '',
421 $self->_incrdecr_header($amt, $init, $exp));
422
423 my $header = substr $data, 0, 8, '';
424 my ($resp_hi, $resp_lo) = unpack "NN", $header;
425 my $resp = ($resp_hi * 2 ** 32) + $resp_lo;
426
427 return $resp, $rcas;
428 }
429
430 sub _incrdecr {
431 my $self = shift;
432 my ($v, $c) = $self->_incrdecr_cas(@_);
433 return $v
434 }
435
436 sub silent_incrdecr {
437 my $self = shift;
438 my ($cmd, $key, $amt, $init, $exp) = @_;
439 my $opaque = 8275753;
440
441 $mc->send_silent($cmd, $key, '', $opaque,
442 $mc->_incrdecr_header($amt, $init, $exp));
443 }
444
445 sub stats {
446 my $self = shift;
447 my $key = shift;
448 my $cas = 0;
449 my $opaque = int(rand(2**32));
450 $self->send_command(::CMD_STAT, $key, '', $opaque, '', $cas);
451
452 my %rv = ();
453 my $found_key = '';
454 my $found_val = '';
455 do {
456 my ($op, $data, $cas, $keylen) = $self->_handle_single_response($opaque);
457 if($keylen > 0) {
458 $found_key = substr($data, 0, $keylen);
459 $found_val = substr($data, $keylen);
460 $rv{$found_key} = $found_val;
461 } else {
462 $found_key = '';
463 }
464 } while($found_key ne '');
465 return %rv;
466 }
467
468 sub get {
469 my $self = shift;
470 my $key = shift;
471 my ($rv, $cas) = $self->_do_command(::CMD_GET, $key, '', '');
472
473 my $header = substr $rv, 0, 4, '';
474 my $flags = unpack("N", $header);
475
476 return ($flags, $rv, $cas);
477 }
478
479 sub get_multi {
480 my $self = shift;
481 my @keys = @_;
482
483 for (my $i = 0; $i < @keys; $i++) {
484 $self->send_command(::CMD_GETQ, $keys[$i], '', $i, '', 0);
485 }
486
487 my $terminal = @keys + 10;
488 $self->send_command(::CMD_NOOP, '', '', $terminal);
489
490 my %return;
491 while (1) {
492 my ($opaque, $data) = $self->_handle_single_response;
493 last if $opaque == $terminal;
494
495 my $header = substr $data, 0, 4, '';
496 my $flags = unpack("N", $header);
497
498 $return{$keys[$opaque]} = [$flags, $data];
499 }
500
501 return %return if wantarray;
502 return \%return;
503 }
504
505 sub touch {
506 my $self = shift;
507 my ($key, $expire) = @_;
508 my $extra_header = pack "N", $expire;
509 my $cas = 0;
510 return $self->_do_command(::CMD_TOUCH, $key, '', $extra_header, $cas);
511 }
512
513 sub gat {
514 my $self = shift;
515 my $key = shift;
516 my $expire = shift;
517 my $extra_header = pack "N", $expire;
518 my ($rv, $cas) = $self->_do_command(::CMD_GAT, $key, '', $extra_header);
519
520 my $header = substr $rv, 0, 4, '';
521 my $flags = unpack("N", $header);
522
523 return ($flags, $rv, $cas);
524 }
525
526 sub version {
527 my $self = shift;
528 return $self->_do_command(::CMD_VERSION, '', '');
529 }
530
531 sub flush {
532 my $self = shift;
533 return $self->_do_command(::CMD_FLUSH, '', '');
534 }
535
536 sub add {
537 my $self = shift;
538 my ($key, $val, $flags, $expire) = @_;
539 my $extra_header = pack "NN", $flags, $expire;
540 my $cas = 0;
541 return $self->_do_command(::CMD_ADD, $key, $val, $extra_header, $cas);
542 }
543
544 sub set {
545 my $self = shift;
546 my ($key, $val, $flags, $expire, $cas) = @_;
547 my $extra_header = pack "NN", $flags, $expire;
548 return $self->_do_command(::CMD_SET, $key, $val, $extra_header, $cas);
549 }
550
551 sub _append_prepend {
552 my $self = shift;
553 my ($cmd, $key, $val, $cas) = @_;
554 return $self->_do_command($cmd, $key, $val, '', $cas);
555 }
556
557 sub replace {
558 my $self = shift;
559 my ($key, $val, $flags, $expire) = @_;
560 my $extra_header = pack "NN", $flags, $expire;
561 my $cas = 0;
562 return $self->_do_command(::CMD_REPLACE, $key, $val, $extra_header, $cas);
563 }
564
565 sub delete {
566 my $self = shift;
567 my ($key) = @_;
568 return $self->_do_command(::CMD_DELETE, $key, '');
569 }
570
571 sub incr {
572 my $self = shift;
573 my ($key, $amt, $init, $exp) = @_;
574 $amt = 1 unless defined $amt;
575 $init = 0 unless defined $init;
576 $exp = 0 unless defined $exp;
577
578 return $self->_incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
579 }
580
581 sub incr_cas {
582 my $self = shift;
583 my ($key, $amt, $init, $exp) = @_;
584 $amt = 1 unless defined $amt;
585 $init = 0 unless defined $init;
586 $exp = 0 unless defined $exp;
587
588 return $self->_incrdecr_cas(::CMD_INCR, $key, $amt, $init, $exp);
589 }
590
591 sub decr {
592 my $self = shift;
593 my ($key, $amt, $init, $exp) = @_;
594 $amt = 1 unless defined $amt;
595 $init = 0 unless defined $init;
596 $exp = 0 unless defined $exp;
597
598 return $self->_incrdecr(::CMD_DECR, $key, $amt, $init, $exp);
599 }
600
601 sub noop {
602 my $self = shift;
603 return $self->_do_command(::CMD_NOOP, '', '');
604 }
605
606 package MC::Error;
607
608 use strict;
609 use warnings;
610
611 use constant ERR_UNKNOWN_CMD => 0x81;
612 use constant ERR_NOT_FOUND => 0x1;
613 use constant ERR_EXISTS => 0x2;
614 use constant ERR_TOO_BIG => 0x3;
615 use constant ERR_EINVAL => 0x4;
616 use constant ERR_NOT_STORED => 0x5;
617 use constant ERR_DELTA_BADVAL => 0x6;
618
619 use overload '""' => sub {
620 my $self = shift;
621 return "Memcache Error ($self->[0]): $self->[1]";
622 };
623
624 sub new {
625 my $class = shift;
626 my $error = [@_];
627 my $self = bless $error, (ref $class || $class);
628
629 return $self;
630 }
631
632 sub not_found {
633 my $self = shift;
634 return $self->[0] == ERR_NOT_FOUND;
635 }
636
637 sub exists {
638 my $self = shift;
639 return $self->[0] == ERR_EXISTS;
640 }
641
642 sub too_big {
643 my $self = shift;
644 return $self->[0] == ERR_TOO_BIG;
645 }
646
647 sub delta_badval {
648 my $self = shift;
649 return $self->[0] == ERR_DELTA_BADVAL;
650 }
651
652 sub einval {
653 my $self = shift;
654 return $self->[0] == ERR_EINVAL;
655 }
656
657 # vim: filetype=perl
658