Skip to content
Permalink
Browse files

[JENKINS-20892] Partial fix of poor scalability in /builds and relate…

…d displays.

(cherry picked from commit c5e2373)
  • Loading branch information
jglick authored and olivergondza committed Feb 28, 2014
1 parent 915000b commit ef6343be19fc990e0fcdab31d4d3f30c56baba0b
@@ -150,6 +150,7 @@

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import hudson.util.RunList;
import java.util.concurrent.atomic.AtomicLong;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -1441,7 +1442,8 @@ public static String runScript(Script script) throws JellyTagException {
}

/**
* Returns a sub-list if the given list is bigger than the specified 'maxSize'
* Returns a sub-list if the given list is bigger than the specified {@code maxSize}.
* <strong>Warning:</strong> do not call this with a {@link RunList}, or you will break lazy loading!
*/
public static <T> List<T> subList(List<T> base, int maxSize) {
if(maxSize<base.size())
@@ -45,13 +45,15 @@
protected final RunList<?> builds;

public BuildTimelineWidget(RunList<?> builds) {
this.builds = builds;
this.builds = builds.limit(20); // TODO instead render lazily
}

@Deprecated
public Run<?, ?> getFirstBuild() {
return builds.getFirstBuild();
}

@Deprecated
public Run<?, ?> getLastBuild() {
return builds.getLastBuild();
}
@@ -1269,7 +1269,7 @@ public String toString() {
}

DataSetBuilder<String, ChartLabel> data = new DataSetBuilder<String, ChartLabel>();
for (Run r : getBuilds()) {
for (Run r : getNewBuilds()) {
if (r.isBuilding())
continue;
data.add(((double) r.getDuration()) / (1000 * 60), "min",
@@ -24,6 +24,7 @@
*/
package hudson.model;

import com.google.common.base.Predicate;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.*;
import hudson.model.Descriptor.FormException;
@@ -448,19 +449,14 @@ private boolean relatedTo(AbstractBuild<?,?> b) {
/**
* Gets the list of {@link Build}s that include changes by this user,
* by the timestamp order.
*
* TODO: do we need some index for this?
*/
@WithBridgeMethods(List.class)
public RunList getBuilds() {
List<AbstractBuild> r = new ArrayList<AbstractBuild>();
for (AbstractProject<?,?> p : Jenkins.getInstance().getAllItems(AbstractProject.class))
for (AbstractBuild<?,?> b : p.getBuilds().newBuilds()){
if (relatedTo(b)) {
r.add(b);
}
return new RunList<Run<?,?>>(Jenkins.getInstance().getAllItems(Job.class)).filter(new Predicate<Run<?,?>>() {
@Override public boolean apply(Run<?,?> r) {
return r instanceof AbstractBuild && relatedTo((AbstractBuild<?,?>) r);
}
return RunList.fromRuns(r);
});
}

/**
@@ -187,9 +187,10 @@ public R getLastBuild() {

/**
* Returns elements that satisfy the given predicate.
* <em>Warning:</em> this method mutates the original list and then returns it.
* @since TODO
*/
// for compatibility reasons, this method doesn't create a new list but updates the current one
private RunList<R> filter(Predicate<R> predicate) {
public RunList<R> filter(Predicate<R> predicate) {
size = null;
first = null;
base = Iterables.filter(base,predicate);
@@ -0,0 +1,89 @@
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package jenkins.widgets;

import hudson.Functions;
import hudson.model.BallColor;
import hudson.model.Run;
import java.util.ArrayList;
import java.util.List;
import jenkins.util.ProgressiveRendering;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;

@Restricted(DoNotUse.class) // only for buildListTable.jelly
public class BuildListTable extends ProgressiveRendering {

private final List<JSONObject> results = new ArrayList<JSONObject>();
private Iterable<? extends Run<?,?>> builds;

/** Jelly cannot call a constructor with arguments. */
public void setBuilds(Iterable<? extends Run<?,?>> builds) {
this.builds = builds;
}

@Override protected void compute() throws Exception {
double decay = 1;
for (Run<?,?> build : builds) {
if (canceled()) {
return;
}
JSONObject element = new JSONObject();
calculate(build, element);
synchronized (results) {
results.add(element);
}
// Since we cannot predict how many there will be, just show an ever-growing bar.
decay *= .99;
progress(1 - decay);
}
}

@Override protected synchronized JSON data() {
JSONArray d = JSONArray.fromObject(results);
results.clear();
return d;
}

private void calculate(Run<?,?> build, JSONObject element) {
BallColor iconColor = build.getIconColor();
element.put("iconColorOrdinal", iconColor.ordinal());
element.put("iconColorDescription", iconColor.getDescription());
element.put("url", build.getUrl());
element.put("buildStatusUrl", build.getBuildStatusUrl());
element.put("parentUrl", build.getParent().getUrl());
element.put("parentFullDisplayName", Functions.breakableString(Functions.escape(build.getParent().getFullDisplayName())));
element.put("displayName", build.getDisplayName());
element.put("timestampString", build.getTimestampString());
element.put("timestampString2", build.getTimestampString2());
Run.Summary buildStatusSummary = build.getBuildStatusSummary();
element.put("buildStatusSummaryWorse", buildStatusSummary.isWorse);
element.put("buildStatusSummaryMessage", buildStatusSummary.message);
}

}
@@ -77,10 +77,7 @@ THE SOFTWARE.
// theme1.autoWidth = true; // Set the Timeline's "width" automatically.
// Set autoWidth on the Timeline's first band's theme,
// will affect all bands.
theme1.timeline_start = new Date(${it.firstBuild.timeInMillis-24*60*60*1000});
theme1.timeline_stop = new Date(${it.lastBuild.timeInMillis+24*60*60*1000});
var d = new Date(${it.lastBuild.timeInMillis});
var bandInfos = [
// the bar that shows outline
Timeline.createBandInfo({
@@ -34,8 +34,6 @@ THE SOFTWARE.
<div style="height:2em"/><!-- spacer -->

<h1>${%Build Time Trend}</h1>
<j:choose>
<j:when test="${it.builds.size()&gt;1}">
<div align="center">
<img src="buildTimeGraph/png" width="500" height="400" lazymap="buildTimeGraph/map" alt="[Build time graph]" style="float:right"/>
</div>
@@ -51,7 +49,7 @@ THE SOFTWARE.
<th>${%Slave}</th>
</j:if>
</tr>
<j:forEach var="r" items="${it.builds}">
<j:forEach var="r" items="${it.builds}"> <!-- TODO progressively render this -->
<tr>
<td data="${r.iconColor.ordinal()}">
<img width="16" height="16" src="${imagesURL}/16x16/${r.buildStatusUrl}" alt="${r.iconColor.description}" />
@@ -73,11 +71,6 @@ THE SOFTWARE.
</j:forEach>
</table>
</div>
</j:when>
<j:otherwise>
${%More than 1 builds are needed for the trend report.}
</j:otherwise>
</j:choose>
</l:main-panel>
</l:layout>
</j:jelly>
@@ -30,6 +30,7 @@ THE SOFTWARE.
<img src="${h.getUserAvatar(it,'48x48')}" alt="" height="48" width="48" />
${%title(it)}
</h1>
<!-- TODO consider adding a BuildTimelineWidget (cf. Job, View, Computer) -->
<t:buildListTable builds="${it.builds}" jobBaseUrl="${rootURL}/" />
</l:main-panel>
</l:layout>
@@ -43,33 +43,65 @@ THE SOFTWARE.
<th>${%Status}</th>
<th><st:nbsp/></th>
</tr>
<!-- TODO: support gradual expansion of the list -->
<j:forEach var="b" items="${h.subList(attrs.builds,50)}">
<tr>
<td data="${b.iconColor.ordinal()}">
<a href="${jobBaseUrl}${b.url}">
<img src="${imagesURL}/${iconSize}/${b.buildStatusUrl}"
alt="${b.iconColor.description}" class="icon${iconSize}"/>
</a>
</td>
<td>
<a href="${jobBaseUrl}${b.parent.url}" class="model-link"><l:breakable value="${b.parent.fullDisplayName}"/></a>
<st:nbsp/>
<a href="${jobBaseUrl}${b.url}" class="model-link inside">${b.displayName}</a>
</td>
<td data="${b.timestampString2}" tooltip="${%Click to center timeline on event}" onclick="javascript:tl.getBand(0).scrollToCenter(Timeline.DateTime.parseGregorianDateTime('${b.timestampString2}'))">
${b.timestampString}
</td>
<td>
<t:buildStatusSummary build="${b}" />
</td>
<td>
<a href="${jobBaseUrl}${b.url}console">
<img src="${imagesURL}/${subIconSize}/terminal.png" title="${%Console output}" alt="${%Console output}" border="0" />
</a>
</td>
</tr>
</j:forEach>
<script>
function displayBuilds(data) {
var p = $$('projectStatus');
for (var x = 0; data.length > x; x++) {
var e = data[x];
var tr = document.createElement('tr');
var td = document.createElement('td');
td.setAttribute('data', e.iconColorOrdinal);
var a = document.createElement('a');
a.href = '${jobBaseUrl}' + e.url;
var img = document.createElement('img');
img.src = '${imagesURL}/${iconSize}/' + e.buildStatusUrl;
img.alt = e.iconColorDescription;
img.className = 'icon${iconSize}';
a.appendChild(img);
td.appendChild(a);
tr.appendChild(td);
td = document.createElement('td');
a = document.createElement('a');
a.href = '${jobBaseUrl}' + e.parentUrl;
a.className = 'model-link';
a.innerHTML = e.parentFullDisplayName;
td.appendChild(a);
td.appendChild(document.createTextNode('\u00A0')); // nbsp
a = document.createElement('a');
a.href = '${jobBaseUrl}' + e.url;
a.className = 'model-link inside';
a.appendChild(document.createTextNode(e.displayName));
td.appendChild(a);
tr.appendChild(td);
td = document.createElement('td');
td.setAttribute('data', e.timestampString2);
td.tooltip = '${%Click to center timeline on event}';
td.onclick = 'javascript:tl.getBand(0).scrollToCenter(Timeline.DateTime.parseGregorianDateTime("' + e.timestampString2 + '"))';
td.appendChild(document.createTextNode(e.timestampString));
tr.appendChild(td);
td = document.createElement('td');
if (e.buildStatusSummaryWorse) {
td.style.color = 'red';
}
td.appendChild(document.createTextNode(e.buildStatusSummaryMessage));
tr.appendChild(td);
td = document.createElement('td');
var a = document.createElement('a');
a.href = '${jobBaseUrl}' + e.url + 'console';
var img = document.createElement('img');
img.src = '${imagesURL}/${subIconSize}/terminal.png';
img.alt = '${%Console output}';
img.border = 0;
a.appendChild(img);
td.appendChild(a);
tr.appendChild(td);
p.appendChild(tr);
}
}
</script>
<j:new var="handler" className="jenkins.widgets.BuildListTable"/>
${handler.setBuilds(attrs.builds)}
<l:progressiveRendering handler="${handler}" callback="displayBuilds"/>
</table>
<t:rssBar-with-iconSize/>
</j:jelly>

0 comments on commit ef6343b

Please sign in to comment.
You can’t perform that action at this time.