1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.pgm.debug;
45
46 import static java.lang.Integer.valueOf;
47 import static java.lang.Long.valueOf;
48
49 import java.io.File;
50 import java.lang.management.ManagementFactory;
51 import java.lang.management.ThreadMXBean;
52 import java.lang.reflect.Field;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.List;
56
57 import org.eclipse.jgit.diff.DiffAlgorithm;
58 import org.eclipse.jgit.diff.HistogramDiff;
59 import org.eclipse.jgit.diff.MyersDiff;
60 import org.eclipse.jgit.diff.RawText;
61 import org.eclipse.jgit.diff.RawTextComparator;
62 import org.eclipse.jgit.errors.LargeObjectException;
63 import org.eclipse.jgit.lib.AbbreviatedObjectId;
64 import org.eclipse.jgit.lib.Constants;
65 import org.eclipse.jgit.lib.FileMode;
66 import org.eclipse.jgit.lib.MutableObjectId;
67 import org.eclipse.jgit.lib.ObjectId;
68 import org.eclipse.jgit.lib.ObjectReader;
69 import org.eclipse.jgit.lib.Repository;
70 import org.eclipse.jgit.lib.RepositoryBuilder;
71 import org.eclipse.jgit.lib.RepositoryCache;
72 import org.eclipse.jgit.pgm.Command;
73 import org.eclipse.jgit.pgm.TextBuiltin;
74 import org.eclipse.jgit.pgm.internal.CLIText;
75 import org.eclipse.jgit.revwalk.RevCommit;
76 import org.eclipse.jgit.revwalk.RevWalk;
77 import org.eclipse.jgit.treewalk.TreeWalk;
78 import org.eclipse.jgit.treewalk.filter.TreeFilter;
79 import org.eclipse.jgit.util.FS;
80 import org.kohsuke.args4j.Option;
81
82 @Command(usage = "usage_DiffAlgorithms")
83 class DiffAlgorithms extends TextBuiltin {
84
85 final Algorithm myers = new Algorithm() {
86 @Override
87 DiffAlgorithm create() {
88 return MyersDiff.INSTANCE;
89 }
90 };
91
92 final Algorithm histogram = new Algorithm() {
93 @Override
94 DiffAlgorithm create() {
95 HistogramDiff d = new HistogramDiff();
96 d.setFallbackAlgorithm(null);
97 return d;
98 }
99 };
100
101 final Algorithm histogram_myers = new Algorithm() {
102 @Override
103 DiffAlgorithm create() {
104 HistogramDiff d = new HistogramDiff();
105 d.setFallbackAlgorithm(MyersDiff.INSTANCE);
106 return d;
107 }
108 };
109
110
111
112
113
114
115
116 @Option(name = "--algorithm", metaVar = "NAME", usage = "Enable algorithm(s)")
117 List<String> algorithms = new ArrayList<>();
118
119 @Option(name = "--text-limit", metaVar = "LIMIT", usage = "Maximum size in KiB to scan per file revision")
120 int textLimit = 15 * 1024;
121
122 @Option(name = "--repository", aliases = { "-r" }, metaVar = "GIT_DIR", usage = "Repository to scan")
123 List<File> gitDirs = new ArrayList<>();
124
125 @Option(name = "--count", metaVar = "LIMIT", usage = "Number of file revisions to be compared")
126 int count = 0;
127
128 private final RawTextComparator cmp = RawTextComparator.DEFAULT;
129
130 private ThreadMXBean mxBean;
131
132
133 @Override
134 protected boolean requiresRepository() {
135 return false;
136 }
137
138
139 @Override
140 protected void run() throws Exception {
141 mxBean = ManagementFactory.getThreadMXBean();
142 if (!mxBean.isCurrentThreadCpuTimeSupported())
143 throw die("Current thread CPU time not supported on this JRE");
144
145 if (gitDirs.isEmpty()) {
146 RepositoryBuilder rb = new RepositoryBuilder()
147 .setGitDir(new File(gitdir))
148 .readEnvironment()
149 .findGitDir();
150 if (rb.getGitDir() == null)
151 throw die(CLIText.get().cantFindGitDirectory);
152 gitDirs.add(rb.getGitDir());
153 }
154
155 for (File dir : gitDirs) {
156 RepositoryBuilder rb = new RepositoryBuilder();
157 if (RepositoryCache.FileKey.isGitRepository(dir, FS.DETECTED))
158 rb.setGitDir(dir);
159 else
160 rb.findGitDir(dir);
161
162 try (Repository repo = rb.build()) {
163 run(repo);
164 }
165 }
166 }
167
168 private void run(Repository repo) throws Exception {
169 List<Test> all = init();
170
171 long files = 0;
172 int commits = 0;
173 int minN = Integer.MAX_VALUE;
174 int maxN = 0;
175
176 AbbreviatedObjectId startId;
177 try (ObjectReader or = repo.newObjectReader();
178 RevWalk rw = new RevWalk(or)) {
179 final MutableObjectId id = new MutableObjectId();
180 TreeWalk tw = new TreeWalk(or);
181 tw.setFilter(TreeFilter.ANY_DIFF);
182 tw.setRecursive(true);
183
184 ObjectId start = repo.resolve(Constants.HEAD);
185 startId = or.abbreviate(start);
186 rw.markStart(rw.parseCommit(start));
187 for (;;) {
188 final RevCommit c = rw.next();
189 if (c == null)
190 break;
191 commits++;
192 if (c.getParentCount() != 1)
193 continue;
194
195 RevCommit p = c.getParent(0);
196 rw.parseHeaders(p);
197 tw.reset(p.getTree(), c.getTree());
198 while (tw.next()) {
199 if (!isFile(tw, 0) || !isFile(tw, 1))
200 continue;
201
202 byte[] raw0;
203 try {
204 tw.getObjectId(id, 0);
205 raw0 = or.open(id).getCachedBytes(textLimit * 1024);
206 } catch (LargeObjectException tooBig) {
207 continue;
208 }
209 if (RawText.isBinary(raw0))
210 continue;
211
212 byte[] raw1;
213 try {
214 tw.getObjectId(id, 1);
215 raw1 = or.open(id).getCachedBytes(textLimit * 1024);
216 } catch (LargeObjectException tooBig) {
217 continue;
218 }
219 if (RawText.isBinary(raw1))
220 continue;
221
222 RawText txt0 = new RawText(raw0);
223 RawText txt1 = new RawText(raw1);
224
225 minN = Math.min(minN, txt0.size() + txt1.size());
226 maxN = Math.max(maxN, txt0.size() + txt1.size());
227
228 for (Test test : all)
229 testOne(test, txt0, txt1);
230 files++;
231 }
232 if (count > 0 && files > count)
233 break;
234 }
235 }
236
237 Collections.sort(all, (Test a, Test b) -> {
238 int result = Long.signum(a.runningTimeNanos - b.runningTimeNanos);
239 if (result == 0) {
240 result = a.algorithm.name.compareTo(b.algorithm.name);
241 }
242 return result;
243 });
244
245 File directory = repo.getDirectory();
246 if (directory != null) {
247 String name = directory.getName();
248 File parent = directory.getParentFile();
249 if (name.equals(Constants.DOT_GIT) && parent != null)
250 name = parent.getName();
251 outw.println(name + ": start at " + startId.name());
252 }
253
254 outw.format(" %12d files, %8d commits\n", valueOf(files),
255 valueOf(commits));
256 outw.format(" N=%10d min lines, %8d max lines\n", valueOf(minN),
257 valueOf(maxN));
258
259 outw.format("%-25s %12s ( %12s %12s )\n",
260 "Algorithm", "Time(ns)", "Time(ns) on", "Time(ns) on");
261 outw.format("%-25s %12s ( %12s %12s )\n",
262 "", "", "N=" + minN, "N=" + maxN);
263 outw.println("-----------------------------------------------------"
264 + "----------------");
265
266 for (Test test : all) {
267 outw.format("%-25s %12d ( %12d %12d )",
268 test.algorithm.name,
269 valueOf(test.runningTimeNanos),
270 valueOf(test.minN.runningTimeNanos),
271 valueOf(test.maxN.runningTimeNanos));
272 outw.println();
273 }
274 outw.println();
275 outw.flush();
276 }
277
278 private static boolean isFile(TreeWalk tw, int ithTree) {
279 FileMode fm = tw.getFileMode(ithTree);
280 return FileMode.REGULAR_FILE.equals(fm)
281 || FileMode.EXECUTABLE_FILE.equals(fm);
282 }
283
284 private static final int minCPUTimerTicks = 10;
285
286 private void testOne(Test test, RawText a, RawText b) {
287 final DiffAlgorithm da = test.algorithm.create();
288 int cpuTimeChanges = 0;
289 int cnt = 0;
290
291 final long startTime = mxBean.getCurrentThreadCpuTime();
292 long lastTime = startTime;
293 while (cpuTimeChanges < minCPUTimerTicks) {
294 da.diff(cmp, a, b);
295 cnt++;
296
297 long interimTime = mxBean.getCurrentThreadCpuTime();
298 if (interimTime != lastTime) {
299 cpuTimeChanges++;
300 lastTime = interimTime;
301 }
302 }
303 final long stopTime = mxBean.getCurrentThreadCpuTime();
304 final long runTime = (stopTime - startTime) / cnt;
305
306 test.runningTimeNanos += runTime;
307
308 if (test.minN == null || a.size() + b.size() < test.minN.n) {
309 test.minN = new Run();
310 test.minN.n = a.size() + b.size();
311 test.minN.runningTimeNanos = runTime;
312 }
313
314 if (test.maxN == null || a.size() + b.size() > test.maxN.n) {
315 test.maxN = new Run();
316 test.maxN.n = a.size() + b.size();
317 test.maxN.runningTimeNanos = runTime;
318 }
319 }
320
321 private List<Test> init() {
322 List<Test> all = new ArrayList<>();
323
324 try {
325 for (Field f : DiffAlgorithms.class.getDeclaredFields()) {
326 if (f.getType() == Algorithm.class) {
327 f.setAccessible(true);
328 Algorithm alg = (Algorithm) f.get(this);
329 alg.name = f.getName();
330 if (included(alg.name, algorithms)) {
331 Test test = new Test();
332 test.algorithm = alg;
333 all.add(test);
334 }
335 }
336 }
337 } catch (IllegalArgumentException | IllegalAccessException e) {
338 throw die("Cannot determine names", e);
339 }
340 return all;
341 }
342
343 private static boolean included(String name, List<String> want) {
344 if (want.isEmpty())
345 return true;
346 for (String s : want) {
347 if (s.equalsIgnoreCase(name))
348 return true;
349 }
350 return false;
351 }
352
353 private static abstract class Algorithm {
354 String name;
355
356 abstract DiffAlgorithm create();
357 }
358
359 private static class Test {
360 Algorithm algorithm;
361
362 long runningTimeNanos;
363
364 Run minN;
365
366 Run maxN;
367 }
368
369 private static class Run {
370 int n;
371
372 long runningTimeNanos;
373 }
374 }