View Javadoc
1   /*
2    * Copyright (C) 2021, Luca Milanesio <luca.milanesio@gmail.com> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.benchmarks;
11  
12  import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE;
13  
14  import java.io.IOException;
15  import java.nio.file.Files;
16  import java.nio.file.Path;
17  import java.nio.file.Paths;
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.concurrent.ThreadLocalRandom;
21  import java.util.concurrent.TimeUnit;
22  import java.util.stream.Collectors;
23  import java.util.stream.IntStream;
24  
25  import org.eclipse.jgit.api.Git;
26  import org.eclipse.jgit.api.errors.GitAPIException;
27  import org.eclipse.jgit.internal.storage.file.FileRepository;
28  import org.eclipse.jgit.lib.BatchRefUpdate;
29  import org.eclipse.jgit.lib.ConfigConstants;
30  import org.eclipse.jgit.lib.Constants;
31  import org.eclipse.jgit.lib.ObjectId;
32  import org.eclipse.jgit.lib.Repository;
33  import org.eclipse.jgit.lib.RepositoryCache;
34  import org.eclipse.jgit.lib.StoredConfig;
35  import org.eclipse.jgit.lib.TextProgressMonitor;
36  import org.eclipse.jgit.revwalk.RevCommit;
37  import org.eclipse.jgit.revwalk.RevWalk;
38  import org.eclipse.jgit.transport.ReceiveCommand;
39  import org.eclipse.jgit.util.FS;
40  import org.eclipse.jgit.util.FileUtils;
41  import org.openjdk.jmh.annotations.Benchmark;
42  import org.openjdk.jmh.annotations.BenchmarkMode;
43  import org.openjdk.jmh.annotations.Measurement;
44  import org.openjdk.jmh.annotations.Mode;
45  import org.openjdk.jmh.annotations.OutputTimeUnit;
46  import org.openjdk.jmh.annotations.Param;
47  import org.openjdk.jmh.annotations.Scope;
48  import org.openjdk.jmh.annotations.Setup;
49  import org.openjdk.jmh.annotations.State;
50  import org.openjdk.jmh.annotations.TearDown;
51  import org.openjdk.jmh.annotations.Warmup;
52  import org.openjdk.jmh.infra.Blackhole;
53  import org.openjdk.jmh.runner.Runner;
54  import org.openjdk.jmh.runner.RunnerException;
55  import org.openjdk.jmh.runner.options.Options;
56  import org.openjdk.jmh.runner.options.OptionsBuilder;
57  
58  @State(Scope.Thread)
59  public class GetRefsBenchmark {
60  
61  	ThreadLocalRandom branchIndex = ThreadLocalRandom.current();
62  
63  	@State(Scope.Benchmark)
64  	public static class BenchmarkState {
65  
66  		@Param({ "true", "false" })
67  		boolean useRefTable;
68  
69  		@Param({ "100", "2500", "10000", "50000" })
70  		int numBranches;
71  
72  		@Param({ "true", "false" })
73  		boolean trustFolderStat;
74  
75  		List<String> branches = new ArrayList<>(numBranches);
76  
77  		Path testDir;
78  
79  		Repository repo;
80  
81  		@Setup
82  		@SuppressWarnings("boxing")
83  		public void setupBenchmark() throws IOException, GitAPIException {
84  			String firstBranch = "firstbranch";
85  			testDir = Files.createDirectory(Paths.get("testrepos"));
86  			String repoName = "branches-" + numBranches + "-trustFolderStat-"
87  					+ trustFolderStat + "-" + refDatabaseType();
88  			Path workDir = testDir.resolve(repoName);
89  			Path repoPath = workDir.resolve(".git");
90  			Git git = Git.init().setDirectory(workDir.toFile()).call();
91  			RevCommit firstCommit = git.commit().setMessage("First commit")
92  					.call();
93  			git.branchCreate().setName(firstBranch).call();
94  
95  			StoredConfig cfg = git.getRepository().getConfig();
96  			if (useRefTable) {
97  				((FileRepository) git.getRepository()).convertRefStorage(
98  						ConfigConstants.CONFIG_REF_STORAGE_REFTABLE, false,
99  						false);
100 			} else {
101 				cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
102 						ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT,
103 						trustFolderStat);
104 			}
105 			cfg.setInt(ConfigConstants.CONFIG_RECEIVE_SECTION, null,
106 					"maxCommandBytes", Integer.MAX_VALUE);
107 			cfg.save();
108 
109 			repo = RepositoryCache.open(RepositoryCache.FileKey
110 					.lenient(repoPath.toFile(), FS.DETECTED));
111 
112 			System.out.println("Preparing test");
113 			System.out.println("- repository: \t\t" + repoPath);
114 			System.out.println("- refDatabase: \t\t" + refDatabaseType());
115 			System.out.println("- trustFolderStat: \t" + trustFolderStat);
116 			System.out.println("- branches: \t\t" + numBranches);
117 
118 			BatchRefUpdate u = repo.getRefDatabase().newBatchUpdate();
119 
120 			branches = IntStream.range(0, numBranches)
121 					.mapToObj(i -> "branch/" + i % 100 + "/" + i)
122 					.collect(Collectors.toList());
123 			for (String branch : branches) {
124 				u.addCommand(new ReceiveCommand(ObjectId.zeroId(),
125 						firstCommit.toObjectId(), Constants.R_HEADS + branch,
126 						CREATE));
127 			}
128 
129 			System.out.println();
130 			System.out.print(
131 					String.format("Creating %d branches ... ", numBranches));
132 
133 			try (RevWalk rw = new RevWalk(repo)) {
134 				u.execute(rw, new TextProgressMonitor());
135 			}
136 			System.out.println("DONE");
137 		}
138 
139 		private String refDatabaseType() {
140 			return useRefTable ? "reftable" : "refdir";
141 		}
142 
143 		@TearDown
144 		public void teardown() throws IOException {
145 			repo.close();
146 			FileUtils.delete(testDir.toFile(),
147 					FileUtils.RECURSIVE | FileUtils.RETRY);
148 		}
149 	}
150 
151 	@Benchmark
152 	@BenchmarkMode({ Mode.AverageTime })
153 	@OutputTimeUnit(TimeUnit.MICROSECONDS)
154 	@Warmup(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
155 	@Measurement(iterations = 2, time = 10, timeUnit = TimeUnit.SECONDS)
156 	public void testGetExactRef(Blackhole blackhole, BenchmarkState state)
157 			throws IOException {
158 		String branchName = state.branches
159 				.get(branchIndex.nextInt(state.numBranches));
160 		blackhole.consume(state.repo.exactRef(branchName));
161 	}
162 
163 	@Benchmark
164 	@BenchmarkMode({ Mode.AverageTime })
165 	@OutputTimeUnit(TimeUnit.MICROSECONDS)
166 	@Warmup(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
167 	@Measurement(iterations = 2, time = 10, timeUnit = TimeUnit.SECONDS)
168 	public void testGetRefsByPrefix(Blackhole blackhole, BenchmarkState state)
169 			throws IOException {
170 		String branchPrefix = "refs/heads/branch/" + branchIndex.nextInt(100)
171 				+ "/";
172 		blackhole.consume(
173 				state.repo.getRefDatabase().getRefsByPrefix(branchPrefix));
174 	}
175 
176 	public static void main(String[] args) throws RunnerException {
177 		Options opt = new OptionsBuilder()
178 				.include(GetRefsBenchmark.class.getSimpleName())
179 				// .addProfiler(StackProfiler.class)
180 				// .addProfiler(GCProfiler.class)
181 				.forks(1).jvmArgs("-ea").build();
182 		new Runner(opt).run();
183 	}
184 }