From 4374e240ddd817a7526c451a8864c545476a5d0c Mon Sep 17 00:00:00 2001 From: emsa23 Date: Sun, 22 Sep 2013 18:50:43 +0200 Subject: [PATCH 01/46] Clean-up, updated developer info, new baseline is version 1.509.1 --- pom.xml | 160 ++++++++++++++++++++++++++------------------------------ 1 file changed, 74 insertions(+), 86 deletions(-) diff --git a/pom.xml b/pom.xml index 998c08b5..0b6b3603 100644 --- a/pom.xml +++ b/pom.xml @@ -1,94 +1,82 @@ - - 4.0.0 - - org.jvnet.hudson.plugins - plugin - 1.343 - ../pom.xml - + + 4.0.0 + + org.jenkins-ci.plugins + plugin + 1.509.1 + + PrioritySorter + 2.0-SNAPSHOT + hpi + Priority Sorter + This plugin allows for the build queue to be sorted based on pre-assigned priorities for each job. + https://wiki.jenkins-ci.org/display/JENKINS/Priority+Sorter+Plugin - hudson.queueSorter - PrioritySorter - 1.4-SNAPSHOT - hpi - Priority Sorter - This plugin allows for the build queue to be sorted based on pre-assigned priorities for each job. - http://wiki.hudson-ci.org/display/HUDSON/Priority+Sorter+Plugin + + + MIT + http://www.opensource.org/licenses/mit-license.php + + Copyright 2013- Magnus Sandberg. All rights reserved. + Copyright 2010- Brad Larson. All rights reserved. + + + - - - MIT - http://www.opensource.org/licenses/mit-license.php - Copyright 2010 Brad Larson. All rights reserved. - - + + + emsa23 + Magnus Sandberg + emsa@switchbeat.com + + developer + maintainer + + CET + + + bklarson + Brad Larson + bklarson@gmail.com + + developer + retired maintainer + + -6 + + - - - bklarson - Brad Larson - bklarson@gmail.com - - developer - maintainer - - -6 - - + + + + org.jenkins-ci.tools + maven-hpi-plugin + 1.96 + true + + + - - - - repo.jenkins-ci.org - http://repo.jenkins-ci.org/public/ - - - - - repo.jenkins-ci.org - http://repo.jenkins-ci.org/public/ - - + + + repo.jenkins-ci.org + http://repo.jenkins-ci.org/public/ + + - - - - org.apache.maven.plugins - maven-release-plugin - 2.0 - - - org.apache.maven.scm - maven-scm-provider-gitexe - 1.3 - - - - deploy - - - - - - org.jvnet.wagon-svn - wagon-svn - 1.9 - - - - - - scm:git:git://github.com/jenkinsci/priority-sorter-plugin.git - scm:git:git@github.com:jenkinsci/priority-sorter-plugin.git - http://github.com/jenkinsci/priority-sorter-plugin - - - - - java.net-m2-repository - http://maven.jenkins-ci.org:8081/content/repositories/releases/ - - + + + repo.jenkins-ci.org + http://repo.jenkins-ci.org/public/ + + + + + maven.jenkins-ci.org + http://maven.jenkins-ci.org:8081/content/repositories/releases/ + + From 8a5a854d86f142d62bfcff63ed76bf148b57eaac Mon Sep 17 00:00:00 2001 From: emsa23 Date: Sun, 22 Sep 2013 18:56:36 +0200 Subject: [PATCH 02/46] Adding Advanced Queue Sorting options and upgrade path. --- .../queueSorter/PrioritySorterDefaults.java | 2 +- .../queueSorter/PrioritySorterJobColumn.java | 20 +- .../PrioritySorterJobProperty.java | 5 + .../PrioritySorterQueueSorter.java | 2 - .../advancedqueue/AdvancedQueueSorter.java | 168 +++++++++++ .../AdvancedQueueSorterJobProperty.java | 81 ++++++ .../java/jenkins/advancedqueue/JobGroup.java | 10 + .../advancedqueue/PriorityConfiguration.java | 153 ++++++++++ .../advancedqueue/PriorityQueueSorter.java | 23 ++ .../PrioritySorterConfiguration.java | 271 ++++++++++++++++++ .../jenkins/advancedqueue/SorterStrategy.java | 20 ++ .../PrioritySorterJobProperty/config.jelly | 14 +- .../config.jelly | 26 ++ .../help-priority.html | 4 + .../PriorityConfiguration/index.jelly | 74 +++++ .../PrioritySorterConfiguration/config.jelly | 36 +++ src/main/webapp/scripts.js | 20 ++ .../PrioritySorterConfigurationTest.java | 46 +++ 18 files changed, 959 insertions(+), 16 deletions(-) create mode 100644 src/main/java/jenkins/advancedqueue/AdvancedQueueSorter.java create mode 100644 src/main/java/jenkins/advancedqueue/AdvancedQueueSorterJobProperty.java create mode 100644 src/main/java/jenkins/advancedqueue/JobGroup.java create mode 100644 src/main/java/jenkins/advancedqueue/PriorityConfiguration.java create mode 100644 src/main/java/jenkins/advancedqueue/PriorityQueueSorter.java create mode 100644 src/main/java/jenkins/advancedqueue/PrioritySorterConfiguration.java create mode 100644 src/main/java/jenkins/advancedqueue/SorterStrategy.java create mode 100644 src/main/resources/jenkins/advancedqueue/AdvancedQueueSorterJobProperty/config.jelly create mode 100644 src/main/resources/jenkins/advancedqueue/AdvancedQueueSorterJobProperty/help-priority.html create mode 100644 src/main/resources/jenkins/advancedqueue/PriorityConfiguration/index.jelly create mode 100644 src/main/resources/jenkins/advancedqueue/PrioritySorterConfiguration/config.jelly create mode 100644 src/main/webapp/scripts.js create mode 100644 src/test/java/jenkins/advancedqueue/PrioritySorterConfigurationTest.java diff --git a/src/main/java/hudson/queueSorter/PrioritySorterDefaults.java b/src/main/java/hudson/queueSorter/PrioritySorterDefaults.java index 4908c7ef..b53c10c3 100644 --- a/src/main/java/hudson/queueSorter/PrioritySorterDefaults.java +++ b/src/main/java/hudson/queueSorter/PrioritySorterDefaults.java @@ -27,7 +27,7 @@ public class PrioritySorterDefaults { final static int defaultPriority = 100; - static int getDefault() { + static public int getDefault() { return defaultPriority; } } diff --git a/src/main/java/hudson/queueSorter/PrioritySorterJobColumn.java b/src/main/java/hudson/queueSorter/PrioritySorterJobColumn.java index 60a75869..55613f09 100644 --- a/src/main/java/hudson/queueSorter/PrioritySorterJobColumn.java +++ b/src/main/java/hudson/queueSorter/PrioritySorterJobColumn.java @@ -21,11 +21,13 @@ */ package hudson.queueSorter; +import jenkins.advancedqueue.PriorityConfiguration; +import jenkins.advancedqueue.PrioritySorterConfiguration; import hudson.Extension; import hudson.model.Job; import hudson.views.ListViewColumn; - import hudson.views.ListViewColumnDescriptor; + import org.kohsuke.stapler.DataBoundConstructor; /** @@ -38,13 +40,17 @@ public PrioritySorterJobColumn() { } public String getPriority(final Job job) { - final PrioritySorterJobProperty jp = - job.getProperty(PrioritySorterJobProperty.class); - if (jp != null) { - return Integer.toString(jp.priority); + if(PrioritySorterConfiguration.get().getLegacyMode()) { + final PrioritySorterJobProperty jp = + job.getProperty(PrioritySorterJobProperty.class); + if (jp != null) { + return Integer.toString(jp.priority); + } else { + // No priority has been set for this job - use the default + return Integer.toString(PrioritySorterDefaults.getDefault()); + } } else { - // No priority has been set for this job - use the default - return Integer.toString(PrioritySorterDefaults.getDefault()); + return String.valueOf(PriorityConfiguration.get().getPriority(job)); } } diff --git a/src/main/java/hudson/queueSorter/PrioritySorterJobProperty.java b/src/main/java/hudson/queueSorter/PrioritySorterJobProperty.java index b0e0c16a..5fb665a7 100644 --- a/src/main/java/hudson/queueSorter/PrioritySorterJobProperty.java +++ b/src/main/java/hudson/queueSorter/PrioritySorterJobProperty.java @@ -23,6 +23,7 @@ */ package hudson.queueSorter; +import jenkins.advancedqueue.PrioritySorterConfiguration; import hudson.Extension; import hudson.model.JobProperty; import hudson.model.JobPropertyDescriptor; @@ -59,5 +60,9 @@ public String getDisplayName() { public int getDefault() { return PrioritySorterDefaults.getDefault(); } + + public boolean isUsed() { + return PrioritySorterConfiguration.get().getLegacyMode(); + } } } diff --git a/src/main/java/hudson/queueSorter/PrioritySorterQueueSorter.java b/src/main/java/hudson/queueSorter/PrioritySorterQueueSorter.java index b0403194..5234b528 100644 --- a/src/main/java/hudson/queueSorter/PrioritySorterQueueSorter.java +++ b/src/main/java/hudson/queueSorter/PrioritySorterQueueSorter.java @@ -23,7 +23,6 @@ */ package hudson.queueSorter; -import hudson.Extension; import hudson.model.AbstractProject; import hudson.model.Queue.BuildableItem; import hudson.model.queue.QueueSorter; @@ -32,7 +31,6 @@ import java.util.Comparator; import java.util.List; -@Extension public class PrioritySorterQueueSorter extends QueueSorter { private static final class BuildableComparitor implements diff --git a/src/main/java/jenkins/advancedqueue/AdvancedQueueSorter.java b/src/main/java/jenkins/advancedqueue/AdvancedQueueSorter.java new file mode 100644 index 00000000..b74b323c --- /dev/null +++ b/src/main/java/jenkins/advancedqueue/AdvancedQueueSorter.java @@ -0,0 +1,168 @@ +package jenkins.advancedqueue; + +import hudson.model.Job; +import hudson.model.Queue.BuildableItem; +import hudson.model.queue.QueueSorter; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class AdvancedQueueSorter extends QueueSorter { + + // Keeps track of what weighted-prio each buildableItems item has + static Map item2weight = new HashMap(); + // Keeps track of how many buildableItems slots of each base-priority we have counted + static Map prio2num = new HashMap(); + + @Override + public void sortBuildableItems(List items) { + //// + // Handle FIFO + SorterStrategy strategy = PrioritySorterConfiguration.get().getStrategy(); + if(SorterStrategy.FIFO == strategy) { + Collections.sort(items, new Comparator() { + public int compare(BuildableItem o1, BuildableItem o2) { + return (int) (o1.getInQueueSince() - o2.getInQueueSince()); + } + }); + return; + } + //// + // Handle ABSOLUTE + if(SorterStrategy.ABSOLUTE == strategy) { + final PriorityConfiguration configuration = PriorityConfiguration.get(); + Collections.sort(items, new Comparator() { + public int compare(BuildableItem o1, BuildableItem o2) { + int p1 = configuration.getPriority((Job) o1.task); + int p2 = configuration.getPriority((Job) o2.task); + if(p1 > p2) { + return 1; + } + if(p1 < p2) { + return -1; + } + return (int) (o1.getInQueueSince() - o2.getInQueueSince()); + } + }); + return; + } + // If the queue is empty reset the internal priority counters + if(items.isEmpty()) { + item2weight.clear(); + prio2num.clear(); + return; + } + //// + // Handle *FQ + // + // How many of priority slots per cycle do we have? + int numberOfPriorities = PrioritySorterConfiguration.get().getNumberOfPriorities(); + // Get the minimum prio we can use due to already started items - this forms the prio-baseline + float minPrioToAssign = getLastStartedPrio(items, item2weight); + // System.out.println("------------ " + minPrioToAssign); + // Calculate the minimum prio we can use for each priority-group + for (int priority = 1; priority <= numberOfPriorities; priority++) { + // The step-size for the priority + float stepSize = priority; + // Calculate the first usable prio for this prio-group + // ... and yes - this can be done better + Integer current = prio2num.get(priority); + if(current == null) { + current = 1; + } + float prioToUse = priority*current; + //(float)numberOfSlots*current; + while(prioToUse < minPrioToAssign) { + current++; + prioToUse += stepSize; + } + // System.out.println(priority + ":" + current + " [" + prioToUse + "/" + stepSize+ "]"); + prio2num.put(priority, current); + } + // Now let's assign prio to items + for (BuildableItem buildableItem : items) { + // If we haven't already seen this BuildableItem calculate its priority + if(!item2weight.containsKey(buildableItem.id)) { + // + int priority = PriorityConfiguration.get().getPriority((Job) buildableItem.task); + // Calculate the prio for this item based on how many of this prio + // has been added already and then increase to count for the next item + Integer current = prio2num.get(priority); + float weighted_priority; + if(SorterStrategy.WFQ == strategy) { + weighted_priority = current * priority + (priority / (float)numberOfPriorities); + } else { // FQ + weighted_priority = current + (priority / (float)numberOfPriorities); + } + item2weight.put(buildableItem.id, weighted_priority); + current++; + prio2num.put(priority, current); + // System.err.println(buildableItem.task.getDisplayName() + " " + buildableItem.id + "(" + priority + ") => " + weighted_priority); + } + } + // Now sort on weight + Collections.sort(items, new Comparator() { + public int compare(BuildableItem o1, BuildableItem o2) { + if(item2weight.get(o1.id) > item2weight.get(o2.id)) { + return 1; + } + if(item2weight.get(o1.id) < item2weight.get(o2.id)) { + return -1; + } + return (int) (o1.getInQueueSince() - o2.getInQueueSince()); + } + }); + // printQueue(items); + } + + private float getLastStartedPrio(List items, Map item2prio) { + float maxRemovedPrio = 0F; + if(items.isEmpty()) { + if(!item2prio.isEmpty()) { + return Collections.max(item2prio.values()); + } + } else { + // Get the lowest prio of the items in the queue that we have seen before + float currentlyMinPrio = Float.MAX_VALUE; + for (BuildableItem buildableItem : items) { + int id = buildableItem.id; + if(item2prio.containsKey(id) && item2prio.get(id) < currentlyMinPrio) { + currentlyMinPrio = item2prio.get(id); + } + } + // Now find the among the seen items the highest prio missing, ie started or possibly deleted + // Value also need be be less than "currentlyMinPrio" not to count items deleted from the middle of the queue + Set> entrySet = item2prio.entrySet(); + for (Entry entry : entrySet) { + Integer id = entry.getKey(); + float prio = entry.getValue(); + boolean itemFound = false; + for (BuildableItem buildableItem : items) { + if(id.equals(buildableItem.id)) { + itemFound = true; + } + } + if(!itemFound && prio < currentlyMinPrio && prio > maxRemovedPrio) { + maxRemovedPrio = prio; + } + } + } + return maxRemovedPrio; + } + + private void printQueue(List items) { + System.err.println("---- QUEUE ----"); + + for (BuildableItem buildableItem : items) { + Float priority = item2weight.get(buildableItem.id); + System.err.println(buildableItem.task.getName() + " - " + buildableItem.task.getDisplayName() + " " + buildableItem.id + "(" + priority + ")"); + } + System.err.println("---------------"); + } + +} diff --git a/src/main/java/jenkins/advancedqueue/AdvancedQueueSorterJobProperty.java b/src/main/java/jenkins/advancedqueue/AdvancedQueueSorterJobProperty.java new file mode 100644 index 00000000..a8f66b71 --- /dev/null +++ b/src/main/java/jenkins/advancedqueue/AdvancedQueueSorterJobProperty.java @@ -0,0 +1,81 @@ +/* + * The MIT License + * + * Copyright (c) 2010, Brad Larson + * + * 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.advancedqueue; + +import hudson.Extension; +import hudson.model.JobProperty; +import hudson.model.JobPropertyDescriptor; +import hudson.model.AbstractProject; +import hudson.util.ListBoxModel; + +import org.kohsuke.stapler.DataBoundConstructor; + +public class AdvancedQueueSorterJobProperty extends + JobProperty> { + + public final boolean useJobPriority; + public final int priority; + + @DataBoundConstructor + public AdvancedQueueSorterJobProperty(boolean useJobPriority, int priority) { + this.useJobPriority = useJobPriority; + this.priority = priority; + } + + public int getPriority() { + return priority; + } + + public boolean getUseJobPriority() { + return useJobPriority; + } + + @Override + public DescriptorImpl getDescriptor() { + return (DescriptorImpl) super.getDescriptor(); + } + + @Extension + public static final class DescriptorImpl extends JobPropertyDescriptor { + @Override + public String getDisplayName() { + return "Job Priority"; + } + + public int getDefault() { + return PrioritySorterConfiguration.get().getDefaultPriority(); + } + + public ListBoxModel getPriorities() { + ListBoxModel items = PrioritySorterConfiguration.get().doGetPriorityItems(); + return items; + } + + public boolean isUsed() { + PrioritySorterConfiguration configuration = PrioritySorterConfiguration.get(); + return !configuration.getLegacyMode() && configuration.getAllowPriorityOnJobs(); + } + + } +} diff --git a/src/main/java/jenkins/advancedqueue/JobGroup.java b/src/main/java/jenkins/advancedqueue/JobGroup.java new file mode 100644 index 00000000..e8f3fc04 --- /dev/null +++ b/src/main/java/jenkins/advancedqueue/JobGroup.java @@ -0,0 +1,10 @@ +package jenkins.advancedqueue; + +public class JobGroup { + + public int id = 0; + public int priority = 2; + public String view; + public boolean useJobFilter = false; + public String jobPattern = ".*"; +} diff --git a/src/main/java/jenkins/advancedqueue/PriorityConfiguration.java b/src/main/java/jenkins/advancedqueue/PriorityConfiguration.java new file mode 100644 index 00000000..be20e187 --- /dev/null +++ b/src/main/java/jenkins/advancedqueue/PriorityConfiguration.java @@ -0,0 +1,153 @@ +package jenkins.advancedqueue; + +import hudson.Extension; +import hudson.ExtensionList; +import hudson.model.Describable; +import hudson.model.Descriptor; +import hudson.model.Job; +import hudson.model.RootAction; +import hudson.model.TopLevelItem; +import hudson.model.View; +import hudson.queueSorter.PrioritySorterDefaults; +import hudson.queueSorter.PrioritySorterJobProperty; +import hudson.util.ListBoxModel; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +import javax.servlet.ServletException; + +import jenkins.model.Jenkins; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; + +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; + +@Extension +public class PriorityConfiguration extends Descriptor implements RootAction, Describable { + + private List jobGroups; + + public PriorityConfiguration() { + super(PriorityConfiguration.class); + jobGroups = new LinkedList(); + load(); + Collections.sort(jobGroups, new Comparator() { + public int compare(JobGroup o1, JobGroup o2) { + return o1.id - o2.id; + } + }); + } + + public String getIconFileName() { + return "clock.png"; + } + + public String getDisplayName() { + return "Build Queue Settings"; + } + + public String getUrlName() { + return "advanced-build-queue"; + } + + + public List getJobGroups() { + return jobGroups; + } + + public ListBoxModel getListViewItems() { + ListBoxModel items = new ListBoxModel(); + Collection views = Jenkins.getInstance().getViews(); + for (View view : views) { + items.add(view.getDisplayName(), view.getViewName()); + } + return items; + } + + public ListBoxModel getPriorities() { + ListBoxModel items = PrioritySorterConfiguration.get().doGetPriorityItems(); + return items; + } + + + public void doPriorityConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { + jobGroups = new LinkedList(); + // + String parameter = req.getParameter("json"); + System.out.println(parameter); + JSONObject jobGroupsObject = JSONObject.fromObject(parameter); + JSONArray jsonArray = JSONArray.fromObject(jobGroupsObject.get("jobGroup")); + int id = 0; + for (Object object : jsonArray) { + JSONObject jobGroupObject = JSONObject.fromObject(object); + JobGroup jobGroup = new JobGroup(); + jobGroup.id = id++; + jobGroup.priority = jobGroupObject.getInt("priority"); + jobGroup.view = jobGroupObject.getString("view"); + jobGroup.useJobFilter = jobGroupObject.has("useJobFilter"); + if(jobGroup.useJobFilter) { + JSONObject jsonObject = jobGroupObject.getJSONObject("useJobFilter"); + jobGroup.jobPattern = jsonObject.getString("jobPattern"); + } + jobGroups.add(jobGroup); + } + save(); + } + + public Descriptor getDescriptor() { + return this; + } + + public int getPriority(Job job) { + if(PrioritySorterConfiguration.get().getAllowPriorityOnJobs()) { + AdvancedQueueSorterJobProperty priorityProperty = job.getProperty(AdvancedQueueSorterJobProperty.class); + if (priorityProperty != null && priorityProperty.getUseJobPriority()) { + int priority = priorityProperty.priority; + if(priority == PrioritySorterConfiguration.get().getUseDefaultPriorityPriority()) { + priority = PrioritySorterConfiguration.get().getDefaultPriority(); + } + return priority; + } + } + // + for (JobGroup jobGroup : jobGroups) { + Collection views = Jenkins.getInstance().getViews(); + for (View view : views) { + if(view.getViewName().equals(jobGroup.view)) { + Collection allItems = view.getItems(); + for (TopLevelItem topLevelItem : allItems) { + if(topLevelItem.getName().equals(job.getName())) { + if(jobGroup.jobPattern.trim().isEmpty() || job.getName().matches(jobGroup.jobPattern)) { + int priority = jobGroup.priority; + if(priority == PrioritySorterConfiguration.get().getUseDefaultPriorityPriority()) { + priority = PrioritySorterConfiguration.get().getDefaultPriority(); + } + return priority; + } + } + } + } + } + } + // + return PrioritySorterConfiguration.get().getDefaultPriority(); + } + + static public PriorityConfiguration get() { + ExtensionList extensionList = Jenkins.getInstance().getExtensionList(RootAction.class); + for (RootAction rootAction : extensionList) { + if (rootAction instanceof PriorityConfiguration) { + return (PriorityConfiguration) rootAction; + } + } + throw new RuntimeException(); + } + + +} diff --git a/src/main/java/jenkins/advancedqueue/PriorityQueueSorter.java b/src/main/java/jenkins/advancedqueue/PriorityQueueSorter.java new file mode 100644 index 00000000..947784a8 --- /dev/null +++ b/src/main/java/jenkins/advancedqueue/PriorityQueueSorter.java @@ -0,0 +1,23 @@ +package jenkins.advancedqueue; + +import java.util.List; + +import hudson.Extension; +import hudson.model.Queue.BuildableItem; +import hudson.model.queue.QueueSorter; +import hudson.queueSorter.PrioritySorterQueueSorter; + +@Extension +public class PriorityQueueSorter extends QueueSorter { + + @Override + public void sortBuildableItems(List queue) { + if(PrioritySorterConfiguration.get().getLegacyMode()) { + new PrioritySorterQueueSorter().sortBuildableItems(queue); + } else { + new AdvancedQueueSorter().sortBuildableItems(queue); + } + + } + +} diff --git a/src/main/java/jenkins/advancedqueue/PrioritySorterConfiguration.java b/src/main/java/jenkins/advancedqueue/PrioritySorterConfiguration.java new file mode 100644 index 00000000..596cc419 --- /dev/null +++ b/src/main/java/jenkins/advancedqueue/PrioritySorterConfiguration.java @@ -0,0 +1,271 @@ +package jenkins.advancedqueue; + +import hudson.Extension; +import hudson.ExtensionList; +import hudson.model.AbstractProject; +import hudson.queueSorter.PrioritySorterJobProperty; +import hudson.util.FormValidation; +import hudson.util.ListBoxModel; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.ServletException; + +import jenkins.model.GlobalConfiguration; +import jenkins.model.Jenkins; +import net.sf.json.JSONObject; + +import org.apache.commons.httpclient.methods.GetMethod; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; + +@Extension +public class PrioritySorterConfiguration extends GlobalConfiguration { + + static int PRIORITY_USE_DEFAULT_PRIORITY = -1; + + private boolean legacyMode = false; + private Integer legacyMaxPriority = Integer.MAX_VALUE; + private Integer legacyMinPriority = Integer.MIN_VALUE; + + private boolean allowPriorityOnJobs; + private int numberOfPriorities; + private int defaultPriority; + private SorterStrategy strategy; + + public PrioritySorterConfiguration() { + numberOfPriorities = 5; + defaultPriority = 3; + strategy = SorterStrategy.ABSOLUTE; + allowPriorityOnJobs = true; + checkLegacy(); + if(!getLegacyMode()) { + load(); + } + } + + @Override + public boolean configure(StaplerRequest req, JSONObject json) + throws FormException { + System.out.println(json); + int prevNumberOfPriorities = numberOfPriorities; + numberOfPriorities = json.getInt("numberOfPriorities"); + defaultPriority = json.getInt("defaultPriority"); + allowPriorityOnJobs = json.getBoolean("allowPriorityOnJobs"); + strategy = SorterStrategy.valueOf(json.getString("strategy")); + if(getLegacyMode()) { + Boolean advanced = json.getBoolean("advanced"); + if(advanced) { + convertFromLegacyToAdvanced(); + } + } else { + updatePriorities(prevNumberOfPriorities); + } + save(); + return true; + } + + public boolean getLegacyMode() { + return legacyMode; + } + + public int getNumberOfPriorities() { + return numberOfPriorities; + } + + public int getDefaultPriority() { + return defaultPriority; + } + + public boolean getAllowPriorityOnJobs() { + return allowPriorityOnJobs; + } + + public SorterStrategy getStrategy() { + return strategy; + } + + public int getUseDefaultPriorityPriority() { + return PRIORITY_USE_DEFAULT_PRIORITY; + } + + public ListBoxModel doFillDefaultPriorityItems() { + return internalFillDefaultPriorityItems(getNumberOfPriorities()); + } + + public ListBoxModel doFillStrategyItems() { + ListBoxModel strategies = new ListBoxModel(); + SorterStrategy[] values = SorterStrategy.values(); + for (SorterStrategy sorterStrategy : values) { + strategies.add(sorterStrategy.getDisplayValue(), sorterStrategy.name()); + } + return strategies; + } + + private ListBoxModel internalFillDefaultPriorityItems(int value) { + ListBoxModel items = new ListBoxModel(); + for (int i = 1; i <= value; i++) { + items.add(String.valueOf(i)); + } + return items; + } + + public ListBoxModel doDefaultPriority(@QueryParameter("value") String value) + throws IOException, ServletException { + return doFillDefaultPriorityItems(); + } + + public ListBoxModel doUpdateDefaultPriorityItems(@QueryParameter("value") String strValue) { + int value = getNumberOfPriorities(); + try { + value = Integer.valueOf(strValue); + } catch(NumberFormatException e) { + // Use default value + } + ListBoxModel items = internalFillDefaultPriorityItems(value); + return items; + } + + protected ListBoxModel doGetPriorityItems() { + ListBoxModel items = internalFillDefaultPriorityItems(getNumberOfPriorities()); + items.add(0, new ListBoxModel.Option("-- use default priority --", + String.valueOf(getUseDefaultPriorityPriority()))); + return items; + } + + public FormValidation doCheckNumberOfPriorities(@QueryParameter String value) + throws IOException, ServletException { + if (value.length() == 0) { + return FormValidation.error("Please enter a value."); + } + try { + int intValue = Integer.parseInt(value); + if (intValue <= 0) { + return FormValidation + .error("Please enter a positive numeric value."); + } + } catch (NumberFormatException e) { + return FormValidation + .error("Please enter a positive numeric value."); + } + return FormValidation.ok(); + } + + private void checkLegacy() { + legacyMode = false; + legacyMaxPriority = Integer.MAX_VALUE; + legacyMinPriority = Integer.MIN_VALUE; + + @SuppressWarnings("rawtypes") + List allProjects = Jenkins.getInstance().getAllItems(AbstractProject.class); + for (AbstractProject project : allProjects) { + PrioritySorterJobProperty priority = project.getProperty(PrioritySorterJobProperty.class); + if (priority != null) { + legacyMode = true; + Math.min(legacyMinPriority, priority.priority); + Math.max(legacyMaxPriority, priority.priority); + } + } + } + + private void updatePriorities(int prevNumberOfPriorities) { + @SuppressWarnings("rawtypes") + List allProjects = Jenkins.getInstance().getAllItems(AbstractProject.class); + for (AbstractProject project : allProjects) { + AdvancedQueueSorterJobProperty priorityProperty = project.getProperty(AdvancedQueueSorterJobProperty.class); + if(priorityProperty != null) { + int newPriority = scale(prevNumberOfPriorities, getNumberOfPriorities(), priorityProperty.priority); + try { + project.removeProperty(priorityProperty); + project.addProperty(new AdvancedQueueSorterJobProperty(priorityProperty.getUseJobPriority(), newPriority)); + } catch (IOException e) { + System.out.println("Failed to update Advanced Job Priority To " + project.getName()); + } + } + } + // + List jobGroups = PriorityConfiguration.get().getJobGroups(); + for (JobGroup jobGroup : jobGroups) { + jobGroup.priority = scale(prevNumberOfPriorities, getNumberOfPriorities(), jobGroup.priority); + } + PriorityConfiguration.get().save(); + } + + private void convertFromLegacyToAdvanced() { + // Update legacy range first + checkLegacy(); + if(getLegacyMode()) { + // + @SuppressWarnings("rawtypes") + List allProjects = Jenkins.getInstance().getAllItems(AbstractProject.class); + for (AbstractProject project : allProjects) { + PrioritySorterJobProperty legacyPriorityProperty = project.getProperty(PrioritySorterJobProperty.class); + if (legacyPriorityProperty != null && getAllowPriorityOnJobs()) { + int offset = normalizedOffset(legacyMinPriority, legacyMaxPriority); + int normalized = inverseAndNormalize(legacyMinPriority, legacyMaxPriority, legacyPriorityProperty.priority); + int advancedPriority = scale(legacyMaxPriority + offset, getNumberOfPriorities(), legacyPriorityProperty.priority); + AdvancedQueueSorterJobProperty advancedQueueSorterJobProperty = new AdvancedQueueSorterJobProperty(true, advancedPriority); + try { + project.addProperty(advancedQueueSorterJobProperty); + } catch (IOException e) { + System.out.println("Failed to add Advanced Job Priority To " + project.getName()); + } + } + try { + project.removeProperty(legacyPriorityProperty); + } catch (IOException e) { + System.out.println("Failed to remove Legacy Job Priority From " + project.getName()); + } + } + } + } + + static int normalizedOffset(int min, int max) { + // Normalise from 1- + int offset = 0; + if(min == 0) { + offset = 1; + } + if(min < 1) { + offset = -min + 1; + } + if(min > 1) { + offset = min; + } + return offset; + } + + static int inverseAndNormalize(int min, int max, int value) { + int offset = normalizedOffset(min, max); + min += offset; + max += offset; + value += offset; + // Inverse + value = max - value + 1; + return value; + } + + static int scale(int oldmax, int newmax, int value) { + if(value == PRIORITY_USE_DEFAULT_PRIORITY) { + return PRIORITY_USE_DEFAULT_PRIORITY; + } + float p = ((float) (value - 1) / (float) (oldmax - 1)); + if(p <= 0.5) { + return (int) (Math.floor(p * (float) (newmax - 1))) + 1; + } + return (int) (Math.ceil(p * (float) (newmax - 1))) + 1; + } + + + static public PrioritySorterConfiguration get() { + ExtensionList extensionList = GlobalConfiguration.all(); + for (GlobalConfiguration globalConfiguration : extensionList) { + if (globalConfiguration instanceof PrioritySorterConfiguration) { + return (PrioritySorterConfiguration) globalConfiguration; + } + } + throw new RuntimeException(); + } + +} diff --git a/src/main/java/jenkins/advancedqueue/SorterStrategy.java b/src/main/java/jenkins/advancedqueue/SorterStrategy.java new file mode 100644 index 00000000..96b1368a --- /dev/null +++ b/src/main/java/jenkins/advancedqueue/SorterStrategy.java @@ -0,0 +1,20 @@ +package jenkins.advancedqueue; + +public enum SorterStrategy { + + FIFO("First In First Out"), + ABSOLUTE("Absolute"), + FQ("Fair Queueing"), + WFQ("Weighted Fair Queueing"); + + private final String displayValue; + + SorterStrategy(String displayValue) { + this.displayValue = displayValue; + } + + public String getDisplayValue() { + return displayValue; + } + +} diff --git a/src/main/resources/hudson/queueSorter/PrioritySorterJobProperty/config.jelly b/src/main/resources/hudson/queueSorter/PrioritySorterJobProperty/config.jelly index 7dd5e393..b005a593 100644 --- a/src/main/resources/hudson/queueSorter/PrioritySorterJobProperty/config.jelly +++ b/src/main/resources/hudson/queueSorter/PrioritySorterJobProperty/config.jelly @@ -6,11 +6,13 @@ xmlns:t="/lib/hudson" xmlns:f="/lib/form"> - - - + + + + + diff --git a/src/main/resources/jenkins/advancedqueue/AdvancedQueueSorterJobProperty/config.jelly b/src/main/resources/jenkins/advancedqueue/AdvancedQueueSorterJobProperty/config.jelly new file mode 100644 index 00000000..eb42ca4c --- /dev/null +++ b/src/main/resources/jenkins/advancedqueue/AdvancedQueueSorterJobProperty/config.jelly @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/src/main/resources/jenkins/advancedqueue/AdvancedQueueSorterJobProperty/help-priority.html b/src/main/resources/jenkins/advancedqueue/AdvancedQueueSorterJobProperty/help-priority.html new file mode 100644 index 00000000..409c7531 --- /dev/null +++ b/src/main/resources/jenkins/advancedqueue/AdvancedQueueSorterJobProperty/help-priority.html @@ -0,0 +1,4 @@ +
+ The priority for this job. Priorities are used when all executors are busy to decide which job + in the build queue to run next. Lower the number higher the priority. +
\ No newline at end of file diff --git a/src/main/resources/jenkins/advancedqueue/PriorityConfiguration/index.jelly b/src/main/resources/jenkins/advancedqueue/PriorityConfiguration/index.jelly new file mode 100644 index 00000000..47dc44fb --- /dev/null +++ b/src/main/resources/jenkins/advancedqueue/PriorityConfiguration/index.jelly @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + + +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/jenkins/advancedqueue/PrioritySorterConfiguration/config.jelly b/src/main/resources/jenkins/advancedqueue/PrioritySorterConfiguration/config.jelly new file mode 100644 index 00000000..3cd595be --- /dev/null +++ b/src/main/resources/jenkins/advancedqueue/PrioritySorterConfiguration/config.jelly @@ -0,0 +1,36 @@ + +