From 861101750224a6447c2cab818ba87cff0787c0c3 Mon Sep 17 00:00:00 2001 From: Mathieu Delrocq Date: Thu, 18 Aug 2022 17:27:21 +0200 Subject: [PATCH 1/6] add agent point --- pom.xml | 1 - .../influxdb/InfluxDbPublicationService.java | 7 + .../generators/AgentPointGenerator.java | 125 ++++++++++++++++++ .../generators/AgentPointGeneratorTest.java | 124 +++++++++++++++++ 4 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java create mode 100644 src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java 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..1608afeb --- /dev/null +++ b/src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java @@ -0,0 +1,125 @@ +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"; + + 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<>(); + for (Map.Entry agentPoint : agentPoints) { + Point point = buildPoint("agent_data", customPrefix, build)// + .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..5a717339 --- /dev/null +++ b/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java @@ -0,0 +1,124 @@ +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.ClassRule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +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 { + + @ClassRule + public static JenkinsRule jenkinsRule = new JenkinsRule(); + + 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 flowNode; + private List flowNodeList; + private FlowExecutionOwner flowExecutionOwner; + private FlowExecution flowExecution; + private WorkspaceAction workspaceAction; + 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(); + flowNode = Mockito.mock(FlowNode.class); + flowExecutionOwner = Mockito.mock(FlowExecutionOwner.class); + flowExecution = Mockito.mock(FlowExecution.class); + flowNodeList = new ArrayList(); + flowNodeList.add(flowNode); + workspaceAction = 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(flowNode.getAction(WorkspaceAction.class)).thenReturn(workspaceAction); + Mockito.when(workspaceAction.getNode()).thenReturn(NODE_NAME); + Mockito.when(workspaceAction.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 lineProtocol = points[0].toLineProtocol(); + assertTrue(lineProtocol.contains("agent_name=\"node_name\"")); + assertTrue(lineProtocol.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\"")); + } +} From 6929258f3e8b1c00af790d984794e6bd089da778 Mon Sep 17 00:00:00 2001 From: Mathieu Delrocq Date: Tue, 30 Aug 2022 15:00:24 +0200 Subject: [PATCH 2/6] add unique_id to avoid overwritting datas --- .../influxdb/generators/AgentPointGenerator.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java b/src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java index 1608afeb..0d9ae978 100644 --- a/src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java +++ b/src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java @@ -30,6 +30,7 @@ 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; @@ -49,8 +50,11 @@ public boolean hasReport() { @Override public Point[] generate() { List points = new ArrayList<>(); - for (Map.Entry agentPoint : agentPoints) { + 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); @@ -87,7 +91,8 @@ private List> getAgentFromAbstractBuild(AbstractBuild< List> agentPointsList = new ArrayList<>(); Node node = build.getBuiltOn(); if (node != null) { - agentPointsList.add(new AbstractMap.SimpleEntry(node.getDisplayName(), node.getLabelString())); + agentPointsList + .add(new AbstractMap.SimpleEntry(node.getDisplayName(), node.getLabelString())); } return agentPointsList; } @@ -115,7 +120,8 @@ private List> getAgentsFromPipeline(FlowExecutionOwner labelString.add(label.getName()); } String nodeName = workspaceAction.getNode(); - agentPointsList.add(new AbstractMap.SimpleEntry(nodeName, labelString.toString())); + agentPointsList + .add(new AbstractMap.SimpleEntry(nodeName, labelString.toString())); } } } From d0c6116bb8b8c6eaa9fd9b5a06a37d92577296ee Mon Sep 17 00:00:00 2001 From: Mathieu Delrocq Date: Wed, 31 Aug 2022 16:50:26 +0200 Subject: [PATCH 3/6] add test with multiple agents --- .../generators/AgentPointGeneratorTest.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java b/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java index 5a717339..75065039 100644 --- a/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java +++ b/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java @@ -35,9 +35,6 @@ * @author Mathieu Delrocq */ public class AgentPointGeneratorTest { - - @ClassRule - public static JenkinsRule jenkinsRule = new JenkinsRule(); private static final String CUSTOM_PREFIX = "test_prefix"; private static final String JOB_NAME = "job_name"; @@ -48,11 +45,13 @@ public class AgentPointGeneratorTest { private Run abstractBuild; private WorkflowRun pipelineBuild; private Node node; - private FlowNode flowNode; + private FlowNode flowNode1; + private FlowNode flowNode2; private List flowNodeList; private FlowExecutionOwner flowExecutionOwner; private FlowExecution flowExecution; - private WorkspaceAction workspaceAction; + private WorkspaceAction workspaceAction1; + private WorkspaceAction workspaceAction2; private TaskListener listener; private long currTime; private ProjectNameRenderer measurementRenderer; @@ -79,21 +78,27 @@ public void before() throws Exception { // Mock for Pipeline pipelineBuild = Mockito.mock(WorkflowRun.class); Mockito.doReturn(job).when(pipelineBuild).getParent(); - flowNode = Mockito.mock(FlowNode.class); + 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(flowNode); - workspaceAction = Mockito.mock(WorkspaceAction.class); + 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(flowNode.getAction(WorkspaceAction.class)).thenReturn(workspaceAction); - Mockito.when(workspaceAction.getNode()).thenReturn(NODE_NAME); - Mockito.when(workspaceAction.getLabels()).thenReturn(labels); + 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 @@ -104,9 +109,12 @@ public void pipeline_agent_present() { 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\"")); + 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 From 126676a0860db9b9f6087fe546f19818d7286e87 Mon Sep 17 00:00:00 2001 From: Mathieu Delrocq Date: Wed, 31 Aug 2022 17:17:34 +0200 Subject: [PATCH 4/6] remove unused imports --- .../plugins/influxdb/generators/AgentPointGeneratorTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java b/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java index 75065039..ad64a64b 100644 --- a/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java +++ b/src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java @@ -14,9 +14,7 @@ import org.jenkinsci.plugins.workflow.graph.FlowNode; import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.junit.Before; -import org.junit.ClassRule; import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; import org.mockito.Mockito; import com.influxdb.client.write.Point; From bd9d62b5fc1e826d1adbc587355f96737658b04f Mon Sep 17 00:00:00 2001 From: Mathieu Delrocq Date: Thu, 1 Sep 2022 11:24:01 +0200 Subject: [PATCH 5/6] doc for agent_data --- doc/available_metrics.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/available_metrics.md b/doc/available_metrics.md index ff299e55..882209f6 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` +| 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 From 614e04ee36b77c27dca692f2e0efc463e9e75e7d Mon Sep 17 00:00:00 2001 From: Mathieu Delrocq Date: Thu, 1 Sep 2022 15:13:28 +0200 Subject: [PATCH 6/6] version on new metrics in doc --- doc/available_metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/available_metrics.md b/doc/available_metrics.md index 882209f6..fe82c763 100644 --- a/doc/available_metrics.md +++ b/doc/available_metrics.md @@ -112,7 +112,7 @@ Tags specific for this measurement: | sqale_index | float | Technical Debt | 2.4 | | sqale_debt_ratio | float | Technical Debt Ratio | 2.4 | -#### `agent_data` +#### `agent_data` (since 3.4) | Metric | Type | Description | Introduced in | | --- | --- | --- | --- | | agent_name | string | Name of an agent called by the build | |