Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding metrics for code coverage #545

Merged
merged 6 commits into from
Sep 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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
Loading