Skip to content

Commit

Permalink
Add node labels to Jenkins Pipelines traces (#212)
Browse files Browse the repository at this point in the history
* Add node labels to Jenkins traces

* Minor issues

* Remove unnecesary imports
  • Loading branch information
drodriguezhdez authored Jun 14, 2021
1 parent 6edc1fc commit 347e090
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,25 @@ public static String getNodeName(Computer computer){
}
}


@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public static Set<String> getNodeLabels(Computer computer) {
Set<LabelAtom> labels;
try {
labels = computer.getNode().getAssignedLabels();
} catch (Exception e){
logger.fine("Could not retrieve labels: " + e.getMessage());
return Collections.emptySet();
}

final Set<String> labelsStr = new HashSet<>();
for(final LabelAtom label : labels) {
labelsStr.add(label.getName());
}

return labelsStr;
}

public static String getUserId() {
User user = User.current();
if (user == null) {
Expand Down Expand Up @@ -771,4 +790,31 @@ public static String toISO8601(Date date) {
return sdf.format(date);
}

/**
* Returns a JSON array string based on the set.
* @param set
* @return json array string
*/
public static String toJson(final Set<String> set) {
if(set == null || set.isEmpty()) {
return "";
}

// We want to avoid using Json libraries cause
// may cause incompatibilities on different Jenkins versions.
final StringBuilder sb = new StringBuilder();
sb.append("[");
int index = 1;
for(String val : set) {
sb.append("\"").append(val).append("\"");
if(index < set.size()) {
sb.append(",");
}
index += 1;
}
sb.append("]");

return sb.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void notifyOfNewStep(@Nonnull Step step, @Nonnull StepContext context) {
if(blockStartNodes.hasNext()) {
final FlowNode candidate = blockStartNodes.next();
if("Start of Pipeline".equals(candidate.getDisplayName())) {
run.addAction(new PipelineNodeInfoAction(stepData.getNodeName() != null ? stepData.getNodeName() : "master"));
run.addAction(new PipelineNodeInfoAction(stepData.getNodeName() != null ? stepData.getNodeName() : "master", stepData.getNodeLabels()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ private void completeInformation(final List<BuildPipelineNode> nodes, final Buil
final BuildPipelineNode executableChildNode = searchExecutableChildNode(node);
if(executableChildNode != null) {
node.setPropagatedNodeName(executableChildNode.getNodeName());
node.setPropagatedNodeLabels(executableChildNode.getNodeLabels());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

Expand Down Expand Up @@ -68,7 +69,9 @@ public String getBuildLevel() {
private Map<String, String> envVars = new HashMap<>();
private String workspace;
private String nodeName;
private Set<String> nodeLabels;
private String propagatedNodeName;
private Set<String> propagatedNodeLabels;
private String nodeHostname;
private AnnotatedLargeText logText;
private long startTime;
Expand Down Expand Up @@ -133,6 +136,7 @@ public BuildPipelineNode(final BlockEndNode endNode) {
this.workspace = stepData.getWorkspace();
this.nodeName = stepData.getNodeName();
this.nodeHostname = stepData.getNodeHostname();
this.nodeLabels = stepData.getNodeLabels();
}
}

Expand Down Expand Up @@ -169,6 +173,7 @@ public BuildPipelineNode(final StepAtomNode stepNode) {
this.workspace = stepData.getWorkspace();
this.nodeName = stepData.getNodeName();
this.nodeHostname = stepData.getNodeHostname();
this.nodeLabels = stepData.getNodeLabels();
}

final FlowNodeQueueData queueData = getQueueData(stepNode);
Expand Down Expand Up @@ -233,6 +238,10 @@ public String getNodeName() {
return nodeName;
}

public Set<String> getNodeLabels() {
return nodeLabels;
}

public String getPropagatedNodeName() {
return propagatedNodeName;
}
Expand All @@ -241,6 +250,14 @@ public void setPropagatedNodeName(String propagatedNodeName) {
this.propagatedNodeName = propagatedNodeName;
}

public Set<String> getPropagatedNodeLabels() {
return propagatedNodeLabels;
}

public void setPropagatedNodeLabels(final Set<String> propagatedNodeLabels) {
this.propagatedNodeLabels = propagatedNodeLabels;
}

public String getNodeHostname() {
return nodeHostname;
}
Expand Down Expand Up @@ -345,6 +362,7 @@ public void updateData(final BuildPipelineNode buildNode) {
this.workspace = buildNode.workspace;
this.nodeName = buildNode.nodeName;
this.nodeHostname = buildNode.nodeHostname;
this.nodeLabels = buildNode.nodeLabels;
this.logText = buildNode.logText;
this.startTime = buildNode.startTime;
this.startTimeMicros = buildNode.startTimeMicros;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
import hudson.model.InvisibleAction;

import java.io.Serializable;
import java.util.Set;

public class PipelineNodeInfoAction extends InvisibleAction implements Serializable {

private final String nodeName;
private final Set<String> nodeLabels;

public PipelineNodeInfoAction(final String nodeName) {
public PipelineNodeInfoAction(final String nodeName, final Set<String> nodeLabels) {
this.nodeName = nodeName;
this.nodeLabels = nodeLabels;
}

public String getNodeName() {
return nodeName;
}

public Set<String> getNodeLabels() {
return nodeLabels;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;

Expand All @@ -22,12 +23,14 @@ public class StepData implements Serializable {
private final String nodeName;
private final String nodeHostname;
private final String workspace;
private final Set<String> nodeLabels;

public StepData(final StepContext stepContext){
this.envVars = getEnvVars(stepContext);
this.nodeName = getNodeName(stepContext);
this.nodeHostname = getNodeHostname(stepContext);
this.workspace = getNodeWorkspace(stepContext);
this.nodeLabels = getNodeLabels(stepContext);
}

public Map<String, String> getEnvVars() {
Expand All @@ -46,6 +49,10 @@ public String getWorkspace() {
return workspace;
}

public Set<String> getNodeLabels() {
return nodeLabels;
}


/**
* Returns the workspace filepath of the remote node which is executing a determined {@code Step}
Expand Down Expand Up @@ -104,6 +111,22 @@ private String getNodeName(StepContext stepContext) {
}


/**
* Returns the nodeLabels of the remote node which is executing a determined {@code Step}
* @param stepContext
* @return node labels of the remote node.
*/
private Set<String> getNodeLabels(StepContext stepContext) {
try {
Computer computer = stepContext.get(Computer.class);
return DatadogUtilities.getNodeLabels(computer);
} catch (Exception e) {
logger.fine("Unable to extract the node labels from StepContext.");
return Collections.emptySet();
}
}


/**
* Returns {@code Map<String,String>} with environment variables of a certain {@code StepContext}
* @param stepContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public class CITags {
public static final String STATUS = "ci.status";
public static final String WORKSPACE_PATH = "ci.workspace_path";
public static final String NODE_NAME = "ci.node.name";
public static final String NODE_LABELS = "ci.node.labels";
public static final String QUEUE_TIME = "ci.queue_time";
public static final String _DD_HOSTNAME = "_dd.hostname";
public static final String _DD_CI_INTERNAL = "_dd.ci.internal";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.datadog.jenkins.plugins.datadog.traces;

import static org.datadog.jenkins.plugins.datadog.DatadogUtilities.getNormalizedResultForTraces;
import static org.datadog.jenkins.plugins.datadog.DatadogUtilities.toJson;
import static org.datadog.jenkins.plugins.datadog.traces.GitInfoUtils.normalizeBranch;
import static org.datadog.jenkins.plugins.datadog.traces.GitInfoUtils.normalizeTag;

Expand All @@ -19,13 +20,15 @@
import org.datadog.jenkins.plugins.datadog.model.PipelineQueueInfoAction;
import org.datadog.jenkins.plugins.datadog.model.StageBreakdownAction;
import org.datadog.jenkins.plugins.datadog.model.StageData;
import org.datadog.jenkins.plugins.datadog.steps.DatadogPipelineAction;
import org.datadog.jenkins.plugins.datadog.util.SuppressFBWarnings;
import org.datadog.jenkins.plugins.datadog.util.json.JsonUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

Expand Down Expand Up @@ -131,6 +134,11 @@ public void finishBuildTrace(final BuildData buildData, final Run<?,?> run) {

final String nodeName = getNodeName(run, buildData, updatedBuildData);
buildSpan.setTag(CITags.NODE_NAME, nodeName);

final String nodeLabelsJson = toJson(getNodeLabels(run, nodeName));
if(!nodeLabelsJson.isEmpty()){
buildSpan.setTag(CITags.NODE_LABELS, nodeLabelsJson);
}
// If the NodeName == master, we don't set _dd.hostname. It will be overridden by the Datadog Agent. (Traces are only available using Datadog Agent)
// If the NodeName != master, we set _dd.hostname to 'none' explicitly, cause we cannot calculate the worker hostname.
if(!"master".equalsIgnoreCase(nodeName)) {
Expand Down Expand Up @@ -246,6 +254,40 @@ private String getNodeName(Run<?, ?> run, BuildData buildData, BuildData updated
return buildData.getNodeName("").isEmpty() ? updatedBuildData.getNodeName("") : buildData.getNodeName("");
}

@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
private Set<String> getNodeLabels(Run<?,?> run, final String nodeName) {
try {
if(run == null){
return Collections.emptySet();
}

final PipelineNodeInfoAction pipelineNodeInfoAction = run.getAction(PipelineNodeInfoAction.class);
if(pipelineNodeInfoAction != null) {
return pipelineNodeInfoAction.getNodeLabels();
}

if(run.getExecutor() != null && run.getExecutor().getOwner() != null) {
Set<String> nodeLabels = DatadogUtilities.getNodeLabels(run.getExecutor().getOwner());
if(nodeLabels != null && !nodeLabels.isEmpty()) {
return nodeLabels;
}
}

// If there is no labels and the node name is master,
// we force the label "master".
if("master".equalsIgnoreCase(nodeName)){
final Set<String> masterLabels = new HashSet<>();
masterLabels.add("master");
return masterLabels;
}

return Collections.emptySet();
} catch (Exception ex) {
logger.fine("Unable to find node labels: " + ex.getMessage());
return Collections.emptySet();
}
}

private long getMillisInQueue(BuildData buildData) {
// Reported by the Jenkins Queue API.
// It's not included in the root span duration.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.datadog.jenkins.plugins.datadog.traces;

import static org.datadog.jenkins.plugins.datadog.DatadogUtilities.getNormalizedResultForTraces;
import static org.datadog.jenkins.plugins.datadog.DatadogUtilities.toJson;
import static org.datadog.jenkins.plugins.datadog.model.BuildPipelineNode.NodeType.PIPELINE;
import static org.datadog.jenkins.plugins.datadog.traces.GitInfoUtils.normalizeBranch;
import static org.datadog.jenkins.plugins.datadog.traces.GitInfoUtils.normalizeTag;
Expand All @@ -25,6 +26,7 @@
import org.datadog.jenkins.plugins.datadog.model.PipelineNodeInfoAction;
import org.datadog.jenkins.plugins.datadog.model.StageBreakdownAction;
import org.datadog.jenkins.plugins.datadog.model.StageData;
import org.datadog.jenkins.plugins.datadog.util.SuppressFBWarnings;
import org.datadog.jenkins.plugins.datadog.util.TagsUtil;
import org.datadog.jenkins.plugins.datadog.util.git.GitUtils;
import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode;
Expand All @@ -35,9 +37,12 @@

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

Expand Down Expand Up @@ -328,6 +333,12 @@ private Map<String, Object> buildTraceTags(final Run run, final BuildPipelineNod
// Node info
final String nodeName = getNodeName(run, current, buildData);
tags.put(CITags.NODE_NAME, nodeName);

final String nodeLabels = toJson(getNodeLabels(run, current, nodeName));
if(!nodeLabels.isEmpty()){
tags.put(CITags.NODE_LABELS, nodeLabels);
}

// If the NodeName == "master", we don't set _dd.hostname. It will be overridden by the Datadog Agent. (Traces are only available using Datadog Agent)
// If the NodeName != "master", we set _dd.hostname to 'none' explicitly, cause we cannot calculate the worker hostname.
if(!"master".equalsIgnoreCase(nodeName)){
Expand Down Expand Up @@ -377,6 +388,36 @@ private Map<String, Object> buildTraceTags(final Run run, final BuildPipelineNod
return tags;
}


@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
private Set<String> getNodeLabels(Run run, BuildPipelineNode current, String nodeName) {
final PipelineNodeInfoAction pipelineNodeInfoAction = run.getAction(PipelineNodeInfoAction.class);
if (current.getPropagatedNodeLabels() != null && !current.getPropagatedNodeLabels().isEmpty()) {
return current.getPropagatedNodeLabels();
} else if (current.getNodeLabels() != null && !current.getNodeLabels().isEmpty()) {
return current.getNodeLabels();
} else if (pipelineNodeInfoAction != null && !pipelineNodeInfoAction.getNodeLabels().isEmpty()) {
return pipelineNodeInfoAction.getNodeLabels();
}

if (run.getExecutor() != null && run.getExecutor().getOwner() != null) {
Set<String> nodeLabels = DatadogUtilities.getNodeLabels(run.getExecutor().getOwner());
if (nodeLabels != null && !nodeLabels.isEmpty()) {
return nodeLabels;
}
}

// If there is no labels and the node name is master,
// we force the label "master".
if ("master".equalsIgnoreCase(nodeName)) {
final Set<String> masterLabels = new HashSet<>();
masterLabels.add("master");
return masterLabels;
}

return Collections.emptySet();
}

private String getResult(BuildPipelineNode current) {
return (current.getPropagatedResult() != null) ? current.getPropagatedResult() : current.getResult();
}
Expand Down
Loading

0 comments on commit 347e090

Please sign in to comment.