diff --git a/docs/metrics/index.md b/docs/metrics/index.md index 31c55ced9..802b00fa8 100644 --- a/docs/metrics/index.md +++ b/docs/metrics/index.md @@ -71,6 +71,7 @@ specific metrics over the configuration page: | default_jenkins_builds_last_build_tests_total | Number of total tests during the last build | gauge | | default_jenkins_builds_last_last_build_tests_skipped | Number of skipped tests during the last build | gauge | | default_jenkins_builds_last_build_tests_failing | Number of failing tests during the last build | gauge | +| default_jenkins_builds_last_stage_result_ordinal | Status ordinal of a stage in a pipeline (0=NOT_EXECUTED,1=ABORTED,2=SUCCESS,3=IN_PROGRESS,4=PAUSED_PENDING_INPUT,5=FAILED,6=UNSTABLE) | gauge | | default_jenkins_builds_last_stage_duration_milliseconds_summary | Summary of Jenkins build times by Job and Stage in the last build | summary | | default_jenkins_builds_last_logfile_size_bytes | Gauge which shows the log file size in bytes. | gauge | diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/JobCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/JobCollector.java index 8ba02f908..632233c3e 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/JobCollector.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/JobCollector.java @@ -43,6 +43,7 @@ private static class BuildMetrics { public MetricCollector, ? extends Collector> jobBuildStartMillis; public MetricCollector, ? extends Collector> jobBuildDuration; public MetricCollector, ? extends Collector> stageSummary; + public MetricCollector, ? extends Collector> stageBuildResultOrdinal; public MetricCollector, ? extends Collector> jobBuildTestsTotal; public MetricCollector, ? extends Collector> jobBuildTestsSkipped; public MetricCollector, ? extends Collector> jobBuildTestsFailing; @@ -65,6 +66,7 @@ public void initCollectors(String[] labelNameArray) { this.jobBuildTestsSkipped = factory.createRunCollector(CollectorType.SKIPPED_TESTS_GAUGE, labelNameArray, buildPrefix); this.jobBuildTestsFailing = factory.createRunCollector(CollectorType.FAILED_TESTS_GAUGE, labelNameArray, buildPrefix); this.stageSummary = factory.createRunCollector(CollectorType.STAGE_SUMMARY, ArrayUtils.add(labelNameArray, "stage"), buildPrefix); + this.stageBuildResultOrdinal = factory.createRunCollector(CollectorType.STAGE_BUILDRESULT_ORDINAL, ArrayUtils.add(labelNameArray, "stage"), buildPrefix); this.jobBuildLikelyStuck = factory.createRunCollector(CollectorType.BUILD_LIKELY_STUCK_GAUGE, labelNameArray, buildPrefix); this.buildLogFileSizeGauge = factory.createRunCollector(CollectorType.BUILD_LOGFILE_SIZE_GAUGE, labelNameArray, buildPrefix); } @@ -214,6 +216,7 @@ private void addSamples(List allSamples, BuildMetrics build addSamples(allSamples, buildMetrics.jobBuildTestsFailing.collect(), "Adding [{}] samples from gauge ({})"); addSamples(allSamples, buildMetrics.jobBuildLikelyStuck.collect(), "Adding [{}] samples from gauge ({})"); addSamples(allSamples, buildMetrics.stageSummary.collect(), "Adding [{}] samples from summary ({})"); + addSamples(allSamples, buildMetrics.stageBuildResultOrdinal.collect(), "Adding [{}] samples from summary ({})"); addSamples(allSamples, buildMetrics.buildLogFileSizeGauge.collect(), "Adding [{}] samples from summary ({})"); } @@ -264,6 +267,7 @@ private void processRun(Job job, Run run, String[] buildLabelValueAr buildMetrics.jobBuildDuration.calculateMetric(run, buildLabelValueArray); // Label values are calculated within stageSummary so we pass null here. buildMetrics.stageSummary.calculateMetric(run, buildLabelValueArray); + buildMetrics.stageBuildResultOrdinal.calculateMetric(run, buildLabelValueArray); buildMetrics.jobBuildTestsTotal.calculateMetric(run, buildLabelValueArray); buildMetrics.jobBuildTestsSkipped.calculateMetric(run, buildLabelValueArray); buildMetrics.jobBuildTestsFailing.calculateMetric(run, buildLabelValueArray); diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorType.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorType.java index 80309e717..000a0129d 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorType.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorType.java @@ -23,6 +23,7 @@ public enum CollectorType { FAILED_TESTS_GAUGE("build_tests_failing"), SKIPPED_TESTS_GAUGE("last_build_tests_skipped"), STAGE_SUMMARY("stage_duration_milliseconds_summary"), + STAGE_BUILDRESULT_ORDINAL("stage_result_ordinal"), TOTAL_TESTS_GAUGE("build_tests_total"), HEALTH_SCORE_GAUGE("health_score"), NB_BUILDS_GAUGE("available_builds_count"), diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCollectorFactory.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCollectorFactory.java index 34deed906..3ed35095f 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCollectorFactory.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCollectorFactory.java @@ -31,6 +31,8 @@ public class BuildCollectorFactory extends BaseCollectorFactory { return saveBuildCollector(new SkippedTestsGauge(labelNames, namespace, subsystem, prefix)); case STAGE_SUMMARY: return saveBuildCollector(new StageSummary(labelNames, namespace, subsystem, prefix)); + case STAGE_BUILDRESULT_ORDINAL: + return saveBuildCollector(new StageBuildResultOrdinalGauge(labelNames, namespace, subsystem, prefix)); case TOTAL_TESTS_GAUGE: return saveBuildCollector(new TotalTestsGauge(labelNames, namespace, subsystem, prefix)); case BUILD_LIKELY_STUCK_GAUGE: diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageBuildResultOrdinalGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageBuildResultOrdinalGauge.java new file mode 100644 index 000000000..713136f5c --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageBuildResultOrdinalGauge.java @@ -0,0 +1,77 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import com.cloudbees.workflow.rest.external.StageNodeExt; +import hudson.model.Job; +import hudson.model.Run; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.apache.commons.lang3.ArrayUtils; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static org.jenkinsci.plugins.prometheus.util.FlowNodes.getSortedStageNodes; + +public class StageBuildResultOrdinalGauge extends BuildsMetricCollector, Gauge> { + + private static final Logger LOGGER = LoggerFactory.getLogger(StageBuildResultOrdinalGauge.class); + + protected StageBuildResultOrdinalGauge(String[] labelNames, String namespace, String subsystem, String prefix) { + super(labelNames, namespace, subsystem, prefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.STAGE_BUILDRESULT_ORDINAL; + } + + @Override + protected String getHelpText() { + return "Build status of a Stage. 0=NOT_EXECUTED,1=ABORTED,2=SUCCESS,3=IN_PROGRESS,4=PAUSED_PENDING_INPUT,5=FAILED,6=UNSTABLE"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run run, String[] labelValues) { + if (run.isBuilding()) { + return; + } + + if (!(run instanceof WorkflowRun)) { + return; + } + + var workflowRun = (WorkflowRun) run; + WorkflowJob job = workflowRun.getParent(); + if (workflowRun.getExecution() != null) { + processPipelineRunStages(job, workflowRun, labelValues); + } + } + + private void processPipelineRunStages(Job job, WorkflowRun workflowRun, String[] labelValues) { + List stages = getSortedStageNodes(workflowRun); + for (StageNodeExt stage : stages) { + if (stage != null) { + observeStage(job, workflowRun, stage, labelValues); + } + } + } + + private void observeStage(Job job, Run run, StageNodeExt stage, String[] labelValues) { + + LOGGER.debug("Observing stage[{}] in run [{}] from job [{}]", stage.getName(), run.getNumber(), job.getName()); + String stageName = stage.getName(); + + String[] values = ArrayUtils.add(labelValues, stageName); + + collector.labels(values).set(stage.getStatus().ordinal()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageSummary.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageSummary.java index d4f8ad4bc..18f1679e6 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageSummary.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageSummary.java @@ -13,13 +13,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; import java.util.List; import static org.jenkinsci.plugins.prometheus.util.FlowNodes.getSortedStageNodes; public class StageSummary extends BuildsMetricCollector, Summary> { - private static final String NOT_AVAILABLE = "NA"; private static final Logger LOGGER = LoggerFactory.getLogger(StageSummary.class); protected StageSummary(String[] labelNames, String namespace, String subsystem, String namePrefix) {