3 * This file is part of XDelta - A binary delta generator.
5 * Copyright (C) 1997, 1998, 2001 Josh MacDonald
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 * Author: Josh MacDonald <jmacd@CS.Berkeley.EDU>
23 * $Id: xdeltatest.c 1.7.1.1 Sat, 27 Jan 2007 17:53:47 -0800 jmacd $
26 #include <sys/param.h>
27 #include <sys/types.h>
31 #if TIME_WITH_SYS_TIME
32 # include <sys/time.h>
36 # include <sys/time.h>
45 #include <sys/resource.h>
51 //////////////////////////////////////////////////////////////////////
52 // Configure these parameters
53 // @@@ Of course, a real test wouldn't require this configuration,
55 //////////////////////////////////////////////////////////////////////
57 const char* cmd_data_source = "/zerostreet2/small-scratch-file";
58 guint cmd_size = 1<<20;
60 guint cmd_warmups = 2;
63 guint cmd_changes = 1000;
64 guint cmd_deletion_length = 30;
65 guint cmd_insertion_length = 15;
67 //////////////////////////////////////////////////////////////////////
69 //////////////////////////////////////////////////////////////////////
71 #define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0]))
73 #define TEST_PREFIX "/tmp/xdeltatest"
75 #define TEST_IS_GZIP 1
76 #define TEST_IS_XDELTA 2
78 typedef struct _File File;
79 typedef struct _Patch Patch;
80 typedef struct _TestProfile TestProfile;
81 typedef struct _Instruction Instruction;
91 char name[MAXPATHLEN];
103 const char *progname;
108 #define BUFSIZ (1<<24)
110 guint8 __tmp_buffer[BUFSIZ];
112 // Note: Of course you should be able to set all this up on the
115 TestProfile cmd_profiles[] =
117 { "xdelta -qn ", "../xdelta", TEST_IS_XDELTA },
118 { "diff --rcs -a", "/usr/bin/diff", 0 },
119 { "gzip ", "/bin/gzip", TEST_IS_GZIP },
122 int cmd_slevels[] = { 16, 32, 64 };
123 int cmd_zlevels[] = { 0, 1, 3, 6, 9 };
124 guint16 cmd_seed[3] = { 47384, 8594, 27489 };
126 FILE* data_source_handle;
127 guint data_source_length;
128 File* data_source_file;
130 long current_to_size;
131 long current_from_size;
143 void add_tv (GTimer *timer)
145 __total_time += g_timer_elapsed (timer, NULL);
149 report (TestProfile *tp, int zlevel, int slevel)
151 static gboolean once = TRUE;
153 double t = __total_time / (double) cmd_reps;
158 if (tp->flags & TEST_IS_XDELTA)
160 sprintf (slevel_str, "%d", slevel);
167 if (tp->flags & TEST_IS_GZIP)
169 s = __dsize / (double) current_to_size;
174 s = __dsize / (double) (cmd_changes * cmd_insertion_length);
175 u = "insertion size";
182 g_print ("Program\t\tzlevel\tslevel\tSeconds\tNormalized\n\t\t\t\t\tcompression\n");
185 g_print ("%s\t%d\t%s\t%0.3f\t%0.3f (of %s)\n", tp->name, zlevel, slevel_str, t, s, u);
189 random_offset (guint len)
191 return lrand48 () % (data_source_length - len);
197 g_warning ("FAILURE\n");
201 gboolean starts_with (const char* s, const char *start)
203 return strncmp (s, start, strlen (start)) == 0;
207 read_patch (File *file, struct stat *sbuf)
209 Patch *p = g_new0 (Patch,1);
210 FILE *f = fopen (file->name, "r");
212 p->length = (int)sbuf->st_size;
214 p->data = g_malloc (p->length);
216 if (! f || fread (p->data, 1, p->length, f) != p->length)
230 static gint count = 0;
231 File *file = g_new0 (File, 1);
233 sprintf (file->name, "%s.%d.%d", TEST_PREFIX, getpid (), count++);
239 compare_files (File* fromfile, File* tofile)
246 guint8 buf1[1024], buf2[1024];
248 toh = fopen (tofile->name, "r");
249 fromh = fopen (fromfile->name, "r");
256 gint readf = fread (buf1, 1, 1024, fromh);
257 gint readt = fread (buf2, 1, 1024, toh);
258 gint i, m = MIN(readf, readt);
260 if (readf < 0 || readt < 0)
263 for (i = 0; i < m; i += 1, pos += 1)
265 if (buf1[i] != buf2[i])
284 write_file (File* file, Instruction* inst)
290 if (! (h = fopen (file->name, "w"))) {
295 for (; inst; inst = inst->next)
297 g_assert (inst->length <= BUFSIZ);
299 if ((ret = fseek (data_source_handle, inst->offset, SEEK_SET))) {
304 if ((ret = fread (__tmp_buffer, 1, inst->length, data_source_handle)) != inst->length) {
309 if ((ret = fwrite (__tmp_buffer, 1, inst->length, h)) != inst->length) {
314 size += inst->length;
317 if ((ret = fclose (h))) {
326 perform_change_rec (Instruction* inst, guint32 change_off, guint* total_len)
328 if (change_off < inst->length)
330 guint32 to_delete = cmd_deletion_length;
331 guint32 avail = inst->length;
332 guint32 this_delete = MIN (to_delete, avail);
333 Instruction* new_inst;
336 inst->length -= this_delete;
337 to_delete -= this_delete;
339 while (to_delete > 0 && inst->next->length < to_delete)
341 to_delete -= inst->next->length;
342 inst->next = inst->next->next;
346 inst->next->offset += to_delete;
349 new_inst = g_new0 (Instruction, 1);
351 new_inst->offset = random_offset (cmd_insertion_length);
352 new_inst->length = cmd_insertion_length;
353 new_inst->next = inst->next;
354 inst->next = new_inst;
356 (* total_len) += cmd_insertion_length - cmd_deletion_length;
362 inst->next = perform_change_rec (inst->next, change_off - inst->length, total_len);
368 perform_change (Instruction* inst, guint* len)
370 return perform_change_rec (inst, lrand48() % ((* len) - cmd_deletion_length), len);
374 xdelta_verify (TestProfile *tp, int zlevel, int slevel, File* from, File* to, File* out, File *re)
378 if ((pid = vfork()) < 0)
390 perror ("execl failed");
394 if (waitpid (pid, &status, 0) != pid)
396 perror ("wait failed");
400 // Note: program is expected to return 0, 1 meaning diff or no diff,
401 // > 1 indicates failure
402 if (! WIFEXITED (status) || WEXITSTATUS (status) > 1)
404 g_warning ("patch command failed\n");
408 compare_files (to, re);
414 run_command (TestProfile *tp, int zlevel, int slevel, File* from, File* to, File* out, File *re, gboolean accounting)
416 int pid, status, outfd;
418 char xdelta_args[16];
419 char xdelta_args2[16];
420 char diff_gzargs[16];
423 GTimer *timer = g_timer_new ();
425 sprintf (xdelta_args, "-qn%d", zlevel);
426 sprintf (xdelta_args2, "-s%d", slevel);
427 sprintf (diff_gzargs, "wb%d", zlevel);
428 sprintf (gzip_args, "-c%d", zlevel);
432 g_timer_start (timer);
434 if ((pid = vfork()) < 0)
439 if (starts_with (tp->name, "xdelta"))
453 outfd = open (out->name, O_CREAT | O_TRUNC | O_WRONLY, 0777);
461 dup2(outfd, STDOUT_FILENO);
469 if (starts_with (tp->name, "diff"))
477 else if (starts_with (tp->name, "gzip"))
487 perror ("execl failed");
491 if (waitpid (pid, &status, 0) != pid)
493 perror ("wait failed");
497 // Note: program is expected to return 0, 1 meaning diff or no diff,
498 // > 1 indicates failure
499 if (! WIFEXITED (status) || WEXITSTATUS (status) > 1)
501 g_warning ("delta command failed\n");
505 if (stat (out->name, & sbuf))
511 // Do zlib compression on behalf of diff
512 if (zlevel > 0 && starts_with (tp->name, "diff"))
514 Patch *patch = read_patch (out, & sbuf);
515 gzFile *rewrite = gzopen (out->name, diff_gzargs);
517 if (! rewrite) fail ();
519 if (gzwrite (rewrite, patch->data, patch->length) == 0) { perror ("gzwrite"); fail (); }
520 if (gzclose (rewrite) != Z_OK) { perror ("gzclose"); fail (); }
521 if (stat (out->name, & sbuf)) { perror ("stat"); fail (); }
524 g_timer_stop (timer);
533 __dsize = sbuf.st_size;
537 if (starts_with (tp->name, "xdelta"))
539 if (! xdelta_verify (tp, zlevel, slevel, from, to, out, re))
541 g_warning ("verify failed");
548 if (__dsize != sbuf.st_size)
550 g_warning ("%s -%d: delta command produced different size deltas: %d and %d",
551 tp->progname, zlevel, (int)__dsize, (int)sbuf.st_size);
556 g_timer_destroy (timer);
562 test1 (TestProfile *test_profile,
569 guint i, change, current_size = cmd_size;
570 guint end_size = (cmd_changes * cmd_insertion_length) + cmd_size;
573 int zlevel_i, slevel_i;
577 if ((ret = stat (cmd_data_source, & sbuf)))
579 perror (cmd_data_source);
583 if (! (data_source_handle = fopen (cmd_data_source, "r")))
585 perror (cmd_data_source);
589 data_source_length = sbuf.st_size;
591 /* arbitrary checks */
592 if (data_source_length < (1.5 * end_size))
593 g_warning ("data source should be longer\n");
595 if ((cmd_changes * cmd_deletion_length) > cmd_size)
597 g_warning ("no copies are expected\n");
601 inst = g_new0 (Instruction, 1);
603 inst->offset = random_offset (cmd_size);
604 inst->length = cmd_size;
606 current_from_size = write_file (from_file, inst);
608 for (change = 0; change < cmd_changes; change += 1)
609 inst = perform_change (inst, & current_size);
611 current_to_size = write_file (to_file, inst);
613 for (slevel_i = 0; slevel_i < ARRAY_LENGTH(cmd_slevels); slevel_i += 1)
615 int slevel = cmd_slevels[slevel_i];
617 if ((test_profile->flags & TEST_IS_XDELTA) == 0 && slevel_i != 0)
622 for (zlevel_i = 0; zlevel_i < ARRAY_LENGTH(cmd_zlevels); zlevel_i += 1)
624 int zlevel = cmd_zlevels[zlevel_i];
626 if (test_profile->flags & TEST_IS_GZIP)
628 if (zlevel != 1 && zlevel != 9)
634 for (i = 0; i < cmd_warmups + cmd_reps; i += 1)
636 if (! run_command (test_profile,
643 (i >= cmd_warmups) /* true => accounting */))
649 report (test_profile, zlevel, slevel);
657 main (gint argc, gchar** argv)
666 system ("rm -rf " TEST_PREFIX "*");
668 from_file = empty_file ();
669 to_file = empty_file ();
670 out_file = empty_file ();
671 re_file = empty_file ();
673 for (profile_i = 0; profile_i < ARRAY_LENGTH(cmd_profiles); profile_i += 1)
675 test1 (& cmd_profiles[profile_i], from_file, to_file, out_file, re_file);
677 system ("rm -rf " TEST_PREFIX "*");