Skip to content

Commit 44adeb5

Browse files
Add PauseAction when throttling pipelines
The PauseAction is used by pipeline visualisers (such as pipeline-stage-view) to indicate when a stage has been paused, and for how long is was staged for. This can improve the display of stages timings, as the paused time is removed from the overall stage time. The PauseAction only applies to stages, so only applies when the throttle step is used in conjunction with a pipeline stage.
1 parent 85cb312 commit 44adeb5

File tree

4 files changed

+71
-12
lines changed

4 files changed

+71
-12
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ THE SOFTWARE.
151151
<artifactId>matrix-auth</artifactId>
152152
<scope>test</scope>
153153
</dependency>
154+
<dependency>
155+
<groupId>org.jenkins-ci.plugins.pipeline-stage-view</groupId>
156+
<artifactId>pipeline-stage-view</artifactId>
157+
<version>2.13</version>
158+
<scope>test</scope>
159+
</dependency>
154160
<dependency>
155161
<groupId>org.jenkins-ci.plugins.workflow</groupId>
156162
<artifactId>workflow-basic-steps</artifactId>

src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.jenkinsci.plugins.workflow.graph.StepNode;
4242
import org.jenkinsci.plugins.workflow.graphanalysis.LinearBlockHoppingScanner;
4343
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
44+
import org.jenkinsci.plugins.workflow.support.actions.PauseAction;
4445
import org.jenkinsci.plugins.workflow.support.concurrent.Timeout;
4546
import org.jenkinsci.plugins.workflow.support.steps.ExecutorStepExecution.PlaceholderTask;
4647

@@ -54,14 +55,17 @@ public class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher {
5455
@Deprecated
5556
@Override
5657
public @CheckForNull CauseOfBlockage canTake(Node node, Task task) {
58+
CauseOfBlockage cause = null;
5759
if (Jenkins.getAuthentication().equals(ACL.SYSTEM)) {
58-
return canTakeImpl(node, task);
59-
}
60-
61-
// Throttle-concurrent-builds requires READ permissions for all projects.
62-
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
63-
return canTakeImpl(node, task);
60+
cause = canTakeImpl(node, task);
61+
} else {
62+
// Throttle-concurrent-builds requires READ permissions for all projects.
63+
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
64+
cause = canTakeImpl(node, task);
65+
}
6466
}
67+
updatePauseAction(task, cause);
68+
return cause;
6569
}
6670

6771
private CauseOfBlockage canTakeImpl(Node node, Task task) {
@@ -218,14 +222,17 @@ private boolean shouldBeThrottled(@NonNull Task task, @CheckForNull ThrottleJobP
218222
}
219223

220224
private CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp, List<String> pipelineCategories) {
225+
CauseOfBlockage cause = null;
221226
if (Jenkins.getAuthentication().equals(ACL.SYSTEM)) {
222-
return canRunImpl(task, tjp, pipelineCategories);
223-
}
224-
225-
// Throttle-concurrent-builds requires READ permissions for all projects.
226-
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
227-
return canRunImpl(task, tjp, pipelineCategories);
227+
cause = canRunImpl(task, tjp, pipelineCategories);
228+
} else {
229+
// Throttle-concurrent-builds requires READ permissions for all projects.
230+
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
231+
cause = canRunImpl(task, tjp, pipelineCategories);
232+
}
228233
}
234+
updatePauseAction(task, cause);
235+
return cause;
229236
}
230237

231238
private CauseOfBlockage canRunImpl(Task task, ThrottleJobProperty tjp, List<String> pipelineCategories) {
@@ -695,5 +702,31 @@ private int getMaxConcurrentPerNodeBasedOnMatchingLabels(
695702
return maxConcurrentPerNodeLabeledIfMatch;
696703
}
697704

705+
private void updatePauseAction(Task task, CauseOfBlockage cause) {
706+
if (task instanceof PlaceholderTask) {
707+
PlaceholderTask placeholderTask = (PlaceholderTask) task;
708+
try {
709+
FlowNode flowNode = placeholderTask.getNode();
710+
if (flowNode == null) {
711+
return;
712+
}
713+
714+
if (cause != null) {
715+
if (PauseAction.getCurrentPause(flowNode) == null) {
716+
flowNode.addAction(new PauseAction(cause.getShortDescription()));
717+
}
718+
} else {
719+
if (PauseAction.getCurrentPause(flowNode) != null) {
720+
PauseAction.endCurrentPause(flowNode);
721+
}
722+
}
723+
} catch (IOException | InterruptedException e) {
724+
LOGGER.log(Level.WARNING, "Error setting pause action on pipeline {0}: {1}", new Object[] {
725+
task.getDisplayName(), e
726+
});
727+
}
728+
}
729+
}
730+
698731
private static final Logger LOGGER = Logger.getLogger(ThrottleQueueTaskDispatcher.class.getName());
699732
}

src/test/java/hudson/plugins/throttleconcurrents/TestUtil.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import hudson.model.Computer;
99
import hudson.model.Executor;
1010
import hudson.model.Node;
11+
import hudson.model.Queue;
1112
import hudson.model.queue.CauseOfBlockage;
1213
import hudson.slaves.DumbSlave;
1314
import hudson.slaves.RetentionStrategy;
@@ -17,6 +18,7 @@
1718
import java.util.Set;
1819
import jenkins.model.queue.CompositeCauseOfBlockage;
1920
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
21+
import org.jenkinsci.plugins.workflow.support.actions.PauseAction;
2022
import org.jenkinsci.plugins.workflow.support.steps.ExecutorStepExecution;
2123
import org.junit.rules.TemporaryFolder;
2224
import org.jvnet.hudson.test.JenkinsRule;
@@ -96,6 +98,12 @@ static Set<String> getBlockageReasons(CauseOfBlockage cob) {
9698
}
9799
}
98100

101+
static void hasPauseActionForItem(Queue.Item item) throws Exception {
102+
assertTrue(item.task instanceof ExecutorStepExecution.PlaceholderTask);
103+
ExecutorStepExecution.PlaceholderTask task = (ExecutorStepExecution.PlaceholderTask) item.task;
104+
assertNotNull(task.getNode().getAction(PauseAction.class));
105+
}
106+
99107
static void hasPlaceholderTaskForRun(Node n, WorkflowRun r) throws Exception {
100108
for (Executor exec : n.toComputer().getExecutors()) {
101109
if (exec.getCurrentExecutable() != null) {

src/test/java/hudson/plugins/throttleconcurrents/ThrottleStepTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public void onePerNode() throws Exception {
9595
blockageReasons,
9696
hasItem(Messages._ThrottleQueueTaskDispatcher_MaxCapacityOnNode(1)
9797
.toString()));
98+
TestUtil.hasPauseActionForItem(queuedItem);
9899
assertEquals(1, agent.toComputer().countBusy());
99100
TestUtil.hasPlaceholderTaskForRun(agent, firstJobFirstRun);
100101

@@ -176,6 +177,8 @@ public void multipleCategories() throws Exception {
176177
WorkflowRun thirdJobFirstRun = thirdJob.scheduleBuild2(0).waitForStart();
177178
j.waitForMessage("Still waiting to schedule task", thirdJobFirstRun);
178179
assertFalse(j.jenkins.getQueue().isEmpty());
180+
Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0];
181+
TestUtil.hasPauseActionForItem(queuedItem);
179182
assertEquals(1, firstAgent.toComputer().countBusy());
180183
TestUtil.hasPlaceholderTaskForRun(firstAgent, firstJobFirstRun);
181184

@@ -245,6 +248,9 @@ public void onePerNodeParallel() throws Exception {
245248
j.waitForMessage("Still waiting to schedule task", run1);
246249
j.waitForMessage("Still waiting to schedule task", run2);
247250

251+
Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0];
252+
TestUtil.hasPauseActionForItem(queuedItem);
253+
248254
SemaphoreStep.success("wait-first-branch-a-job/1", null);
249255
SemaphoreStep.waitForStart("wait-first-branch-c-job/1", run1);
250256
assertEquals(1, firstAgent.toComputer().countBusy());
@@ -293,6 +299,8 @@ public void twoTotal() throws Exception {
293299
j.waitForMessage("Still waiting to schedule task", thirdJobFirstRun);
294300
j.jenkins.getQueue().maintain();
295301
assertFalse(j.jenkins.getQueue().isEmpty());
302+
Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0];
303+
TestUtil.hasPauseActionForItem(queuedItem);
296304
assertEquals(1, firstAgent.toComputer().countBusy());
297305
TestUtil.hasPlaceholderTaskForRun(firstAgent, firstJobFirstRun);
298306

@@ -377,6 +385,8 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
377385
WorkflowRun secondJobFirstRun = secondJob.scheduleBuild2(0).waitForStart();
378386
j.waitForMessage("Still waiting to schedule task", secondJobFirstRun);
379387
assertFalse(j.jenkins.getQueue().isEmpty());
388+
Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0];
389+
TestUtil.hasPauseActionForItem(queuedItem);
380390

381391
assertEquals(1, agent.toComputer().countBusy());
382392
for (Executor e : agent.toComputer().getExecutors()) {
@@ -460,6 +470,7 @@ private String getThrottleScript(String jobName, List<String> categories, String
460470
return "throttle(["
461471
+ StringUtils.join(quoted, ", ")
462472
+ "]) {\n"
473+
+ "stage('wait') {\n"
463474
+ " echo 'hi there'\n"
464475
+ " node('"
465476
+ label
@@ -468,6 +479,7 @@ private String getThrottleScript(String jobName, List<String> categories, String
468479
+ jobName
469480
+ "-job'\n"
470481
+ " }\n"
482+
+ "}\n"
471483
+ "}\n";
472484
}
473485

0 commit comments

Comments
 (0)