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