diff --git a/doc/available_metrics.md b/doc/available_metrics.md
index ff299e55..fe82c763 100644
--- a/doc/available_metrics.md
+++ b/doc/available_metrics.md
@@ -112,8 +112,11 @@ Tags specific for this measurement:
| sqale_index | float | Technical Debt | 2.4 |
| sqale_debt_ratio | float | Technical Debt Ratio | 2.4 |
-
-
+#### `agent_data` (since 3.4)
+| Metric | Type | Description | Introduced in |
+| --- | --- | --- | --- |
+| agent_name | string | Name of an agent called by the build | |
+| agent_label | string | Label of an agent called by the build | |
### Cobertura plugin
diff --git a/pom.xml b/pom.xml
index 31d9b97d..26c04698 100644
--- a/pom.xml
+++ b/pom.xml
@@ -217,7 +217,6 @@ THE SOFTWARE.
org.jenkins-ci.plugins.workflow
workflow-api
${workflow-api.version}
- test
org.jenkins-ci.plugins.workflow
diff --git a/src/main/java/jenkinsci/plugins/influxdb/InfluxDbPublicationService.java b/src/main/java/jenkinsci/plugins/influxdb/InfluxDbPublicationService.java
index b3dff810..badf57bc 100644
--- a/src/main/java/jenkinsci/plugins/influxdb/InfluxDbPublicationService.java
+++ b/src/main/java/jenkinsci/plugins/influxdb/InfluxDbPublicationService.java
@@ -178,6 +178,13 @@ public void perform(Run, ?> build, TaskListener listener, EnvVars env) {
JenkinsBasePointGenerator jGen = new JenkinsBasePointGenerator(build, listener, measurementRenderer, timestamp, jenkinsEnvParameterTag, jenkinsEnvParameterField, customPrefix, measurementName, env);
addPoints(pointsToWrite, jGen, listener);
+ AgentPointGenerator agentGen = new AgentPointGenerator(build, listener, measurementRenderer, timestamp, jenkinsEnvParameterTag, customPrefix);
+ if (agentGen.hasReport()) {
+ addPoints(pointsToWrite, agentGen, listener);
+ } else {
+ logger.log(Level.FINE, "Data source empty: Agent Point");
+ }
+
CustomDataPointGenerator cdGen = new CustomDataPointGenerator(build, listener, measurementRenderer, timestamp, jenkinsEnvParameterTag, customPrefix, customData, customDataTags, measurementName);
if (cdGen.hasReport()) {
listener.getLogger().println("[InfluxDB Plugin] Custom data found. Writing to InfluxDB...");
diff --git a/src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java b/src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java
new file mode 100644
index 00000000..0d9ae978
--- /dev/null
+++ b/src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java
@@ -0,0 +1,131 @@
+package jenkinsci.plugins.influxdb.generators;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.jenkinsci.plugins.workflow.actions.WorkspaceAction;
+import org.jenkinsci.plugins.workflow.flow.FlowExecution;
+import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
+import org.jenkinsci.plugins.workflow.graph.FlowGraphWalker;
+import org.jenkinsci.plugins.workflow.graph.FlowNode;
+
+import com.influxdb.client.write.Point;
+
+import hudson.model.AbstractBuild;
+import hudson.model.Node;
+import hudson.model.Run;
+import hudson.model.TaskListener;
+import hudson.model.labels.LabelAtom;
+import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer;
+
+/**
+ * @author Mathieu Delrocq
+ */
+public class AgentPointGenerator extends AbstractPointGenerator {
+
+ protected static final String AGENT_NAME = "agent_name";
+ protected static final String AGENT_LABEL = "agent_label";
+ protected static final String UNIQUE_ID = "unique_id";
+
+ private List> agentPoints;
+ private String customPrefix;
+
+ public AgentPointGenerator(Run, ?> build, TaskListener listener, ProjectNameRenderer projectNameRenderer,
+ long timestamp, String jenkinsEnvParameterTag, String customPrefix) {
+ super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag);
+ this.agentPoints = getAgentPoints(build);
+ this.customPrefix = customPrefix;
+ }
+
+ @Override
+ public boolean hasReport() {
+ return CollectionUtils.isNotEmpty(agentPoints);
+ }
+
+ @Override
+ public Point[] generate() {
+ List points = new ArrayList<>();
+ Map.Entry agentPoint = null;
+ for (int i = 0; i < agentPoints.size(); i++) {
+ agentPoint = agentPoints.get(i);
+ Point point = buildPoint("agent_data", customPrefix, build)//
+ .addTag(UNIQUE_ID, String.valueOf(i+1))//
+ .addField(AGENT_NAME, agentPoint.getKey())//
+ .addField(AGENT_LABEL, agentPoint.getValue());
+ points.add(point);
+ }
+ return points.toArray(new Point[0]);
+ }
+
+ public String getFirstAgent() {
+ return !CollectionUtils.isEmpty(agentPoints) ? agentPoints.get(0).getKey() : "";
+ }
+
+ /**
+ * Retrieve agent(s) used by the build and return {@link AgentPoint}
+ *
+ * @param build
+ * @return list of {@link AgentPoint}
+ */
+ private List> getAgentPoints(Run, ?> build) {
+ if (build instanceof AbstractBuild) {
+ return getAgentFromAbstractBuild((AbstractBuild, ?>) build);
+ } else if (build instanceof FlowExecutionOwner.Executable) {
+ return getAgentsFromPipeline((FlowExecutionOwner.Executable) build);
+ }
+ return new ArrayList<>();
+ }
+
+ /**
+ * Retrieve agent(s) for traditional jobs
+ *
+ * @param build
+ * @return list of {@link AgentPoint}
+ */
+ private List> getAgentFromAbstractBuild(AbstractBuild, ?> build) {
+ List> agentPointsList = new ArrayList<>();
+ Node node = build.getBuiltOn();
+ if (node != null) {
+ agentPointsList
+ .add(new AbstractMap.SimpleEntry(node.getDisplayName(), node.getLabelString()));
+ }
+ return agentPointsList;
+ }
+
+ /**
+ * Retrieve agent(s) for pipeline jobs
+ *
+ * @param build
+ * @return list of {@link AgentPoint}
+ */
+ private List> getAgentsFromPipeline(FlowExecutionOwner.Executable build) {
+ List> agentPointsList = new ArrayList<>();
+ FlowExecutionOwner flowExecutionOwner = build.asFlowExecutionOwner();
+ if (flowExecutionOwner != null) {
+ FlowExecution flowExecution = flowExecutionOwner.getOrNull();
+ if (flowExecution != null) {
+ FlowGraphWalker graphWalker = new FlowGraphWalker(flowExecution);
+ for (FlowNode flowNode : graphWalker) {
+ WorkspaceAction workspaceAction = flowNode.getAction(WorkspaceAction.class);
+ if (null != workspaceAction) {
+ Set labels = workspaceAction.getLabels();
+ StringJoiner labelString = new StringJoiner(", ");
+ labelString.setEmptyValue("");
+ for (LabelAtom label : labels) {
+ labelString.add(label.getName());
+ }
+ String nodeName = workspaceAction.getNode();
+ agentPointsList
+ .add(new AbstractMap.SimpleEntry(nodeName, labelString.toString()));
+ }
+ }
+ }
+ }
+ return agentPointsList;
+ }
+}
diff --git a/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java b/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java
new file mode 100644
index 00000000..ad64a64b
--- /dev/null
+++ b/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java
@@ -0,0 +1,130 @@
+package jenkinsci.plugins.influxdb.generators;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jenkinsci.plugins.workflow.actions.WorkspaceAction;
+import org.jenkinsci.plugins.workflow.flow.FlowExecution;
+import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
+import org.jenkinsci.plugins.workflow.graph.FlowNode;
+import org.jenkinsci.plugins.workflow.job.WorkflowRun;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.influxdb.client.write.Point;
+
+import hudson.model.AbstractBuild;
+import hudson.model.HealthReport;
+import hudson.model.Job;
+import hudson.model.Node;
+import hudson.model.Run;
+import hudson.model.TaskListener;
+import hudson.model.labels.LabelAtom;
+import jenkins.model.Jenkins;
+import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer;
+
+/**
+ * @author Mathieu Delrocq
+ */
+public class AgentPointGeneratorTest {
+
+ private static final String CUSTOM_PREFIX = "test_prefix";
+ private static final String JOB_NAME = "job_name";
+
+ private static final String NODE_NAME = "node_name";
+ private static final String NODE_LABEL = "node_label";
+
+ private Run, ?> abstractBuild;
+ private WorkflowRun pipelineBuild;
+ private Node node;
+ private FlowNode flowNode1;
+ private FlowNode flowNode2;
+ private List flowNodeList;
+ private FlowExecutionOwner flowExecutionOwner;
+ private FlowExecution flowExecution;
+ private WorkspaceAction workspaceAction1;
+ private WorkspaceAction workspaceAction2;
+ private TaskListener listener;
+ private long currTime;
+ private ProjectNameRenderer measurementRenderer;
+
+ @Before
+ public void before() throws Exception {
+ // Global Mocks
+ listener = Mockito.mock(TaskListener.class);
+ currTime = System.currentTimeMillis();
+ measurementRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null);
+ Job, ?> job = Mockito.mock(Job.class);
+ Mockito.when(job.getName()).thenReturn(JOB_NAME);
+ Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + JOB_NAME);
+ Mockito.when(job.getBuildHealth()).thenReturn(new HealthReport());
+
+ // Mocks for AbstractBuild
+ abstractBuild = Mockito.mock(AbstractBuild.class);
+ Mockito.doReturn(job).when(abstractBuild).getParent();
+ node = Mockito.mock(Node.class);
+ Mockito.when(((AbstractBuild, ?>) abstractBuild).getBuiltOn()).thenReturn(node);
+ Mockito.when(node.getDisplayName()).thenReturn(NODE_NAME);
+ Mockito.when(node.getLabelString()).thenReturn(NODE_LABEL);
+
+ // Mock for Pipeline
+ pipelineBuild = Mockito.mock(WorkflowRun.class);
+ Mockito.doReturn(job).when(pipelineBuild).getParent();
+ flowNode1 = Mockito.mock(FlowNode.class);
+ flowNode2 = Mockito.mock(FlowNode.class);
+ flowExecutionOwner = Mockito.mock(FlowExecutionOwner.class);
+ flowExecution = Mockito.mock(FlowExecution.class);
+ flowNodeList = new ArrayList();
+ flowNodeList.add(flowNode1);
+ flowNodeList.add(flowNode2);
+ workspaceAction1 = Mockito.mock(WorkspaceAction.class);
+ workspaceAction2 = Mockito.mock(WorkspaceAction.class);
+ Set labels = new HashSet();
+ LabelAtom label = new LabelAtom(NODE_LABEL);
+ labels.add(label);
+ Mockito.when(pipelineBuild.asFlowExecutionOwner()).thenReturn(flowExecutionOwner);
+ Mockito.when(flowExecutionOwner.getOrNull()).thenReturn(flowExecution);
+ Mockito.when(flowExecution.getCurrentHeads()).thenReturn(flowNodeList);
+ Mockito.when(flowNode1.getAction(WorkspaceAction.class)).thenReturn(workspaceAction1);
+ Mockito.when(flowNode2.getAction(WorkspaceAction.class)).thenReturn(workspaceAction2);
+ Mockito.when(workspaceAction1.getNode()).thenReturn(NODE_NAME);
+ Mockito.when(workspaceAction1.getLabels()).thenReturn(labels);
+ Mockito.when(workspaceAction2.getNode()).thenReturn(NODE_NAME + "2");
+ Mockito.when(workspaceAction2.getLabels()).thenReturn(labels);
+ }
+
+ @Test
+ public void pipeline_agent_present() {
+ AgentPointGenerator gen = new AgentPointGenerator(pipelineBuild, listener, measurementRenderer, currTime,
+ StringUtils.EMPTY, CUSTOM_PREFIX);
+ assertTrue(gen.hasReport());
+ Point[] points = gen.generate();
+ assertTrue(points != null && points.length != 0);
+ assertTrue(points[0].hasFields());
+ String lineProtocol1 = points[0].toLineProtocol();
+ String lineProtocol2 = points[1].toLineProtocol();
+ assertTrue(lineProtocol1.contains("agent_name=\"node_name2\""));
+ assertTrue(lineProtocol1.contains("agent_label=\"node_label\""));
+ assertTrue(lineProtocol2.contains("agent_name=\"node_name\""));
+ assertTrue(lineProtocol2.contains("agent_label=\"node_label\""));
+ }
+
+ @Test
+ public void abstractbuild_agent_present() {
+ AgentPointGenerator gen = new AgentPointGenerator(abstractBuild, listener, measurementRenderer, currTime,
+ StringUtils.EMPTY, CUSTOM_PREFIX);
+ assertTrue(gen.hasReport());
+ Point[] points = gen.generate();
+ assertTrue(points != null && points.length != 0);
+ assertTrue(points[0].hasFields());
+ String lineProtocol = points[0].toLineProtocol();
+ assertTrue(lineProtocol.contains("agent_name=\"node_name\""));
+ assertTrue(lineProtocol.contains("agent_label=\"node_label\""));
+ }
+}