Skip to content

Commit

Permalink
Adding metrics for code coverage (#545)
Browse files Browse the repository at this point in the history
* Base implementation

* Need to update to latest LTS because of new dependency

* Implementing CodeCoverageCollector

* Fixing calculation of coverage metrics

* Adding Tests

* Removing unnecessary constructor call
  • Loading branch information
Waschndolos authored Sep 3, 2023
1 parent 8b5f2ed commit 607db93
Show file tree
Hide file tree
Showing 36 changed files with 1,600 additions and 8 deletions.
16 changes: 13 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@

<properties>
<!-- when updated the io.jenkins.tools.bom:bom-X.Y.Z must be updated too -->
<jenkins.version>2.361.4</jenkins.version>
<jenkins.version>2.387.3</jenkins.version>
<java.level>11</java.level>

<prometheus.simpleclient.version>0.16.0</prometheus.simpleclient.version>
<slf4j.version>2.0.7</slf4j.version>

<cloudbees-disk-usage-simple.version>0.10</cloudbees-disk-usage-simple.version>
<code-coverage-api.version>4.7.0</code-coverage-api.version>
<metrics.version>4.1.6.1</metrics.version>
<pipeline-rest-api.version>2.21</pipeline-rest-api.version>

Expand All @@ -47,6 +48,9 @@
</licenses>

<developers>
<developer>
<id>Waschndolos</id>
</developer>
<developer>
<id>devguy</id>
<name>Marky Jackson</name>
Expand Down Expand Up @@ -79,8 +83,8 @@
<dependencies>
<dependency>
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-2.319.x</artifactId>
<version>1654.vcb_69d035fa_20</version>
<artifactId>bom-2.387.x</artifactId>
<version>2329.v078520e55c19</version>
<scope>import</scope>
<type>pom</type>
</dependency>
Expand Down Expand Up @@ -120,6 +124,12 @@
<version>${cloudbees-disk-usage-simple.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.jenkins.plugins</groupId>
<artifactId>code-coverage-api</artifactId>
<version>${code-coverage-api.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>junit</artifactId>
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/jenkinsci/plugins/prometheus/BaseCollector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.jenkinsci.plugins.prometheus;

import io.prometheus.client.Collector;
import io.prometheus.client.Gauge;
import org.jenkinsci.plugins.prometheus.util.ConfigurationUtils;

public abstract class BaseCollector extends Collector {


protected static Gauge.Builder newGaugeBuilder(String... labels) {
return newGaugeBuilder().labelNames(labels);

Check warning on line 11 in src/main/java/org/jenkinsci/plugins/prometheus/BaseCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 11 is not covered by tests
}

protected static Gauge.Builder newGaugeBuilder() {
return Gauge.build()

Check warning on line 15 in src/main/java/org/jenkinsci/plugins/prometheus/BaseCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 15 is not covered by tests
.namespace(ConfigurationUtils.getNamespace())

Check warning on line 16 in src/main/java/org/jenkinsci/plugins/prometheus/BaseCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 16 is not covered by tests
.subsystem(ConfigurationUtils.getSubSystem());

Check warning on line 17 in src/main/java/org/jenkinsci/plugins/prometheus/BaseCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 17 is not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.jenkinsci.plugins.prometheus;

import hudson.model.Job;
import hudson.model.Run;
import io.jenkins.plugins.coverage.metrics.steps.CoverageBuildAction;
import io.prometheus.client.Collector;
import jenkins.model.Jenkins;
import org.apache.commons.collections.CollectionUtils;
import org.jenkinsci.plugins.prometheus.collectors.CollectorFactory;
import org.jenkinsci.plugins.prometheus.collectors.CollectorType;
import org.jenkinsci.plugins.prometheus.collectors.MetricCollector;
import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration;
import org.jenkinsci.plugins.prometheus.util.Jobs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class CodeCoverageCollector extends BaseCollector {

private static final Logger LOGGER = LoggerFactory.getLogger(CodeCoverageCollector.class);

@Override
public List<MetricFamilySamples> collect() {

if (!isCodeCoverageAPIPluginLoaded()) {

Check warning on line 30 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 30 is only partially covered, one branch is missing
LOGGER.warn("Cannot collect code coverage data because plugin Code Coverage API (shortname: 'code-coverage-api') is not loaded.");

Check warning on line 31 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 31 is not covered by tests
return Collections.emptyList();

Check warning on line 32 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 32 is not covered by tests
}

if (!isCodeCoverageCollectionConfigured()) {

Check warning on line 35 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 35 is only partially covered, one branch is missing
return Collections.emptyList();
}

List<List<MetricFamilySamples>> samples = new ArrayList<>();

Check warning on line 39 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 39 is not covered by tests
Jobs.forEachJob(job -> CollectionUtils.addIgnoreNull(samples, collectCoverageMetricForJob(job)));

Check warning on line 40 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 40 is not covered by tests


return samples.stream().flatMap(Collection::stream).collect(Collectors.toList());

Check warning on line 43 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 43 is not covered by tests
}

private List<MetricFamilySamples> collectCoverageMetricForJob(Job<?,?> job) {
if (job == null) {

Check warning on line 47 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 47 is only partially covered, 2 branches are missing
return Collections.emptyList();

Check warning on line 48 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 48 is not covered by tests
}

Run<?,?> lastBuild = job.getLastBuild();

Check warning on line 51 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 51 is not covered by tests
if (lastBuild == null || lastBuild.isBuilding()) {

Check warning on line 52 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 52 is only partially covered, 4 branches are missing
return Collections.emptyList();

Check warning on line 53 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 53 is not covered by tests
}

CoverageBuildAction coverageBuildAction = lastBuild.getAction(CoverageBuildAction.class);

Check warning on line 56 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 56 is not covered by tests
if (coverageBuildAction == null) {

Check warning on line 57 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 57 is only partially covered, 2 branches are missing
return Collections.emptyList();

Check warning on line 58 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 58 is not covered by tests
}

CollectorFactory factory = new CollectorFactory();

Check warning on line 61 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 61 is not covered by tests
List<MetricCollector<Run<?,?>, ? extends Collector>> collectors = new ArrayList<>();

Check warning on line 62 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 62 is not covered by tests

collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_CLASS_COVERED, new String[]{"job_name"}));

Check warning on line 64 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 64 is not covered by tests
collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_CLASS_MISSED, new String[]{"job_name"}));

Check warning on line 65 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 65 is not covered by tests
collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_CLASS_TOTAL, new String[]{"job_name"}));

Check warning on line 66 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 66 is not covered by tests

collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_BRANCH_COVERED, new String[]{"job_name"}));

Check warning on line 68 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 68 is not covered by tests
collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_BRANCH_MISSED, new String[]{"job_name"}));

Check warning on line 69 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 69 is not covered by tests
collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_BRANCH_TOTAL, new String[]{"job_name"}));

Check warning on line 70 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 70 is not covered by tests

collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_INSTRUCTION_COVERED, new String[]{"job_name"}));

Check warning on line 72 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 72 is not covered by tests
collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_INSTRUCTION_MISSED, new String[]{"job_name"}));

Check warning on line 73 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 73 is not covered by tests
collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_INSTRUCTION_TOTAL, new String[]{"job_name"}));

Check warning on line 74 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 74 is not covered by tests

collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_FILE_COVERED, new String[]{"job_name"}));

Check warning on line 76 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 76 is not covered by tests
collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_FILE_MISSED, new String[]{"job_name"}));

Check warning on line 77 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 77 is not covered by tests
collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_FILE_TOTAL, new String[]{"job_name"}));

Check warning on line 78 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 78 is not covered by tests

collectors.forEach(c -> c.calculateMetric(lastBuild, new String[]{job.getName()}));

Check warning on line 80 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 80 is not covered by tests

return collectors.stream()

Check warning on line 82 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 82 is not covered by tests
.map(MetricCollector::collect)

Check warning on line 83 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 83 is not covered by tests
.flatMap(Collection::stream)

Check warning on line 84 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 84 is not covered by tests
.collect(Collectors.toList());

Check warning on line 85 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 85 is not covered by tests
}
private boolean isCodeCoverageAPIPluginLoaded() {
return Jenkins.get().getPlugin("code-coverage-api") != null;

Check warning on line 88 in src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 88 is only partially covered, one branch is missing
}

private boolean isCodeCoverageCollectionConfigured() {
return PrometheusConfiguration.get().isCollectCodeCoverage();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import io.prometheus.client.Collector;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.prometheus.collectors.builds.BuildCollectorFactory;
import org.jenkinsci.plugins.prometheus.collectors.coverage.CoverageCollectorFactory;
import org.jenkinsci.plugins.prometheus.collectors.disk.DiskCollectorFactory;
import org.jenkinsci.plugins.prometheus.collectors.executors.ExecutorCollectorFactory;
import org.jenkinsci.plugins.prometheus.collectors.jenkins.JenkinsCollectorFactory;
import org.jenkinsci.plugins.prometheus.collectors.jobs.JobCollectorFactory;
import org.json.Cookie;

import java.nio.file.FileStore;

Expand All @@ -23,12 +25,19 @@ public class CollectorFactory {
private final ExecutorCollectorFactory executorCollectorFactory;
private final DiskCollectorFactory diskCollectorFactory;

private final CoverageCollectorFactory coverageCollectorFactory;

public CollectorFactory() {
buildCollectorFactory = new BuildCollectorFactory();
jobCollectorFactory = new JobCollectorFactory();
jenkinsCollectorFactory = new JenkinsCollectorFactory();
executorCollectorFactory = new ExecutorCollectorFactory();
diskCollectorFactory = new DiskCollectorFactory();
coverageCollectorFactory = new CoverageCollectorFactory();
}

public MetricCollector<Run<?,?>, ? extends Collector> createCoverageRunCollector(CollectorType type, String[] labelNames) {
return coverageCollectorFactory.createCollector(type, labelNames);

Check warning on line 40 in src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorFactory.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 40 is not covered by tests
}

public MetricCollector<Run<?, ?>, ? extends Collector> createRunCollector(CollectorType type, String[] labelNames, String prefix) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,27 @@ public enum CollectorType {
FILE_STORE_CAPACITY_GAUGE("file_store_capacity_bytes"),
JOB_USAGE_BYTES_GAUGE("job_usage_bytes"),

BUILD_FAILED_TESTS("build_tests_failing");
BUILD_FAILED_TESTS("build_tests_failing"),

COVERAGE_CLASS_COVERED("coverage_class_covered"),
COVERAGE_CLASS_MISSED("coverage_class_missed"),
COVERAGE_CLASS_TOTAL("coverage_class_total"),

COVERAGE_BRANCH_COVERED("coverage_branch_covered"),
COVERAGE_BRANCH_MISSED("coverage_branch_missed"),
COVERAGE_BRANCH_TOTAL("coverage_branch_total"),

COVERAGE_INSTRUCTION_COVERED("coverage_instruction_covered"),
COVERAGE_INSTRUCTION_MISSED("coverage_instruction_missed"),
COVERAGE_INSTRUCTION_TOTAL("coverage_instruction_total"),

COVERAGE_FILE_COVERED("coverage_file_covered"),
COVERAGE_FILE_MISSED("coverage_file_missed"),
COVERAGE_FILE_TOTAL("coverage_file_total"),



;

private final String name;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.jenkinsci.plugins.prometheus.collectors.coverage;

import edu.hm.hafner.coverage.Coverage;
import edu.hm.hafner.coverage.Metric;
import hudson.model.Run;
import io.jenkins.plugins.coverage.metrics.model.Baseline;
import io.prometheus.client.Gauge;
import io.prometheus.client.SimpleCollector;
import org.jenkinsci.plugins.prometheus.collectors.CollectorType;

import java.util.Optional;

public class CoverageBranchCoveredGauge extends CoverageMetricsCollector<Run<?, ?>, Gauge> {

protected CoverageBranchCoveredGauge(String[] labelNames, String namespace, String subsystem) {
super(labelNames, namespace, subsystem);
}

@Override
protected CollectorType getCollectorType() {
return CollectorType.COVERAGE_BRANCH_COVERED;
}

@Override
protected String getHelpText() {
return "Returns the number of branches covered";
}

@Override
protected SimpleCollector.Builder<?, Gauge> getCollectorBuilder() {
return Gauge.build();
}

@Override
public void calculateMetric(Run<?, ?> jenkinsObject, String[] labelValues) {

Optional<Coverage> optional = getCoverage(jenkinsObject, Metric.BRANCH, Baseline.PROJECT);
if (optional.isEmpty()) {
return;
}

Coverage coverage = optional.get();
collector.labels(labelValues).set(coverage.getCovered());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.jenkinsci.plugins.prometheus.collectors.coverage;

import edu.hm.hafner.coverage.Coverage;
import edu.hm.hafner.coverage.Metric;
import hudson.model.Run;
import io.jenkins.plugins.coverage.metrics.model.Baseline;
import io.prometheus.client.Gauge;
import io.prometheus.client.SimpleCollector;
import org.jenkinsci.plugins.prometheus.collectors.CollectorType;

import java.util.Optional;

public class CoverageBranchMissedGauge extends CoverageMetricsCollector<Run<?, ?>, Gauge> {

protected CoverageBranchMissedGauge(String[] labelNames, String namespace, String subsystem) {
super(labelNames, namespace, subsystem);
}

@Override
protected CollectorType getCollectorType() {
return CollectorType.COVERAGE_BRANCH_MISSED;
}

@Override
protected String getHelpText() {
return "Returns the number of branches missed";
}

@Override
protected SimpleCollector.Builder<?, Gauge> getCollectorBuilder() {
return Gauge.build();
}

@Override
public void calculateMetric(Run<?, ?> jenkinsObject, String[] labelValues) {

Optional<Coverage> optional = getCoverage(jenkinsObject, Metric.BRANCH, Baseline.PROJECT);
if (optional.isEmpty()) {
return;
}

Coverage coverage = optional.get();
collector.labels(labelValues).set(coverage.getMissed());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.jenkinsci.plugins.prometheus.collectors.coverage;

import edu.hm.hafner.coverage.Coverage;
import edu.hm.hafner.coverage.Metric;
import hudson.model.Run;
import io.jenkins.plugins.coverage.metrics.model.Baseline;
import io.prometheus.client.Gauge;
import io.prometheus.client.SimpleCollector;
import org.jenkinsci.plugins.prometheus.collectors.CollectorType;

import java.util.Optional;

public class CoverageBranchTotalGauge extends CoverageMetricsCollector<Run<?, ?>, Gauge> {

protected CoverageBranchTotalGauge(String[] labelNames, String namespace, String subsystem) {
super(labelNames, namespace, subsystem);
}

@Override
protected CollectorType getCollectorType() {
return CollectorType.COVERAGE_BRANCH_TOTAL;
}

@Override
protected String getHelpText() {
return "Returns the number of branches total";
}

@Override
protected SimpleCollector.Builder<?, Gauge> getCollectorBuilder() {
return Gauge.build();
}

@Override
public void calculateMetric(Run<?, ?> jenkinsObject, String[] labelValues) {

Optional<Coverage> optional = getCoverage(jenkinsObject, Metric.BRANCH, Baseline.PROJECT);
if (optional.isEmpty()) {
return;
}

Coverage coverage = optional.get();
collector.labels(labelValues).set(coverage.getTotal());
}
}
Loading

0 comments on commit 607db93

Please sign in to comment.