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\"")); + } +}