Skip to content

Commit b2fdd97

Browse files
committed
Prepare performance fix
Take over two classes from Gitblit 1.7.1 that could benefit from some performance tuning.
1 parent dd86380 commit b2fdd97

File tree

3 files changed

+367
-0
lines changed

3 files changed

+367
-0
lines changed

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ limitations under the License.
234234
<exclude>com/gitblit/utils/MetricUtils$*.class</exclude>
235235
<exclude>com/gitblit/utils/GitBlitDiffFormatter.class</exclude><!-- Number of diff context lines -->
236236
<exclude>com/gitblit/utils/GitBlitDiffFormatter$*.class</exclude>
237+
<exclude>com/gitblit/utils/ArrayUtils.class</exclude><!-- Performance fix, c.f. issue #23 -->
238+
<exclude>com/gitblit/utils/ArrayUtils$*.class</exclude>
239+
<exclude>com/gitblit/utils/CommitCache.class</exclude><!-- Performance fix, c.f. issue #23 -->
240+
<exclude>com/gitblit/utils/CommitCache$*.class</exclude>
237241
<!-- JGit compatibility. -->
238242
<exclude>com/gitblit/utils/DiffUtils.class</exclude>
239243
<exclude>com/gitblit/utils/DiffUtils$*.class</exclude>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2012 gitblit.com.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.gitblit.utils;
17+
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.Collection;
21+
import java.util.List;
22+
23+
24+
/**
25+
* Utility class for arrays and collections.
26+
*
27+
* @author James Moger
28+
*
29+
*/
30+
public class ArrayUtils {
31+
32+
public static boolean isEmpty(byte [] array) {
33+
return array == null || array.length == 0;
34+
}
35+
36+
public static boolean isEmpty(char [] array) {
37+
return array == null || array.length == 0;
38+
}
39+
40+
public static boolean isEmpty(Object [] array) {
41+
return array == null || array.length == 0;
42+
}
43+
44+
public static boolean isEmpty(Collection<?> collection) {
45+
return collection == null || collection.size() == 0;
46+
}
47+
48+
public static String toString(Collection<?> collection) {
49+
if (isEmpty(collection)) {
50+
return "";
51+
}
52+
StringBuilder sb = new StringBuilder();
53+
for (Object o : collection) {
54+
sb.append(o.toString()).append(", ");
55+
}
56+
// trim trailing comma-space
57+
sb.setLength(sb.length() - 2);
58+
return sb.toString();
59+
}
60+
61+
public static Collection<String> fromString(String value) {
62+
if (StringUtils.isEmpty(value)) {
63+
value = "";
64+
}
65+
List<String> list = new ArrayList<String>();
66+
String [] values = value.split(",|;");
67+
for (String v : values) {
68+
String string = v.trim();
69+
if (!StringUtils.isEmpty(string)) {
70+
list.add(string);
71+
}
72+
}
73+
return list;
74+
}
75+
76+
public static <X> List<X> join(List<X>... elements) {
77+
List<X> list = new ArrayList<X>();
78+
for (List<X> element : elements) {
79+
list.addAll(element);
80+
}
81+
return list;
82+
}
83+
84+
public static <X> List<X> join(X[]... elements) {
85+
List<X> list = new ArrayList<X>();
86+
for (X[] element : elements) {
87+
list.addAll(Arrays.asList(element));
88+
}
89+
return list;
90+
}
91+
}
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*
2+
* Copyright 2013 gitblit.com.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.gitblit.utils;
17+
18+
import java.text.MessageFormat;
19+
import java.util.ArrayList;
20+
import java.util.Calendar;
21+
import java.util.Date;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.concurrent.ConcurrentHashMap;
25+
import java.util.concurrent.TimeUnit;
26+
27+
import org.eclipse.jgit.lib.ObjectId;
28+
import org.eclipse.jgit.lib.Repository;
29+
import org.eclipse.jgit.revwalk.RevCommit;
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
32+
33+
import com.gitblit.models.RefModel;
34+
import com.gitblit.models.RepositoryCommit;
35+
36+
/**
37+
* Caches repository commits for re-use in the dashboard and activity pages.
38+
*
39+
* @author James Moger
40+
*
41+
*/
42+
public class CommitCache {
43+
44+
private static final CommitCache instance;
45+
46+
protected final Logger logger = LoggerFactory.getLogger(getClass());
47+
48+
protected final Map<String, ObjectCache<List<RepositoryCommit>>> cache;
49+
50+
protected int cacheDays = -1;
51+
52+
public static CommitCache instance() {
53+
return instance;
54+
}
55+
56+
static {
57+
instance = new CommitCache();
58+
}
59+
60+
protected CommitCache() {
61+
cache = new ConcurrentHashMap<String, ObjectCache<List<RepositoryCommit>>>();
62+
}
63+
64+
/**
65+
* Returns the cutoff date for the cache. Commits after this date are cached.
66+
* Commits before this date are not cached.
67+
*
68+
* @return
69+
*/
70+
public Date getCutoffDate() {
71+
final Calendar cal = Calendar.getInstance();
72+
cal.setTimeInMillis(System.currentTimeMillis());
73+
cal.set(Calendar.HOUR_OF_DAY, 0);
74+
cal.set(Calendar.MINUTE, 0);
75+
cal.set(Calendar.SECOND, 0);
76+
cal.set(Calendar.MILLISECOND, 0);
77+
cal.add(Calendar.DATE, -1*cacheDays);
78+
return cal.getTime();
79+
}
80+
81+
/**
82+
* Sets the number of days to cache.
83+
*
84+
* @param days
85+
*/
86+
public synchronized void setCacheDays(int days) {
87+
this.cacheDays = days;
88+
clear();
89+
}
90+
91+
/**
92+
* Clears the entire commit cache.
93+
*
94+
*/
95+
public void clear() {
96+
cache.clear();
97+
}
98+
99+
/**
100+
* Clears the commit cache for a specific repository.
101+
*
102+
* @param repositoryName
103+
*/
104+
public void clear(String repositoryName) {
105+
String repoKey = repositoryName.toLowerCase();
106+
ObjectCache<List<RepositoryCommit>> repoCache = cache.remove(repoKey);
107+
if (repoCache != null) {
108+
logger.info(MessageFormat.format("{0} commit cache cleared", repositoryName));
109+
}
110+
}
111+
112+
/**
113+
* Clears the commit cache for a specific branch of a specific repository.
114+
*
115+
* @param repositoryName
116+
* @param branch
117+
*/
118+
public void clear(String repositoryName, String branch) {
119+
String repoKey = repositoryName.toLowerCase();
120+
ObjectCache<List<RepositoryCommit>> repoCache = cache.get(repoKey);
121+
if (repoCache != null) {
122+
List<RepositoryCommit> commits = repoCache.remove(branch.toLowerCase());
123+
if (!ArrayUtils.isEmpty(commits)) {
124+
logger.info(MessageFormat.format("{0}:{1} commit cache cleared", repositoryName, branch));
125+
}
126+
}
127+
}
128+
129+
/**
130+
* Get all commits for the specified repository:branch that are in the cache.
131+
*
132+
* @param repositoryName
133+
* @param repository
134+
* @param branch
135+
* @return a list of commits
136+
*/
137+
public List<RepositoryCommit> getCommits(String repositoryName, Repository repository, String branch) {
138+
return getCommits(repositoryName, repository, branch, getCutoffDate());
139+
}
140+
141+
/**
142+
* Get all commits for the specified repository:branch since a specific date.
143+
* These commits may be retrieved from the cache if the sinceDate is after
144+
* the cacheCutoffDate.
145+
*
146+
* @param repositoryName
147+
* @param repository
148+
* @param branch
149+
* @param sinceDate
150+
* @return a list of commits
151+
*/
152+
public List<RepositoryCommit> getCommits(String repositoryName, Repository repository, String branch, Date sinceDate) {
153+
long start = System.nanoTime();
154+
Date cacheCutoffDate = getCutoffDate();
155+
List<RepositoryCommit> list;
156+
if (cacheDays > 0 && (sinceDate.getTime() >= cacheCutoffDate.getTime())) {
157+
// request fits within the cache window
158+
String repoKey = repositoryName.toLowerCase();
159+
if (!cache.containsKey(repoKey)) {
160+
cache.put(repoKey, new ObjectCache<List<RepositoryCommit>>());
161+
}
162+
163+
ObjectCache<List<RepositoryCommit>> repoCache = cache.get(repoKey);
164+
String branchKey = branch.toLowerCase();
165+
166+
RevCommit tip = JGitUtils.getCommit(repository, branch);
167+
Date tipDate = JGitUtils.getCommitDate(tip);
168+
169+
List<RepositoryCommit> commits;
170+
if (!repoCache.hasCurrent(branchKey, tipDate)) {
171+
commits = repoCache.getObject(branchKey);
172+
if (ArrayUtils.isEmpty(commits)) {
173+
// we don't have any cached commits for this branch, reload
174+
commits = get(repositoryName, repository, branch, cacheCutoffDate);
175+
repoCache.updateObject(branchKey, tipDate, commits);
176+
logger.debug(MessageFormat.format("parsed {0} commits from {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
177+
commits.size(), repositoryName, branch, cacheCutoffDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
178+
} else {
179+
// incrementally update cache since the last cached commit
180+
ObjectId sinceCommit = commits.get(0).getId();
181+
List<RepositoryCommit> incremental = get(repositoryName, repository, branch, sinceCommit);
182+
logger.info(MessageFormat.format("incrementally added {0} commits to cache for {1}:{2} in {3} msecs",
183+
incremental.size(), repositoryName, branch, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
184+
incremental.addAll(commits);
185+
repoCache.updateObject(branchKey, tipDate, incremental);
186+
commits = incremental;
187+
}
188+
} else {
189+
// cache is current
190+
commits = repoCache.getObject(branchKey);
191+
// evict older commits outside the cache window
192+
commits = reduce(commits, cacheCutoffDate);
193+
// update cache
194+
repoCache.updateObject(branchKey, tipDate, commits);
195+
}
196+
197+
if (sinceDate.equals(cacheCutoffDate)) {
198+
list = commits;
199+
} else {
200+
// reduce the commits to those since the specified date
201+
list = reduce(commits, sinceDate);
202+
}
203+
logger.debug(MessageFormat.format("retrieved {0} commits from cache of {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
204+
list.size(), repositoryName, branch, sinceDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
205+
} else {
206+
// not caching or request outside cache window
207+
list = get(repositoryName, repository, branch, sinceDate);
208+
logger.debug(MessageFormat.format("parsed {0} commits from {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
209+
list.size(), repositoryName, branch, sinceDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
210+
}
211+
return list;
212+
}
213+
214+
/**
215+
* Returns a list of commits for the specified repository branch.
216+
*
217+
* @param repositoryName
218+
* @param repository
219+
* @param branch
220+
* @param sinceDate
221+
* @return a list of commits
222+
*/
223+
protected List<RepositoryCommit> get(String repositoryName, Repository repository, String branch, Date sinceDate) {
224+
Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, false);
225+
List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>();
226+
for (RevCommit commit : JGitUtils.getRevLog(repository, branch, sinceDate)) {
227+
RepositoryCommit commitModel = new RepositoryCommit(repositoryName, branch, commit);
228+
List<RefModel> commitRefs = allRefs.get(commitModel.getId());
229+
commitModel.setRefs(commitRefs);
230+
commits.add(commitModel);
231+
}
232+
return commits;
233+
}
234+
235+
/**
236+
* Returns a list of commits for the specified repository branch since the specified commit.
237+
*
238+
* @param repositoryName
239+
* @param repository
240+
* @param branch
241+
* @param sinceCommit
242+
* @return a list of commits
243+
*/
244+
protected List<RepositoryCommit> get(String repositoryName, Repository repository, String branch, ObjectId sinceCommit) {
245+
Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, false);
246+
List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>();
247+
for (RevCommit commit : JGitUtils.getRevLog(repository, sinceCommit.getName(), branch)) {
248+
RepositoryCommit commitModel = new RepositoryCommit(repositoryName, branch, commit);
249+
List<RefModel> commitRefs = allRefs.get(commitModel.getId());
250+
commitModel.setRefs(commitRefs);
251+
commits.add(commitModel);
252+
}
253+
return commits;
254+
}
255+
256+
/**
257+
* Reduces the list of commits to those since the specified date.
258+
*
259+
* @param commits
260+
* @param sinceDate
261+
* @return a list of commits
262+
*/
263+
protected List<RepositoryCommit> reduce(List<RepositoryCommit> commits, Date sinceDate) {
264+
List<RepositoryCommit> filtered = new ArrayList<RepositoryCommit>();
265+
for (RepositoryCommit commit : commits) {
266+
if (commit.getCommitDate().compareTo(sinceDate) >= 0) {
267+
filtered.add(commit);
268+
}
269+
}
270+
return filtered;
271+
}
272+
}

0 commit comments

Comments
 (0)