From 025e7cb907281a60ed6f1c03fa9a6a68bb8c14b6 Mon Sep 17 00:00:00 2001 From: Peter Samoshkin Date: Thu, 8 Nov 2012 16:03:25 +0200 Subject: [PATCH 1/2] Implemented release lock action --- pom.xml | 2 +- .../ReleaseThrottleLockAction.java | 27 +++++++ .../ReleaseThrottleLockBuilder.java | 70 +++++++++++++++++++ .../ReleaseThrottleLockPublisher.java | 70 +++++++++++++++++++ .../ThrottleJobProperty.java | 6 +- .../ThrottleQueueTaskDispatcher.java | 15 ++-- 6 files changed, 173 insertions(+), 17 deletions(-) create mode 100644 src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockAction.java create mode 100644 src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockBuilder.java create mode 100644 src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockPublisher.java diff --git a/pom.xml b/pom.xml index edef7334..b263ff77 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ THE SOFTWARE. org.jenkins-ci.plugins plugin - 1.399 + 1.477 throttle-concurrents diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockAction.java b/src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockAction.java new file mode 100644 index 00000000..6d074ef0 --- /dev/null +++ b/src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockAction.java @@ -0,0 +1,27 @@ +package hudson.plugins.throttleconcurrents; + +import hudson.model.Action; + +/** +* Created with IntelliJ IDEA. +* User: psamoshkin +* Date: 10/25/12 +* Time: 12:07 PM +* To change this template use File | Settings | File Templates. +*/ +public class ReleaseThrottleLockAction implements Action { + @Override + public String getIconFileName() { + return null; + } + + @Override + public String getDisplayName() { + return null; + } + + @Override + public String getUrlName() { + return null; + } +} diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockBuilder.java b/src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockBuilder.java new file mode 100644 index 00000000..88c4d598 --- /dev/null +++ b/src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockBuilder.java @@ -0,0 +1,70 @@ +package hudson.plugins.throttleconcurrents; + +import hudson.Extension; +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.tasks.BuildStepDescriptor; +import hudson.tasks.BuildStepMonitor; +import hudson.tasks.Builder; +import hudson.tasks.Publisher; +import net.sf.json.JSONObject; +import org.kohsuke.stapler.StaplerRequest; + +import java.io.IOException; + +/** + * Created with IntelliJ IDEA. + * User: psamoshkin + * Date: 10/24/12 + * Time: 5:09 PM + * To change this template use File | Settings | File Templates. + */ +public class ReleaseThrottleLockBuilder extends Builder { + + @Extension + public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); + + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + build.addAction(new ReleaseThrottleLockAction()); + return true; + } + + @Override + public BuildStepMonitor getRequiredMonitorService() { + return BuildStepMonitor.NONE; + } + + @Override + public BuildStepDescriptor getDescriptor() { + return DESCRIPTOR; + } + + private static class DescriptorImpl extends BuildStepDescriptor { + protected DescriptorImpl() { + super(ReleaseThrottleLockBuilder.class); + } + + @Override + public String getDisplayName() { + return "Release throttle-concurrent lock"; + } + + @Override + public String getHelpFile() { + return null; + } + + @Override + public boolean isApplicable(Class jobType) { + return true; + } + + @Override + public Builder newInstance(StaplerRequest req, JSONObject formData) throws FormException { + return new ReleaseThrottleLockBuilder(); + } + } +} diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockPublisher.java b/src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockPublisher.java new file mode 100644 index 00000000..51932d24 --- /dev/null +++ b/src/main/java/hudson/plugins/throttleconcurrents/ReleaseThrottleLockPublisher.java @@ -0,0 +1,70 @@ +package hudson.plugins.throttleconcurrents; + +import hudson.Extension; +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.tasks.BuildStepDescriptor; +import hudson.tasks.BuildStepMonitor; +import hudson.tasks.Publisher; +import net.sf.json.JSONObject; +import org.kohsuke.stapler.StaplerRequest; + +import java.io.IOException; + +/** + * Created with IntelliJ IDEA. + * User: psamoshkin + * Date: 10/24/12 + * Time: 5:09 PM + * To change this template use File | Settings | File Templates. + */ +public class ReleaseThrottleLockPublisher extends Publisher { + + @Extension + public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); + + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + build.addAction(new ReleaseThrottleLockAction()); + return true; + } + + @Override + public BuildStepMonitor getRequiredMonitorService() { + return BuildStepMonitor.NONE; + } + + @Override + public BuildStepDescriptor getDescriptor() { + return DESCRIPTOR; + } + + private static class DescriptorImpl extends BuildStepDescriptor { + protected DescriptorImpl() { + super(ReleaseThrottleLockPublisher.class); + } + + @Override + public String getDisplayName() { + return "Release throttle-concurrent lock"; + } + + @Override + public String getHelpFile() { + return null; + } + + @Override + public boolean isApplicable(Class jobType) { + return true; + } + + @Override + public Publisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { + return new ReleaseThrottleLockPublisher(); + } + } + +} diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java index 572c8e5c..f38b7202 100644 --- a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java @@ -1,11 +1,7 @@ package hudson.plugins.throttleconcurrents; import hudson.Extension; -import hudson.model.AbstractProject; -import hudson.model.Job; -import hudson.model.JobProperty; -import hudson.model.JobPropertyDescriptor; -import hudson.model.Node; +import hudson.model.*; import hudson.util.FormValidation; import hudson.util.ListBoxModel; import hudson.Util; diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java index d52682a3..44810ced 100644 --- a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java @@ -3,22 +3,13 @@ import hudson.Extension; import hudson.matrix.MatrixConfiguration; import hudson.matrix.MatrixProject; -import hudson.model.AbstractBuild; -import hudson.model.AbstractProject; -import hudson.model.Computer; -import hudson.model.Executor; -import hudson.model.Hudson; -import hudson.model.Node; -import hudson.model.Queue; +import hudson.model.*; import hudson.model.Queue.Task; import hudson.model.queue.CauseOfBlockage; import hudson.model.queue.QueueTaskDispatcher; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; @@ -196,7 +187,9 @@ private int buildsOfProjectOnAllNodes(Task task) { private int buildsOnExecutor(Task task, Executor exec) { int runCount = 0; if (exec.getCurrentExecutable() != null - && exec.getCurrentExecutable().getParent() == task) { + && exec.getCurrentExecutable().getParent() == task + && !((exec.getCurrentExecutable() instanceof Actionable) + && Actionable.class.cast(exec).getAction(ReleaseThrottleLockAction.class) != null)) { runCount++; } From 180919a18ec0e84a51677d9013d6ec8b002a0c58 Mon Sep 17 00:00:00 2001 From: advantiss Date: Fri, 23 Nov 2012 18:33:48 +0200 Subject: [PATCH 2/2] - Refactorings - added "Interval" throttle option - enable configuring both project and category rules in job configuration - redesign config.jelly --- pom.xml | 4 +- .../CategoryTaskDispatcher.java | 105 ++++++++++ .../ProjectTaskDispatcher.java | 60 ++++++ .../ThrottleJobProperty.java | 88 +++++++-- .../ThrottleQueueTaskDispatcher.java | 187 ++++++------------ .../throttleconcurrents/Messages.properties | 1 + .../ThrottleJobProperty/config.jelly | 73 ++++--- .../ThrottleJobProperty/global.jelly | 6 +- .../help-categories.interval.html | 3 + .../help-categories.maxConcurrentPerNode.html | 3 + .../help-categories.maxConcurrentTotal.html | 3 + .../ThrottleJobProperty/help-interval.html | 3 + 12 files changed, 361 insertions(+), 175 deletions(-) create mode 100644 src/main/java/hudson/plugins/throttleconcurrents/CategoryTaskDispatcher.java create mode 100644 src/main/java/hudson/plugins/throttleconcurrents/ProjectTaskDispatcher.java create mode 100644 src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.interval.html create mode 100644 src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.maxConcurrentPerNode.html create mode 100644 src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.maxConcurrentTotal.html create mode 100644 src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-interval.html diff --git a/pom.xml b/pom.xml index b263ff77..d9ecba98 100644 --- a/pom.xml +++ b/pom.xml @@ -99,13 +99,11 @@ THE SOFTWARE. scm:git:git@github.com:jenkinsci/throttle-concurrent-builds-plugin.git http://github.com/jenkinsci/throttle-concurrents-plugin - - repo.jenkins-ci.org http://repo.jenkins-ci.org/public/ - + diff --git a/src/main/java/hudson/plugins/throttleconcurrents/CategoryTaskDispatcher.java b/src/main/java/hudson/plugins/throttleconcurrents/CategoryTaskDispatcher.java new file mode 100644 index 00000000..1d433155 --- /dev/null +++ b/src/main/java/hudson/plugins/throttleconcurrents/CategoryTaskDispatcher.java @@ -0,0 +1,105 @@ +package hudson.plugins.throttleconcurrents; + +import hudson.Extension; +import hudson.matrix.MatrixConfiguration; +import hudson.model.AbstractProject; +import hudson.model.Hudson; +import hudson.model.Node; +import hudson.model.Queue; +import hudson.model.queue.CauseOfBlockage; + +import java.util.List; +import java.util.logging.Logger; + +/** + * Created with IntelliJ IDEA. + * User: psamoshkin + * Date: 11/9/12 + * Time: 5:04 PM + * To change this template use File | Settings | File Templates. + */ +@Extension +public class CategoryTaskDispatcher extends ThrottleQueueTaskDispatcher { + @Override + public CauseOfBlockage canTake(Node node, Queue.Task task, ThrottleJobProperty tjp) { + if (tjp.getThrottleCategory()) { + if (tjp.getCategories() != null && !tjp.getCategories().isEmpty()) { + for (String catNm : tjp.getCategories()) { + ThrottleJobProperty.ThrottleCategory category = + ((ThrottleJobProperty.DescriptorImpl) tjp.getDescriptor()).getCategoryByName(catNm); + if (category != null) { + synchronized (category) { + int maxConcurrentPerNode = category.getMaxConcurrentPerNode().intValue(); + long startIntervalPerNode = category.getInterval(); + if (maxConcurrentPerNode > 0 || startIntervalPerNode > 0) { + List> categoryProjects = category.getProjects(); + int runCount = 0; + long minElapsedTime = 4294967296L; + long elapsedTime = minElapsedTime; + + for (AbstractProject catProj : categoryProjects) { + if (Hudson.getInstance().getQueue().isPending(catProj)) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); + } + runCount += buildsOfProjectOnNode(node, catProj); + elapsedTime = buildsOfProjectOnNodeMinimalElapsedTime(node, catProj); + if(elapsedTime < minElapsedTime){ + minElapsedTime = elapsedTime; + } + } + // This would mean that there are as many or more builds currently running than are allowed. + if (maxConcurrentPerNode > 0 && runCount >= maxConcurrentPerNode) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityOnNode(runCount)); + } + else if(runCount > 0 && startIntervalPerNode > 0 + && minElapsedTime != LONG_MAX && startIntervalPerNode > minElapsedTime){ + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_IntervalOnNode(runCount)); + } + } + } + } + } + } + } + return null; + } + + @Override + public CauseOfBlockage canRun(Queue.Task task, ThrottleJobProperty tjp) { + if (tjp.getThrottleCategory()) { + if (tjp.getCategories() != null && !tjp.getCategories().isEmpty()) { + for (String catNm : tjp.getCategories()) { + ThrottleJobProperty.ThrottleCategory category; + + category = ((ThrottleJobProperty.DescriptorImpl) tjp.getDescriptor()).getCategoryByName(catNm); + + if (category != null) { + synchronized (category){ + List> categoryProjects = category.getProjects(); + + int maxConcurrentTotal = category.getMaxConcurrentTotal().intValue(); + if (maxConcurrentTotal > 0) { + int totalRunCount = 0; + + for (AbstractProject catProj : categoryProjects) { + if (Hudson.getInstance().getQueue().isPending(catProj)) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); + } + totalRunCount += buildsOfProjectOnAllNodes(catProj); + } + + if (totalRunCount >= maxConcurrentTotal) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityTotal(totalRunCount)); + } + } + } + } + } + } + } + + return null; + } + + private static final Logger LOGGER = Logger.getLogger(CategoryTaskDispatcher.class.getName()); +} diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ProjectTaskDispatcher.java b/src/main/java/hudson/plugins/throttleconcurrents/ProjectTaskDispatcher.java new file mode 100644 index 00000000..f50dd5e0 --- /dev/null +++ b/src/main/java/hudson/plugins/throttleconcurrents/ProjectTaskDispatcher.java @@ -0,0 +1,60 @@ +package hudson.plugins.throttleconcurrents; + +import hudson.Extension; +import hudson.model.Node; +import hudson.model.Queue; +import hudson.model.queue.CauseOfBlockage; + +import java.util.logging.Logger; + +/** + * Created with IntelliJ IDEA. + * User: psamoshkin + * Date: 11/9/12 + * Time: 5:04 PM + * To change this template use File | Settings | File Templates. + */ +@Extension +public class ProjectTaskDispatcher extends ThrottleQueueTaskDispatcher { + + @Override + public CauseOfBlockage canTake(Node node, Queue.Task task, ThrottleJobProperty tjp) { + if (tjp.getThrottleProjectAlone()) { + if (tjp.getMaxConcurrentPerNode().intValue() > 0) { + int maxConcurrentPerNode = tjp.getMaxConcurrentPerNode().intValue(); + long interval= tjp.getInterval().intValue(); + int runCount = buildsOfProjectOnNode(node, task); + + long minElapsedTime = buildsOfProjectOnNodeMinimalElapsedTime(node, task); + + // This would mean that there are as many or more builds currently running than are allowed. + if (runCount >= maxConcurrentPerNode) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityOnNode(runCount)); + } else if(runCount>0 && interval > 0 + && minElapsedTime != LONG_MAX && interval > minElapsedTime){ + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_IntervalOnNode(runCount)); + } + } + } + + return null; + } + + public CauseOfBlockage canRun(Queue.Task task, ThrottleJobProperty tjp) { + + if (tjp.getThrottleProjectAlone()) { + if (tjp.getMaxConcurrentTotal().intValue() > 0) { + int maxConcurrentTotal = tjp.getMaxConcurrentTotal().intValue(); + int totalRunCount = buildsOfProjectOnAllNodes(task); + + if (totalRunCount >= maxConcurrentTotal) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityTotal(totalRunCount)); + } + } + } + + return null; + } + + private static final Logger LOGGER = Logger.getLogger(ProjectTaskDispatcher.class.getName()); +} diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java index f38b7202..8730fdb4 100644 --- a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java @@ -24,30 +24,38 @@ public class ThrottleJobProperty extends JobProperty> { // Moving category to categories, to support, well, multiple categories per job. @Deprecated transient String category; + // trottleOption replaced with flags throttleProjectAlone and throttleCategory + @Deprecated transient String throttleOption; private Integer maxConcurrentPerNode; private Integer maxConcurrentTotal; + private Long interval; private List categories; private boolean throttleEnabled; - private String throttleOption; /** * Store a config version so we're able to migrate config on various * functionality upgrades. */ private Long configVersion; - + private boolean throttleProjectAlone; + private boolean throttleCategory; + @DataBoundConstructor public ThrottleJobProperty(Integer maxConcurrentPerNode, Integer maxConcurrentTotal, + Long interval, List categories, boolean throttleEnabled, - String throttleOption) { + boolean throttleProjectAlone, + boolean throttleCategory) { this.maxConcurrentPerNode = maxConcurrentPerNode == null ? 0 : maxConcurrentPerNode; this.maxConcurrentTotal = maxConcurrentTotal == null ? 0 : maxConcurrentTotal; + this.interval = interval == null ? 0 : interval; this.categories = categories; this.throttleEnabled = throttleEnabled; - this.throttleOption = throttleOption; + this.throttleProjectAlone = throttleProjectAlone; + this.throttleCategory = throttleCategory; } @@ -66,17 +74,27 @@ public Object readResolve() { category = null; } - if (configVersion < 1 && throttleOption == null) { + if (configVersion < 1) { if (categories.isEmpty()) { - throttleOption = "project"; + throttleProjectAlone = true; } else { - throttleOption = "category"; + throttleCategory = true; maxConcurrentPerNode = 0; maxConcurrentTotal = 0; + interval = 0L; } } - configVersion = 1L; + else if(configVersion == 1){ + if(throttleOption == "project"){ + throttleProjectAlone = true; + } + if(throttleOption == "category"){ + throttleCategory = true; + } + } + + configVersion = 2L; return this; } @@ -85,10 +103,6 @@ public boolean getThrottleEnabled() { return throttleEnabled; } - public String getThrottleOption() { - return throttleOption; - } - public List getCategories() { return categories; } @@ -107,6 +121,21 @@ public Integer getMaxConcurrentTotal() { return maxConcurrentTotal; } + public Long getInterval() { + if (interval == null) + interval = 0L; + + return interval; + } + + public boolean getThrottleProjectAlone() { + return throttleProjectAlone; + } + + public boolean getThrottleCategory() { + return throttleCategory; + } + @Extension public static final class DescriptorImpl extends JobPropertyDescriptor { private List categories; @@ -198,17 +227,25 @@ public ListBoxModel doFillCategoryItems() { } - public static final class ThrottleCategory { + public static final class ThrottleCategory extends AbstractDescribableImpl { private Integer maxConcurrentPerNode; private Integer maxConcurrentTotal; private String categoryName; - + private Long interval; + + @Extension + public static class DescriptorImpl extends Descriptor { + public String getDisplayName() { return ""; } + } + @DataBoundConstructor public ThrottleCategory(String categoryName, Integer maxConcurrentPerNode, - Integer maxConcurrentTotal) { + Integer maxConcurrentTotal, + Long interval) { this.maxConcurrentPerNode = maxConcurrentPerNode == null ? 0 : maxConcurrentPerNode; this.maxConcurrentTotal = maxConcurrentTotal == null ? 0 : maxConcurrentTotal; + this.interval = interval == null ? 0 : interval; this.categoryName = categoryName; } @@ -226,9 +263,30 @@ public Integer getMaxConcurrentTotal() { return maxConcurrentTotal; } + public long getInterval() { + if (interval == null) + interval = 0L; + + return interval; + } + public String getCategoryName() { return categoryName; } + + public List> getProjects() { + List> categoryProjects = new ArrayList>(); + for (AbstractProject p : Hudson.getInstance().getAllItems(AbstractProject.class)) { + ThrottleJobProperty t = p.getProperty(ThrottleJobProperty.class); + + if (t!=null && t.getThrottleEnabled() && t.getThrottleCategory()) { + if (t.getCategories()!=null && t.getCategories().contains(categoryName)) { + categoryProjects.add(p); + } + } + } + return categoryProjects; + } } private static Logger LOGGER = Logger.getLogger(ThrottleJobProperty.class.getName()); diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java index 44810ced..d45a74ca 100644 --- a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java @@ -8,70 +8,32 @@ import hudson.model.queue.CauseOfBlockage; import hudson.model.queue.QueueTaskDispatcher; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; import java.util.logging.Logger; -@Extension -public class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher { +public abstract class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher { + final long LONG_MAX = 4294967296L; + + public abstract CauseOfBlockage canTake(Node node, Task task, ThrottleJobProperty jpp); + + public abstract CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp); @Override public CauseOfBlockage canTake(Node node, Task task) { if (task instanceof MatrixConfiguration) { return null; } - ThrottleJobProperty tjp = getThrottleJobProperty(task); - if (tjp!=null && tjp.getThrottleEnabled()) { - CauseOfBlockage cause = canRun(task, tjp); - if (cause != null) return cause; - - if (tjp.getThrottleOption().equals("project")) { - if (tjp.getMaxConcurrentPerNode().intValue() > 0) { - int maxConcurrentPerNode = tjp.getMaxConcurrentPerNode().intValue(); - int runCount = buildsOfProjectOnNode(node, task); - - // This would mean that there are as many or more builds currently running than are allowed. - if (runCount >= maxConcurrentPerNode) { - return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityOnNode(runCount)); - } - } + if (tjp != null && tjp.getThrottleEnabled()){ + if (Hudson.getInstance().getQueue().isPending(task)) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); } - else if (tjp.getThrottleOption().equals("category")) { - // If the project is in one or more categories... - if (tjp.getCategories() != null && !tjp.getCategories().isEmpty()) { - for (String catNm : tjp.getCategories()) { - // Quick check that catNm itself is a real string. - if (catNm != null && !catNm.equals("")) { - List> categoryProjects = getCategoryProjects(catNm); - - ThrottleJobProperty.ThrottleCategory category = - ((ThrottleJobProperty.DescriptorImpl)tjp.getDescriptor()).getCategoryByName(catNm); - - // Double check category itself isn't null - if (category != null) { - // Max concurrent per node for category - if (category.getMaxConcurrentPerNode().intValue() > 0) { - int maxConcurrentPerNode = category.getMaxConcurrentPerNode().intValue(); - int runCount = 0; - - for (AbstractProject catProj : categoryProjects) { - if (Hudson.getInstance().getQueue().isPending(catProj)) { - return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); - } - runCount += buildsOfProjectOnNode(node, catProj); - } - // This would mean that there are as many or more builds currently running than are allowed. - if (runCount >= maxConcurrentPerNode) { - return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityOnNode(runCount)); - } - } - } - } - } - } + CauseOfBlockage cause = canRun(task, tjp); + if (cause != null) { + return cause; } + return canTake(node, task, tjp); } return null; @@ -79,70 +41,21 @@ else if (tjp.getThrottleOption().equals("category")) { // @Override on jenkins 4.127+ , but still compatible with 1.399 public CauseOfBlockage canRun(Queue.Item item) { - ThrottleJobProperty tjp = getThrottleJobProperty(item.task); - if (tjp!=null && tjp.getThrottleEnabled()) { - return canRun(item.task, tjp); - } - return null; - } - - public CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp) { - if (task instanceof MatrixConfiguration) { + if (item.task instanceof MatrixConfiguration) { return null; } - if (Hudson.getInstance().getQueue().isPending(task)) { - return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); - } - if (tjp.getThrottleOption().equals("project")) { - if (tjp.getMaxConcurrentTotal().intValue() > 0) { - int maxConcurrentTotal = tjp.getMaxConcurrentTotal().intValue(); - int totalRunCount = buildsOfProjectOnAllNodes(task); - - if (totalRunCount >= maxConcurrentTotal) { - return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityTotal(totalRunCount)); - } - } - } - // If the project is in one or more categories... - else if (tjp.getThrottleOption().equals("category")) { - if (tjp.getCategories() != null && !tjp.getCategories().isEmpty()) { - for (String catNm : tjp.getCategories()) { - // Quick check that catNm itself is a real string. - if (catNm != null && !catNm.equals("")) { - List> categoryProjects = getCategoryProjects(catNm); - - ThrottleJobProperty.ThrottleCategory category = - ((ThrottleJobProperty.DescriptorImpl)tjp.getDescriptor()).getCategoryByName(catNm); - - // Double check category itself isn't null - if (category != null) { - if (category.getMaxConcurrentTotal().intValue() > 0) { - int maxConcurrentTotal = category.getMaxConcurrentTotal().intValue(); - int totalRunCount = 0; - - for (AbstractProject catProj : categoryProjects) { - if (Hudson.getInstance().getQueue().isPending(catProj)) { - return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); - } - totalRunCount += buildsOfProjectOnAllNodes(catProj); - } - - if (totalRunCount >= maxConcurrentTotal) { - return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_MaxCapacityTotal(totalRunCount)); - } - } - - } - } - } + ThrottleJobProperty tjp = getThrottleJobProperty(item.task); + if (tjp!=null && tjp.getThrottleEnabled()) { + Set unblockedTasks = Hudson.getInstance().getQueue().getUnblockedTasks(); + if (Hudson.getInstance().getQueue().isPending(item.task)) { + return CauseOfBlockage.fromMessage(Messages._ThrottleQueueTaskDispatcher_BuildPending()); } + return canRun(item.task, tjp); } - return null; } - - private ThrottleJobProperty getThrottleJobProperty(Task task) { + protected ThrottleJobProperty getThrottleJobProperty(Task task) { if (task instanceof AbstractProject) { AbstractProject p = (AbstractProject) task; if (task instanceof MatrixConfiguration) { @@ -154,9 +67,8 @@ private ThrottleJobProperty getThrottleJobProperty(Task task) { return null; } - private int buildsOfProjectOnNode(Node node, Task task) { + protected int buildsOfProjectOnNode(Node node, Task task) { int runCount = 0; - LOGGER.fine("Checking for builds of " + task.getName() + " on node " + node.getDisplayName()); // I think this'll be more reliable than job.getBuilds(), which seemed to not always get // a build right after it was launched, for some reason. @@ -175,7 +87,32 @@ private int buildsOfProjectOnNode(Node node, Task task) { return runCount; } - private int buildsOfProjectOnAllNodes(Task task) { + protected long buildsOfProjectOnNodeMinimalElapsedTime(Node node, Task task) { + long minimalRunningTime = LONG_MAX; + // I think this'll be more reliable than job.getBuilds(), which seemed to not always get + // a build right after it was launched, for some reason. + Computer computer = node.toComputer(); + if (computer != null) { //Not all nodes are certain to become computers, like nodes with 0 executors. + for (Executor e : computer.getExecutors()) { + long elapsedTime = minimalElapsedBuildTimeOnExecutor(task, e); + if(elapsedTime < minimalRunningTime){ + minimalRunningTime = elapsedTime; + } + } + if (task instanceof MatrixProject) { + for (Executor e : computer.getOneOffExecutors()) { + long elapsedTime = minimalElapsedBuildTimeOnExecutor(task, e); + if(elapsedTime < minimalRunningTime){ + minimalRunningTime = elapsedTime; + } + } + } + } + + return minimalRunningTime; + } + + protected int buildsOfProjectOnAllNodes(Task task) { int totalRunCount = buildsOfProjectOnNode(Hudson.getInstance(), task); for (Node node : Hudson.getInstance().getNodes()) { @@ -187,32 +124,24 @@ private int buildsOfProjectOnAllNodes(Task task) { private int buildsOnExecutor(Task task, Executor exec) { int runCount = 0; if (exec.getCurrentExecutable() != null - && exec.getCurrentExecutable().getParent() == task - && !((exec.getCurrentExecutable() instanceof Actionable) - && Actionable.class.cast(exec).getAction(ReleaseThrottleLockAction.class) != null)) { + && exec.getCurrentExecutable().getParent() == task) { runCount++; } return runCount; } - - private List> getCategoryProjects(String category) { - List> categoryProjects = new ArrayList>(); - - if (category != null && !category.equals("")) { - for (AbstractProject p : Hudson.getInstance().getAllItems(AbstractProject.class)) { - ThrottleJobProperty t = p.getProperty(ThrottleJobProperty.class); - - if (t!=null && t.getThrottleEnabled()) { - if (t.getCategories()!=null && t.getCategories().contains(category)) { - categoryProjects.add(p); - } - } + private Long minimalElapsedBuildTimeOnExecutor(Task task, Executor exec) { + Long minimalRunningTime = LONG_MAX; + if (exec.getCurrentExecutable() != null + && exec.getCurrentExecutable().getParent() == task) { + long elapsedTime = exec.getElapsedTime(); + if(elapsedTime < minimalRunningTime){ + minimalRunningTime = elapsedTime; } } - return categoryProjects; + return minimalRunningTime; } private static final Logger LOGGER = Logger.getLogger(ThrottleQueueTaskDispatcher.class.getName()); diff --git a/src/main/resources/hudson/plugins/throttleconcurrents/Messages.properties b/src/main/resources/hudson/plugins/throttleconcurrents/Messages.properties index bb5815d4..6bdc758c 100644 --- a/src/main/resources/hudson/plugins/throttleconcurrents/Messages.properties +++ b/src/main/resources/hudson/plugins/throttleconcurrents/Messages.properties @@ -1,3 +1,4 @@ ThrottleQueueTaskDispatcher.MaxCapacityOnNode=Already running {0} builds on node +ThrottleQueueTaskDispatcher.IntervalOnNode=Already running {0} builds on node in allowed interval ThrottleQueueTaskDispatcher.MaxCapacityTotal=Already running {0} builds across all nodes ThrottleQueueTaskDispatcher.BuildPending=A build is pending launch \ No newline at end of file diff --git a/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/config.jelly b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/config.jelly index a805a6d2..4a9f1c2e 100644 --- a/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/config.jelly +++ b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/config.jelly @@ -3,32 +3,53 @@ title="${%Throttle Concurrent Builds}" inline="true" checked="${instance.throttleEnabled}"> - - - - - - - - - - - - - - - - - - - - - - + + + + +
+ + + + + + + + + +
+
+ +
+ + + + + + + + + + + + + + You must declare throttle categories in jenkins config + +
+
+
+ + + + + diff --git a/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/global.jelly b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/global.jelly index 035d1320..694193e5 100644 --- a/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/global.jelly +++ b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/global.jelly @@ -7,13 +7,15 @@ - + + + +
diff --git a/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.interval.html b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.interval.html new file mode 100644 index 00000000..075e66a0 --- /dev/null +++ b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.interval.html @@ -0,0 +1,3 @@ +
+

The time in milliseconds have to be elapsed before next concurrent build start.

+
\ No newline at end of file diff --git a/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.maxConcurrentPerNode.html b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.maxConcurrentPerNode.html new file mode 100644 index 00000000..dedf248e --- /dev/null +++ b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.maxConcurrentPerNode.html @@ -0,0 +1,3 @@ +
+

The maximum number of concurrent builds of category projects to be allowed to run per node.

+
diff --git a/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.maxConcurrentTotal.html b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.maxConcurrentTotal.html new file mode 100644 index 00000000..b7fcd481 --- /dev/null +++ b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-categories.maxConcurrentTotal.html @@ -0,0 +1,3 @@ +
+

The maxmimum number of concurrent builds of category projects to be allowed to run at any one time, across all nodes.

+
\ No newline at end of file diff --git a/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-interval.html b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-interval.html new file mode 100644 index 00000000..075e66a0 --- /dev/null +++ b/src/main/resources/hudson/plugins/throttleconcurrents/ThrottleJobProperty/help-interval.html @@ -0,0 +1,3 @@ +
+

The time in milliseconds have to be elapsed before next concurrent build start.

+
\ No newline at end of file