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 | 11 | ||||
-rwxr-xr-x | t/perf/aggregate.perl | 26 | ||||
-rwxr-xr-x | t/perf/bisect_regression | 2 | ||||
-rwxr-xr-x | t/perf/p1400-update-ref.sh | 33 | ||||
-rwxr-xr-x | t/perf/p2000-sparse-operations.sh | 114 | ||||
-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 | 47 | ||||
-rwxr-xr-x | t/perf/p5303-many-packs.sh | 69 | ||||
-rwxr-xr-x | t/perf/p5310-pack-bitmaps.sh | 50 | ||||
-rwxr-xr-x | t/perf/p5600-partial-clone.sh | 16 | ||||
-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 | 56 | ||||
-rwxr-xr-x | t/perf/run | 25 |
18 files changed, 637 insertions, 134 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 c7b70e2d28..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: @@ -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 66554d2161..82c0df4553 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -4,7 +4,6 @@ use lib '../../perl/build/lib'; use strict; use warnings; use Getopt::Long; -use Git; use Cwd qw(realpath); sub get_times { @@ -59,6 +58,7 @@ sub usage { Options: --codespeed * Format output for Codespeed --reponame <str> * Send given reponame to codespeed + --results-dir <str> * Directory where test results are located --sort-by <str> * Sort output (only "regression" criteria is supported) --subsection <str> * Use results from given subsection @@ -85,13 +85,20 @@ sub format_size { return $out; } +sub sane_backticks { + open(my $fh, '-|', @_); + return <$fh>; +} + my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests, $codespeed, $sortby, $subsection, $reponame); +my $resultsdir = "test-results"; Getopt::Long::Configure qw/ require_order /; my $rc = GetOptions("codespeed" => \$codespeed, "reponame=s" => \$reponame, + "results-dir=s" => \$resultsdir, "sort-by=s" => \$sortby, "subsection=s" => \$subsection); usage() unless $rc; @@ -102,7 +109,8 @@ while (scalar @ARGV) { 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 = '.'; @@ -132,8 +140,6 @@ if (not @tests) { @tests = glob "p????-*.sh"; } -my $resultsdir = "test-results"; - if (! $subsection and exists $ENV{GIT_PERF_SUBSECTION} and $ENV{GIT_PERF_SUBSECTION} ne "") { @@ -219,13 +225,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]; @@ -267,7 +267,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, @@ -327,7 +327,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, 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/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..597626276f --- /dev/null +++ b/t/perf/p2000-sparse-operations.sh @@ -0,0 +1,114 @@ +#!/bin/sh + +test_description="test performance of Git operations using the index" + +. ./perf-lib.sh + +test_perf_default_repo + +SPARSE_CONE=f2/f4 + +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 3) + 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 sparse-checkout set $SPARSE_CONE && + git checkout -b wide $OLD_COMMIT && + + for l2 in f1 f2 f3 f4 + do + echo more bogus >>$SPARSE_CONE/$l2/a && + git commit -a -m "edit $SPARSE_CONE/$l2/a" || return 1 + done && + + git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-v3 && + ( + cd full-v3 && + git sparse-checkout init --cone && + git sparse-checkout set $SPARSE_CONE && + git config index.version 3 && + git update-index --index-version=3 && + git checkout HEAD~4 + ) && + git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . full-v4 && + ( + cd full-v4 && + git sparse-checkout init --cone && + git sparse-checkout set $SPARSE_CONE && + git config index.version 4 && + git update-index --index-version=4 && + git checkout HEAD~4 + ) && + git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-v3 && + ( + cd sparse-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 checkout HEAD~4 + ) && + git -c core.sparseCheckoutCone=true clone --branch=wide --sparse . sparse-v4 && + ( + cd sparse-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 && + git checkout HEAD~4 + ) +' + +test_perf_on_all () { + command="$@" + for repo in full-v3 full-v4 \ + sparse-v3 sparse-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_perf_on_all git checkout -f - + +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 a9b3e112d9..228593d9ad 100755 --- a/t/perf/p5302-pack-index.sh +++ b/t/perf/p5302-pack-index.sh @@ -13,35 +13,36 @@ test_expect_success 'repack' ' export PACK ' -test_perf '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 ' ' - rm -rf repo.git && - git init --bare repo.git && - GIT_DIR=repo.git GIT_FORCE_THREADS=1 git index-pack --threads=1 --stdin < $PACK +# 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 + threads="$t $threads" + t=$((t / 2)) + done ' -test_perf 'index-pack 2 threads' ' +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=2 --stdin < $PACK -' - -test_perf 'index-pack 4 threads' ' - rm -rf repo.git && - git init --bare repo.git && - GIT_DIR=repo.git git index-pack --threads=4 --stdin < $PACK + GIT_DIR=repo.git git index-pack --threads=1 --stdin < $PACK ' -test_perf 'index-pack 8 threads' ' - rm -rf repo.git && - git init --bare repo.git && - GIT_DIR=repo.git 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' ' rm -rf repo.git && 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/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh index 6a3a42531b..452be01056 100755 --- a/t/perf/p5310-pack-bitmaps.sh +++ b/t/perf/p5310-pack-bitmaps.sh @@ -15,6 +15,12 @@ test_expect_success 'setup bitmap config' ' 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' ' git repack -ad ' @@ -31,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) && @@ -68,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/p5600-partial-clone.sh b/t/perf/p5600-partial-clone.sh index 3e04bd2ae1..a965f2c4d6 100755 --- a/t/perf/p5600-partial-clone.sh +++ b/t/perf/p5600-partial-clone.sh @@ -23,4 +23,20 @@ test_perf 'checkout of result' ' 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/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 b58a43ea43..f5ed092ee5 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -45,7 +45,7 @@ export TEST_DIRECTORY TRASH_DIRECTORY GIT_BUILD_DIR GIT_TEST_CMP MODERN_GIT=$GIT_BUILD_DIR/bin-wrappers/git export MODERN_GIT -perf_results_dir=$TEST_OUTPUT_DIRECTORY/test-results +perf_results_dir=$TEST_RESULTS_DIR test -n "$GIT_PERF_SUBSECTION" && perf_results_dir="$perf_results_dir/$GIT_PERF_SUBSECTION" mkdir -p "$perf_results_dir" rm -f "$perf_results_dir"/$(basename "$0" .sh).subtests @@ -70,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" @@ -77,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" && @@ -147,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=$? @@ -214,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 () { @@ -223,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_ "$@" @@ -238,10 +253,15 @@ test_size () { # and does it after running everything) test_at_end_hook_ () { if test -z "$GIT_PERF_AGGREGATING_LATER"; then - ( cd "$TEST_DIRECTORY"/perf && ./aggregate.perl $(basename "$0") ) + ( + cd "$TEST_DIRECTORY"/perf && + ./aggregate.perl --results-dir="$TEST_RESULTS_DIR" $(basename "$0") + ) fi } 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 c7b86104e1..d19dec258a 100755 --- a/t/perf/run +++ b/t/perf/run @@ -188,10 +188,10 @@ run_subsection () { if test -z "$GIT_PERF_SEND_TO_CODESPEED" then - ./aggregate.perl $codespeed_opt "$@" + ./aggregate.perl --results-dir="$TEST_RESULTS_DIR" $codespeed_opt "$@" else - json_res_file="test-results/$GIT_PERF_SUBSECTION/aggregate.json" - ./aggregate.perl --codespeed "$@" | tee "$json_res_file" + json_res_file=""$TEST_RESULTS_DIR"/$GIT_PERF_SUBSECTION/aggregate.json" + ./aggregate.perl --results-dir="$TEST_RESULTS_DIR" --codespeed "$@" | tee "$json_res_file" send_data_url="$GIT_PERF_SEND_TO_CODESPEED/result/add/json/" curl -v --request POST --data-urlencode "json=$(cat "$json_res_file")" "$send_data_url" fi @@ -203,10 +203,17 @@ get_var_from_env_or_config "GIT_PERF_SEND_TO_CODESPEED" "perf" "sendToCodespeed" cd "$(dirname $0)" . ../../GIT-BUILD-OPTIONS -mkdir -p test-results -get_subsections "perf" >test-results/run_subsections.names +if test -n "$TEST_OUTPUT_DIRECTORY" +then + TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" +else + TEST_RESULTS_DIR=test-results +fi + +mkdir -p "$TEST_RESULTS_DIR" +get_subsections "perf" >"$TEST_RESULTS_DIR"/run_subsections.names -if test $(wc -l <test-results/run_subsections.names) -eq 0 +if test $(wc -l <"$TEST_RESULTS_DIR"/run_subsections.names) -eq 0 then if test -n "$GIT_PERF_SUBSECTION" then @@ -222,10 +229,10 @@ then ) elif test -n "$GIT_PERF_SUBSECTION" then - egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names >/dev/null || + egrep "^$GIT_PERF_SUBSECTION\$" "$TEST_RESULTS_DIR"/run_subsections.names >/dev/null || die "subsection '$GIT_PERF_SUBSECTION' not found in '$GIT_PERF_CONFIG_FILE'" - egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names | while read -r subsec + egrep "^$GIT_PERF_SUBSECTION\$" "$TEST_RESULTS_DIR"/run_subsections.names | while read -r subsec do ( GIT_PERF_SUBSECTION="$subsec" @@ -243,5 +250,5 @@ else echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========" run_subsection "$@" ) - done <test-results/run_subsections.names + done <"$TEST_RESULTS_DIR"/run_subsections.names fi |