1 // Copyright 2005 and onwards Google Inc.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 // * Neither the name of Google Inc. nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 #include <math.h>
30 #include <stdlib.h>
31
32
33 #include <algorithm>
34 #include <string>
35 #include <vector>
36
37 #include "snappy.h"
38 #include "snappy-internal.h"
39 #include "snappy-test.h"
40 #include "snappy-sinksource.h"
41
42 DEFINE_int32(start_len, -1,
43 "Starting prefix size for testing (-1: just full file contents)");
44 DEFINE_int32(end_len, -1,
45 "Starting prefix size for testing (-1: just full file contents)");
46 DEFINE_int32(bytes, 10485760,
47 "How many bytes to compress/uncompress per file for timing");
48
49 DEFINE_bool(zlib, false,
50 "Run zlib compression (http://www.zlib.net)");
51 DEFINE_bool(lzo, false,
52 "Run LZO compression (http://www.oberhumer.com/opensource/lzo/)");
53 DEFINE_bool(quicklz, false,
54 "Run quickLZ compression (http://www.quicklz.com/)");
55 DEFINE_bool(liblzf, false,
56 "Run libLZF compression "
57 "(http://www.goof.com/pcg/marc/liblzf.html)");
58 DEFINE_bool(fastlz, false,
59 "Run FastLZ compression (http://www.fastlz.org/");
60 DEFINE_bool(snappy, true, "Run snappy compression");
61
62 DEFINE_bool(write_compressed, false,
63 "Write compressed versions of each file to <file>.comp");
64 DEFINE_bool(write_uncompressed, false,
65 "Write uncompressed versions of each file to <file>.uncomp");
66
67 namespace snappy {
68
69
70 #ifdef HAVE_FUNC_MMAP
71
72 // To test against code that reads beyond its input, this class copies a
73 // string to a newly allocated group of pages, the last of which
74 // is made unreadable via mprotect. Note that we need to allocate the
75 // memory with mmap(), as POSIX allows mprotect() only on memory allocated
76 // with mmap(), and some malloc/posix_memalign implementations expect to
77 // be able to read previously allocated memory while doing heap allocations.
78 class DataEndingAtUnreadablePage {
79 public:
DataEndingAtUnreadablePage(const string & s)80 explicit DataEndingAtUnreadablePage(const string& s) {
81 const size_t page_size = getpagesize();
82 const size_t size = s.size();
83 // Round up space for string to a multiple of page_size.
84 size_t space_for_string = (size + page_size - 1) & ~(page_size - 1);
85 alloc_size_ = space_for_string + page_size;
86 mem_ = mmap(NULL, alloc_size_,
87 PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
88 CHECK_NE(MAP_FAILED, mem_);
89 protected_page_ = reinterpret_cast<char*>(mem_) + space_for_string;
90 char* dst = protected_page_ - size;
91 memcpy(dst, s.data(), size);
92 data_ = dst;
93 size_ = size;
94 // Make guard page unreadable.
95 CHECK_EQ(0, mprotect(protected_page_, page_size, PROT_NONE));
96 }
97
~DataEndingAtUnreadablePage()98 ~DataEndingAtUnreadablePage() {
99 // Undo the mprotect.
100 CHECK_EQ(0, mprotect(protected_page_, getpagesize(), PROT_READ|PROT_WRITE));
101 CHECK_EQ(0, munmap(mem_, alloc_size_));
102 }
103
data() const104 const char* data() const { return data_; }
size() const105 size_t size() const { return size_; }
106
107 private:
108 size_t alloc_size_;
109 void* mem_;
110 char* protected_page_;
111 const char* data_;
112 size_t size_;
113 };
114
115 #else // HAVE_FUNC_MMAP
116
117 // Fallback for systems without mmap.
118 typedef string DataEndingAtUnreadablePage;
119
120 #endif
121
122 enum CompressorType {
123 ZLIB, LZO, LIBLZF, QUICKLZ, FASTLZ, SNAPPY
124 };
125
126 const char* names[] = {
127 "ZLIB", "LZO", "LIBLZF", "QUICKLZ", "FASTLZ", "SNAPPY"
128 };
129
MinimumRequiredOutputSpace(size_t input_size,CompressorType comp)130 static size_t MinimumRequiredOutputSpace(size_t input_size,
131 CompressorType comp) {
132 switch (comp) {
133 #ifdef ZLIB_VERSION
134 case ZLIB:
135 return ZLib::MinCompressbufSize(input_size);
136 #endif // ZLIB_VERSION
137
138 #ifdef LZO_VERSION
139 case LZO:
140 return input_size + input_size/64 + 16 + 3;
141 #endif // LZO_VERSION
142
143 #ifdef LZF_VERSION
144 case LIBLZF:
145 return input_size;
146 #endif // LZF_VERSION
147
148 #ifdef QLZ_VERSION_MAJOR
149 case QUICKLZ:
150 return input_size + 36000; // 36000 is used for scratch.
151 #endif // QLZ_VERSION_MAJOR
152
153 #ifdef FASTLZ_VERSION
154 case FASTLZ:
155 return max(static_cast<int>(ceil(input_size * 1.05)), 66);
156 #endif // FASTLZ_VERSION
157
158 case SNAPPY:
159 return snappy::MaxCompressedLength(input_size);
160
161 default:
162 LOG(FATAL) << "Unknown compression type number " << comp;
163 return 0;
164 }
165 }
166
167 // Returns true if we successfully compressed, false otherwise.
168 //
169 // If compressed_is_preallocated is set, do not resize the compressed buffer.
170 // This is typically what you want for a benchmark, in order to not spend
171 // time in the memory allocator. If you do set this flag, however,
172 // "compressed" must be preinitialized to at least MinCompressbufSize(comp)
173 // number of bytes, and may contain junk bytes at the end after return.
Compress(const char * input,size_t input_size,CompressorType comp,string * compressed,bool compressed_is_preallocated)174 static bool Compress(const char* input, size_t input_size, CompressorType comp,
175 string* compressed, bool compressed_is_preallocated) {
176 if (!compressed_is_preallocated) {
177 compressed->resize(MinimumRequiredOutputSpace(input_size, comp));
178 }
179
180 switch (comp) {
181 #ifdef ZLIB_VERSION
182 case ZLIB: {
183 ZLib zlib;
184 uLongf destlen = compressed->size();
185 int ret = zlib.Compress(
186 reinterpret_cast<Bytef*>(string_as_array(compressed)),
187 &destlen,
188 reinterpret_cast<const Bytef*>(input),
189 input_size);
190 CHECK_EQ(Z_OK, ret);
191 if (!compressed_is_preallocated) {
192 compressed->resize(destlen);
193 }
194 return true;
195 }
196 #endif // ZLIB_VERSION
197
198 #ifdef LZO_VERSION
199 case LZO: {
200 unsigned char* mem = new unsigned char[LZO1X_1_15_MEM_COMPRESS];
201 lzo_uint destlen;
202 int ret = lzo1x_1_15_compress(
203 reinterpret_cast<const uint8*>(input),
204 input_size,
205 reinterpret_cast<uint8*>(string_as_array(compressed)),
206 &destlen,
207 mem);
208 CHECK_EQ(LZO_E_OK, ret);
209 delete[] mem;
210 if (!compressed_is_preallocated) {
211 compressed->resize(destlen);
212 }
213 break;
214 }
215 #endif // LZO_VERSION
216
217 #ifdef LZF_VERSION
218 case LIBLZF: {
219 int destlen = lzf_compress(input,
220 input_size,
221 string_as_array(compressed),
222 input_size);
223 if (destlen == 0) {
224 // lzf *can* cause lots of blowup when compressing, so they
225 // recommend to limit outsize to insize, and just not compress
226 // if it's bigger. Ideally, we'd just swap input and output.
227 compressed->assign(input, input_size);
228 destlen = input_size;
229 }
230 if (!compressed_is_preallocated) {
231 compressed->resize(destlen);
232 }
233 break;
234 }
235 #endif // LZF_VERSION
236
237 #ifdef QLZ_VERSION_MAJOR
238 case QUICKLZ: {
239 qlz_state_compress *state_compress = new qlz_state_compress;
240 int destlen = qlz_compress(input,
241 string_as_array(compressed),
242 input_size,
243 state_compress);
244 delete state_compress;
245 CHECK_NE(0, destlen);
246 if (!compressed_is_preallocated) {
247 compressed->resize(destlen);
248 }
249 break;
250 }
251 #endif // QLZ_VERSION_MAJOR
252
253 #ifdef FASTLZ_VERSION
254 case FASTLZ: {
255 // Use level 1 compression since we mostly care about speed.
256 int destlen = fastlz_compress_level(
257 1,
258 input,
259 input_size,
260 string_as_array(compressed));
261 if (!compressed_is_preallocated) {
262 compressed->resize(destlen);
263 }
264 CHECK_NE(destlen, 0);
265 break;
266 }
267 #endif // FASTLZ_VERSION
268
269 case SNAPPY: {
270 size_t destlen;
271 snappy::RawCompress(input, input_size,
272 string_as_array(compressed),
273 &destlen);
274 CHECK_LE(destlen, snappy::MaxCompressedLength(input_size));
275 if (!compressed_is_preallocated) {
276 compressed->resize(destlen);
277 }
278 break;
279 }
280
281 default: {
282 return false; // the asked-for library wasn't compiled in
283 }
284 }
285 return true;
286 }
287
Uncompress(const string & compressed,CompressorType comp,int size,string * output)288 static bool Uncompress(const string& compressed, CompressorType comp,
289 int size, string* output) {
290 switch (comp) {
291 #ifdef ZLIB_VERSION
292 case ZLIB: {
293 output->resize(size);
294 ZLib zlib;
295 uLongf destlen = output->size();
296 int ret = zlib.Uncompress(
297 reinterpret_cast<Bytef*>(string_as_array(output)),
298 &destlen,
299 reinterpret_cast<const Bytef*>(compressed.data()),
300 compressed.size());
301 CHECK_EQ(Z_OK, ret);
302 CHECK_EQ(static_cast<uLongf>(size), destlen);
303 break;
304 }
305 #endif // ZLIB_VERSION
306
307 #ifdef LZO_VERSION
308 case LZO: {
309 output->resize(size);
310 lzo_uint destlen;
311 int ret = lzo1x_decompress(
312 reinterpret_cast<const uint8*>(compressed.data()),
313 compressed.size(),
314 reinterpret_cast<uint8*>(string_as_array(output)),
315 &destlen,
316 NULL);
317 CHECK_EQ(LZO_E_OK, ret);
318 CHECK_EQ(static_cast<lzo_uint>(size), destlen);
319 break;
320 }
321 #endif // LZO_VERSION
322
323 #ifdef LZF_VERSION
324 case LIBLZF: {
325 output->resize(size);
326 int destlen = lzf_decompress(compressed.data(),
327 compressed.size(),
328 string_as_array(output),
329 output->size());
330 if (destlen == 0) {
331 // This error probably means we had decided not to compress,
332 // and thus have stored input in output directly.
333 output->assign(compressed.data(), compressed.size());
334 destlen = compressed.size();
335 }
336 CHECK_EQ(destlen, size);
337 break;
338 }
339 #endif // LZF_VERSION
340
341 #ifdef QLZ_VERSION_MAJOR
342 case QUICKLZ: {
343 output->resize(size);
344 qlz_state_decompress *state_decompress = new qlz_state_decompress;
345 int destlen = qlz_decompress(compressed.data(),
346 string_as_array(output),
347 state_decompress);
348 delete state_decompress;
349 CHECK_EQ(destlen, size);
350 break;
351 }
352 #endif // QLZ_VERSION_MAJOR
353
354 #ifdef FASTLZ_VERSION
355 case FASTLZ: {
356 output->resize(size);
357 int destlen = fastlz_decompress(compressed.data(),
358 compressed.length(),
359 string_as_array(output),
360 size);
361 CHECK_EQ(destlen, size);
362 break;
363 }
364 #endif // FASTLZ_VERSION
365
366 case SNAPPY: {
367 snappy::RawUncompress(compressed.data(), compressed.size(),
368 string_as_array(output));
369 break;
370 }
371
372 default: {
373 return false; // the asked-for library wasn't compiled in
374 }
375 }
376 return true;
377 }
378
Measure(const char * data,size_t length,CompressorType comp,int repeats,int block_size)379 static void Measure(const char* data,
380 size_t length,
381 CompressorType comp,
382 int repeats,
383 int block_size) {
384 // Run tests a few time and pick median running times
385 static const int kRuns = 5;
386 double ctime[kRuns];
387 double utime[kRuns];
388 int compressed_size = 0;
389
390 {
391 // Chop the input into blocks
392 int num_blocks = (length + block_size - 1) / block_size;
393 vector<const char*> input(num_blocks);
394 vector<size_t> input_length(num_blocks);
395 vector<string> compressed(num_blocks);
396 vector<string> output(num_blocks);
397 for (int b = 0; b < num_blocks; b++) {
398 int input_start = b * block_size;
399 int input_limit = min<int>((b+1)*block_size, length);
400 input[b] = data+input_start;
401 input_length[b] = input_limit-input_start;
402
403 // Pre-grow the output buffer so we don't measure string append time.
404 compressed[b].resize(MinimumRequiredOutputSpace(block_size, comp));
405 }
406
407 // First, try one trial compression to make sure the code is compiled in
408 if (!Compress(input[0], input_length[0], comp, &compressed[0], true)) {
409 LOG(WARNING) << "Skipping " << names[comp] << ": "
410 << "library not compiled in";
411 return;
412 }
413
414 for (int run = 0; run < kRuns; run++) {
415 CycleTimer ctimer, utimer;
416
417 for (int b = 0; b < num_blocks; b++) {
418 // Pre-grow the output buffer so we don't measure string append time.
419 compressed[b].resize(MinimumRequiredOutputSpace(block_size, comp));
420 }
421
422 ctimer.Start();
423 for (int b = 0; b < num_blocks; b++)
424 for (int i = 0; i < repeats; i++)
425 Compress(input[b], input_length[b], comp, &compressed[b], true);
426 ctimer.Stop();
427
428 // Compress once more, with resizing, so we don't leave junk
429 // at the end that will confuse the decompressor.
430 for (int b = 0; b < num_blocks; b++) {
431 Compress(input[b], input_length[b], comp, &compressed[b], false);
432 }
433
434 for (int b = 0; b < num_blocks; b++) {
435 output[b].resize(input_length[b]);
436 }
437
438 utimer.Start();
439 for (int i = 0; i < repeats; i++)
440 for (int b = 0; b < num_blocks; b++)
441 Uncompress(compressed[b], comp, input_length[b], &output[b]);
442 utimer.Stop();
443
444 ctime[run] = ctimer.Get();
445 utime[run] = utimer.Get();
446 }
447
448 compressed_size = 0;
449 for (size_t i = 0; i < compressed.size(); i++) {
450 compressed_size += compressed[i].size();
451 }
452 }
453
454 sort(ctime, ctime + kRuns);
455 sort(utime, utime + kRuns);
456 const int med = kRuns/2;
457
458 float comp_rate = (length / ctime[med]) * repeats / 1048576.0;
459 float uncomp_rate = (length / utime[med]) * repeats / 1048576.0;
460 string x = names[comp];
461 x += ":";
462 string urate = (uncomp_rate >= 0)
463 ? StringPrintf("%.1f", uncomp_rate)
464 : string("?");
465 printf("%-7s [b %dM] bytes %6d -> %6d %4.1f%% "
466 "comp %5.1f MB/s uncomp %5s MB/s\n",
467 x.c_str(),
468 block_size/(1<<20),
469 static_cast<int>(length), static_cast<uint32>(compressed_size),
470 (compressed_size * 100.0) / max<int>(1, length),
471 comp_rate,
472 urate.c_str());
473 }
474
VerifyString(const string & input)475 static int VerifyString(const string& input) {
476 string compressed;
477 DataEndingAtUnreadablePage i(input);
478 const size_t written = snappy::Compress(i.data(), i.size(), &compressed);
479 CHECK_EQ(written, compressed.size());
480 CHECK_LE(compressed.size(),
481 snappy::MaxCompressedLength(input.size()));
482 CHECK(snappy::IsValidCompressedBuffer(compressed.data(), compressed.size()));
483
484 string uncompressed;
485 DataEndingAtUnreadablePage c(compressed);
486 CHECK(snappy::Uncompress(c.data(), c.size(), &uncompressed));
487 CHECK_EQ(uncompressed, input);
488 return uncompressed.size();
489 }
490
VerifyStringSink(const string & input)491 static void VerifyStringSink(const string& input) {
492 string compressed;
493 DataEndingAtUnreadablePage i(input);
494 const size_t written = snappy::Compress(i.data(), i.size(), &compressed);
495 CHECK_EQ(written, compressed.size());
496 CHECK_LE(compressed.size(),
497 snappy::MaxCompressedLength(input.size()));
498 CHECK(snappy::IsValidCompressedBuffer(compressed.data(), compressed.size()));
499
500 string uncompressed;
501 uncompressed.resize(input.size());
502 snappy::UncheckedByteArraySink sink(string_as_array(&uncompressed));
503 DataEndingAtUnreadablePage c(compressed);
504 snappy::ByteArraySource source(c.data(), c.size());
505 CHECK(snappy::Uncompress(&source, &sink));
506 CHECK_EQ(uncompressed, input);
507 }
508
VerifyIOVec(const string & input)509 static void VerifyIOVec(const string& input) {
510 string compressed;
511 DataEndingAtUnreadablePage i(input);
512 const size_t written = snappy::Compress(i.data(), i.size(), &compressed);
513 CHECK_EQ(written, compressed.size());
514 CHECK_LE(compressed.size(),
515 snappy::MaxCompressedLength(input.size()));
516 CHECK(snappy::IsValidCompressedBuffer(compressed.data(), compressed.size()));
517
518 // Try uncompressing into an iovec containing a random number of entries
519 // ranging from 1 to 10.
520 char* buf = new char[input.size()];
521 ACMRandom rnd(input.size());
522 size_t num = rnd.Next() % 10 + 1;
523 if (input.size() < num) {
524 num = input.size();
525 }
526 struct iovec* iov = new iovec[num];
527 int used_so_far = 0;
528 for (size_t i = 0; i < num; ++i) {
529 iov[i].iov_base = buf + used_so_far;
530 if (i == num - 1) {
531 iov[i].iov_len = input.size() - used_so_far;
532 } else {
533 // Randomly choose to insert a 0 byte entry.
534 if (rnd.OneIn(5)) {
535 iov[i].iov_len = 0;
536 } else {
537 iov[i].iov_len = rnd.Uniform(input.size());
538 }
539 }
540 used_so_far += iov[i].iov_len;
541 }
542 CHECK(snappy::RawUncompressToIOVec(
543 compressed.data(), compressed.size(), iov, num));
544 CHECK(!memcmp(buf, input.data(), input.size()));
545 delete[] iov;
546 delete[] buf;
547 }
548
549 // Test that data compressed by a compressor that does not
550 // obey block sizes is uncompressed properly.
VerifyNonBlockedCompression(const string & input)551 static void VerifyNonBlockedCompression(const string& input) {
552 if (input.length() > snappy::kBlockSize) {
553 // We cannot test larger blocks than the maximum block size, obviously.
554 return;
555 }
556
557 string prefix;
558 Varint::Append32(&prefix, input.size());
559
560 // Setup compression table
561 snappy::internal::WorkingMemory wmem;
562 int table_size;
563 uint16* table = wmem.GetHashTable(input.size(), &table_size);
564
565 // Compress entire input in one shot
566 string compressed;
567 compressed += prefix;
568 compressed.resize(prefix.size()+snappy::MaxCompressedLength(input.size()));
569 char* dest = string_as_array(&compressed) + prefix.size();
570 char* end = snappy::internal::CompressFragment(input.data(), input.size(),
571 dest, table, table_size);
572 compressed.resize(end - compressed.data());
573
574 // Uncompress into string
575 string uncomp_str;
576 CHECK(snappy::Uncompress(compressed.data(), compressed.size(), &uncomp_str));
577 CHECK_EQ(uncomp_str, input);
578
579 // Uncompress using source/sink
580 string uncomp_str2;
581 uncomp_str2.resize(input.size());
582 snappy::UncheckedByteArraySink sink(string_as_array(&uncomp_str2));
583 snappy::ByteArraySource source(compressed.data(), compressed.size());
584 CHECK(snappy::Uncompress(&source, &sink));
585 CHECK_EQ(uncomp_str2, input);
586
587 // Uncompress into iovec
588 {
589 static const int kNumBlocks = 10;
590 struct iovec vec[kNumBlocks];
591 const int block_size = 1 + input.size() / kNumBlocks;
592 string iovec_data(block_size * kNumBlocks, 'x');
593 for (int i = 0; i < kNumBlocks; i++) {
594 vec[i].iov_base = string_as_array(&iovec_data) + i * block_size;
595 vec[i].iov_len = block_size;
596 }
597 CHECK(snappy::RawUncompressToIOVec(compressed.data(), compressed.size(),
598 vec, kNumBlocks));
599 CHECK_EQ(string(iovec_data.data(), input.size()), input);
600 }
601 }
602
603 // Expand the input so that it is at least K times as big as block size
Expand(const string & input)604 static string Expand(const string& input) {
605 static const int K = 3;
606 string data = input;
607 while (data.size() < K * snappy::kBlockSize) {
608 data += input;
609 }
610 return data;
611 }
612
Verify(const string & input)613 static int Verify(const string& input) {
614 VLOG(1) << "Verifying input of size " << input.size();
615
616 // Compress using string based routines
617 const int result = VerifyString(input);
618
619 // Verify using sink based routines
620 VerifyStringSink(input);
621
622 VerifyNonBlockedCompression(input);
623 VerifyIOVec(input);
624 if (!input.empty()) {
625 const string expanded = Expand(input);
626 VerifyNonBlockedCompression(expanded);
627 VerifyIOVec(input);
628 }
629
630 return result;
631 }
632
633
IsValidCompressedBuffer(const string & c)634 static bool IsValidCompressedBuffer(const string& c) {
635 return snappy::IsValidCompressedBuffer(c.data(), c.size());
636 }
Uncompress(const string & c,string * u)637 static bool Uncompress(const string& c, string* u) {
638 return snappy::Uncompress(c.data(), c.size(), u);
639 }
640
641 // This test checks to ensure that snappy doesn't coredump if it gets
642 // corrupted data.
TEST(CorruptedTest,VerifyCorrupted)643 TEST(CorruptedTest, VerifyCorrupted) {
644 string source = "making sure we don't crash with corrupted input";
645 VLOG(1) << source;
646 string dest;
647 string uncmp;
648 snappy::Compress(source.data(), source.size(), &dest);
649
650 // Mess around with the data. It's hard to simulate all possible
651 // corruptions; this is just one example ...
652 CHECK_GT(dest.size(), 3);
653 dest[1]--;
654 dest[3]++;
655 // this really ought to fail.
656 CHECK(!IsValidCompressedBuffer(dest));
657 CHECK(!Uncompress(dest, &uncmp));
658
659 // This is testing for a security bug - a buffer that decompresses to 100k
660 // but we lie in the snappy header and only reserve 0 bytes of memory :)
661 source.resize(100000);
662 for (size_t i = 0; i < source.length(); ++i) {
663 source[i] = 'A';
664 }
665 snappy::Compress(source.data(), source.size(), &dest);
666 dest[0] = dest[1] = dest[2] = dest[3] = 0;
667 CHECK(!IsValidCompressedBuffer(dest));
668 CHECK(!Uncompress(dest, &uncmp));
669
670 if (sizeof(void *) == 4) {
671 // Another security check; check a crazy big length can't DoS us with an
672 // over-allocation.
673 // Currently this is done only for 32-bit builds. On 64-bit builds,
674 // where 3 GB might be an acceptable allocation size, Uncompress()
675 // attempts to decompress, and sometimes causes the test to run out of
676 // memory.
677 dest[0] = dest[1] = dest[2] = dest[3] = '\xff';
678 // This decodes to a really large size, i.e., about 3 GB.
679 dest[4] = 'k';
680 CHECK(!IsValidCompressedBuffer(dest));
681 CHECK(!Uncompress(dest, &uncmp));
682 } else {
683 LOG(WARNING) << "Crazy decompression lengths not checked on 64-bit build";
684 }
685
686 // This decodes to about 2 MB; much smaller, but should still fail.
687 dest[0] = dest[1] = dest[2] = '\xff';
688 dest[3] = 0x00;
689 CHECK(!IsValidCompressedBuffer(dest));
690 CHECK(!Uncompress(dest, &uncmp));
691
692 // try reading stuff in from a bad file.
693 for (int i = 1; i <= 3; ++i) {
694 string data = ReadTestDataFile(StringPrintf("baddata%d.snappy", i).c_str(),
695 0);
696 string uncmp;
697 // check that we don't return a crazy length
698 size_t ulen;
699 CHECK(!snappy::GetUncompressedLength(data.data(), data.size(), &ulen)
700 || (ulen < (1<<20)));
701 uint32 ulen2;
702 snappy::ByteArraySource source(data.data(), data.size());
703 CHECK(!snappy::GetUncompressedLength(&source, &ulen2) ||
704 (ulen2 < (1<<20)));
705 CHECK(!IsValidCompressedBuffer(data));
706 CHECK(!Uncompress(data, &uncmp));
707 }
708 }
709
710 // Helper routines to construct arbitrary compressed strings.
711 // These mirror the compression code in snappy.cc, but are copied
712 // here so that we can bypass some limitations in the how snappy.cc
713 // invokes these routines.
AppendLiteral(string * dst,const string & literal)714 static void AppendLiteral(string* dst, const string& literal) {
715 if (literal.empty()) return;
716 int n = literal.size() - 1;
717 if (n < 60) {
718 // Fit length in tag byte
719 dst->push_back(0 | (n << 2));
720 } else {
721 // Encode in upcoming bytes
722 char number[4];
723 int count = 0;
724 while (n > 0) {
725 number[count++] = n & 0xff;
726 n >>= 8;
727 }
728 dst->push_back(0 | ((59+count) << 2));
729 *dst += string(number, count);
730 }
731 *dst += literal;
732 }
733
AppendCopy(string * dst,int offset,int length)734 static void AppendCopy(string* dst, int offset, int length) {
735 while (length > 0) {
736 // Figure out how much to copy in one shot
737 int to_copy;
738 if (length >= 68) {
739 to_copy = 64;
740 } else if (length > 64) {
741 to_copy = 60;
742 } else {
743 to_copy = length;
744 }
745 length -= to_copy;
746
747 if ((to_copy >= 4) && (to_copy < 12) && (offset < 2048)) {
748 assert(to_copy-4 < 8); // Must fit in 3 bits
749 dst->push_back(1 | ((to_copy-4) << 2) | ((offset >> 8) << 5));
750 dst->push_back(offset & 0xff);
751 } else if (offset < 65536) {
752 dst->push_back(2 | ((to_copy-1) << 2));
753 dst->push_back(offset & 0xff);
754 dst->push_back(offset >> 8);
755 } else {
756 dst->push_back(3 | ((to_copy-1) << 2));
757 dst->push_back(offset & 0xff);
758 dst->push_back((offset >> 8) & 0xff);
759 dst->push_back((offset >> 16) & 0xff);
760 dst->push_back((offset >> 24) & 0xff);
761 }
762 }
763 }
764
TEST(Snappy,SimpleTests)765 TEST(Snappy, SimpleTests) {
766 Verify("");
767 Verify("a");
768 Verify("ab");
769 Verify("abc");
770
771 Verify("aaaaaaa" + string(16, 'b') + string("aaaaa") + "abc");
772 Verify("aaaaaaa" + string(256, 'b') + string("aaaaa") + "abc");
773 Verify("aaaaaaa" + string(2047, 'b') + string("aaaaa") + "abc");
774 Verify("aaaaaaa" + string(65536, 'b') + string("aaaaa") + "abc");
775 Verify("abcaaaaaaa" + string(65536, 'b') + string("aaaaa") + "abc");
776 }
777
778 // Verify max blowup (lots of four-byte copies)
TEST(Snappy,MaxBlowup)779 TEST(Snappy, MaxBlowup) {
780 string input;
781 for (int i = 0; i < 20000; i++) {
782 ACMRandom rnd(i);
783 uint32 bytes = static_cast<uint32>(rnd.Next());
784 input.append(reinterpret_cast<char*>(&bytes), sizeof(bytes));
785 }
786 for (int i = 19999; i >= 0; i--) {
787 ACMRandom rnd(i);
788 uint32 bytes = static_cast<uint32>(rnd.Next());
789 input.append(reinterpret_cast<char*>(&bytes), sizeof(bytes));
790 }
791 Verify(input);
792 }
793
TEST(Snappy,RandomData)794 TEST(Snappy, RandomData) {
795 ACMRandom rnd(FLAGS_test_random_seed);
796
797 const int num_ops = 20000;
798 for (int i = 0; i < num_ops; i++) {
799 if ((i % 1000) == 0) {
800 VLOG(0) << "Random op " << i << " of " << num_ops;
801 }
802
803 string x;
804 size_t len = rnd.Uniform(4096);
805 if (i < 100) {
806 len = 65536 + rnd.Uniform(65536);
807 }
808 while (x.size() < len) {
809 int run_len = 1;
810 if (rnd.OneIn(10)) {
811 run_len = rnd.Skewed(8);
812 }
813 char c = (i < 100) ? rnd.Uniform(256) : rnd.Skewed(3);
814 while (run_len-- > 0 && x.size() < len) {
815 x += c;
816 }
817 }
818
819 Verify(x);
820 }
821 }
822
TEST(Snappy,FourByteOffset)823 TEST(Snappy, FourByteOffset) {
824 // The new compressor cannot generate four-byte offsets since
825 // it chops up the input into 32KB pieces. So we hand-emit the
826 // copy manually.
827
828 // The two fragments that make up the input string.
829 string fragment1 = "012345689abcdefghijklmnopqrstuvwxyz";
830 string fragment2 = "some other string";
831
832 // How many times each fragment is emitted.
833 const int n1 = 2;
834 const int n2 = 100000 / fragment2.size();
835 const int length = n1 * fragment1.size() + n2 * fragment2.size();
836
837 string compressed;
838 Varint::Append32(&compressed, length);
839
840 AppendLiteral(&compressed, fragment1);
841 string src = fragment1;
842 for (int i = 0; i < n2; i++) {
843 AppendLiteral(&compressed, fragment2);
844 src += fragment2;
845 }
846 AppendCopy(&compressed, src.size(), fragment1.size());
847 src += fragment1;
848 CHECK_EQ(length, src.size());
849
850 string uncompressed;
851 CHECK(snappy::IsValidCompressedBuffer(compressed.data(), compressed.size()));
852 CHECK(snappy::Uncompress(compressed.data(), compressed.size(),
853 &uncompressed));
854 CHECK_EQ(uncompressed, src);
855 }
856
TEST(Snappy,IOVecEdgeCases)857 TEST(Snappy, IOVecEdgeCases) {
858 // Test some tricky edge cases in the iovec output that are not necessarily
859 // exercised by random tests.
860
861 // Our output blocks look like this initially (the last iovec is bigger
862 // than depicted):
863 // [ ] [ ] [ ] [ ] [ ]
864 static const int kLengths[] = { 2, 1, 4, 8, 128 };
865
866 struct iovec iov[ARRAYSIZE(kLengths)];
867 for (int i = 0; i < ARRAYSIZE(kLengths); ++i) {
868 iov[i].iov_base = new char[kLengths[i]];
869 iov[i].iov_len = kLengths[i];
870 }
871
872 string compressed;
873 Varint::Append32(&compressed, 22);
874
875 // A literal whose output crosses three blocks.
876 // [ab] [c] [123 ] [ ] [ ]
877 AppendLiteral(&compressed, "abc123");
878
879 // A copy whose output crosses two blocks (source and destination
880 // segments marked).
881 // [ab] [c] [1231] [23 ] [ ]
882 // ^--^ --
883 AppendCopy(&compressed, 3, 3);
884
885 // A copy where the input is, at first, in the block before the output:
886 //
887 // [ab] [c] [1231] [231231 ] [ ]
888 // ^--- ^---
889 // Then during the copy, the pointers move such that the input and
890 // output pointers are in the same block:
891 //
892 // [ab] [c] [1231] [23123123] [ ]
893 // ^- ^-
894 // And then they move again, so that the output pointer is no longer
895 // in the same block as the input pointer:
896 // [ab] [c] [1231] [23123123] [123 ]
897 // ^-- ^--
898 AppendCopy(&compressed, 6, 9);
899
900 // Finally, a copy where the input is from several blocks back,
901 // and it also crosses three blocks:
902 //
903 // [ab] [c] [1231] [23123123] [123b ]
904 // ^ ^
905 // [ab] [c] [1231] [23123123] [123bc ]
906 // ^ ^
907 // [ab] [c] [1231] [23123123] [123bc12 ]
908 // ^- ^-
909 AppendCopy(&compressed, 17, 4);
910
911 CHECK(snappy::RawUncompressToIOVec(
912 compressed.data(), compressed.size(), iov, ARRAYSIZE(iov)));
913 CHECK_EQ(0, memcmp(iov[0].iov_base, "ab", 2));
914 CHECK_EQ(0, memcmp(iov[1].iov_base, "c", 1));
915 CHECK_EQ(0, memcmp(iov[2].iov_base, "1231", 4));
916 CHECK_EQ(0, memcmp(iov[3].iov_base, "23123123", 8));
917 CHECK_EQ(0, memcmp(iov[4].iov_base, "123bc12", 7));
918
919 for (int i = 0; i < ARRAYSIZE(kLengths); ++i) {
920 delete[] reinterpret_cast<char *>(iov[i].iov_base);
921 }
922 }
923
TEST(Snappy,IOVecLiteralOverflow)924 TEST(Snappy, IOVecLiteralOverflow) {
925 static const int kLengths[] = { 3, 4 };
926
927 struct iovec iov[ARRAYSIZE(kLengths)];
928 for (int i = 0; i < ARRAYSIZE(kLengths); ++i) {
929 iov[i].iov_base = new char[kLengths[i]];
930 iov[i].iov_len = kLengths[i];
931 }
932
933 string compressed;
934 Varint::Append32(&compressed, 8);
935
936 AppendLiteral(&compressed, "12345678");
937
938 CHECK(!snappy::RawUncompressToIOVec(
939 compressed.data(), compressed.size(), iov, ARRAYSIZE(iov)));
940
941 for (int i = 0; i < ARRAYSIZE(kLengths); ++i) {
942 delete[] reinterpret_cast<char *>(iov[i].iov_base);
943 }
944 }
945
TEST(Snappy,IOVecCopyOverflow)946 TEST(Snappy, IOVecCopyOverflow) {
947 static const int kLengths[] = { 3, 4 };
948
949 struct iovec iov[ARRAYSIZE(kLengths)];
950 for (int i = 0; i < ARRAYSIZE(kLengths); ++i) {
951 iov[i].iov_base = new char[kLengths[i]];
952 iov[i].iov_len = kLengths[i];
953 }
954
955 string compressed;
956 Varint::Append32(&compressed, 8);
957
958 AppendLiteral(&compressed, "123");
959 AppendCopy(&compressed, 3, 5);
960
961 CHECK(!snappy::RawUncompressToIOVec(
962 compressed.data(), compressed.size(), iov, ARRAYSIZE(iov)));
963
964 for (int i = 0; i < ARRAYSIZE(kLengths); ++i) {
965 delete[] reinterpret_cast<char *>(iov[i].iov_base);
966 }
967 }
968
CheckUncompressedLength(const string & compressed,size_t * ulength)969 static bool CheckUncompressedLength(const string& compressed,
970 size_t* ulength) {
971 const bool result1 = snappy::GetUncompressedLength(compressed.data(),
972 compressed.size(),
973 ulength);
974
975 snappy::ByteArraySource source(compressed.data(), compressed.size());
976 uint32 length;
977 const bool result2 = snappy::GetUncompressedLength(&source, &length);
978 CHECK_EQ(result1, result2);
979 return result1;
980 }
981
TEST(SnappyCorruption,TruncatedVarint)982 TEST(SnappyCorruption, TruncatedVarint) {
983 string compressed, uncompressed;
984 size_t ulength;
985 compressed.push_back('\xf0');
986 CHECK(!CheckUncompressedLength(compressed, &ulength));
987 CHECK(!snappy::IsValidCompressedBuffer(compressed.data(), compressed.size()));
988 CHECK(!snappy::Uncompress(compressed.data(), compressed.size(),
989 &uncompressed));
990 }
991
TEST(SnappyCorruption,UnterminatedVarint)992 TEST(SnappyCorruption, UnterminatedVarint) {
993 string compressed, uncompressed;
994 size_t ulength;
995 compressed.push_back('\x80');
996 compressed.push_back('\x80');
997 compressed.push_back('\x80');
998 compressed.push_back('\x80');
999 compressed.push_back('\x80');
1000 compressed.push_back(10);
1001 CHECK(!CheckUncompressedLength(compressed, &ulength));
1002 CHECK(!snappy::IsValidCompressedBuffer(compressed.data(), compressed.size()));
1003 CHECK(!snappy::Uncompress(compressed.data(), compressed.size(),
1004 &uncompressed));
1005 }
1006
TEST(Snappy,ReadPastEndOfBuffer)1007 TEST(Snappy, ReadPastEndOfBuffer) {
1008 // Check that we do not read past end of input
1009
1010 // Make a compressed string that ends with a single-byte literal
1011 string compressed;
1012 Varint::Append32(&compressed, 1);
1013 AppendLiteral(&compressed, "x");
1014
1015 string uncompressed;
1016 DataEndingAtUnreadablePage c(compressed);
1017 CHECK(snappy::Uncompress(c.data(), c.size(), &uncompressed));
1018 CHECK_EQ(uncompressed, string("x"));
1019 }
1020
1021 // Check for an infinite loop caused by a copy with offset==0
TEST(Snappy,ZeroOffsetCopy)1022 TEST(Snappy, ZeroOffsetCopy) {
1023 const char* compressed = "\x40\x12\x00\x00";
1024 // \x40 Length (must be > kMaxIncrementCopyOverflow)
1025 // \x12\x00\x00 Copy with offset==0, length==5
1026 char uncompressed[100];
1027 EXPECT_FALSE(snappy::RawUncompress(compressed, 4, uncompressed));
1028 }
1029
TEST(Snappy,ZeroOffsetCopyValidation)1030 TEST(Snappy, ZeroOffsetCopyValidation) {
1031 const char* compressed = "\x05\x12\x00\x00";
1032 // \x05 Length
1033 // \x12\x00\x00 Copy with offset==0, length==5
1034 EXPECT_FALSE(snappy::IsValidCompressedBuffer(compressed, 4));
1035 }
1036
1037 namespace {
1038
TestFindMatchLength(const char * s1,const char * s2,unsigned length)1039 int TestFindMatchLength(const char* s1, const char *s2, unsigned length) {
1040 return snappy::internal::FindMatchLength(s1, s2, s2 + length);
1041 }
1042
1043 } // namespace
1044
TEST(Snappy,FindMatchLength)1045 TEST(Snappy, FindMatchLength) {
1046 // Exercise all different code paths through the function.
1047 // 64-bit version:
1048
1049 // Hit s1_limit in 64-bit loop, hit s1_limit in single-character loop.
1050 EXPECT_EQ(6, TestFindMatchLength("012345", "012345", 6));
1051 EXPECT_EQ(11, TestFindMatchLength("01234567abc", "01234567abc", 11));
1052
1053 // Hit s1_limit in 64-bit loop, find a non-match in single-character loop.
1054 EXPECT_EQ(9, TestFindMatchLength("01234567abc", "01234567axc", 9));
1055
1056 // Same, but edge cases.
1057 EXPECT_EQ(11, TestFindMatchLength("01234567abc!", "01234567abc!", 11));
1058 EXPECT_EQ(11, TestFindMatchLength("01234567abc!", "01234567abc?", 11));
1059
1060 // Find non-match at once in first loop.
1061 EXPECT_EQ(0, TestFindMatchLength("01234567xxxxxxxx", "?1234567xxxxxxxx", 16));
1062 EXPECT_EQ(1, TestFindMatchLength("01234567xxxxxxxx", "0?234567xxxxxxxx", 16));
1063 EXPECT_EQ(4, TestFindMatchLength("01234567xxxxxxxx", "01237654xxxxxxxx", 16));
1064 EXPECT_EQ(7, TestFindMatchLength("01234567xxxxxxxx", "0123456?xxxxxxxx", 16));
1065
1066 // Find non-match in first loop after one block.
1067 EXPECT_EQ(8, TestFindMatchLength("abcdefgh01234567xxxxxxxx",
1068 "abcdefgh?1234567xxxxxxxx", 24));
1069 EXPECT_EQ(9, TestFindMatchLength("abcdefgh01234567xxxxxxxx",
1070 "abcdefgh0?234567xxxxxxxx", 24));
1071 EXPECT_EQ(12, TestFindMatchLength("abcdefgh01234567xxxxxxxx",
1072 "abcdefgh01237654xxxxxxxx", 24));
1073 EXPECT_EQ(15, TestFindMatchLength("abcdefgh01234567xxxxxxxx",
1074 "abcdefgh0123456?xxxxxxxx", 24));
1075
1076 // 32-bit version:
1077
1078 // Short matches.
1079 EXPECT_EQ(0, TestFindMatchLength("01234567", "?1234567", 8));
1080 EXPECT_EQ(1, TestFindMatchLength("01234567", "0?234567", 8));
1081 EXPECT_EQ(2, TestFindMatchLength("01234567", "01?34567", 8));
1082 EXPECT_EQ(3, TestFindMatchLength("01234567", "012?4567", 8));
1083 EXPECT_EQ(4, TestFindMatchLength("01234567", "0123?567", 8));
1084 EXPECT_EQ(5, TestFindMatchLength("01234567", "01234?67", 8));
1085 EXPECT_EQ(6, TestFindMatchLength("01234567", "012345?7", 8));
1086 EXPECT_EQ(7, TestFindMatchLength("01234567", "0123456?", 8));
1087 EXPECT_EQ(7, TestFindMatchLength("01234567", "0123456?", 7));
1088 EXPECT_EQ(7, TestFindMatchLength("01234567!", "0123456??", 7));
1089
1090 // Hit s1_limit in 32-bit loop, hit s1_limit in single-character loop.
1091 EXPECT_EQ(10, TestFindMatchLength("xxxxxxabcd", "xxxxxxabcd", 10));
1092 EXPECT_EQ(10, TestFindMatchLength("xxxxxxabcd?", "xxxxxxabcd?", 10));
1093 EXPECT_EQ(13, TestFindMatchLength("xxxxxxabcdef", "xxxxxxabcdef", 13));
1094
1095 // Same, but edge cases.
1096 EXPECT_EQ(12, TestFindMatchLength("xxxxxx0123abc!", "xxxxxx0123abc!", 12));
1097 EXPECT_EQ(12, TestFindMatchLength("xxxxxx0123abc!", "xxxxxx0123abc?", 12));
1098
1099 // Hit s1_limit in 32-bit loop, find a non-match in single-character loop.
1100 EXPECT_EQ(11, TestFindMatchLength("xxxxxx0123abc", "xxxxxx0123axc", 13));
1101
1102 // Find non-match at once in first loop.
1103 EXPECT_EQ(6, TestFindMatchLength("xxxxxx0123xxxxxxxx",
1104 "xxxxxx?123xxxxxxxx", 18));
1105 EXPECT_EQ(7, TestFindMatchLength("xxxxxx0123xxxxxxxx",
1106 "xxxxxx0?23xxxxxxxx", 18));
1107 EXPECT_EQ(8, TestFindMatchLength("xxxxxx0123xxxxxxxx",
1108 "xxxxxx0132xxxxxxxx", 18));
1109 EXPECT_EQ(9, TestFindMatchLength("xxxxxx0123xxxxxxxx",
1110 "xxxxxx012?xxxxxxxx", 18));
1111
1112 // Same, but edge cases.
1113 EXPECT_EQ(6, TestFindMatchLength("xxxxxx0123", "xxxxxx?123", 10));
1114 EXPECT_EQ(7, TestFindMatchLength("xxxxxx0123", "xxxxxx0?23", 10));
1115 EXPECT_EQ(8, TestFindMatchLength("xxxxxx0123", "xxxxxx0132", 10));
1116 EXPECT_EQ(9, TestFindMatchLength("xxxxxx0123", "xxxxxx012?", 10));
1117
1118 // Find non-match in first loop after one block.
1119 EXPECT_EQ(10, TestFindMatchLength("xxxxxxabcd0123xx",
1120 "xxxxxxabcd?123xx", 16));
1121 EXPECT_EQ(11, TestFindMatchLength("xxxxxxabcd0123xx",
1122 "xxxxxxabcd0?23xx", 16));
1123 EXPECT_EQ(12, TestFindMatchLength("xxxxxxabcd0123xx",
1124 "xxxxxxabcd0132xx", 16));
1125 EXPECT_EQ(13, TestFindMatchLength("xxxxxxabcd0123xx",
1126 "xxxxxxabcd012?xx", 16));
1127
1128 // Same, but edge cases.
1129 EXPECT_EQ(10, TestFindMatchLength("xxxxxxabcd0123", "xxxxxxabcd?123", 14));
1130 EXPECT_EQ(11, TestFindMatchLength("xxxxxxabcd0123", "xxxxxxabcd0?23", 14));
1131 EXPECT_EQ(12, TestFindMatchLength("xxxxxxabcd0123", "xxxxxxabcd0132", 14));
1132 EXPECT_EQ(13, TestFindMatchLength("xxxxxxabcd0123", "xxxxxxabcd012?", 14));
1133 }
1134
TEST(Snappy,FindMatchLengthRandom)1135 TEST(Snappy, FindMatchLengthRandom) {
1136 const int kNumTrials = 10000;
1137 const int kTypicalLength = 10;
1138 ACMRandom rnd(FLAGS_test_random_seed);
1139
1140 for (int i = 0; i < kNumTrials; i++) {
1141 string s, t;
1142 char a = rnd.Rand8();
1143 char b = rnd.Rand8();
1144 while (!rnd.OneIn(kTypicalLength)) {
1145 s.push_back(rnd.OneIn(2) ? a : b);
1146 t.push_back(rnd.OneIn(2) ? a : b);
1147 }
1148 DataEndingAtUnreadablePage u(s);
1149 DataEndingAtUnreadablePage v(t);
1150 int matched = snappy::internal::FindMatchLength(
1151 u.data(), v.data(), v.data() + t.size());
1152 if (matched == t.size()) {
1153 EXPECT_EQ(s, t);
1154 } else {
1155 EXPECT_NE(s[matched], t[matched]);
1156 for (int j = 0; j < matched; j++) {
1157 EXPECT_EQ(s[j], t[j]);
1158 }
1159 }
1160 }
1161 }
1162
CompressFile(const char * fname)1163 static void CompressFile(const char* fname) {
1164 string fullinput;
1165 CHECK_OK(file::GetContents(fname, &fullinput, file::Defaults()));
1166
1167 string compressed;
1168 Compress(fullinput.data(), fullinput.size(), SNAPPY, &compressed, false);
1169
1170 CHECK_OK(file::SetContents(string(fname).append(".comp"), compressed,
1171 file::Defaults()));
1172 }
1173
UncompressFile(const char * fname)1174 static void UncompressFile(const char* fname) {
1175 string fullinput;
1176 CHECK_OK(file::GetContents(fname, &fullinput, file::Defaults()));
1177
1178 size_t uncompLength;
1179 CHECK(CheckUncompressedLength(fullinput, &uncompLength));
1180
1181 string uncompressed;
1182 uncompressed.resize(uncompLength);
1183 CHECK(snappy::Uncompress(fullinput.data(), fullinput.size(), &uncompressed));
1184
1185 CHECK_OK(file::SetContents(string(fname).append(".uncomp"), uncompressed,
1186 file::Defaults()));
1187 }
1188
MeasureFile(const char * fname)1189 static void MeasureFile(const char* fname) {
1190 string fullinput;
1191 CHECK_OK(file::GetContents(fname, &fullinput, file::Defaults()));
1192 printf("%-40s :\n", fname);
1193
1194 int start_len = (FLAGS_start_len < 0) ? fullinput.size() : FLAGS_start_len;
1195 int end_len = fullinput.size();
1196 if (FLAGS_end_len >= 0) {
1197 end_len = min<int>(fullinput.size(), FLAGS_end_len);
1198 }
1199 for (int len = start_len; len <= end_len; len++) {
1200 const char* const input = fullinput.data();
1201 int repeats = (FLAGS_bytes + len) / (len + 1);
1202 if (FLAGS_zlib) Measure(input, len, ZLIB, repeats, 1024<<10);
1203 if (FLAGS_lzo) Measure(input, len, LZO, repeats, 1024<<10);
1204 if (FLAGS_liblzf) Measure(input, len, LIBLZF, repeats, 1024<<10);
1205 if (FLAGS_quicklz) Measure(input, len, QUICKLZ, repeats, 1024<<10);
1206 if (FLAGS_fastlz) Measure(input, len, FASTLZ, repeats, 1024<<10);
1207 if (FLAGS_snappy) Measure(input, len, SNAPPY, repeats, 4096<<10);
1208
1209 // For block-size based measurements
1210 if (0 && FLAGS_snappy) {
1211 Measure(input, len, SNAPPY, repeats, 8<<10);
1212 Measure(input, len, SNAPPY, repeats, 16<<10);
1213 Measure(input, len, SNAPPY, repeats, 32<<10);
1214 Measure(input, len, SNAPPY, repeats, 64<<10);
1215 Measure(input, len, SNAPPY, repeats, 256<<10);
1216 Measure(input, len, SNAPPY, repeats, 1024<<10);
1217 }
1218 }
1219 }
1220
1221 static struct {
1222 const char* label;
1223 const char* filename;
1224 size_t size_limit;
1225 } files[] = {
1226 { "html", "html", 0 },
1227 { "urls", "urls.10K", 0 },
1228 { "jpg", "fireworks.jpeg", 0 },
1229 { "jpg_200", "fireworks.jpeg", 200 },
1230 { "pdf", "paper-100k.pdf", 0 },
1231 { "html4", "html_x_4", 0 },
1232 { "txt1", "alice29.txt", 0 },
1233 { "txt2", "asyoulik.txt", 0 },
1234 { "txt3", "lcet10.txt", 0 },
1235 { "txt4", "plrabn12.txt", 0 },
1236 { "pb", "geo.protodata", 0 },
1237 { "gaviota", "kppkn.gtb", 0 },
1238 };
1239
BM_UFlat(int iters,int arg)1240 static void BM_UFlat(int iters, int arg) {
1241 StopBenchmarkTiming();
1242
1243 // Pick file to process based on "arg"
1244 CHECK_GE(arg, 0);
1245 CHECK_LT(arg, ARRAYSIZE(files));
1246 string contents = ReadTestDataFile(files[arg].filename,
1247 files[arg].size_limit);
1248
1249 string zcontents;
1250 snappy::Compress(contents.data(), contents.size(), &zcontents);
1251 char* dst = new char[contents.size()];
1252
1253 SetBenchmarkBytesProcessed(static_cast<int64>(iters) *
1254 static_cast<int64>(contents.size()));
1255 SetBenchmarkLabel(files[arg].label);
1256 StartBenchmarkTiming();
1257 while (iters-- > 0) {
1258 CHECK(snappy::RawUncompress(zcontents.data(), zcontents.size(), dst));
1259 }
1260 StopBenchmarkTiming();
1261
1262 delete[] dst;
1263 }
1264 BENCHMARK(BM_UFlat)->DenseRange(0, ARRAYSIZE(files) - 1);
1265
BM_UValidate(int iters,int arg)1266 static void BM_UValidate(int iters, int arg) {
1267 StopBenchmarkTiming();
1268
1269 // Pick file to process based on "arg"
1270 CHECK_GE(arg, 0);
1271 CHECK_LT(arg, ARRAYSIZE(files));
1272 string contents = ReadTestDataFile(files[arg].filename,
1273 files[arg].size_limit);
1274
1275 string zcontents;
1276 snappy::Compress(contents.data(), contents.size(), &zcontents);
1277
1278 SetBenchmarkBytesProcessed(static_cast<int64>(iters) *
1279 static_cast<int64>(contents.size()));
1280 SetBenchmarkLabel(files[arg].label);
1281 StartBenchmarkTiming();
1282 while (iters-- > 0) {
1283 CHECK(snappy::IsValidCompressedBuffer(zcontents.data(), zcontents.size()));
1284 }
1285 StopBenchmarkTiming();
1286 }
1287 BENCHMARK(BM_UValidate)->DenseRange(0, 4);
1288
BM_UIOVec(int iters,int arg)1289 static void BM_UIOVec(int iters, int arg) {
1290 StopBenchmarkTiming();
1291
1292 // Pick file to process based on "arg"
1293 CHECK_GE(arg, 0);
1294 CHECK_LT(arg, ARRAYSIZE(files));
1295 string contents = ReadTestDataFile(files[arg].filename,
1296 files[arg].size_limit);
1297
1298 string zcontents;
1299 snappy::Compress(contents.data(), contents.size(), &zcontents);
1300
1301 // Uncompress into an iovec containing ten entries.
1302 const int kNumEntries = 10;
1303 struct iovec iov[kNumEntries];
1304 char *dst = new char[contents.size()];
1305 int used_so_far = 0;
1306 for (int i = 0; i < kNumEntries; ++i) {
1307 iov[i].iov_base = dst + used_so_far;
1308 if (used_so_far == contents.size()) {
1309 iov[i].iov_len = 0;
1310 continue;
1311 }
1312
1313 if (i == kNumEntries - 1) {
1314 iov[i].iov_len = contents.size() - used_so_far;
1315 } else {
1316 iov[i].iov_len = contents.size() / kNumEntries;
1317 }
1318 used_so_far += iov[i].iov_len;
1319 }
1320
1321 SetBenchmarkBytesProcessed(static_cast<int64>(iters) *
1322 static_cast<int64>(contents.size()));
1323 SetBenchmarkLabel(files[arg].label);
1324 StartBenchmarkTiming();
1325 while (iters-- > 0) {
1326 CHECK(snappy::RawUncompressToIOVec(zcontents.data(), zcontents.size(), iov,
1327 kNumEntries));
1328 }
1329 StopBenchmarkTiming();
1330
1331 delete[] dst;
1332 }
1333 BENCHMARK(BM_UIOVec)->DenseRange(0, 4);
1334
BM_UFlatSink(int iters,int arg)1335 static void BM_UFlatSink(int iters, int arg) {
1336 StopBenchmarkTiming();
1337
1338 // Pick file to process based on "arg"
1339 CHECK_GE(arg, 0);
1340 CHECK_LT(arg, ARRAYSIZE(files));
1341 string contents = ReadTestDataFile(files[arg].filename,
1342 files[arg].size_limit);
1343
1344 string zcontents;
1345 snappy::Compress(contents.data(), contents.size(), &zcontents);
1346 char* dst = new char[contents.size()];
1347
1348 SetBenchmarkBytesProcessed(static_cast<int64>(iters) *
1349 static_cast<int64>(contents.size()));
1350 SetBenchmarkLabel(files[arg].label);
1351 StartBenchmarkTiming();
1352 while (iters-- > 0) {
1353 snappy::ByteArraySource source(zcontents.data(), zcontents.size());
1354 snappy::UncheckedByteArraySink sink(dst);
1355 CHECK(snappy::Uncompress(&source, &sink));
1356 }
1357 StopBenchmarkTiming();
1358
1359 string s(dst, contents.size());
1360 CHECK_EQ(contents, s);
1361
1362 delete[] dst;
1363 }
1364
1365 BENCHMARK(BM_UFlatSink)->DenseRange(0, ARRAYSIZE(files) - 1);
1366
BM_ZFlat(int iters,int arg)1367 static void BM_ZFlat(int iters, int arg) {
1368 StopBenchmarkTiming();
1369
1370 // Pick file to process based on "arg"
1371 CHECK_GE(arg, 0);
1372 CHECK_LT(arg, ARRAYSIZE(files));
1373 string contents = ReadTestDataFile(files[arg].filename,
1374 files[arg].size_limit);
1375
1376 char* dst = new char[snappy::MaxCompressedLength(contents.size())];
1377
1378 SetBenchmarkBytesProcessed(static_cast<int64>(iters) *
1379 static_cast<int64>(contents.size()));
1380 StartBenchmarkTiming();
1381
1382 size_t zsize = 0;
1383 while (iters-- > 0) {
1384 snappy::RawCompress(contents.data(), contents.size(), dst, &zsize);
1385 }
1386 StopBenchmarkTiming();
1387 const double compression_ratio =
1388 static_cast<double>(zsize) / std::max<size_t>(1, contents.size());
1389 SetBenchmarkLabel(StringPrintf("%s (%.2f %%)",
1390 files[arg].label, 100.0 * compression_ratio));
1391 VLOG(0) << StringPrintf("compression for %s: %zd -> %zd bytes",
1392 files[arg].label, contents.size(), zsize);
1393 delete[] dst;
1394 }
1395 BENCHMARK(BM_ZFlat)->DenseRange(0, ARRAYSIZE(files) - 1);
1396
1397 } // namespace snappy
1398
1399
main(int argc,char ** argv)1400 int main(int argc, char** argv) {
1401 InitGoogle(argv[0], &argc, &argv, true);
1402 RunSpecifiedBenchmarks();
1403
1404 if (argc >= 2) {
1405 for (int arg = 1; arg < argc; arg++) {
1406 if (FLAGS_write_compressed) {
1407 CompressFile(argv[arg]);
1408 } else if (FLAGS_write_uncompressed) {
1409 UncompressFile(argv[arg]);
1410 } else {
1411 MeasureFile(argv[arg]);
1412 }
1413 }
1414 return 0;
1415 }
1416
1417 return RUN_ALL_TESTS();
1418 }
1419