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
134 @Override
135 protected boolean requiresRepository() {
136 return false;
137 }
138
139
140 @Override
141 protected void run() throws Exception {
142 mxBean = ManagementFactory.getThreadMXBean();
143 if (!mxBean.isCurrentThreadCpuTimeSupported())
144 throw die("Current thread CPU time not supported on this JRE");
145
146 if (gitDirs.isEmpty()) {
147 RepositoryBuilder rb = new RepositoryBuilder()
148 .setGitDir(new File(gitdir))
149 .readEnvironment()
150 .findGitDir();
151 if (rb.getGitDir() == null)
152 throw die(CLIText.get().cantFindGitDirectory);
153 gitDirs.add(rb.getGitDir());
154 }
155
156 for (File dir : gitDirs) {
157 RepositoryBuilder rb = new RepositoryBuilder();
158 if (RepositoryCache.FileKey.isGitRepository(dir, FS.DETECTED))
159 rb.setGitDir(dir);
160 else
161 rb.findGitDir(dir);
162
163 try (Repository repo = rb.build()) {
164 run(repo);
165 }
166 }
167 }
168
169 private void run(Repository repo) throws Exception {
170 List<Test> all = init();
171
172 long files = 0;
173 int commits = 0;
174 int minN = Integer.MAX_VALUE;
175 int maxN = 0;
176
177 AbbreviatedObjectId startId;
178 try (ObjectReader or = repo.newObjectReader();
179 RevWalk rw = new RevWalk(or)) {
180 final MutableObjectId id = new MutableObjectId();
181 TreeWalk tw = new TreeWalk(or);
182 tw.setFilter(TreeFilter.ANY_DIFF);
183 tw.setRecursive(true);
184
185 ObjectId start = repo.resolve(Constants.HEAD);
186 startId = or.abbreviate(start);
187 rw.markStart(rw.parseCommit(start));
188 for (;;) {
189 final RevCommit c = rw.next();
190 if (c == null)
191 break;
192 commits++;
193 if (c.getParentCount() != 1)
194 continue;
195
196 RevCommit p = c.getParent(0);
197 rw.parseHeaders(p);
198 tw.reset(p.getTree(), c.getTree());
199 while (tw.next()) {
200 if (!isFile(tw, 0) || !isFile(tw, 1))
201 continue;
202
203 byte[] raw0;
204 try {
205 tw.getObjectId(id, 0);
206 raw0 = or.open(id).getCachedBytes(textLimit * 1024);
207 } catch (LargeObjectException tooBig) {
208 continue;
209 }
210 if (RawText.isBinary(raw0))
211 continue;
212
213 byte[] raw1;
214 try {
215 tw.getObjectId(id, 1);
216 raw1 = or.open(id).getCachedBytes(textLimit * 1024);
217 } catch (LargeObjectException tooBig) {
218 continue;
219 }
220 if (RawText.isBinary(raw1))
221 continue;
222
223 RawText txt0 = new RawText(raw0);
224 RawText txt1 = new RawText(raw1);
225
226 minN = Math.min(minN, txt0.size() + txt1.size());
227 maxN = Math.max(maxN, txt0.size() + txt1.size());
228
229 for (Test test : all)
230 testOne(test, txt0, txt1);
231 files++;
232 }
233 if (count > 0 && files > count)
234 break;
235 }
236 }
237
238 Collections.sort(all, new Comparator<Test>() {
239 @Override
240 public int compare(Test a, Test b) {
241 int result = Long.signum(a.runningTimeNanos - b.runningTimeNanos);
242 if (result == 0) {
243 result = a.algorithm.name.compareTo(b.algorithm.name);
244 }
245 return result;
246 }
247 });
248
249 File directory = repo.getDirectory();
250 if (directory != null) {
251 String name = directory.getName();
252 File parent = directory.getParentFile();
253 if (name.equals(Constants.DOT_GIT) && parent != null)
254 name = parent.getName();
255 outw.println(name + ": start at " + startId.name());
256 }
257
258 outw.format(" %12d files, %8d commits\n", valueOf(files),
259 valueOf(commits));
260 outw.format(" N=%10d min lines, %8d max lines\n", valueOf(minN),
261 valueOf(maxN));
262
263 outw.format("%-25s %12s ( %12s %12s )\n",
264 "Algorithm", "Time(ns)", "Time(ns) on", "Time(ns) on");
265 outw.format("%-25s %12s ( %12s %12s )\n",
266 "", "", "N=" + minN, "N=" + maxN);
267 outw.println("-----------------------------------------------------"
268 + "----------------");
269
270 for (Test test : all) {
271 outw.format("%-25s %12d ( %12d %12d )",
272 test.algorithm.name,
273 valueOf(test.runningTimeNanos),
274 valueOf(test.minN.runningTimeNanos),
275 valueOf(test.maxN.runningTimeNanos));
276 outw.println();
277 }
278 outw.println();
279 outw.flush();
280 }
281
282 private static boolean isFile(TreeWalk tw, int ithTree) {
283 FileMode fm = tw.getFileMode(ithTree);
284 return FileMode.REGULAR_FILE.equals(fm)
285 || FileMode.EXECUTABLE_FILE.equals(fm);
286 }
287
288 private static final int minCPUTimerTicks = 10;
289
290 private void testOne(Test test, RawText a, RawText b) {
291 final DiffAlgorithm da = test.algorithm.create();
292 int cpuTimeChanges = 0;
293 int cnt = 0;
294
295 final long startTime = mxBean.getCurrentThreadCpuTime();
296 long lastTime = startTime;
297 while (cpuTimeChanges < minCPUTimerTicks) {
298 da.diff(cmp, a, b);
299 cnt++;
300
301 long interimTime = mxBean.getCurrentThreadCpuTime();
302 if (interimTime != lastTime) {
303 cpuTimeChanges++;
304 lastTime = interimTime;
305 }
306 }
307 final long stopTime = mxBean.getCurrentThreadCpuTime();
308 final long runTime = (stopTime - startTime) / cnt;
309
310 test.runningTimeNanos += runTime;
311
312 if (test.minN == null || a.size() + b.size() < test.minN.n) {
313 test.minN = new Run();
314 test.minN.n = a.size() + b.size();
315 test.minN.runningTimeNanos = runTime;
316 }
317
318 if (test.maxN == null || a.size() + b.size() > test.maxN.n) {
319 test.maxN = new Run();
320 test.maxN.n = a.size() + b.size();
321 test.maxN.runningTimeNanos = runTime;
322 }
323 }
324
325 private List<Test> init() {
326 List<Test> all = new ArrayList<>();
327
328 try {
329 for (Field f : DiffAlgorithms.class.getDeclaredFields()) {
330 if (f.getType() == Algorithm.class) {
331 f.setAccessible(true);
332 Algorithm alg = (Algorithm) f.get(this);
333 alg.name = f.getName();
334 if (included(alg.name, algorithms)) {
335 Test test = new Test();
336 test.algorithm = alg;
337 all.add(test);
338 }
339 }
340 }
341 } catch (IllegalArgumentException e) {
342 throw die("Cannot determine names", e);
343 } catch (IllegalAccessException e) {
344 throw die("Cannot determine names", e);
345 }
346
347 return all;
348 }
349
350 private static boolean included(String name, List<String> want) {
351 if (want.isEmpty())
352 return true;
353 for (String s : want) {
354 if (s.equalsIgnoreCase(name))
355 return true;
356 }
357 return false;
358 }
359
360 private static abstract class Algorithm {
361 String name;
362
363 abstract DiffAlgorithm create();
364 }
365
366 private static class Test {
367 Algorithm algorithm;
368
369 long runningTimeNanos;
370
371 Run minN;
372
373 Run maxN;
374 }
375
376 private static class Run {
377 int n;
378
379 long runningTimeNanos;
380 }
381 }