diff options
Diffstat (limited to 't/perf')
-rw-r--r-- | t/perf/.gitignore | 1 | ||||
-rw-r--r-- | t/perf/Makefile | 9 | ||||
-rw-r--r-- | t/perf/README | 13 | ||||
-rwxr-xr-x | t/perf/aggregate.perl | 42 | ||||
-rwxr-xr-x | t/perf/bisect_regression | 2 | ||||
-rwxr-xr-x | t/perf/p0001-rev-list.sh | 18 | ||||
-rwxr-xr-x | t/perf/p1400-update-ref.sh | 33 | ||||
-rwxr-xr-x | t/perf/p2000-sparse-operations.sh | 101 | ||||
-rwxr-xr-x | t/perf/p3400-rebase.sh | 6 | ||||
-rwxr-xr-x | t/perf/p4205-log-pretty-formats.sh | 2 | ||||
-rwxr-xr-x | t/perf/p4209-pickaxe.sh | 70 | ||||
-rwxr-xr-x | t/perf/p5302-pack-index.sh | 48 | ||||
-rwxr-xr-x | t/perf/p5303-many-packs.sh | 69 | ||||
-rwxr-xr-x | t/perf/p5304-prune.sh | 35 | ||||
-rwxr-xr-x | t/perf/p5310-pack-bitmaps.sh | 53 | ||||
-rwxr-xr-x | t/perf/p5311-pack-bitmaps-fetch.sh | 1 | ||||
-rwxr-xr-x | t/perf/p5600-partial-clone.sh | 42 | ||||
-rwxr-xr-x | t/perf/p5601-clone-reference.sh | 27 | ||||
-rwxr-xr-x | t/perf/p7519-fsmonitor.sh | 211 | ||||
-rwxr-xr-x | t/perf/p9300-fast-import-export.sh | 23 | ||||
-rw-r--r-- | t/perf/perf-lib.sh | 87 | ||||
-rwxr-xr-x | t/perf/run | 43 |
22 files changed, 769 insertions, 167 deletions
diff --git a/t/perf/.gitignore b/t/perf/.gitignore index 982eb8e3a9..72f5d0d314 100644 --- a/t/perf/.gitignore +++ b/t/perf/.gitignore @@ -1,3 +1,4 @@ /build/ /test-results/ +/test-trace/ /trash directory*/ diff --git a/t/perf/Makefile b/t/perf/Makefile index 8c47155a7c..2465770a78 100644 --- a/t/perf/Makefile +++ b/t/perf/Makefile @@ -1,15 +1,18 @@ -include ../../config.mak export GIT_TEST_OPTIONS -all: perf +all: test-lint perf perf: pre-clean ./run pre-clean: - rm -rf test-results + rm -rf test-results test-trace clean: - rm -rf build "trash directory".* test-results + rm -rf build "trash directory".* test-results test-trace + +test-lint: + $(MAKE) -C .. test-lint .PHONY: all perf pre-clean clean diff --git a/t/perf/README b/t/perf/README index be12090c38..fb9127a66f 100644 --- a/t/perf/README +++ b/t/perf/README @@ -28,6 +28,8 @@ the tests on the current git repository. 7810.3: grep --cached, cheap regex 3.07(3.02+0.25) 7810.4: grep --cached, expensive regex 9.39(30.57+0.24) +Output format is in seconds "Elapsed(User + System)" + You can compare multiple repositories and even git revisions with the 'run' script: @@ -45,7 +47,7 @@ call the aggregation script to summarize the results: $ ./p0001-rev-list.sh [...] - $ GIT_BUILD_DIR=/path/to/other/git ./p0001-rev-list.sh + $ ./run /path/to/other/git -- ./p0001-rev-list.sh [...] $ ./aggregate.perl . /path/to/other/git ./p0001-rev-list.sh @@ -84,6 +86,15 @@ You can set the following variables (also in your config.mak): probably be about linux.git size for optimal results. Both default to the git.git you are running from. + GIT_PERF_EXTRA + Boolean to enable additional tests. Most test scripts are + written to detect regressions between two versions of Git, and + the output will compare timings for individual tests between + those versions. Some scripts have additional tests which are not + run by default, that show patterns within a single version of + Git (e.g., performance of index-pack as the number of threads + changes). These can be enabled with GIT_PERF_EXTRA. + You can also pass the options taken by ordinary git tests; the most useful one is: diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index 494907a892..14e4cda287 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -3,9 +3,8 @@ use lib '../../perl/build/lib'; use strict; use warnings; -use JSON; use Getopt::Long; -use Git; +use Cwd qw(realpath); sub get_times { my $name = shift; @@ -85,6 +84,11 @@ sub format_size { return $out; } +sub sane_backticks { + open(my $fh, '-|', @_); + return <$fh>; +} + my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests, $codespeed, $sortby, $subsection, $reponame); @@ -99,18 +103,22 @@ usage() unless $rc; while (scalar @ARGV) { my $arg = $ARGV[0]; my $dir; + my $prefix = ''; last if -f $arg or $arg eq "--"; if (! -d $arg) { - my $rev = Git::command_oneline(qw(rev-parse --verify), $arg); + my $rev = sane_backticks(qw(git rev-parse --verify), $arg); + chomp $rev; $dir = "build/".$rev; + } elsif ($arg eq '.') { + $dir = '.'; } else { - $arg =~ s{/*$}{}; - $dir = $arg; - $dirabbrevs{$dir} = $dir; + $dir = realpath($arg); + $dirnames{$dir} = $dir; + $prefix .= 'bindir'; } push @dirs, $dir; - $dirnames{$dir} = $arg; - my $prefix = $dir; + $dirnames{$dir} ||= $arg; + $prefix .= $dir; $prefix =~ tr/^a-zA-Z0-9/_/c; $prefixes{$dir} = $prefix . '.'; shift @ARGV; @@ -216,13 +224,7 @@ sub print_default_results { for my $i (0..$#dirs) { my $d = $dirs[$i]; my $base = "$resultsdir/$prefixes{$d}$t"; - $times{$prefixes{$d}.$t} = []; - foreach my $type (qw(times size)) { - if (-e "$base.$type") { - $times{$prefixes{$d}.$t} = [get_times("$base.$type")]; - last; - } - } + $times{$prefixes{$d}.$t} = [get_times("$base.result")]; my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}}; my $w = length format_times($r,$u,$s,$firstr); $colwidth[$i] = $w if $w > $colwidth[$i]; @@ -264,7 +266,7 @@ sub print_sorted_results { my ($prevr, $prevu, $prevs, $prevrev); for my $i (0..$#dirs) { my $d = $dirs[$i]; - my ($r, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.times"); + my ($r, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.result"); if ($i > 0 and defined $r and defined $prevr and $prevr > 0) { my $percent = 100.0 * ($r - $prevr) / $prevr; push @evolutions, { "percent" => $percent, @@ -312,9 +314,6 @@ sub print_codespeed_results { $environment = $reponame; } elsif (exists $ENV{GIT_PERF_REPO_NAME} and $ENV{GIT_PERF_REPO_NAME} ne "") { $environment = $ENV{GIT_PERF_REPO_NAME}; - } elsif (exists $ENV{GIT_TEST_INSTALLED} and $ENV{GIT_TEST_INSTALLED} ne "") { - $environment = $ENV{GIT_TEST_INSTALLED}; - $environment =~ s|/bin-wrappers$||; } else { $environment = `uname -r`; chomp $environment; @@ -327,7 +326,7 @@ sub print_codespeed_results { my $commitid = $prefixes{$d}; $commitid =~ s/^build_//; $commitid =~ s/\.$//; - my ($result_value, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.times"); + my ($result_value, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.result"); my %vals = ( "commitid" => $commitid, @@ -342,7 +341,8 @@ sub print_codespeed_results { } } - print to_json(\@data, {utf8 => 1, pretty => 1, canonical => 1}), "\n"; + require JSON; + print JSON::to_json(\@data, {utf8 => 1, pretty => 1, canonical => 1}), "\n"; } binmode STDOUT, ":utf8" or die "PANIC on binmode: $!"; diff --git a/t/perf/bisect_regression b/t/perf/bisect_regression index a94d9955d0..ce47e1662a 100755 --- a/t/perf/bisect_regression +++ b/t/perf/bisect_regression @@ -51,7 +51,7 @@ oldtime=$(echo "$oldtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/') newtime=$(echo "$newtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/') test $(echo "$newtime" "$oldtime" | awk '{ print ($1 > $2) }') = 1 || - die "New time '$newtime' shoud be greater than old time '$oldtime'" + die "New time '$newtime' should be greater than old time '$oldtime'" tmpdir=$(mktemp -d -t bisect_regression_XXXXXX) || die "Failed to create temp directory" echo "$oldtime" >"$tmpdir/oldtime" || die "Failed to write to '$tmpdir/oldtime'" diff --git a/t/perf/p0001-rev-list.sh b/t/perf/p0001-rev-list.sh index ebf172401b..3042a85666 100755 --- a/t/perf/p0001-rev-list.sh +++ b/t/perf/p0001-rev-list.sh @@ -14,6 +14,24 @@ test_perf 'rev-list --all --objects' ' git rev-list --all --objects >/dev/null ' +test_perf 'rev-list --parents' ' + git rev-list --parents HEAD >/dev/null +' + +test_expect_success 'create dummy file' ' + echo unlikely-to-already-be-there >dummy && + git add dummy && + git commit -m dummy +' + +test_perf 'rev-list -- dummy' ' + git rev-list HEAD -- dummy +' + +test_perf 'rev-list --parents -- dummy' ' + git rev-list --parents HEAD -- dummy +' + test_expect_success 'create new unreferenced commit' ' commit=$(git commit-tree HEAD^{tree} -p HEAD) && test_export commit diff --git a/t/perf/p1400-update-ref.sh b/t/perf/p1400-update-ref.sh new file mode 100755 index 0000000000..dda8a74866 --- /dev/null +++ b/t/perf/p1400-update-ref.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +test_description="Tests performance of update-ref" + +. ./perf-lib.sh + +test_perf_fresh_repo + +test_expect_success "setup" ' + test_commit PRE && + test_commit POST && + for i in $(test_seq 5000) + do + printf "start\ncreate refs/heads/%d PRE\ncommit\n" $i && + printf "start\nupdate refs/heads/%d POST PRE\ncommit\n" $i && + printf "start\ndelete refs/heads/%d POST\ncommit\n" $i + done >instructions +' + +test_perf "update-ref" ' + for i in $(test_seq 1000) + do + git update-ref refs/heads/branch PRE && + git update-ref refs/heads/branch POST PRE && + git update-ref -d refs/heads/branch + done +' + +test_perf "update-ref --stdin" ' + git update-ref --stdin <instructions >/dev/null +' + +test_done diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh new file mode 100755 index 0000000000..94513c9774 --- /dev/null +++ b/t/perf/p2000-sparse-operations.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +test_description="test performance of Git operations using the index" + +. ./perf-lib.sh + +test_perf_default_repo + +SPARSE_CONE=f2/f4/f1 + +test_expect_success 'setup repo and indexes' ' + git reset --hard HEAD && + + # Remove submodules from the example repo, because our + # duplication of the entire repo creates an unlikely data shape. + if git config --file .gitmodules --get-regexp "submodule.*.path" >modules + then + git rm $(awk "{print \$2}" modules) && + git commit -m "remove submodules" || return 1 + fi && + + echo bogus >a && + cp a b && + git add a b && + git commit -m "level 0" && + BLOB=$(git rev-parse HEAD:a) && + OLD_COMMIT=$(git rev-parse HEAD) && + OLD_TREE=$(git rev-parse HEAD^{tree}) && + + for i in $(test_seq 1 4) + do + cat >in <<-EOF && + 100755 blob $BLOB a + 040000 tree $OLD_TREE f1 + 040000 tree $OLD_TREE f2 + 040000 tree $OLD_TREE f3 + 040000 tree $OLD_TREE f4 + EOF + NEW_TREE=$(git mktree <in) && + NEW_COMMIT=$(git commit-tree $NEW_TREE -p $OLD_COMMIT -m "level $i") && + OLD_TREE=$NEW_TREE && + OLD_COMMIT=$NEW_COMMIT || return 1 + done && + + git sparse-checkout init --cone && + git branch -f wide $OLD_COMMIT && + git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v3 && + ( + cd full-index-v3 && + git sparse-checkout init --cone && + git sparse-checkout set $SPARSE_CONE && + git config index.version 3 && + git update-index --index-version=3 + ) && + git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-index-v4 && + ( + cd full-index-v4 && + git sparse-checkout init --cone && + git sparse-checkout set $SPARSE_CONE && + git config index.version 4 && + git update-index --index-version=4 + ) && + git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v3 && + ( + cd sparse-index-v3 && + git sparse-checkout init --cone --sparse-index && + git sparse-checkout set $SPARSE_CONE && + git config index.version 3 && + git update-index --index-version=3 + ) && + git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-index-v4 && + ( + cd sparse-index-v4 && + git sparse-checkout init --cone --sparse-index && + git sparse-checkout set $SPARSE_CONE && + git config index.version 4 && + git update-index --index-version=4 + ) +' + +test_perf_on_all () { + command="$@" + for repo in full-index-v3 full-index-v4 \ + sparse-index-v3 sparse-index-v4 + do + test_perf "$command ($repo)" " + ( + cd $repo && + echo >>$SPARSE_CONE/a && + $command + ) + " + done +} + +test_perf_on_all git status +test_perf_on_all git add -A +test_perf_on_all git add . +test_perf_on_all git commit -a -m A + +test_done diff --git a/t/perf/p3400-rebase.sh b/t/perf/p3400-rebase.sh index d202aaed06..7a0bb29448 100755 --- a/t/perf/p3400-rebase.sh +++ b/t/perf/p3400-rebase.sh @@ -9,16 +9,16 @@ test_expect_success 'setup rebasing on top of a lot of changes' ' git checkout -f -B base && git checkout -B to-rebase && git checkout -B upstream && - for i in $(seq 100) + for i in $(test_seq 100) do # simulate huge diffs echo change$i >unrelated-file$i && - seq 1000 >>unrelated-file$i && + test_seq 1000 >>unrelated-file$i && git add unrelated-file$i && test_tick && git commit -m commit$i unrelated-file$i && echo change$i >unrelated-file$i && - seq 1000 | tac >>unrelated-file$i && + test_seq 1000 | tac >>unrelated-file$i && git add unrelated-file$i && test_tick && git commit -m commit$i-reverse unrelated-file$i || diff --git a/t/perf/p4205-log-pretty-formats.sh b/t/perf/p4205-log-pretty-formats.sh index 7c26f4f337..609fecd65d 100755 --- a/t/perf/p4205-log-pretty-formats.sh +++ b/t/perf/p4205-log-pretty-formats.sh @@ -6,7 +6,7 @@ test_description='Tests the performance of various pretty format placeholders' test_perf_default_repo -for format in %H %h %T %t %P %p %h-%h-%h +for format in %H %h %T %t %P %p %h-%h-%h %an-%ae-%s do test_perf "log with $format" " git log --format=\"$format\" >/dev/null diff --git a/t/perf/p4209-pickaxe.sh b/t/perf/p4209-pickaxe.sh new file mode 100755 index 0000000000..f585a4465a --- /dev/null +++ b/t/perf/p4209-pickaxe.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description="Test pickaxe performance" + +. ./perf-lib.sh + +test_perf_default_repo + +# Not --max-count, as that's the number of matching commit, so it's +# unbounded. We want to limit our revision walk here. +from_rev_desc= +from_rev= +max_count=1000 +if test_have_prereq EXPENSIVE +then + max_count=10000 +fi +from_rev=" $(git rev-list HEAD | head -n $max_count | tail -n 1).." +from_rev_desc=" <limit-rev>.." + +for icase in \ + '' \ + '-i ' +do + # -S (no regex) + for pattern in \ + 'int main' \ + 'æ' + do + for opts in \ + '-S' + do + test_perf "git log $icase$opts'$pattern'$from_rev_desc" " + git log --pretty=format:%H $icase$opts'$pattern'$from_rev + " + done + done + + # -S (regex) + for pattern in \ + '(int|void|null)' \ + 'if *\([^ ]+ & ' \ + '[àáâãäåæñøùúûüýþ]' + do + for opts in \ + '--pickaxe-regex -S' + do + test_perf "git log $icase$opts'$pattern'$from_rev_desc" " + git log --pretty=format:%H $icase$opts'$pattern'$from_rev + " + done + done + + # -G + for pattern in \ + '(int|void|null)' \ + 'if *\([^ ]+ & ' \ + '[àáâãäåæñøùúûüýþ]' + do + for opts in \ + '-G' + do + test_perf "git log $icase$opts'$pattern'$from_rev_desc" " + git log --pretty=format:%H $icase$opts'$pattern'$from_rev + " + done + done +done + +test_done diff --git a/t/perf/p5302-pack-index.sh b/t/perf/p5302-pack-index.sh index 99bdb16c85..228593d9ad 100755 --- a/t/perf/p5302-pack-index.sh +++ b/t/perf/p5302-pack-index.sh @@ -13,35 +13,41 @@ test_expect_success 'repack' ' export PACK ' -test_expect_success 'create target repositories' ' - for repo in t1 t2 t3 t4 t5 t6 +# Rather than counting up and doubling each time, count down from the endpoint, +# halving each time. That ensures that our final test uses as many threads as +# CPUs, even if it isn't a power of 2. +test_expect_success 'set up thread-counting tests' ' + t=$(test-tool online-cpus) && + threads= && + while test $t -gt 0 do - git init --bare $repo + threads="$t $threads" + t=$((t / 2)) done ' -test_perf 'index-pack 0 threads' ' - GIT_DIR=t1 git index-pack --threads=1 --stdin < $PACK +test_perf PERF_EXTRA 'index-pack 0 threads' ' + rm -rf repo.git && + git init --bare repo.git && + GIT_DIR=repo.git git index-pack --threads=1 --stdin < $PACK ' -test_perf 'index-pack 1 thread ' ' - GIT_DIR=t2 GIT_FORCE_THREADS=1 git index-pack --threads=1 --stdin < $PACK -' - -test_perf 'index-pack 2 threads' ' - GIT_DIR=t3 git index-pack --threads=2 --stdin < $PACK -' - -test_perf 'index-pack 4 threads' ' - GIT_DIR=t4 git index-pack --threads=4 --stdin < $PACK -' - -test_perf 'index-pack 8 threads' ' - GIT_DIR=t5 git index-pack --threads=8 --stdin < $PACK -' +for t in $threads +do + THREADS=$t + export THREADS + test_perf PERF_EXTRA "index-pack $t threads" ' + rm -rf repo.git && + git init --bare repo.git && + GIT_DIR=repo.git GIT_FORCE_THREADS=1 \ + git index-pack --threads=$THREADS --stdin <$PACK + ' +done test_perf 'index-pack default number of threads' ' - GIT_DIR=t6 git index-pack --stdin < $PACK + rm -rf repo.git && + git init --bare repo.git && + GIT_DIR=repo.git git index-pack --stdin < $PACK ' test_done diff --git a/t/perf/p5303-many-packs.sh b/t/perf/p5303-many-packs.sh index 3779851941..35c0cbdf49 100755 --- a/t/perf/p5303-many-packs.sh +++ b/t/perf/p5303-many-packs.sh @@ -21,14 +21,25 @@ repack_into_n () { mkdir staging && git rev-list --first-parent HEAD | - sed -n '1~5p' | - head -n "$1" | - perl -e 'print reverse <>' \ - >pushes + perl -e ' + my $n = shift; + while (<>) { + last unless @commits < $n; + push @commits, $_ if $. % 5 == 1; + } + print reverse @commits; + ' "$1" >pushes && # create base packfile - head -n 1 pushes | - git pack-objects --delta-base-offset --revs staging/pack + base_pack=$( + head -n 1 pushes | + git pack-objects --delta-base-offset --revs staging/pack + ) && + test_export base_pack && + + # create an empty packfile + empty_pack=$(git pack-objects staging/pack </dev/null) && + test_export empty_pack && # and then incrementals between each pair of commits last= && @@ -45,6 +56,12 @@ repack_into_n () { last=$rev done <pushes && + ( + find staging -type f -name 'pack-*.pack' | + xargs -n 1 basename | grep -v "$base_pack" && + printf "^pack-%s.pack\n" $base_pack + ) >stdin.packs + # and install the whole thing rm -f .git/objects/pack/* && mv staging/* .git/objects/pack/ @@ -73,15 +90,55 @@ do git rev-list --objects --all >/dev/null ' + test_perf "abbrev-commit ($nr_packs)" ' + git rev-list --abbrev-commit HEAD >/dev/null + ' + # This simulates the interesting part of the repack, which is the # actual pack generation, without smudging the on-disk setup # between trials. test_perf "repack ($nr_packs)" ' + GIT_TEST_FULL_IN_PACK_ARRAY=1 \ + git pack-objects --keep-true-parents \ + --honor-pack-keep --non-empty --all \ + --reflog --indexed-objects --delta-base-offset \ + --stdout </dev/null >/dev/null + ' + + test_perf "repack with kept ($nr_packs)" ' git pack-objects --keep-true-parents \ + --keep-pack=pack-$empty_pack.pack \ --honor-pack-keep --non-empty --all \ --reflog --indexed-objects --delta-base-offset \ --stdout </dev/null >/dev/null ' + + test_perf "repack with --stdin-packs ($nr_packs)" ' + git pack-objects \ + --keep-true-parents \ + --stdin-packs \ + --non-empty \ + --delta-base-offset \ + --stdout <stdin.packs >/dev/null + ' done +# Measure pack loading with 10,000 packs. +test_expect_success 'generate lots of packs' ' + for i in $(test_seq 10000); do + echo "blob" + echo "data <<EOF" + echo "blob $i" + echo "EOF" + echo "checkpoint" + done | + git -c fastimport.unpackLimit=0 fast-import +' + +# The purpose of this test is to evaluate load time for a large number +# of packs while doing as little other work as possible. +test_perf "load 10,000 packs" ' + git rev-parse --verify "HEAD^{commit}" +' + test_done diff --git a/t/perf/p5304-prune.sh b/t/perf/p5304-prune.sh new file mode 100755 index 0000000000..83baedb8a4 --- /dev/null +++ b/t/perf/p5304-prune.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description='performance tests of prune' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'remove reachable loose objects' ' + git repack -ad +' + +test_expect_success 'remove unreachable loose objects' ' + git prune +' + +test_expect_success 'confirm there are no loose objects' ' + git count-objects | grep ^0 +' + +test_perf 'prune with no objects' ' + git prune +' + +test_expect_success 'repack with bitmaps' ' + git repack -adb +' + +# We have to create the object in each trial run, since otherwise +# runs after the first see no object and just skip the traversal entirely! +test_perf 'prune with bitmaps' ' + echo "probably not present in repo" | git hash-object -w --stdin && + git prune +' + +test_done diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh index bb91dbb173..452be01056 100755 --- a/t/perf/p5310-pack-bitmaps.sh +++ b/t/perf/p5310-pack-bitmaps.sh @@ -12,8 +12,13 @@ test_perf_large_repo # We intentionally use the deprecated pack.writebitmaps # config so that we can test against older versions of git. test_expect_success 'setup bitmap config' ' - git config pack.writebitmaps true && - git config pack.writebitmaphashcache true + git config pack.writebitmaps true +' + +# we need to create the tag up front such that it is covered by the repack and +# thus by generated bitmaps. +test_expect_success 'create tags' ' + git tag --message="tag pointing to HEAD" perf-tag HEAD ' test_perf 'repack to disk' ' @@ -32,14 +37,45 @@ test_perf 'simulated fetch' ' } | git pack-objects --revs --stdout >/dev/null ' -test_perf 'pack to file' ' - git pack-objects --all pack1 </dev/null >/dev/null -' - test_perf 'pack to file (bitmap)' ' git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null ' +test_perf 'rev-list (commits)' ' + git rev-list --all --use-bitmap-index >/dev/null +' + +test_perf 'rev-list (objects)' ' + git rev-list --all --use-bitmap-index --objects >/dev/null +' + +test_perf 'rev-list with tag negated via --not --all (objects)' ' + git rev-list perf-tag --not --all --use-bitmap-index --objects >/dev/null +' + +test_perf 'rev-list with negative tag (objects)' ' + git rev-list HEAD --not perf-tag --use-bitmap-index --objects >/dev/null +' + +test_perf 'rev-list count with blob:none' ' + git rev-list --use-bitmap-index --count --objects --all \ + --filter=blob:none >/dev/null +' + +test_perf 'rev-list count with blob:limit=1k' ' + git rev-list --use-bitmap-index --count --objects --all \ + --filter=blob:limit=1k >/dev/null +' + +test_perf 'rev-list count with tree:0' ' + git rev-list --use-bitmap-index --count --objects --all \ + --filter=tree:0 >/dev/null +' + +test_perf 'simulated partial clone' ' + git pack-objects --stdout --all --filter=blob:none </dev/null >/dev/null +' + test_expect_success 'create partial bitmap state' ' # pick a commit to represent the repo tip in the past cutoff=$(git rev-list HEAD~100 -1) && @@ -69,4 +105,9 @@ test_perf 'pack to file (partial bitmap)' ' git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null ' +test_perf 'rev-list with tree filter (partial bitmap)' ' + git rev-list --use-bitmap-index --count --objects --all \ + --filter=tree:0 >/dev/null +' + test_done diff --git a/t/perf/p5311-pack-bitmaps-fetch.sh b/t/perf/p5311-pack-bitmaps-fetch.sh index b04575951f..47c3fd7581 100755 --- a/t/perf/p5311-pack-bitmaps-fetch.sh +++ b/t/perf/p5311-pack-bitmaps-fetch.sh @@ -7,7 +7,6 @@ test_perf_default_repo test_expect_success 'create bitmapped server repo' ' git config pack.writebitmaps true && - git config pack.writebitmaphashcache true && git repack -ad ' diff --git a/t/perf/p5600-partial-clone.sh b/t/perf/p5600-partial-clone.sh new file mode 100755 index 0000000000..a965f2c4d6 --- /dev/null +++ b/t/perf/p5600-partial-clone.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description='performance of partial clones' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'enable server-side config' ' + git config uploadpack.allowFilter true && + git config uploadpack.allowAnySHA1InWant true +' + +test_perf 'clone without blobs' ' + rm -rf bare.git && + git clone --no-local --bare --filter=blob:none . bare.git +' + +test_perf 'checkout of result' ' + rm -rf worktree && + mkdir -p worktree/.git && + tar -C bare.git -cf - . | tar -C worktree/.git -xf - && + git -C worktree config core.bare false && + git -C worktree checkout -f +' + +test_perf 'fsck' ' + git -C bare.git fsck +' + +test_perf 'count commits' ' + git -C bare.git rev-list --all --count +' + +test_perf 'count non-promisor commits' ' + git -C bare.git rev-list --all --count --exclude-promisor-objects +' + +test_perf 'gc' ' + git -C bare.git gc +' + +test_done diff --git a/t/perf/p5601-clone-reference.sh b/t/perf/p5601-clone-reference.sh new file mode 100755 index 0000000000..68fed66347 --- /dev/null +++ b/t/perf/p5601-clone-reference.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +test_description='speed of clone --reference' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'create shareable repository' ' + git clone --bare . shared.git +' + +test_expect_success 'advance base repository' ' + # Do not use test_commit here; its test_tick will + # use some ancient hard-coded date. The resulting clock + # skew will cause pack-objects to traverse in a very + # sub-optimal order, skewing the results. + echo content >new-file-that-does-not-exist && + git add new-file-that-does-not-exist && + git commit -m "new commit" +' + +test_perf 'clone --reference' ' + rm -rf dst.git && + git clone --no-local --bare --reference shared.git . dst.git +' + +test_done diff --git a/t/perf/p7519-fsmonitor.sh b/t/perf/p7519-fsmonitor.sh index def7ecdbc7..5eb5044a10 100755 --- a/t/perf/p7519-fsmonitor.sh +++ b/t/perf/p7519-fsmonitor.sh @@ -22,7 +22,9 @@ test_description="Test core.fsmonitor" # # GIT_PERF_7519_UNTRACKED_CACHE: used to configure core.untrackedCache # GIT_PERF_7519_SPLIT_INDEX: used to configure core.splitIndex -# GIT_PERF_7519_FSMONITOR: used to configure core.fsMonitor +# GIT_PERF_7519_FSMONITOR: used to configure core.fsMonitor. May be an +# absolute path to an integration. May be a space delimited list of +# absolute paths to integrations. # # The big win for using fsmonitor is the elimination of the need to scan the # working directory looking for changed and untracked files. If the file @@ -30,6 +32,8 @@ test_description="Test core.fsmonitor" # # GIT_PERF_7519_DROP_CACHE: if set, the OS caches are dropped between tests # +# GIT_PERF_7519_TRACE: if set, enable trace logging during the test. +# Trace logs will be grouped by fsmonitor provider. test_perf_large_repo test_checkout_worktree @@ -68,7 +72,33 @@ then fi fi -test_expect_success "setup for fsmonitor" ' +trace_start() { + if test -n "$GIT_PERF_7519_TRACE" + then + name="$1" + TEST_TRACE_DIR="$TEST_OUTPUT_DIRECTORY/test-trace/p7519/" + echo "Writing trace logging to $TEST_TRACE_DIR" + + mkdir -p "$TEST_TRACE_DIR" + + # Start Trace2 logging and any other GIT_TRACE_* logs that you + # want for this named test case. + + GIT_TRACE2_PERF="$TEST_TRACE_DIR/$name.trace2perf" + export GIT_TRACE2_PERF + + >"$GIT_TRACE2_PERF" + fi +} + +trace_stop() { + if test -n "$GIT_PERF_7519_TRACE" + then + unset GIT_TRACE2_PERF + fi +} + +test_expect_success "one time repo setup" ' # set untrackedCache depending on the environment if test -n "$GIT_PERF_7519_UNTRACKED_CACHE" then @@ -88,24 +118,36 @@ test_expect_success "setup for fsmonitor" ' git config core.splitIndex "$GIT_PERF_7519_SPLIT_INDEX" fi && + mkdir 1_file 10_files 100_files 1000_files 10000_files && + for i in $(test_seq 1 10); do touch 10_files/$i; done && + for i in $(test_seq 1 100); do touch 100_files/$i; done && + for i in $(test_seq 1 1000); do touch 1000_files/$i; done && + for i in $(test_seq 1 10000); do touch 10000_files/$i; done && + git add 1_file 10_files 100_files 1000_files 10000_files && + git commit -qm "Add files" && + + # If Watchman exists, watch the work tree and attempt a query. + if test_have_prereq WATCHMAN; then + watchman watch "$GIT_WORK_TREE" && + watchman watch-list | grep -q -F "p7519-fsmonitor" + fi +' + +setup_for_fsmonitor() { # set INTEGRATION_SCRIPT depending on the environment - if test -n "$GIT_PERF_7519_FSMONITOR" + if test -n "$INTEGRATION_PATH" then - INTEGRATION_SCRIPT="$GIT_PERF_7519_FSMONITOR" + INTEGRATION_SCRIPT="$INTEGRATION_PATH" else # # Choose integration script based on existence of Watchman. - # If Watchman exists, watch the work tree and attempt a query. - # If everything succeeds, use Watchman integration script, - # else fall back to an empty integration script. + # Fall back to an empty integration script. # mkdir .git/hooks && if test_have_prereq WATCHMAN then INTEGRATION_SCRIPT=".git/hooks/fsmonitor-watchman" && - cp "$TEST_DIRECTORY/../templates/hooks--fsmonitor-watchman.sample" "$INTEGRATION_SCRIPT" && - watchman watch "$GIT_WORK_TREE" && - watchman watch-list | grep -q -F "$GIT_WORK_TREE" + cp "$TEST_DIRECTORY/../templates/hooks--fsmonitor-watchman.sample" "$INTEGRATION_SCRIPT" else INTEGRATION_SCRIPT=".git/hooks/fsmonitor-empty" && write_script "$INTEGRATION_SCRIPT"<<-\EOF @@ -114,62 +156,110 @@ test_expect_success "setup for fsmonitor" ' fi && git config core.fsmonitor "$INTEGRATION_SCRIPT" && - git update-index --fsmonitor -' + git update-index --fsmonitor 2>error && + if test_have_prereq WATCHMAN + then + test_must_be_empty error # ensure no silent error + else + grep "Empty last update token" error + fi +} -if test -n "$GIT_PERF_7519_DROP_CACHE"; then - test-tool drop-caches -fi +test_perf_w_drop_caches () { + if test -n "$GIT_PERF_7519_DROP_CACHE"; then + test-tool drop-caches + fi -test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" ' - git status -' + test_perf "$@" +} -if test -n "$GIT_PERF_7519_DROP_CACHE"; then - test-tool drop-caches -fi +test_fsmonitor_suite() { + if test -n "$INTEGRATION_SCRIPT"; then + DESC="fsmonitor=$(basename $INTEGRATION_SCRIPT)" + else + DESC="fsmonitor=disabled" + fi -test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" ' - git status -uno -' + test_expect_success "test_initialization" ' + git reset --hard && + git status # Warm caches + ' -if test -n "$GIT_PERF_7519_DROP_CACHE"; then - test-tool drop-caches -fi + test_perf_w_drop_caches "status ($DESC)" ' + git status + ' -test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" ' - git status -uall -' + test_perf_w_drop_caches "status -uno ($DESC)" ' + git status -uno + ' -test_expect_success "setup without fsmonitor" ' - unset INTEGRATION_SCRIPT && - git config --unset core.fsmonitor && - git update-index --no-fsmonitor -' + test_perf_w_drop_caches "status -uall ($DESC)" ' + git status -uall + ' -if test -n "$GIT_PERF_7519_DROP_CACHE"; then - test-tool drop-caches -fi + # Update the mtimes on upto 100k files to make status think + # that they are dirty. For simplicity, omit any files with + # LFs (i.e. anything that ls-files thinks it needs to dquote). + # Then fully backslash-quote the paths to capture any + # whitespace so that they pass thru xargs properly. + # + test_perf_w_drop_caches "status (dirty) ($DESC)" ' + git ls-files | \ + head -100000 | \ + grep -v \" | \ + sed '\''s/\(.\)/\\\1/g'\'' | \ + xargs test-tool chmtime -300 && + git status + ' -test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" ' - git status -' + test_perf_w_drop_caches "diff ($DESC)" ' + git diff + ' -if test -n "$GIT_PERF_7519_DROP_CACHE"; then - test-tool drop-caches -fi + test_perf_w_drop_caches "diff HEAD ($DESC)" ' + git diff HEAD + ' -test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" ' - git status -uno -' + test_perf_w_drop_caches "diff -- 0_files ($DESC)" ' + git diff -- 1_file + ' -if test -n "$GIT_PERF_7519_DROP_CACHE"; then - test-tool drop-caches -fi + test_perf_w_drop_caches "diff -- 10_files ($DESC)" ' + git diff -- 10_files + ' -test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" ' - git status -uall -' + test_perf_w_drop_caches "diff -- 100_files ($DESC)" ' + git diff -- 100_files + ' + + test_perf_w_drop_caches "diff -- 1000_files ($DESC)" ' + git diff -- 1000_files + ' + + test_perf_w_drop_caches "diff -- 10000_files ($DESC)" ' + git diff -- 10000_files + ' + + test_perf_w_drop_caches "add ($DESC)" ' + git add --all + ' +} + +# +# Run a full set of perf tests using each Hook-based fsmonitor provider, +# such as Watchman. +# + +trace_start fsmonitor-watchman +if test -n "$GIT_PERF_7519_FSMONITOR"; then + for INTEGRATION_PATH in $GIT_PERF_7519_FSMONITOR; do + test_expect_success "setup for fsmonitor $INTEGRATION_PATH" 'setup_for_fsmonitor' + test_fsmonitor_suite + done +else + test_expect_success "setup for fsmonitor" 'setup_for_fsmonitor' + test_fsmonitor_suite +fi if test_have_prereq WATCHMAN then @@ -179,5 +269,20 @@ then # preventing the removal of the trash directory watchman shutdown-server >/dev/null 2>&1 fi +trace_stop + +# +# Run a full set of perf tests with the fsmonitor feature disabled. +# + +trace_start fsmonitor-disabled +test_expect_success "setup without fsmonitor" ' + unset INTEGRATION_SCRIPT && + git config --unset core.fsmonitor && + git update-index --no-fsmonitor +' + +test_fsmonitor_suite +trace_stop test_done diff --git a/t/perf/p9300-fast-import-export.sh b/t/perf/p9300-fast-import-export.sh new file mode 100755 index 0000000000..586161e9ad --- /dev/null +++ b/t/perf/p9300-fast-import-export.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test_description='test fast-import and fast-export performance' +. ./perf-lib.sh + +test_perf_default_repo + +# Use --no-data here to produce a vastly smaller export file. +# This is much cheaper to work with but should still exercise +# fast-import pretty well (we'll still process all commits and +# trees, which account for 60% or more of objects in most repos). +# +# Use --reencode to avoid the default of aborting on non-utf8 commits, +# which lets this test run against a wider variety of sample repos. +test_perf 'export (no-blobs)' ' + git fast-export --reencode=yes --no-data HEAD >export +' + +test_perf 'import (no-blobs)' ' + git fast-import --force <export +' + +test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index 2e33ab3ec3..601d9f67dd 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -17,37 +17,27 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/ . -# do the --tee work early; it otherwise confuses our careful -# GIT_BUILD_DIR mangling -case "$GIT_TEST_TEE_STARTED, $* " in -done,*) - # do not redirect again - ;; -*' --tee '*|*' --va'*) - mkdir -p test-results - BASE=test-results/$(basename "$0" .sh) - (GIT_TEST_TEE_STARTED=done ${SHELL-sh} "$0" "$@" 2>&1; - echo $? > $BASE.exit) | tee $BASE.out - test "$(cat $BASE.exit)" = 0 - exit - ;; -esac - +# These variables must be set before the inclusion of test-lib.sh below, +# because it will change our working directory. TEST_DIRECTORY=$(pwd)/.. TEST_OUTPUT_DIRECTORY=$(pwd) -if test -z "$GIT_TEST_INSTALLED"; then - perf_results_prefix= -else - perf_results_prefix=$(printf "%s" "${GIT_TEST_INSTALLED%/bin-wrappers}" | tr -c "[a-zA-Z0-9]" "[_*]")"." - # make the tested dir absolute - GIT_TEST_INSTALLED=$(cd "$GIT_TEST_INSTALLED" && pwd) -fi TEST_NO_CREATE_REPO=t TEST_NO_MALLOC_CHECK=t . ../test-lib.sh +if test -n "$GIT_TEST_INSTALLED" -a -z "$PERF_SET_GIT_TEST_INSTALLED" +then + error "Do not use GIT_TEST_INSTALLED with the perf tests. + +Instead use: + + ./run <path-to-git> -- <tests> + +See t/perf/README for details." +fi + # Variables from test-lib that are normally internal to the tests; we # need to export them for test_perf subshells export TEST_DIRECTORY TRASH_DIRECTORY GIT_BUILD_DIR GIT_TEST_CMP @@ -80,6 +70,19 @@ test_perf_do_repo_symlink_config_ () { test_have_prereq SYMLINKS || git config core.symlinks false } +test_perf_copy_repo_contents () { + for stuff in "$1"/* + do + case "$stuff" in + */objects|*/hooks|*/config|*/commondir|*/gitdir|*/worktrees) + ;; + *) + cp -R "$stuff" "$repo/.git/" || exit 1 + ;; + esac + done +} + test_perf_create_repo_from () { test "$#" = 2 || BUG "not 2 parameters to test-create-repo" @@ -87,20 +90,20 @@ test_perf_create_repo_from () { source="$2" source_git="$("$MODERN_GIT" -C "$source" rev-parse --git-dir)" objects_dir="$("$MODERN_GIT" -C "$source" rev-parse --git-path objects)" + common_dir="$("$MODERN_GIT" -C "$source" rev-parse --git-common-dir)" mkdir -p "$repo/.git" ( cd "$source" && { cp -Rl "$objects_dir" "$repo/.git/" 2>/dev/null || cp -R "$objects_dir" "$repo/.git/"; } && - for stuff in "$source_git"/*; do - case "$stuff" in - */objects|*/hooks|*/config|*/commondir) - ;; - *) - cp -R "$stuff" "$repo/.git/" || exit 1 - ;; - esac - done + + # common_dir must come first here, since we want source_git to + # take precedence and overwrite any overlapping files + test_perf_copy_repo_contents "$common_dir" + if test "$source_git" != "$common_dir" + then + test_perf_copy_repo_contents "$source_git" + fi ) && ( cd "$repo" && @@ -157,14 +160,16 @@ test_run_perf_ () { "$GTIME" -f "%E %U %S" -o test_time.$i "$SHELL" -c ' . '"$TEST_DIRECTORY"/test-lib-functions.sh' test_export () { - [ $# != 0 ] || return 0 - test_export_="$test_export_\\|$1" - shift - test_export "$@" + test_export_="$test_export_ $*" } '"$1"' ret=$? -set | sed -n "s'"/'/'\\\\''/g"';s/^\\($test_export_\\)/export '"'&'"'/p" >test_vars +needles= +for v in $test_export_ +do + needles="$needles;s/^$v=/export $v=/p" +done +set | sed -n "s'"/'/'\\\\''/g"'$needles" >test_vars exit $ret' >&3 2>&4 eval_ret=$? @@ -191,7 +196,7 @@ test_wrapper_ () { base=$(basename "$0" .sh) echo "$test_count" >>"$perf_results_dir"/$base.subtests echo "$1" >"$perf_results_dir"/$base.$test_count.descr - base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count" + base="$perf_results_dir"/"$PERF_RESULTS_PREFIX$(basename "$0" .sh)"."$test_count" "$test_wrapper_func_" "$@" fi @@ -224,7 +229,7 @@ test_perf_ () { else test_ok_ "$1" fi - "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times + "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".result } test_perf () { @@ -233,7 +238,7 @@ test_perf () { test_size_ () { say >&3 "running: $2" - if test_eval_ "$2" 3>"$base".size; then + if test_eval_ "$2" 3>"$base".result; then test_ok_ "$1" else test_failure_ "$@" @@ -255,3 +260,5 @@ test_at_end_hook_ () { test_export () { export "$@" } + +test_lazy_prereq PERF_EXTRA 'test_bool_env GIT_PERF_EXTRA false' diff --git a/t/perf/run b/t/perf/run index 9aaa733c77..c7b86104e1 100755 --- a/t/perf/run +++ b/t/perf/run @@ -70,6 +70,24 @@ build_git_rev () { ) || die "failed to build revision '$mydir'" } +set_git_test_installed () { + mydir=$1 + + mydir_abs=$(cd $mydir && pwd) + mydir_abs_wrappers="$mydir_abs_wrappers/bin-wrappers" + if test -d "$mydir_abs_wrappers" + then + GIT_TEST_INSTALLED=$mydir_abs_wrappers + else + # Older versions of git lacked bin-wrappers; + # fallback to the files in the root. + GIT_TEST_INSTALLED=$mydir_abs + fi + export GIT_TEST_INSTALLED + PERF_SET_GIT_TEST_INSTALLED=true + export PERF_SET_GIT_TEST_INSTALLED +} + run_dirs_helper () { mydir=${1%/} shift @@ -79,7 +97,16 @@ run_dirs_helper () { if test $# -gt 0 -a "$1" = --; then shift fi - if [ ! -d "$mydir" ]; then + + PERF_RESULTS_PREFIX= + if test "$mydir" = "." + then + unset GIT_TEST_INSTALLED + elif test -d "$mydir" + then + PERF_RESULTS_PREFIX=bindir$(cd $mydir && printf "%s" "$(pwd)" | tr -c "[a-zA-Z0-9]" "_"). + set_git_test_installed "$mydir" + else rev=$(git rev-parse --verify "$mydir" 2>/dev/null) || die "'$mydir' is neither a directory nor a valid revision" if [ ! -d build/$rev ]; then @@ -87,16 +114,12 @@ run_dirs_helper () { fi build_git_rev $rev "$mydir" mydir=build/$rev + + PERF_RESULTS_PREFIX=build_$rev. + set_git_test_installed "$mydir" fi - if test "$mydir" = .; then - unset GIT_TEST_INSTALLED - else - GIT_TEST_INSTALLED="$mydir/bin-wrappers" - # Older versions of git lacked bin-wrappers; fallback to the - # files in the root. - test -d "$GIT_TEST_INSTALLED" || GIT_TEST_INSTALLED=$mydir - export GIT_TEST_INSTALLED - fi + export PERF_RESULTS_PREFIX + run_one_dir "$@" } |