Skip to content

Commit

Permalink
[JENKINS-41243] Slightly faster tagging of synthetic stages
Browse files Browse the repository at this point in the history
This approach yielded an average time from just before the stage call
to just before actually attaching the tag of 15.4ms over 5 runs, vs
40.8ms over 5 runs with the previous approach. I'm sure there are many
things wrong with this approach, though.
  • Loading branch information
abayer committed Jan 23, 2017
1 parent cc6d4a3 commit c24cef8
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 20 deletions.
Expand Up @@ -27,6 +27,14 @@ package org.jenkinsci.plugins.pipeline.modeldefinition


public class SyntheticStageNames {
public static List<String> preStages() {
return [checkout(), agentSetup(), toolInstall()]
}

public static List<String> postStages() {
return [postBuild()]
}

public static String checkout() {
return "Declarative: Checkout SCM"
}
Expand All @@ -42,8 +50,4 @@ public class SyntheticStageNames {
public static String postBuild() {
return "Declarative: Post Build Actions"
}

public static String notifications() {
return "Declarative: Notifications"
}
}
Expand Up @@ -60,7 +60,9 @@ import org.jenkinsci.plugins.workflow.support.steps.StageStep
import javax.annotation.Nullable
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger;

// TODO: Prune like mad once we have step-in-groovy and don't need these static whitelisted wrapper methods.
/**
Expand Down Expand Up @@ -240,6 +242,15 @@ public class Utils {
execution?.getOwner()?.getListener()?.getLogger()?.println(s)
}

static void attachSyntheticStageGraphListener() {
CpsThread thread = CpsThread.current()
CpsFlowExecution execution = thread?.execution

if (execution != null && !execution.complete) {
execution.addListener(new SyntheticStageGraphListener())
}
}

/**
* Returns true if we're currently nested under a stage.
*
Expand Down Expand Up @@ -295,16 +306,6 @@ public class Utils {
return getTagMetadata(SyntheticStage.class)
}

/**
* Marks the containing stage with this name as a synthetic stage, with the appropriate context.
*
* @param stageName
* @param context
*/
static void markSyntheticStage(String stageName, String context) {
markStageWithTag(stageName, getSyntheticStageMetadata().tagName, context)
}

static void markStageFailedAndContinued(String stageName) {
markStageWithTag(stageName, getStageStatusMetadata().tagName, getStageStatusMetadata().failedAndContinued)
}
Expand Down
@@ -0,0 +1,70 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* 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 org.jenkinsci.plugins.pipeline.modeldefinition;

import org.jenkinsci.plugins.pipeline.SyntheticStage;
import org.jenkinsci.plugins.workflow.actions.TagsAction;
import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode;
import org.jenkinsci.plugins.workflow.flow.GraphListener;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.support.steps.StageStep;

import java.io.IOException;

public final class SyntheticStageGraphListener implements GraphListener {
@Override
public void onNewHead(FlowNode node) {
if (node != null && node instanceof StepStartNode &&
((StepStartNode) node).getDescriptor() instanceof StageStep.DescriptorImpl) {
if (SyntheticStageNames.preStages().contains(node.getDisplayName()) ||
SyntheticStageNames.postStages().contains(node.getDisplayName())) {

if (SyntheticStageNames.preStages().contains(node.getDisplayName())) {
attachTag(node, SyntheticStage.getPre());
}
if (SyntheticStageNames.postStages().contains(node.getDisplayName())) {
attachTag(node, SyntheticStage.getPost());
}
}
}
}

private void attachTag(FlowNode currentNode, String syntheticContext) {
TagsAction tagsAction = currentNode.getAction(TagsAction.class);

if (tagsAction == null) {
tagsAction = new TagsAction();
tagsAction.addTag(SyntheticStage.TAG_NAME, syntheticContext);
currentNode.addAction(tagsAction);
} else if (tagsAction.getTagValue(SyntheticStage.TAG_NAME) == null) {
tagsAction.addTag(SyntheticStage.TAG_NAME, syntheticContext);
try {
currentNode.save();
} catch (IOException e) {
// TODO: should probably do something here? Would prefer to be calling FlowNode.persistSafe() but it's private
}
}
}
}
Expand Up @@ -63,6 +63,7 @@ public class ModelInterpreter implements Serializable {
Throwable firstError

if (root != null) {
Utils.attachSyntheticStageGraphListener()
boolean postBuildRun = false

try {
Expand Down Expand Up @@ -249,7 +250,6 @@ public class ModelInterpreter implements Serializable {
def toolsList = tools.getToolEntries()
if (!Utils.withinAStage()) {
script.stage(SyntheticStageNames.toolInstall()) {
Utils.markSyntheticStage(SyntheticStageNames.toolInstall(), Utils.getSyntheticStageMetadata().pre)
toolEnv = actualToolsInstall(toolsList)
}
} else {
Expand Down Expand Up @@ -408,7 +408,6 @@ public class ModelInterpreter implements Serializable {
Throwable stageError = null
if (root.hasSatisfiedConditions(root.post, script.getProperty("currentBuild"))) {
script.stage(SyntheticStageNames.postBuild()) {
Utils.markSyntheticStage(SyntheticStageNames.postBuild(), Utils.getSyntheticStageMetadata().post)
stageError = runPostConditions(root.post, root.agent, stageError)
}
}
Expand Down
Expand Up @@ -43,7 +43,6 @@ public class DockerPipelineFromDockerfileScript extends AbstractDockerPipelineSc
def img = null
if (!Utils.withinAStage()) {
script.stage(SyntheticStageNames.agentSetup()) {
Utils.markSyntheticStage(SyntheticStageNames.agentSetup(), Utils.getSyntheticStageMetadata().pre)
try {
img = buildImage().call()
} catch (Exception e) {
Expand Down
Expand Up @@ -41,7 +41,6 @@ public class DockerPipelineScript extends AbstractDockerPipelineScript<DockerPip
return {
if (!Utils.withinAStage()) {
script.stage(SyntheticStageNames.agentSetup()) {
Utils.markSyntheticStage(SyntheticStageNames.agentSetup(), Utils.getSyntheticStageMetadata().pre)
try {
script.getProperty("docker").image(describable.image).pull()
} catch (Exception e) {
Expand Down
Expand Up @@ -50,7 +50,6 @@ public class LabelScript extends DeclarativeAgentScript<Label> {
SkipDefaultCheckout skip = (SkipDefaultCheckout)root.options?.options?.get("skipDefaultCheckout")
if (!skip?.isSkipDefaultCheckout() && Utils.hasScmContext(script)) {
script.stage(SyntheticStageNames.checkout()) {
Utils.markSyntheticStage(SyntheticStageNames.checkout(), Utils.getSyntheticStageMetadata().pre)
script.checkout script.scm
}
}
Expand Down

0 comments on commit c24cef8

Please sign in to comment.