diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index fb1cf0d09..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: markyjackson-taulia diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 2c926e823..9bf589090 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -28,4 +28,4 @@ Tell us what happens instead ### Notify -@markyjackson-taulia +@Waschndolos diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3eb336031..ea617baa3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,4 +14,4 @@ Fixes #... ### Notify -@markyjackson-taulia +@Waschndolos diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f9e0d0e89..c6da28a48 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,5 +9,5 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" + interval: "monthly" open-pull-requests-limit: 10 diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 9f47e8c1a..0d0b1c994 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,3 +1 @@ _extends: .github -tag-template: prometheus-$NEXT_PATCH_VERSION -version-template: $MAJOR.$MINOR.$PATCH diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 000000000..f8cb1f8d6 --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,20 @@ +# Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins + +name: cd +on: + workflow_dispatch: +# Want to do manual releases for now until I'm used to the CD +# check_run: +# types: +# - completed + +permissions: + checks: read + contents: write + +jobs: + maven-cd: + uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 + secrets: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml deleted file mode 100644 index 001295081..000000000 --- a/.github/workflows/codacy-analysis.yml +++ /dev/null @@ -1,46 +0,0 @@ -# This workflow checks out code, performs a Codacy security scan -# and integrates the results with the -# GitHub Advanced Security code scanning feature. For more information on -# the Codacy security scan action usage and parameters, see -# https://github.com/codacy/codacy-analysis-cli-action. -# For more information on Codacy Analysis CLI in general, see -# https://github.com/codacy/codacy-analysis-cli. - -name: Codacy Security Scan - -on: - push: - branches: [ "master", "main" ] - pull_request: - branches: [ "master", "main" ] - -jobs: - codacy-security-scan: - name: Codacy Security Scan - runs-on: ubuntu-latest - steps: - # Checkout the repository to the GitHub Actions runner - - name: Checkout code - uses: actions/checkout@v2 - - # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - - name: Run Codacy Analysis CLI - uses: codacy/codacy-analysis-cli-action@3.0.0 - with: - # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository - # You can also omit the token and run the tools that support default configurations - project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - verbose: true - output: results.sarif - format: sarif - # Adjust severity of non-security issues - gh-code-scanning-compat: true - # Force 0 exit code to allow SARIF file generation - # This will handover control about PR rejection to the GitHub side - max-allowed-issues: 2147483647 - - # Upload the SARIF file generated in the previous step - - name: Upload SARIF results file - uses: github/codeql-action/upload-sarif@v1 - with: - sarif_file: results.sarif diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 150919edd..000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,62 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -name: "CodeQL" - -on: - push: - branches: [master] - pull_request: - # The branches below must be a subset of the branches above - branches: [master] - schedule: - - cron: '0 9 * * 0' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ['java'] - # Learn more... - # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/jenkins-security-scan.yml b/.github/workflows/jenkins-security-scan.yml new file mode 100644 index 000000000..c7b41fc29 --- /dev/null +++ b/.github/workflows/jenkins-security-scan.yml @@ -0,0 +1,21 @@ +name: Jenkins Security Scan + +on: + push: + branches: + - master + pull_request: + types: [ opened, synchronize, reopened ] + workflow_dispatch: + +permissions: + security-events: write + contents: read + actions: read + +jobs: + security-scan: + uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 + with: + java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. + # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b671fc094..62808d409 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v9.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'Stale issue message' diff --git a/.gitignore b/.gitignore index 331dc36fa..be0d22fb7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ work .classpath .settings/ build/ -*.DS_Store \ No newline at end of file +*.DS_Store +.vscode/* \ No newline at end of file diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 000000000..4e0774d51 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,7 @@ + + + io.jenkins.tools.incrementals + git-changelist-maven-extension + 1.8 + + diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 000000000..61cf4e5e7 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1,3 @@ +-Pconsume-incrementals +-Pmight-produce-incrementals +-Dchangelist.format=%d.v%s \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index b01965102..387957edf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,2 +1,10 @@ /* `buildPlugin` step provided by: https://github.com/jenkins-infra/pipeline-library */ -buildPlugin() + +buildPlugin( + forkCount: '1C', + useContainerAgent: true, + configurations: [ + [platform: 'linux', jdk: 25], + [platform: 'windows', jdk: 25], + [platform: 'linux', jdk: 21], +]) diff --git a/README.md b/README.md index e05f0c5ea..85cf9c971 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,36 @@ # Jenkins Prometheus Metrics Plugin +[![Coverage](https://ci.jenkins.io/job/Plugins/job/prometheus-plugin/job/master/badge/icon?status=${instructionCoverage}&subject=coverage&color=${colorInstructionCoverage})](https://ci.jenkins.io/job/Plugins/job/prometheus-plugin/job/master) +[![LOC](https://ci.jenkins.io/job/Plugins/job/prometheus-plugin/job/master/badge/icon?job=test&status=${lineOfCode}&subject=line%20of%20code&color=blue)](https://ci.jenkins.io/job/Plugins/job/prometheus-plugin/job/master) +[![Jenkins Plugin Installs](https://img.shields.io/jenkins/plugin/i/prometheus.svg?color=blue)](https://plugins.jenkins.io/prometheus) + + ## About -Jenkins Prometheus Plugin expose an endpoint (default `/prometheus`) with metrics where a Prometheus Server can scrape. +Jenkins Prometheus Plugin expose an endpoint (default `/prometheus/`) with metrics where a Prometheus Server can scrape. Documentation can be found [here](https://plugins.jenkins.io/prometheus) Please note that the documentation is a WIP. ## Metrics exposed -Currently only metrics from the [Metrics-plugin](https://github.com/jenkinsci/metrics-plugin) and summary of build -duration of jobs and pipeline stages +2 types of metrics are exposed: + +- Metrics from [Metrics-plugin](https://github.com/jenkinsci/metrics-plugin) +- Metrics from this plugin. Refer [Prometheus-plugin](docs/metrics/index.md) + +## Scraping the endpoint +The endpoint you've configured or the default endpoint `/prometheus/` in case you didn't configure an endpoint, needs to +end with a trailing slash when you configure the endpoint in your scraping tool. If you miss adding the trailing slash +you'll get a 302 response with a redirection to the endpoint ending with a slash. Some tools cannot handle this well. + +## Configuring the plugin +You can find some examples in this documentation [Configuring Plugin](docs/configuration/configuration.md) ## Environment variables `PROMETHEUS_NAMESPACE` Prefix of metric (Default: `default`). -`PROMETHEUS_ENDPOINT` REST Endpoint (Default: `prometheus`) +`PROMETHEUS_ENDPOINT` REST Endpoint (Default: `/prometheus/`) `COLLECTING_METRICS_PERIOD_IN_SECONDS` Async task period in seconds (Default: `120` seconds) @@ -28,9 +43,8 @@ duration of jobs and pipeline stages mvn hpi:hpi ## Author / Maintainer -[Lars Sjöström](https://github.com/lsjostro) -[Marky Jackson](https://github.com/markyjackson-taulia) +[Waschndolos](https://github.com/waschndolos) # Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. @@ -89,9 +103,11 @@ Have a look at CONTRIBUTING.md. There are 3 easy ways to contribute to this proj Commit and push your changes. +``` git add -A git commit -m "Your commit message" git push --set-upstream origin new-branch +``` Create a Pull Request by navigating to your forked repository and clicking the New pull request button on your left-hand side of the page. Add in a title, edit the PR template, and then press the Create pull request button. diff --git a/docs/assets/metrics_per_build.png b/docs/assets/metrics_per_build.png new file mode 100644 index 000000000..122bfdc2d Binary files /dev/null and b/docs/assets/metrics_per_build.png differ diff --git a/docs/assets/prometheus_configuration_namespace.png b/docs/assets/prometheus_configuration_namespace.png new file mode 100644 index 000000000..26b8f7997 Binary files /dev/null and b/docs/assets/prometheus_configuration_namespace.png differ diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md new file mode 100644 index 000000000..7be55daa2 --- /dev/null +++ b/docs/configuration/configuration.md @@ -0,0 +1,76 @@ +# Plugin configuration + +## Path +With the path configuration you can configure under which url the Prometheus page will be rendered. The default +is `prometheus` which will cause the rending to be at `http(s)://yourInstance:(port)/(jenkins?)/prometheus` + +## Default Namespace +You can configure a namespace for all metrics generated by this plugin which will be prefixed to the output + +## Enable authentication for prometheus end-point +If you check this the accessing the endpoint needs to have the `Metrics.VIEW` permission + +## Collecting metrics period in second +The metrics are collected every x seconds. You can configure the interval here. + +## Count duration of successful builds +If checked the plugin wil calculate the duration of successful builds + +## Count duration of unstable builds +If checked the plugin wil calculate the duration of unstable builds + +## Count duration of not-built builds +If checked the plugin wil calculate the duration of not-built builds + +## Count duration of aborted builds +If checked the plugin wil calculate the duration of aborted builds + +## Fetch the test results of builds +If checked the plugin will return test results as metrics + +## Add build parameter label to metrics +If checked the plugin will add the build (input) parameters to the metrics. +Note: If the cardinality of your build parameters is high, this will create a large number of metrics. +This can result in poor jenkins and prometheus performance. + +## Add build status label to metrics +If checked the plugin will add the build status like SUCCESS, FAILURE, RUNNING to the metrics of +`duration_milliseconds_summary`, `success_build_count` and `failed_build_count metrics` + +## Job attribute name +You can configure the label of the field which contains the job name here. + +## Build parameters that will be added as separate labels to metrics +Please refer online documentation (question mark symbol in jenkins configuration page) + +## Collect disk usage +If checked the plugin will collect the disk usage of your master agent. Do not use this on a cloud storage provider. + +## Collect node status +If checked the plugin will collect data of up/down status of your Jenkins agents + +## Collect metrics for each run per build +If checked it will cause the plugin to add metrics for every build available. The build number will be added as label. Use with caution! + +## Collect Code coverage (since v2.3.0) +If checked and you publish your code coverage results with [https://plugins.jenkins.io/coverage](https://plugins.jenkins.io/coverage) +the plugin will output metrics for: +* (Class | Branch | Instruction | File | Line ) Missed +* (Class | Branch | Instruction | File | Line ) Covered +* (Class | Branch | Instruction | File | Line ) Total +* (Class | Branch | Instruction | File | Line ) Percent + +## Disable Metrics (since v2.3.0) +Sometimes you don't need all metrics in your prometheus endpoint which this plugin provides. +You can disable certain metrics. These metrics are not being collected by the plugin and therefore not added in the +prometheus endpoint. + +![img.png](img/disabled_metrics.png) + +### Regex Entry +A Regex entry can be used to disable a group of metrics. E.g. if you want to disable everything with +default_jenkins_disk.* + +### Fully qualified Name Entry +If you want to disable certain individual entries you can do it with this entry. The value should be the same +as you can see it in the prometheus endpoint. It's case-insensitive. diff --git a/docs/configuration/img/disabled_metrics.png b/docs/configuration/img/disabled_metrics.png new file mode 100644 index 000000000..3ff13b6eb Binary files /dev/null and b/docs/configuration/img/disabled_metrics.png differ diff --git a/docs/metrics/index.md b/docs/metrics/index.md new file mode 100644 index 000000000..802b00fa8 --- /dev/null +++ b/docs/metrics/index.md @@ -0,0 +1,94 @@ +# Metrics collected by [prometheus-plugin](../../README.md) + +Metrics collected by this Plugin are prefixed with "default_jenkins". +You can change the "default" prefix either via configuration page (Default Namespace): + +![prometheus_configuration_namespace.png](../assets/prometheus_configuration_namespace.png) + +or an environment variable ```PROMETHEUS_NAMESPACE```. +If the environment variable is defined this value will be taken. + +## DiskUsageCollector + +Required Plugin: +[cloudbees-disk-usage-simple-plugin](https://github.com/jenkinsci/cloudbees-disk-usage-simple-plugin) + +| metric | description | Prometheus Type | +|--------------------------------------------|--------------------------------------------------------------|-----------------| +| default_jenkins_disk_usage_bytes | Disk usage of first level folder in JENKINS_HOME in bytes | gauge | +| default_jenkins_job_usage_bytes | Amount of disk usage for each job in Jenkins in bytes | gauge | +| default_jenkins_file_store_capacity_bytes | Total size in bytes of the file stores used by Jenkins | gauge | +| default_jenkins_file_store_available_bytes | Estimated available space on the file stores used by Jenkins | gauge | + +## ExecutorCollector + +| metric | description | Prometheus Type | +|----------------------------------------|-----------------------------------------------------------------|-----------------| +| default_jenkins_executors_available | Shows how many Jenkins Executors are available | gauge | +| default_jenkins_executors_busy | Shows how many Jenkins Executors busy | gauge | +| default_jenkins_executors_connecting | Shows how many Jenkins Executors are connecting | gauge | +| default_jenkins_executors_defined | Shows how many Jenkins Executors are defined | gauge | +| default_jenkins_executors_idle | Shows how many Jenkins Executors are idle | gauge | +| default_jenkins_executors_online | Shows how many Jenkins Executors are online | gauge | +| default_jenkins_executors_queue_length | Shows number of items that can run but waiting on free executor | gauge | + +## JenkinsStatusCollector + +| metric | description | Prometheus Type | +|------------------------------|--------------------------------------------|-----------------| +| default_jenkins_version | Shows the jenkins Version | info | +| default_jenkins_up | Shows if jenkins ready to receive requests | gauge | +| default_jenkins_uptime | Shows time since Jenkins was initialized | gauge | +| default_jenkins_nodes_online | Shows Nodes online status | gauge | +| default_jenkins_quietdown | Shows if jenkins is in quiet mode | gauge | + +## JobCollector + +The JobCollector provides metrics about the Job and build specific metrics. + +| metric | description | Prometheus Type | +|------------------------------------------------------------|---------------------------------------------------------------------------|-----------------| +| default_jenkins_builds_duration_milliseconds_summary | Summary of Jenkins build times in milliseconds by Job | summary | +| default_jenkins_builds_success_build_count | Successful build count | counter | +| default_jenkins_builds_failed_build_count | Failed build count | counter | +| default_jenkins_builds_unstable_build_count | Unstable build count | counter | +| default_jenkins_builds_total_build_count | Total build count (excluding not_built statuses) | counter | +| default_jenkins_builds_aborted_build_count | Aborted build count | counter | +| default_jenkins_builds_health_score | Health score of a job | gauge | +| default_jenkins_builds_available_builds_count | Gauge which indicates how many builds are available for the given job | gauge | +| default_jenkins_builds_discard_active | Gauge which indicates if the build discard feature is active for the job. | gauge | +| default_jenkins_builds_running_build_duration_milliseconds | Gauge which indicates the runtime of the current build. | gauge | + +There are additional build specific metrics returning stats for the last build of a job if you don't enable build +specific metrics over the configuration page: + +| metric | description | Prometheus Type | +|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| +| default_jenkins_builds_last_build_result_ordinal | Build status of a job (last build) (0=SUCCESS,1=UNSTABLE,2=FAILURE,3=NOT_BUILT,4=ABORTED) | gauge | +| default_jenkins_builds_last_build_result | Build status of a job as a boolean value (1 or 0). <br/>Where 1 stands for the build status SUCCESS or UNSTABLE and 0 for the build states FAILURE,NOT_BUILT or ABORTED | gauge | +| default_jenkins_builds_last_build_duration_milliseconds | Build times in milliseconds of last build | gauge | +| default_jenkins_builds_last_build_start_time_milliseconds | Last build start timestamp in milliseconds | gauge | +| 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 | + +If you enable the per build metrics, there will be additional metrics for every build, additionally to the metrics for +the `last_build`. They will be created for every build available in the UI so be careful if you have many jobs with many +builds. The metrics will have a new label called `number` containing the build number of the given build. + +![metrics_per_build.png](..%2Fassets%2Fmetrics_per_build.png) + +| metric | description | Prometheus Type | +|------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| +| default_jenkins_builds_build_result_ordinal | Build status of a job (last build) (0=SUCCESS,1=UNSTABLE,2=FAILURE,3=NOT_BUILT,4=ABORTED) | gauge | +| default_jenkins_builds_build_result | Build status of a job as a boolean value (1 or 0). <br/>Where 1 stands for the build status SUCCESS or UNSTABLE and 0 for the build states FAILURE,NOT_BUILT or ABORTED | gauge | +| default_jenkins_builds_build_duration_milliseconds | Build times in milliseconds of last build | gauge | +| default_jenkins_builds_build_start_time_milliseconds | Last build start timestamp in milliseconds | gauge | +| default_jenkins_builds_build_tests_total | Number of total tests during the last build | gauge | +| default_jenkins_builds_build_tests_skipped | Number of skipped tests during the last build | gauge | +| default_jenkins_builds_build_tests_failing | Number of failing tests during the last build | gauge | +| default_jenkins_builds_stage_duration_milliseconds_summary | Summary of Jenkins build times by Job and Stage in the last build | summary | +| default_jenkins_builds_logfile_size_bytes | Gauge which shows the log file size in bytes. | gauge | diff --git a/pom.xml b/pom.xml index aa488b61d..335c83417 100644 --- a/pom.xml +++ b/pom.xml @@ -4,12 +4,12 @@ org.jenkins-ci.plugins plugin - 4.18 + 5.28 prometheus - 2.0.11-SNAPSHOT + ${changelist} hpi Prometheus metrics plugin Expose Jenkins metrics in prometheus format @@ -17,34 +17,21 @@ 2016 - 2.222.4 - 8 - 0.8.0 - 2.42 - 2.40 - 2.23 - 4.0.2.7 - 1.28 - 0.10 - 1.22 - 2.8.6 + 999999-SNAPSHOT + jenkinsci/prometheus-plugin + + 2.479 + ${jenkins.baseline}.3 + + 0.16.0 + + 2.7.0 + + 3.24.2 1.1.1 - 4.4.14 - 1.1.0 - 3.14.0 - 1.7.30 - 2.15 - 3.8 - 1.9 - 1.76 - 2.12.2 + 1.2.1 - - Methodair - http://www.methodair.io - - Apache License, Version 2.0 @@ -55,18 +42,15 @@ - devguy - Marky Jackson - Methodair - http://www.methodair.io/ + Waschndolos - scm:git:git://github.com/jenkinsci/prometheus-plugin.git - scm:git:git@github.com:jenkinsci/prometheus-plugin.git - https://github.com/jenkinsci/prometheus-plugin/ - prometheus-2.0.8 + scm:git:https://github.com/${gitHubRepo}.git + scm:git:git@github.com:${gitHubRepo}.git + https://github.com/${gitHubRepo} + ${scmTag} @@ -82,145 +66,82 @@ + + + + io.jenkins.tools.bom + bom-${jenkins.baseline}.x + 4969.v6ffa_18d90c9f + import + pom + + + + - - org.jenkins-ci.plugins - jackson2-api - ${jackson2.version} - - - org.jenkins-ci.plugins.workflow - workflow-api - ${workflow.api.version} - - - org.jenkins-ci.plugins.pipeline-stage-view - pipeline-rest-api - ${pipeline.rest.api.version} - - - org.jenkins-ci.plugins.workflow - workflow-job - ${workflow.job.version} - - - org.jenkins-ci.plugins.workflow - workflow-support - ${workflow.support.version} - - - org.jenkins-ci.plugins.workflow - workflow-step-api - ${workflow.step.version} - - - org.jenkins-ci.plugins - pipeline-graph-analysis - ${pipeline.graph.analysis.version} - - - org.jenkins-ci.plugins - structs - ${jenkins.structs.version} - - - org.jenkins-ci.plugins - script-security - ${script.security.version} - - - org.apache.httpcomponents - httpcore - ${httpcore.version} - - - org.jenkins-ci.plugins - metrics - ${jenkins.metrics.version} - - - com.google.code.gson - gson - ${gson.version} - - - io.prometheus - simpleclient_hotspot - ${prometheus.simpleClientJava.version} - io.prometheus simpleclient - ${prometheus.simpleClientJava.version} + ${prometheus.simpleclient.version} io.prometheus simpleclient_common - ${prometheus.simpleClientJava.version} + ${prometheus.simpleclient.version} io.prometheus simpleclient_dropwizard - ${prometheus.simpleClientJava.version} + ${prometheus.simpleclient.version} - org.jenkins-ci.plugins - junit - ${jenkins.junit.version} + io.prometheus + simpleclient_hotspot + ${prometheus.simpleclient.version} + org.jenkins-ci.plugins cloudbees-disk-usage-simple - ${jenkins.diskUsage.version} true - org.slf4j - slf4j-api - ${slf4j.version} + io.jenkins.plugins + coverage + ${coverage.version} + true - org.slf4j - slf4j-jdk14 - ${slf4j.version} + io.jenkins.plugins + commons-lang3-api - org.slf4j - log4j-over-slf4j - ${slf4j.version} + org.jenkins-ci.plugins + junit - org.slf4j - jcl-over-slf4j - ${slf4j.version} - - - - - pl.pragmatists - JUnitParams - ${JUnitParams.version} - test + org.jenkins-ci.plugins + metrics - org.powermock - powermock-api-mockito2 - test + org.jenkins-ci.plugins.pipeline-stage-view + pipeline-rest-api + - org.powermock - powermock-module-junit4 + org.mockito + mockito-core test - org.hamcrest - hamcrest-core - 2.2 + org.mockito + mockito-junit-jupiter test - org.mockito - mockito-core + pl.pragmatists + JUnitParams + ${JUnitParams.version} test @@ -229,11 +150,5 @@ ${system-lambda.version} test - - org.assertj - assertj-core - ${assertj-core.version} - test - diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java new file mode 100644 index 000000000..94d812721 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollector.java @@ -0,0 +1,110 @@ +package org.jenkinsci.plugins.prometheus; + +import hudson.model.Run; +import io.jenkins.plugins.coverage.metrics.steps.CoverageBuildAction; +import io.prometheus.client.Collector; +import jenkins.model.Jenkins; +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.config.disabledmetrics.MetricStatusChecker; +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 Collector { + + private static final Logger LOGGER = LoggerFactory.getLogger(CodeCoverageCollector.class); + + @Override + public List collect() { + + if (!isCoveragePluginLoaded()) { + LOGGER.debug("Cannot collect code coverage data because plugin Code Coverage API (shortname: 'code-coverage-api') is not loaded."); + return Collections.emptyList(); + } + + if (!isCoverageCollectionConfigured()) { + return Collections.emptyList(); + } + + List, ? extends Collector>> collectors = createCollectors(); + + return collectCoverageMetricForJob(collectors); + } + + private List, ? extends Collector>> createCollectors() { + CollectorFactory factory = new CollectorFactory(); + List, ? extends Collector>> collectors = new ArrayList<>(); + + String jobAttributeName = PrometheusConfiguration.get().getJobAttributeName(); + + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_CLASS_COVERED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_CLASS_MISSED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_CLASS_TOTAL, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_CLASS_PERCENT, new String[]{jobAttributeName})); + + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_BRANCH_COVERED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_BRANCH_MISSED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_BRANCH_TOTAL, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_BRANCH_PERCENT, new String[]{jobAttributeName})); + + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_INSTRUCTION_COVERED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_INSTRUCTION_MISSED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_INSTRUCTION_TOTAL, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_INSTRUCTION_PERCENT, new String[]{jobAttributeName})); + + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_FILE_COVERED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_FILE_MISSED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_FILE_TOTAL, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_FILE_PERCENT, new String[]{jobAttributeName})); + + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_LINE_COVERED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_LINE_MISSED, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_LINE_TOTAL, new String[]{jobAttributeName})); + collectors.add(factory.createCoverageRunCollector(CollectorType.COVERAGE_LINE_PERCENT, new String[]{jobAttributeName})); + + return collectors; + } + + private List collectCoverageMetricForJob(List, ? extends Collector>> collectors) { + + Jobs.forEachJob(job -> { + Run lastBuild = job.getLastBuild(); + if (lastBuild == null || lastBuild.isBuilding()) { + return; + } + if (!MetricStatusChecker.isJobEnabled(job.getFullName())) { + LOGGER.debug("Job '{}' is excluded by configuration", job.getFullName()); + return; + } + + CoverageBuildAction coverageBuildAction = lastBuild.getAction(CoverageBuildAction.class); + if (coverageBuildAction == null) { + return; + } + + collectors.forEach(c -> c.calculateMetric(lastBuild, new String[]{job.getFullName()})); + }); + + return collectors.stream() + .map(MetricCollector::collect) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + private boolean isCoveragePluginLoaded() { + return Jenkins.get().getPlugin("coverage") != null; + } + + private boolean isCoverageCollectionConfigured() { + return PrometheusConfiguration.get().isCollectCodeCoverage(); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/DiskUsageCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/DiskUsageCollector.java index 787891292..7e2adb7d6 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/DiskUsageCollector.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/DiskUsageCollector.java @@ -1,76 +1,133 @@ package org.jenkinsci.plugins.prometheus; +import com.cloudbees.simplediskusage.DiskItem; +import com.cloudbees.simplediskusage.JobDiskItem; +import edu.umd.cs.findbugs.annotations.NonNull; import io.prometheus.client.Collector; -import io.prometheus.client.Gauge; import jenkins.model.Jenkins; -import org.jenkinsci.plugins.prometheus.util.ConfigurationUtils; +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.config.disabledmetrics.MetricStatusChecker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nonnull; +import java.io.File; import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Files; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class DiskUsageCollector extends Collector { - private static final Logger logger = LoggerFactory.getLogger(DiskUsageCollector.class); - private Jenkins jenkins; - private Gauge directoryUsageGauge; - private Gauge jobUsageGauge; - private boolean collectDiskUsage; - - public DiskUsageCollector() { - jenkins = Jenkins.get(); - this.collectDiskUsage = ConfigurationUtils.getCollectDiskUsage(); - - if(collectDiskUsage) { - directoryUsageGauge = Gauge.build() - .namespace(ConfigurationUtils.getNamespace()) - .subsystem(ConfigurationUtils.getSubSystem()) - .name("disk_usage_bytes") - .labelNames("directory") - .help("Disk usage of first level folder in JENKINS_HOME in bytes") - .create(); - jobUsageGauge = Gauge.build() - .namespace(ConfigurationUtils.getNamespace()) - .subsystem(ConfigurationUtils.getSubSystem()) - .name("job_usage_bytes") - .labelNames("jobName", "url") - .help("Amount of disk usage (bytes) for each job in Jenkins") - .create(); - } - } + private static final Logger LOGGER = LoggerFactory.getLogger(DiskUsageCollector.class); @Override - @Nonnull + @NonNull public List collect() { - List samples = new ArrayList<>(); - if(!this.collectDiskUsage) { return samples; } + + if (!PrometheusConfiguration.get().getCollectDiskUsage()) { + return Collections.emptyList(); + } + try { - com.cloudbees.simplediskusage.QuickDiskUsagePlugin diskUsagePlugin = jenkins.getPlugin(com.cloudbees.simplediskusage.QuickDiskUsagePlugin.class); - if (diskUsagePlugin == null) { - logger.warn("Cannot collect disk usage data because plugin CloudBees Disk Usage Simple is not installed."); - return samples; - } - diskUsagePlugin.getDirectoriesUsages().forEach( - i -> directoryUsageGauge.labels(i.getDisplayName()) - .set(i.getUsage() * 1024) - ); - samples.addAll(directoryUsageGauge.collect()); - - diskUsagePlugin.getJobsUsages().forEach( - i -> jobUsageGauge.labels(i.getFullName(), i.getUrl()) - .set(i.getUsage() * 1024) - ); - samples.addAll(jobUsageGauge.collect()); - } catch (IOException e) { - logger.warn("Cannot get disk usage data due to an unexpected error", e); - } catch (java.lang.NoClassDefFoundError e) { - logger.warn("Cannot get disk usage data. Install CloudBees Disk Usage Simple plugin to enable"); + return collectDiskUsage(); + } catch (final IOException | RuntimeException e) { + LOGGER.warn("Failed to get disk usage data due to an unexpected error.", e); + return Collections.emptyList(); + } catch (final NoClassDefFoundError e) { + LOGGER.warn("Cannot collect disk usage data because plugin CloudBees Disk Usage Simple is not installed: {}", e.toString()); + LOGGER.info("You can remove this warning if you disable Collect Disk Usage in Prometheus Configuration."); + return Collections.emptyList(); } + } + + @NonNull + private static List collectDiskUsage() throws IOException { + final com.cloudbees.simplediskusage.QuickDiskUsagePlugin diskUsagePlugin = Jenkins.get() + .getPlugin(com.cloudbees.simplediskusage.QuickDiskUsagePlugin.class); + if (diskUsagePlugin == null) { + return Collections.emptyList(); + } + + CollectorFactory factory = new CollectorFactory(); + final Set usedFileStores = new HashSet<>(); + List> diskItemCollectors = new ArrayList<>(); + diskItemCollectors.add(factory.createDiskItemCollector(CollectorType.DISK_USAGE_BYTES_GAUGE, new String[]{"file_store", "directory"})); + diskItemCollectors.add(factory.createDiskItemCollector(CollectorType.DISK_USAGE_FILE_COUNT_GAUGE, new String[]{"file_store", "directory"})); + + diskUsagePlugin.getDirectoriesUsages().forEach(i -> { + final Optional fileStore = getFileStore(i.getPath()); + fileStore.ifPresent(usedFileStores::add); + diskItemCollectors.forEach(c -> c.calculateMetric(i, new String[]{toLabelValue(fileStore), i.getDisplayName()})); + }); + + List> jobDiskItemCollectors = new ArrayList<>(); + jobDiskItemCollectors.add(factory.createJobDiskItemCollector(CollectorType.JOB_USAGE_BYTES_GAUGE, new String[]{"file_store", "jobName", "url"})); + + diskUsagePlugin.getJobsUsages().forEach(i -> { + if (!MetricStatusChecker.isJobEnabled(i.getFullName())) { + return; + } + final Optional fileStore = getFileStore(i.getPath()); + fileStore.ifPresent(usedFileStores::add); + jobDiskItemCollectors.forEach(c -> c.calculateMetric(i, new String[]{toLabelValue(fileStore), i.getFullName(), i.getUrl()})); + }); + + List> fileStoreCollectors = new ArrayList<>(); + fileStoreCollectors.add(factory.createFileStoreCollector(CollectorType.FILE_STORE_CAPACITY_GAUGE, new String[]{"file_store"})); + fileStoreCollectors.add(factory.createFileStoreCollector(CollectorType.FILE_STORE_AVAILABLE_GAUGE, new String[]{"file_store"})); + + usedFileStores.forEach(store -> { + final String labelValue = toLabelValue(Optional.of(store)); + fileStoreCollectors.forEach(c -> c.calculateMetric(store, new String[]{labelValue})); + }); + + List samples = new ArrayList<>(); + + samples.addAll(Stream.of(diskItemCollectors) + .flatMap(Collection::stream) + .map(MetricCollector::collect) + .flatMap(Collection::stream) + .collect(Collectors.toList())); + + samples.addAll(Stream.of(jobDiskItemCollectors) + .flatMap(Collection::stream) + .map(MetricCollector::collect) + .flatMap(Collection::stream) + .collect(Collectors.toList())); + + samples.addAll(Stream.of(fileStoreCollectors) + .flatMap(Collection::stream) + .map(MetricCollector::collect) + .flatMap(Collection::stream) + .collect(Collectors.toList())); + return samples; } + + private static String toLabelValue(Optional fileStore) { + // At least on Linux, FileStore::name is not unique, whereas FileStore::toString includes the mount point, which + // makes it unique. So it's possible to have duplicate metrics with different label values for the same file + // store mounted to different paths. + return fileStore.map(FileStore::toString).orElse(""); + } + + private static Optional getFileStore(File file) { + try { + return Optional.of(Files.getFileStore(file.toPath().toRealPath())); + } catch (IOException | RuntimeException e) { + LOGGER.debug("Failed to get file store for {}", file, e); + return Optional.empty(); + } + } } diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/ExecutorCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/ExecutorCollector.java index cd8fdb291..137376fab 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/ExecutorCollector.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/ExecutorCollector.java @@ -3,123 +3,52 @@ import hudson.model.Label; import hudson.model.LoadStatistics; import io.prometheus.client.Collector; -import io.prometheus.client.Gauge; import jenkins.model.Jenkins; -import org.jenkinsci.plugins.prometheus.util.ConfigurationUtils; +import org.jenkinsci.plugins.prometheus.collectors.CollectorFactory; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.MetricCollector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; public class ExecutorCollector extends Collector { - private static final Logger logger = LoggerFactory.getLogger(ExecutorCollector.class); - - private Gauge executorsAvailable; - private Gauge executorsBusy; - private Gauge executorsConnecting; - private Gauge executorsDefined; - private Gauge executorsIdle; - private Gauge executorsOnline; - private Gauge queueLength; - - - public ExecutorCollector() { - } + private static final Logger LOGGER = LoggerFactory.getLogger(ExecutorCollector.class); @Override public List collect() { - logger.debug("Collecting executor metrics for prometheus"); + LOGGER.debug("Collecting executor metrics for prometheus"); + + CollectorFactory factory = new CollectorFactory(); - String namespace = ConfigurationUtils.getNamespace(); - List samples = new ArrayList<>(); String prefix = "executors"; - String subsystem = ConfigurationUtils.getSubSystem(); String[] labelNameArray = {"label"}; - // Number of executors (among the online executors) that are available to carry out builds. - executorsAvailable = Gauge.build() - .name(prefix + "_available") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Executors Available") - .create(); - - // Number of executors (among the online executors) that are carrying out builds. - executorsBusy = Gauge.build() - .name(prefix + "_busy") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Executors Busy") - .create(); + List> collectors = new ArrayList<>(); - // Number of executors that are currently in the process of connecting to Jenkins. - executorsConnecting = Gauge.build() - .name(prefix + "_connecting") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Executors Connecting") - .create(); + collectors.add(factory.createLoadStatisticsCollector(CollectorType.EXECUTORS_AVAILABLE_GAUGE, labelNameArray, prefix)); + collectors.add(factory.createLoadStatisticsCollector(CollectorType.EXECUTORS_BUSY_GAUGE, labelNameArray, prefix)); + collectors.add(factory.createLoadStatisticsCollector(CollectorType.EXECUTORS_CONNECTING_GAUGE, labelNameArray, prefix)); + collectors.add(factory.createLoadStatisticsCollector(CollectorType.EXECUTORS_DEFINED_GAUGE, labelNameArray, prefix)); + collectors.add(factory.createLoadStatisticsCollector(CollectorType.EXECUTORS_IDLE_GAUGE, labelNameArray, prefix)); + collectors.add(factory.createLoadStatisticsCollector(CollectorType.EXECUTORS_ONLINE_GAUGE, labelNameArray, prefix)); + collectors.add(factory.createLoadStatisticsCollector(CollectorType.EXECUTORS_QUEUE_LENGTH_GAUGE, labelNameArray, prefix)); - // Number of executors that Jenkins currently knows, this includes all off-line agents. - executorsDefined = Gauge.build() - .name(prefix + "_defined") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Executors Defined") - .create(); - - // Number of executors that are currently on-line and idle. - executorsIdle = Gauge.build() - .name(prefix + "_idle") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Executors Idle") - .create(); - - // Sum of all executors across all online computers in this label. - executorsOnline = Gauge.build() - .name(prefix + "_online") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Executors Online") - .create(); - - // Number of jobs that are in the build queue, waiting for an available executor of this label. - queueLength = Gauge.build() - .name(prefix + "_queue_length") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Executors Queue Length") - .create(); - - logger.debug("getting load statistics for Executors"); + LOGGER.debug("getting load statistics for Executors"); Label[] labels = Jenkins.get().getLabels().toArray(new Label[0]); for (Label l : labels) { - appendExecutorMetrics(l.getDisplayName(), l.loadStatistics.computeSnapshot()); + collectors.forEach(c -> c.calculateMetric(l.loadStatistics.computeSnapshot(), new String[]{l.getDisplayName()})); } - samples.addAll(executorsAvailable.collect()); - samples.addAll(executorsBusy.collect()); - samples.addAll(executorsConnecting.collect()); - samples.addAll(executorsDefined.collect()); - samples.addAll(executorsIdle.collect()); - samples.addAll(executorsOnline.collect()); - samples.addAll(queueLength.collect()); - - return samples; + return collectors.stream() + .map(MetricCollector::collect) + .flatMap(Collection::stream) + .collect(Collectors.toList()); } - protected void appendExecutorMetrics(String labelDisplayName, LoadStatistics.LoadStatisticsSnapshot computeSnapshot) { - String[] labelValueArray = {labelDisplayName}; - executorsAvailable.labels(labelValueArray).set(computeSnapshot.getAvailableExecutors()); - executorsBusy.labels(labelValueArray).set(computeSnapshot.getBusyExecutors()); - executorsConnecting.labels(labelValueArray).set(computeSnapshot.getConnectingExecutors()); - executorsDefined.labels(labelValueArray).set(computeSnapshot.getDefinedExecutors()); - executorsIdle.labels(labelValueArray).set(computeSnapshot.getIdleExecutors()); - executorsOnline.labels(labelValueArray).set(computeSnapshot.getOnlineExecutors()); - queueLength.labels(labelValueArray).set(computeSnapshot.getQueueLength()); - } } diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/JenkinsStatusCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/JenkinsStatusCollector.java index 83ac3ccd4..258429939 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/JenkinsStatusCollector.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/JenkinsStatusCollector.java @@ -1,47 +1,36 @@ package org.jenkinsci.plugins.prometheus; -import hudson.model.Computer; import io.prometheus.client.Collector; -import io.prometheus.client.Gauge; import jenkins.model.Jenkins; -import org.jenkinsci.plugins.prometheus.util.ConfigurationUtils; +import org.jenkinsci.plugins.prometheus.collectors.CollectorFactory; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.MetricCollector; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; public class JenkinsStatusCollector extends Collector { - @Override public List collect() { - String subsystem = ConfigurationUtils.getSubSystem(); - String namespace = ConfigurationUtils.getNamespace(); - List samples = new ArrayList<>(); - - Gauge jenkinsUp = Gauge.build() - .name("up") - .labelNames() - .subsystem(subsystem) - .namespace(namespace) - .help("Is Jenkins ready to receive requests") - .create(); - Jenkins jenkins = Jenkins.getInstance(); - jenkinsUp.set(jenkins.getInitLevel() == hudson.init.InitMilestone.COMPLETED ? 1 : 0); - samples.addAll(jenkinsUp.collect()); - - Gauge jenkinsUptime = Gauge.build() - .name("uptime") - .labelNames() - .subsystem(subsystem) - .namespace(namespace) - .help("Time since Jenkins machine was initialized") - .create(); - Computer computer = jenkins.toComputer(); - if (computer != null) { - long upTime = computer.getConnectTime(); - jenkinsUptime.set(System.currentTimeMillis() - upTime); - samples.addAll(jenkinsUptime.collect()); - } - - return samples; + + CollectorFactory factory = new CollectorFactory(); + Jenkins jenkins = Jenkins.get(); + List> collectors = new ArrayList<>(); + + collectors.add(factory.createJenkinsCollector(CollectorType.JENKINS_VERSION_INFO_GAUGE, new String[]{})); + collectors.add(factory.createJenkinsCollector(CollectorType.JENKINS_UP_GAUGE, new String[]{})); + collectors.add(factory.createJenkinsCollector(CollectorType.JENKINS_UPTIME_GAUGE, new String[]{})); + collectors.add(factory.createJenkinsCollector(CollectorType.NODES_ONLINE_GAUGE, new String[]{"node"})); + collectors.add(factory.createJenkinsCollector(CollectorType.JENKINS_QUIETDOWN_GAUGE, new String[]{})); + + collectors.forEach(c -> c.calculateMetric(jenkins, new String[]{})); + + return collectors.stream() + .map(MetricCollector::collect) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/JobCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/JobCollector.java index 3c38571e4..b593dddac 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/JobCollector.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/JobCollector.java @@ -1,150 +1,88 @@ package org.jenkinsci.plugins.prometheus; -import com.cloudbees.workflow.rest.external.StageNodeExt; -import com.cloudbees.workflow.rest.external.StatusExt; import hudson.model.Job; -import hudson.model.Result; import hudson.model.Run; -import hudson.tasks.test.AbstractTestResultAction; import io.prometheus.client.Collector; -import io.prometheus.client.Counter; -import io.prometheus.client.Gauge; -import io.prometheus.client.Summary; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.ArrayUtils; +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.collectors.builds.BuildCompletionListener; +import org.jenkinsci.plugins.prometheus.collectors.builds.BuildCompletionListener.CloseableIterator; +import org.jenkinsci.plugins.prometheus.collectors.builds.CounterManager; +import org.jenkinsci.plugins.prometheus.collectors.builds.JobLabel; import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; -import org.jenkinsci.plugins.prometheus.util.ConfigurationUtils; +import org.jenkinsci.plugins.prometheus.config.disabledmetrics.MetricStatusChecker; import org.jenkinsci.plugins.prometheus.util.Jobs; import org.jenkinsci.plugins.prometheus.util.Runs; -import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.stream.Collectors; - -import static org.jenkinsci.plugins.prometheus.util.FlowNodes.getSortedStageNodes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; public class JobCollector extends Collector { - private static final Logger logger = LoggerFactory.getLogger(JobCollector.class); - private static final String NOT_AVAILABLE = "NA"; - private static final String UNDEFINED = "UNDEFINED"; + private static final Logger LOGGER = LoggerFactory.getLogger(JobCollector.class); - private Summary summary; - private Counter jobSuccessCount; - private Counter jobFailedCount; - private Gauge jobHealthScore; + private final BuildMetrics lastBuildMetrics = new BuildMetrics("last"); + private final BuildMetrics perBuildMetrics = new BuildMetrics(""); - private static class BuildMetrics { + private MetricCollector, ? extends Collector> summary; + private MetricCollector, ? extends Collector> jobHealthScoreGauge; + private MetricCollector, ? extends Collector> nbBuildsGauge; + private MetricCollector, ? extends Collector> buildDiscardGauge; + private MetricCollector, ? extends Collector> currentRunDurationGauge; + private MetricCollector, ? extends Collector> logUpdatedGauge; - public Gauge jobBuildResultOrdinal; - public Gauge jobBuildResult; - public Gauge jobBuildStartMillis; - public Gauge jobBuildDuration; - public Summary stageSummary; - public Gauge jobBuildTestsTotal; - public Gauge jobBuildTestsSkipped; - public Gauge jobBuildTestsFailing; + private static class BuildMetrics { - private String buildPrefix; + public MetricCollector, ? extends Collector> jobBuildResultOrdinal; + public MetricCollector, ? extends Collector> jobBuildResult; + 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; + public MetricCollector, ? extends Collector> jobBuildLikelyStuck; + private MetricCollector, ? extends Collector> buildLogFileSizeGauge; + private MetricCollector, ? extends Collector> jobBuildWaitingDurationGauge; + + private final String buildPrefix; public BuildMetrics(String buildPrefix) { this.buildPrefix = buildPrefix; } - public void initCollectors(String fullname, String subsystem, String namespace, String[] labelNameArray, String[] labelStageNameArray) { - this.jobBuildResultOrdinal = Gauge.build() - .name(fullname + this.buildPrefix +"_build_result_ordinal") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Build status of a job.") - .create(); - - this.jobBuildResult = Gauge.build() - .name(fullname + this.buildPrefix +"_build_result") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Build status of a job as a boolean (0 or 1)") - .create(); - - this.jobBuildDuration = Gauge.build() - .name(fullname + this.buildPrefix +"_build_duration_milliseconds") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Build times in milliseconds of last build") - .create(); - - this.jobBuildStartMillis = Gauge.build() - .name(fullname + this.buildPrefix +"_build_start_time_milliseconds") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Last build start timestamp in milliseconds") - .create(); - - this.jobBuildTestsTotal = Gauge.build() - .name(fullname + this.buildPrefix +"_build_tests_total") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Number of total tests during the last build") - .create(); - - this.jobBuildTestsSkipped = Gauge.build() - .name(fullname + "_last_build_tests_skipped") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Number of skipped tests during the last build") - .create(); - - this.jobBuildTestsFailing = Gauge.build() - .name(fullname + this.buildPrefix +"_build_tests_failing") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Number of failing tests during the last build") - .create(); - - this.stageSummary = Summary.build().name(fullname + this.buildPrefix +"_stage_duration_milliseconds_summary") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelStageNameArray) - .help("Summary of Jenkins build times by Job and Stage in the last build") - .create(); + public void initCollectors(String[] labelNameArray) { + CollectorFactory factory = new CollectorFactory(); + this.jobBuildResultOrdinal = factory.createRunCollector(CollectorType.BUILD_RESULT_ORDINAL_GAUGE, labelNameArray, buildPrefix); + this.jobBuildResult = factory.createRunCollector(CollectorType.BUILD_RESULT_GAUGE, labelNameArray, buildPrefix); + this.jobBuildDuration = factory.createRunCollector(CollectorType.BUILD_DURATION_GAUGE, labelNameArray, buildPrefix); + this.jobBuildStartMillis = factory.createRunCollector(CollectorType.BUILD_START_GAUGE, labelNameArray, buildPrefix); + this.jobBuildTestsTotal = factory.createRunCollector(CollectorType.TOTAL_TESTS_GAUGE, labelNameArray, buildPrefix); + 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); + this.jobBuildWaitingDurationGauge = factory.createRunCollector(CollectorType.BUILD_WAITING_GAUGE, labelNameArray, buildPrefix); } } - private final BuildMetrics lastBuildMetrics = new BuildMetrics("_last"); - - public JobCollector() { - } - @Override public List collect() { - logger.debug("Collecting metrics for prometheus"); + LOGGER.debug("Collecting metrics for prometheus"); - String namespace = ConfigurationUtils.getNamespace(); + CollectorFactory factory = new CollectorFactory(); List samples = new ArrayList<>(); - String fullname = "builds"; - String subsystem = ConfigurationUtils.getSubSystem(); - String jobAttribute = PrometheusConfiguration.get().getJobAttributeName(); - - String[] labelBaseNameArray = {jobAttribute, "repo"}; - - String[] labelNameArray = labelBaseNameArray; - if( PrometheusConfiguration.get().isAppendParamLabel() ){ - labelNameArray = Arrays.copyOf(labelNameArray, labelNameArray.length + 1); - labelNameArray[labelNameArray.length - 1] = "parameters"; - } - if( PrometheusConfiguration.get().isAppendStatusLabel() ){ - labelNameArray = Arrays.copyOf(labelNameArray, labelNameArray.length + 1); - labelNameArray[labelNameArray.length - 1] = "status"; - } - - String[] buildParameterNamesAsArray = PrometheusConfiguration.get().getLabeledBuildParameterNamesAsArray(); - for (String buildParam : buildParameterNamesAsArray) { - labelNameArray = Arrays.copyOf(labelNameArray, labelNameArray.length + 1); - labelNameArray[labelNameArray.length - 1] = buildParam.trim(); - } - String[] labelStageNameArray = Arrays.copyOf(labelBaseNameArray, labelBaseNameArray.length + 1); - labelStageNameArray[labelBaseNameArray.length] = "stage"; + String[] labelBaseNameArray = JobLabel.getBaseLabelNames(); + String[] labelNameArray = JobLabel.getJobLabelNames(); boolean processDisabledJobs = PrometheusConfiguration.get().isProcessingDisabledBuilds(); boolean ignoreBuildMetrics = @@ -154,257 +92,207 @@ public List collect() { !PrometheusConfiguration.get().isCountSuccessfulBuilds() && !PrometheusConfiguration.get().isCountUnstableBuilds(); + BuildCompletionListener listener = BuildCompletionListener.getInstance(); + if (ignoreBuildMetrics) { + listener.unregister(); return samples; } - // Below three metrics use labelNameArray which might include the optional labels + // Below metrics use labelNameArray which might include the optional labels // of "parameters" or "status" - summary = Summary.build() - .name(fullname + "_duration_milliseconds_summary") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Summary of Jenkins build times in milliseconds by Job") - .create(); - - jobSuccessCount = Counter.build() - .name(fullname + "_success_build_count") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Successful build count") - .create(); - - jobFailedCount = Counter.build() - .name(fullname + "_failed_build_count") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelNameArray) - .help("Failed build count") - .create(); + summary = factory.createRunCollector(CollectorType.BUILD_DURATION_SUMMARY, labelNameArray, null); + + + // Counter manager acts as a DB to retrieve any counters that are already in memory instead of reinitializing + // them with each iteration of collect. + var manager = CounterManager.getManager(); + MetricCollector, ? extends Collector> jobSuccessCount = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, labelBaseNameArray, null); + MetricCollector, ? extends Collector> jobFailedCount = manager.getCounter(CollectorType.BUILD_FAILED_COUNTER, labelBaseNameArray, null); + MetricCollector, ? extends Collector> jobTotalCount = manager.getCounter(CollectorType.BUILD_TOTAL_COUNTER, labelBaseNameArray, null); + MetricCollector, ? extends Collector> jobAbortedCount = manager.getCounter(CollectorType.BUILD_ABORTED_COUNTER, labelBaseNameArray, null); + MetricCollector, ? extends Collector> jobUnstableCount = manager.getCounter(CollectorType.BUILD_UNSTABLE_COUNTER, labelBaseNameArray, null); + + // This is a try with resources block it ensures close is called + // so if an exception occurs we don't reach deadlock. This is analogous to a using + // block where dispose is called after we leave the block. + // The closeable iterator synchronizes receiving jobs and reading the iterator, + // so we don't modify the collection while iterating. + try (CloseableIterator> iterator = listener.iterator()) { + // Go through each run received since the last scrape. + while (iterator.hasNext()) { + Run run = iterator.next(); + Job job = run.getParent(); + + // Calculate the metrics. + String[] labelValues = JobLabel.getBaseLabelValues(job); + jobFailedCount.calculateMetric(run, labelValues); + jobSuccessCount.calculateMetric(run, labelValues); + jobTotalCount.calculateMetric(run, labelValues); + jobAbortedCount.calculateMetric(run, labelValues); + jobUnstableCount.calculateMetric(run,labelValues); + } + } // This metric uses "base" labels as it is just the health score reported // by the job object and the optional labels params and status don't make much // sense in this context. - jobHealthScore = Gauge.build() - .name(fullname + "_health_score") - .subsystem(subsystem).namespace(namespace) - .labelNames(labelBaseNameArray) - .help("Health score of a job") - .create(); + jobHealthScoreGauge = factory.createJobCollector(CollectorType.HEALTH_SCORE_GAUGE, labelBaseNameArray); + + nbBuildsGauge = factory.createJobCollector(CollectorType.NB_BUILDS_GAUGE, labelBaseNameArray); + + buildDiscardGauge = factory.createJobCollector(CollectorType.BUILD_DISCARD_GAUGE, labelBaseNameArray); + + currentRunDurationGauge = factory.createJobCollector(CollectorType.CURRENT_RUN_DURATION_GAUGE, labelBaseNameArray); + + logUpdatedGauge = factory.createJobCollector(CollectorType.JOB_LOG_UPDATED_GAUGE, labelBaseNameArray); + + if (PrometheusConfiguration.get().isPerBuildMetrics()) { + labelNameArray = Arrays.copyOf(labelNameArray, labelNameArray.length + 1); + labelNameArray[labelNameArray.length - 1] = "number"; + perBuildMetrics.initCollectors(labelNameArray); + } // The lastBuildMetrics are initialized with the "base" labels - lastBuildMetrics.initCollectors(fullname, subsystem, namespace, labelBaseNameArray, labelStageNameArray); + lastBuildMetrics.initCollectors(labelBaseNameArray); + Jobs.forEachJob(job -> { - try{ - if (!job.isBuildable() && processDisabledJobs) { - logger.debug("job [{}] is disabled", job.getFullName()); - return; + try { + if (job.isBuildable()) { + if (!MetricStatusChecker.isJobEnabled(job.getFullName())) { + LOGGER.debug("Job [{}] is excluded by configuration", job.getFullName()); + return; + } + LOGGER.debug("Collecting metrics for job [{}]", job.getFullName()); + appendJobMetrics(job); + } else { + if (processDisabledJobs) { + appendJobMetrics(job); + } else { + LOGGER.debug("job [{}] is disabled", job.getFullName()); + } } - logger.debug("Collecting metrics for job [{}]", job.getFullName()); - appendJobMetrics(job); - } - catch (IllegalArgumentException e) { + } catch (IllegalArgumentException e) { if (!e.getMessage().contains("Incorrect number of labels")) { - logger.warn("Caught error when processing job [{}] error: ", job.getFullName(), e); + LOGGER.warn("Caught error when processing job [{}] error: ", job.getFullName(), e); } // else - ignore exception + } catch (Exception e) { + LOGGER.warn("Caught error when processing job [{}] error: ", job.getFullName(), e); } - catch( Exception e ){ - logger.warn("Caught error when processing job [{}] error: ", job.getFullName(), e); - } - - }); - addSamples(samples, summary.collect(), "Adding [{}] samples from summary"); - addSamples(samples, jobSuccessCount.collect(), "Adding [{}] samples from counter"); - addSamples(samples, jobFailedCount.collect(), "Adding [{}] samples from counter"); - addSamples(samples, jobHealthScore.collect(), "Adding [{}] samples from gauge"); + }); + addSamples(samples, summary.collect(), "Adding [{}] samples from summary ({})"); + addSamples(samples, jobSuccessCount.collect(), "Adding [{}] samples from counter ({})"); + addSamples(samples, jobFailedCount.collect(), "Adding [{}] samples from counter ({})"); + addSamples(samples, jobAbortedCount.collect(), "Adding [{}] samples from counter ({})"); + addSamples(samples, jobUnstableCount.collect(), "Adding [{}] samples from counter ({})"); + addSamples(samples, jobTotalCount.collect(), "Adding [{}] samples from counter ({})"); + addSamples(samples, jobHealthScoreGauge.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(samples, nbBuildsGauge.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(samples, buildDiscardGauge.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(samples, currentRunDurationGauge.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(samples, logUpdatedGauge.collect(), "Adding [{}] samples from gauge ({})"); addSamples(samples, lastBuildMetrics); + if (PrometheusConfiguration.get().isPerBuildMetrics()) { + addSamples(samples, perBuildMetrics); + } return samples; } private void addSamples(List allSamples, List newSamples, String logMessage) { - int sampleCount = newSamples.get(0).samples.size(); - if (sampleCount > 0) { - logger.debug(logMessage, sampleCount); - allSamples.addAll(newSamples); + for (MetricFamilySamples metricFamilySample : newSamples) { + int sampleCount = metricFamilySample.samples.size(); + if (sampleCount > 0) { + LOGGER.debug(logMessage, sampleCount, metricFamilySample.name); + allSamples.addAll(newSamples); + } } } private void addSamples(List allSamples, BuildMetrics buildMetrics) { - addSamples(allSamples, buildMetrics.jobBuildResultOrdinal.collect(), "Adding [{}] samples from gauge"); - addSamples(allSamples, buildMetrics.jobBuildResult.collect(), "Adding [{}] samples from gauge"); - addSamples(allSamples, buildMetrics.jobBuildDuration.collect(), "Adding [{}] samples from gauge"); - addSamples(allSamples, buildMetrics.jobBuildStartMillis.collect(), "Adding [{}] samples from gauge"); - addSamples(allSamples, buildMetrics.jobBuildTestsTotal.collect(), "Adding [{}] samples from gauge"); - addSamples(allSamples, buildMetrics.jobBuildTestsSkipped.collect(), "Adding [{}] samples from gauge"); - addSamples(allSamples, buildMetrics.jobBuildTestsFailing.collect(), "Adding [{}] samples from gauge"); - addSamples(allSamples, buildMetrics.stageSummary.collect(), "Adding [{}] samples from summary"); + addSamples(allSamples, buildMetrics.jobBuildResultOrdinal.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(allSamples, buildMetrics.jobBuildResult.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(allSamples, buildMetrics.jobBuildDuration.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(allSamples, buildMetrics.jobBuildWaitingDurationGauge.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(allSamples, buildMetrics.jobBuildStartMillis.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(allSamples, buildMetrics.jobBuildTestsTotal.collect(), "Adding [{}] samples from gauge ({})"); + addSamples(allSamples, buildMetrics.jobBuildTestsSkipped.collect(), "Adding [{}] samples from gauge ({})"); + 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 ({})"); } - protected void appendJobMetrics(Job job) { - // Add this to the repo as well so I can group by Github Repository - String repoName = StringUtils.substringBetween(job.getFullName(), "/"); - if (repoName == null) { - repoName = NOT_AVAILABLE; - } - String[] baseLabelValueArray = {job.getFullName(), repoName }; + protected void appendJobMetrics(Job job) { + boolean isPerBuildMetrics = PrometheusConfiguration.get().isPerBuildMetrics(); + String[] baseLabelValueArray = JobLabel.getBaseLabelValues(job); + + Run buildToCheck = job.getLastBuild(); - Run lastBuild = job.getLastBuild(); // Never built - if (null == lastBuild) { - logger.debug("job [{}] never built", job.getFullName()); + if (null == buildToCheck) { + LOGGER.debug("job [{}] never built", job.getFullName()); return; } - int score = job.getBuildHealth().getScore(); - jobHealthScore.labels(baseLabelValueArray).set(score); + if (buildToCheck.isBuilding()) { + LOGGER.debug("Build [{}] is currently building. Will calculate previous build.", buildToCheck.number); + buildToCheck = buildToCheck.getPreviousBuild(); + if (buildToCheck == null) { + LOGGER.debug("Previous build does not exist. Skipping calculation for [{}].", job.getFullName()); + return; + } + } - processRun(job, lastBuild, baseLabelValueArray, lastBuildMetrics); + LOGGER.debug("Calculating job metrics for [{}]", buildToCheck.number); - boolean isAppendParamLabel = PrometheusConfiguration.get().isAppendParamLabel(); - boolean isAppendStatusLabel = PrometheusConfiguration.get().isAppendStatusLabel(); - String[] buildParameterNamesAsArray = PrometheusConfiguration.get().getLabeledBuildParameterNamesAsArray(); + nbBuildsGauge.calculateMetric(job, baseLabelValueArray); + jobHealthScoreGauge.calculateMetric(job, baseLabelValueArray); + buildDiscardGauge.calculateMetric(job, baseLabelValueArray); + currentRunDurationGauge.calculateMetric(job, baseLabelValueArray); + logUpdatedGauge.calculateMetric(job, baseLabelValueArray); - Run run = lastBuild; + processRun(job, buildToCheck, baseLabelValueArray, lastBuildMetrics); + + Run run = buildToCheck; while (run != null) { - logger.debug("getting metrics for run [{}] from job [{}]", run.getNumber(), job.getName()); + LOGGER.debug("getting metrics for run [{}] from job [{}], include per run metrics [{}]", run.getNumber(), job.getName(), isPerBuildMetrics); if (Runs.includeBuildInMetrics(run)) { - logger.debug("getting build info for run [{}] from job [{}]", run.getNumber(), job.getName()); - - Result runResult = run.getResult(); - String[] labelValueArray = baseLabelValueArray; + LOGGER.debug("getting build info for run [{}] from job [{}]", run.getNumber(), job.getName()); + String[] labelValueArray = JobLabel.getJobLabelValues(job, run); - if( isAppendParamLabel ){ - String params = Runs.getBuildParameters(run).entrySet().stream().map(e -> "" + e.getKey() + "=" + String.valueOf(e.getValue())).collect(Collectors.joining(";")); - labelValueArray = Arrays.copyOf(labelValueArray, labelValueArray.length + 1); - labelValueArray[labelValueArray.length - 1] = params; - } - if( isAppendStatusLabel ){ - String resultString = UNDEFINED; - if (runResult != null) { - resultString = runResult.toString(); - } - labelValueArray = Arrays.copyOf(labelValueArray, labelValueArray.length + 1); - labelValueArray[labelValueArray.length - 1] = run.isBuilding() ? "RUNNING" : resultString; - } - - for (String configBuildParam : buildParameterNamesAsArray) { - labelValueArray = Arrays.copyOf(labelValueArray, labelValueArray.length + 1); - String paramValue = UNDEFINED; - Object paramInBuild = Runs.getBuildParameters(run).get(configBuildParam); - if (paramInBuild != null) { - paramValue = String.valueOf(paramInBuild); - } - labelValueArray[labelValueArray.length - 1] = paramValue; - } + summary.calculateMetric(run, labelValueArray); - long duration = run.getDuration(); - if (!run.isBuilding()) { - summary.labels(labelValueArray).observe(duration); - } + if (isPerBuildMetrics) { + labelValueArray = Arrays.copyOf(labelValueArray, labelValueArray.length + 1); + labelValueArray[labelValueArray.length - 1] = String.valueOf(run.getNumber()); - if (runResult != null && !run.isBuilding()) { - if (runResult.ordinal == 0 || runResult.ordinal == 1) { - jobSuccessCount.labels(labelValueArray).inc(); - } else { - jobFailedCount.labels(labelValueArray).inc(); - } + processRun(job, run, labelValueArray, perBuildMetrics); } } run = run.getPreviousBuild(); } } - private void processRun(Job job, Run run, String[] buildLabelValueArray, BuildMetrics buildMetrics) { - long millis; - Result runResult; - long duration; - int ordinal = -1; - duration = run.getDuration(); - millis = run.getStartTimeInMillis(); - runResult = run.getResult(); - if (null != runResult) { - ordinal = runResult.ordinal; - } - - /* - * _last_build_result _last_build_result_ordinal - * - * SUCCESS 0 true - The build had no errors. - * UNSTABLE 1 true - The build had some errors but they were not fatal. For example, some tests failed. - * FAILURE 2 false - The build had a fatal error. - * NOT_BUILT 3 false - The module was not built. - * ABORTED 4 false - The build was manually aborted. - */ - buildMetrics.jobBuildResultOrdinal.labels(buildLabelValueArray).set(ordinal); - buildMetrics.jobBuildResult.labels(buildLabelValueArray).set(ordinal < 2 ? 1 : 0); - - logger.debug("Processing run [{}] from job [{}]", run.getNumber(), job.getName()); - - buildMetrics.jobBuildStartMillis.labels(buildLabelValueArray).set(millis); - if (!run.isBuilding()) { - - buildMetrics.jobBuildDuration.labels(buildLabelValueArray).set(duration); - processRunTestsResults(run, buildLabelValueArray, buildMetrics); - - if (run instanceof WorkflowRun) { - logger.debug("run [{}] from job [{}] is of type workflowRun", run.getNumber(), job.getName()); - WorkflowRun workflowRun = (WorkflowRun) run; - if (workflowRun.getExecution() != null) { - processPipelineRunStages(job, run, workflowRun, buildMetrics.stageSummary); - } - } - } - } - - private void processRunTestsResults(Run run, String[] buildLabelValueArray, BuildMetrics buildMetrics) { - if (PrometheusConfiguration.get().isFetchTestResults() && hasTestResults(run) && !run.isBuilding()) { - int testsTotal = run.getAction(AbstractTestResultAction.class).getTotalCount(); - int testsFail = run.getAction(AbstractTestResultAction.class).getFailCount(); - int testsSkipped = run.getAction(AbstractTestResultAction.class).getSkipCount(); - - buildMetrics.jobBuildTestsTotal.labels(buildLabelValueArray).set(testsTotal); - buildMetrics.jobBuildTestsSkipped.labels(buildLabelValueArray).set(testsSkipped); - buildMetrics.jobBuildTestsFailing.labels(buildLabelValueArray).set(testsFail); - } + private void processRun(Job job, Run run, String[] buildLabelValueArray, BuildMetrics buildMetrics) { + LOGGER.debug("Processing run [{}] from job [{}]", run.getNumber(), job.getName()); + buildMetrics.jobBuildResultOrdinal.calculateMetric(run, buildLabelValueArray); + buildMetrics.jobBuildResult.calculateMetric(run, buildLabelValueArray); + buildMetrics.jobBuildStartMillis.calculateMetric(run, buildLabelValueArray); + 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); + buildMetrics.jobBuildLikelyStuck.calculateMetric(run,buildLabelValueArray); + buildMetrics.buildLogFileSizeGauge.calculateMetric(run, buildLabelValueArray); + buildMetrics.jobBuildWaitingDurationGauge.calculateMetric(run, buildLabelValueArray); } - private void processPipelineRunStages(Job job, Run latestfinishedRun, WorkflowRun workflowRun, Summary stageSummary) { - try { - logger.debug("getting the sorted stage nodes for run[{}] from job [{}]", latestfinishedRun.getNumber(), job.getName()); - List stages = getSortedStageNodes(workflowRun); - for (StageNodeExt stage : stages) { - observeStage(job, latestfinishedRun, stage, stageSummary); - } - } catch (NullPointerException e) { - // ignored - } - } - - private void observeStage(Job job, Run run, StageNodeExt stage, Summary stageSummary) { - logger.debug("Observing stage[{}] in run [{}] from job [{}]", stage.getName(), run.getNumber(), job.getName()); - // Add this to the repo as well so I can group by Github Repository - String repoName = StringUtils.substringBetween(job.getFullName(), "/"); - if (repoName == null) { - repoName = NOT_AVAILABLE; - } - String jobName = job.getFullName(); - String stageName = stage.getName(); - String[] labelValueArray = {jobName, repoName, stageName}; - - if (stage.getStatus() == StatusExt.SUCCESS || stage.getStatus() == StatusExt.UNSTABLE) { - logger.debug("getting duration for stage[{}] in run [{}] from job [{}]", stage.getName(), run.getNumber(), job.getName()); - long duration = stage.getDurationMillis(); - logger.debug("duration was [{}] for stage[{}] in run [{}] from job [{}]", duration, stage.getName(), run.getNumber(), job.getName()); - stageSummary.labels(labelValueArray).observe(duration); - } else { - logger.debug("Stage[{}] in run [{}] from job [{}] was not successful and will be ignored", stage.getName(), run.getNumber(), job.getName()); - } - } - - private boolean hasTestResults(Run job) { - return job.getAction(AbstractTestResultAction.class) != null; - } } diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/BaseCollectorFactory.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/BaseCollectorFactory.java new file mode 100644 index 000000000..7e04fc00d --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/BaseCollectorFactory.java @@ -0,0 +1,24 @@ +package org.jenkinsci.plugins.prometheus.collectors; + +import org.jenkinsci.plugins.prometheus.config.disabledmetrics.MetricStatusChecker; +import org.jenkinsci.plugins.prometheus.util.ConfigurationUtils; + +public abstract class BaseCollectorFactory { + + protected final String namespace; + protected final String subsystem; + + public BaseCollectorFactory() { + namespace = ConfigurationUtils.getNamespace(); + subsystem = ConfigurationUtils.getSubSystem(); + } + + + protected MetricCollector saveBuildCollector(MetricCollector collector) { + String fullName = namespace + "_" + subsystem + "_" + collector.calculateName(); + if (MetricStatusChecker.isEnabled(fullName)) { + return collector; + } + return new NoOpMetricCollector<>(); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/BaseMetricCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/BaseMetricCollector.java new file mode 100644 index 000000000..d9bfb38fd --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/BaseMetricCollector.java @@ -0,0 +1,90 @@ +package org.jenkinsci.plugins.prometheus.collectors; + +import io.prometheus.client.Collector; +import io.prometheus.client.SimpleCollector; + +import java.util.List; + +public abstract class BaseMetricCollector> implements MetricCollector { + + protected final static String SEPARATOR = "_"; + + protected final String[] labelNames; + protected final String namespace; + protected final String subsystem; + protected final String namePrefix; + protected final I collector; + + protected BaseMetricCollector(String[] labelNames, String namespace, String subsystem, String namePrefix) { + this.labelNames = labelNames; + this.namespace = namespace; + this.subsystem = subsystem; + this.namePrefix = namePrefix; + collector = initCollector(); + } + + protected BaseMetricCollector(String[] labelNames, String namespace, String subsystem) { + this.labelNames = labelNames; + this.namespace = namespace; + this.subsystem = subsystem; + this.namePrefix = ""; + collector = initCollector(); + } + + /** + * @return - the name of the collector without subsystem, namespace, prefix + */ + protected abstract CollectorType getCollectorType(); + + /** + * @return - the help text which should be displayed + */ + protected abstract String getHelpText(); + + /** + * @return - builder object of the type of collector + */ + protected abstract SimpleCollector.Builder getCollectorBuilder(); + + protected I initCollector() { + return getCollectorBuilder() + .name(calculateName()) + .subsystem(subsystem) + .namespace(namespace) + .labelNames(labelNames) + .help(getHelpText()) + .create(); + } + + @Override + public List collect() { + return collector.collect(); + } + + public String calculateName() { + String name = getCollectorType().getName(); + StringBuilder sb = new StringBuilder(); + if (isBaseNameSet()) { + sb.append(getBaseName()).append(SEPARATOR); + } + + if (isNamePrefixSet()) { + sb.append(namePrefix).append(SEPARATOR); + } + return sb.append(name).toString(); + } + + private boolean isBaseNameSet() { + return getBaseName() != null && !"".equals(getBaseName()); + } + + private boolean isNamePrefixSet() { + return namePrefix != null && !namePrefix.isEmpty(); + } + + protected String getBaseName() { + return ""; + } + + public abstract void calculateMetric(T jenkinsObject, String[] labelValues); +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorFactory.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorFactory.java new file mode 100644 index 000000000..cc72019de --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorFactory.java @@ -0,0 +1,69 @@ +package org.jenkinsci.plugins.prometheus.collectors; + +import com.cloudbees.simplediskusage.DiskItem; +import com.cloudbees.simplediskusage.JobDiskItem; +import hudson.model.Job; +import hudson.model.LoadStatistics; +import hudson.model.Run; +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 java.nio.file.FileStore; + +public class CollectorFactory { + + private final BuildCollectorFactory buildCollectorFactory; + private final JobCollectorFactory jobCollectorFactory; + private final JenkinsCollectorFactory jenkinsCollectorFactory; + 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, ? extends Collector> createCoverageRunCollector(CollectorType type, String[] labelNames) { + return coverageCollectorFactory.createCollector(type, labelNames); + } + + public MetricCollector, ? extends Collector> createRunCollector(CollectorType type, String[] labelNames, String prefix) { + return buildCollectorFactory.createCollector(type, labelNames, prefix); + } + + public MetricCollector, ? extends Collector> createJobCollector(CollectorType type, String[] labelNames) { + return jobCollectorFactory.createCollector(type, labelNames); + } + + public MetricCollector createJenkinsCollector(CollectorType type, String[] labelNames) { + return jenkinsCollectorFactory.createCollector(type, labelNames); + } + + public MetricCollector createLoadStatisticsCollector(CollectorType type, String[] labelNames, String prefix) { + return executorCollectorFactory.createCollector(type, labelNames, prefix); + } + + public MetricCollector createDiskItemCollector(CollectorType type, String[] labelNames) { + return diskCollectorFactory.createDiskItemCollector(type, labelNames); + } + + public MetricCollector createJobDiskItemCollector(CollectorType type, String[] labelNames) { + return diskCollectorFactory.createJobDiskItemCollector(type, labelNames); + } + + public MetricCollector createFileStoreCollector(CollectorType type, String[] labelNames) { + return diskCollectorFactory.createFileStoreCollector(type, labelNames); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorType.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorType.java new file mode 100644 index 000000000..c0222c5cf --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/CollectorType.java @@ -0,0 +1,85 @@ +package org.jenkinsci.plugins.prometheus.collectors; + +public enum CollectorType { + + JENKINS_UP_GAUGE("up"), + JENKINS_UPTIME_GAUGE("uptime"), + JENKINS_VERSION_INFO_GAUGE("version"), + JENKINS_QUIETDOWN_GAUGE("quietdown"), + NODES_ONLINE_GAUGE("nodes_online"), + BUILD_DURATION_GAUGE("build_duration_milliseconds"), + BUILD_WAITING_GAUGE("build_waiting_milliseconds"), + BUILD_LOGFILE_SIZE_GAUGE("build_logfile_size_bytes"), + BUILD_DURATION_SUMMARY("duration_milliseconds_summary"), + BUILD_RESULT_GAUGE("build_result"), + BUILD_RESULT_ORDINAL_GAUGE("build_result_ordinal"), + BUILD_START_GAUGE("build_start_time_milliseconds"), + BUILD_FAILED_COUNTER("failed_build_count"), + BUILD_TOTAL_COUNTER("total_build_count"), + BUILD_SUCCESSFUL_COUNTER("success_build_count"), + BUILD_UNSTABLE_COUNTER("unstable_build_count"), + BUILD_ABORTED_COUNTER("aborted_build_count"), + BUILD_LIKELY_STUCK_GAUGE("likely_stuck"), + + 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"), + BUILD_DISCARD_GAUGE("discard_active"), + CURRENT_RUN_DURATION_GAUGE("running_build_duration_milliseconds"), + EXECUTORS_AVAILABLE_GAUGE("available"), + EXECUTORS_BUSY_GAUGE("busy"), + EXECUTORS_CONNECTING_GAUGE("connecting"), + EXECUTORS_DEFINED_GAUGE("defined"), + EXECUTORS_IDLE_GAUGE("idle"), + EXECUTORS_ONLINE_GAUGE("online"), + EXECUTORS_QUEUE_LENGTH_GAUGE("queue_length"), + + DISK_USAGE_BYTES_GAUGE("disk_usage_bytes"), + DISK_USAGE_FILE_COUNT_GAUGE("disk_usage_file_count"), + FILE_STORE_AVAILABLE_GAUGE("file_store_available_bytes"), + FILE_STORE_CAPACITY_GAUGE("file_store_capacity_bytes"), + JOB_USAGE_BYTES_GAUGE("job_usage_bytes"), + + 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_CLASS_PERCENT("coverage_class_percent"), + + COVERAGE_BRANCH_COVERED("coverage_branch_covered"), + COVERAGE_BRANCH_MISSED("coverage_branch_missed"), + COVERAGE_BRANCH_TOTAL("coverage_branch_total"), + COVERAGE_BRANCH_PERCENT("coverage_branch_percent"), + + COVERAGE_INSTRUCTION_COVERED("coverage_instruction_covered"), + COVERAGE_INSTRUCTION_MISSED("coverage_instruction_missed"), + COVERAGE_INSTRUCTION_TOTAL("coverage_instruction_total"), + COVERAGE_INSTRUCTION_PERCENT("coverage_instruction_percent"), + + COVERAGE_FILE_COVERED("coverage_file_covered"), + COVERAGE_FILE_MISSED("coverage_file_missed"), + COVERAGE_FILE_TOTAL("coverage_file_total"), + COVERAGE_FILE_PERCENT("coverage_file_percent"), + + COVERAGE_LINE_COVERED("coverage_line_covered"), + COVERAGE_LINE_MISSED("coverage_line_missed"), + COVERAGE_LINE_TOTAL("coverage_line_total"), + COVERAGE_LINE_PERCENT("coverage_line_percent"), + + JOB_LOG_UPDATED_GAUGE("job_log_updated"); + + private final String name; + + CollectorType(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/MetricCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/MetricCollector.java new file mode 100644 index 000000000..a98416b87 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/MetricCollector.java @@ -0,0 +1,33 @@ +package org.jenkinsci.plugins.prometheus.collectors; + +import io.prometheus.client.Collector; + +import java.util.List; + +/** + * Implementations of this interface shall be able to construct and calculate any subclass of + * {@link io.prometheus.client.SimpleCollector} + * + * @param - any subclass of {@link io.prometheus.client.SimpleCollector} + */ +public interface MetricCollector { + + /** + * This method contains the logic to calculate a metric value based on the given Jenkins object (e.g. Job, Run,...) + * + * @param jenkinsObject - Examples: {@link hudson.model.Job}, {@link hudson.model.Run} + * @param labelValues - The label values for the calculation + */ + void calculateMetric(T jenkinsObject, String[] labelValues); + + /** + * Calling this method basically calls I.collect() + */ + List collect(); + + /** + * Calling this method will return the resulting name of the metric with base name and prefix + * @return the full name of the collector + */ + String calculateName(); +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/NoOpMetricCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/NoOpMetricCollector.java new file mode 100644 index 000000000..88ec02857 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/NoOpMetricCollector.java @@ -0,0 +1,24 @@ +package org.jenkinsci.plugins.prometheus.collectors; + +import io.prometheus.client.Collector; + +import java.util.ArrayList; +import java.util.List; + +public class NoOpMetricCollector implements MetricCollector { + @Override + public void calculateMetric(T jenkinsObject, String[] labelValues) { + // do nothing + } + + @Override + public List collect() { + // do nothing + return new ArrayList<>(); + } + + @Override + public String calculateName() { + return ""; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/TestBasedMetricCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/TestBasedMetricCollector.java new file mode 100644 index 000000000..ec7b0e586 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/TestBasedMetricCollector.java @@ -0,0 +1,31 @@ +package org.jenkinsci.plugins.prometheus.collectors; + +import hudson.model.Run; +import hudson.tasks.test.AbstractTestResultAction; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.builds.BuildsMetricCollector; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; + +public abstract class TestBasedMetricCollector> extends BuildsMetricCollector { + + public TestBasedMetricCollector(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + protected boolean canBeCalculated(Run run) { + if (run == null) { + return false; + } + + if (run.isBuilding()) { + return false; + } + + return PrometheusConfiguration.get().isFetchTestResults() && hasTestResults(run); + } + + + private boolean hasTestResults(Run job) { + return job.getAction(AbstractTestResultAction.class) != null; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildAbortedCounter.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildAbortedCounter.java new file mode 100644 index 000000000..10be23418 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildAbortedCounter.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import hudson.model.Run; +import io.prometheus.client.Counter; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildAbortedCounter extends BuildsMetricCollector, Counter> { + protected BuildAbortedCounter(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + protected BuildAbortedCounter(String[] labelNames, String namespace, String subsystem, String prefix) { + super(labelNames, namespace, subsystem, prefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_ABORTED_COUNTER; + } + + @Override + protected String getHelpText() { + return "aborted build count"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Counter.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + // Increment counter if result was unstable. + if(jenkinsObject.getResult() == Result.ABORTED){ + this.collector.labels(labelValues).inc(); + } + } +} 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 new file mode 100644 index 000000000..08478dcba --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCollectorFactory.java @@ -0,0 +1,54 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.BaseCollectorFactory; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.MetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.NoOpMetricCollector; + +public class BuildCollectorFactory extends BaseCollectorFactory { + + public MetricCollector, ? extends Collector> createCollector(CollectorType type, String[] labelNames, String prefix) { + switch (type) { + case BUILD_DURATION_GAUGE: + return saveBuildCollector(new BuildDurationGauge(labelNames, namespace, subsystem, prefix)); + case BUILD_DURATION_SUMMARY: + return saveBuildCollector(new BuildDurationSummary(labelNames, namespace, subsystem)); + case BUILD_FAILED_COUNTER: + return saveBuildCollector(new BuildFailedCounter(labelNames, namespace, subsystem)); + case BUILD_RESULT_GAUGE: + return saveBuildCollector(new BuildResultGauge(labelNames, namespace, subsystem, prefix)); + case BUILD_RESULT_ORDINAL_GAUGE: + return saveBuildCollector(new BuildResultOrdinalGauge(labelNames, namespace, subsystem, prefix)); + case BUILD_START_GAUGE: + return saveBuildCollector(new BuildStartGauge(labelNames, namespace, subsystem, prefix)); + case BUILD_SUCCESSFUL_COUNTER: + return saveBuildCollector(new BuildSuccessfulCounter(labelNames, namespace, subsystem)); + case FAILED_TESTS_GAUGE: + return saveBuildCollector(new FailedTestsGauge(labelNames, namespace, subsystem, prefix)); + case SKIPPED_TESTS_GAUGE: + 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: + return saveBuildCollector(new BuildLikelyStuckGauge(labelNames, namespace, subsystem, prefix)); + case BUILD_ABORTED_COUNTER: + return saveBuildCollector(new BuildAbortedCounter(labelNames, namespace, subsystem, prefix)); + case BUILD_UNSTABLE_COUNTER: + return saveBuildCollector(new BuildUnstableCounter(labelNames, namespace, subsystem, prefix)); + case BUILD_TOTAL_COUNTER: + return saveBuildCollector(new BuildTotalCounter(labelNames, namespace, subsystem, prefix)); + case BUILD_LOGFILE_SIZE_GAUGE: + return saveBuildCollector(new BuildLogFileSizeGauge(labelNames, namespace, subsystem, prefix)); + case BUILD_WAITING_GAUGE: + return saveBuildCollector(new BuildWaitingDurationGauge(labelNames, namespace, subsystem, prefix)); + default: + return new NoOpMetricCollector<>(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCompletionListener.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCompletionListener.java new file mode 100644 index 000000000..02829e8c3 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCompletionListener.java @@ -0,0 +1,129 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.model.listeners.RunListener; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/* + * Listens to builds that have been completed and stores them in a list. + * The JobCollector reads items in the list when it performs a scrape and + * publishes the data. + * Class extends https://javadoc.jenkins.io/hudson/model/listeners/RunListener.html + */ +public class BuildCompletionListener extends RunListener> { + // static instance of the class to use as a singleton. + private static BuildCompletionListener _Listener; + + // Lock to synchronize iteration and adding to the collection + private final Lock lock; + + // Holds the list o runs in queue. + private final List> runStack; + + // Iterable that defines a close method (allows us to use try resource) block + // in JobCollector.java + public interface CloseableIterator extends Iterator, AutoCloseable { + void close(); + } + + // Protected so no one can create their own copy of the class. + protected BuildCompletionListener(){ + runStack = Collections.synchronizedList(new ArrayList<>()); + lock = new ReentrantLock(); + } + + @Override + public void unregister() { + super.unregister(); + try { + lock.lock(); + this.runStack.clear(); + } finally { + lock.unlock(); + } + } + + /* + * Extension tells Jenkins to register this class as a RunListener and to use + * this method in order to retrieve an instance of the class. It is a singleton, + * so we can get the same reference registered in Jenkins in another class. + */ + @Extension + public synchronized static BuildCompletionListener getInstance(){ + if(_Listener == null){ + _Listener = new BuildCompletionListener(); + } + return _Listener; + } + + /* + * Fires on completion of a job. + */ + public void onCompleted(Run run, @NonNull TaskListener listener){ + push(run); + } + + /* + * Pushes a run onto the list + */ + private synchronized void push(Run run){ + // Acquire lock + lock.lock(); + + // Try to add the run to the list. If something goes wrong, make sure + // we still unlock the lock! + try{ + runStack.add(run); + } + finally{ + lock.unlock(); + } + } + + /* + * Returns a closeable iterator + */ + public synchronized CloseableIterator> iterator(){ + // acquire lock before iterating + lock.lock(); + return new CloseableIterator<>() { + // Get iterator from the list + private final Iterator> iterator = runStack.iterator(); + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Run next() { + return iterator.next(); + } + + @Override + public void remove() { + iterator.remove(); + } + + // When we close the iterator, clear the list right before we unlock. + // This ensures we don't see the same job twice if iterator is called again. + public void close() { + runStack.clear(); + lock.unlock(); + } + }; + } + + List> getRunStack() { + return runStack; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationGauge.java new file mode 100644 index 000000000..cad689d99 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationGauge.java @@ -0,0 +1,35 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildDurationGauge extends BuildsMetricCollector, Gauge> { + + protected BuildDurationGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_DURATION_GAUGE; + } + + @Override + protected String getHelpText() { + return "Build times in milliseconds of last build"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + if (!jenkinsObject.isBuilding()) { + collector.labels(labelValues).set(jenkinsObject.getDuration()); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationSummary.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationSummary.java new file mode 100644 index 000000000..438577819 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationSummary.java @@ -0,0 +1,36 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import io.prometheus.client.SimpleCollector; +import io.prometheus.client.Summary; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildDurationSummary extends BuildsMetricCollector, Summary> { + + protected BuildDurationSummary(String[] labelNames, String namespace, String subSystem) { + super(labelNames, namespace, subSystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_DURATION_SUMMARY; + } + + @Override + protected String getHelpText() { + return "Summary of Jenkins build times in milliseconds by Job"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Summary.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + if (!jenkinsObject.isBuilding()) { + long duration = jenkinsObject.getDuration(); + this.collector.labels(labelValues).observe(duration); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildFailedCounter.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildFailedCounter.java new file mode 100644 index 000000000..3a8db24e7 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildFailedCounter.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import hudson.model.Run; +import io.prometheus.client.Counter; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildFailedCounter extends BuildsMetricCollector, Counter> { + protected BuildFailedCounter(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + protected BuildFailedCounter(String[] labelNames, String namespace, String subsystem, String prefix) { + super(labelNames, namespace, subsystem, prefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_FAILED_COUNTER; + } + + @Override + protected String getHelpText() { + return "Failed build count"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Counter.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + // increment counter if the build failed. + if(jenkinsObject.getResult() == Result.FAILURE){ + this.collector.labels(labelValues).inc(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLikelyStuckGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLikelyStuckGauge.java new file mode 100644 index 000000000..c722aec73 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLikelyStuckGauge.java @@ -0,0 +1,44 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Executor; +import hudson.model.Run; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildLikelyStuckGauge extends BuildsMetricCollector, Gauge> { + + protected BuildLikelyStuckGauge(String[] labelNames, String namespace, String subsystem, String prefix) { + super(labelNames, namespace, subsystem, prefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_LIKELY_STUCK_GAUGE; + } + + @Override + protected String getHelpText() { + return "Provides a hint if a build is likely stuck. Uses Jenkins function Executor#isLikelyStuck."; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run run, String[] labelValues) { + if (run == null || !run.isBuilding()) { + return; + } + + Executor executor = run.getExecutor(); + if (executor == null) { + return; + } + + double likelyStuckDoubleValue = executor.isLikelyStuck() ? 1.0 : 0.0; + this.collector.labels(labelValues).set(likelyStuckDoubleValue); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLogFileSizeGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLogFileSizeGauge.java new file mode 100644 index 000000000..c568e2225 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLogFileSizeGauge.java @@ -0,0 +1,39 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.console.AnnotatedLargeText; +import hudson.model.Run; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildLogFileSizeGauge extends BuildsMetricCollector, Gauge> { + + protected BuildLogFileSizeGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_LOGFILE_SIZE_GAUGE; + } + + @Override + protected String getHelpText() { + return "Build logfile size in bytes"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + if (!jenkinsObject.isBuilding()) { + AnnotatedLargeText logText = jenkinsObject.getLogText(); + long logFileSize = logText.length(); + + collector.labels(labelValues).set(logFileSize); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultGauge.java new file mode 100644 index 000000000..2343a8248 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultGauge.java @@ -0,0 +1,46 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildResultGauge extends BuildsMetricCollector, Gauge> { + + protected BuildResultGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_RESULT_GAUGE; + } + + @Override + protected String getHelpText() { + return "Build status of a job as a boolean (0 or 1)"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + /* + * _last_build_result _last_build_result_ordinal + * + * SUCCESS 0 true - The build had no errors. + * UNSTABLE 1 true - The build had some errors, but they were not fatal. For example, some tests failed. + * FAILURE 2 false - The build had a fatal error. + * NOT_BUILT 3 false - The module was not built. + * ABORTED 4 false - The build was manually aborted. + */ + int ordinal = -1; + var runResult = jenkinsObject.getResult(); + if (null != runResult) { + ordinal = runResult.ordinal; + } + collector.labels(labelValues).set(ordinal < 2 ? 1 : 0); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultOrdinalGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultOrdinalGauge.java new file mode 100644 index 000000000..3558baf76 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultOrdinalGauge.java @@ -0,0 +1,52 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import hudson.model.Run; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildResultOrdinalGauge extends BuildsMetricCollector, Gauge> { + + protected BuildResultOrdinalGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_RESULT_ORDINAL_GAUGE; + } + + @Override + protected String getHelpText() { + return "Build status of a job."; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + if (this.collector == null) { + return; + } + + if (jenkinsObject == null) { + return; + } + + int ordinal = -1; + Result result = jenkinsObject.getResult(); + if (null != result) { + ordinal = result.ordinal; + } + + if (labelValues == null) { + this.collector.labels().set(ordinal); + } else { + this.collector.labels(labelValues).set(ordinal); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildStartGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildStartGauge.java new file mode 100644 index 000000000..a33603316 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildStartGauge.java @@ -0,0 +1,34 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildStartGauge extends BuildsMetricCollector, Gauge> { + + protected BuildStartGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_START_GAUGE; + } + + @Override + protected String getHelpText() { + return "Last build start timestamp in milliseconds"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + long millis = jenkinsObject.getStartTimeInMillis(); + collector.labels(labelValues).set(millis); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildSuccessfulCounter.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildSuccessfulCounter.java new file mode 100644 index 000000000..d45c100a8 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildSuccessfulCounter.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import hudson.model.Run; +import io.prometheus.client.Counter; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildSuccessfulCounter extends BuildsMetricCollector, Counter> { + protected BuildSuccessfulCounter(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + protected BuildSuccessfulCounter(String[] labelNames, String namespace, String subsystem, String prefix) { + super(labelNames, namespace, subsystem, prefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_SUCCESSFUL_COUNTER; + } + + @Override + protected String getHelpText() { + return "Successful build count"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Counter.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + // Increment the counter if the result of run was successful. + if(jenkinsObject.getResult() == Result.SUCCESS){ + this.collector.labels(labelValues).inc(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildTotalCounter.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildTotalCounter.java new file mode 100644 index 000000000..1219d4712 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildTotalCounter.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import hudson.model.Run; +import io.prometheus.client.Counter; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildTotalCounter extends BuildsMetricCollector, Counter> { + protected BuildTotalCounter(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + protected BuildTotalCounter(String[] labelNames, String namespace, String subsystem, String prefix) { + super(labelNames, namespace, subsystem, prefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_TOTAL_COUNTER; + } + + @Override + protected String getHelpText() { + return "Total build count"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Counter.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + // Increment counter every run that is completed. + if(jenkinsObject.getResult() != Result.NOT_BUILT){ + this.collector.labels(labelValues).inc(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildUnstableCounter.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildUnstableCounter.java new file mode 100644 index 000000000..b1367cfc0 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildUnstableCounter.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import hudson.model.Run; +import io.prometheus.client.Counter; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildUnstableCounter extends BuildsMetricCollector, Counter> { + protected BuildUnstableCounter(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + protected BuildUnstableCounter(String[] labelNames, String namespace, String subsystem, String prefix) { + super(labelNames, namespace, subsystem, prefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_UNSTABLE_COUNTER; + } + + @Override + protected String getHelpText() { + return "Unstable build count"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Counter.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + // increment counter if the result was unstable. + if(jenkinsObject.getResult() == Result.UNSTABLE){ + this.collector.labels(labelValues).inc(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildWaitingDurationGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildWaitingDurationGauge.java new file mode 100644 index 000000000..b0892b607 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildWaitingDurationGauge.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import jenkins.metrics.impl.TimeInQueueAction; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class BuildWaitingDurationGauge extends BuildsMetricCollector, Gauge> { + + protected BuildWaitingDurationGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_WAITING_GAUGE; + } + + @Override + protected String getHelpText() { + return "Duration this Run spent queuing, that is the wall time from when it entered the queue until it left the queue."; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + if (!jenkinsObject.isBuilding()) { + TimeInQueueAction action = jenkinsObject.getAction(TimeInQueueAction.class); + if (action != null) { + long queuingDurationMillis = action.getQueuingDurationMillis(); + collector.labels(labelValues).set(queuingDurationMillis); + } + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildsMetricCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildsMetricCollector.java new file mode 100644 index 000000000..a46cea16d --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildsMetricCollector.java @@ -0,0 +1,19 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; + +public abstract class BuildsMetricCollector> extends BaseMetricCollector { + protected BuildsMetricCollector(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + protected BuildsMetricCollector(String[] labelNames, String namespace, String subsystem, String prefix) { + super(labelNames, namespace, subsystem, prefix); + } + + @Override + protected String getBaseName() { + return "builds"; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/CounterManager.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/CounterManager.java new file mode 100644 index 000000000..8675b8ddf --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/CounterManager.java @@ -0,0 +1,132 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.MetricCollector; +import org.jenkinsci.plugins.prometheus.util.ConfigurationUtils; + +import java.util.Arrays; +import java.util.HashMap; + +/* + * This class acts as a database to keep track of counters and return an existing counter + * if it has already been initialized. This class is necessary due to the way the plugin handles + * configuration changes. Changing the plugins configuration can cause the labels of a metric + * to change. This manager compares whether it has seen a counter with a specific label before + * and returns an existing counter if it exists. Otherwise, it will return a new counter initialized at zero. + */ +public class CounterManager { + // Keeps track of Counters we have seen. + private final HashMap, ? extends Collector>> registeredCounters; + + // Static singleton instance. + private static CounterManager manager; + + // Initialize the map + private CounterManager() { + registeredCounters = new HashMap<>(); + } + + /* + * Singleton instance method to get the manager. + */ + public static CounterManager getManager() { + if (manager == null) { + manager = new CounterManager(); + } + return manager; + } + + /* + Determine if we have seen the counter before + returns true if so otherwise false. + */ + private Boolean hasCounter(CounterEntry entry) { + return registeredCounters.containsKey(entry); + } + + /* + * Retrieves a counter or initializes a new one if it doesn't exist + * @return Metric collector counter. + */ + public MetricCollector, ? extends Collector> getCounter(CollectorType type, String[]labels, String prefix){ + CounterEntry entry = new CounterEntry(type, labels, prefix); + + // If we have the counter return it. + if(hasCounter(entry)){ + return registeredCounters.get(entry); + } + + // Uses the build collector factory to initialize any new instances if necessary. + var factory = new BuildCollectorFactory(); + var counterCollector = factory.createCollector(type, labels, prefix); + + // Add the collector to the map + registeredCounters.put(entry, counterCollector); + return counterCollector; + } + + /* + * Holds metadata about a counter to determine if we need to create a new counter. + */ + private static class CounterEntry { + // Labels that the counter was initialized with + private final String[] labels; + + // What collector type the counter is. + private final CollectorType type; + + // Prefix of the counter. + private final String prefix; + + // namespace of the counter + private final String namespace; + + /* + * Creates new counter entry + */ + public CounterEntry(CollectorType type, String[] labels, String prefix) { + this.labels = labels; + this.type = type; + this.prefix = prefix; + this.namespace = ConfigurationUtils.getNamespace(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + + CounterEntry entry = (CounterEntry) obj; + + // Compare the prefix + if(this.prefix != null && !this.prefix.equals(entry.prefix)){ + return false; + } + + // Compare the entry Counter type + if(this.type != entry.type){ + return false; + } + + // Compare namespace values. + if(this.namespace != null && !this.namespace.equals(entry.namespace)){ + return false; + } + + // Compare labels + return Arrays.equals(labels, entry.labels); + } + + @Override + public int hashCode() { + int typeHash = type != null ? type.hashCode() : 0; + int prefixHash = prefix != null ? prefix.hashCode() : 0; + int namespaceHash = namespace != null ? namespace.hashCode() : 0; + return 31 * (typeHash + Arrays.hashCode(labels) + prefixHash + namespaceHash); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/FailedTestsGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/FailedTestsGauge.java new file mode 100644 index 000000000..c930518de --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/FailedTestsGauge.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import hudson.tasks.test.AbstractTestResultAction; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.TestBasedMetricCollector; + +public class FailedTestsGauge extends TestBasedMetricCollector, Gauge> { + + protected FailedTestsGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_FAILED_TESTS; + } + + @Override + protected String getHelpText() { + return "Number of failing tests during the last build"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + if (!canBeCalculated(jenkinsObject)) { + return; + } + + int testsFailed = jenkinsObject.getAction(AbstractTestResultAction.class).getFailCount(); + collector.labels(labelValues).set(testsFailed); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/JobLabel.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/JobLabel.java new file mode 100644 index 000000000..f1ac19fac --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/JobLabel.java @@ -0,0 +1,104 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Job; +import hudson.model.Result; +import hudson.model.Run; +import org.apache.commons.lang3.StringUtils; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.jenkinsci.plugins.prometheus.util.Runs; + +import java.util.Arrays; +import java.util.stream.Collectors; + +/* + * Static class that defines what labels should be added to a metric. + */ +public class JobLabel { + private static final String NOT_AVAILABLE = "NA"; + private static final String UNDEFINED = "UNDEFINED"; + + /* + * Returns the base label names based of the Prometheus configuration + * @return an array of label names + */ + public static String[] getBaseLabelNames(){ + String jobAttribute = PrometheusConfiguration.get().getJobAttributeName(); + + return new String[]{jobAttribute, "repo", "buildable"}; + } + + /* + * Returns job specific label names which appends build parameters and status + * as a possible label. + * @return array of label names with appropriate labels based off the prometheus config. + */ + public static String[] getJobLabelNames(){ + String[] labelNameArray = getBaseLabelNames(); + if (PrometheusConfiguration.get().isAppendParamLabel()) { + labelNameArray = Arrays.copyOf(labelNameArray, labelNameArray.length + 1); + labelNameArray[labelNameArray.length - 1] = "parameters"; + } + if (PrometheusConfiguration.get().isAppendStatusLabel()) { + labelNameArray = Arrays.copyOf(labelNameArray, labelNameArray.length + 1); + labelNameArray[labelNameArray.length - 1] = "status"; + } + + String[] buildParameterNamesAsArray = PrometheusConfiguration.get().getLabeledBuildParameterNamesAsArray(); + for (String buildParam : buildParameterNamesAsArray) { + labelNameArray = Arrays.copyOf(labelNameArray, labelNameArray.length + 1); + labelNameArray[labelNameArray.length - 1] = buildParam.trim(); + } + return labelNameArray; + } + + /* + * Gets the base label values of a job. Common fields between all the metric label values. + * @return array of base labels. + */ + public static String[] getBaseLabelValues(Job job) { + // Add this to the repo as well, so I can group by GitHub Repository + String repoName = StringUtils.substringBetween(job.getFullName(), "/"); + if (repoName == null) { + repoName = NOT_AVAILABLE; + } + return new String[]{ job.getFullName(), repoName, String.valueOf(job.isBuildable()) }; + } + + /* + * Gets label values specific to job centric metrics. + * @return array of label values for a job. + */ + public static String[] getJobLabelValues(Job job, Run run) { + boolean isAppendParamLabel = PrometheusConfiguration.get().isAppendParamLabel(); + boolean isAppendStatusLabel = PrometheusConfiguration.get().isAppendStatusLabel(); + String[] buildParameterNamesAsArray = PrometheusConfiguration.get().getLabeledBuildParameterNamesAsArray(); + + Result runResult = run.getResult(); + String[] labelValueArray = getBaseLabelValues(job); + if (isAppendParamLabel) { + String params = Runs.getBuildParameters(run).entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.joining(";")); + labelValueArray = Arrays.copyOf(labelValueArray, labelValueArray.length + 1); + labelValueArray[labelValueArray.length - 1] = params; + } + if (isAppendStatusLabel) { + String resultString = UNDEFINED; + if (runResult != null) { + resultString = runResult.toString(); + } + labelValueArray = Arrays.copyOf(labelValueArray, labelValueArray.length + 1); + labelValueArray[labelValueArray.length - 1] = run.isBuilding() ? "RUNNING" : resultString; + } + + for (String configBuildParam : buildParameterNamesAsArray) { + labelValueArray = Arrays.copyOf(labelValueArray, labelValueArray.length + 1); + String paramValue = UNDEFINED; + Object paramInBuild = Runs.getBuildParameters(run).get(configBuildParam); + if (paramInBuild != null) { + paramValue = String.valueOf(paramInBuild); + } + labelValueArray[labelValueArray.length - 1] = paramValue; + } + return labelValueArray; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/SkippedTestsGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/SkippedTestsGauge.java new file mode 100644 index 000000000..44f1c8e1c --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/SkippedTestsGauge.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import hudson.tasks.test.AbstractTestResultAction; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.TestBasedMetricCollector; + +public class SkippedTestsGauge extends TestBasedMetricCollector, Gauge> { + + protected SkippedTestsGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.SKIPPED_TESTS_GAUGE; + } + + @Override + protected String getHelpText() { + return "Number of skipped tests during the last build"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + if (!canBeCalculated(jenkinsObject)) { + return; + } + + int testsSkipped = jenkinsObject.getAction(AbstractTestResultAction.class).getSkipCount(); + collector.labels(labelValues).set(testsSkipped); + } +} 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 new file mode 100644 index 000000000..f4c44caf2 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageSummary.java @@ -0,0 +1,87 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import com.cloudbees.workflow.rest.external.StageNodeExt; +import com.cloudbees.workflow.rest.external.StatusExt; +import hudson.model.Job; +import hudson.model.Run; +import io.prometheus.client.SimpleCollector; +import io.prometheus.client.Summary; +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 StageSummary extends BuildsMetricCollector, Summary> { + + private static final Logger LOGGER = LoggerFactory.getLogger(StageSummary.class); + + protected StageSummary(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.STAGE_SUMMARY; + } + + @Override + protected String getHelpText() { + return "Summary of Jenkins build times by Job and Stage in the last build"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Summary.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + if (jenkinsObject.isBuilding()) { + return; + } + + if (!(jenkinsObject instanceof WorkflowRun)) { + return; + } + + var workflowRun = (WorkflowRun) jenkinsObject; + 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); + + if (stage.getStatus() == StatusExt.SUCCESS || stage.getStatus() == StatusExt.UNSTABLE) { + LOGGER.debug("getting duration for stage[{}] in run [{}] from job [{}]", stage.getName(), run.getNumber(), job.getName()); + long duration = stage.getDurationMillis(); + LOGGER.debug("duration was [{}] for stage[{}] in run [{}] from job [{}]", duration, stage.getName(), run.getNumber(), job.getName()); + collector.labels(values).observe(duration); + } else { + LOGGER.debug("Stage[{}] in run [{}] from job [{}] was not successful and will be ignored", stage.getName(), run.getNumber(), job.getName()); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/TotalTestsGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/TotalTestsGauge.java new file mode 100644 index 000000000..7a324237f --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/builds/TotalTestsGauge.java @@ -0,0 +1,39 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import hudson.tasks.test.AbstractTestResultAction; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.TestBasedMetricCollector; + +public class TotalTestsGauge extends TestBasedMetricCollector, Gauge> { + protected TotalTestsGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.TOTAL_TESTS_GAUGE; + } + + @Override + protected String getHelpText() { + return "Number of total tests during the last build"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + if (!canBeCalculated(jenkinsObject)) { + return; + } + + int testsTotal = jenkinsObject.getAction(AbstractTestResultAction.class).getTotalCount(); + this.collector.labels(labelValues).set(testsTotal); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchCoveredGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchCoveredGauge.java new file mode 100644 index 000000000..d99997fca --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchCoveredGauge.java @@ -0,0 +1,46 @@ +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, 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 getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.BRANCH, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getCovered()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchMissedGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchMissedGauge.java new file mode 100644 index 000000000..d299a0a47 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchMissedGauge.java @@ -0,0 +1,46 @@ +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, 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 getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.BRANCH, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getMissed()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchPercentGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchPercentGauge.java new file mode 100644 index 000000000..4c07cf162 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchPercentGauge.java @@ -0,0 +1,47 @@ +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 CoverageBranchPercentGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageBranchPercentGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_BRANCH_PERCENT; + } + + @Override + protected String getHelpText() { + return "Returns the coverage of branches in percent"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.BRANCH, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(calculatePercentage(coverage)); + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchTotalGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchTotalGauge.java new file mode 100644 index 000000000..0af9a8d37 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchTotalGauge.java @@ -0,0 +1,46 @@ +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, 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 getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.BRANCH, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getTotal()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassCoveredGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassCoveredGauge.java new file mode 100644 index 000000000..b7addbae2 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassCoveredGauge.java @@ -0,0 +1,46 @@ +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 CoverageClassCoveredGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageClassCoveredGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_CLASS_COVERED; + } + + @Override + protected String getHelpText() { + return "Returns the number of classes covered"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.CLASS, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getCovered()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassMissedGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassMissedGauge.java new file mode 100644 index 000000000..30cab4fa6 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassMissedGauge.java @@ -0,0 +1,46 @@ +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 CoverageClassMissedGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageClassMissedGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_CLASS_MISSED; + } + + @Override + protected String getHelpText() { + return "Returns the number of classes missed"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.CLASS, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getMissed()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassPercentGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassPercentGauge.java new file mode 100644 index 000000000..61ff0a8af --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassPercentGauge.java @@ -0,0 +1,47 @@ +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 CoverageClassPercentGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageClassPercentGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_CLASS_PERCENT; + } + + @Override + protected String getHelpText() { + return "Returns the coverage of classes in percent"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.CLASS, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(calculatePercentage(coverage)); + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassTotalGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassTotalGauge.java new file mode 100644 index 000000000..d36b39d58 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassTotalGauge.java @@ -0,0 +1,46 @@ +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 CoverageClassTotalGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageClassTotalGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_CLASS_TOTAL; + } + + @Override + protected String getHelpText() { + return "Returns the number of classes total"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.CLASS, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getTotal()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageCollectorFactory.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageCollectorFactory.java new file mode 100644 index 000000000..c6bf04941 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageCollectorFactory.java @@ -0,0 +1,63 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import hudson.model.Run; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.BaseCollectorFactory; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.MetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.NoOpMetricCollector; + +public class CoverageCollectorFactory extends BaseCollectorFactory { + + public MetricCollector, ? extends Collector> createCollector(CollectorType type, String[] labelNames) { + switch (type) { + case COVERAGE_CLASS_COVERED: + return saveBuildCollector(new CoverageClassCoveredGauge(labelNames, namespace, subsystem)); + case COVERAGE_CLASS_MISSED: + return saveBuildCollector(new CoverageClassMissedGauge(labelNames, namespace, subsystem)); + case COVERAGE_CLASS_TOTAL: + return saveBuildCollector(new CoverageClassTotalGauge(labelNames, namespace, subsystem)); + case COVERAGE_CLASS_PERCENT: + return saveBuildCollector(new CoverageClassPercentGauge(labelNames, namespace, subsystem)); + + case COVERAGE_BRANCH_COVERED: + return saveBuildCollector(new CoverageBranchCoveredGauge(labelNames, namespace, subsystem)); + case COVERAGE_BRANCH_MISSED: + return saveBuildCollector(new CoverageBranchMissedGauge(labelNames, namespace, subsystem)); + case COVERAGE_BRANCH_TOTAL: + return saveBuildCollector(new CoverageBranchTotalGauge(labelNames, namespace, subsystem)); + case COVERAGE_BRANCH_PERCENT: + return saveBuildCollector(new CoverageBranchPercentGauge(labelNames, namespace, subsystem)); + + case COVERAGE_INSTRUCTION_COVERED: + return saveBuildCollector(new CoverageInstructionCoveredGauge(labelNames, namespace, subsystem)); + case COVERAGE_INSTRUCTION_MISSED: + return saveBuildCollector(new CoverageInstructionMissedGauge(labelNames, namespace, subsystem)); + case COVERAGE_INSTRUCTION_TOTAL: + return saveBuildCollector(new CoverageInstructionTotalGauge(labelNames, namespace, subsystem)); + case COVERAGE_INSTRUCTION_PERCENT: + return saveBuildCollector(new CoverageInstructionPercentGauge(labelNames, namespace, subsystem)); + + case COVERAGE_FILE_COVERED: + return saveBuildCollector(new CoverageFileCoveredGauge(labelNames, namespace, subsystem)); + case COVERAGE_FILE_MISSED: + return saveBuildCollector(new CoverageFileMissedGauge(labelNames, namespace, subsystem)); + case COVERAGE_FILE_TOTAL: + return saveBuildCollector(new CoverageFileTotalGauge(labelNames, namespace, subsystem)); + case COVERAGE_FILE_PERCENT: + return saveBuildCollector(new CoverageFilePercentGauge(labelNames, namespace, subsystem)); + + case COVERAGE_LINE_COVERED: + return saveBuildCollector(new CoverageLineCoveredGauge(labelNames, namespace, subsystem)); + case COVERAGE_LINE_MISSED: + return saveBuildCollector(new CoverageLineMissedGauge(labelNames, namespace, subsystem)); + case COVERAGE_LINE_TOTAL: + return saveBuildCollector(new CoverageLineTotalGauge(labelNames, namespace, subsystem)); + case COVERAGE_LINE_PERCENT: + return saveBuildCollector(new CoverageLinePercentGauge(labelNames, namespace, subsystem)); + + default: + return new NoOpMetricCollector<>(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileCoveredGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileCoveredGauge.java new file mode 100644 index 000000000..ee5032b59 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileCoveredGauge.java @@ -0,0 +1,46 @@ +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 CoverageFileCoveredGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageFileCoveredGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_FILE_COVERED; + } + + @Override + protected String getHelpText() { + return "Returns the number of files covered"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.FILE, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getCovered()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileMissedGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileMissedGauge.java new file mode 100644 index 000000000..b07355738 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileMissedGauge.java @@ -0,0 +1,46 @@ +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 CoverageFileMissedGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageFileMissedGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_FILE_MISSED; + } + + @Override + protected String getHelpText() { + return "Returns the number of files missed"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.FILE, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getMissed()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFilePercentGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFilePercentGauge.java new file mode 100644 index 000000000..335e7e60d --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFilePercentGauge.java @@ -0,0 +1,47 @@ +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 CoverageFilePercentGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageFilePercentGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_FILE_PERCENT; + } + + @Override + protected String getHelpText() { + return "Returns the coverage of files in percent"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.FILE, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(calculatePercentage(coverage)); + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileTotalGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileTotalGauge.java new file mode 100644 index 000000000..4b877babd --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileTotalGauge.java @@ -0,0 +1,46 @@ +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 CoverageFileTotalGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageFileTotalGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_FILE_TOTAL; + } + + @Override + protected String getHelpText() { + return "Returns the number of files total"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.FILE, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getTotal()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionCoveredGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionCoveredGauge.java new file mode 100644 index 000000000..75f2d4570 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionCoveredGauge.java @@ -0,0 +1,46 @@ +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 CoverageInstructionCoveredGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageInstructionCoveredGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_INSTRUCTION_COVERED; + } + + @Override + protected String getHelpText() { + return "Returns the number of instructions covered"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.INSTRUCTION, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getCovered()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionMissedGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionMissedGauge.java new file mode 100644 index 000000000..8fb9f4fb8 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionMissedGauge.java @@ -0,0 +1,46 @@ +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 CoverageInstructionMissedGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageInstructionMissedGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_INSTRUCTION_MISSED; + } + + @Override + protected String getHelpText() { + return "Returns the number of instructions missed"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.INSTRUCTION, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getMissed()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionPercentGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionPercentGauge.java new file mode 100644 index 000000000..fafef3d85 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionPercentGauge.java @@ -0,0 +1,47 @@ +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 CoverageInstructionPercentGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageInstructionPercentGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_INSTRUCTION_PERCENT; + } + + @Override + protected String getHelpText() { + return "Returns the coverage of instructions in percent"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.INSTRUCTION, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(calculatePercentage(coverage)); + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionTotalGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionTotalGauge.java new file mode 100644 index 000000000..ee482a467 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionTotalGauge.java @@ -0,0 +1,46 @@ +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 CoverageInstructionTotalGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageInstructionTotalGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_INSTRUCTION_TOTAL; + } + + @Override + protected String getHelpText() { + return "Returns the number of instructions total"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.INSTRUCTION, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getTotal()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineCoveredGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineCoveredGauge.java new file mode 100644 index 000000000..efcef5a08 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineCoveredGauge.java @@ -0,0 +1,46 @@ +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 CoverageLineCoveredGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageLineCoveredGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_LINE_COVERED; + } + + @Override + protected String getHelpText() { + return "Returns the number of lines covered"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.LINE, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getCovered()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineMissedGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineMissedGauge.java new file mode 100644 index 000000000..35934d279 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineMissedGauge.java @@ -0,0 +1,46 @@ +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 CoverageLineMissedGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageLineMissedGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_LINE_MISSED; + } + + @Override + protected String getHelpText() { + return "Returns the number of lines missed"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.LINE, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getMissed()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLinePercentGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLinePercentGauge.java new file mode 100644 index 000000000..66f1187db --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLinePercentGauge.java @@ -0,0 +1,47 @@ +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 CoverageLinePercentGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageLinePercentGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_LINE_PERCENT; + } + + @Override + protected String getHelpText() { + return "Returns the coverage of lines in percent"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.LINE, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(calculatePercentage(coverage)); + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineTotalGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineTotalGauge.java new file mode 100644 index 000000000..14d71d388 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineTotalGauge.java @@ -0,0 +1,46 @@ +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 CoverageLineTotalGauge extends CoverageMetricsCollector, Gauge> { + + protected CoverageLineTotalGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.COVERAGE_LINE_TOTAL; + } + + @Override + protected String getHelpText() { + return "Returns the number of lines total"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Run jenkinsObject, String[] labelValues) { + + Optional optional = getCoverage(jenkinsObject, Metric.LINE, Baseline.PROJECT); + if (optional.isEmpty()) { + collector.labels(labelValues).set(-1); + return; + } + + Coverage coverage = optional.get(); + collector.labels(labelValues).set(coverage.getTotal()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageMetricsCollector.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageMetricsCollector.java new file mode 100644 index 000000000..094d427c7 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageMetricsCollector.java @@ -0,0 +1,49 @@ +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.jenkins.plugins.coverage.metrics.steps.CoverageBuildAction; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.builds.BuildsMetricCollector; + +import java.util.Optional; + +public abstract class CoverageMetricsCollector> extends BuildsMetricCollector { + protected CoverageMetricsCollector(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + protected Optional getCoverage(Run jenkinsRun, Metric metric, Baseline baseline) { + + CoverageBuildAction coverageBuildAction = jenkinsRun.getAction(CoverageBuildAction.class); + if (coverageBuildAction == null) { + return Optional.empty(); + } + + return coverageBuildAction.getAllValues(baseline).stream() + .filter(value -> metric.equals(value.getMetric()) && value instanceof Coverage) + .map(x -> (Coverage)x) + .findFirst(); + } + + protected double calculatePercentage(Coverage coverage) { + if (coverage == null) { + return -1.0; + } + + long covered = coverage.getCovered(); + long total = coverage.getTotal(); + + if (covered >= 0 && total >= 0) { + if (total != 0) { + return (double) (covered * 100) / total; + } else { + return -1.0; + } + } else { + return -1.0; + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskCollectorFactory.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskCollectorFactory.java new file mode 100644 index 000000000..d247c4428 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskCollectorFactory.java @@ -0,0 +1,47 @@ +package org.jenkinsci.plugins.prometheus.collectors.disk; + +import com.cloudbees.simplediskusage.DiskItem; +import com.cloudbees.simplediskusage.JobDiskItem; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.BaseCollectorFactory; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.MetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.NoOpMetricCollector; + +import java.nio.file.FileStore; +import java.util.Objects; + +import static org.jenkinsci.plugins.prometheus.collectors.CollectorType.DISK_USAGE_BYTES_GAUGE; +import static org.jenkinsci.plugins.prometheus.collectors.CollectorType.DISK_USAGE_FILE_COUNT_GAUGE; +import static org.jenkinsci.plugins.prometheus.collectors.CollectorType.JOB_USAGE_BYTES_GAUGE; + +public class DiskCollectorFactory extends BaseCollectorFactory { + + public MetricCollector createDiskItemCollector(CollectorType type, String[] labelNames) { + if (Objects.requireNonNull(type) == DISK_USAGE_BYTES_GAUGE) { + return saveBuildCollector(new DiskUsageBytesGauge(labelNames, namespace, subsystem)); + } + if (Objects.requireNonNull(type) == DISK_USAGE_FILE_COUNT_GAUGE) { + return saveBuildCollector(new DiskUsageFileCountGauge(labelNames, namespace, subsystem)); + } + return new NoOpMetricCollector<>(); + } + + public MetricCollector createJobDiskItemCollector(CollectorType type, String[] labelNames) { + if (Objects.requireNonNull(type) == JOB_USAGE_BYTES_GAUGE) { + return saveBuildCollector(new JobUsageBytesGauge(labelNames, namespace, subsystem)); + } + return new NoOpMetricCollector<>(); + } + + public MetricCollector createFileStoreCollector(CollectorType type, String[] labelNames) { + switch (type) { + case FILE_STORE_AVAILABLE_GAUGE: + return saveBuildCollector(new FileStoreAvailableGauge(labelNames, namespace, subsystem)); + case FILE_STORE_CAPACITY_GAUGE: + return saveBuildCollector(new FileStoreCapacityGauge(labelNames, namespace, subsystem)); + default: + return new NoOpMetricCollector<>(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageBytesGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageBytesGauge.java new file mode 100644 index 000000000..81a743b79 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageBytesGauge.java @@ -0,0 +1,43 @@ +package org.jenkinsci.plugins.prometheus.collectors.disk; + +import com.cloudbees.simplediskusage.DiskItem; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class DiskUsageBytesGauge extends BaseMetricCollector { + + protected DiskUsageBytesGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.DISK_USAGE_BYTES_GAUGE; + } + + @Override + protected String getHelpText() { + return "Disk usage of first level folder in JENKINS_HOME in bytes"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(DiskItem jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + + Long usage = jenkinsObject.getUsage(); + if (usage == null) { + return; + } + + this.collector.labels(labelValues).set(usage * 1024); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageFileCountGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageFileCountGauge.java new file mode 100644 index 000000000..db2688abd --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageFileCountGauge.java @@ -0,0 +1,41 @@ +package org.jenkinsci.plugins.prometheus.collectors.disk; + +import com.cloudbees.simplediskusage.DiskItem; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class DiskUsageFileCountGauge extends BaseMetricCollector { + + protected DiskUsageFileCountGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.DISK_USAGE_FILE_COUNT_GAUGE; + } + + @Override + protected String getHelpText() { + return "Disk usage file count of the first level folder in JENKINS_HOME"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(DiskItem jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + Long count = jenkinsObject.getCount(); + if (count == null) { + return; + } + this.collector.labels(labelValues).set(count); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/FileStoreAvailableGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/FileStoreAvailableGauge.java new file mode 100644 index 000000000..e9597795f --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/FileStoreAvailableGauge.java @@ -0,0 +1,49 @@ +package org.jenkinsci.plugins.prometheus.collectors.disk; + +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.FileStore; + +public class FileStoreAvailableGauge extends BaseMetricCollector { + + + private static final Logger LOGGER = LoggerFactory.getLogger(FileStoreAvailableGauge.class); + + protected FileStoreAvailableGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.FILE_STORE_AVAILABLE_GAUGE; + } + + @Override + protected String getHelpText() { + return "Estimated available space on the file stores used by Jenkins"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(FileStore jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + try { + this.collector.labels(labelValues).set(jenkinsObject.getUsableSpace()); + } catch (IOException | RuntimeException e) { + LOGGER.debug("Failed to get usable space of {}", jenkinsObject, e); + this.collector.labels(labelValues).set(Double.NaN); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/FileStoreCapacityGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/FileStoreCapacityGauge.java new file mode 100644 index 000000000..da3c67520 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/FileStoreCapacityGauge.java @@ -0,0 +1,49 @@ +package org.jenkinsci.plugins.prometheus.collectors.disk; + +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.FileStore; + +public class FileStoreCapacityGauge extends BaseMetricCollector { + + + private static final Logger LOGGER = LoggerFactory.getLogger(FileStoreCapacityGauge.class); + + protected FileStoreCapacityGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.FILE_STORE_CAPACITY_GAUGE; + } + + @Override + protected String getHelpText() { + return "Total size in bytes of the file stores used by Jenkins"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(FileStore jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + try { + this.collector.labels(labelValues).set(jenkinsObject.getTotalSpace()); + } catch (IOException e) { + LOGGER.debug("Failed to get total space of {}", jenkinsObject, e); + this.collector.labels(labelValues).set(Double.NaN); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/JobUsageBytesGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/JobUsageBytesGauge.java new file mode 100644 index 000000000..a9188d940 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/disk/JobUsageBytesGauge.java @@ -0,0 +1,42 @@ +package org.jenkinsci.plugins.prometheus.collectors.disk; + +import com.cloudbees.simplediskusage.JobDiskItem; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class JobUsageBytesGauge extends BaseMetricCollector { + + protected JobUsageBytesGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.JOB_USAGE_BYTES_GAUGE; + } + + @Override + protected String getHelpText() { + return "Amount of disk usage (bytes) for each job in Jenkins"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(JobDiskItem jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + Long usage = jenkinsObject.getUsage(); + if (usage == null) { + return; + } + + this.collector.labels(labelValues).set(usage * 1024); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorCollectorFactory.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorCollectorFactory.java new file mode 100644 index 000000000..80d341ce8 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorCollectorFactory.java @@ -0,0 +1,33 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import hudson.model.LoadStatistics; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.BaseCollectorFactory; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.MetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.NoOpMetricCollector; + +public class ExecutorCollectorFactory extends BaseCollectorFactory { + + public MetricCollector createCollector(CollectorType type, String[] labelNames, String prefix) { + switch (type) { + case EXECUTORS_AVAILABLE_GAUGE: + return saveBuildCollector(new ExecutorsAvailableGauge(labelNames, namespace, subsystem, prefix)); + case EXECUTORS_BUSY_GAUGE: + return saveBuildCollector(new ExecutorsBusyGauge(labelNames, namespace, subsystem, prefix)); + case EXECUTORS_CONNECTING_GAUGE: + return saveBuildCollector(new ExecutorsConnectingGauge(labelNames, namespace, subsystem, prefix)); + case EXECUTORS_DEFINED_GAUGE: + return saveBuildCollector(new ExecutorsDefinedGauge(labelNames, namespace, subsystem, prefix)); + case EXECUTORS_IDLE_GAUGE: + return saveBuildCollector(new ExecutorsIdleGauge(labelNames, namespace, subsystem, prefix)); + case EXECUTORS_ONLINE_GAUGE: + return saveBuildCollector(new ExecutorsOnlineGauge(labelNames, namespace, subsystem, prefix)); + case EXECUTORS_QUEUE_LENGTH_GAUGE: + return saveBuildCollector(new ExecutorsQueueLengthGauge(labelNames, namespace, subsystem, prefix)); + default: + return new NoOpMetricCollector<>(); + } + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsAvailableGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsAvailableGauge.java new file mode 100644 index 000000000..904f36b68 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsAvailableGauge.java @@ -0,0 +1,38 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import hudson.model.LoadStatistics; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class ExecutorsAvailableGauge extends BaseMetricCollector { + + + protected ExecutorsAvailableGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.EXECUTORS_AVAILABLE_GAUGE; + } + + @Override + protected String getHelpText() { + return "Executors Available"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(LoadStatistics.LoadStatisticsSnapshot jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.labels(labelValues).set(jenkinsObject.getAvailableExecutors()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsBusyGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsBusyGauge.java new file mode 100644 index 000000000..3c4de5c6e --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsBusyGauge.java @@ -0,0 +1,38 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import hudson.model.LoadStatistics; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class ExecutorsBusyGauge extends BaseMetricCollector { + + + protected ExecutorsBusyGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.EXECUTORS_BUSY_GAUGE; + } + + @Override + protected String getHelpText() { + return "Executors Busy"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(LoadStatistics.LoadStatisticsSnapshot jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.labels(labelValues).set(jenkinsObject.getBusyExecutors()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsConnectingGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsConnectingGauge.java new file mode 100644 index 000000000..9a9f5eb4f --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsConnectingGauge.java @@ -0,0 +1,38 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import hudson.model.LoadStatistics; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class ExecutorsConnectingGauge extends BaseMetricCollector { + + + protected ExecutorsConnectingGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.EXECUTORS_CONNECTING_GAUGE; + } + + @Override + protected String getHelpText() { + return "Executors Connecting"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(LoadStatistics.LoadStatisticsSnapshot jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.labels(labelValues).set(jenkinsObject.getConnectingExecutors()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsDefinedGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsDefinedGauge.java new file mode 100644 index 000000000..840b934df --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsDefinedGauge.java @@ -0,0 +1,38 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import hudson.model.LoadStatistics; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class ExecutorsDefinedGauge extends BaseMetricCollector { + + + protected ExecutorsDefinedGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.EXECUTORS_DEFINED_GAUGE; + } + + @Override + protected String getHelpText() { + return "Executors Defined"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(LoadStatistics.LoadStatisticsSnapshot jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.labels(labelValues).set(jenkinsObject.getDefinedExecutors()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsIdleGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsIdleGauge.java new file mode 100644 index 000000000..e5783ff8a --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsIdleGauge.java @@ -0,0 +1,38 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import hudson.model.LoadStatistics; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class ExecutorsIdleGauge extends BaseMetricCollector { + + + protected ExecutorsIdleGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.EXECUTORS_IDLE_GAUGE; + } + + @Override + protected String getHelpText() { + return "Executors Idle"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(LoadStatistics.LoadStatisticsSnapshot jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.labels(labelValues).set(jenkinsObject.getIdleExecutors()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsOnlineGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsOnlineGauge.java new file mode 100644 index 000000000..534bf5f99 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsOnlineGauge.java @@ -0,0 +1,38 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import hudson.model.LoadStatistics; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class ExecutorsOnlineGauge extends BaseMetricCollector { + + + protected ExecutorsOnlineGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.EXECUTORS_ONLINE_GAUGE; + } + + @Override + protected String getHelpText() { + return "Executors Online"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(LoadStatistics.LoadStatisticsSnapshot jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.labels(labelValues).set(jenkinsObject.getOnlineExecutors()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsQueueLengthGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsQueueLengthGauge.java new file mode 100644 index 000000000..858334f8a --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsQueueLengthGauge.java @@ -0,0 +1,38 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import hudson.model.LoadStatistics; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class ExecutorsQueueLengthGauge extends BaseMetricCollector { + + + protected ExecutorsQueueLengthGauge(String[] labelNames, String namespace, String subsystem, String namePrefix) { + super(labelNames, namespace, subsystem, namePrefix); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.EXECUTORS_QUEUE_LENGTH_GAUGE; + } + + @Override + protected String getHelpText() { + return "Executors Queue Length"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(LoadStatistics.LoadStatisticsSnapshot jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.labels(labelValues).set(jenkinsObject.getQueueLength()); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsCollectorFactory.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsCollectorFactory.java new file mode 100644 index 000000000..21491e467 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsCollectorFactory.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import io.prometheus.client.Collector; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.collectors.BaseCollectorFactory; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.MetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.NoOpMetricCollector; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; + +/** + * All Collectors need to be created via the CollectorFactory + */ +public class JenkinsCollectorFactory extends BaseCollectorFactory { + + public MetricCollector createCollector(CollectorType type, String[] labelNames) { + switch (type) { + case JENKINS_UP_GAUGE: + return saveBuildCollector(new JenkinsUpGauge(labelNames, namespace, subsystem)); + case JENKINS_QUIETDOWN_GAUGE: + return saveBuildCollector(new JenkinsQuietDownGauge(labelNames, namespace, subsystem)); + case NODES_ONLINE_GAUGE: + if (!isNodeOnlineGaugeEnabled()) { + return new NoOpMetricCollector<>(); + } + return saveBuildCollector(new NodesOnlineGauge(labelNames, namespace, subsystem)); + case JENKINS_UPTIME_GAUGE: + return saveBuildCollector(new JenkinsUptimeGauge(labelNames, namespace, subsystem)); + case JENKINS_VERSION_INFO_GAUGE: + return saveBuildCollector(new JenkinsVersionInfo(labelNames, namespace, subsystem)); + default: + return new NoOpMetricCollector<>(); + } + } + + private boolean isNodeOnlineGaugeEnabled() { + return PrometheusConfiguration.get().isCollectNodeStatus(); + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsQuietDownGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsQuietDownGauge.java new file mode 100644 index 000000000..e22f42ba6 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsQuietDownGauge.java @@ -0,0 +1,37 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class JenkinsQuietDownGauge extends BaseMetricCollector { + + JenkinsQuietDownGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.JENKINS_QUIETDOWN_GAUGE; + } + + @Override + protected String getHelpText() { + return "Is Jenkins in quiet mode"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Jenkins jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.set(jenkinsObject.isQuietingDown() ? 1 : 0); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUpGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUpGauge.java new file mode 100644 index 000000000..b8e80383c --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUpGauge.java @@ -0,0 +1,37 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class JenkinsUpGauge extends BaseMetricCollector { + + JenkinsUpGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.JENKINS_UP_GAUGE; + } + + @Override + protected String getHelpText() { + return "Is Jenkins ready to receive requests"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Jenkins jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.set(jenkinsObject.getInitLevel() == hudson.init.InitMilestone.COMPLETED ? 1 : 0); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUptimeGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUptimeGauge.java new file mode 100644 index 000000000..cd034728b --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUptimeGauge.java @@ -0,0 +1,46 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import hudson.model.Computer; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +import java.time.Clock; + +public class JenkinsUptimeGauge extends BaseMetricCollector { + + JenkinsUptimeGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.JENKINS_UPTIME_GAUGE; + } + + @Override + protected String getHelpText() { + return "Time since Jenkins machine was initialized"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Jenkins jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + Computer computer = jenkinsObject.toComputer(); + if (computer == null) { + return; + } + long upTime = computer.getConnectTime(); + // Using Clock to be able to mock in test + collector.set(Clock.systemUTC().millis() - upTime); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsVersionInfo.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsVersionInfo.java new file mode 100644 index 000000000..caa736496 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsVersionInfo.java @@ -0,0 +1,37 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import io.prometheus.client.Info; +import io.prometheus.client.SimpleCollector; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class JenkinsVersionInfo extends BaseMetricCollector { + + JenkinsVersionInfo(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.JENKINS_VERSION_INFO_GAUGE; + } + + @Override + protected String getHelpText() { + return "Jenkins Application Version"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Info.build(); + } + + @Override + public void calculateMetric(Jenkins jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + this.collector.info("version", Jenkins.VERSION); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/NodesOnlineGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/NodesOnlineGauge.java new file mode 100644 index 000000000..d0ba7dc20 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/NodesOnlineGauge.java @@ -0,0 +1,51 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import hudson.model.Computer; +import hudson.model.Node; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.collectors.BaseMetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; + +public class NodesOnlineGauge extends BaseMetricCollector { + + NodesOnlineGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.NODES_ONLINE_GAUGE; + } + + @Override + protected String getHelpText() { + return "Jenkins nodes online status"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Jenkins jenkinsObject, String[] labelValues) { + if (jenkinsObject == null) { + return; + } + for (Node node : jenkinsObject.getNodes()) { + //Check whether the node is online or offline + Computer comp = node.toComputer(); + if (comp == null) { + continue; + } + + if (comp.isOnline()) { // https://javadoc.jenkins.io/hudson/model/Computer.html + this.collector.labels(node.getNodeName()).set(1); + } else { + this.collector.labels(node.getNodeName()).set(0); + } + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/BuildDiscardGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/BuildDiscardGauge.java new file mode 100644 index 000000000..da32b67f5 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/BuildDiscardGauge.java @@ -0,0 +1,37 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.model.Job; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import jenkins.model.BuildDiscarder; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.builds.BuildsMetricCollector; + +public class BuildDiscardGauge extends BuildsMetricCollector, Gauge> { + + protected BuildDiscardGauge(String[] labelNames, String namespace, String subSystem) { + super(labelNames, namespace, subSystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_DISCARD_GAUGE; + } + + @Override + protected String getHelpText() { + return "Indicates if the build discarder is active for the given job"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Job jenkinsObject, String[] labelValues) { + BuildDiscarder buildDiscarder = jenkinsObject.getBuildDiscarder(); + double status = buildDiscarder != null ? 1.0 : 0.0; + this.collector.labels(labelValues).set(status); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/CurrentRunDurationGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/CurrentRunDurationGauge.java new file mode 100644 index 000000000..f28b86dfb --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/CurrentRunDurationGauge.java @@ -0,0 +1,45 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.model.Job; +import hudson.model.Run; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.builds.BuildsMetricCollector; + +import java.time.Clock; + +public class CurrentRunDurationGauge extends BuildsMetricCollector, Gauge> { + + protected CurrentRunDurationGauge(String[] labelNames, String namespace, String subSystem) { + super(labelNames, namespace, subSystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.CURRENT_RUN_DURATION_GAUGE; + } + + @Override + protected String getHelpText() { + return "Indicates the runtime of the run currently building if there is a run currently building"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Job jenkinsObject, String[] labelValues) { + + Run runningBuild = jenkinsObject.getLastBuild(); + if (runningBuild != null && runningBuild.isBuilding()) { + long start = runningBuild.getStartTimeInMillis(); + // Using Clock to be able to mock in test + long end = Clock.systemUTC().millis(); + long duration = Math.max(end - start, 0); + this.collector.labels(labelValues).set(duration); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/HealthScoreGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/HealthScoreGauge.java new file mode 100644 index 000000000..389be1755 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/HealthScoreGauge.java @@ -0,0 +1,36 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.model.Job; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.builds.BuildsMetricCollector; + +public class HealthScoreGauge extends BuildsMetricCollector, Gauge> { + + protected HealthScoreGauge(String[] labelNames, String namespace, String subSystem) { + super(labelNames, namespace, subSystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.HEALTH_SCORE_GAUGE; + } + + @Override + protected String getHelpText() { + return "Health score of a job"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Job jenkinsObject, String[] labelValues) { + int score = jenkinsObject.getBuildHealth().getScore(); + this.collector.labels(labelValues).set(score); + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/JobCollectorFactory.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/JobCollectorFactory.java new file mode 100644 index 000000000..966ded5f5 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/JobCollectorFactory.java @@ -0,0 +1,29 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.model.Job; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.BaseCollectorFactory; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.MetricCollector; +import org.jenkinsci.plugins.prometheus.collectors.NoOpMetricCollector; + +public class JobCollectorFactory extends BaseCollectorFactory { + + public MetricCollector, ? extends Collector> createCollector(CollectorType type, String[] labelNames) { + switch (type) { + case HEALTH_SCORE_GAUGE: + return saveBuildCollector(new HealthScoreGauge(labelNames, namespace, subsystem)); + case NB_BUILDS_GAUGE: + return saveBuildCollector(new NbBuildsGauge(labelNames, namespace, subsystem)); + case BUILD_DISCARD_GAUGE: + return saveBuildCollector(new BuildDiscardGauge(labelNames, namespace, subsystem)); + case CURRENT_RUN_DURATION_GAUGE: + return saveBuildCollector(new CurrentRunDurationGauge(labelNames, namespace, subsystem)); + case JOB_LOG_UPDATED_GAUGE: + return saveBuildCollector(new LogUpdatedGauge(labelNames, namespace, subsystem)); + default: + return new NoOpMetricCollector<>(); + } + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/LogUpdatedGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/LogUpdatedGauge.java new file mode 100644 index 000000000..b9e1acfb2 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/LogUpdatedGauge.java @@ -0,0 +1,38 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.model.Job; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.builds.BuildsMetricCollector; + +public class LogUpdatedGauge extends BuildsMetricCollector, Gauge> { + + protected LogUpdatedGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.JOB_LOG_UPDATED_GAUGE; + } + + @Override + protected String getHelpText() { + return "Provides a hint if a job is still logging. Uses Jenkins function Job#isLogUpdated"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Job jenkinsObject, String[] labelValues) { + + if (jenkinsObject != null && jenkinsObject.isBuilding()) { + boolean logUpdated = jenkinsObject.isLogUpdated(); + this.collector.labels(labelValues).set(logUpdated ? 1.0 : 0.0); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/NbBuildsGauge.java b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/NbBuildsGauge.java new file mode 100644 index 000000000..29af287e2 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/collectors/jobs/NbBuildsGauge.java @@ -0,0 +1,43 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.model.Job; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.builds.BuildsMetricCollector; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class NbBuildsGauge extends BuildsMetricCollector, Gauge> { + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + protected NbBuildsGauge(String[] labelNames, String namespace, String subsystem) { + super(labelNames, namespace, subsystem); + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.NB_BUILDS_GAUGE; + } + + @Override + protected String getHelpText() { + return "Number of builds available for this job"; + } + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + @Override + public void calculateMetric(Job jenkinsObject, String[] labelValues) { + lock.readLock().lock(); + try { + int nbBuilds = jenkinsObject.getBuildsAsMap().size(); + this.collector.labels(labelValues).set(nbBuilds); + } finally { + lock.readLock().unlock(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration.java b/src/main/java/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration.java index 2848db4ca..ff8d0c323 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration.java @@ -1,16 +1,17 @@ package org.jenkinsci.plugins.prometheus.config; +import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.Extension; -import hudson.model.Descriptor; import hudson.util.FormValidation; import jenkins.YesNoMaybe; import jenkins.model.GlobalConfiguration; import jenkins.model.Jenkins; -import net.sf.json.JSONException; import net.sf.json.JSONObject; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; +import org.jenkinsci.plugins.prometheus.config.disabledmetrics.DisabledMetricConfig; +import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerRequest2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,15 +33,14 @@ public class PrometheusConfiguration extends GlobalConfiguration { private static final String DEFAULT_ENDPOINT = "prometheus"; static final String COLLECTING_METRICS_PERIOD_IN_SECONDS = "COLLECTING_METRICS_PERIOD_IN_SECONDS"; static final long DEFAULT_COLLECTING_METRICS_PERIOD_IN_SECONDS = TimeUnit.MINUTES.toSeconds(2); - private static final String COLLECT_DISK_USAGE = "COLLECT_DISK_USAGE"; - static final boolean DEFAULT_COLLECT_DISK_USAGE = true; + static final String COLLECT_DISK_USAGE = "COLLECT_DISK_USAGE"; private String urlName = null; private String additionalPath; private String defaultNamespace = "default"; private String jobAttributeName = "jenkins_job"; private boolean useAuthenticatedEndpoint; - private Long collectingMetricsPeriodInSeconds = null; + private long collectingMetricsPeriodInSeconds = -1L; private boolean countSuccessfulBuilds = true; private boolean countUnstableBuilds = true; @@ -53,53 +53,45 @@ public class PrometheusConfiguration extends GlobalConfiguration { private boolean appendParamLabel = false; private boolean appendStatusLabel = false; + private boolean perBuildMetrics = false; + private transient boolean collectDiskUsageEnvironmentVariableSet = false; + private String labeledBuildParameterNames = ""; private boolean collectDiskUsage = true; + private boolean collectCodeCoverage = false; + private boolean collectNodeStatus = true; + + @CheckForNull + private DisabledMetricConfig disabledMetricConfig; public PrometheusConfiguration() { load(); - setPath(urlName); + setPath(getPath()); setCollectingMetricsPeriodInSeconds(collectingMetricsPeriodInSeconds); - setCollectDiskUsage(null); + setCollectDiskUsageBasedOnEnvironmentVariableIfDefined(); } public static PrometheusConfiguration get() { - Descriptor configuration = Jenkins.getInstance().getDescriptor(PrometheusConfiguration.class); - return (PrometheusConfiguration) configuration; + return (PrometheusConfiguration) Jenkins.get().getDescriptor(PrometheusConfiguration.class); } @Override - public boolean configure(StaplerRequest req, JSONObject json) throws FormException { - setPath(json.getString("path")); - setCollectDiskUsage(json.getBoolean("collectDiskUsage")); - useAuthenticatedEndpoint = json.getBoolean("useAuthenticatedEndpoint"); - defaultNamespace = json.getString("defaultNamespace"); - jobAttributeName = json.getString("jobAttributeName"); - countSuccessfulBuilds = json.getBoolean("countSuccessfulBuilds"); - countUnstableBuilds = json.getBoolean("countUnstableBuilds"); - countFailedBuilds = json.getBoolean("countFailedBuilds"); - countNotBuiltBuilds = json.getBoolean("countNotBuiltBuilds"); - countAbortedBuilds = json.getBoolean("countAbortedBuilds"); - fetchTestResults = json.getBoolean("fetchTestResults"); - collectingMetricsPeriodInSeconds = validateProcessingMetricsPeriodInSeconds(json); - processingDisabledBuilds = json.getBoolean("processingDisabledBuilds"); - appendParamLabel = json.getBoolean("appendParamLabel"); - appendStatusLabel = json.getBoolean("appendStatusLabel"); - - labeledBuildParameterNames = json.getString("labeledBuildParameterNames"); - + public boolean configure(StaplerRequest2 req, JSONObject json) { + disabledMetricConfig = null; + req.bindJSON(this, json); save(); - return super.configure(req, json); + return true; } public String getPath() { - return StringUtils.isEmpty(additionalPath) ? urlName : urlName + "/" + additionalPath; + return StringUtils.isEmpty(additionalPath) ? urlName : urlName + additionalPath; } + @DataBoundSetter public void setPath(String path) { if (path == null) { Map env = System.getenv(); @@ -108,146 +100,177 @@ public void setPath(String path) { urlName = path.split("/")[0]; List pathParts = Arrays.asList(path.split("/")); additionalPath = (pathParts.size() > 1 ? "/" : "") + StringUtils.join(pathParts.subList(1, pathParts.size()), "/"); - save(); } public String getJobAttributeName() { return jobAttributeName; } + @DataBoundSetter public void setJobAttributeName(String jobAttributeName) { this.jobAttributeName = jobAttributeName; - save(); } public String getDefaultNamespace() { return defaultNamespace; } + @DataBoundSetter public void setDefaultNamespace(String path) { this.defaultNamespace = path; - save(); } - public void setCollectDiskUsage(Boolean collectDiskUsage) { - if (collectDiskUsage == null) { - Map env = System.getenv(); - this.collectDiskUsage = Boolean.parseBoolean(env.getOrDefault(COLLECT_DISK_USAGE, "true")); - } - else { - this.collectDiskUsage = collectDiskUsage; + @DataBoundSetter + public void setCollectDiskUsage(boolean collectDiskUsage) { + this.collectDiskUsage = collectDiskUsage; + } + + public void setCollectDiskUsageBasedOnEnvironmentVariableIfDefined() { + try { + final String envValue = System.getenv(COLLECT_DISK_USAGE); + if (envValue != null) { + setCollectDiskUsage(getValidBooleanValueOrThrowException(envValue)); + collectDiskUsageEnvironmentVariableSet = true; + } + } catch (IllegalArgumentException e) { + logger.warn("Unable to parse environment variable '{}'. Must either be 'true' or 'false'. Ignoring...", COLLECT_DISK_USAGE); } + } - save(); + public boolean isCollectDiskUsageEnvironmentVariableSet() { + return collectDiskUsageEnvironmentVariableSet; + } + + private boolean getValidBooleanValueOrThrowException(String value) throws IllegalArgumentException { + if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { + return Boolean.parseBoolean(value); + } + throw new IllegalArgumentException(); } public boolean getCollectDiskUsage() { return collectDiskUsage; } - public boolean getDefaultCollectDiskUsage() {return DEFAULT_COLLECT_DISK_USAGE; } - public long getCollectingMetricsPeriodInSeconds() { return collectingMetricsPeriodInSeconds; } - public void setCollectingMetricsPeriodInSeconds(Long collectingMetricsPeriodInSeconds) { - if (collectingMetricsPeriodInSeconds == null) { + @DataBoundSetter + public void setCollectingMetricsPeriodInSeconds(long collectingMetricsPeriodInSeconds) { + if (collectingMetricsPeriodInSeconds == -1L) { this.collectingMetricsPeriodInSeconds = parseLongFromEnv(); } else { this.collectingMetricsPeriodInSeconds = collectingMetricsPeriodInSeconds; } - save(); } public boolean isUseAuthenticatedEndpoint() { return useAuthenticatedEndpoint; } + @DataBoundSetter public void setUseAuthenticatedEndpoint(boolean useAuthenticatedEndpoint) { this.useAuthenticatedEndpoint = useAuthenticatedEndpoint; - save(); } public boolean isCountSuccessfulBuilds() { return countSuccessfulBuilds; } + @DataBoundSetter public void setCountSuccessfulBuilds(boolean countSuccessfulBuilds) { this.countSuccessfulBuilds = countSuccessfulBuilds; - save(); } public boolean isCountUnstableBuilds() { return countUnstableBuilds; } + @DataBoundSetter public void setCountUnstableBuilds(boolean countUnstableBuilds) { this.countUnstableBuilds = countUnstableBuilds; - save(); } public boolean isCountFailedBuilds() { return countFailedBuilds; } + @DataBoundSetter public void setCountFailedBuilds(boolean countFailedBuilds) { this.countFailedBuilds = countFailedBuilds; - save(); } public boolean isCountNotBuiltBuilds() { return countNotBuiltBuilds; } + @DataBoundSetter public void setCountNotBuiltBuilds(boolean countNotBuiltBuilds) { this.countNotBuiltBuilds = countNotBuiltBuilds; - save(); } public boolean isCountAbortedBuilds() { return countAbortedBuilds; } + @DataBoundSetter public void setCountAbortedBuilds(boolean countAbortedBuilds) { this.countAbortedBuilds = countAbortedBuilds; - save(); } public boolean isFetchTestResults() { return fetchTestResults; } + @DataBoundSetter public void setFetchTestResults(boolean fetchTestResults) { this.fetchTestResults = fetchTestResults; - save(); } public boolean isProcessingDisabledBuilds() { return processingDisabledBuilds; } + @DataBoundSetter public void setProcessingDisabledBuilds(boolean processingDisabledBuilds) { this.processingDisabledBuilds = processingDisabledBuilds; - save(); } public boolean isAppendParamLabel() { return appendParamLabel; } + @DataBoundSetter public void setAppendParamLabel(boolean appendParamLabel) { this.appendParamLabel = appendParamLabel; - save(); } public boolean isAppendStatusLabel() { return appendStatusLabel; } + @DataBoundSetter public void setAppendStatusLabel(boolean appendStatusLabel) { this.appendStatusLabel = appendStatusLabel; - save(); + } + + public boolean isPerBuildMetrics() { + return perBuildMetrics; + } + + @DataBoundSetter + public void setPerBuildMetrics(boolean perBuildMetrics) { + this.perBuildMetrics = perBuildMetrics; + } + + public boolean isCollectNodeStatus() { + return collectNodeStatus; + } + + @DataBoundSetter + public void setCollectNodeStatus(boolean collectNodeStatus) { + this.collectNodeStatus = collectNodeStatus; } public String getUrlName() { @@ -262,6 +285,7 @@ public String getLabeledBuildParameterNames() { return labeledBuildParameterNames; } + @DataBoundSetter public void setLabeledBuildParameterNames(String labeledBuildParameterNames) { this.labeledBuildParameterNames = labeledBuildParameterNames; } @@ -270,6 +294,26 @@ public String[] getLabeledBuildParameterNamesAsArray() { return parseParameterNamesFromStringSeparatedByComma(labeledBuildParameterNames); } + public DisabledMetricConfig getDisabledMetricConfig() { + return disabledMetricConfig; + } + + @DataBoundSetter + public void setDisabledMetricConfig(DisabledMetricConfig disabledMetricConfig) { + this.disabledMetricConfig = disabledMetricConfig; + } + + public boolean isCollectCodeCoverage() { + return collectCodeCoverage; + } + + public boolean isCodeCoverageApiPluginInstalled() { + return Jenkins.get().getPlugin("code-coverage-api") != null; + } + + public void setCollectCodeCoverage(boolean collectCodeCoverage) { + this.collectCodeCoverage = collectCodeCoverage; + } public FormValidation doCheckPath(@QueryParameter String value) { if (StringUtils.isEmpty(value)) { @@ -281,15 +325,16 @@ public FormValidation doCheckPath(@QueryParameter String value) { } } - private Long validateProcessingMetricsPeriodInSeconds(JSONObject json) throws FormException { + public FormValidation doCheckCollectingMetricsPeriodInSeconds(@QueryParameter String value) { try { - long value = json.getLong("collectingMetricsPeriodInSeconds"); - if (value > 0) { - return value; + long longValue = Long.parseLong(value); + if (longValue > 0) { + return FormValidation.ok(); } - } catch (JSONException ignored) { + } catch (NumberFormatException ignore) { + // ignore exception. If it comes it's not a positive long } - throw new FormException("CollectingMetricsPeriodInSeconds must be a positive integer", "collectingMetricsPeriodInSeconds"); + return FormValidation.error("CollectingMetricsPeriodInSeconds must be a positive value"); } private long parseLongFromEnv() { @@ -310,10 +355,9 @@ private long parseLongFromEnv() { } private String[] parseParameterNamesFromStringSeparatedByComma(String stringValue) { - if (stringValue==null || stringValue.trim().length() < 1) { - return new String[] {}; + if (stringValue == null || stringValue.trim().isEmpty()) { + return new String[]{}; } return stringValue.split("\\s*,\\s*"); } - } diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/DisabledMetricConfig.java b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/DisabledMetricConfig.java new file mode 100644 index 000000000..0dbb1fea0 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/DisabledMetricConfig.java @@ -0,0 +1,30 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import hudson.Extension; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; +import org.kohsuke.stapler.DataBoundConstructor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class DisabledMetricConfig extends AbstractDescribableImpl { + + private final List entries; + + @DataBoundConstructor + public DisabledMetricConfig(List entries) { + this.entries = entries != null ? new ArrayList<>(entries) : Collections.emptyList(); + } + + public List getEntries() { + return Collections.unmodifiableList(entries); + } + + @Extension + public static class DescriptorImpl extends Descriptor { + } + + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/Entry.java b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/Entry.java new file mode 100644 index 000000000..a8e01d198 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/Entry.java @@ -0,0 +1,6 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import hudson.model.AbstractDescribableImpl; + +public abstract class Entry extends AbstractDescribableImpl { +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/FilteredMetricEnumeration.java b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/FilteredMetricEnumeration.java new file mode 100644 index 000000000..405c256f0 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/FilteredMetricEnumeration.java @@ -0,0 +1,44 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import io.prometheus.client.Collector; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; + +public class FilteredMetricEnumeration implements Enumeration { + + private final Iterator filteredList; + + public FilteredMetricEnumeration(Iterator fullList) { + this.filteredList = filterList(fullList); + } + + private Iterator filterList(Iterator fullList) { + List filteredList = new ArrayList<>(); + while (fullList.hasNext()) { + Collector.MetricFamilySamples familySamples = fullList.next(); + if (MetricStatusChecker.isEnabled(familySamples.name)) { + filteredList.add(familySamples); + } + } + return filteredList.iterator(); + } + + + @Override + public boolean hasMoreElements() { + return filteredList.hasNext(); + } + + @Override + public Collector.MetricFamilySamples nextElement() { + return filteredList.next(); + } + + @Override + public Iterator asIterator() { + return filteredList; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/JobRegexDisabledMetric.java b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/JobRegexDisabledMetric.java new file mode 100644 index 000000000..25cfe6586 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/JobRegexDisabledMetric.java @@ -0,0 +1,35 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.model.Descriptor; +import org.kohsuke.stapler.DataBoundConstructor; + +public class JobRegexDisabledMetric extends Entry { + + private final String regex; + + @DataBoundConstructor + public JobRegexDisabledMetric(String regex) { + this.regex = regex; + } + + public String getRegex() { + return regex; + } + + @Override + public Descriptor getDescriptor() { + return new DescriptorImpl(); + } + + @Extension + public static class DescriptorImpl extends Descriptor { + @Override + @NonNull + public String getDisplayName() { + return "Job Regex Entry"; + } + + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/MetricStatusChecker.java b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/MetricStatusChecker.java new file mode 100644 index 000000000..e7a4745c4 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/MetricStatusChecker.java @@ -0,0 +1,85 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class MetricStatusChecker { + + private static final Logger LOGGER = LoggerFactory.getLogger(MetricStatusChecker.class); + + public static boolean isEnabled(String metricName) { + + List entries = getEntries(); + + for (Entry entry : entries) { + if (entry instanceof RegexDisabledMetric) { + Pattern pattern = Pattern.compile(((RegexDisabledMetric) entry).getRegex()); + Matcher matcher = pattern.matcher(metricName); + if (matcher.matches()) { + LOGGER.debug("Metric named '{}' is disabled via Jenkins Prometheus Plugin configuration. Reason: Regex", metricName); + return false; + } + } + + if (entry instanceof NamedDisabledMetric) { + if (metricName.equalsIgnoreCase(((NamedDisabledMetric) entry).getMetricName())) { + LOGGER.debug("Metric named '{}' is disabled via Jenkins Prometheus Plugin configuration. Reason: Named", metricName); + return false; + } + } + } + return true; + } + + public static boolean isJobEnabled(String jobName) { + + List entries = getEntries(); + + for (Entry entry : entries) { + if (entry instanceof JobRegexDisabledMetric) { + Pattern pattern = Pattern.compile(((JobRegexDisabledMetric) entry).getRegex()); + Matcher matcher = pattern.matcher(jobName); + if (matcher.matches()) { + LOGGER.debug("Job named '{}' is disabled via Jenkins Prometheus Plugin configuration. Reason: JobRegexDisabledMetric", jobName); + return false; + } + } + } + return true; + } + + public static Set filter(List allMetricNames) { + if (allMetricNames == null) { + return new HashSet<>(); + } + return allMetricNames.stream().filter(MetricStatusChecker::isEnabled).collect(Collectors.toSet()); + } + + private static List getEntries() { + PrometheusConfiguration configuration = PrometheusConfiguration.get(); + if (configuration == null) { + LOGGER.warn("Cannot check if job is enabled. No PrometheusConfiguration"); + return List.of(); + } + DisabledMetricConfig disabledMetricConfig = configuration.getDisabledMetricConfig(); + if (disabledMetricConfig == null) { + LOGGER.debug("Cannot check if metric is enabled. No DisabledMetricConfig."); + return List.of(); + } + + List entries = disabledMetricConfig.getEntries(); + if (entries == null || entries.isEmpty()) { + LOGGER.debug("Cannot check if metric is enabled. No entries specified in DisabledMetricConfig."); + return List.of(); + } + return entries; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/NamedDisabledMetric.java b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/NamedDisabledMetric.java new file mode 100644 index 000000000..4c4d1b3e6 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/NamedDisabledMetric.java @@ -0,0 +1,36 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.model.Descriptor; +import org.kohsuke.stapler.DataBoundConstructor; + +public class NamedDisabledMetric extends Entry { + + private final String metricName; + + @DataBoundConstructor + public NamedDisabledMetric(String metricName) { + this.metricName = metricName; + } + + public String getMetricName() { + return metricName; + } + + @Override + public Descriptor getDescriptor() { + return new DescriptorImpl(); + } + + @Extension + public static class DescriptorImpl extends Descriptor { + @Override + @NonNull + public String getDisplayName() { + return "Fully qualified Name Entry"; + } + + } + +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/RegexDisabledMetric.java b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/RegexDisabledMetric.java new file mode 100644 index 000000000..2c1bd032d --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/RegexDisabledMetric.java @@ -0,0 +1,35 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.model.Descriptor; +import org.kohsuke.stapler.DataBoundConstructor; + +public class RegexDisabledMetric extends Entry { + + private final String regex; + + @DataBoundConstructor + public RegexDisabledMetric(String regex) { + this.regex = regex; + } + + public String getRegex() { + return regex; + } + + @Override + public Descriptor getDescriptor() { + return new DescriptorImpl(); + } + + @Extension + public static class DescriptorImpl extends Descriptor { + @Override + @NonNull + public String getDisplayName() { + return "Regex Entry"; + } + + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/context/Context.java b/src/main/java/org/jenkinsci/plugins/prometheus/context/Context.java deleted file mode 100644 index f26c83232..000000000 --- a/src/main/java/org/jenkinsci/plugins/prometheus/context/Context.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.jenkinsci.plugins.prometheus.context; - -import com.google.inject.AbstractModule; -import hudson.Extension; -import org.jenkinsci.plugins.prometheus.service.PrometheusMetrics; -import org.jenkinsci.plugins.prometheus.service.DefaultPrometheusMetrics; - -@Extension -public class Context extends AbstractModule { - - @Override - public void configure() { - bind(PrometheusMetrics.class).to(DefaultPrometheusMetrics.class).in(com.google.inject.Singleton.class); - } - -} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java b/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java index abe57cc28..f44ece243 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java @@ -1,6 +1,5 @@ package org.jenkinsci.plugins.prometheus.rest; -import com.google.inject.Inject; import hudson.Extension; import hudson.model.UnprotectedRootAction; import hudson.util.HttpResponses; @@ -8,20 +7,18 @@ import jenkins.metrics.api.Metrics; import jenkins.model.Jenkins; import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.jenkinsci.plugins.prometheus.service.DefaultPrometheusMetrics; import org.jenkinsci.plugins.prometheus.service.PrometheusMetrics; import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.StaplerRequest2; +import org.kohsuke.stapler.StaplerResponse2; + +import java.io.IOException; @Extension public class PrometheusAction implements UnprotectedRootAction { - private PrometheusMetrics prometheusMetrics; - - @Inject - public void setPrometheusMetrics(PrometheusMetrics prometheusMetrics) { - this.prometheusMetrics = prometheusMetrics; - } + private final PrometheusMetrics prometheusMetrics = DefaultPrometheusMetrics.get(); @Override public String getIconFileName() { @@ -38,7 +35,7 @@ public String getUrlName() { return PrometheusConfiguration.get().getUrlName(); } - public HttpResponse doDynamic(StaplerRequest request) { + public HttpResponse doDynamic(StaplerRequest2 request) { if (request.getRestOfPath().equals(PrometheusConfiguration.get().getAdditionalPath())) { if (hasAccess()) { return prometheusResponse(); @@ -50,17 +47,21 @@ public HttpResponse doDynamic(StaplerRequest request) { private boolean hasAccess() { if (PrometheusConfiguration.get().isUseAuthenticatedEndpoint()) { - return Jenkins.getInstance().hasPermission(Metrics.VIEW); + return Jenkins.get().hasPermission(Metrics.VIEW); } return true; } + private HttpResponse prometheusResponse() { - return (request, response, node) -> { - response.setStatus(StaplerResponse.SC_OK); - response.setContentType(TextFormat.CONTENT_TYPE_004); - response.addHeader("Cache-Control", "must-revalidate,no-cache,no-store"); - response.getWriter().write(prometheusMetrics.getMetrics()); + return new HttpResponse() { + @Override + public void generateResponse(StaplerRequest2 request, StaplerResponse2 response, Object node) throws IOException { + response.setStatus(StaplerResponse2.SC_OK); + response.setContentType(TextFormat.CONTENT_TYPE_004); + response.addHeader("Cache-Control", "must-revalidate,no-cache,no-store"); + response.getWriter().write(prometheusMetrics.getMetrics()); + } }; } } diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java b/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java index e0b6f34e6..a7941ea84 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java @@ -1,48 +1,95 @@ package org.jenkinsci.plugins.prometheus.service; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.ExtensionList; +import hudson.init.InitMilestone; +import hudson.init.Initializer; +import hudson.triggers.SafeTimerTask; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.dropwizard.DropwizardExports; import io.prometheus.client.exporter.common.TextFormat; import io.prometheus.client.hotspot.DefaultExports; import jenkins.metrics.api.Metrics; +import jenkins.util.Timer; +import org.jenkinsci.plugins.prometheus.CodeCoverageCollector; import org.jenkinsci.plugins.prometheus.DiskUsageCollector; import org.jenkinsci.plugins.prometheus.ExecutorCollector; import org.jenkinsci.plugins.prometheus.JenkinsStatusCollector; import org.jenkinsci.plugins.prometheus.JobCollector; -import org.jenkinsci.plugins.prometheus.util.MetricsFormatter; +import org.jenkinsci.plugins.prometheus.config.disabledmetrics.FilteredMetricEnumeration; +import org.jenkinsci.plugins.prometheus.util.JenkinsNodeBuildsSampleBuilder; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.StringWriter; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class DefaultPrometheusMetrics implements PrometheusMetrics { private static final Logger logger = LoggerFactory.getLogger(DefaultPrometheusMetrics.class); + private static DefaultPrometheusMetrics INSTANCE = null; + private final CollectorRegistry collectorRegistry; private final AtomicReference cachedMetrics; - public DefaultPrometheusMetrics() { + private DefaultPrometheusMetrics() { CollectorRegistry collectorRegistry = CollectorRegistry.defaultRegistry; - collectorRegistry.register(new JobCollector()); - collectorRegistry.register(new JenkinsStatusCollector()); - collectorRegistry.register(new DropwizardExports(Metrics.metricRegistry())); - collectorRegistry.register(new DiskUsageCollector()); - collectorRegistry.register(new ExecutorCollector()); - - // other collectors from other plugins - ExtensionList.lookup(Collector.class).forEach( c -> collectorRegistry.register(c)); - DefaultExports.initialize(); - this.collectorRegistry = collectorRegistry; this.cachedMetrics = new AtomicReference<>(""); } + public static synchronized DefaultPrometheusMetrics get() { + if(INSTANCE == null) { + INSTANCE = new DefaultPrometheusMetrics(); + } + return INSTANCE; + } + + @Restricted(NoExternalUse.class) + private void initRegistry() { + this.collectorRegistry.clear(); + DefaultExports.register(this.collectorRegistry); + } + + @Restricted(NoExternalUse.class) + private void registerCollector(@NonNull Collector collector) { + collectorRegistry.register(collector); + logger.debug(String.format("Collector %s registered", collector.getClass().getName())); + } + + @Restricted(NoExternalUse.class) + @Initializer(after = InitMilestone.JOB_LOADED, before = InitMilestone.JOB_CONFIG_ADAPTED) + public static void registerCollectors() { + Timer.get() + .schedule( + new SafeTimerTask() { + @Override + public void doRun() throws Exception { + logger.debug("Initializing Collectors"); + DefaultPrometheusMetrics instance = get(); + instance.initRegistry(); + instance.registerCollector(new JenkinsStatusCollector()); + instance.registerCollector(new DropwizardExports(Metrics.metricRegistry(), new JenkinsNodeBuildsSampleBuilder())); + instance.registerCollector(new DiskUsageCollector()); + instance.registerCollector(new ExecutorCollector()); + instance.registerCollector(new JobCollector()); + instance.registerCollector(new CodeCoverageCollector()); + // other collectors from other plugins + ExtensionList.lookup(Collector.class).forEach(instance::registerCollector); + logger.debug("Finished initializing Collectors"); + } + }, + 1, + TimeUnit.SECONDS); + } + @Override public String getMetrics() { return cachedMetrics.get(); @@ -51,8 +98,8 @@ public String getMetrics() { @Override public void collectMetrics() { try (StringWriter buffer = new StringWriter()) { - TextFormat.write004(buffer, collectorRegistry.metricFamilySamples()); - cachedMetrics.set(MetricsFormatter.formatMetrics(buffer.toString())); + TextFormat.write004(buffer, new FilteredMetricEnumeration(collectorRegistry.metricFamilySamples().asIterator())); + cachedMetrics.set(buffer.toString()); } catch (IOException e) { logger.debug("Unable to collect metrics"); } diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java b/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java index 291af6a30..bbf7d295c 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java @@ -1,6 +1,5 @@ package org.jenkinsci.plugins.prometheus.service; -import com.google.inject.Inject; import hudson.Extension; import hudson.model.AsyncPeriodicWork; import hudson.model.TaskListener; @@ -16,17 +15,12 @@ public class PrometheusAsyncWorker extends AsyncPeriodicWork { private static final Logger logger = LoggerFactory.getLogger(PrometheusAsyncWorker.class); - private PrometheusMetrics prometheusMetrics; + private final PrometheusMetrics prometheusMetrics = DefaultPrometheusMetrics.get(); public PrometheusAsyncWorker() { super("prometheus_async_worker"); } - @Inject - public void setPrometheusMetrics(PrometheusMetrics prometheusMetrics) { - this.prometheusMetrics = prometheusMetrics; - } - @Override public long getRecurrencePeriod() { long collectingMetricsPeriodInMillis = @@ -41,7 +35,7 @@ public void execute(TaskListener taskListener) { prometheusMetrics.collectMetrics(); logger.debug("Prometheus metrics collected successfully"); } - + @Override protected Level getNormalLoggingLevel() { return Level.FINE; diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/util/ConfigurationUtils.java b/src/main/java/org/jenkinsci/plugins/prometheus/util/ConfigurationUtils.java index 0f2e8171e..72e4f7339 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/util/ConfigurationUtils.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/util/ConfigurationUtils.java @@ -1,9 +1,14 @@ package org.jenkinsci.plugins.prometheus.util; +import org.apache.commons.lang3.StringUtils; import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; -import org.apache.commons.lang.StringUtils; -public class ConfigurationUtils { +public final class ConfigurationUtils { + + private ConfigurationUtils() { + // prevents creating new instances + } + public static String getNamespace() { // get the namespace from the environment first String namespace = System.getenv("PROMETHEUS_NAMESPACE"); @@ -17,12 +22,4 @@ public static String getNamespace() { public static String getSubSystem() { return "jenkins"; } - - public static boolean getCollectDiskUsage() { - String envCollectDiskUsage = System.getenv("COLLECT_DISK_USAGE"); - if(StringUtils.isEmpty(envCollectDiskUsage)) { - return PrometheusConfiguration.get().getDefaultCollectDiskUsage(); - } - return Boolean.parseBoolean(envCollectDiskUsage); - } } diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/util/JenkinsNodeBuildsSampleBuilder.java b/src/main/java/org/jenkinsci/plugins/prometheus/util/JenkinsNodeBuildsSampleBuilder.java new file mode 100644 index 000000000..2b6103c06 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/prometheus/util/JenkinsNodeBuildsSampleBuilder.java @@ -0,0 +1,45 @@ +package org.jenkinsci.plugins.prometheus.util; + +import io.prometheus.client.Collector; +import io.prometheus.client.dropwizard.samplebuilder.DefaultSampleBuilder; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A class that converts jenkins.node[.<node_name>].builds to jenkins.node.builds with a label of node=<node_name> (or master if not set) + * before creating a Sample + */ +public class JenkinsNodeBuildsSampleBuilder extends DefaultSampleBuilder { + // Note that nodes can have '.' in their name + final static Pattern PATTERN = Pattern.compile("jenkins\\.node(\\.(?.*))?\\.builds"); + + @Override + public Collector.MetricFamilySamples.Sample createSample(String dropwizardName, String nameSuffix, List additionalLabelNames, List additionalLabelValues, double value) { + Matcher matcher = PATTERN.matcher(dropwizardName); + + if (matcher.matches()) { + String processedDropwizardName = "jenkins.node.builds"; + String node = matcher.group("node"); + + if (node == null) { + node = "master"; + } + + List processedAdditionalLabelNames = new ArrayList(); + List processedAdditionalLabelValues = new ArrayList(); + + processedAdditionalLabelNames.add("node"); + processedAdditionalLabelNames.addAll(additionalLabelNames); + + processedAdditionalLabelValues.add(node); + processedAdditionalLabelValues.addAll(additionalLabelValues); + + return super.createSample(processedDropwizardName, nameSuffix, processedAdditionalLabelNames, processedAdditionalLabelValues, value); + } else { + return super.createSample(dropwizardName, nameSuffix, additionalLabelNames, additionalLabelValues, value); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/util/Jobs.java b/src/main/java/org/jenkinsci/plugins/prometheus/util/Jobs.java index 4bed87358..ecb5c4cbe 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/util/Jobs.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/util/Jobs.java @@ -6,9 +6,14 @@ import java.util.List; import java.util.function.Consumer; -public class Jobs { +public final class Jobs { + + private Jobs() { + // prevents creating new instances + } + public static void forEachJob(Consumer consumer) { - List jobs = Jenkins.getInstance().getAllItems(Job.class); + List jobs = Jenkins.get().getAllItems(Job.class); if (jobs != null) { for (Job item : jobs) { consumer.accept(item); diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/util/MetricsFormatter.java b/src/main/java/org/jenkinsci/plugins/prometheus/util/MetricsFormatter.java deleted file mode 100644 index 36d4069db..000000000 --- a/src/main/java/org/jenkinsci/plugins/prometheus/util/MetricsFormatter.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.jenkinsci.plugins.prometheus.util; - -public class MetricsFormatter { - - public static String formatMetrics(String formatString) { - //node specific build counts - formatString = formatString.replaceAll("jenkins_node_builds_count (.*)", "jenkins_node_builds_count{node=\"master\"} $1"); - formatString = formatString.replaceAll("jenkins_node_(.*)_builds_count (.*)", "jenkins_node_builds_count{node=\"$1\"} $2"); - - //node specific histograms - formatString = formatString.replaceAll("jenkins_node_builds\\{(.*)} (.*)", "jenkins_node_builds{node=\"master\",$1} $2"); - formatString = formatString.replaceAll("jenkins_node_(.*)_builds\\{(.*)} (.*)", "jenkins_node_builds{node=\"$1\",$2} $3"); - - return formatString; - } -} diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/util/Runs.java b/src/main/java/org/jenkinsci/plugins/prometheus/util/Runs.java index 61d2a80d4..9cce9ea46 100644 --- a/src/main/java/org/jenkinsci/plugins/prometheus/util/Runs.java +++ b/src/main/java/org/jenkinsci/plugins/prometheus/util/Runs.java @@ -18,30 +18,20 @@ public static boolean includeBuildInMetrics(Run build) { include = true; Result result = build.getResult(); if (result != null) { - if (result == Result.ABORTED) { + if (result == Result.ABORTED) { include = PrometheusConfiguration.get().isCountAbortedBuilds(); - } else if (result == Result.FAILURE) { + } else if (result == Result.FAILURE) { include = PrometheusConfiguration.get().isCountFailedBuilds(); - } else if (result == Result.NOT_BUILT) { + } else if (result == Result.NOT_BUILT) { include = PrometheusConfiguration.get().isCountNotBuiltBuilds(); - } else if (result == Result.SUCCESS) { + } else if (result == Result.SUCCESS) { include = PrometheusConfiguration.get().isCountSuccessfulBuilds(); - } else if (result == Result.UNSTABLE) { + } else if (result == Result.UNSTABLE) { include = PrometheusConfiguration.get().isCountUnstableBuilds(); } } - } - return include; - } - - public static String getResultText(Run run) { - if (run != null) { - Result result = run.getResult(); - if (result != null) { - return result.toString(); - } } - return null; + return include; } public static Map getBuildParameters(Run build) { diff --git a/src/main/resources/index.jelly b/src/main/resources/index.jelly new file mode 100644 index 000000000..9e1e34c53 --- /dev/null +++ b/src/main/resources/index.jelly @@ -0,0 +1,5 @@ + +
+Jenkins Prometheus Plugin expose an endpoint (default /prometheus) with metrics where a Prometheus Server can scrape. +
+ diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/config.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/config.jelly index f13bed9df..974671849 100644 --- a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/config.jelly @@ -47,7 +47,19 @@ + + + + + + + + + + + + diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectCodeCoverage.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectCodeCoverage.jelly new file mode 100644 index 000000000..009277975 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectCodeCoverage.jelly @@ -0,0 +1,9 @@ + + +
+

+ If prometheus should collect code coverage metrics from https://plugins.jenkins.io/coverage plugin. + This option is disabled when coverage plugin is not installed. +

+
+
diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectDiskUsage.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectDiskUsage.jelly index ab0c4f333..85a085516 100644 --- a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectDiskUsage.jelly +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectDiskUsage.jelly @@ -3,6 +3,8 @@

If prometheus should collect disk usage or not. Disable this if you are using a cloud storage provider. + You can overwrite the default behavior with the environment variable COLLECT_DISK_USAGE. + This field is disabled if the variable is set.

diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectNodeStatus.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectNodeStatus.jelly new file mode 100644 index 000000000..69512f15b --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-collectNodeStatus.jelly @@ -0,0 +1,8 @@ + + +
+

+ If prometheus should collect node status (Up/Down). +

+
+
diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-defaultNamespace.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-defaultNamespace.jelly index fc9fe3b8e..e8cad577e 100644 --- a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-defaultNamespace.jelly +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-defaultNamespace.jelly @@ -3,6 +3,7 @@

The default namespace (default value is 'default'). Setting this to an empty value will return the build values as Jenkins metrics. + Note: If you change the namespace the counter metrics will be reset

diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-disabledMetricConfig.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-disabledMetricConfig.jelly new file mode 100644 index 000000000..5ec06ebaf --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-disabledMetricConfig.jelly @@ -0,0 +1,10 @@ + + +
+

+ It's possible to disable metrics which are calculated by this plugin. + Disabling metrics results in a smaller prometheus endpoint output which makes the scraping faster. + In case you are not interested in a certain metric you can disable it here +

+
+
diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-perBuildMetrics.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-perBuildMetrics.jelly new file mode 100644 index 000000000..7e859d726 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/PrometheusConfiguration/help-perBuildMetrics.jelly @@ -0,0 +1,13 @@ + + +
+

+ Use with caution! This option generates lots of metrics which can lead to a multitude of errors and/or high resource consumption. + Be sure you know about the consequences and implications before using this option (e.g. Prometheus disk usage)! +

+

+ Enable per run metrics. This will create metrics for each run of each build including status, startTime, duration, etc. + Make sure your total number of runs in Jenkins is limited by using the "discard old builds" feature. +

+
+
diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/DisabledMetricConfig/config.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/DisabledMetricConfig/config.jelly new file mode 100644 index 000000000..71b53a206 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/DisabledMetricConfig/config.jelly @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/JobRegexDisabledMetric/config.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/JobRegexDisabledMetric/config.jelly new file mode 100644 index 000000000..8cc48f7f4 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/JobRegexDisabledMetric/config.jelly @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/NamedDisabledMetric/config.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/NamedDisabledMetric/config.jelly new file mode 100644 index 000000000..76d7cb607 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/NamedDisabledMetric/config.jelly @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/RegexDisabledMetric/config.jelly b/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/RegexDisabledMetric/config.jelly new file mode 100644 index 000000000..5c95aa2da --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/prometheus/config/disabledmetrics/RegexDisabledMetric/config.jelly @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollectorTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollectorTest.java new file mode 100644 index 000000000..70780649c --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/CodeCoverageCollectorTest.java @@ -0,0 +1,96 @@ +package org.jenkinsci.plugins.prometheus; + +import hudson.Plugin; +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.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.jenkinsci.plugins.prometheus.util.ConfigurationUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + + +@ExtendWith(MockitoExtension.class) +class CodeCoverageCollectorTest { + + @Mock + private Jenkins jenkins; + + @Mock + private PrometheusConfiguration config; + + @Test + void shouldNotProduceMetricsWhenNoCoveragePluginDetected() { + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + when(jenkins.getPlugin("coverage")).thenReturn(null); + CodeCoverageCollector sut = new CodeCoverageCollector(); + + List collect = sut.collect(); + assertEquals(0, collect.size()); + } + } + + @Test + void shouldNotProduceMetricsWhenItIsNotConfigured() { + try ( + MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configurationStatic = mockStatic(PrometheusConfiguration.class) + ) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configurationStatic.when(PrometheusConfiguration::get).thenReturn(config); + when(jenkins.getPlugin("coverage")).thenReturn(new Plugin.DummyImpl()); + when(config.isCollectCodeCoverage()).thenReturn(false); + + CodeCoverageCollector sut = new CodeCoverageCollector(); + + List collect = sut.collect(); + assertEquals(0, collect.size()); + } + } + + @Test + void shouldProduceMetricsWhenJobHasCoverageBuildAction() { + try ( + MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configurationStatic = mockStatic(PrometheusConfiguration.class); + MockedStatic configurationUtils = mockStatic(ConfigurationUtils.class); + ) { + configurationUtils.when(ConfigurationUtils::getNamespace).thenReturn("foo"); + configurationUtils.when(ConfigurationUtils::getSubSystem).thenReturn("bar"); + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + + Job jobUnderTest = mock(Job.class); + when(jobUnderTest.getFullName()).thenReturn("some/job"); + + Run lastBuild = mock(Run.class); + when(lastBuild.isBuilding()).thenReturn(false); + when(jobUnderTest.getLastBuild()).thenReturn(lastBuild); + CoverageBuildAction action = mock(CoverageBuildAction.class); + when(lastBuild.getAction(CoverageBuildAction.class)).thenReturn(action); + when(jenkins.getAllItems(Job.class)).thenReturn(List.of(jobUnderTest)); + when(jenkins.getPlugin("coverage")).thenReturn(new Plugin.DummyImpl()); + when(config.getJobAttributeName()).thenReturn("jenkins_job"); + when(config.isCollectCodeCoverage()).thenReturn(true); + + configurationStatic.when(PrometheusConfiguration::get).thenReturn(config); + + CodeCoverageCollector sut = new CodeCoverageCollector(); + + List collect = sut.collect(); + assertEquals(20, collect.size(), "20 metrics should have been collected"); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/DiskUsageCollectorTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/DiskUsageCollectorTest.java new file mode 100644 index 000000000..c3eb4f30f --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/DiskUsageCollectorTest.java @@ -0,0 +1,210 @@ +package org.jenkinsci.plugins.prometheus; + +import com.cloudbees.simplediskusage.DiskItem; +import com.cloudbees.simplediskusage.JobDiskItem; +import com.cloudbees.simplediskusage.QuickDiskUsagePlugin; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import io.prometheus.client.Collector; +import io.prometheus.client.Collector.MetricFamilySamples; +import jenkins.model.Jenkins; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.jenkinsci.plugins.prometheus.util.ConfigurationUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +import static java.util.Objects.requireNonNull; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +@ExtendWith(MockitoExtension.class) +public class DiskUsageCollectorTest { + + @Mock + private Jenkins jenkins; + @Mock + private QuickDiskUsagePlugin quickDiskUsagePlugin; + + + @Test + public void shouldNotProduceMetricsWhenDisabled() { + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + DiskUsageCollector underTest = new DiskUsageCollector(); + final List samples = underTest.collect(); + + assertThat(samples, is(empty())); + } + } + + @Test + public void shouldProduceMetrics() throws IOException { + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + when(config.getCollectDiskUsage()).thenReturn(true); + try (MockedStatic configurationUtils = mockStatic(ConfigurationUtils.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class); + MockedStatic jenkinsStatic = mockStatic(Jenkins.class)) { + configurationUtils.when(ConfigurationUtils::getNamespace).thenReturn("foo"); + configurationUtils.when(ConfigurationUtils::getSubSystem).thenReturn("bar"); + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + when(jenkins.getPlugin(QuickDiskUsagePlugin.class)).thenReturn(quickDiskUsagePlugin); + + final FileStore store = mock(FileStore.class, "the file store"); + given(store.getTotalSpace()).willReturn(4711L); + given(store.getUsableSpace()).willReturn(1337L); + + final DiskItem dir = mock(DiskItem.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS)); + given(dir.getDisplayName()).willReturn("dir name"); + given(dir.getUsage()).willReturn(11L); + mockFileStore(dir, store); + final CopyOnWriteArrayList directories = new CopyOnWriteArrayList<>(); + directories.add(dir); + given(quickDiskUsagePlugin.getDirectoriesUsages()).willReturn(directories); + + final JobDiskItem job = mock(JobDiskItem.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS)); + given(job.getFullName()).willReturn("job name"); + given(job.getUrl()).willReturn("/job"); + given(job.getUsage()).willReturn(7L); + mockFileStore(job, store); + final CopyOnWriteArrayList jobs = new CopyOnWriteArrayList<>(); + jobs.add(job); + given(quickDiskUsagePlugin.getJobsUsages()).willReturn(jobs); + + DiskUsageCollector underTest = new DiskUsageCollector(); + final List samples = underTest.collect(); + + assertThat(samples, containsInAnyOrder( + gauges("foo_bar_disk_usage_bytes", containsInAnyOrder( + sample(ImmutableMap.of("file_store", "the file store", "directory", "dir"), equalTo(11. * 1024)) + )), + gauges("foo_bar_disk_usage_file_count", containsInAnyOrder( + sample(ImmutableMap.of("file_store", "the file store", "directory", "dir"), equalTo(0.0)) + )), + gauges("foo_bar_job_usage_bytes", containsInAnyOrder( + sample(ImmutableMap.of("file_store", "the file store", "jobName", "job name", "url", "/job"), equalTo(7. * 1024)) + )), + gauges("foo_bar_file_store_capacity_bytes", containsInAnyOrder( + sample(ImmutableMap.of("file_store", "the file store"), equalTo(4711.)) + )), + gauges("foo_bar_file_store_available_bytes", containsInAnyOrder( + sample(ImmutableMap.of("file_store", "the file store"), equalTo(1337.)) + )) + )); + } + } + + private static void mockFileStore(DiskItem item, FileStore store) throws IOException { + final Path path = item.getPath().toPath().toRealPath(); + when(path.getFileSystem().provider().getFileStore(path)).thenReturn(store); + } + + private static Matcher gauges(String name, Matcher> samples) { + requireNonNull(name); + requireNonNull(samples); + + return new TypeSafeDiagnosingMatcher<>(MetricFamilySamples.class) { + @Override + public void describeTo(Description description) { + description.appendText("gauges named ") + .appendValue(name) + .appendText(" with samples: ") + .appendDescriptionOf(samples); + } + + @Override + protected boolean matchesSafely(MetricFamilySamples item, Description mismatchDescription) { + if (!Objects.equal(item.name, name)) { + mismatchDescription.appendText("name was ").appendValue(item.name); + return false; + } + + if (item.type != Collector.Type.GAUGE) { + mismatchDescription.appendText("type was ").appendValue(item.type); + return false; + } + + if (!samples.matches(item.samples)) { + mismatchDescription.appendText("mismatch in samples: "); + samples.describeMismatch(item.samples, mismatchDescription); + return false; + } + + return true; + } + }; + } + + private static Matcher sample(Map labels, Matcher value) { + requireNonNull(labels); + + return new TypeSafeDiagnosingMatcher<>(MetricFamilySamples.Sample.class) { + @Override + public void describeTo(Description description) { + description.appendText("sample labeled ") + .appendValue(labels) + .appendText(" with value ") + .appendDescriptionOf(value); + } + + @Override + protected boolean matchesSafely(MetricFamilySamples.Sample item, Description mismatchDescription) { + if (item.labelNames == null) { + mismatchDescription.appendText("labelNames was ").appendValue(null); + return false; + } + + if (item.labelValues == null) { + mismatchDescription.appendText("labelValues was ").appendValue(null); + return false; + } + + final Map actualLabels = new HashMap<>(); + final Iterator names = item.labelNames.iterator(); + final Iterator values = item.labelValues.iterator(); + while (names.hasNext() && values.hasNext()) { + actualLabels.put(names.next(), values.next()); + } + if (names.hasNext() || values.hasNext()) { + mismatchDescription.appendText("number of label names doesn't match number of label values"); + } + + if (!actualLabels.equals(labels)) { + mismatchDescription.appendText("labels were ").appendValue(actualLabels); + } + + if (!value.matches(item.value)) { + value.describeMismatch(item.value, mismatchDescription); + return false; + } + + return true; + } + }; + } +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/JenkinsStatusCollectorTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/JenkinsStatusCollectorTest.java new file mode 100644 index 000000000..0205fad41 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/JenkinsStatusCollectorTest.java @@ -0,0 +1,43 @@ +package org.jenkinsci.plugins.prometheus; + +import io.prometheus.client.Collector.MetricFamilySamples; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class JenkinsStatusCollectorTest { + + @Test + public void shouldProduceNodeMetrics() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + String namespace = "TestNamespace"; + int numberOfMetrics = 4; + + when(mockedConfig.getDefaultNamespace()).thenReturn(namespace); + when(mockedConfig.isCollectNodeStatus()).thenReturn(false); + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + JenkinsStatusCollector jenkinsStatusCollector = new JenkinsStatusCollector(); + + List samples = jenkinsStatusCollector.collect(); + assertEquals(numberOfMetrics, samples.size()); + } + } +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/TestBasedMetricCollectorTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/TestBasedMetricCollectorTest.java new file mode 100644 index 000000000..8d727fc90 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/TestBasedMetricCollectorTest.java @@ -0,0 +1,72 @@ +package org.jenkinsci.plugins.prometheus.collectors; + +import hudson.tasks.test.AbstractTestResultAction; +import io.prometheus.client.Gauge; +import io.prometheus.client.SimpleCollector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +public class TestBasedMetricCollectorTest extends MockedRunCollectorTest { + + @Test + public void testCannotBeExecuted() { + + + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + + TestBasedMetricCollector sut = new TestBasedMetricCollector<>(getLabelNames(), getNamespace(), getSubSystem(), "") { + + + @Override + public void calculateMetric(Object jenkinsObject, String[] labelValues) { + // do nothing + } + + @Override + protected CollectorType getCollectorType() { + return CollectorType.BUILD_DURATION_GAUGE; + } + + @Override + protected String getHelpText() { + return null; + } + + + @Override + protected SimpleCollector.Builder getCollectorBuilder() { + return Gauge.build(); + } + + }; + + Assertions.assertFalse(sut.canBeCalculated(null), "Cannot be calculated when run is null"); + + when(mock.isBuilding()).thenReturn(false); + Assertions.assertFalse(sut.canBeCalculated(mock), "Cannot be calculated if build is running"); + + when(mock.isBuilding()).thenReturn(true); + Assertions.assertFalse(sut.canBeCalculated(mock), "Cannot be calculated if build is running"); + + when(mock.isBuilding()).thenReturn(false); + Assertions.assertFalse(sut.canBeCalculated(mock), "Cannot be calculated when isFetchTestResults is false"); + + when(config.isFetchTestResults()).thenReturn(true); + Assertions.assertFalse(sut.canBeCalculated(mock), "Cannot be calculated when isFetchTestResults is true but no test results found"); + + + AbstractTestResultAction action = mock(AbstractTestResultAction.class); + when(mock.getAction(AbstractTestResultAction.class)).thenReturn(action); + Assertions.assertTrue(sut.canBeCalculated(mock), "Can be collected"); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildAbortedCounterTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildAbortedCounterTest.java new file mode 100644 index 000000000..4d53b34f1 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildAbortedCounterTest.java @@ -0,0 +1,100 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class BuildAbortedCounterTest extends MockedRunCollectorTest { + + + @Test + public void testNothingIsIncreasedOnUnstableBuild() { + when(mock.getResult()).thenReturn(Result.UNSTABLE); + testNonFailureStateBuild(); + } + + @Test + public void testNothingIsIncreasedOnSuccessfulBuild() { + when(mock.getResult()).thenReturn(Result.SUCCESS); + testNonFailureStateBuild(); + } + + @Test + public void testNothingIsIncreasedOnNotBuiltBuild() { + when(mock.getResult()).thenReturn(Result.NOT_BUILT); + testSingleCalculation(0); + } + + @Test + public void testIncreasedOnAbortedBuild() { + when(mock.getResult()).thenReturn(Result.ABORTED); + testSingleCalculation(2); + } + + @Test + public void testNothingIsIncreasedOnBuildResultFailure() { + when(mock.getResult()).thenReturn(Result.FAILURE); + testSingleCalculation(0); + } + + private void testSingleCalculation(int expectedCount) { + BuildAbortedCounter sut = new BuildAbortedCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(expectedCount, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_failed_build_count_total")) { + Assertions.assertEquals(1.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_failed_build_count_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } + + @Test + public void testCounterIsNotChangedResultFailure() { + when(mock.getResult()).thenReturn(Result.FAILURE); + + BuildAbortedCounter sut = new BuildAbortedCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_failed_build_count_total")) { + Assertions.assertEquals(2.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_failed_build_count_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } + + private void testNonFailureStateBuild() { + BuildAbortedCounter sut = new BuildAbortedCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size(), "Would expect one result"); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCompletionListenerTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCompletionListenerTest.java new file mode 100644 index 000000000..632001e92 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildCompletionListenerTest.java @@ -0,0 +1,30 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Run; +import hudson.model.TaskListener; +import org.junit.jupiter.api.Test; +import org.jvnet.hudson.test.Issue; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +class BuildCompletionListenerTest { + + @Test + @Issue("#643") + void unregisterClearsRunStack() { + Run mock = mock(Run.class); + TaskListener taskListener = mock(TaskListener.class); + + BuildCompletionListener sut = BuildCompletionListener.getInstance(); + + sut.onCompleted(mock, taskListener); + + assertEquals(1, sut.getRunStack().size()); + + sut.unregister(); + + assertEquals(0, sut.getRunStack().size(), "Unregister should clear the list. Otherwise a memory leak can occur."); + + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationGaugeTest.java new file mode 100644 index 000000000..ae154b5ca --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationGaugeTest.java @@ -0,0 +1,40 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.List; + +public class BuildDurationGaugeTest extends MockedRunCollectorTest { + + @Test + public void testCalculateDurationWhenRunIsNotBuilding() { + Mockito.when(mock.isBuilding()).thenReturn(false); + Mockito.when(mock.getDuration()).thenReturn(1000L); + + BuildDurationGauge sut = new BuildDurationGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1000L, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_build_duration_milliseconds", collect.get(0).samples.get(0).name); + } + + @Test + public void testCalculateDurationIsNotCalculatedWhenRunIsBuilding() { + Mockito.when(mock.isBuilding()).thenReturn(true); + + BuildDurationGauge sut = new BuildDurationGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size()); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationSummaryTestJobTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationSummaryTestJobTest.java new file mode 100644 index 000000000..f3bf26a95 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildDurationSummaryTestJobTest.java @@ -0,0 +1,60 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + + +@ExtendWith(MockitoExtension.class) +public class BuildDurationSummaryTestJobTest extends MockedRunCollectorTest { + + @Test + public void testNothingCalculatedWhenRunIsBuilding() { + + Mockito.when(mock.isBuilding()).thenReturn(true); + + BuildDurationSummary sut = new BuildDurationSummary(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size(), "Would expect no sample created when run is running"); + } + + + @Test + public void testCollectResult() { + Mockito.when(mock.isBuilding()).thenReturn(false); + Mockito.when(mock.getDuration()).thenReturn(1000L); + + BuildDurationSummary sut = new BuildDurationSummary(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + + Assertions.assertEquals(3, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_duration_milliseconds_summary_count")) { + Assertions.assertEquals(1.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_duration_milliseconds_summary_sum")) { + Assertions.assertEquals(1000.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_duration_milliseconds_summary_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildFailedCounterTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildFailedCounterTest.java new file mode 100644 index 000000000..a4a0bbd67 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildFailedCounterTest.java @@ -0,0 +1,100 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class BuildFailedCounterTest extends MockedRunCollectorTest { + + + @Test + public void testNothingIsIncreasedOnUnstableBuild() { + when(mock.getResult()).thenReturn(Result.UNSTABLE); + testNonFailureStateBuild(); + } + + @Test + public void testNothingIsIncreasedOnSuccessfulBuild() { + when(mock.getResult()).thenReturn(Result.SUCCESS); + testNonFailureStateBuild(); + } + + @Test + public void testNothingIsIncreasedOnNotBuiltBuild() { + when(mock.getResult()).thenReturn(Result.NOT_BUILT); + testSingleCalculation(0); + } + + @Test + public void testNothingIsIncreasedOnAbortedBuild() { + when(mock.getResult()).thenReturn(Result.ABORTED); + testSingleCalculation(0); + } + + @Test + public void testCollectOnBuildResultFailure() { + when(mock.getResult()).thenReturn(Result.FAILURE); + testSingleCalculation(2); + } + + private void testSingleCalculation(int expectedCount) { + BuildFailedCounter sut = new BuildFailedCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(expectedCount, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_failed_build_count_total")) { + Assertions.assertEquals(1.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_failed_build_count_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } + + @Test + public void testCounterIsIncreasedOnBuildResultFailure() { + when(mock.getResult()).thenReturn(Result.FAILURE); + + BuildFailedCounter sut = new BuildFailedCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(2, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_failed_build_count_total")) { + Assertions.assertEquals(2.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_failed_build_count_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } + + private void testNonFailureStateBuild() { + BuildFailedCounter sut = new BuildFailedCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size(), "Would expect one result"); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLikelyStuckGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLikelyStuckGaugeTest.java new file mode 100644 index 000000000..1fc2a9c57 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLikelyStuckGaugeTest.java @@ -0,0 +1,96 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Executor; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public class BuildLikelyStuckGaugeTest extends MockedRunCollectorTest { + + + @Test + public void testNothingCalculatedWhenRunIsNull() { + BuildLikelyStuckGauge sut = new BuildLikelyStuckGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size()); + + } + + @Test + public void testNothingCalculatedWhenJobIsNotBuilding() { + when(mock.isBuilding()).thenReturn(false); + + BuildLikelyStuckGauge sut = new BuildLikelyStuckGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size()); + + } + + @Test + public void testNothingCalculatedWhenNoExecutorFound() { + when(mock.isBuilding()).thenReturn(true); + when(mock.getExecutor()).thenReturn(null); + + BuildLikelyStuckGauge sut = new BuildLikelyStuckGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size()); + } + + @Test + public void testBuildIsLikelyStuck() { + when(mock.isBuilding()).thenReturn(true); + Executor mockedExecutor = mock(Executor.class); + when(mockedExecutor.isLikelyStuck()).thenReturn(true); + when(mock.getExecutor()).thenReturn(mockedExecutor); + + BuildLikelyStuckGauge sut = new BuildLikelyStuckGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1, collect.get(0).samples.size()); + Assertions.assertEquals(1.0, collect.get(0).samples.get(0).value); + } + + @Test + public void testBuildIsNotLikelyStuck() { + when(mock.isBuilding()).thenReturn(true); + Executor mockedExecutor = mock(Executor.class); + when(mockedExecutor.isLikelyStuck()).thenReturn(false); + when(mock.getExecutor()).thenReturn(mockedExecutor); + + BuildLikelyStuckGauge sut = new BuildLikelyStuckGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1, collect.get(0).samples.size()); + Assertions.assertEquals(0.0, collect.get(0).samples.get(0).value); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLogFileSizeGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLogFileSizeGaugeTest.java new file mode 100644 index 000000000..2ae5a0c2e --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildLogFileSizeGaugeTest.java @@ -0,0 +1,53 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.console.AnnotatedLargeText; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class BuildLogFileSizeGaugeTest extends MockedRunCollectorTest { + + @Test + public void testNothingCalculatedWhenRunIsBuilding() { + + when(mock.isBuilding()).thenReturn(true); + + BuildLogFileSizeGauge sut = new BuildLogFileSizeGauge(getLabelNames(), getNamespace(), getSubSystem(), "default"); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + assertEquals(1, collect.size()); + assertEquals(0, collect.get(0).samples.size(), "Would expect no sample created when run is running"); + } + + @Test + public void testCollectResult() { + + when(mock.isBuilding()).thenReturn(false); + AnnotatedLargeText annotatedLargeText = Mockito.mock(AnnotatedLargeText.class); + when(annotatedLargeText.length()).thenReturn(3000L); + + when(mock.getLogText()).thenReturn(annotatedLargeText); + + BuildLogFileSizeGauge sut = new BuildLogFileSizeGauge(getLabelNames(), getNamespace(), getSubSystem(), "default"); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + assertEquals(1, collect.size()); + assertEquals(3000.0, collect.get(0).samples.get(0).value); + + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultGaugeTest.java new file mode 100644 index 000000000..d884cdf53 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultGaugeTest.java @@ -0,0 +1,103 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.List; + +public class BuildResultGaugeTest extends MockedRunCollectorTest { + + @Test + public void testSuccessResultWithNoPrefix() { + Mockito.when(mock.getResult()).thenReturn(Result.SUCCESS); + + BuildResultGauge sut = new BuildResultGauge(getLabelNames(), getNamespace() ,getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1.0, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_build_result", collect.get(0).samples.get(0).name); + + } + + @Test + public void testSuccessResultWithPrefix() { + Mockito.when(mock.getResult()).thenReturn(Result.SUCCESS); + + BuildResultGauge sut = new BuildResultGauge(getLabelNames(), getNamespace() ,getSubSystem(), "last"); + + sut.calculateMetric(mock, getLabelValues()); + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1.0, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_last_build_result", collect.get(0).samples.get(0).name); + + } + + @Test + public void testUnstableResultWithNoPrefix() { + Mockito.when(mock.getResult()).thenReturn(Result.UNSTABLE); + + BuildResultGauge sut = new BuildResultGauge(getLabelNames(), getNamespace() ,getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1.0, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_build_result", collect.get(0).samples.get(0).name); + + } + + @Test + public void testFailureResultWithNoPrefix() { + Mockito.when(mock.getResult()).thenReturn(Result.FAILURE); + + BuildResultGauge sut = new BuildResultGauge(getLabelNames(), getNamespace() ,getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0.0, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_build_result", collect.get(0).samples.get(0).name); + + } + + @Test + public void testNotBuiltResultWithNoPrefix() { + Mockito.when(mock.getResult()).thenReturn(Result.NOT_BUILT); + + BuildResultGauge sut = new BuildResultGauge(getLabelNames(), getNamespace() ,getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0.0, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_build_result", collect.get(0).samples.get(0).name); + + } + + @Test + public void testAbortedResultWithNoPrefix() { + Mockito.when(mock.getResult()).thenReturn(Result.ABORTED); + + BuildResultGauge sut = new BuildResultGauge(getLabelNames(), getNamespace() ,getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0.0, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_build_result", collect.get(0).samples.get(0).name); + + } +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultOrdinalGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultOrdinalGaugeTest.java new file mode 100644 index 000000000..cda351641 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildResultOrdinalGaugeTest.java @@ -0,0 +1,73 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.List; + +public class BuildResultOrdinalGaugeTest extends MockedRunCollectorTest { + + + @Test + public void testResultIsMinusOneWhenRunResultIsNull() { + + Mockito.when(mock.getResult()).thenReturn(null); + + BuildResultOrdinalGauge sut = new BuildResultOrdinalGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1, collect.get(0).samples.size(), "Would expect one result"); + + Assertions.assertEquals("default_jenkins_builds_build_result_ordinal", collect.get(0).samples.get(0).name); + Assertions.assertEquals(-1.0, collect.get(0).samples.get(0).value); + + } + + @Test + public void testOrdinalCalculated() { + + Mockito.when(mock.getResult()).thenReturn(Result.SUCCESS); + + BuildResultOrdinalGauge sut = new BuildResultOrdinalGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1, collect.get(0).samples.size(), "Would expect one result"); + + + Assertions.assertEquals("default_jenkins_builds_build_result_ordinal", collect.get(0).samples.get(0).name); + Assertions.assertEquals(0.0, collect.get(0).samples.get(0).value); + + } + + @Test + public void testPrefixedOrdinalCalculated() { + + Mockito.when(mock.getResult()).thenReturn(Result.SUCCESS); + + BuildResultOrdinalGauge sut = new BuildResultOrdinalGauge(getLabelNames(), getNamespace(), getSubSystem(), "last"); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1, collect.get(0).samples.size(), "Would expect one result"); + + + Assertions.assertEquals("default_jenkins_builds_last_build_result_ordinal", collect.get(0).samples.get(0).name); + Assertions.assertEquals(0.0, collect.get(0).samples.get(0).value); + + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildStartGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildStartGaugeTest.java new file mode 100644 index 000000000..791c55e6f --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildStartGaugeTest.java @@ -0,0 +1,43 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.List; + +public class BuildStartGaugeTest extends MockedRunCollectorTest { + + + @Test + public void testStartTimeGaugeWithNoPrefix() { + Mockito.when(mock.getStartTimeInMillis()).thenReturn(1000L); + + BuildStartGauge sut = new BuildStartGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1000.0, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_build_start_time_milliseconds", collect.get(0).samples.get(0).name); + } + + @Test + public void testStartTimeGaugeWithPrefix() { + Mockito.when(mock.getStartTimeInMillis()).thenReturn(1000L); + + BuildStartGauge sut = new BuildStartGauge(getLabelNames(), getNamespace(), getSubSystem(), "latest"); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(1000.0, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_latest_build_start_time_milliseconds", collect.get(0).samples.get(0).name); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildSuccessfulCounterTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildSuccessfulCounterTest.java new file mode 100644 index 000000000..c05e844be --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildSuccessfulCounterTest.java @@ -0,0 +1,102 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class BuildSuccessfulCounterTest extends MockedRunCollectorTest { + + @Test + public void testNothingIsIncreasedOnUnstableBuild() { + when(mock.getResult()).thenReturn(Result.UNSTABLE); + testSingleCalculation(0); + } + + @Test + public void testIncreasedOnSuccessfulBuild() { + when(mock.getResult()).thenReturn(Result.SUCCESS); + testSingleCalculation(2); + } + + @Test + public void testNothingIsIncreasedOnNotBuiltBuild() { + when(mock.getResult()).thenReturn(Result.NOT_BUILT); + testNonSuccessStateBuild(); + } + + @Test + public void testNothingIsIncreasedOnAbortedBuild() { + when(mock.getResult()).thenReturn(Result.ABORTED); + testNonSuccessStateBuild(); + } + + @Test + public void testCollectOnBuildResultFailure() { + when(mock.getResult()).thenReturn(Result.FAILURE); + testNonSuccessStateBuild(); + } + + private void testSingleCalculation(int expectedCount) { + BuildSuccessfulCounter sut = new BuildSuccessfulCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + + Assertions.assertEquals(expectedCount, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_success_build_count_total")) { + Assertions.assertEquals(1.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_success_build_count_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } + + @Test + public void testCounterIsIncreasedOnBuildResultFailure() { + when(mock.getResult()).thenReturn(Result.SUCCESS); + + BuildSuccessfulCounter sut = new BuildSuccessfulCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + + System.out.println(collect.get(0).samples); + Assertions.assertEquals(2, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_success_build_count_total")) { + Assertions.assertEquals(2.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_success_build_count_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } + + private void testNonSuccessStateBuild() { + BuildSuccessfulCounter sut = new BuildSuccessfulCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size(), "Would expect one result"); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildTotalCounterTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildTotalCounterTest.java new file mode 100644 index 000000000..4071ef9c1 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildTotalCounterTest.java @@ -0,0 +1,64 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class BuildTotalCounterTest extends MockedRunCollectorTest { + + @Test + public void testIncreasedOnUnstableBuild() { + when(mock.getResult()).thenReturn(Result.UNSTABLE); + testSingleCalculation(2); + } + + @Test + public void tesIncreasedOnSuccessfulBuild() { + when(mock.getResult()).thenReturn(Result.SUCCESS); + testSingleCalculation(2); + } + + @Test + public void testNothingIsIncreasedOnNotBuiltBuild() { + when(mock.getResult()).thenReturn(Result.NOT_BUILT); + testSingleCalculation(0); + } + + @Test + public void testIncreasedOnAbortedBuild() { + when(mock.getResult()).thenReturn(Result.ABORTED); + testSingleCalculation(2); + } + + @Test + public void testIncreasedOnBuildResultFailure() { + when(mock.getResult()).thenReturn(Result.FAILURE); + testSingleCalculation(2); + } + + private void testSingleCalculation(int expectedCount) { + BuildTotalCounter sut = new BuildTotalCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(expectedCount, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_failed_build_count_total")) { + Assertions.assertEquals(1.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_failed_build_count_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildUnstableCounterTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildUnstableCounterTest.java new file mode 100644 index 000000000..b17a71671 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildUnstableCounterTest.java @@ -0,0 +1,100 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.model.Result; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class BuildUnstableCounterTest extends MockedRunCollectorTest { + + + @Test + public void testIncreasedOnUnstableBuild() { + when(mock.getResult()).thenReturn(Result.UNSTABLE); + testSingleCalculation(2); + } + + @Test + public void testNothingIsIncreasedOnSuccessfulBuild() { + when(mock.getResult()).thenReturn(Result.SUCCESS); + testNonFailureStateBuild(); + } + + @Test + public void testNothingIsIncreasedOnNotBuiltBuild() { + when(mock.getResult()).thenReturn(Result.NOT_BUILT); + testSingleCalculation(0); + } + + @Test + public void testNoIncreaseOnAbortedBuild() { + when(mock.getResult()).thenReturn(Result.ABORTED); + testSingleCalculation(0); + } + + @Test + public void testNothingIsIncreasedOnBuildResultFailure() { + when(mock.getResult()).thenReturn(Result.FAILURE); + testSingleCalculation(0); + } + + private void testSingleCalculation(int expectedCount) { + BuildUnstableCounter sut = new BuildUnstableCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(expectedCount, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_failed_build_count_total")) { + Assertions.assertEquals(1.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_failed_build_count_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } + + @Test + public void testCounterIsNotChangedResultFailure() { + when(mock.getResult()).thenReturn(Result.FAILURE); + + BuildUnstableCounter sut = new BuildUnstableCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size(), "Would expect one result"); + + for (Collector.MetricFamilySamples.Sample sample : collect.get(0).samples) { + if (sample.name.equals("default_jenkins_builds_failed_build_count_total")) { + Assertions.assertEquals(2.0, sample.value); + } + if (sample.name.equals("default_jenkins_builds_failed_build_count_created")) { + Assertions.assertTrue(sample.value > 0); + } + } + } + + private void testNonFailureStateBuild() { + BuildUnstableCounter sut = new BuildUnstableCounter(getLabelNames(), getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size(), "Would expect one result"); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildWaitingDurationGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildWaitingDurationGaugeTest.java new file mode 100644 index 000000000..e066744e5 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/BuildWaitingDurationGaugeTest.java @@ -0,0 +1,43 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import io.prometheus.client.Collector; +import jenkins.metrics.impl.TimeInQueueAction; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.List; + + +public class BuildWaitingDurationGaugeTest extends MockedRunCollectorTest { + + @Test + public void testCalculateDurationWhenRunIsNotBuilding() { + Mockito.when(mock.isBuilding()).thenReturn(false); + TimeInQueueAction timeInQueueAction = new TimeInQueueAction(200,1,1,1); + Mockito.doReturn(timeInQueueAction).when(mock).getAction(TimeInQueueAction.class); + + BuildWaitingDurationGauge sut = new BuildWaitingDurationGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(200L, collect.get(0).samples.get(0).value); + Assertions.assertEquals("default_jenkins_builds_build_waiting_milliseconds", collect.get(0).samples.get(0).name); + } + + @Test + public void testCalculateDurationIsNotCalculatedWhenRunIsBuilding() { + Mockito.when(mock.isBuilding()).thenReturn(true); + + BuildWaitingDurationGauge sut = new BuildWaitingDurationGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size()); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/CounterManagerTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/CounterManagerTest.java new file mode 100644 index 000000000..04c2a191d --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/CounterManagerTest.java @@ -0,0 +1,113 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import org.jenkinsci.plugins.prometheus.collectors.CollectorType; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.Arrays; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +public class CounterManagerTest extends MockedRunCollectorTest { + private final CounterManager manager; + + public CounterManagerTest() { + manager = CounterManager.getManager(); + } + + @Test + public void TestEquivalentEntryReturnsCounter() { + String[] labels = new String[] { "TestLabel" }; + + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + when(config.getDefaultNamespace()).thenReturn(getNamespace()); + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + var retrievedCounter = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, labels, null); + var retrievedCounter2 = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, labels, null); + + // Should be a value reference comparison. They should be the exact same + // MetricCollector. + Assertions.assertEquals(retrievedCounter, retrievedCounter2); + } + } + + @Test + public void TestNamespaceChangeReturnsNewCounter() { + String[] labels = new String[] { "TestLabel" }; + + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + + // Use default namespace for one counter + when(config.getDefaultNamespace()).thenReturn(getNamespace()); + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + var retrievedCounter = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, labels, null); + + // Second counter returns modified namespace + when(config.getDefaultNamespace()).thenReturn("modified_namespace"); + var retrievedCounter2 = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, labels, null); + + // Should be a value reference comparison. They should not be the same metric since the namespace has changed. + Assertions.assertNotEquals(retrievedCounter, retrievedCounter2); + } + } + + @Test + public void TestLabelChangeReturnsNewCounter(){ + String[] label1 = new String[]{"labels"}; + String[] label2 = Arrays.copyOf(label1, 2); + label2[1] = "Hi"; + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + PrometheusConfiguration config = getMockCounterManagerConfig(); + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + var retrievedCounter = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, label1, null); + var retrievedCounter2 = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, label2, null); + + // Should be a value reference comparison. They should be different since labels differ. + Assertions.assertNotEquals(retrievedCounter, retrievedCounter2); + } + } + + @Test + public void TestPrefixChangeReturnsNewCounter(){ + String[] label1 = new String[]{"labels"}; + + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + PrometheusConfiguration config = getMockCounterManagerConfig(); + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + var retrievedCounter = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, label1, "yes"); + var retrievedCounter2 = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, label1, null); + + // Should be a value reference comparison. They should not be the same since prefix changed + Assertions.assertNotEquals(retrievedCounter, retrievedCounter2); + } + } + + @Test + public void TestDifferentCounterReturnsUniqueCounter(){ + String[] label1 = new String[]{"labels"}; + + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + PrometheusConfiguration config = getMockCounterManagerConfig(); + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + var retrievedCounter = manager.getCounter(CollectorType.BUILD_ABORTED_COUNTER, label1, null); + var retrievedCounter2 = manager.getCounter(CollectorType.BUILD_SUCCESSFUL_COUNTER, label1, null); + + // Should be a value reference comparison. They should not be the same since the collector type differs. + Assertions.assertNotEquals(retrievedCounter, retrievedCounter2); + } + } + + public PrometheusConfiguration getMockCounterManagerConfig(){ + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + when(config.getDefaultNamespace()).thenReturn(getNamespace()); + + return config; + } +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/FailedTestsGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/FailedTestsGaugeTest.java new file mode 100644 index 000000000..4d80f46ad --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/FailedTestsGaugeTest.java @@ -0,0 +1,58 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.tasks.test.AbstractTestResultAction; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@SuppressWarnings("rawtypes") +public class FailedTestsGaugeTest extends MockedRunCollectorTest { + + @Test + public void testCannotExecute() { + + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + FailedTestsGauge sut = new FailedTestsGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + assertEquals(1, collect.size()); + assertEquals(0, collect.get(0).samples.size()); + } + } + + @Test + public void testCollectFails() { + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + + when(config.isFetchTestResults()).thenReturn(true); + AbstractTestResultAction testResultAction = mock(AbstractTestResultAction.class); + when(testResultAction.getFailCount()).thenReturn(100); + when(mock.getAction(AbstractTestResultAction.class)).thenReturn(testResultAction); + FailedTestsGauge sut = new FailedTestsGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + assertEquals(1, collect.size()); + assertEquals(100.0, collect.get(0).samples.get(0).value, 0.0); + assertEquals("default_jenkins_builds_build_tests_failing", collect.get(0).samples.get(0).name); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/SkippedTestsGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/SkippedTestsGaugeTest.java new file mode 100644 index 000000000..26478a5d8 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/SkippedTestsGaugeTest.java @@ -0,0 +1,59 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.tasks.test.AbstractTestResultAction; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@SuppressWarnings("rawtypes") +public class SkippedTestsGaugeTest extends MockedRunCollectorTest { + + @Test + public void testCannotExecute() { + + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + SkippedTestsGauge sut = new SkippedTestsGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + assertEquals(1, collect.size()); + assertEquals(0, collect.get(0).samples.size()); + } + } + + @Test + public void testCollectSkipped() { + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + + when(config.isFetchTestResults()).thenReturn(true); + AbstractTestResultAction testResultAction = mock(AbstractTestResultAction.class); + when(testResultAction.getSkipCount()).thenReturn(100); + when(mock.getAction(AbstractTestResultAction.class)).thenReturn(testResultAction); + SkippedTestsGauge sut = new SkippedTestsGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + assertEquals(1, collect.size()); + assertEquals(100.0, collect.get(0).samples.get(0).value, 0.0); + assertEquals("default_jenkins_builds_last_build_tests_skipped", collect.get(0).samples.get(0).name); + } + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageSummaryTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageSummaryTest.java new file mode 100644 index 000000000..553ac311f --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/StageSummaryTest.java @@ -0,0 +1,60 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.List; + + +public class StageSummaryTest extends MockedRunCollectorTest { + + @Test + public void testNothingCalculatedWhenJobIsBuilding() { + Mockito.when(mock.isBuilding()).thenReturn(true); + + StageSummary sut = new StageSummary(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size()); + + } + + @Test + public void testNothingCalculatedWhenJobNotAWorkflowJob() { + Mockito.when(mock.isBuilding()).thenReturn(false); + + + StageSummary sut = new StageSummary(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size()); + + } + + @Test + public void testNothingCalculatedWhenThereIsNoWorkflowExecution() { + + StageSummary sut = new StageSummary(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + Assertions.assertEquals(1, collect.size()); + Assertions.assertEquals(0, collect.get(0).samples.size()); + } + + // TODO: Write more tests + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/TotalTestsGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/TotalTestsGaugeTest.java new file mode 100644 index 000000000..38769f079 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/builds/TotalTestsGaugeTest.java @@ -0,0 +1,58 @@ +package org.jenkinsci.plugins.prometheus.collectors.builds; + +import hudson.tasks.test.AbstractTestResultAction; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@SuppressWarnings("rawtypes") +public class TotalTestsGaugeTest extends MockedRunCollectorTest { + + @Test + public void testCannotExecute() { + + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + TotalTestsGauge sut = new TotalTestsGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + assertEquals(1, collect.size()); + assertEquals(0, collect.get(0).samples.size()); + } + } + + @Test + public void testCollectTotal() { + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + + when(config.isFetchTestResults()).thenReturn(true); + AbstractTestResultAction testResultAction = mock(AbstractTestResultAction.class); + when(testResultAction.getTotalCount()).thenReturn(100); + when(mock.getAction(AbstractTestResultAction.class)).thenReturn(testResultAction); + TotalTestsGauge sut = new TotalTestsGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + assertEquals(1, collect.size()); + assertEquals(100.0, collect.get(0).samples.get(0).value, 0.0); + assertEquals("default_jenkins_builds_build_tests_total", collect.get(0).samples.get(0).name); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchCoveredGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchCoveredGaugeTest.java new file mode 100644 index 000000000..0915718b3 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchCoveredGaugeTest.java @@ -0,0 +1,66 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageBranchCoveredGaugeTest extends CoverageTest { + + + public CoverageBranchCoveredGaugeTest() { + super(Baseline.PROJECT, Metric.BRANCH); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForCovered(); + CoverageBranchCoveredGauge sut = new CoverageBranchCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of branches covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_branch_covered", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(10.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageBranchCoveredGauge sut = new CoverageBranchCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of branches covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_branch_covered", familySamples.name); + + List samples = familySamples.samples; + Assertions.assertEquals(1, familySamples.samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchMissedGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchMissedGaugeTest.java new file mode 100644 index 000000000..491d057c5 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchMissedGaugeTest.java @@ -0,0 +1,69 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageBranchMissedGaugeTest extends CoverageTest { + + + + public CoverageBranchMissedGaugeTest() { + super(Baseline.PROJECT, Metric.BRANCH); + } + + @Test + public void testMissed() { + setUpSuccessfulMocksForMissed(); + CoverageBranchMissedGauge sut = new CoverageBranchMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of branches missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_branch_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(5.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageBranchMissedGauge sut = new CoverageBranchMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of branches missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_branch_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchPercentGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchPercentGaugeTest.java new file mode 100644 index 000000000..29985d736 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchPercentGaugeTest.java @@ -0,0 +1,67 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageBranchPercentGaugeTest extends CoverageTest { + + + public CoverageBranchPercentGaugeTest() { + super(Baseline.PROJECT, Metric.BRANCH); + } + + @Test + public void testPercentage() { + setUpSuccessfulMocksForPercent(); + CoverageBranchPercentGauge sut = new CoverageBranchPercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of branches in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_branch_percent", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(25.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageBranchPercentGauge sut = new CoverageBranchPercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of branches in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_branch_percent", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchTotalGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchTotalGaugeTest.java new file mode 100644 index 000000000..e42382592 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageBranchTotalGaugeTest.java @@ -0,0 +1,67 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageBranchTotalGaugeTest extends CoverageTest { + + + public CoverageBranchTotalGaugeTest() { + super(Baseline.PROJECT, Metric.BRANCH); + } + + @Test + public void testMissed() { + setUpSuccessfulMocksForTotal(); + CoverageBranchTotalGauge sut = new CoverageBranchTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of branches total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_branch_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(15.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageBranchTotalGauge sut = new CoverageBranchTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of branches total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_branch_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassCoveredGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassCoveredGaugeTest.java new file mode 100644 index 000000000..43ac2767d --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassCoveredGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageClassCoveredGaugeTest extends CoverageTest { + + + public CoverageClassCoveredGaugeTest() { + super(Baseline.PROJECT, Metric.CLASS); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForCovered(); + CoverageClassCoveredGauge sut = new CoverageClassCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of classes covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_class_covered", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(10.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageClassCoveredGauge sut = new CoverageClassCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of classes covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_class_covered", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassMissedGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassMissedGaugeTest.java new file mode 100644 index 000000000..031ce9226 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassMissedGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageClassMissedGaugeTest extends CoverageTest { + + + public CoverageClassMissedGaugeTest() { + super(Baseline.PROJECT, Metric.CLASS); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForMissed(); + CoverageClassMissedGauge sut = new CoverageClassMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of classes missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_class_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(5.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageClassMissedGauge sut = new CoverageClassMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of classes missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_class_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassPercentGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassPercentGaugeTest.java new file mode 100644 index 000000000..eb5d60073 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassPercentGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageClassPercentGaugeTest extends CoverageTest { + + + public CoverageClassPercentGaugeTest() { + super(Baseline.PROJECT, Metric.CLASS); + } + + @Test + public void testPercentage() { + setUpSuccessfulMocksForPercent(); + CoverageClassPercentGauge sut = new CoverageClassPercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of classes in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_class_percent", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(25.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageClassPercentGauge sut = new CoverageClassPercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of classes in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_class_percent", familySamples.name); + + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassTotalGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassTotalGaugeTest.java new file mode 100644 index 000000000..ce8172b97 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageClassTotalGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageClassTotalGaugeTest extends CoverageTest { + + + public CoverageClassTotalGaugeTest() { + super(Baseline.PROJECT, Metric.CLASS); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForTotal(); + CoverageClassTotalGauge sut = new CoverageClassTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of classes total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_class_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(15.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageClassTotalGauge sut = new CoverageClassTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of classes total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_class_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileCoveredGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileCoveredGaugeTest.java new file mode 100644 index 000000000..3b9d2f6a4 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileCoveredGaugeTest.java @@ -0,0 +1,67 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageFileCoveredGaugeTest extends CoverageTest { + + + public CoverageFileCoveredGaugeTest() { + super(Baseline.PROJECT, Metric.FILE); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForCovered(); + CoverageFileCoveredGauge sut = new CoverageFileCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of files covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_file_covered", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(10.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageFileCoveredGauge sut = new CoverageFileCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of files covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_file_covered", familySamples.name); + + List samples = familySamples.samples; + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileMissedGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileMissedGaugeTest.java new file mode 100644 index 000000000..af624418d --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileMissedGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageFileMissedGaugeTest extends CoverageTest { + + + public CoverageFileMissedGaugeTest() { + super(Baseline.PROJECT, Metric.FILE); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForMissed(); + CoverageFileMissedGauge sut = new CoverageFileMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of files missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_file_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(5.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageFileMissedGauge sut = new CoverageFileMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of files missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_file_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFilePercentGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFilePercentGaugeTest.java new file mode 100644 index 000000000..0badb44b7 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFilePercentGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageFilePercentGaugeTest extends CoverageTest { + + + public CoverageFilePercentGaugeTest() { + super(Baseline.PROJECT, Metric.FILE); + } + + @Test + public void testPercentage() { + setUpSuccessfulMocksForPercent(); + CoverageFilePercentGauge sut = new CoverageFilePercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of files in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_file_percent", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(25.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageFilePercentGauge sut = new CoverageFilePercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of files in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_file_percent", familySamples.name); + + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileTotalGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileTotalGaugeTest.java new file mode 100644 index 000000000..3692ed7a0 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageFileTotalGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageFileTotalGaugeTest extends CoverageTest { + + + public CoverageFileTotalGaugeTest() { + super(Baseline.PROJECT, Metric.FILE); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForTotal(); + CoverageFileTotalGauge sut = new CoverageFileTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of files total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_file_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(15.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageFileTotalGauge sut = new CoverageFileTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of files total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_file_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionCoveredGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionCoveredGaugeTest.java new file mode 100644 index 000000000..7f191b65e --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionCoveredGaugeTest.java @@ -0,0 +1,67 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageInstructionCoveredGaugeTest extends CoverageTest { + + + public CoverageInstructionCoveredGaugeTest() { + super(Baseline.PROJECT, Metric.INSTRUCTION); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForCovered(); + CoverageInstructionCoveredGauge sut = new CoverageInstructionCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of instructions covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_instruction_covered", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(10.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageInstructionCoveredGauge sut = new CoverageInstructionCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of instructions covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_instruction_covered", familySamples.name); + + List samples = familySamples.samples; + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionMissedGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionMissedGaugeTest.java new file mode 100644 index 000000000..03082d700 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionMissedGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageInstructionMissedGaugeTest extends CoverageTest { + + + public CoverageInstructionMissedGaugeTest() { + super(Baseline.PROJECT, Metric.INSTRUCTION); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForMissed(); + CoverageInstructionMissedGauge sut = new CoverageInstructionMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of instructions missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_instruction_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(5.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageInstructionMissedGauge sut = new CoverageInstructionMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of instructions missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_instruction_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionPercentGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionPercentGaugeTest.java new file mode 100644 index 000000000..1e3746a2c --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionPercentGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageInstructionPercentGaugeTest extends CoverageTest { + + + public CoverageInstructionPercentGaugeTest() { + super(Baseline.PROJECT, Metric.INSTRUCTION); + } + + @Test + public void testPercentage() { + setUpSuccessfulMocksForPercent(); + CoverageInstructionPercentGauge sut = new CoverageInstructionPercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of instructions in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_instruction_percent", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(25.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageInstructionPercentGauge sut = new CoverageInstructionPercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of instructions in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_instruction_percent", familySamples.name); + + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionTotalGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionTotalGaugeTest.java new file mode 100644 index 000000000..4fafb3d89 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageInstructionTotalGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageInstructionTotalGaugeTest extends CoverageTest { + + + public CoverageInstructionTotalGaugeTest() { + super(Baseline.PROJECT, Metric.INSTRUCTION); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForTotal(); + CoverageInstructionTotalGauge sut = new CoverageInstructionTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of instructions total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_instruction_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(15.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageInstructionTotalGauge sut = new CoverageInstructionTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of instructions total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_instruction_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineCoveredGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineCoveredGaugeTest.java new file mode 100644 index 000000000..9a917bc8e --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineCoveredGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageLineCoveredGaugeTest extends CoverageTest { + + + public CoverageLineCoveredGaugeTest() { + super(Baseline.PROJECT, Metric.LINE); + } + + @Test + public void testCovered() { + setUpSuccessfulMocksForCovered(); + CoverageLineCoveredGauge sut = new CoverageLineCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of lines covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_line_covered", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(10.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageLineCoveredGauge sut = new CoverageLineCoveredGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of lines covered", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_line_covered", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineMissedGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineMissedGaugeTest.java new file mode 100644 index 000000000..870018850 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineMissedGaugeTest.java @@ -0,0 +1,67 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class CoverageLineMissedGaugeTest extends CoverageTest { + + + public CoverageLineMissedGaugeTest() { + super(Baseline.PROJECT, Metric.LINE); + } + + @Test + public void testMissed() { + setUpSuccessfulMocksForMissed(); + CoverageLineMissedGauge sut = new CoverageLineMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of lines missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_line_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(5.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageLineMissedGauge sut = new CoverageLineMissedGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of lines missed", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_line_missed", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLinePercentGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLinePercentGaugeTest.java new file mode 100644 index 000000000..0c4052a72 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLinePercentGaugeTest.java @@ -0,0 +1,68 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class CoverageLinePercentGaugeTest extends CoverageTest { + + + public CoverageLinePercentGaugeTest() { + super(Baseline.PROJECT, Metric.LINE); + } + + @Test + public void testPercentage() { + setUpSuccessfulMocksForPercent(); + CoverageLinePercentGauge sut = new CoverageLinePercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of lines in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_line_percent", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(25.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageLinePercentGauge sut = new CoverageLinePercentGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the coverage of lines in percent", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_line_percent", familySamples.name); + + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineTotalGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineTotalGaugeTest.java new file mode 100644 index 000000000..86a4ad564 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageLineTotalGaugeTest.java @@ -0,0 +1,67 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class CoverageLineTotalGaugeTest extends CoverageTest { + + + public CoverageLineTotalGaugeTest() { + super(Baseline.PROJECT, Metric.LINE); + } + + @Test + public void testMissed() { + setUpSuccessfulMocksForTotal(); + CoverageLineTotalGauge sut = new CoverageLineTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of lines total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_line_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(15.0, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + + } + + @Test + public void testNothingFailsIfNoCoverageFound() { + setUpUnsuccessfulMocks(); + + CoverageLineTotalGauge sut = new CoverageLineTotalGauge(new String[]{"job"}, getNamespace(), getSubSystem()); + + sut.calculateMetric(mock, new String[]{"myJob"}); + + List metricFamilySamples = sut.collect(); + Assertions.assertEquals(1, metricFamilySamples.size()); + + Collector.MetricFamilySamples familySamples = metricFamilySamples.get(0); + + Assertions.assertEquals("Returns the number of lines total", familySamples.help); + Assertions.assertEquals("default_jenkins_builds_coverage_line_total", familySamples.name); + + List samples = familySamples.samples; + + Assertions.assertEquals(1, samples.size()); + + Collector.MetricFamilySamples.Sample sample = samples.get(0); + Assertions.assertEquals(-1, sample.value); + Assertions.assertEquals("myJob", sample.labelValues.get(0)); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageTest.java new file mode 100644 index 000000000..6167d4016 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/coverage/CoverageTest.java @@ -0,0 +1,74 @@ +package org.jenkinsci.plugins.prometheus.collectors.coverage; + +import edu.hm.hafner.coverage.Coverage; +import edu.hm.hafner.coverage.Metric; +import io.jenkins.plugins.coverage.metrics.model.Baseline; +import io.jenkins.plugins.coverage.metrics.steps.CoverageBuildAction; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedRunCollectorTest; +import org.mockito.Mockito; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public abstract class CoverageTest extends MockedRunCollectorTest { + + private final Baseline baseline; + private final Metric metric; + + public CoverageTest(Baseline baseline, Metric metric) { + this.baseline = baseline; + this.metric = metric; + } + + + protected void setUpSuccessfulMocksForCovered() { + CoverageBuildAction mockedCoverageBuildAction = Mockito.mock(CoverageBuildAction.class); + Coverage mockedCoverage = Mockito.mock(Coverage.class); + when(mockedCoverage.getMetric()).thenReturn(metric); + when(mockedCoverage.getCovered()).thenReturn(10); + when(mockedCoverageBuildAction.getAllValues(baseline)).thenReturn(List.of(mockedCoverage)); + when(mock.getAction(CoverageBuildAction.class)).thenReturn(mockedCoverageBuildAction); + } + + protected void setUpSuccessfulMocksForMissed() { + CoverageBuildAction mockedCoverageBuildAction = Mockito.mock(CoverageBuildAction.class); + Coverage mockedCoverage = Mockito.mock(Coverage.class); + when(mockedCoverage.getMetric()).thenReturn(metric); + when(mockedCoverage.getMissed()).thenReturn(5); + when(mockedCoverageBuildAction.getAllValues(baseline)).thenReturn(List.of(mockedCoverage)); + when(mock.getAction(CoverageBuildAction.class)).thenReturn(mockedCoverageBuildAction); + } + + protected void setUpSuccessfulMocksForTotal() { + CoverageBuildAction mockedCoverageBuildAction = Mockito.mock(CoverageBuildAction.class); + Coverage mockedCoverage = Mockito.mock(Coverage.class); + when(mockedCoverage.getMetric()).thenReturn(metric); + when(mockedCoverage.getTotal()).thenReturn(15); + when(mockedCoverageBuildAction.getAllValues(baseline)).thenReturn(List.of(mockedCoverage)); + when(mock.getAction(CoverageBuildAction.class)).thenReturn(mockedCoverageBuildAction); + } + + protected void setUpSuccessfulMocksForPercent() { + CoverageBuildAction mockedCoverageBuildAction = Mockito.mock(CoverageBuildAction.class); + Coverage mockedCoverage = Mockito.mock(Coverage.class); + when(mockedCoverage.getMetric()).thenReturn(metric); + when(mockedCoverage.getTotal()).thenReturn(100); + when(mockedCoverage.getCovered()).thenReturn(25); + when(mockedCoverageBuildAction.getAllValues(baseline)).thenReturn(List.of(mockedCoverage)); + when(mock.getAction(CoverageBuildAction.class)).thenReturn(mockedCoverageBuildAction); + } + + + + + + + protected void setUpUnsuccessfulMocks() { + CoverageBuildAction mockedCoverageBuildAction = Mockito.mock(CoverageBuildAction.class); + Coverage mockedCoverage = Mockito.mock(Coverage.class); + when(mockedCoverageBuildAction.getAllValues(baseline)).thenReturn(List.of(mockedCoverage)); + when(mock.getAction(CoverageBuildAction.class)).thenReturn(mockedCoverageBuildAction); + } + +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageBytesGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageBytesGaugeTest.java new file mode 100644 index 000000000..fe2b8b08e --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageBytesGaugeTest.java @@ -0,0 +1,63 @@ +package org.jenkinsci.plugins.prometheus.collectors.disk; + +import com.cloudbees.simplediskusage.DiskItem; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.CollectorTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class DiskUsageBytesGaugeTest extends CollectorTest { + + @Mock + DiskItem mock; + + @Test + public void testCollectResult() { + + when(mock.getUsage()).thenReturn(10L); + + DiskUsageBytesGauge sut = new DiskUsageBytesGauge(getLabelNames(), getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_disk_usage_bytes"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Disk usage of first level folder in JENKINS_HOME in bytes"); + validateValue(samples, 0, 10240.0); + } + + @Test + public void testDiskItemIsNull() { + DiskUsageBytesGauge sut = new DiskUsageBytesGauge(getLabelNames(), getNamespace(), getSubSystem()); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } + + @Test + public void testDiskItemUsageIsNull() { + when(mock.getUsage()).thenReturn(null); + DiskUsageBytesGauge sut = new DiskUsageBytesGauge(getLabelNames(), getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageFileCountGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageFileCountGaugeTest.java new file mode 100644 index 000000000..4c055d65b --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/disk/DiskUsageFileCountGaugeTest.java @@ -0,0 +1,63 @@ +package org.jenkinsci.plugins.prometheus.collectors.disk; + +import com.cloudbees.simplediskusage.DiskItem; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.CollectorTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class DiskUsageFileCountGaugeTest extends CollectorTest { + + @Mock + DiskItem mock; + + @Test + public void testCollectResult() { + + when(mock.getCount()).thenReturn(10L); + + DiskUsageFileCountGauge sut = new DiskUsageFileCountGauge(getLabelNames(), getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_disk_usage_file_count"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Disk usage file count of the first level folder in JENKINS_HOME"); + validateValue(samples, 0, 10.0); + } + + @Test + public void testDiskItemIsNull() { + DiskUsageFileCountGauge sut = new DiskUsageFileCountGauge(getLabelNames(), getNamespace(), getSubSystem()); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } + + @Test + public void testDiskItemCountIsNull() { + when(mock.getCount()).thenReturn(null); + DiskUsageFileCountGauge sut = new DiskUsageFileCountGauge(getLabelNames(), getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/disk/JobUsageBytesGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/disk/JobUsageBytesGaugeTest.java new file mode 100644 index 000000000..1dd39839a --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/disk/JobUsageBytesGaugeTest.java @@ -0,0 +1,65 @@ +package org.jenkinsci.plugins.prometheus.collectors.disk; + +import com.cloudbees.simplediskusage.JobDiskItem; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.CollectorTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.mockito.Mockito.when; + + + +@ExtendWith(MockitoExtension.class) +class JobUsageBytesGaugeTest extends CollectorTest { + + @Mock + JobDiskItem mock; + + @Test + public void testCollectResult() { + + when(mock.getUsage()).thenReturn(10L); + + JobUsageBytesGauge sut = new JobUsageBytesGauge(getLabelNames(), getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_job_usage_bytes"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Amount of disk usage (bytes) for each job in Jenkins"); + validateValue(samples, 0, 10240.0); + } + + @Test + public void testJobDiskItemIsNull() { + JobUsageBytesGauge sut = new JobUsageBytesGauge(getLabelNames(), getNamespace(), getSubSystem()); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } + + @Test + public void testJobDiskItemUsageIsNull() { + when(mock.getUsage()).thenReturn(null); + JobUsageBytesGauge sut = new JobUsageBytesGauge(getLabelNames(), getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsAvailableGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsAvailableGaugeTest.java new file mode 100644 index 000000000..a819b2247 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsAvailableGaugeTest.java @@ -0,0 +1,44 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedLoadStatisticSnapshotTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class ExecutorsAvailableGaugeTest extends MockedLoadStatisticSnapshotTest { + + + @Test + public void testCollectResult() { + + when(mock.getAvailableExecutors()).thenReturn(10); + + ExecutorsAvailableGauge sut = new ExecutorsAvailableGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_available"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Executors Available"); + validateValue(samples, 0, 10.0); + } + + @Test + public void testSnapshotIsNull() { + ExecutorsAvailableGauge sut = new ExecutorsAvailableGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsBusyGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsBusyGaugeTest.java new file mode 100644 index 000000000..66838f06d --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsBusyGaugeTest.java @@ -0,0 +1,44 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedLoadStatisticSnapshotTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class ExecutorsBusyGaugeTest extends MockedLoadStatisticSnapshotTest { + + + @Test + public void testCollectResult() { + + when(mock.getBusyExecutors()).thenReturn(10); + + ExecutorsBusyGauge sut = new ExecutorsBusyGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_busy"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Executors Busy"); + validateValue(samples, 0, 10.0); + } + + @Test + public void testSnapshotIsNull() { + ExecutorsBusyGauge sut = new ExecutorsBusyGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsConnectingGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsConnectingGaugeTest.java new file mode 100644 index 000000000..3491f4891 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsConnectingGaugeTest.java @@ -0,0 +1,44 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedLoadStatisticSnapshotTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class ExecutorsConnectingGaugeTest extends MockedLoadStatisticSnapshotTest { + + + @Test + public void testCollectResult() { + + when(mock.getConnectingExecutors()).thenReturn(10); + + ExecutorsConnectingGauge sut = new ExecutorsConnectingGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_connecting"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Executors Connecting"); + validateValue(samples, 0, 10.0); + } + + @Test + public void testSnapshotIsNull() { + ExecutorsConnectingGauge sut = new ExecutorsConnectingGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsDefinedGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsDefinedGaugeTest.java new file mode 100644 index 000000000..3149a001f --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsDefinedGaugeTest.java @@ -0,0 +1,45 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedLoadStatisticSnapshotTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class ExecutorsDefinedGaugeTest extends MockedLoadStatisticSnapshotTest { + + + @Test + public void testCollectResult() { + + when(mock.getDefinedExecutors()).thenReturn(10); + + ExecutorsDefinedGauge sut = new ExecutorsDefinedGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_defined"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Executors Defined"); + validateValue(samples, 0, 10.0); + } + + @Test + public void testSnapshotIsNull() { + ExecutorsDefinedGauge sut = new ExecutorsDefinedGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsIdleGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsIdleGaugeTest.java new file mode 100644 index 000000000..c6328e8c9 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsIdleGaugeTest.java @@ -0,0 +1,45 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedLoadStatisticSnapshotTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class ExecutorsIdleGaugeTest extends MockedLoadStatisticSnapshotTest { + + + @Test + public void testCollectResult() { + + when(mock.getIdleExecutors()).thenReturn(10); + + ExecutorsIdleGauge sut = new ExecutorsIdleGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_idle"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Executors Idle"); + validateValue(samples, 0, 10.0); + } + + @Test + public void testSnapshotIsNull() { + ExecutorsIdleGauge sut = new ExecutorsIdleGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsOnlineGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsOnlineGaugeTest.java new file mode 100644 index 000000000..82b0a65cc --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsOnlineGaugeTest.java @@ -0,0 +1,44 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedLoadStatisticSnapshotTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class ExecutorsOnlineGaugeTest extends MockedLoadStatisticSnapshotTest { + + @Test + public void testCollectResult() { + + when(mock.getOnlineExecutors()).thenReturn(10); + + ExecutorsOnlineGauge sut = new ExecutorsOnlineGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_online"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Executors Online"); + validateValue(samples, 0, 10.0); + } + + @Test + public void testSnapshotIsNull() { + ExecutorsOnlineGauge sut = new ExecutorsOnlineGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsQueueLengthGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsQueueLengthGaugeTest.java new file mode 100644 index 000000000..5dc7a9dd0 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/executors/ExecutorsQueueLengthGaugeTest.java @@ -0,0 +1,44 @@ +package org.jenkinsci.plugins.prometheus.collectors.executors; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedLoadStatisticSnapshotTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class ExecutorsQueueLengthGaugeTest extends MockedLoadStatisticSnapshotTest { + + @Test + public void testCollectResult() { + + when(mock.getQueueLength()).thenReturn(10); + + ExecutorsQueueLengthGauge sut = new ExecutorsQueueLengthGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_queue_length"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Executors Queue Length"); + validateValue(samples, 0, 10.0); + } + + @Test + public void testSnapshotIsNull() { + ExecutorsQueueLengthGauge sut = new ExecutorsQueueLengthGauge(getLabelNames(), getNamespace(), getSubSystem(), ""); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + validateMetricFamilySampleSize(collect.get(0), 0); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsQuietDownGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsQuietDownGaugeTest.java new file mode 100644 index 000000000..676075cc6 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsQuietDownGaugeTest.java @@ -0,0 +1,71 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedJenkinsTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class JenkinsQuietDownGaugeTest extends MockedJenkinsTest { + + + @Test + public void testCollectResultForJenkinsQuietModeEnabled() { + + when(mock.isQuietingDown()).thenReturn(true); + + JenkinsQuietDownGauge sut = new JenkinsQuietDownGauge(new String[]{}, getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_quietdown"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Is Jenkins in quiet mode"); + validateValue(samples, 0, 1.0); + } + + + @Test + public void testCollectResultForJenkinsQuietModeDisabled() { + + when(mock.isQuietingDown()).thenReturn(false); + + JenkinsQuietDownGauge sut = new JenkinsQuietDownGauge(new String[]{}, getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_quietdown"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Is Jenkins in quiet mode"); + validateValue(samples, 0, 0.0); + } + + @Test + public void testJenkinsIsNull() { + JenkinsQuietDownGauge sut = new JenkinsQuietDownGauge(new String[]{}, getNamespace(), getSubSystem()); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_quietdown"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Is Jenkins in quiet mode"); + validateValue(samples, 0, 0.0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUpGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUpGaugeTest.java new file mode 100644 index 000000000..433e94fdf --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUpGaugeTest.java @@ -0,0 +1,72 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import hudson.init.InitMilestone; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedJenkinsTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class JenkinsUpGaugeTest extends MockedJenkinsTest { + + + @Test + public void testCollectResultForJenkinsStarted() { + + when(mock.getInitLevel()).thenReturn(InitMilestone.STARTED); + + JenkinsUpGauge sut = new JenkinsUpGauge(new String[]{}, getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_up"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Is Jenkins ready to receive requests"); + validateValue(samples, 0, 0.0); + } + + + @Test + public void testCollectResultForJenkinsCompleted() { + + when(mock.getInitLevel()).thenReturn(InitMilestone.COMPLETED); + + JenkinsUpGauge sut = new JenkinsUpGauge(new String[]{}, getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_up"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Is Jenkins ready to receive requests"); + validateValue(samples, 0, 1.0); + } + + @Test + public void testJenkinsIsNull() { + JenkinsUpGauge sut = new JenkinsUpGauge(new String[]{}, getNamespace(), getSubSystem()); + sut.calculateMetric(null, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_up"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Is Jenkins ready to receive requests"); + validateValue(samples, 0, 0.0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUptimeGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUptimeGaugeTest.java new file mode 100644 index 000000000..529c1978a --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsUptimeGaugeTest.java @@ -0,0 +1,48 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import hudson.model.Computer; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedJenkinsTest; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.time.Clock; +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class JenkinsUptimeGaugeTest extends MockedJenkinsTest { + + @Test + public void testCollectResult() { + + try (MockedStatic clockMockedStatic = Mockito.mockStatic(Clock.class)) { + + Clock mockedClock = mock(Clock.class); + clockMockedStatic.when(Clock::systemUTC).thenReturn(mockedClock); + when(mockedClock.millis()).thenReturn(2000L); + Computer computerMock = mock(Computer.class); + + when(computerMock.getConnectTime()).thenReturn(500L); + + when(mock.toComputer()).thenReturn(computerMock); + + JenkinsUptimeGauge sut = new JenkinsUptimeGauge(new String[]{}, getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_uptime"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Time since Jenkins machine was initialized"); + validateValue(samples, 0, 1500.0); + } + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsVersionInfoTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsVersionInfoTest.java new file mode 100644 index 000000000..c7a81d378 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/JenkinsVersionInfoTest.java @@ -0,0 +1,47 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import io.prometheus.client.Collector; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedJenkinsTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.List; + +public class JenkinsVersionInfoTest extends MockedJenkinsTest { + + @Test + public void testCollectResult() throws Exception { + + + setFinalStaticTo123(Jenkins.class.getDeclaredField("VERSION")); + + + JenkinsVersionInfo sut = new JenkinsVersionInfo(new String[]{}, getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + System.out.println(samples); + validateNames(samples, new String[]{"default_jenkins_version_info", "default_jenkins_version"}); + validateMetricFamilySampleSize(samples, 1); + validateHelp(samples, "Jenkins Application Version"); + validateValue(samples, 0, 1.0); + + + Assertions.assertEquals("version", samples.samples.get(0).labelNames.get(0)); + Assertions.assertEquals("123", samples.samples.get(0).labelValues.get(0)); + + } + + static void setFinalStaticTo123(Field field) throws Exception { + field.setAccessible(true); + field.set(null, "123"); + } + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/NodesOnlineGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/NodesOnlineGaugeTest.java new file mode 100644 index 000000000..abd955968 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jenkins/NodesOnlineGaugeTest.java @@ -0,0 +1,74 @@ +package org.jenkinsci.plugins.prometheus.collectors.jenkins; + +import hudson.model.Computer; +import hudson.model.Node; +import io.prometheus.client.Collector; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.collectors.testutils.MockedJenkinsTest; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class NodesOnlineGaugeTest extends MockedJenkinsTest { + + + @Test + public void testCollectResult() throws Exception { + + + setFinalStaticTo123(Jenkins.class.getDeclaredField("VERSION")); + + List nodes = new ArrayList<>(); + nodes.add(mockNode("node1", true)); + nodes.add(mockNode("node2", false)); + nodes.add(mockNode("node3", true)); + nodes.add(mockNode("nullNode", false)); + when(mock.getNodes()).thenReturn(nodes); + + NodesOnlineGauge sut = new NodesOnlineGauge(new String[]{"node"}, getNamespace(), getSubSystem()); + sut.calculateMetric(mock, getLabelValues()); + + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + // we pass 4 nodes but we only get 3 -> there's a node with null computer + validateMetricFamilySampleSize(samples, 3); + for (Collector.MetricFamilySamples.Sample sample : samples.samples) { + System.out.println(sample); + if (sample.labelValues.contains("node1")) { + validateValue(sample, 1.0); + } + if (sample.labelValues.contains("node2")) { + validateValue(sample, 0.0); + } + if (sample.labelValues.contains("node3")) { + validateValue(sample, 1.0); + } + } + System.out.println(samples); + validateNames(samples, new String[]{"default_jenkins_nodes_online"}); + } + + private Node mockNode(String nodeName, boolean isOnline) { + Node nodeMock = mock(Node.class); + if (!"nullNode".equalsIgnoreCase(nodeName)) { + Computer computerMock = mock(Computer.class); + when(computerMock.isOnline()).thenReturn(isOnline); + when(nodeMock.toComputer()).thenReturn(computerMock); + when(nodeMock.getNodeName()).thenReturn(nodeName); + } + return nodeMock; + } + + static void setFinalStaticTo123(Field field) throws Exception { + field.setAccessible(true); + field.set(null, "123"); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/BuildDiscardGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/BuildDiscardGaugeTest.java new file mode 100644 index 000000000..8372c78fe --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/BuildDiscardGaugeTest.java @@ -0,0 +1,48 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.tasks.LogRotator; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class BuildDiscardGaugeTest extends JobCollectorTest { + + + @Test + public void testCollectResult() { + when(job.getBuildDiscarder()).thenReturn(null); + + BuildDiscardGauge sut = new BuildDiscardGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + validateNames(samples, new String[]{"default_jenkins_builds_discard_active"}); + validateMetricFamilySampleSize(samples, 1); + validateValue(samples.samples.get(0), 0.0); + + } + + @Test + public void testBuildDiscarderActive() { + when(job.getBuildDiscarder()).thenReturn(new LogRotator(1,2,3,4)); + + BuildDiscardGauge sut = new BuildDiscardGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + validateNames(samples, new String[]{"default_jenkins_builds_discard_active"}); + validateMetricFamilySampleSize(samples, 1); + validateValue(samples.samples.get(0), 1.0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/CurrentRunDurationGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/CurrentRunDurationGaugeTest.java new file mode 100644 index 000000000..63c589737 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/CurrentRunDurationGaugeTest.java @@ -0,0 +1,84 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.model.Run; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.time.Clock; +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CurrentRunDurationGaugeTest extends JobCollectorTest { + + @Mock + @SuppressWarnings("rawtypes") + Run currentRun; + + + @Test + public void testCollectResult() { + when(currentRun.isBuilding()).thenReturn(true); + when(currentRun.getStartTimeInMillis()).thenReturn(1000L); + when(job.getLastBuild()).thenReturn(currentRun); + + CurrentRunDurationGauge sut = new CurrentRunDurationGauge(getLabelNames(), getNamespace(), getSubSystem()); + + try (MockedStatic mock = Mockito.mockStatic(Clock.class)) { + + Clock mockedClock = mock(Clock.class); + mock.when(Clock::systemUTC).thenReturn(mockedClock); + when(mockedClock.millis()).thenReturn(2000L); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + validateNames(samples, new String[]{"default_jenkins_builds_running_build_duration_milliseconds"}); + validateMetricFamilySampleSize(samples, 1); + validateValue(samples.samples.get(0), 1000.0); + } + } + + @Test + public void testCurrentlyRunningLabelValue() { + when(currentRun.isBuilding()).thenReturn(true); + when(currentRun.getStartTimeInMillis()).thenReturn(1000L); + when(job.getLastBuild()).thenReturn(currentRun); + + CurrentRunDurationGauge sut = new CurrentRunDurationGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + + List labelNames = sut.collect().get(0).samples.get(0).labelNames; + List labelValues = sut.collect().get(0).samples.get(0).labelValues; + + Assertions.assertEquals(2, labelNames.size()); + Assertions.assertEquals(2, labelValues.size()); + + double value = sut.collect().get(0).samples.get(0).value; + + Assertions.assertNotEquals(0.0, value); + } + + @Test + public void testCurrentlyNotRunning() { + when(currentRun.isBuilding()).thenReturn(false); + when(job.getLastBuild()).thenReturn(currentRun); + + CurrentRunDurationGauge sut = new CurrentRunDurationGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + + Assertions.assertEquals(1, sut.collect().size(), "Expected not to be calculated"); + } + + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/HealthScoreGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/HealthScoreGaugeTest.java new file mode 100644 index 000000000..77bcd252e --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/HealthScoreGaugeTest.java @@ -0,0 +1,33 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.model.HealthReport; +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class HealthScoreGaugeTest extends JobCollectorTest { + + + @Test + public void testCollectResult() { + + HealthReport healthReport = new HealthReport(); + healthReport.setScore(44); + when(job.getBuildHealth()).thenReturn(healthReport); + + HealthScoreGauge sut = new HealthScoreGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + validateNames(samples, new String[]{"default_jenkins_builds_health_score"}); + validateMetricFamilySampleSize(samples, 1); + validateValue(samples.samples.get(0), 44.0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/JobCollectorTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/JobCollectorTest.java new file mode 100644 index 000000000..2df8719f1 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/JobCollectorTest.java @@ -0,0 +1,18 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + + +import hudson.model.Job; +import org.jenkinsci.plugins.prometheus.collectors.testutils.CollectorTest; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public abstract class JobCollectorTest extends CollectorTest { + + @Mock + @SuppressWarnings("rawtypes") + protected Job job; + + +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/LogUpdatedGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/LogUpdatedGaugeTest.java new file mode 100644 index 000000000..c55b3b2c4 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/LogUpdatedGaugeTest.java @@ -0,0 +1,72 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.when; + +public class LogUpdatedGaugeTest extends JobCollectorTest { + + @Test + public void testOnlyCalculatedIfCurrentlyBuilding() { + when(job.isBuilding()).thenReturn(false); + + LogUpdatedGauge sut = new LogUpdatedGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + validateNames(samples, new String[]{"default_jenkins_builds_job_log_updated"}); + validateMetricFamilySampleSize(samples, 0); + } + + @Test + public void testBasicAttributes() { + when(job.isLogUpdated()).thenReturn(true); + when(job.isBuilding()).thenReturn(true); + + LogUpdatedGauge sut = new LogUpdatedGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + validateNames(samples, new String[]{"default_jenkins_builds_job_log_updated"}); + validateMetricFamilySampleSize(samples, 1); + } + + @Test + public void testLogIsUpdatedReturnsOne() { + + when(job.isLogUpdated()).thenReturn(true); + when(job.isBuilding()).thenReturn(true); + + LogUpdatedGauge sut = new LogUpdatedGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + List collect = sut.collect(); + Collector.MetricFamilySamples samples = collect.get(0); + validateValue(samples.samples.get(0), 1.0); + } + + @Test + public void testLogIsNotUpdatedReturnsZero() { + + when(job.isLogUpdated()).thenReturn(false); + when(job.isBuilding()).thenReturn(true); + + LogUpdatedGauge sut = new LogUpdatedGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + List collect = sut.collect(); + Collector.MetricFamilySamples samples = collect.get(0); + validateValue(samples.samples.get(0), 0.0); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/NbBuildsGaugeTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/NbBuildsGaugeTest.java new file mode 100644 index 000000000..05ed87f5e --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/jobs/NbBuildsGaugeTest.java @@ -0,0 +1,39 @@ +package org.jenkinsci.plugins.prometheus.collectors.jobs; + +import hudson.model.RunMap; +import io.prometheus.client.Collector; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import java.util.List; + +import static org.mockito.Mockito.when; + + +public class NbBuildsGaugeTest extends JobCollectorTest { + + @Mock + RunMap runMap; + + @Test + public void testCollectResult() { + + when(runMap.size()).thenReturn(12); + when(job.getBuildsAsMap()).thenReturn(runMap); + + NbBuildsGauge sut = new NbBuildsGauge(new String[]{"jenkins_job", "repo"}, "default", "jenkins"); + + sut.calculateMetric(job, new String[]{"job1", "NA"}); + List collect = sut.collect(); + + validateMetricFamilySampleListSize(collect, 1); + + Collector.MetricFamilySamples samples = collect.get(0); + + validateNames(samples, new String[]{"default_jenkins_builds_available_builds_count"}); + validateMetricFamilySampleSize(samples, 1); + validateValue(samples.samples.get(0), 12.0); + + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/CollectorTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/CollectorTest.java new file mode 100644 index 000000000..44038c0c8 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/CollectorTest.java @@ -0,0 +1,52 @@ +package org.jenkinsci.plugins.prometheus.collectors.testutils; + +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public abstract class CollectorTest { + + public void validateNames(Collector.MetricFamilySamples samples, String[] expectedNames) { + Assertions.assertArrayEquals(expectedNames, samples.getNames()); + } + + public void validateMetricFamilySampleSize(Collector.MetricFamilySamples samples, int expectedSize) { + Assertions.assertEquals(expectedSize, samples.samples.size()); + } + + public void validateValue(Collector.MetricFamilySamples.Sample sample, double expectedValue) { + Assertions.assertEquals(expectedValue, sample.value); + } + + public void validateMetricFamilySampleListSize(List collect, int expectedSize) { + Assertions.assertEquals(expectedSize, collect.size()); + } + + public void validateHelp(Collector.MetricFamilySamples samples, String expectedHelp) { + assertEquals(expectedHelp, samples.help); + } + + public void validateValue(Collector.MetricFamilySamples samples, int index, double expectedValue) { + assertEquals(expectedValue ,samples.samples.get(index).value, 0.0); + } + + protected String[] getLabelNames() { + return new String[]{"jenkins_job", "repo"}; + } + + protected String[] getLabelValues() { + return new String[]{"job1", "NA"}; + } + + protected String getNamespace() { + return "default"; + } + + protected String getSubSystem() { + return "jenkins"; + } + +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/MockedJenkinsTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/MockedJenkinsTest.java new file mode 100644 index 000000000..fc67e1bb1 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/MockedJenkinsTest.java @@ -0,0 +1,14 @@ +package org.jenkinsci.plugins.prometheus.collectors.testutils; + +import jenkins.model.Jenkins; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public abstract class MockedJenkinsTest extends CollectorTest { + + + @Mock + protected Jenkins mock; +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/MockedLoadStatisticSnapshotTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/MockedLoadStatisticSnapshotTest.java new file mode 100644 index 000000000..45c926837 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/MockedLoadStatisticSnapshotTest.java @@ -0,0 +1,14 @@ +package org.jenkinsci.plugins.prometheus.collectors.testutils; + +import hudson.model.LoadStatistics; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public abstract class MockedLoadStatisticSnapshotTest extends CollectorTest { + + + @Mock + protected LoadStatistics.LoadStatisticsSnapshot mock; +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/MockedRunCollectorTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/MockedRunCollectorTest.java new file mode 100644 index 000000000..eea6508d0 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/collectors/testutils/MockedRunCollectorTest.java @@ -0,0 +1,15 @@ +package org.jenkinsci.plugins.prometheus.collectors.testutils; + +import hudson.model.Run; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public abstract class MockedRunCollectorTest extends CollectorTest { + + @Mock + protected Run mock; + + +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/config/PrometheusConfigurationTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/config/PrometheusConfigurationTest.java index 275561c5c..931bbf2f0 100644 --- a/src/test/java/org/jenkinsci/plugins/prometheus/config/PrometheusConfigurationTest.java +++ b/src/test/java/org/jenkinsci/plugins/prometheus/config/PrometheusConfigurationTest.java @@ -1,63 +1,68 @@ package org.jenkinsci.plugins.prometheus.config; import hudson.model.Descriptor; -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; +import hudson.util.FormValidation; import net.sf.json.JSONObject; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.kohsuke.stapler.StaplerRequest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.kohsuke.stapler.StaplerRequest2; import org.mockito.Mockito; import java.util.Arrays; import java.util.List; import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; -@RunWith(JUnitParamsRunner.class) +@SuppressWarnings("rawtypes") public class PrometheusConfigurationTest { - private PrometheusConfiguration configuration; + private final PrometheusConfiguration configuration; - @Before - public void setup() { - configuration = Mockito.mock(PrometheusConfiguration.class); + public PrometheusConfigurationTest() { + this.configuration = Mockito.mock(PrometheusConfiguration.class); Mockito.doNothing().when((Descriptor) configuration).load(); } - private List wrongMetricCollectorPeriodsProvider() { + private static List wrongMetricCollectorPeriodsProvider() { return Arrays.asList("0", "-1", "test", null, "100L"); } - @Test - @Parameters(method = "wrongMetricCollectorPeriodsProvider") + @ParameterizedTest + @MethodSource("wrongMetricCollectorPeriodsProvider") public void shouldGetErrorWhenNotPositiveNumber(String metricCollectorPeriod) throws Descriptor.FormException { //given - Mockito.when(configuration.configure(any(), any())).thenCallRealMethod(); + Mockito.when(configuration.configure(any(StaplerRequest2.class), any())).thenCallRealMethod(); + Mockito.when(configuration.doCheckCollectingMetricsPeriodInSeconds(any())).thenCallRealMethod(); JSONObject config = getDefaultConfig(); config.accumulate("collectingMetricsPeriodInSeconds", metricCollectorPeriod); - // when - assertThatThrownBy(() -> configuration.configure(null, config)) - .isInstanceOf(Descriptor.FormException.class) - .hasMessageContaining("CollectingMetricsPeriodInSeconds must be a positive integer"); + + FormValidation formValidation = configuration.doCheckCollectingMetricsPeriodInSeconds(metricCollectorPeriod); + + + assertEquals(formValidation.kind, FormValidation.Kind.ERROR); + assertEquals(formValidation.getMessage(), "CollectingMetricsPeriodInSeconds must be a positive value"); } - private List correctMetricCollectorPeriodsProvider() { + private static List correctMetricCollectorPeriodsProvider() { return Arrays.asList("1", "100", "5.7", String.valueOf(Integer.MAX_VALUE)); } - @Test - @Parameters(method = "correctMetricCollectorPeriodsProvider") + @ParameterizedTest + @MethodSource("correctMetricCollectorPeriodsProvider") public void shouldReturnOk(String metricCollectorPeriod) throws Descriptor.FormException { //given - Mockito.when(configuration.configure(any(), any())).thenCallRealMethod(); + Mockito.when(configuration.configure(any(StaplerRequest2.class), any())).thenCallRealMethod(); + Mockito.when(configuration.doCheckCollectingMetricsPeriodInSeconds(any())).thenCallRealMethod(); JSONObject config = getDefaultConfig(); - StaplerRequest request = Mockito.mock(StaplerRequest.class); + StaplerRequest2 request = Mockito.mock(StaplerRequest2.class); Mockito.doNothing().when(request).bindJSON(any(Object.class), any(JSONObject.class)); config.accumulate("collectingMetricsPeriodInSeconds", metricCollectorPeriod); @@ -65,30 +70,30 @@ public void shouldReturnOk(String metricCollectorPeriod) throws Descriptor.FormE boolean actual = configuration.configure(request, config); // then - assertThat(actual).isTrue(); + assertTrue(actual); } @Test public void shouldSetDefaultValue() { // given - Mockito.doCallRealMethod().when(configuration).setCollectingMetricsPeriodInSeconds(any()); + Mockito.doCallRealMethod().when(configuration).setCollectingMetricsPeriodInSeconds(anyLong()); Mockito.when(configuration.getCollectingMetricsPeriodInSeconds()).thenCallRealMethod(); - Long metricCollectorPeriod = null; + long metricCollectorPeriod = -1L; // when configuration.setCollectingMetricsPeriodInSeconds(metricCollectorPeriod); long actual = configuration.getCollectingMetricsPeriodInSeconds(); // then - assertThat(actual).isEqualTo(PrometheusConfiguration.DEFAULT_COLLECTING_METRICS_PERIOD_IN_SECONDS); + assertEquals(actual, PrometheusConfiguration.DEFAULT_COLLECTING_METRICS_PERIOD_IN_SECONDS); } @Test - public void shouldSetValueFromEnvForCollectingMetricsPeriodInSeconds() throws Exception{ + public void shouldSetValueFromEnvForCollectingMetricsPeriodInSeconds() throws Exception { // given - Mockito.doCallRealMethod().when(configuration).setCollectingMetricsPeriodInSeconds(any()); + Mockito.doCallRealMethod().when(configuration).setCollectingMetricsPeriodInSeconds(anyLong()); Mockito.when(configuration.getCollectingMetricsPeriodInSeconds()).thenCallRealMethod(); - Long metricCollectorPeriod = null; + long metricCollectorPeriod = -1L; // when withEnvironmentVariable(PrometheusConfiguration.COLLECTING_METRICS_PERIOD_IN_SECONDS, "1000") @@ -96,40 +101,101 @@ public void shouldSetValueFromEnvForCollectingMetricsPeriodInSeconds() throws Ex long actual = configuration.getCollectingMetricsPeriodInSeconds(); // then - assertThat(actual).isEqualTo(1000); + assertEquals(actual, 1000); } - @Test - public void shouldSetValueFromEnvForCollectDiskUsage() throws Exception{ + @ParameterizedTest + @MethodSource("wrongMetricCollectorPeriodsProvider") + public void shouldSetDefaultValueWhenEnvCannotBeConvertedToLongOrNegativeValue(String wrongValue) throws Exception { // given - Mockito.doCallRealMethod().when(configuration).setCollectDiskUsage(any()); - Mockito.when(configuration.getCollectDiskUsage()).thenCallRealMethod(); - Boolean collectDiskUsage = null; + Mockito.doCallRealMethod().when(configuration).setCollectingMetricsPeriodInSeconds(anyLong()); + Mockito.when(configuration.getCollectingMetricsPeriodInSeconds()).thenCallRealMethod(); + long metricCollectorPeriod = -1L; // when - withEnvironmentVariable(String.valueOf(PrometheusConfiguration.DEFAULT_COLLECT_DISK_USAGE), String.valueOf(true)) - .execute(() -> configuration.setCollectDiskUsage(collectDiskUsage)); - boolean actual = configuration.getCollectDiskUsage(); + withEnvironmentVariable(PrometheusConfiguration.COLLECTING_METRICS_PERIOD_IN_SECONDS, wrongValue) + .execute(() -> configuration.setCollectingMetricsPeriodInSeconds(metricCollectorPeriod)); + long actual = configuration.getCollectingMetricsPeriodInSeconds(); // then - assertThat(actual).isEqualTo(true); + assertEquals(actual, PrometheusConfiguration.DEFAULT_COLLECTING_METRICS_PERIOD_IN_SECONDS); } @Test - @Parameters(method = "wrongMetricCollectorPeriodsProvider") - public void shouldSetDefaultValueWhenEnvCannotBeConvertedToLongORNegativeValue(String wrongValue) throws Exception { - // given - Mockito.doCallRealMethod().when(configuration).setCollectingMetricsPeriodInSeconds(any()); - Mockito.when(configuration.getCollectingMetricsPeriodInSeconds()).thenCallRealMethod(); - Long metricCollectorPeriod = null; + public void shouldTakeDefaultValueWhenNothingConfigured() { + Mockito.doCallRealMethod().when(configuration).setCollectDiskUsageBasedOnEnvironmentVariableIfDefined(); + Mockito.doCallRealMethod().when(configuration).getCollectDiskUsage(); - // when - withEnvironmentVariable(PrometheusConfiguration.COLLECTING_METRICS_PERIOD_IN_SECONDS, wrongValue) - .execute(() -> configuration.setCollectingMetricsPeriodInSeconds(metricCollectorPeriod)); - long actual = configuration.getCollectingMetricsPeriodInSeconds(); + // simulate constructor call + configuration.setCollectDiskUsageBasedOnEnvironmentVariableIfDefined(); - // then - assertThat(actual).isEqualTo(PrometheusConfiguration.DEFAULT_COLLECTING_METRICS_PERIOD_IN_SECONDS); + + assertFalse(configuration.getCollectDiskUsage()); + } + + @Test + public void shouldTakeEnvironmentVariableWhenNothingConfigured() throws Exception { + Mockito.doCallRealMethod().when(configuration).setCollectDiskUsageBasedOnEnvironmentVariableIfDefined(); + Mockito.doCallRealMethod().when(configuration).getCollectDiskUsage(); + + // simulate constructor call + withEnvironmentVariable(PrometheusConfiguration.COLLECT_DISK_USAGE, "false") + .execute(configuration::setCollectDiskUsageBasedOnEnvironmentVariableIfDefined); + + assertFalse(configuration.getCollectDiskUsage()); + } + + @Test + public void shouldTakeDefaultIfEnvironmentVariableIsFaulty() throws Exception { + Mockito.doCallRealMethod().when(configuration).setCollectDiskUsageBasedOnEnvironmentVariableIfDefined(); + Mockito.doCallRealMethod().when(configuration).getCollectDiskUsage(); + + // simulate constructor call + withEnvironmentVariable(PrometheusConfiguration.COLLECT_DISK_USAGE, "not_true_not_false") + .execute(configuration::setCollectDiskUsageBasedOnEnvironmentVariableIfDefined); + + assertFalse(configuration.getCollectDiskUsage()); + } + + @Test + public void shouldTakeConfiguredValueIfEnvironmentVariableIsFaulty() throws Exception { + Mockito.doCallRealMethod().when(configuration).setCollectDiskUsageBasedOnEnvironmentVariableIfDefined(); + Mockito.doCallRealMethod().when(configuration).getCollectDiskUsage(); + Mockito.doCallRealMethod().when(configuration).setCollectDiskUsage(anyBoolean()); + + withEnvironmentVariable(PrometheusConfiguration.COLLECT_DISK_USAGE, "not_true_not_false") + .execute(() -> { + + // simulate user clicked on checkbox + configuration.setCollectDiskUsage(true); + + // simulate constructor call + configuration.setCollectDiskUsageBasedOnEnvironmentVariableIfDefined(); + }); + + assertTrue(configuration.getCollectDiskUsage()); + } + + @Test + public void shouldTakeConfiguredValueIfItIsConfigured() { + Mockito.doCallRealMethod().when(configuration).setCollectDiskUsage(anyBoolean()); + Mockito.doCallRealMethod().when(configuration).getCollectDiskUsage(); + + // simulate someone configured it over the UI + configuration.setCollectDiskUsage(false); + assertFalse(configuration.getCollectDiskUsage()); + + // simulate constructor call + configuration.setCollectDiskUsageBasedOnEnvironmentVariableIfDefined(); + assertFalse(configuration.getCollectDiskUsage()); + + // simulate someone configured it over the UI + configuration.setCollectDiskUsage(true); + assertTrue(configuration.getCollectDiskUsage()); + + // simulate constructor call + configuration.setCollectDiskUsageBasedOnEnvironmentVariableIfDefined(); + assertTrue(configuration.getCollectDiskUsage()); } private JSONObject getDefaultConfig() { @@ -149,6 +215,8 @@ private JSONObject getDefaultConfig() { config.accumulate("appendStatusLabel", "false"); config.accumulate("labeledBuildParameterNames", ""); config.accumulate("collectDiskUsage", "true"); + config.accumulate("collectNodeStatus", "true"); + config.accumulate("perBuildMetrics", "false"); return config; } diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/FilteredMetricEnumerationTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/FilteredMetricEnumerationTest.java new file mode 100644 index 000000000..62bffde59 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/FilteredMetricEnumerationTest.java @@ -0,0 +1,74 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.Iterator; +import java.util.List; + +import static org.mockito.Mockito.mockStatic; + +public class FilteredMetricEnumerationTest { + + @Test + void testFilterMatches() { + try (MockedStatic statusCheckerMockedStatic = mockStatic(MetricStatusChecker.class)) { + + statusCheckerMockedStatic.when(() -> MetricStatusChecker.isEnabled("metric_1")).thenReturn(false); + statusCheckerMockedStatic.when(() -> MetricStatusChecker.isEnabled("metric_2")).thenReturn(true); + + List list = List.of( + new Collector.MetricFamilySamples("metric_1", Collector.Type.GAUGE, "help1", List.of()), + new Collector.MetricFamilySamples("metric_2", Collector.Type.GAUGE, "help2", List.of()) + ); + + Iterator iterator = list.iterator(); + + + FilteredMetricEnumeration filteredMetricEnumeration = new FilteredMetricEnumeration(iterator); + + int foundElements = 0; + String foundKey = ""; + while (filteredMetricEnumeration.hasMoreElements()) { + Collector.MetricFamilySamples familySamples = filteredMetricEnumeration.nextElement(); + foundKey = familySamples.name; + foundElements++; + } + Assertions.assertEquals(1, foundElements); + Assertions.assertEquals("metric_2", foundKey); + + } + } + + @Test + void testIterator() { + try (MockedStatic statusCheckerMockedStatic = mockStatic(MetricStatusChecker.class)) { + + statusCheckerMockedStatic.when(() -> MetricStatusChecker.isEnabled("metric_1")).thenReturn(false); + statusCheckerMockedStatic.when(() -> MetricStatusChecker.isEnabled("metric_2")).thenReturn(true); + + List list = List.of( + new Collector.MetricFamilySamples("metric_1", Collector.Type.GAUGE, "help1", List.of()), + new Collector.MetricFamilySamples("metric_2", Collector.Type.GAUGE, "help2", List.of()) + ); + + Iterator iterator = list.iterator(); + + + FilteredMetricEnumeration filteredMetricEnumeration = new FilteredMetricEnumeration(iterator); + + int foundElements = 0; + String foundKey = ""; + while (filteredMetricEnumeration.asIterator().hasNext()) { + Collector.MetricFamilySamples familySamples = filteredMetricEnumeration.nextElement(); + foundKey = familySamples.name; + foundElements++; + } + Assertions.assertEquals(1, foundElements); + Assertions.assertEquals("metric_2", foundKey); + + } + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/MetricStatusCheckerTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/MetricStatusCheckerTest.java new file mode 100644 index 000000000..23e144838 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/MetricStatusCheckerTest.java @@ -0,0 +1,239 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.mockito.MockedStatic; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +public class MetricStatusCheckerTest { + + @Test + void testNoConfigWontFail() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + String namespace = "TestNamespace"; + + when(mockedConfig.getDefaultNamespace()).thenReturn(namespace); + when(mockedConfig.isCollectNodeStatus()).thenReturn(false); + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(null); + + boolean enabled = MetricStatusChecker.isEnabled("some_metric"); + Assertions.assertTrue(enabled); + } + } + + @Test + void testNoDisabledMetricConfigWontFail() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + when(mockedConfig.getDisabledMetricConfig()).thenReturn(null); + + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + boolean enabled = MetricStatusChecker.isEnabled("some_metric"); + Assertions.assertTrue(enabled); + } + } + + @Test + void testNoEntriesWontFail() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + when(mockedConfig.getDisabledMetricConfig()).thenReturn(new DisabledMetricConfig(new ArrayList<>())); + + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + boolean enabled = MetricStatusChecker.isEnabled("some_metric"); + Assertions.assertTrue(enabled); + } + } + + @Test + void testRegexMatches() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + List entries = new ArrayList<>(); + entries.add(new RegexDisabledMetric("some.*")); + DisabledMetricConfig disabledMetricConfig = new DisabledMetricConfig(entries); + + when(mockedConfig.getDisabledMetricConfig()).thenReturn(disabledMetricConfig); + + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + boolean enabled = MetricStatusChecker.isEnabled("some_metric"); + Assertions.assertFalse(enabled); + } + } + + @Test + void testWrongRegexWontMatch() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + List entries = new ArrayList<>(); + entries.add(new RegexDisabledMetric("some*")); + DisabledMetricConfig disabledMetricConfig = new DisabledMetricConfig(entries); + + when(mockedConfig.getDisabledMetricConfig()).thenReturn(disabledMetricConfig); + + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + boolean enabled = MetricStatusChecker.isEnabled("some_metric"); + Assertions.assertTrue(enabled); + } + } + + @Test + void testNamedDisabledMetricMatch() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + List entries = new ArrayList<>(); + entries.add(new NamedDisabledMetric("some_metric")); + DisabledMetricConfig disabledMetricConfig = new DisabledMetricConfig(entries); + + when(mockedConfig.getDisabledMetricConfig()).thenReturn(disabledMetricConfig); + + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + boolean enabled = MetricStatusChecker.isEnabled("some_metric"); + Assertions.assertFalse(enabled); + } + } + + @Test + void testNamedDisabledMetricIgnoresCaseAndMatches() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + List entries = new ArrayList<>(); + entries.add(new NamedDisabledMetric("somE_mEtrIc")); + DisabledMetricConfig disabledMetricConfig = new DisabledMetricConfig(entries); + + when(mockedConfig.getDisabledMetricConfig()).thenReturn(disabledMetricConfig); + + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + boolean enabled = MetricStatusChecker.isEnabled("some_metric"); + Assertions.assertFalse(enabled); + } + } + + @Test + void testNamedDisabledMetricAndRegexAvailableMatches() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + List entries = new ArrayList<>(); + entries.add(new NamedDisabledMetric("other_metric")); + entries.add(new RegexDisabledMetric("some.*")); + DisabledMetricConfig disabledMetricConfig = new DisabledMetricConfig(entries); + + when(mockedConfig.getDisabledMetricConfig()).thenReturn(disabledMetricConfig); + + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + boolean enabled = MetricStatusChecker.isEnabled("some_metric"); + Assertions.assertFalse(enabled); + } + } + + + @Test + void testFilterExternalMetricNames() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + List entries = new ArrayList<>(); + entries.add(new NamedDisabledMetric("other_metric")); + entries.add(new RegexDisabledMetric("j?vm.*")); + DisabledMetricConfig disabledMetricConfig = new DisabledMetricConfig(entries); + + when(mockedConfig.getDisabledMetricConfig()).thenReturn(disabledMetricConfig); + + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + List allMetrics = List.of("some_metric", "jvm_xxx", "vm_xxx"); + Set filteredMetrics = MetricStatusChecker.filter(allMetrics); + Assertions.assertEquals(1, filteredMetrics.size()); + Assertions.assertEquals("some_metric", filteredMetrics.stream().findFirst().get()); + } + } + + @Test + // shouldn't take more than 3 seconds + @Timeout(value = 3L) + void test10000RegexDisabledMetricConfiguredLoadTest() { + Jenkins jenkins = mock(Jenkins.class); + + PrometheusConfiguration mockedConfig = mock(PrometheusConfiguration.class); + List entries = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + entries.add(new RegexDisabledMetric("some" + i + ".*")); + } + // the last one matches. To see how much time it takes + entries.add(new RegexDisabledMetric("some.*")); + DisabledMetricConfig disabledMetricConfig = new DisabledMetricConfig(entries); + + when(mockedConfig.getDisabledMetricConfig()).thenReturn(disabledMetricConfig); + + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class); + MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + configStatic.when(PrometheusConfiguration::get).thenReturn(mockedConfig); + + boolean enabled = MetricStatusChecker.isEnabled("some_metric"); + Assertions.assertFalse(enabled); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/NamedDisabledMetricTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/NamedDisabledMetricTest.java new file mode 100644 index 000000000..44001a03d --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/NamedDisabledMetricTest.java @@ -0,0 +1,15 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import hudson.model.Descriptor; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class NamedDisabledMetricTest { + + @Test + void testDescriptorName() { + NamedDisabledMetric sut = new NamedDisabledMetric("some_metric"); + Descriptor descriptor = sut.getDescriptor(); + Assertions.assertEquals("Fully qualified Name Entry", descriptor.getDisplayName()); + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/RegexDisabledMetricTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/RegexDisabledMetricTest.java new file mode 100644 index 000000000..e20766402 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/config/disabledmetrics/RegexDisabledMetricTest.java @@ -0,0 +1,15 @@ +package org.jenkinsci.plugins.prometheus.config.disabledmetrics; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class RegexDisabledMetricTest { + + @Test + void testDescriptorName() { + RegexDisabledMetric sut = new RegexDisabledMetric("some_regex"); + Assertions.assertEquals("Regex Entry", sut.getDescriptor().getDisplayName()); + } + + +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java index 144ba9474..70888271e 100644 --- a/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java +++ b/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java @@ -1,128 +1,134 @@ package org.jenkinsci.plugins.prometheus.rest; -import static java.net.HttpURLConnection.HTTP_FORBIDDEN; -import static java.net.HttpURLConnection.HTTP_NOT_FOUND; -import static java.net.HttpURLConnection.HTTP_OK; -import static org.junit.Assert.assertEquals; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; - -import javax.servlet.ServletException; - +import io.prometheus.client.exporter.common.TextFormat; +import jenkins.metrics.api.Metrics; +import jenkins.model.Jenkins; import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; -import org.jenkinsci.plugins.prometheus.service.PrometheusMetrics; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.jenkinsci.plugins.prometheus.service.DefaultPrometheusMetrics; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.StaplerRequest2; +import org.kohsuke.stapler.StaplerResponse2; import org.mockito.Mock; -import org.mockito.Mockito; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; -import io.prometheus.client.exporter.common.TextFormat; -import jenkins.metrics.api.Metrics; -import jenkins.model.Jenkins; +import jakarta.servlet.ServletException; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_OK; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -@RunWith(PowerMockRunner.class) -@PrepareForTest({Jenkins.class}) -// PowerMockIgnore needed for: https://github.com/powermock/powermock/issues/864 -@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.*", "com.sun.org.apache.xalan.*"}) +@ExtendWith(MockitoExtension.class) public class PrometheusActionTest { @Mock private Jenkins jenkins; - @Mock private PrometheusConfiguration configuration; - @Before + private MockedStatic jenkinsStatic; + + @BeforeEach public void setUp() { - PowerMockito.mockStatic(Jenkins.class); - PowerMockito.when(Jenkins.getInstance()).thenReturn(jenkins); - PowerMockito.when(jenkins.getDescriptor(PrometheusConfiguration.class)).thenReturn(configuration); - PowerMockito.when(configuration.getAdditionalPath()).thenReturn("prometheus"); + jenkinsStatic = mockStatic(Jenkins.class); + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + when(jenkins.getDescriptor(PrometheusConfiguration.class)).thenReturn(configuration); + when(configuration.getAdditionalPath()).thenReturn("prometheus"); + } + + @AfterEach + public void tearDown() { + jenkinsStatic.close(); } @Test - public void shouldThrowExceptionWhenDoesNotMatchPath() throws IOException, ServletException { + public void shouldThrowExceptionWhenDoesNotMatchPath() throws IOException, ServletException, jakarta.servlet.ServletException { // given PrometheusAction action = new PrometheusAction(); - StaplerRequest request = Mockito.mock(StaplerRequest.class); + StaplerRequest2 request = mock(StaplerRequest2.class); String url = ""; - Mockito.when(request.getRestOfPath()).thenReturn(url); + when(request.getRestOfPath()).thenReturn(url); // when HttpResponse actual = action.doDynamic(request); // then AssertStaplerResponse.from(actual) - .call() - .assertHttpStatus(HTTP_NOT_FOUND); + .call() + .assertHttpStatus(HTTP_NOT_FOUND); } @Test - public void shouldThrowExceptionWhenAuthenticationEnabledAndInsufficientPermission() throws IOException, ServletException { + public void shouldThrowExceptionWhenAuthenticationEnabledAndInsufficientPermission() throws IOException, ServletException, jakarta.servlet.ServletException { // given PrometheusAction action = new PrometheusAction(); - StaplerRequest request = Mockito.mock(StaplerRequest.class); + StaplerRequest2 request = mock(StaplerRequest2.class); String url = "prometheus"; - Mockito.when(request.getRestOfPath()).thenReturn(url); - Mockito.when(configuration.isUseAuthenticatedEndpoint()).thenReturn(true); - Mockito.when(jenkins.hasPermission(Metrics.VIEW)).thenReturn(false); + when(request.getRestOfPath()).thenReturn(url); + when(configuration.isUseAuthenticatedEndpoint()).thenReturn(true); + when(jenkins.hasPermission(Metrics.VIEW)).thenReturn(false); // when HttpResponse actual = action.doDynamic(request); // then AssertStaplerResponse.from(actual) - .call() - .assertHttpStatus(HTTP_FORBIDDEN); + .call() + .assertHttpStatus(HTTP_FORBIDDEN); } @Test - public void shouldReturnMetrics() throws IOException, ServletException { + public void shouldReturnMetrics() throws IOException, jakarta.servlet.ServletException { // given - PrometheusAction action = new PrometheusAction(); - PrometheusMetrics prometheusMetrics = Mockito.mock(PrometheusMetrics.class); + DefaultPrometheusMetrics prometheusMetrics = mock(DefaultPrometheusMetrics.class); String responseBody = "testMetric"; - Mockito.when(prometheusMetrics.getMetrics()).thenReturn(responseBody); - action.setPrometheusMetrics(prometheusMetrics); - StaplerRequest request = Mockito.mock(StaplerRequest.class); - String url = "prometheus"; - Mockito.when(request.getRestOfPath()).thenReturn(url); - - // when - HttpResponse actual = action.doDynamic(request); - - // then - AssertStaplerResponse.from(actual) + when(prometheusMetrics.getMetrics()).thenReturn(responseBody); + try (MockedStatic defaultPrometheusMetricsMockedStatic = mockStatic(DefaultPrometheusMetrics.class)) { + defaultPrometheusMetricsMockedStatic.when(DefaultPrometheusMetrics::get).thenReturn(prometheusMetrics); + PrometheusAction action = new PrometheusAction(); + StaplerRequest2 request = mock(StaplerRequest2.class); + String url = "prometheus"; + when(request.getRestOfPath()).thenReturn(url); + + // when + HttpResponse actual = action.doDynamic(request); + + // then + AssertStaplerResponse.from(actual) .call() .assertHttpStatus(HTTP_OK) .assertContentType(TextFormat.CONTENT_TYPE_004) .assertHttpHeader("Cache-Control", "must-revalidate,no-cache,no-store") .assertBody(responseBody); + } } - private static class AssertStaplerResponse { - private final StaplerResponse response; + private final StaplerResponse2 response; private final HttpResponse httpResponse; private final StringWriter stringWriter; + private AssertStaplerResponse(HttpResponse httpResponse) throws IOException { this.httpResponse = httpResponse; - this.response = Mockito.mock(StaplerResponse.class); + this.response = mock(StaplerResponse2.class); stringWriter = new StringWriter(); PrintWriter writer = new PrintWriter(stringWriter); - Mockito.when(response.getWriter()).thenReturn(writer); + + lenient().when(response.getWriter()).thenReturn(writer); } static AssertStaplerResponse from(HttpResponse actual) throws IOException { @@ -130,26 +136,26 @@ static AssertStaplerResponse from(HttpResponse actual) throws IOException { } private AssertStaplerResponse assertHttpStatus(int status) { - Mockito.verify(response).setStatus(status); + verify(response).setStatus(status); return this; } private AssertStaplerResponse assertContentType(String contentType) { - Mockito.verify(response).setContentType(contentType); + verify(response).setContentType(contentType); return this; } private AssertStaplerResponse assertHttpHeader(String name, String value) { - Mockito.verify(response).addHeader(name, value); + verify(response).addHeader(name, value); return this; } private AssertStaplerResponse assertBody(String payload) { - assertEquals(payload, stringWriter.toString()); + Assertions.assertEquals(stringWriter.toString(), payload); return this; } - private AssertStaplerResponse call() throws IOException, ServletException { + private AssertStaplerResponse call() throws IOException, jakarta.servlet.ServletException { httpResponse.generateResponse(null, response, null); return this; } diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java index 0ca930be4..9fe678f9d 100644 --- a/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java +++ b/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java @@ -1,43 +1,59 @@ package org.jenkinsci.plugins.prometheus.service; -import org.junit.Test; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Test; +import org.jvnet.hudson.test.Issue; +import org.mockito.MockedStatic; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -import static org.assertj.core.api.Assertions.assertThat; public class PrometheusAsyncWorkerTest { @Test public void shouldCollectMetrics() { - // given - PrometheusAsyncWorker asyncWorker = new PrometheusAsyncWorker(); - PrometheusMetrics metrics = new TestPrometheusMetrics(); - asyncWorker.setPrometheusMetrics(metrics); - - // when - asyncWorker.execute(null); - - // then - String actual = metrics.getMetrics(); - assertThat(actual).isEqualTo("1"); - } - - private static class TestPrometheusMetrics implements PrometheusMetrics { - private final AtomicReference cachedMetrics = new AtomicReference<>(""); - private final AtomicInteger counter = new AtomicInteger(0); - - @Override - public String getMetrics() { - return cachedMetrics.get(); + try (MockedStatic defaultPrometheusMetricsMockedStatic = mockStatic(DefaultPrometheusMetrics.class)) { + // given + DefaultPrometheusMetrics metrics = spy(DefaultPrometheusMetrics.class); + doNothing().when(metrics).collectMetrics(); + defaultPrometheusMetricsMockedStatic.when(DefaultPrometheusMetrics::get).thenReturn(metrics); + PrometheusAsyncWorker asyncWorker = new PrometheusAsyncWorker(); + + // when + asyncWorker.execute(null); + + // then + verify(metrics, times(1)).collectMetrics(); } - @Override - public void collectMetrics() { - String metrics = String.valueOf(counter.incrementAndGet()); - cachedMetrics.set(metrics); + } + @Test + public void testConvertSecondsToMillis() { + try (MockedStatic configurationStatic = mockStatic(PrometheusConfiguration.class)) { + + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + configurationStatic.when(PrometheusConfiguration::get).thenReturn(config); + when(config.getCollectingMetricsPeriodInSeconds()).thenReturn(12345L); + PrometheusAsyncWorker sut = new PrometheusAsyncWorker(); + long recurrencePeriod = sut.getRecurrencePeriod(); + assertEquals(12345000L, recurrencePeriod); } + } + @Test + @Issue("#157") + public void ensureLoggingLevel() { + PrometheusAsyncWorker sut = new PrometheusAsyncWorker(); + Level level = sut.getNormalLoggingLevel(); + assertEquals(Level.FINE, level); } } diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/util/ConfigurationUtilsTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/util/ConfigurationUtilsTest.java new file mode 100644 index 000000000..5a37121fa --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/util/ConfigurationUtilsTest.java @@ -0,0 +1,46 @@ +package org.jenkinsci.plugins.prometheus.util; + +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.MockedStatic; + +import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + + +public class ConfigurationUtilsTest { + + @Test + public void verifyGetNamespaceWhenEnvIsNonEmpty() throws Exception { + String namespace = "foobar"; + withEnvironmentVariable("PROMETHEUS_NAMESPACE", namespace).execute(() -> { + String result = ConfigurationUtils.getNamespace(); + assertEquals(namespace, result); + }); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void verifyGetNamespaceWhenEnvIsNotSetOrEmpty(boolean notSetOrEmpty) throws Exception { + PrometheusConfiguration config = mock(PrometheusConfiguration.class); + String namespace = "default-namespace"; + when(config.getDefaultNamespace()).thenReturn(namespace); + try (MockedStatic configStatic = mockStatic(PrometheusConfiguration.class)) { + configStatic.when(PrometheusConfiguration::get).thenReturn(config); + withEnvironmentVariable("PROMETHEUS_NAMESPACE", notSetOrEmpty ? null : "").execute(() -> { + String result = ConfigurationUtils.getNamespace(); + assertEquals(namespace, result); + }); + } + } + + @Test + public void verifyGetSubSystem() { + assertEquals("jenkins", ConfigurationUtils.getSubSystem()); + } +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/util/FlowNodesTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/util/FlowNodesTest.java new file mode 100644 index 000000000..83b51d232 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/util/FlowNodesTest.java @@ -0,0 +1,28 @@ +package org.jenkinsci.plugins.prometheus.util; + +import com.cloudbees.workflow.rest.external.StageNodeExt; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class FlowNodesTest { + + @Mock + private WorkflowRun run; + + @Test + void returnEmptyListOnPassingNull() { + when(run.getExecution()).thenReturn(null); + List result = FlowNodes.getSortedStageNodes(run); + assertEquals(0, result.size()); + } + +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/util/JenkinsNodeBuildsSampleBuilderTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/util/JenkinsNodeBuildsSampleBuilderTest.java new file mode 100644 index 000000000..c185d1757 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/util/JenkinsNodeBuildsSampleBuilderTest.java @@ -0,0 +1,87 @@ +package org.jenkinsci.plugins.prometheus.util; + +import io.prometheus.client.Collector; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class JenkinsNodeBuildsSampleBuilderTest { + @Test + public void masterNodeCountFormat() { + assertEquals( + new Collector.MetricFamilySamples.Sample( + "jenkins_node_builds_count", + Arrays.asList("node", "quantile"), + Arrays.asList("master", "0.5"), + 0.091670452 + ), + new JenkinsNodeBuildsSampleBuilder().createSample( + "jenkins.node.builds", + "_count", + List.of("quantile"), + List.of("0.5"), + 0.091670452 + ) + ); + } + + @Test + public void masterNodeHistogramFormat() { + assertEquals( + new Collector.MetricFamilySamples.Sample( + "jenkins_node_builds", + Arrays.asList("node", "quantile"), + Arrays.asList("master", "0.999"), + 0.091670452 + ), + new JenkinsNodeBuildsSampleBuilder().createSample( + "jenkins.node.builds", + "", + List.of("quantile"), + List.of("0.999"), + 0.091670452 + ) + ); + } + + @Test + public void namedNodeCountFormat() { + assertEquals( + new Collector.MetricFamilySamples.Sample( + "jenkins_node_builds_count", + Arrays.asList("node", "quantile"), + Arrays.asList("evil node_name.com", "0.5"), + 0.091670452 + ), + new JenkinsNodeBuildsSampleBuilder().createSample( + "jenkins.node.evil node_name.com.builds", + "_count", + List.of("quantile"), + List.of("0.5"), + 0.091670452 + ) + ); + } + + @Test + public void named_node_histogram_format() { + assertEquals( + new Collector.MetricFamilySamples.Sample( + "jenkins_node_builds", + Arrays.asList("node", "quantile"), + Arrays.asList("evil node_name.com", "0.999"), + 0.091670452 + ), + new JenkinsNodeBuildsSampleBuilder().createSample( + "jenkins.node.evil node_name.com.builds", + "", + List.of("quantile"), + List.of("0.999"), + 0.091670452 + ) + ); + } +} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/util/JobsTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/util/JobsTest.java new file mode 100644 index 000000000..1a025d9b8 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/util/JobsTest.java @@ -0,0 +1,43 @@ +package org.jenkinsci.plugins.prometheus.util; + +import hudson.model.Job; +import jenkins.model.Jenkins; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@SuppressWarnings("rawtypes") +public class JobsTest { + + @Test + void testEachJob() { + try (MockedStatic jenkinsStatic = mockStatic(Jenkins.class)) { + Jenkins jenkins = mock(Jenkins.class); + List jobs = List.of(mockJob("name1"), mockJob("name2")); + when(jenkins.getAllItems(Job.class)).thenReturn(jobs); + jenkinsStatic.when(Jenkins::get).thenReturn(jenkins); + + + List names = new ArrayList<>(); + Jobs.forEachJob(job -> names.add(job.getName())); + + Assertions.assertEquals(2, names.size()); + Assertions.assertTrue(names.contains("name1")); + Assertions.assertTrue(names.contains("name2")); + } + } + + + private static Job mockJob(String name) { + Job mock = mock(Job.class); + when(mock.getName()).thenReturn(name); + return mock; + } +} \ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/util/MetricsFormatterTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/util/MetricsFormatterTest.java deleted file mode 100644 index 62f8639aa..000000000 --- a/src/test/java/org/jenkinsci/plugins/prometheus/util/MetricsFormatterTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.jenkinsci.plugins.prometheus.util; - -import org.junit.Assert; -import org.junit.Test; - -public class MetricsFormatterTest { - - private final String inString = "jenkins_node_builds{quantile=\"0.5\",} 0.091670452\n" + - "jenkins_node_builds{quantile=\"0.75\",} 0.091670452\n" + - "jenkins_node_builds{quantile=\"0.95\",} 0.091670452\n" + - "jenkins_node_builds{quantile=\"0.98\",} 0.091670452\n" + - "jenkins_node_builds{quantile=\"0.99\",} 0.091670452\n" + - "jenkins_node_builds{quantile=\"0.999\",} 0.091670452\n" + - "jenkins_node_my_node_1_builds{quantile=\"0.99\",} 0.091670452\n" + - "jenkins_node_my_node_4_builds{quantile=\"0.999\",} 0.091670452\n" + - "jenkins_node_builds_count 28458.0\n" + - "jenkins_node_my_node_1_builds_count 12345"; - - @Test - public void master_node_count_format() { - String formatString = MetricsFormatter.formatMetrics(inString); - //master node count - Assert.assertFalse(formatString.contains("jenkins_node_builds_count 28458.0\n")); - Assert.assertTrue(formatString.contains("jenkins_node_builds_count{node=\"master\"} 28458.0\n")); - } - - @Test - public void master_node_histogram_format() { - String formatString = MetricsFormatter.formatMetrics(inString); - //master node histogram - Assert.assertFalse(formatString.contains("jenkins_node_builds{quantile=\"0.999\",} 0.091670452\n")); - Assert.assertTrue(formatString.contains("jenkins_node_builds{node=\"master\",quantile=\"0.999\",} 0.091670452\n")); - } - - @Test - public void named_node_count_format() { - String formatString = MetricsFormatter.formatMetrics(inString); - //named node count - Assert.assertFalse(formatString.contains("jenkins_node_my_node_1_builds_count")); - Assert.assertTrue(formatString.contains("jenkins_node_builds_count{node=\"my_node_1\"} 12345")); - } - - @Test - public void named_node_histogram_format() { - String formatString = MetricsFormatter.formatMetrics(inString); - //named node histogram - Assert.assertFalse(formatString.contains("jenkins_node_my_node_4_builds{quantile=\"0.999\",} 0.091670452\n")); - Assert.assertTrue(formatString.contains("jenkins_node_builds{node=\"my_node_4\",quantile=\"0.999\",} 0.091670452\n")); - } -} diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/util/RunsTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/util/RunsTest.java new file mode 100644 index 000000000..aedb09936 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/prometheus/util/RunsTest.java @@ -0,0 +1,118 @@ +package org.jenkinsci.plugins.prometheus.util; + +import hudson.model.ParameterValue; +import hudson.model.ParametersAction; +import hudson.model.Result; +import hudson.model.Run; +import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class RunsTest { + + @Mock + Run mockedRun; + + @Test + void testIncludeBuildMetricsReturnsFalseIfRunIsBuilding() { + when(mockedRun.isBuilding()).thenReturn(true); + + boolean include = Runs.includeBuildInMetrics(mockedRun); + assertFalse(include); + } + + @Test + void testGetBuildParametersWontFailIfNoActionsAvailable() { + when(mockedRun.getActions(ParametersAction.class)).thenReturn(List.of()); + + Map parameters = Runs.getBuildParameters(mockedRun); + assertEquals(0, parameters.size()); + } + + @Test + void testGetBuildParametersWontFailIfParameterValueIsNull() { + ParameterValue parameterValue = mock(ParameterValue.class); + when(parameterValue.getName()).thenReturn("failBuildOnError"); + when(parameterValue.getValue()).thenReturn(true); + ParametersAction action = new ParametersAction(parameterValue); + when(mockedRun.getActions(ParametersAction.class)).thenReturn(List.of(action)); + + Map parameters = Runs.getBuildParameters(mockedRun); + assertEquals(1, parameters.size()); + + + assertEquals(true, parameters.get("failBuildOnError")); + } + + @ParameterizedTest + @MethodSource("provideBuildResults") + void testIncludeBuildMetrics(Result result) { + when(mockedRun.isBuilding()).thenReturn(false); + when(mockedRun.getResult()).thenReturn(result); + try (MockedStatic prometheusConfigurationStatic = mockStatic(PrometheusConfiguration.class)) { + + + PrometheusConfiguration configuration = getPrometheusConfigurationForTest(result, true); + prometheusConfigurationStatic.when(PrometheusConfiguration::get).thenReturn(configuration); + + boolean include = Runs.includeBuildInMetrics(mockedRun); + assertTrue(include, "Run is aborted and Prometheus is configured to return results for these builds"); + + configuration = getPrometheusConfigurationForTest(result, false); + prometheusConfigurationStatic.when(PrometheusConfiguration::get).thenReturn(configuration); + + include = Runs.includeBuildInMetrics(mockedRun); + assertFalse(include, "Run is aborted and Prometheus is not configured to return results for these builds"); + } + } + + + + private static Stream provideBuildResults() { + return Stream.of( + Arguments.of(Result.ABORTED), + Arguments.of(Result.FAILURE), + Arguments.of(Result.NOT_BUILT), + Arguments.of(Result.SUCCESS), + Arguments.of(Result.UNSTABLE) + ); + } + + private PrometheusConfiguration getPrometheusConfigurationForTest(Result result, boolean prometheusPluginConfiguredToReturn) { + PrometheusConfiguration mockedPrometheusConfiguration = mock(PrometheusConfiguration.class); + if (Result.ABORTED.equals(result)) { + when(mockedPrometheusConfiguration.isCountAbortedBuilds()).thenReturn(prometheusPluginConfiguredToReturn); + } + if (Result.FAILURE.equals(result)) { + when(mockedPrometheusConfiguration.isCountFailedBuilds()).thenReturn(prometheusPluginConfiguredToReturn); + } + if (Result.NOT_BUILT.equals(result)) { + when(mockedPrometheusConfiguration.isCountNotBuiltBuilds()).thenReturn(prometheusPluginConfiguredToReturn); + } + if (Result.SUCCESS.equals(result)) { + when(mockedPrometheusConfiguration.isCountSuccessfulBuilds()).thenReturn(prometheusPluginConfiguredToReturn); + } + if (Result.UNSTABLE.equals(result)) { + when(mockedPrometheusConfiguration.isCountUnstableBuilds()).thenReturn(prometheusPluginConfiguredToReturn); + } + return mockedPrometheusConfiguration; + } +} \ No newline at end of file