{"thread":{"id":"14491","subject":"[StGit PATCH] Add some performance testing scripts","startedAt":"2008-07-16T19:35:49Z","lastAt":"2008-07-18T21:41:32Z","messageCount":2,"participants":["Karl Hasselström","Catalin Marinas"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"83571","messageId":"20080716193549.3467.13072.stgit@yoghurt","threadId":"14491","inReplyTo":null,"subject":"[StGit PATCH] Add some performance testing scripts","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2008-07-16T19:35:49Z","receivedAt":"2008-07-16T19:35:49Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"find_patchbomb.py: Given a git repo, finds the longest linear sequence\n  of commits. Useful for testing StGit on a real repository.\n\nsetup.sh: Creates two test repositories, one synthetic and one based\n  on the Linux kernel repo, with strategically placed tags.\n\ncreate_synthetic_repo.py: Helper script for setup.sh; it produces\n  output that is to be fed to git fast-import.\n\nperftest.py: Runs one of a (small) number of hard-coded performance\n  tests against a copy of one of the repos created by setup.sh. The\n  initial testcases all involve uncommitting a large number of patches\n  and then rebasing them.\n\nSigned-off-by: Karl Hasselström <kha@treskal.com>\n\n---\n\n perf/.gitignore               |    2 +\n perf/create_synthetic_repo.py |   61 ++++++++++++++++++++++++++++\n perf/find_patchbomb.py        |   31 ++++++++++++++\n perf/perftest.py              |   89 +++++++++++++++++++++++++++++++++++++++++\n perf/setup.sh                 |   52 ++++++++++++++++++++++++\n 5 files changed, 235 insertions(+), 0 deletions(-)\n create mode 100644 perf/.gitignore\n create mode 100644 perf/create_synthetic_repo.py\n create mode 100644 perf/find_patchbomb.py\n create mode 100644 perf/perftest.py\n create mode 100644 perf/setup.sh\n\n\ndiff --git a/perf/.gitignore b/perf/.gitignore\nnew file mode 100644\nindex 0000000..dfae110\n--- /dev/null\n+++ b/perf/.gitignore\n@@ -0,0 +1,2 @@\n+/*.orig\n+/*.trash\ndiff --git a/perf/create_synthetic_repo.py b/perf/create_synthetic_repo.py\nnew file mode 100644\nindex 0000000..4d6ef6b\n--- /dev/null\n+++ b/perf/create_synthetic_repo.py\n@@ -0,0 +1,61 @@\n+next_mark = 1\n+def get_mark():\n+    global next_mark\n+    next_mark += 1\n+    return (next_mark - 1)\n+\n+def write_data(s):\n+    print 'data %d' % len(s)\n+    print s\n+\n+def write_blob(s):\n+    print 'blob'\n+    m = get_mark()\n+    print 'mark :%d' % m\n+    write_data(s)\n+    return m\n+\n+def write_commit(branch, files, msg, parent = None):\n+    print 'commit %s' % branch\n+    m = get_mark()\n+    print 'mark :%d' % m\n+    auth = 'X Ample <xa@example.com> %d +0000' % (1000000000 + m)\n+    print 'author %s' % auth\n+    print 'committer %s' % auth\n+    write_data(msg)\n+    if parent != None:\n+        print 'from :%d' % parent\n+    for fn, fm in sorted(files.iteritems()):\n+        print 'M 100644 :%d %s' % (fm, fn)\n+    return m\n+\n+def set_ref(ref, mark):\n+    print 'reset %s' % ref\n+    print 'from :%d' % mark\n+\n+def stdblob(fn):\n+    return ''.join('%d %s\\n' % (x, fn) for x in xrange(10))\n+\n+def iter_paths():\n+    for i in xrange(32):\n+        for j in xrange(32):\n+            for k in xrange(32):\n+                yield '%02d/%02d/%02d' % (i, j, k)\n+\n+def setup():\n+    def t(name): return 'refs/tags/%s' % name\n+    files = dict((fn, write_blob(stdblob(fn))) for fn in iter_paths())\n+    initial = write_commit(t('bomb-base'), files, 'Initial commit')\n+    set_ref(t('bomb-top'), initial)\n+    for fn in iter_paths():\n+        write_commit(t('bomb-top'),\n+                     { fn: write_blob(stdblob(fn) + 'Last line\\n') },\n+                     'Add last line to %s' % fn)\n+    write_commit(t('add-file'), { 'woo-hoo.txt': write_blob('woo-hoo\\n') },\n+                 'Add a new file', parent = initial)\n+    files = dict((fn, write_blob('First line\\n' + stdblob(fn)))\n+                 for fn in iter_paths())\n+    write_commit(t('modify-all'), files, 'Add first line to all files',\n+                 parent = initial)\n+\n+setup()\ndiff --git a/perf/find_patchbomb.py b/perf/find_patchbomb.py\nnew file mode 100644\nindex 0000000..69a78c7\n--- /dev/null\n+++ b/perf/find_patchbomb.py\n@@ -0,0 +1,31 @@\n+# Feed this with git rev-list HEAD --parents\n+\n+import sys\n+\n+parents = {}\n+for line in sys.stdin.readlines():\n+    commits = line.split()\n+    parents[commits[0]] = commits[1:]\n+\n+sequence_num = {}\n+stack = []\n+for commit in parents.keys():\n+    stack.append(commit)\n+    while stack:\n+        c = stack.pop()\n+        if c in sequence_num:\n+            continue\n+        ps = parents[c]\n+        if len(ps) == 1:\n+            p = ps[0]\n+            if p in sequence_num:\n+                sequence_num[c] = 1 + sequence_num[p]\n+            else:\n+                stack.append(c)\n+                stack.append(p)\n+        else:\n+            sequence_num[c] = 0\n+\n+(num, commit) = max((num, commit) for (commit, num)\n+                    in sequence_num.iteritems())\n+print '%s is a sequence of %d patches' % (commit, num)\ndiff --git a/perf/perftest.py b/perf/perftest.py\nnew file mode 100644\nindex 0000000..2655869\n--- /dev/null\n+++ b/perf/perftest.py\n@@ -0,0 +1,89 @@\n+import datetime, subprocess, sys\n+\n+def duration(t1, t2):\n+    d = t2 - t1\n+    return 86400*d.days + d.seconds + 1e-6*d.microseconds\n+\n+class Run(object):\n+    def __init__(self):\n+        self.__cwd = None\n+        self.__log = []\n+    def __call__(self, *cmd, **args):\n+        kwargs = { 'cwd': self.__cwd }\n+        if args.get('capture_stdout', False):\n+            kwargs['stdout'] = subprocess.PIPE\n+        start = datetime.datetime.now()\n+        p = subprocess.Popen(cmd, **kwargs)\n+        (out, err) = p.communicate()\n+        stop = datetime.datetime.now()\n+        self.__log.append((cmd, duration(start, stop)))\n+        return out\n+    def cd(self, dir):\n+        self.__cwd = dir\n+    def summary(self):\n+        def pcmd(c): return ' '.join(c)\n+        def ptime(t): return '%.3f' % t\n+        (cs, times) = zip(*self.__log)\n+        ttime = sum(times)\n+        cl = max(len(pcmd(c)) for c in cs)\n+        tl = max(len(ptime(t)) for t in list(times) + [ttime])\n+        for (c, t) in self.__log:\n+            print '%*s  %*s' % (tl, ptime(t), -cl, pcmd(c))\n+        print '%*s' % (tl, ptime(ttime))\n+\n+perftests = {}\n+perftestdesc = {}\n+def perftest(desc, name = None):\n+    def decorator(f):\n+        def g():\n+            r = Run()\n+            f(r)\n+            r.summary()\n+        perftests[name or f.__name__] = g\n+        perftestdesc[name or f.__name__] = desc\n+        return g\n+    return decorator\n+\n+def copy_testdir(dir):\n+    dir = dir.rstrip('/')\n+    tmp = dir + '.trash'\n+    r = Run()\n+    r('rsync', '-a', '--delete', dir + '/', tmp)\n+    return tmp\n+\n+def new_rebase(r, ref):\n+    top = r('stg', 'top', capture_stdout = True)\n+    r('stg', 'pop', '-a')\n+    r('git', 'reset', '--hard', ref)\n+    r('stg', 'goto', top.strip())\n+\n+def old_rebase(r, ref):\n+    r('stg', 'rebase', ref)\n+\n+def def_rebasetest(rebase, dir, tag):\n+    @perftest('%s rebase onto %s in %s' % (rebase, tag, dir),\n+              'rebase-%srebase-%s-%s' % (rebase, tag, dir))\n+    def rebasetest(r):\n+        r.cd(copy_testdir(dir))\n+        r('stg', 'init')\n+        if dir == 'synt':\n+            r('stg', 'uncommit', '-n', '500')\n+        else:\n+            r('stg', 'uncommit', '-x', '-t', 'bomb-base')\n+        if rebase == 'new':\n+            new_rebase(r, tag)\n+        else:\n+            old_rebase(r, tag)\n+for rebase in ['old', 'new']:\n+    for (dir, tag) in [('synt', 'add-file'),\n+                       ('synt', 'modify-all'),\n+                       ('linux', 'add-file')]:\n+        def_rebasetest(rebase, dir, tag)\n+\n+args = sys.argv[1:]\n+if len(args) == 0:\n+    for (fun, desc) in sorted(perftestdesc.iteritems()):\n+        print '%s: %s' % (fun, desc)\n+else:\n+    for test in args:\n+        perftests[test]()\ndiff --git a/perf/setup.sh b/perf/setup.sh\nnew file mode 100644\nindex 0000000..b92ddfc\n--- /dev/null\n+++ b/perf/setup.sh\n@@ -0,0 +1,52 @@\n+krepo='git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git'\n+\n+get_linux() {\n+    rm -rf linux.orig\n+    git clone \"$krepo\" linux.orig\n+}\n+\n+mod_linux() {\n+    # Tag the top and base of a very long linear sequence of commits.\n+    git tag bomb-top 85040bcb4643cba578839e953f25e2d1965d83d0\n+    git tag bomb-base bomb-top~1470\n+\n+    # Add a file at the base of the linear sequence.\n+    git checkout bomb-base\n+    echo \"woo-hoo\" > woo-hoo.txt\n+    git add woo-hoo.txt\n+    git commit -m \"Add a file\"\n+    git tag add-file\n+\n+    # Clean up and go to start position.\n+    git gc\n+    git update-ref refs/heads/master bomb-top\n+    git checkout master\n+}\n+\n+setup_linux () {\n+    get_linux\n+    ( cd linux.orig && mod_linux )\n+}\n+\n+create_empty () {\n+    dir=\"$1\"\n+    rm -rf $dir\n+    mkdir $dir\n+    ( cd $dir && git init )\n+}\n+\n+fill_synthetic () {\n+    python ../create_synthetic_repo.py | git fast-import\n+    git gc --aggressive\n+    git update-ref refs/heads/master bomb-top\n+    git checkout master\n+}\n+\n+setup_synthetic()\n+{\n+    create_empty synt.orig\n+    ( cd synt.orig && fill_synthetic )\n+}\n+\n+setup_linux\n+setup_synthetic\n"},{"id":"83915","messageId":"b0943d9e0807181441u2ff0180ei1b7a28dc24358320@mail.gmail.com","threadId":"14491","inReplyTo":"20080716193549.3467.13072.stgit@yoghurt","subject":"Re: [StGit PATCH] Add some performance testing scripts","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2008-07-18T21:41:32Z","receivedAt":"2008-07-18T21:41:32Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"2008/7/16 Karl Hasselström <kha@treskal.com>:\n> find_patchbomb.py: Given a git repo, finds the longest linear sequence\n>  of commits. Useful for testing StGit on a real repository.\n>\n> setup.sh: Creates two test repositories, one synthetic and one based\n>  on the Linux kernel repo, with strategically placed tags.\n>\n> create_synthetic_repo.py: Helper script for setup.sh; it produces\n>  output that is to be fed to git fast-import.\n>\n> perftest.py: Runs one of a (small) number of hard-coded performance\n>  tests against a copy of one of the repos created by setup.sh. The\n>  initial testcases all involve uncommitting a large number of patches\n>  and then rebasing them.\n\nLooks good. Thanks.\n\n-- \nCatalin\n"}]}