├── Jenkinsfile
├── .gitignore
├── src
└── main
│ ├── resources
│ ├── index.jelly
│ └── cz
│ │ └── muni
│ │ └── fi
│ │ └── xkozubi1
│ │ └── ShutdownQueueConfiguration
│ │ ├── help-timeOpenQueueMillis.html
│ │ ├── help-sorterOn.html
│ │ ├── help-permeability.html
│ │ ├── help-periodRunnable.html
│ │ ├── config.jelly
│ │ └── help-strategyType.html
│ └── java
│ └── cz
│ └── muni
│ └── fi
│ └── xkozubi1
│ ├── ShutdownQueueQueueListener.java
│ ├── DefaultSorter.java
│ ├── EstimatedDurationComparator.java
│ ├── ShutdownTask.java
│ ├── ShutdownQueueSorter.java
│ ├── Utils.java
│ ├── ShutdownQueueComputerListener.java
│ ├── ShutdownQueueConfiguration.java
│ └── HandleQuietingDown.java
├── README.md
├── release.properties
├── LICENSE
├── .github
└── workflows
│ └── cd.yml
├── pom.xml
└── pom.xml.releaseBackup
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | buildPlugin()
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | target/
3 | work
4 | *.iml
5 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | This plugin enables short jobs to overtake frozen queue during the shutdown time of long-running tasks.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/cz/muni/fi/xkozubi1/ShutdownQueueConfiguration/help-timeOpenQueueMillis.html:
--------------------------------------------------------------------------------
1 |
2 | Time for which Jenkins quieting down is interrupted. During this time, buildable items have a
3 | chance to be executed. Time is in milliseconds.
4 |
--------------------------------------------------------------------------------
/src/main/resources/cz/muni/fi/xkozubi1/ShutdownQueueConfiguration/help-sorterOn.html:
--------------------------------------------------------------------------------
1 |
2 | If checked, buildable items are sorted by an estimated duration in ascending order.
3 | If plugin is turned off, items are sorted by Jenkins default sorter.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/cz/muni/fi/xkozubi1/ShutdownQueueConfiguration/help-permeability.html:
--------------------------------------------------------------------------------
1 |
2 | If set to X, jobs with an estimated duration * X <= longest remaining executor time are allowed
3 | to overrun frozen queue while shutdown. Allowed values are from interval <0;1>.
4 |
--------------------------------------------------------------------------------
/src/main/resources/cz/muni/fi/xkozubi1/ShutdownQueueConfiguration/help-periodRunnable.html:
--------------------------------------------------------------------------------
1 |
2 | Every given X seconds following conditions are checked:
3 | if Jenkins is in shutdown mode
4 | if the plugin is on
5 | if the buildable queue is not empty
6 | If these conditions are satisfied, appropriate logic is being done.
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # shutdown-queue
2 |
3 | This plugin ensures that short jobs can overtake frozen queue during the shutdown time of long-running tasks.
4 |
5 | In shutdown mode, no new jobs are scheduled. But if there is last 24h taking job, and queue is full of short jobs, it is wasted time to not run them.
6 | This plugin, allows to do exactly this. Set acceptance treshold, set strategy, and no longer waste HW cycles in long shutdown mode.
7 |
--------------------------------------------------------------------------------
/src/main/java/cz/muni/fi/xkozubi1/ShutdownQueueQueueListener.java:
--------------------------------------------------------------------------------
1 | package cz.muni.fi.xkozubi1;
2 |
3 | import hudson.model.Queue;
4 | import hudson.model.queue.QueueListener;
5 |
6 |
7 | public class ShutdownQueueQueueListener extends QueueListener {
8 | @Override
9 | public void onEnterWaiting(Queue.WaitingItem wi) {
10 | if (Utils.isCanAddToQueue()) {
11 | super.onEnterWaiting(wi);
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/cz/muni/fi/xkozubi1/DefaultSorter.java:
--------------------------------------------------------------------------------
1 | package cz.muni.fi.xkozubi1;
2 |
3 | import hudson.model.Queue;
4 | import hudson.model.queue.AbstractQueueSorterImpl;
5 |
6 | import java.io.Serializable;
7 | import java.util.List;
8 |
9 |
10 | /**
11 | * Imitates Jenkins default sorter. Sorts items based on their getQueueSince().
12 | */
13 | public class DefaultSorter extends AbstractQueueSorterImpl implements Serializable {
14 |
15 | @Override
16 | public void sortBuildableItems(List list) {
17 | list.sort(this);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/cz/muni/fi/xkozubi1/EstimatedDurationComparator.java:
--------------------------------------------------------------------------------
1 | package cz.muni.fi.xkozubi1;
2 |
3 | import hudson.model.Queue;
4 |
5 | import java.io.Serializable;
6 | import java.util.Comparator;
7 |
8 |
9 | /**
10 | * Compares Queue.BuildableItems by getEstimatedDuration().
11 | * Used with ShutdownQueueSorter.
12 | * @author Dominik Kozubik
13 | */
14 | public class EstimatedDurationComparator implements Comparator, Serializable {
15 |
16 | @Override
17 | public int compare(Queue.BuildableItem buildableItem0, Queue.BuildableItem buildableItem1) {
18 |
19 | if (buildableItem0.task.getEstimatedDuration() < buildableItem1.task.getEstimatedDuration()) {
20 | return -1;
21 | }
22 |
23 | if (buildableItem0.task.getEstimatedDuration() > buildableItem1.task.getEstimatedDuration()) {
24 | return 1;
25 | }
26 |
27 | return 0;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/resources/cz/muni/fi/xkozubi1/ShutdownQueueConfiguration/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/release.properties:
--------------------------------------------------------------------------------
1 | #release configuration
2 | #Sat Dec 25 11:48:22 CET 2021
3 | projectVersionPolicyId=default
4 | remoteTagging=true
5 | scm.commentPrefix=[maven-release-plugin]
6 | project.scm.io.jenkins.plugins\:shutdown-queue.connection=scm\:git\:git\://github.com/jenkinsci/${project.artifactId}-plugin.git
7 | project.scm.io.jenkins.plugins\:shutdown-queue.developerConnection=scm\:git\:git@github.com\:jenkinsci/${project.artifactId}-plugin.git
8 | project.scm.io.jenkins.plugins\:shutdown-queue.tag=HEAD
9 | completedPhase=end-release
10 | scm.url=scm\:git\:git@github.com\:jenkinsci/shutdown-queue-plugin.git
11 | exec.additionalArguments=
12 | scm.tagNameFormat=@{project.artifactId}-@{project.version}
13 | pushChanges=true
14 | scm.tag=shutdown-queue-1.2
15 | project.rel.io.jenkins.plugins\:shutdown-queue=1.2
16 | exec.snapshotReleasePluginAllowed=false
17 | project.dev.io.jenkins.plugins\:shutdown-queue=1.3-SNAPSHOT
18 | preparationGoals=clean install
19 | project.scm.io.jenkins.plugins\:shutdown-queue.url=https\://github.com/jenkinsci/${project.artifactId}-plugin
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 dkozubik
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/resources/cz/muni/fi/xkozubi1/ShutdownQueueConfiguration/help-strategyType.html:
--------------------------------------------------------------------------------
1 |
2 |
Note:
3 |
"buildable overflowing": Shutdown mode is canceled for "Open queue time". During this time, buildable items can
4 | left the buildable queue in order to be executed. After "Open queue time", shutdown mode is turned back on.
5 |
6 |
7 |
8 |
9 | Copying strategy
10 | Creates a copy of the buildable queue and chooses items that satisfy conditions to overtake the frozen queue
11 | (whitelist). If there are any, the original buildable queue is cleared. After "buildable overflowing", items
12 | except whitelist are put back into the queue.
13 |
14 |
15 |
16 | Remove longer strategy
17 | Simply removes tasks from the queue whose estimated duration does not satisfy condition before
18 | "buildable overflowing".
19 |
20 |
21 |
22 | Sort and remove longer strategy
23 | Sorts the queue by an estimated duration and a rest is the same as Remove longer strategy.
24 | Due to sorting at the beginning, more tasks might be executed in the same amount of time.
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/java/cz/muni/fi/xkozubi1/ShutdownTask.java:
--------------------------------------------------------------------------------
1 | package cz.muni.fi.xkozubi1;
2 |
3 | import hudson.model.Computer;
4 | import jenkins.model.Jenkins;
5 |
6 | import java.util.logging.Logger;
7 |
8 |
9 | /**
10 | * Plugin runnable.
11 | * If the plugin is on, Jenkins is going to shut down, and the buildable queue is not empty, calls a method
12 | * handleLogic() from HandleQuietingDown which takes care of the rest of the plugin's logic.
13 | *
14 | * @author Dominik Kozubik
15 | */
16 | public class ShutdownTask implements Runnable {
17 | private HandleQuietingDown handleQuietingDown;
18 | private static Logger logger = Logger.getLogger(ShutdownTask.class.getName());
19 |
20 | public ShutdownTask(Computer computer) {
21 | handleQuietingDown = new HandleQuietingDown(computer);
22 | }
23 |
24 | @Override
25 | public void run() {
26 | if (ShutdownQueueConfiguration.getInstance().getPluginOn() &&
27 | Jenkins.get().isQuietingDown() &&
28 | Jenkins.get().getQueue().getBuildableItems().size() > 0) {
29 | try {
30 | handleQuietingDown.handleLogic();
31 | } catch (InterruptedException e) {
32 | logger.warning("Interrupted exception occurred while doing shutdown-queue plugin logic.");
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/cz/muni/fi/xkozubi1/ShutdownQueueSorter.java:
--------------------------------------------------------------------------------
1 | package cz.muni.fi.xkozubi1;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 | import hudson.model.Queue;
5 | import hudson.model.queue.QueueSorter;
6 | import jenkins.model.Jenkins;
7 |
8 | import java.util.Collections;
9 | import java.util.List;
10 |
11 |
12 | /**
13 | * Plugin's sorter. It is used if sorterOn or strategy type "Sort and remove longer" is on.
14 | * Also preserves default Jenkins sorter.
15 | */
16 | public class ShutdownQueueSorter extends QueueSorter {
17 |
18 | private final QueueSorter originalQueueSorter;
19 | private final EstimatedDurationComparator comparator;
20 |
21 | public ShutdownQueueSorter(QueueSorter sorter) {
22 | originalQueueSorter = sorter;
23 | comparator = new EstimatedDurationComparator();
24 | }
25 |
26 | @Override
27 | public void sortBuildableItems(List buildableItems) {
28 | if (originalQueueSorter != null) {
29 | originalQueueSorter.sortBuildableItems(buildableItems);
30 | }
31 |
32 | Collections.sort(buildableItems, comparator);
33 | }
34 |
35 | @SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "filled async, but may be correct")
36 | void reset() {
37 | sortBuildableItems(Jenkins.getInstanceOrNull().getQueue().getBuildableItems());
38 | Queue.getInstance().maintain();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yml:
--------------------------------------------------------------------------------
1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins
2 |
3 | name: cd
4 | on:
5 | workflow_dispatch:
6 | check_run:
7 | types:
8 | - completed
9 |
10 | jobs:
11 | validate:
12 | runs-on: ubuntu-latest
13 | outputs:
14 | should_release: ${{ steps.verify-ci-status.outputs.result == 'success' && steps.interesting-categories.outputs.interesting == 'true' }}
15 | steps:
16 | - name: Verify CI status
17 | uses: jenkins-infra/verify-ci-status-action@v1.2.0
18 | id: verify-ci-status
19 | with:
20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 | output_result: true
22 |
23 | - name: Release Drafter
24 | uses: release-drafter/release-drafter@v5
25 | if: steps.verify-ci-status.outputs.result == 'success'
26 | with:
27 | name: next
28 | tag: next
29 | version: next
30 | env:
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 |
33 | - name: Check interesting categories
34 | uses: jenkins-infra/interesting-category-action@v1.0.0
35 | id: interesting-categories
36 | if: steps.verify-ci-status.outputs.result == 'success'
37 | with:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 |
40 | release:
41 | runs-on: ubuntu-latest
42 | needs: [validate]
43 | if: needs.validate.outputs.should_release == 'true'
44 | steps:
45 | - name: Check out
46 | uses: actions/checkout@v2.3.4
47 | with:
48 | fetch-depth: 0
49 | - name: Set up JDK 8
50 | uses: actions/setup-java@v3.0.0
51 | with:
52 | distribution: 'temurin'
53 | java-version: 8
54 | - name: Release
55 | uses: jenkins-infra/jenkins-maven-cd-action@v1.2.0
56 | with:
57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
59 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
60 |
--------------------------------------------------------------------------------
/src/main/java/cz/muni/fi/xkozubi1/Utils.java:
--------------------------------------------------------------------------------
1 | package cz.muni.fi.xkozubi1;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 | import hudson.model.queue.QueueSorter;
5 | import jenkins.model.Jenkins;
6 |
7 |
8 | /**
9 | * @author Dominik Kozubik
10 | */
11 | public class Utils {
12 |
13 |
14 | private static boolean canAddToQueue = true;
15 |
16 | public static void setCanAddToQueue(boolean canAddToQueue) {
17 | Utils.canAddToQueue = canAddToQueue;
18 | }
19 |
20 | public static boolean isCanAddToQueue() {
21 | return canAddToQueue;
22 | }
23 |
24 | /**
25 | * Based on input condition, sets QueueSorter either to ShutdownQueueSorter or the original sorter.
26 | * @param isSorterOn boolean value from the settings option "Sorter on"
27 | */
28 | @SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "filled async, but may be correct")
29 | public static void handleSorterOn(boolean isSorterOn) {
30 | if (isSorterOn) {
31 | QueueSorter originalSorter = Jenkins.getInstanceOrNull().getQueue().getSorter();
32 | if (originalSorter == null) {
33 | originalSorter = new DefaultSorter();
34 | }
35 | Jenkins.getInstanceOrNull().getQueue().setSorter(new ShutdownQueueSorter(originalSorter));
36 |
37 | } else {
38 | doReset();
39 | }
40 | }
41 |
42 | /**
43 | * Sets QueueSorter to DefaultSorter if it was ShutdownQueueSorter before.
44 | */
45 | @SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "filled async, but may be correct")
46 | public static void doReset() {
47 | QueueSorter sorter = Jenkins.getInstanceOrNull().getQueue().getSorter();
48 | if (sorter instanceof ShutdownQueueSorter) {
49 | Jenkins.getInstanceOrNull().getQueue().setSorter(new DefaultSorter());
50 | ((ShutdownQueueSorter) sorter).reset();
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/cz/muni/fi/xkozubi1/ShutdownQueueComputerListener.java:
--------------------------------------------------------------------------------
1 | package cz.muni.fi.xkozubi1;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 | import hudson.Extension;
5 | import hudson.model.Computer;
6 | import hudson.model.TaskListener;
7 | import hudson.slaves.ComputerListener;
8 |
9 | import java.util.concurrent.Executors;
10 | import java.util.concurrent.ScheduledExecutorService;
11 | import java.util.concurrent.ScheduledFuture;
12 | import java.util.concurrent.TimeUnit;
13 | import java.util.logging.Logger;
14 |
15 |
16 | /**
17 | * Plugin initiation
18 | * @author Dominik Kozubik
19 | */
20 | @Extension
21 | public class ShutdownQueueComputerListener extends ComputerListener {
22 | private static Logger logger = Logger.getLogger(ShutdownQueueConfiguration.class.getName());
23 | private static ScheduledExecutorService executorService;
24 | private static ShutdownTask shutdownTask;
25 | private static ScheduledFuture> futureTask;
26 |
27 | @Override
28 | public void onOnline(Computer c, TaskListener listener) {
29 | onTemporarilyOnline(c);
30 | }
31 |
32 | @Override
33 | @SuppressFBWarnings(value = {"ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD"}, justification = "intentional global state keepers")
34 | public void onTemporarilyOnline(Computer computer) {
35 | executorService = Executors.newSingleThreadScheduledExecutor();
36 | shutdownTask = new ShutdownTask(computer);
37 | changeScheduleInterval(ShutdownQueueConfiguration.getInstance().getPeriodRunnable());
38 |
39 | logger.info("Shutdown-queue plugin thread has started.");
40 | }
41 |
42 | /**
43 | * Changes delay value of a periodic callable shutdownTask.
44 | * @param time seconds
45 | */
46 | static synchronized void changeScheduleInterval(long time)
47 | {
48 | if(time > 0)
49 | {
50 | if (futureTask != null)
51 | {
52 | futureTask.cancel(false);
53 | }
54 |
55 | futureTask = executorService.scheduleAtFixedRate(shutdownTask, 1, time, TimeUnit.SECONDS);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.jenkins-ci.plugins
6 | plugin
7 | 4.12
8 |
9 |
10 | io.jenkins.plugins
11 | shutdown-queue
12 | 1.4-SNAPSHOT
13 | hpi
14 |
15 | 2.235.1
16 | 8
17 | true
18 |
19 |
20 | Shutdown Queue
21 | Enables short jobs to overtake frozen queue during the shutdown time of long-running tasks.
22 |
23 |
24 | MIT License
25 | https://opensource.org/licenses/MIT
26 |
27 |
28 |
29 |
30 |
31 | dkozubik
32 | Dominik Kozubik
33 | xkozubi1@fi.muni.cz
34 |
35 |
36 |
37 |
38 | https://github.com/jenkinsci/${project.artifactId}-plugin
39 |
40 | scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git
41 | scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git
42 | https://github.com/jenkinsci/${project.artifactId}-plugin
43 | HEAD
44 |
45 |
46 |
47 |
48 | repo.jenkins-ci.org
49 | https://repo.jenkins-ci.org/public/
50 |
51 |
52 |
53 |
54 | repo.jenkins-ci.org
55 | https://repo.jenkins-ci.org/public/
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/pom.xml.releaseBackup:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.jenkins-ci.plugins
6 | plugin
7 | 4.12
8 |
9 |
10 | io.jenkins.plugins
11 | shutdown-queue
12 | 1.2-SNAPSHOT
13 | hpi
14 |
15 | 2.235.1
16 | 8
17 | true
18 |
19 |
20 | Shutdown Queue
21 | Enables short jobs to overtake frozen queue during the shutdown time of long-running tasks.
22 |
23 |
24 | MIT License
25 | https://opensource.org/licenses/MIT
26 |
27 |
28 |
29 |
30 |
31 | dkozubik
32 | Dominik Kozubik
33 | xkozubi1@fi.muni.cz
34 |
35 |
36 |
37 |
38 | https://github.com/jenkinsci/${project.artifactId}-plugin
39 |
40 | scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git
41 | scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git
42 | https://github.com/jenkinsci/${project.artifactId}-plugin
43 | HEAD
44 |
45 |
46 |
47 |
48 | repo.jenkins-ci.org
49 | https://repo.jenkins-ci.org/public/
50 |
51 |
52 |
53 |
54 | repo.jenkins-ci.org
55 | https://repo.jenkins-ci.org/public/
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/main/java/cz/muni/fi/xkozubi1/ShutdownQueueConfiguration.java:
--------------------------------------------------------------------------------
1 | package cz.muni.fi.xkozubi1;
2 |
3 | import hudson.Extension;
4 | import hudson.util.FormValidation;
5 | import hudson.util.ListBoxModel;
6 | import jenkins.model.GlobalConfiguration;
7 | import net.sf.json.JSONObject;
8 | import org.kohsuke.stapler.QueryParameter;
9 | import org.kohsuke.stapler.StaplerRequest;
10 |
11 | import java.util.logging.Logger;
12 |
13 |
14 | /**
15 | * Settings for the plugin. Can be found in Jenkins global settings.
16 | * @author Dominik Kozubik
17 | */
18 | @Extension
19 | public class ShutdownQueueConfiguration extends GlobalConfiguration {
20 | private static Logger logger = Logger.getLogger(ShutdownQueueConfiguration.class.getName());
21 |
22 | private boolean pluginOn;
23 | private boolean sorterOn;
24 | private String strategyOption;
25 | private double permeability;
26 | private long periodRunnable;
27 | private long timeOpenQueueMillis;
28 |
29 | public ShutdownQueueConfiguration() {
30 | load();
31 | setDefaultValues();
32 | }
33 |
34 | public boolean getPluginOn() {
35 | return pluginOn;
36 | }
37 |
38 | public String getStrategyOption() {
39 | return strategyOption;
40 | }
41 |
42 | public long getPeriodRunnable() {
43 | return periodRunnable;
44 | }
45 |
46 | public double getPermeability() {
47 | return permeability;
48 | }
49 |
50 | public long getTimeOpenQueueMillis() {
51 | return timeOpenQueueMillis;
52 | }
53 |
54 | @Override
55 | public boolean configure(StaplerRequest staplerRequest, JSONObject json) throws FormException {
56 | pluginOn = json.optBoolean("pluginOn", true);
57 | sorterOn = json.optBoolean("sorterOn", false);
58 | strategyOption = json.optString("strategyType", "copying");
59 | periodRunnable = json.optLong("periodRunnable", 10);
60 | permeability = json.optDouble("permeability", 0.7);
61 | timeOpenQueueMillis = json.optLong("timeOpenQueueMillis", 500);
62 |
63 | logger.info("\nShutdown-queue plugin CONFIGURATION\n" +
64 | "\nplugin: " + pluginOn +
65 | "\nsorter: " + sorterOn +
66 | "\nstrategy: " + strategyOption +
67 | "\nperiod: " + periodRunnable +
68 | "\npermeability: " + permeability +
69 | "\ntimeOpenQueueMillis " + timeOpenQueueMillis
70 | );
71 |
72 | if (!pluginOn) {
73 | Utils.doReset();
74 | }
75 |
76 | Utils.handleSorterOn(sorterOn);
77 | save();
78 | ShutdownQueueComputerListener.changeScheduleInterval(periodRunnable);
79 |
80 | return super.configure(staplerRequest, json);
81 | }
82 |
83 | public FormValidation doCheckPermeability(@QueryParameter String value) {
84 | try {
85 | double valueD = Double.parseDouble(value);
86 | if (valueD < 0 || valueD > 1) {
87 | return FormValidation.error("Please, enter a number in the interval <0;1>.");
88 | }
89 | return FormValidation.ok();
90 |
91 | } catch (NumberFormatException e) {
92 | return FormValidation.error("Please, enter an integer.");
93 | }
94 | }
95 |
96 | public FormValidation doCheckPeriodRunnable(@QueryParameter String value) {
97 | try {
98 | long valueI = Long.parseLong(value);
99 | if (valueI < 0) {
100 | return FormValidation.error("Please, enter a positive integer.");
101 | }
102 | return FormValidation.ok();
103 |
104 | } catch (NumberFormatException e) {
105 | return FormValidation.error("Please, enter an integer.");
106 | }
107 | }
108 |
109 | public ListBoxModel doFillStrategyTypeItems() {
110 | ListBoxModel items = new ListBoxModel();
111 |
112 | items.add("Copying", "copying");
113 | items.add("Remove longer", "removeLonger");
114 | items.add("Sort and remove longer", "sortRemoveLonger");
115 |
116 | return items;
117 | }
118 |
119 | private void setDefaultValues()
120 | {
121 | pluginOn = true;
122 | sorterOn = false;
123 | strategyOption = "copying";
124 | permeability = 0.6;
125 | periodRunnable = 10;
126 | timeOpenQueueMillis = 500;
127 | }
128 |
129 | public static ShutdownQueueConfiguration getInstance() {
130 | return GlobalConfiguration.all().get(ShutdownQueueConfiguration.class);
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/java/cz/muni/fi/xkozubi1/HandleQuietingDown.java:
--------------------------------------------------------------------------------
1 | package cz.muni.fi.xkozubi1;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 | import hudson.model.Computer;
5 | import hudson.model.Queue;
6 | import jenkins.model.Jenkins;
7 |
8 | import java.util.Collection;
9 | import java.util.List;
10 | import java.util.OptionalLong;
11 | import java.util.logging.Logger;
12 | import java.util.stream.Collectors;
13 |
14 |
15 | /**
16 | * Based on a strategy type, appropriate logic is performed.
17 | * @author Dominik Kozubik
18 | */
19 | public class HandleQuietingDown {
20 | private final Computer computer;
21 | private final Jenkins jenkinsInstance;
22 | private static Logger logger = Logger.getLogger(HandleQuietingDown.class.getName());
23 |
24 | HandleQuietingDown(Computer computer) {
25 | this.computer = computer;
26 | this.jenkinsInstance = Jenkins.getInstanceOrNull();
27 | }
28 |
29 | /**
30 | * The main function, calls other methods based on obtained strategy type if there is any idle executor.
31 | * @throws InterruptedException in case the method is interrupted
32 | */
33 | public void handleLogic() throws InterruptedException {
34 | long idleExecutorsCount = getIdleExecutorsCount();
35 | if (idleExecutorsCount == 0) {
36 | logger.warning("No idle executor is available");
37 | return;
38 | }
39 |
40 | long longestRemainingTime = getLongestExecutorRemainingTime();
41 | if (longestRemainingTime <= -1L) { // check whether all executors are idle, i.e. longest task finished
42 | logger.info("The longest task has finished.");
43 | return;
44 | }
45 |
46 | double ratio = ShutdownQueueConfiguration.getInstance().getPermeability();
47 | String strategyOption = ShutdownQueueConfiguration.getInstance().getStrategyOption();
48 |
49 | if (strategyOption.equals("copying")) {
50 | logger.info("Performing strategy.");
51 | strategyCopying(idleExecutorsCount, longestRemainingTime, ratio);
52 | }
53 | else if (strategyOption.equals("removeLonger")) {
54 | logger.info("Performing strategy");
55 | strategyRemoveLonger(longestRemainingTime, ratio);
56 | }
57 | else if (strategyOption.equals("sortRemoveLonger")) {
58 | logger.info("Performing strategy");
59 |
60 | Utils.handleSorterOn(true);
61 | jenkinsInstance.getQueue().getSorter().sortBuildableItems(jenkinsInstance.getQueue().getBuildableItems());
62 |
63 | // first it sorts buildables and then does the same as remove longer strategy
64 | strategyRemoveLonger(longestRemainingTime, ratio);
65 | Utils.doReset();
66 | }
67 | }
68 |
69 | /**
70 | * Performs Copying strategy.
71 | * @param idleExecutorsCount the number of idle executors
72 | * @param longestExecutorTime executor's longest estimated duration
73 | * @param ratio permeability value from the settings
74 | * @throws InterruptedException
75 | */
76 | private void strategyCopying(long idleExecutorsCount, long longestExecutorTime, double ratio) throws InterruptedException {
77 | List whiteListIDs = getWhiteListIDs(longestExecutorTime, ratio, idleExecutorsCount);
78 |
79 | if (whiteListIDs.size() == 0) {
80 | logger.info("No tasks satisfy condition.");
81 | return;
82 | }
83 |
84 | Queue.BuildableItem[] buildablesCopy = getBuildablesCopy();
85 | logBuildablesCopy(buildablesCopy);
86 |
87 | logger.info("Executor longest remaining time: " + longestExecutorTime +
88 | "\nIdle executors count: " + idleExecutorsCount +
89 | "\nwhiteListIDs count: " + whiteListIDs.size());
90 |
91 | cancelTasksButWhitelist(whiteListIDs);
92 | cancelAndDoQuietDown(ShutdownQueueConfiguration.getInstance().getTimeOpenQueueMillis());
93 | putTasksBackToQueueBut(buildablesCopy, whiteListIDs);
94 | }
95 |
96 | /**
97 | * This method performs "remove longer strategy"
98 | * @param longestExecutorTime executor's longest estimated duration
99 | * @param ratio permeability value from the settings
100 | * @throws InterruptedException
101 | */
102 | private void strategyRemoveLonger(long longestExecutorTime, double ratio) throws InterruptedException {
103 | if (longestExecutorTime != 0) {
104 | cancelTasksLongerThan(longestExecutorTime, ratio);
105 | }
106 |
107 | cancelAndDoQuietDown(ShutdownQueueConfiguration.getInstance().getTimeOpenQueueMillis());
108 | }
109 |
110 | /**
111 | * @return the longest estimated remaining time of all executors
112 | */
113 | private long getLongestExecutorRemainingTime() {
114 | OptionalLong maxRemainingTime = computer.getExecutors()
115 | .stream()
116 | .mapToLong(e -> e.getEstimatedRemainingTimeMillis())
117 | .max();
118 |
119 | return maxRemainingTime.isPresent() ? maxRemainingTime.getAsLong() : -1L;
120 | }
121 |
122 | /**
123 | * @return the number of idle executors
124 | */
125 | private long getIdleExecutorsCount() {
126 | return computer.getExecutors()
127 | .stream()
128 | .filter(e -> e.isIdle())
129 | .count();
130 | }
131 |
132 | /**
133 | * Cancels tasks from buildable queue which estimated duration * ratio is longer than longestExecutorTime
134 | * @param longestExecutorTime executor's longest estimated duration
135 | * @param ratio "permeability" value from the settings
136 | */
137 | private void cancelTasksLongerThan(long longestExecutorTime, double ratio) {
138 | Queue queue = Jenkins.get().getQueue();
139 |
140 | queue.getBuildableItems()
141 | .stream()
142 | .filter(b -> b.task.getEstimatedDuration() * ratio >= longestExecutorTime)
143 | .forEach(buildableItem -> {
144 | logCancelTaskInfo(buildableItem);
145 | queue.cancel(buildableItem);
146 | });
147 | }
148 |
149 | /**
150 | * Cancels tasks from the buildable queue which are not in the whitelist
151 | * @param whitelistIDs list of BuildableItems' IDs which are allowed to stay in the BuildableQueue
152 | */
153 | private void cancelTasksButWhitelist(List whitelistIDs) {
154 | Queue queue = Jenkins.get().getQueue();
155 |
156 | queue.getBuildableItems()
157 | .stream()
158 | .filter(b -> !whitelistIDs.contains(b.getId()))
159 | .forEach(b -> queue.cancel(b));
160 | }
161 |
162 | /**
163 | * @param longestExecutorTime executor's longest estimated duration
164 | * @param ratio permeability value from the settings
165 | * @param idleExecutorsCount the number of idle executors
166 | * @returns list of BuildableItems' IDs which satisfy conditions. Length of the list is less than or equal to the
167 | * number of idle executors.
168 | */
169 | private List getWhiteListIDs(long longestExecutorTime, double ratio, long idleExecutorsCount) {
170 | return Jenkins.get().getQueue().getBuildableItems()
171 | .stream()
172 | .filter(b -> b.task.getEstimatedDuration() != -1L
173 | && b.task.getEstimatedDuration() * ratio < longestExecutorTime)
174 | .map(b -> b.getId())
175 | .limit(idleExecutorsCount)
176 | .collect(Collectors.toList());
177 | }
178 |
179 | /**
180 | * Logs info about an item to be cancelled
181 | * @param item Queue.BuildableItem
182 | */
183 | private void logCancelTaskInfo(Queue.BuildableItem item) {
184 | logger.info("Canceling task " + item.task.getName() + " with an estimated duration "
185 | + item.task.getEstimatedDuration());
186 | }
187 |
188 | /**
189 | * @return array copy of BuildableItems
190 | */
191 | private Queue.BuildableItem[] getBuildablesCopy() {
192 | Collection buildables = Jenkins.get().getQueue().getBuildableItems();
193 | return buildables.toArray(new Queue.BuildableItem[buildables.size()]);
194 | }
195 |
196 | /**
197 | * logs info about items
198 | * @param items array of Queue.BuildableItem
199 | */
200 | private void logBuildablesCopy(Queue.BuildableItem[] items) {
201 | logger.info("BUILDABLE ITEMS COPY\n");
202 | for (Queue.BuildableItem item : items) {
203 | logger.info("Task name: " + item.task.getName() + " ID: " + item.getId());
204 | }
205 |
206 | //could use Arrays.stream()...
207 | }
208 |
209 | /**
210 | * Cancels quieting down, waits for timeMillis and then starts quieting down again
211 | * @param timeMillis "Open queue time" from the settings
212 | * @throws InterruptedException
213 | */
214 | @SuppressFBWarnings(value = {"ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD"}, justification = "intentional global state keepers")
215 | private void cancelAndDoQuietDown(long timeMillis) throws InterruptedException {
216 | if (jenkinsInstance.getQueue().getBuildableItems().size() > 0)
217 | {
218 | Utils.setCanAddToQueue(false);
219 | logger.warning("Canceling shutdown for " + timeMillis + " milliseconds.");
220 | jenkinsInstance.doCancelQuietDown();
221 | Thread.sleep(timeMillis);
222 | logger.warning("Start shutdown.");
223 | jenkinsInstance.doQuietDown();
224 | }
225 |
226 | Utils.setCanAddToQueue(false);
227 | }
228 |
229 | /**
230 | * schedules BuildableItems' tasks which are not in whiteListIDs and were cancelled before
231 | * @param buildablesCopy array copy of BuildableItems
232 | * @param whiteListIDs list of BuildableItems' IDs which are allowed to stay in the BuildableQueue
233 | */
234 | private void putTasksBackToQueueBut(Queue.BuildableItem[] buildablesCopy, List whiteListIDs) {
235 | for (Queue.BuildableItem item : buildablesCopy) {
236 | if (!whiteListIDs.contains(item.getId())) {
237 | logger.info("Adding " + item.task.getName() + " back to the queue.");
238 | jenkinsInstance.getQueue().schedule(item.task, 0);
239 | }
240 | }
241 | }
242 | }
243 |
--------------------------------------------------------------------------------