├── .travis.yml ├── src ├── test │ ├── resources │ │ ├── mockito-extensions │ │ │ └── org.mockito.plugins.MockMaker │ │ └── jenkinsci │ │ │ └── plugins │ │ │ └── influxdb │ │ │ ├── generators │ │ │ └── sonarqube │ │ │ │ ├── report-task.txt │ │ │ │ └── build-log.txt │ │ │ ├── ConfigurationAsCodeTest │ │ │ └── configuration-as-code.yml │ │ │ └── IntegrationBaseTest │ │ │ └── configuration-as-code.yml │ └── java │ │ └── jenkinsci │ │ └── plugins │ │ └── influxdb │ │ ├── generators │ │ ├── serenity │ │ │ ├── SerenityCannedJsonSummaryFile.java │ │ │ ├── SerenityJsonSummaryFileTest.java │ │ │ └── SerenityPointGeneratorTest.java │ │ ├── MetricsPointGeneratorTest.java │ │ ├── CustomDataPointGeneratorTest.java │ │ ├── GitPointGeneratorTest.java │ │ ├── CustomDataMapPointGeneratorTest.java │ │ ├── PerfPublisherPointGeneratorTest.java │ │ ├── AgentPointGeneratorTest.java │ │ ├── PointGeneratorBaseTest.java │ │ ├── JUnitPointGeneratorTest.java │ │ └── ChangeLogGeneratorTest.java │ │ ├── renderer │ │ └── ProjectNameRendererTest.java │ │ ├── InfluxDbPublisherTest.java │ │ ├── ConfigurationAsCodeTest.java │ │ └── IntegrationTest.java └── main │ ├── resources │ ├── jenkinsci │ │ └── plugins │ │ │ └── influxdb │ │ │ ├── models │ │ │ └── Target │ │ │ │ ├── help-url.html │ │ │ │ ├── help-database.html │ │ │ │ ├── help-globalListenerFilter.html │ │ │ │ ├── help-usingJenkinsProxy.html │ │ │ │ ├── help-description.html │ │ │ │ ├── help-globalListener.html │ │ │ │ ├── help-organization.html │ │ │ │ ├── help-jobScheduledTimeAsPointsTimestamp.html │ │ │ │ ├── help-exposeExceptions.html │ │ │ │ ├── help-retentionPolicy.html │ │ │ │ └── config.jelly │ │ │ ├── InfluxDbStep │ │ │ ├── help-selectedTarget.html │ │ │ ├── help-customPrefix.html │ │ │ ├── help-customProjectName.html │ │ │ ├── help-jenkinsEnvParameterTag.html │ │ │ ├── help-jenkinsEnvParameterField.html │ │ │ └── config.jelly │ │ │ ├── InfluxDbPublisher │ │ │ ├── help-selectedTarget.html │ │ │ ├── help-customPrefix.html │ │ │ ├── help-customProjectName.html │ │ │ ├── help-jenkinsEnvParameterTag.html │ │ │ ├── help-jenkinsEnvParameterField.html │ │ │ └── config.jelly │ │ │ └── InfluxDbGlobalConfig │ │ │ └── config.jelly │ └── index.jelly │ ├── java │ └── jenkinsci │ │ └── plugins │ │ └── influxdb │ │ ├── renderer │ │ ├── MeasurementRenderer.java │ │ └── ProjectNameRenderer.java │ │ ├── generators │ │ ├── serenity │ │ │ ├── ISerenityJsonSummaryFile.java │ │ │ ├── SerenityJsonSummaryFile.java │ │ │ └── SerenityPointGenerator.java │ │ ├── TimeGenerator.java │ │ ├── PointGenerator.java │ │ ├── CustomDataMapPointGenerator.java │ │ ├── CustomDataPointGenerator.java │ │ ├── JacocoPointGenerator.java │ │ ├── MetricsPointGenerator.java │ │ ├── GitPointGenerator.java │ │ ├── CoveragePointGenerator.java │ │ ├── CoberturaPointGenerator.java │ │ ├── PerformancePointGenerator.java │ │ ├── JUnitPointGenerator.java │ │ ├── ChangeLogPointGenerator.java │ │ ├── AbstractPointGenerator.java │ │ ├── AgentPointGenerator.java │ │ ├── PerfPublisherPointGenerator.java │ │ └── RobotFrameworkPointGenerator.java │ │ ├── InfluxReportException.java │ │ ├── InfluxDbStepExecution.java │ │ ├── InfluxDbGlobalConfig.java │ │ ├── models │ │ └── AbstractPoint.java │ │ ├── global │ │ └── GlobalRunListener.java │ │ ├── InfluxDbStep.java │ │ └── InfluxDbPublisher.java │ └── main.iml ├── doc ├── img │ ├── advanced-options.png │ ├── post-build-action.png │ ├── jenkins-configuration.png │ └── select-influxdb-target.png ├── breaking_changes.md └── SonarQube_integration.md ├── .gitignore ├── .github ├── release-drafter.yml └── workflows │ ├── release-drafter.yml │ ├── jenkins-security-scan.yml │ └── codeql-analysis.yml ├── Jenkinsfile └── LICENSE /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-url.html: -------------------------------------------------------------------------------- 1 | URL of the InfluxDB. 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-database.html: -------------------------------------------------------------------------------- 1 | Name of the database. 2 | -------------------------------------------------------------------------------- /doc/img/advanced-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/influxdb-plugin/HEAD/doc/img/advanced-options.png -------------------------------------------------------------------------------- /doc/img/post-build-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/influxdb-plugin/HEAD/doc/img/post-build-action.png -------------------------------------------------------------------------------- /doc/img/jenkins-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/influxdb-plugin/HEAD/doc/img/jenkins-configuration.png -------------------------------------------------------------------------------- /doc/img/select-influxdb-target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/influxdb-plugin/HEAD/doc/img/select-influxdb-target.png -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-globalListenerFilter.html: -------------------------------------------------------------------------------- 1 | Regular expression to match only certain jobs. 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-usingJenkinsProxy.html: -------------------------------------------------------------------------------- 1 | Use the global Jenkins proxy configuration when connecting to this server. 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbStep/help-selectedTarget.html: -------------------------------------------------------------------------------- 1 | InfluxDB target to send data to. Targets have to be configured in the global Jenkins settings. 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbPublisher/help-selectedTarget.html: -------------------------------------------------------------------------------- 1 | InfluxDB target to send data to. Targets have to be configured in the global Jenkins settings. 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-description.html: -------------------------------------------------------------------------------- 1 | Description for the InfluxDB target. 2 |

This is used in a job's configuration to select which InfluxDB target to use.

3 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/renderer/MeasurementRenderer.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.renderer; 2 | 3 | public interface MeasurementRenderer { 4 | 5 | String render(T input); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | This plugin allows sending build results to InfluxDB. It was inspired by https://github.com/jrajala-eficode/jenkins-ci.influxdb-plugin 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbStep/help-customPrefix.html: -------------------------------------------------------------------------------- 1 | A custom prefix that gets prepended to the job name, for example to prevent several 'master' metrics for different jobs in multi branch pipeline jobs. 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbPublisher/help-customPrefix.html: -------------------------------------------------------------------------------- 1 | A custom prefix that gets prepended to the job name, for example to prevent several 'master' metrics for different jobs in multi branch pipeline jobs. 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-globalListener.html: -------------------------------------------------------------------------------- 1 | Whether to globally enable publishing build results to this target. 2 |

Even if enabled, it is still possible to configure an additional target per job.

3 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-organization.html: -------------------------------------------------------------------------------- 1 | Leave this empty when using InfluxDB 1.x targets! 2 |

3 | 4 | Organization name for InfluxDB 2.x targets. If this field is blank, the plugin interprets it as a InfluxDB 1.x target. -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbStep/help-customProjectName.html: -------------------------------------------------------------------------------- 1 | Sets a custom value for the InfluxDB 'project_name' tag key, that overrides the default, which is the job name. 2 |

Useful to easily group metrics for different jobs in multi branch pipeline jobs.

3 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbPublisher/help-customProjectName.html: -------------------------------------------------------------------------------- 1 | Sets a custom value for the InfluxDB 'project_name' tag key, that overrides the default, which is the job name. 2 |

Useful to easily group metrics for different jobs in multi branch pipeline jobs.

3 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-jobScheduledTimeAsPointsTimestamp.html: -------------------------------------------------------------------------------- 1 | Which timestamp to use for InfluxDB points. 2 |

3 | If enabled, the time when a job is scheduled.
4 | If disabled, the time when a point is created (the job is almost done). 5 |

6 | -------------------------------------------------------------------------------- /src/test/resources/jenkinsci/plugins/influxdb/generators/sonarqube/report-task.txt: -------------------------------------------------------------------------------- 1 | projectKey=InfluxDBPlugin 2 | serverUrl=http://sonarqube:9000 3 | serverVersion=9.9.1.69595 4 | dashboardUrl=http://sonarqube:9000/dashboard?id=InfluxDBPlugin 5 | ceTaskId=123EXAMPLE 6 | ceTaskUrl=http://sonarqube:9000/api/ce/task?id=123EXAMPLE -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-exposeExceptions.html: -------------------------------------------------------------------------------- 1 | If enabled, exceptions communicating with InfluxDB are exposed and jobs may fail if a report could not be written to InfluxDB.
2 | If disabled, even if an exception occurs while connecting to InfluxDB, this exception is not exposed but just logged. 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !src/main/resources/jenkinsci/plugins/influxdb/models/Target 3 | 4 | *.swp 5 | pom.xml.releaseBackup 6 | work/ 7 | release.properties 8 | .idea 9 | *.iml 10 | .DS_Store 11 | changes.txt 12 | 13 | # Local VS code created hidden directories 14 | .classpath 15 | .factorypath 16 | .project 17 | .settings/ 18 | .vscode/ 19 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/serenity/ISerenityJsonSummaryFile.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators.serenity; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Path; 5 | 6 | public interface ISerenityJsonSummaryFile { 7 | boolean exists(); 8 | Path getPath(); 9 | String getContents() throws IOException; 10 | } -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbStep/help-jenkinsEnvParameterTag.html: -------------------------------------------------------------------------------- 1 | Custom tag set that will be added to all measurements, configured as key-value pairs (one per line, in Java Properties file format). 2 |

Current build parameters and/or environment variables can be used in the form of ${PARAM}

3 | 8 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbPublisher/help-jenkinsEnvParameterTag.html: -------------------------------------------------------------------------------- 1 | Custom tag set that will be added to all measurements, configured as key-value pairs (one per line, in Java Properties file format). 2 |

Current build parameters and/or environment variables can be used in the form of ${PARAM}

3 | 8 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbStep/help-jenkinsEnvParameterField.html: -------------------------------------------------------------------------------- 1 | Custom field set that will be added to the default measurement 'jenkins_data', configured as key-value pairs (one per line, in Java Properties file format). 2 |

Current build parameters and/or environment variables can be used in the form of ${PARAM}

3 | 8 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/TimeGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | public class TimeGenerator { 4 | 5 | private final long currentTime; 6 | private long nanoOffSet = 0; 7 | 8 | TimeGenerator(long currentTime) { 9 | this.currentTime = currentTime; 10 | } 11 | 12 | public long next() { 13 | nanoOffSet++; 14 | return currentTime + nanoOffSet; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbPublisher/help-jenkinsEnvParameterField.html: -------------------------------------------------------------------------------- 1 | Custom field set that will be added to the default measurement 'jenkins_data', configured as key-value pairs (one per line, in Java Properties file format). 2 |

Current build parameters and/or environment variables can be used in the form of ${PARAM}

3 | 8 | -------------------------------------------------------------------------------- /src/main/main.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/help-retentionPolicy.html: -------------------------------------------------------------------------------- 1 | The retention policy for the data (i.e. how long the data is stored). 2 |

3 | The default value for InfluxDB 1.0+ versions is "autogen".
4 | The default value for InfluxDB <1.0 versions is "default". 5 |

6 |

The "autogen" or "default" retention policies mean the data is stored for infinity.

7 |

The input can be set e.g. "2m", "3d", "4w", for 2 minutes, 3 days, or 4 weeks respectively.

8 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | name-template: $NEXT_PATCH_VERSION 3 | tag-template: influxdb-$NEXT_VERSION_VERSION 4 | version-template: $MAJOR.$MINOR.$PATCH 5 | categories: 6 | - title: '🚀 Features' 7 | labels: 8 | - 'feature' 9 | - 'enhancement' 10 | - title: '🐛 Bug Fixes' 11 | labels: 12 | - 'fix' 13 | - 'bugfix' 14 | - 'bug' 15 | - title: '🧰 Maintenance' 16 | label: 'maintenance' 17 | exclude-labels: 18 | - 'skip-changelog' 19 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin( 6 | forkCount: '1C', // run this number of tests in parallel for faster feedback. If the number terminates with a 'C', the value will be multiplied by the number of available CPU cores 7 | useContainerAgent: false, // Set to `false` if you need to use Docker for containerized tests 8 | configurations: [ 9 | [platform: 'linux', jdk: 21], 10 | [platform: 'windows', jdk: 17], 11 | ]) -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc 2 | 3 | name: Release Drafter 4 | 5 | on: 6 | push: 7 | branches: 8 | - development 9 | 10 | jobs: 11 | update_release_draft: 12 | runs-on: ubuntu-latest 13 | steps: 14 | # Drafts your next Release notes as Pull Requests are merged into the default branch 15 | - uses: release-drafter/release-drafter@v5 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /src/test/resources/jenkinsci/plugins/influxdb/ConfigurationAsCodeTest/configuration-as-code.yml: -------------------------------------------------------------------------------- 1 | unclassified: 2 | influxDbGlobalConfig: 3 | targets: 4 | - credentialsId: "some_id" 5 | database: "some_database" 6 | description: "some description" 7 | exposeExceptions: true 8 | globalListener: true 9 | globalListenerFilter: "some filter" 10 | jobScheduledTimeAsPointsTimestamp: true 11 | organization: "some_organization" 12 | retentionPolicy: "some_policy" 13 | url: "http://some/url" 14 | usingJenkinsProxy: true 15 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbGlobalConfig/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - development 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/PointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Run; 4 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 5 | 6 | public interface PointGenerator { 7 | 8 | boolean hasReport(); 9 | 10 | AbstractPoint[] generate(); 11 | 12 | /** 13 | * Initializes a basic build point with the basic data already set with a specified timestamp. 14 | */ 15 | AbstractPoint buildPoint(String name, String customPrefix, Run build, long timeStamp); 16 | 17 | /** 18 | * Initializes a basic build point with the basic data already set. 19 | */ 20 | AbstractPoint buildPoint(String name, String customPrefix, Run build); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/InfluxReportException.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb; 2 | 3 | /** 4 | * Generic Exception thrown whenever an Exception occurs writing to InfluxDB. 5 | */ 6 | public class InfluxReportException extends RuntimeException { 7 | 8 | public InfluxReportException(String message) { 9 | super(message); 10 | } 11 | 12 | public InfluxReportException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public InfluxReportException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | public InfluxReportException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/InfluxDbPublisher/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/serenity/SerenityCannedJsonSummaryFile.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators.serenity; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | 9 | public class SerenityCannedJsonSummaryFile implements ISerenityJsonSummaryFile { 10 | 11 | public SerenityCannedJsonSummaryFile() {} 12 | 13 | public boolean exists() { 14 | return Files.exists(getPath()); 15 | } 16 | 17 | public Path getPath() { 18 | try { 19 | return Paths.get(ClassLoader.getSystemResource("serenity/serenity-summary.json").toURI()); 20 | } catch (URISyntaxException e) { 21 | // 22 | } 23 | return null; 24 | } 25 | 26 | public String getContents() throws IOException { 27 | return Files.readString(getPath()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016- Eficode Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/serenity/SerenityJsonSummaryFile.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators.serenity; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | 7 | public class SerenityJsonSummaryFile implements ISerenityJsonSummaryFile { 8 | 9 | private static final String SERENITY_OUTPUT_DIRECTORY = "target/site/serenity"; 10 | private static final String SERENITY_JSON_SUMMARY_FILE = "serenity-summary.json"; 11 | 12 | private final String workspace; 13 | 14 | public SerenityJsonSummaryFile(String workspace) { 15 | this.workspace = workspace; 16 | } 17 | 18 | public boolean exists() { 19 | try { 20 | return Files.exists(getPath()); 21 | } catch (IllegalArgumentException e) { 22 | return false; 23 | } 24 | } 25 | 26 | public Path getPath() { 27 | if (workspace == null) { 28 | throw new IllegalArgumentException("no workspace"); 29 | } 30 | return java.nio.file.Paths.get(workspace, SERENITY_OUTPUT_DIRECTORY, SERENITY_JSON_SUMMARY_FILE); 31 | } 32 | 33 | public String getContents() throws IOException { 34 | return Files.readString(getPath()); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/renderer/ProjectNameRenderer.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.renderer; 2 | 3 | import hudson.model.Run; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.util.Objects; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.Stream; 9 | 10 | public class ProjectNameRenderer implements MeasurementRenderer> { 11 | 12 | private final String customPrefix; 13 | private final String customProjectName; 14 | 15 | public ProjectNameRenderer(String customPrefix, String customProjectName) { 16 | this.customPrefix = StringUtils.trimToNull(customPrefix); 17 | this.customProjectName = StringUtils.trimToNull(customProjectName); 18 | } 19 | 20 | @Override 21 | public String render(Run input) { 22 | return projectName(customPrefix, customProjectName, input); 23 | } 24 | 25 | private String projectName(String prefix, String projectName, Run build) { 26 | if (projectName == null) { 27 | projectName = StringUtils.trimToNull(build.getParent().getName()); 28 | } 29 | 30 | return Stream.of(prefix, projectName) 31 | .filter(Objects::nonNull) 32 | .collect(Collectors.joining("_")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/jenkinsci/plugins/influxdb/models/Target/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/InfluxDbStepExecution.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb; 2 | 3 | import hudson.EnvVars; 4 | import hudson.FilePath; 5 | import hudson.Launcher; 6 | import hudson.model.Run; 7 | import hudson.model.TaskListener; 8 | import org.jenkinsci.plugins.workflow.steps.StepContext; 9 | import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; 10 | 11 | public class InfluxDbStepExecution extends SynchronousNonBlockingStepExecution { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | private transient final InfluxDbStep step; 16 | 17 | InfluxDbStepExecution(InfluxDbStep step, StepContext context) { 18 | super(context); 19 | this.step = step; 20 | } 21 | 22 | @Override 23 | protected Void run() throws Exception { 24 | FilePath workspace = getContext().get(FilePath.class); 25 | InfluxDbPublisher publisher = new InfluxDbPublisher(step.getSelectedTarget()); 26 | publisher.setCustomData(step.getCustomData()); 27 | publisher.setCustomDataMap(step.getCustomDataMap()); 28 | publisher.setCustomDataMapTags(step.getCustomDataMapTags()); 29 | publisher.setCustomDataTags(step.getCustomDataTags()); 30 | publisher.setCustomPrefix(step.getCustomPrefix()); 31 | publisher.setCustomProjectName(step.getCustomProjectName()); 32 | publisher.setJenkinsEnvParameterField(step.getJenkinsEnvParameterField()); 33 | publisher.setJenkinsEnvParameterTag(step.getJenkinsEnvParameterTag()); 34 | publisher.setMeasurementName(step.getMeasurementName()); 35 | publisher.setEnv(getContext().get(EnvVars.class)); 36 | 37 | publisher.perform(getContext().get(Run.class), workspace, getContext().get(EnvVars.class), getContext().get(Launcher.class), getContext().get(TaskListener.class)); 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/serenity/SerenityJsonSummaryFileTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators.serenity; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.charset.StandardCharsets; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.io.TempDir; 12 | 13 | class SerenityJsonSummaryFileTest { 14 | @TempDir 15 | private File temporaryFolder; 16 | 17 | @Test 18 | void testAvailableSummary() throws Exception { 19 | writeToTemporaryPath("target/site/serenity/serenity-summary.json", "expected summary content"); 20 | 21 | SerenityJsonSummaryFile serenityJsonSummaryFile = new SerenityJsonSummaryFile(pathOfTemporaryFolder()); 22 | 23 | assertTrue(serenityJsonSummaryFile.exists()); 24 | assertEquals("expected summary content", serenityJsonSummaryFile.getContents()); 25 | } 26 | 27 | @Test 28 | void testUnavailableSummary() { 29 | SerenityJsonSummaryFile serenityJsonSummaryFile = new SerenityJsonSummaryFile(pathOfTemporaryFolder()); 30 | 31 | assertFalse(serenityJsonSummaryFile.exists()); 32 | } 33 | 34 | @Test 35 | void testUnavailableWorkspace() { 36 | SerenityJsonSummaryFile serenityJsonSummaryFile = new SerenityJsonSummaryFile(null); 37 | 38 | assertFalse(serenityJsonSummaryFile.exists()); 39 | } 40 | 41 | private void writeToTemporaryPath(String path, String content) throws IOException { 42 | Path temporaryPath = temporaryFolder.toPath(); 43 | Path summaryJson = temporaryPath.resolve(path); 44 | Files.createDirectories(summaryJson.getParent()); 45 | Files.writeString(summaryJson, content, StandardCharsets.UTF_8); 46 | } 47 | 48 | private String pathOfTemporaryFolder() { 49 | return temporaryFolder.getAbsolutePath(); 50 | } 51 | } -------------------------------------------------------------------------------- /src/test/resources/jenkinsci/plugins/influxdb/IntegrationBaseTest/configuration-as-code.yml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | systemMessage: "Jenkins with JCasC provisioned InfluxDB instances" 3 | log: 4 | recorders: 5 | - loggers: 6 | - level: "FINE" 7 | name: "jenkinsci.plugins.influxdb.*" 8 | name: "InfluxDB" 9 | 10 | unclassified: 11 | influxDbGlobalConfig: 12 | targets: 13 | - credentialsId: "influxdb-v1-credentials" 14 | database: "${INFLUXDB_DATABASE_OR_BUCKET}" 15 | description: "InfluxDB v1" 16 | exposeExceptions: true 17 | globalListener: true 18 | jobScheduledTimeAsPointsTimestamp: true 19 | url: "${INFLUXDB_V1_URL}" 20 | 21 | - credentialsId: "influxdb-v2-credentials" 22 | database: "${INFLUXDB_DATABASE_OR_BUCKET}" 23 | description: "InfluxDB v2" 24 | exposeExceptions: true 25 | globalListener: true 26 | organization: "${INFLUXDB_ORGANIZATION}" 27 | jobScheduledTimeAsPointsTimestamp: true 28 | url: "${INFLUXDB_V2_URL}" 29 | 30 | - credentialsId: "influxdb-v3-credentials" 31 | database: "${INFLUXDB_DATABASE_OR_BUCKET}" 32 | description: "InfluxDB v3" 33 | exposeExceptions: true 34 | globalListener: true 35 | jobScheduledTimeAsPointsTimestamp: true 36 | url: "${INFLUXDB_V3_URL}" 37 | 38 | credentials: 39 | system: 40 | domainCredentials: 41 | - credentials: 42 | - usernamePassword: 43 | scope: GLOBAL 44 | id: "influxdb-v1-credentials" 45 | username: "${INFLUXDB_USERNAME}" 46 | password: "${INFLUXDB_PASSWORD}" 47 | description: "Username/Password for InfluxDB v1" 48 | - string: 49 | scope: GLOBAL 50 | id: "influxdb-v2-credentials" 51 | secret: "${INFLUXDB_V2_TOKEN}" 52 | description: "Token for InfluxDB v2" 53 | - string: 54 | scope: GLOBAL 55 | id: "influxdb-v3-credentials" 56 | secret: "${INFLUXDB_V3_TOKEN}" 57 | description: "Token for InfluxDB v3" -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/CustomDataMapPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Run; 4 | import hudson.model.TaskListener; 5 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 6 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public class CustomDataMapPointGenerator extends AbstractPointGenerator { 13 | 14 | private final String customPrefix; 15 | private final Map> customDataMap; 16 | private final Map> customDataMapTags; 17 | 18 | public CustomDataMapPointGenerator(Run build, TaskListener listener, 19 | ProjectNameRenderer projectNameRenderer, 20 | long timestamp, String jenkinsEnvParameterTag, 21 | String customPrefix, Map> customDataMap, 22 | Map> customDataMapTags) { 23 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 24 | this.customPrefix = customPrefix; 25 | this.customDataMap = customDataMap; 26 | this.customDataMapTags = customDataMapTags; 27 | } 28 | 29 | public boolean hasReport() { 30 | return (customDataMap != null && customDataMap.size() > 0); 31 | } 32 | 33 | public AbstractPoint[] generate() { 34 | List points = new ArrayList<>(); 35 | 36 | for (Map.Entry> entry : customDataMap.entrySet()) { 37 | AbstractPoint point = buildPoint(entry.getKey(), customPrefix, build).addFields(entry.getValue()); 38 | 39 | if (customDataMapTags != null) { 40 | Map customTags = customDataMapTags.get(entry.getKey()); 41 | if (customTags != null) { 42 | if (customTags.size() > 0) { 43 | point.addTags(customTags); 44 | } 45 | } 46 | } 47 | 48 | 49 | points.add(point); 50 | } 51 | 52 | return points.toArray(new AbstractPoint[0]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/CustomDataPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Run; 4 | import hudson.model.TaskListener; 5 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 6 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 7 | 8 | import java.util.Map; 9 | 10 | import static jenkinsci.plugins.influxdb.InfluxDbPublisher.DEFAULT_MEASUREMENT_NAME; 11 | 12 | public class CustomDataPointGenerator extends AbstractPointGenerator { 13 | 14 | private static final String BUILD_TIME = "build_time"; 15 | 16 | private final String customPrefix; 17 | private final String measurementName; 18 | private final Map customData; 19 | private final Map customDataTags; 20 | 21 | public CustomDataPointGenerator(Run build, TaskListener listener, 22 | ProjectNameRenderer projectNameRenderer, 23 | long timestamp, String jenkinsEnvParameterTag, 24 | String customPrefix, Map customData, 25 | Map customDataTags, String measurementName) { 26 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 27 | this.customPrefix = customPrefix; 28 | this.customData = customData; 29 | this.customDataTags = customDataTags; 30 | // Extra logic to retain compatibility with existing "jenkins_custom_data" tables 31 | this.measurementName = DEFAULT_MEASUREMENT_NAME.equals(measurementName) ? "jenkins_custom_data" : "custom_" + measurementName; 32 | } 33 | 34 | public boolean hasReport() { 35 | return (customData != null && !customData.isEmpty()); 36 | } 37 | 38 | public AbstractPoint[] generate() { 39 | long startTime = build.getTimeInMillis(); 40 | long currTime = System.currentTimeMillis(); 41 | long dt = currTime - startTime; 42 | 43 | AbstractPoint point = buildPoint(measurementName, customPrefix, build) 44 | .addField(BUILD_TIME, build.getDuration() == 0 ? dt : build.getDuration()) 45 | .addFields(customData); 46 | 47 | if (customDataTags != null) { 48 | if (!customDataTags.isEmpty()) { 49 | point.addTags(customDataTags); 50 | } 51 | } 52 | 53 | 54 | return new AbstractPoint[]{point}; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /doc/breaking_changes.md: -------------------------------------------------------------------------------- 1 | # Breaking Changes 2 | 3 | ## 6.0 4 | 5 | - Fields `project_name` and `project_path` have been removed from **all metrics** since they are already defined as 6 | tags. 7 | - Field `build_result` has been removed from the **jenkins_data** metrics since it is already defined as tag. 8 | 9 | ## 5.0 10 | 11 | - Changes to Robt Framework metrics: 12 | - `rf_critical_pass_percentage` has been removed. It has been replaced by `rf_pass_percentage_total`, 13 | which sends the percentage of tests that passed including skipped tests. 14 | 15 | ## 4.0 16 | 17 | - Changes to Robot Framework metrics: 18 | - All `rf_critical_*` metrics have been removed as criticality has been deprecated by the Robot Framework plugin. 19 | - EXCEPT `rf_critical_pass_percentage`. It now sends the percentage of tests that passed including skipped tests. 20 | - `rf_pass_percentage` continues behaviour as before and sends the percentage of tests that passed excluding skipped tests. 21 | 22 | ## 3.0 23 | 24 | - InfluxDB 1.7 and lower are no longer supported. Only supported 1.x version is 1.8.x. 25 | - "username" and "password" are no longer used when defining new InfluxDB Targets. 26 | Credentials are used instead. Please check your InfluxDB Target configurations from 27 | Manage Jenkins --> Configure System. 28 | - All pipelines that create a new Target inside 29 | the pipeline need to be modified so, that they use `target.credentialsId` instead of 30 | `target.username` and `target.password`. 31 | - JCasC configurations need to be modified, so that they use `credentialsId` instead of `username` 32 | and `password`. 33 | - JUnit `test_name` field/tag changed to remove pipeline name. If your pipeline had multiple `junit` steps, 34 | the pipeline step information is now recorded in the `pipeline_step` field/tag. For example: 35 | - Before
`test_name`: `Tests / Test Stage 1 / my_test_name` 36 | - After
`test_name`: `my_test_name`
`test_full_class_name`: `mypackage.MyClass`
`pipeline_step`: `Tests / Test Stage 1` 37 | 38 | 39 | ## 2.0 40 | 41 | - From version 2.0 onwards `selectedTarget` is a **mandatory** parameter 42 | for pipelines and the `target` parameter is no longer supported. 43 | - Configuration As Code: the configuration needs to be changed from 44 | `influxDbPublisher` to `influxDbGlobalConfig`. 45 | - Might cause issues when creating new targets in pipelines. The 46 | `InfluxDbPublisher` instance is now 47 | under jenkinsci.plugins.influxdb.**InfluxDbStep**.DescriptorImpl. 48 | 49 | ## 1.13 50 | 51 | From version 1.13 onwards different plugins are listed as optional 52 | dependencies. In order to get rid of mandatory dependency errors, 53 | the InfluxDB plugin must be re-installed. -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/JacocoPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Run; 4 | import hudson.model.TaskListener; 5 | import hudson.plugins.jacoco.JacocoBuildAction; 6 | import hudson.plugins.jacoco.model.Coverage; 7 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 8 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 9 | 10 | public class JacocoPointGenerator extends AbstractPointGenerator { 11 | private static final String JACOCO_CLASS = "jacoco_class"; 12 | private static final String JACOCO_LINE = "jacoco_line"; 13 | private static final String JACOCO_BRANCH = "jacoco_branch"; 14 | private static final String JACOCO_METHOD = "jacoco_method"; 15 | private static final String JACOCO_INSTRUCTION = "jacoco_instruction"; 16 | private static final String JACOCO_COMPLEXITY = "jacoco_complexity"; 17 | 18 | private final String customPrefix; 19 | private final JacocoBuildAction jacocoBuildAction; 20 | 21 | public JacocoPointGenerator(Run build, TaskListener listener, 22 | ProjectNameRenderer projectNameRenderer, 23 | long timestamp, String jenkinsEnvParameterTag, 24 | String customPrefix) { 25 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 26 | this.customPrefix = customPrefix; 27 | jacocoBuildAction = build.getAction(JacocoBuildAction.class); 28 | } 29 | 30 | public boolean hasReport() { 31 | return jacocoBuildAction != null && jacocoBuildAction.getResult() != null; 32 | } 33 | 34 | public AbstractPoint[] generate() { 35 | 36 | AbstractPoint point = buildPoint("jacoco_data", customPrefix, build); 37 | addFields(point, JACOCO_CLASS, jacocoBuildAction.getResult().getClassCoverage()); 38 | addFields(point, JACOCO_LINE, jacocoBuildAction.getResult().getLineCoverage()); 39 | addFields(point, JACOCO_BRANCH, jacocoBuildAction.getResult().getBranchCoverage()); 40 | addFields(point, JACOCO_METHOD, jacocoBuildAction.getResult().getMethodCoverage()); 41 | addFields(point, JACOCO_INSTRUCTION, jacocoBuildAction.getResult().getInstructionCoverage()); 42 | addFields(point, JACOCO_COMPLEXITY, jacocoBuildAction.getResult().getComplexityScore()); 43 | 44 | return new AbstractPoint[]{point}; 45 | } 46 | 47 | private void addFields(AbstractPoint point, String prefix, Coverage coverage) { 48 | point.addField(prefix + "_coverage_rate", coverage.getPercentageFloat()); 49 | point.addField(prefix + "_covered", coverage.getCovered()); 50 | point.addField(prefix + "_missed", coverage.getMissed()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/MetricsPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Run; 4 | import hudson.model.TaskListener; 5 | import jenkins.metrics.impl.TimeInQueueAction; 6 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 7 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 8 | 9 | public class MetricsPointGenerator extends AbstractPointGenerator { 10 | private static final String BLOCKED_TIME = "blocked_time"; 11 | private static final String BUILDABLE_TIME = "buildable_time"; 12 | private static final String BUILDING_TIME = "building_time"; 13 | private static final String EXECUTING_TIME = "executing_time"; 14 | private static final String EXECUTOR_UTILIZATION = "executor_utilization"; 15 | private static final String QUEUEING_TIME = "queue_time"; 16 | private static final String SUBTASK_COUNT = "subtask_count"; 17 | private static final String TOTAL_DURATION = "total_duration"; 18 | private static final String WAITING_TIME = "waiting_time"; 19 | 20 | private final Run build; 21 | private final String customPrefix; 22 | private final TimeInQueueAction timeInQueueAction; 23 | 24 | public MetricsPointGenerator(Run build, TaskListener listener, 25 | ProjectNameRenderer projectNameRenderer, 26 | long timestamp, String jenkinsEnvParameterTag, 27 | String customPrefix) { 28 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 29 | this.build = build; 30 | this.customPrefix = customPrefix; 31 | timeInQueueAction = build.getAction(TimeInQueueAction.class); 32 | } 33 | 34 | public boolean hasReport() { 35 | return timeInQueueAction != null; 36 | } 37 | 38 | public AbstractPoint[] generate() { 39 | AbstractPoint point = buildPoint("metrics_data", customPrefix, build); 40 | point.addField(BLOCKED_TIME, timeInQueueAction.getBlockedDurationMillis()); 41 | point.addField(BUILDABLE_TIME, timeInQueueAction.getBuildableDurationMillis()); 42 | point.addField(BUILDING_TIME, timeInQueueAction.getBuildingDurationMillis()); 43 | point.addField(EXECUTING_TIME, timeInQueueAction.getExecutingTimeMillis()); 44 | point.addField(EXECUTOR_UTILIZATION, timeInQueueAction.getExecutorUtilization()); 45 | point.addField(QUEUEING_TIME, timeInQueueAction.getQueuingDurationMillis()); 46 | point.addField(SUBTASK_COUNT, timeInQueueAction.getSubTaskCount()); 47 | point.addField(TOTAL_DURATION, timeInQueueAction.getTotalDurationMillis()); 48 | point.addField(WAITING_TIME, timeInQueueAction.getWaitingDurationMillis()); 49 | return new AbstractPoint[]{point}; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/InfluxDbGlobalConfig.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb; 2 | 3 | import hudson.Extension; 4 | import hudson.ExtensionList; 5 | import hudson.init.InitMilestone; 6 | import hudson.init.Initializer; 7 | import jenkins.model.GlobalConfiguration; 8 | import jenkinsci.plugins.influxdb.models.Target; 9 | import net.sf.json.JSONObject; 10 | import org.kohsuke.stapler.StaplerRequest2; 11 | 12 | import java.util.Arrays; 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.concurrent.CopyOnWriteArrayList; 17 | 18 | @Extension 19 | public class InfluxDbGlobalConfig extends GlobalConfiguration { 20 | 21 | private List targets = new CopyOnWriteArrayList<>(); 22 | private boolean targetsMigrated = false; 23 | 24 | public InfluxDbGlobalConfig() { 25 | load(); 26 | } 27 | 28 | public static InfluxDbGlobalConfig getInstance() { 29 | return GlobalConfiguration.all().get(InfluxDbGlobalConfig.class); 30 | } 31 | 32 | public List getTargets() { 33 | return Collections.unmodifiableList(targets); 34 | } 35 | 36 | public void setTargets(List targets) { 37 | this.targets = targets; 38 | save(); 39 | } 40 | 41 | @SuppressWarnings("deprecation") 42 | @Initializer(after = InitMilestone.JOB_LOADED) 43 | public void migrateTargets() { 44 | if (targetsMigrated) { 45 | return; 46 | } 47 | Optional optionalDescriptor = ExtensionList.lookup(InfluxDbPublisher.DescriptorImpl.class).stream().findFirst(); 48 | 49 | optionalDescriptor.ifPresent(publisher -> { 50 | if (publisher.getDeprecatedTargets().length > 0) { 51 | targets = Arrays.asList(publisher.getDeprecatedTargets()); 52 | save(); 53 | } 54 | publisher.removeDeprecatedTargets(); 55 | }); 56 | targetsMigrated = true; 57 | save(); 58 | } 59 | 60 | @Override 61 | public boolean configure(StaplerRequest2 req, JSONObject formData) { 62 | targets = new CopyOnWriteArrayList<>(); 63 | targets.addAll(req.bindJSONToList(Target.class, formData.get("targets"))); 64 | save(); 65 | return true; 66 | } 67 | 68 | /** 69 | * Add target to list of targets 70 | * 71 | * @param target Target to add 72 | */ 73 | public void addTarget(Target target) { 74 | targets.add(target); 75 | } 76 | 77 | /** 78 | * Remove target from list of targets 79 | * 80 | * @param targetDescription Target description of target to remove. 81 | */ 82 | public void removeTarget(String targetDescription) { 83 | targets.removeIf(target -> target.getDescription().equals(targetDescription)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/renderer/ProjectNameRendererTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.renderer; 2 | 3 | import hudson.model.Job; 4 | import hudson.model.Run; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.mockito.Mockito; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | class ProjectNameRendererTest { 12 | 13 | private static final String JOB_NAME = "master"; 14 | private static final int BUILD_NUMBER = 11; 15 | private static final String CUSTOM_PREFIX = "test_prefix"; 16 | private static final String CUSTOM_PROJECT_NAME = "test_projectname"; 17 | 18 | private Run build; 19 | private Job job; 20 | 21 | @BeforeEach 22 | void before() { 23 | build = Mockito.mock(Run.class); 24 | job = Mockito.mock(Job.class); 25 | 26 | Mockito.when(build.getNumber()).thenReturn(BUILD_NUMBER); 27 | Mockito.doReturn(job).when(build).getParent(); 28 | Mockito.when(job.getName()).thenReturn(JOB_NAME); 29 | } 30 | 31 | @Test 32 | void customProjectNameWithCustomPrefix() { 33 | ProjectNameRenderer projectNameRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, CUSTOM_PROJECT_NAME); 34 | String renderedProjectName = projectNameRenderer.render(build); 35 | assertTrue(renderedProjectName.startsWith(CUSTOM_PREFIX + "_" + CUSTOM_PROJECT_NAME)); 36 | } 37 | 38 | @Test 39 | void customProjectNameWithNullPrefix() { 40 | ProjectNameRenderer projectNameRenderer = new ProjectNameRenderer(null, CUSTOM_PROJECT_NAME); 41 | String renderedProjectName = projectNameRenderer.render(build); 42 | assertTrue(renderedProjectName.startsWith(CUSTOM_PROJECT_NAME)); 43 | } 44 | 45 | @Test 46 | void nullProjectNameWithCustomPrefix() { 47 | ProjectNameRenderer projectNameRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null); 48 | String renderedProjectName = projectNameRenderer.render(build); 49 | assertTrue(renderedProjectName.startsWith(CUSTOM_PREFIX + "_" + JOB_NAME)); 50 | } 51 | 52 | @Test 53 | void nullProjectNameWithNullPrefix() { 54 | ProjectNameRenderer projectNameRenderer = new ProjectNameRenderer(null, null); 55 | String renderedProjectName = projectNameRenderer.render(build); 56 | assertTrue(renderedProjectName.startsWith(JOB_NAME)); 57 | } 58 | 59 | @Test 60 | void nullProjectNameWithNullPrefix_NoSideEffects() { 61 | ProjectNameRenderer projectNameRenderer = new ProjectNameRenderer(null, null); 62 | 63 | Mockito.when(job.getName()).thenReturn("job 1"); 64 | String renderedProjectName1 = projectNameRenderer.render(build); 65 | assertTrue(renderedProjectName1.startsWith("job 1")); 66 | 67 | Mockito.when(job.getName()).thenReturn("job 2"); 68 | String renderedProjectName2 = projectNameRenderer.render(build); 69 | assertTrue(renderedProjectName2.startsWith("job 2")); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ development ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ development ] 20 | schedule: 21 | - cron: '30 1 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/GitPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Run; 4 | import hudson.model.TaskListener; 5 | import hudson.plugins.git.Branch; 6 | import hudson.plugins.git.Revision; 7 | import hudson.plugins.git.util.BuildData; 8 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 9 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 10 | import org.apache.commons.collections.CollectionUtils; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collection; 14 | import java.util.List; 15 | 16 | /** 17 | * @author Mathieu Delrocq 18 | */ 19 | public class GitPointGenerator extends AbstractPointGenerator { 20 | 21 | // Point fields names 22 | protected static final String GIT_REPOSITORY = "git_repository"; 23 | protected static final String GIT_REVISION = "git_revision"; 24 | protected static final String GIT_REFERENCE = "git_reference"; 25 | protected static final String UNIQUE_ID = "unique_id"; 26 | 27 | private String customPrefix; 28 | private List gitActions; 29 | 30 | public GitPointGenerator(Run build, TaskListener listener, ProjectNameRenderer projectNameRenderer, 31 | long timestamp, String jenkinsEnvParameterTag, String customPrefix) { 32 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 33 | this.customPrefix = customPrefix; 34 | gitActions = build.getActions(BuildData.class); 35 | } 36 | 37 | /** 38 | * Check if git infos are presents in the build 39 | * 40 | * @return true if present 41 | */ 42 | @Override 43 | public boolean hasReport() { 44 | return CollectionUtils.isNotEmpty(gitActions); 45 | } 46 | 47 | /** 48 | * Generates Git Points with datas in Git plugins 49 | * 50 | * @return Array of Point 51 | */ 52 | @Override 53 | public AbstractPoint[] generate() { 54 | List points = new ArrayList<>(); 55 | String sha1String = null; 56 | String branchName = null; 57 | BuildData gitAction = null; 58 | for (int i = 0; i < gitActions.size(); i++) { 59 | gitAction = gitActions.get(i); 60 | Revision revision = gitAction.getLastBuiltRevision(); 61 | if (revision != null) { 62 | sha1String = revision.getSha1String(); 63 | Collection branches = revision.getBranches(); 64 | if (CollectionUtils.isNotEmpty(branches)) { 65 | branchName = branches.iterator().next().getName(); 66 | } 67 | } 68 | AbstractPoint point = buildPoint("git_data", customPrefix, build) 69 | .addTag(UNIQUE_ID, String.valueOf(i + 1)) 70 | .addField(GIT_REPOSITORY, !CollectionUtils.isEmpty(gitAction.getRemoteUrls()) ? gitAction.getRemoteUrls().iterator().next() : "")// 71 | .addField(GIT_REFERENCE, branchName) 72 | .addField(GIT_REVISION, sha1String); 73 | points.add(point); 74 | } 75 | return points.toArray(new AbstractPoint[0]); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/InfluxDbPublisherTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb; 2 | 3 | import hudson.EnvVars; 4 | import hudson.FilePath; 5 | import hudson.Launcher; 6 | import hudson.model.FreeStyleProject; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | import jenkins.model.Jenkins; 10 | import jenkinsci.plugins.influxdb.models.Target; 11 | import org.junit.jupiter.api.Test; 12 | import org.jvnet.hudson.test.Issue; 13 | import org.jvnet.hudson.test.JenkinsRule; 14 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 15 | import org.mockito.Mock; 16 | import org.mockito.Mockito; 17 | 18 | import java.io.File; 19 | import java.util.Collections; 20 | 21 | import static org.junit.jupiter.api.Assertions.assertEquals; 22 | import static org.junit.jupiter.api.Assertions.assertThrows; 23 | 24 | @WithJenkins 25 | class InfluxDbPublisherTest { 26 | 27 | private FilePath workspace = new FilePath(new File(".")); 28 | @Mock 29 | private Run build; 30 | @Mock 31 | private Launcher launcher; 32 | @Mock 33 | private TaskListener listener; 34 | @Mock 35 | private EnvVars envVars; 36 | 37 | @Test 38 | void testEmptyTargetShouldThrowException(JenkinsRule j) { 39 | 40 | InfluxDbPublisher.DescriptorImpl descriptorMock = Mockito.mock(InfluxDbPublisher.DescriptorImpl.class); 41 | Jenkins jenkinsMock = Mockito.mock(Jenkins.class); 42 | Mockito.when(descriptorMock.getTargets()).thenReturn(Collections.emptyList()); 43 | Mockito.when(jenkinsMock.getDescriptorByType(InfluxDbPublisher.DescriptorImpl.class)).thenReturn(descriptorMock); 44 | 45 | assertThrows(RuntimeException.class, () -> new InfluxDbPublisher("").perform(build, workspace, envVars, launcher, listener), "Target was null!"); 46 | } 47 | 48 | @Test 49 | @Issue("JENKINS-61305") 50 | void testConfigRoundTripShouldPreserveSelectedTarget(JenkinsRule j) throws Exception { 51 | InfluxDbGlobalConfig globalConfig = InfluxDbGlobalConfig.getInstance(); 52 | Target target1 = new Target(); 53 | target1.setDescription("Target1"); 54 | Target target2 = new Target(); 55 | target2.setDescription("Target2"); 56 | globalConfig.addTarget(target1); 57 | globalConfig.addTarget(target2); 58 | 59 | InfluxDbPublisher before = new InfluxDbPublisher("Target2"); 60 | assertEquals("Target2", before.getSelectedTarget()); 61 | assertEquals(before.getTarget(), target2); 62 | FreeStyleProject project = j.createFreeStyleProject(); 63 | project.getPublishersList().add(before); 64 | j.configRoundtrip(project); 65 | 66 | InfluxDbPublisher after = project.getPublishersList().get(InfluxDbPublisher.class); 67 | j.assertEqualBeans(before, after, "selectedTarget"); 68 | } 69 | 70 | @Test 71 | void testGetTargetShouldReturnFirstTargetWithNull(JenkinsRule j) { 72 | InfluxDbGlobalConfig globalConfig = InfluxDbGlobalConfig.getInstance(); 73 | 74 | Target target1 = new Target(); 75 | target1.setDescription("Target1"); 76 | globalConfig.addTarget(target1); 77 | 78 | InfluxDbPublisher publisher = new InfluxDbPublisher(null); 79 | assertEquals(target1, publisher.getTarget()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/CoveragePointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import edu.hm.hafner.coverage.Metric; 4 | import hudson.model.Run; 5 | import hudson.model.TaskListener; 6 | import io.jenkins.plugins.coverage.metrics.model.Baseline; 7 | import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; 8 | import io.jenkins.plugins.coverage.metrics.model.ElementFormatter; 9 | import io.jenkins.plugins.coverage.metrics.steps.CoverageBuildAction; 10 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 11 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 12 | import org.apache.commons.lang3.StringUtils; 13 | 14 | import java.text.NumberFormat; 15 | import java.text.ParseException; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | 20 | public class CoveragePointGenerator extends AbstractPointGenerator { 21 | 22 | 23 | private final String customPrefix; 24 | 25 | public CoveragePointGenerator(Run build, 26 | TaskListener listener, 27 | ProjectNameRenderer projectNameRenderer, 28 | long timestamp, 29 | String jenkinsEnvParameterTag, 30 | String customPrefix) { 31 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 32 | this.customPrefix = customPrefix; 33 | } 34 | 35 | @Override 36 | public boolean hasReport() { 37 | return build.getAction(CoverageBuildAction.class) != null; 38 | } 39 | 40 | @Override 41 | public AbstractPoint[] generate() { 42 | 43 | List points = new ArrayList<>(); 44 | CoverageBuildAction action = build.getAction(CoverageBuildAction.class); 45 | CoverageStatistics coverageStatistics = action.getStatistics(); 46 | for (Baseline baseline : new Baseline[]{Baseline.PROJECT, Baseline.MODIFIED_LINES, Baseline.MODIFIED_FILES}) { 47 | points.add(buildSubPoint("coverage_" + baseline.toString().toLowerCase() + "_data", customPrefix, build, baseline, coverageStatistics)); 48 | } 49 | 50 | return points.toArray(new AbstractPoint[0]); 51 | } 52 | 53 | private AbstractPoint buildSubPoint(String name, String customPrefix, Run build, Baseline baseline, CoverageStatistics coverageStatistics) { 54 | AbstractPoint point = buildPoint(name, customPrefix, build); 55 | ElementFormatter formatter = new ElementFormatter(); 56 | for (Metric m : Metric.values()) { 57 | coverageStatistics.getValue(baseline, m).ifPresent(value -> { 58 | String x = formatter.format(value); 59 | try { 60 | Number number = NumberFormat.getInstance().parse(StringUtils.substringBefore(x, "%")); 61 | if (StringUtils.contains(x, "%")) { // failsafe to enforce percentage values as floats 62 | number = number.floatValue(); 63 | } 64 | point.addField(m.toTagName(), number); 65 | } catch (ParseException e) { 66 | // No operation 67 | } 68 | }); 69 | } 70 | 71 | return point; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/CoberturaPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Run; 4 | import hudson.model.TaskListener; 5 | import hudson.plugins.cobertura.CoberturaBuildAction; 6 | import hudson.plugins.cobertura.Ratio; 7 | import hudson.plugins.cobertura.targets.CoverageMetric; 8 | import hudson.plugins.cobertura.targets.CoverageResult; 9 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 10 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 11 | 12 | public class CoberturaPointGenerator extends AbstractPointGenerator { 13 | 14 | private static final String COBERTURA_PACKAGE_COVERAGE_RATE = "cobertura_package_coverage_rate"; 15 | private static final String COBERTURA_CLASS_COVERAGE_RATE = "cobertura_class_coverage_rate"; 16 | private static final String COBERTURA_LINE_COVERAGE_RATE = "cobertura_line_coverage_rate"; 17 | private static final String COBERTURA_BRANCH_COVERAGE_RATE = "cobertura_branch_coverage_rate"; 18 | private static final String COBERTURA_NUMBER_OF_PACKAGES = "cobertura_number_of_packages"; 19 | private static final String COBERTURA_NUMBER_OF_SOURCEFILES = "cobertura_number_of_sourcefiles"; 20 | private static final String COBERTURA_NUMBER_OF_CLASSES = "cobertura_number_of_classes"; 21 | 22 | private final CoberturaBuildAction coberturaBuildAction; 23 | private final String customPrefix; 24 | 25 | public CoberturaPointGenerator(Run build, TaskListener listener, 26 | ProjectNameRenderer projectNameRenderer, 27 | long timestamp, String jenkinsEnvParameterTag, 28 | String customPrefix) { 29 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 30 | this.customPrefix = customPrefix; 31 | coberturaBuildAction = build.getAction(CoberturaBuildAction.class); 32 | } 33 | 34 | public boolean hasReport() { 35 | return coberturaBuildAction != null && coberturaBuildAction.getResult() != null; 36 | } 37 | 38 | public AbstractPoint[] generate() { 39 | CoverageResult result = coberturaBuildAction.getResult(); 40 | Ratio conditionals = result.getCoverage(CoverageMetric.CONDITIONAL); 41 | Ratio lines = result.getCoverage(CoverageMetric.LINE); 42 | Ratio packages = result.getCoverage(CoverageMetric.PACKAGES); 43 | Ratio classes = result.getCoverage(CoverageMetric.CLASSES); 44 | Ratio files = result.getCoverage(CoverageMetric.FILES); 45 | 46 | AbstractPoint point = buildPoint("cobertura_data", customPrefix, build) 47 | .addField(COBERTURA_NUMBER_OF_PACKAGES, packages.denominator) 48 | .addField(COBERTURA_NUMBER_OF_SOURCEFILES, files.denominator) 49 | .addField(COBERTURA_NUMBER_OF_CLASSES, classes.denominator) 50 | .addField(COBERTURA_BRANCH_COVERAGE_RATE, conditionals.getPercentageFloat()) 51 | .addField(COBERTURA_LINE_COVERAGE_RATE, lines.getPercentageFloat()) 52 | .addField(COBERTURA_PACKAGE_COVERAGE_RATE, packages.getPercentageFloat()) 53 | .addField(COBERTURA_CLASS_COVERAGE_RATE, classes.getPercentageFloat()); 54 | 55 | return new AbstractPoint[]{point}; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/ConfigurationAsCodeTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb; 2 | 3 | import io.jenkins.plugins.casc.ConfigurationAsCode; 4 | import jenkinsci.plugins.influxdb.models.Target; 5 | import org.apache.commons.io.IOUtils; 6 | import org.junit.jupiter.api.Test; 7 | import org.jvnet.hudson.test.JenkinsRule; 8 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 9 | 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.InputStream; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.Collections; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | 18 | @WithJenkins 19 | class ConfigurationAsCodeTest { 20 | 21 | @Test 22 | void should_support_jcasc_from_yaml(JenkinsRule j) { 23 | InfluxDbGlobalConfig globalConfig = InfluxDbGlobalConfig.getInstance(); 24 | 25 | String yamlUrl = getClass().getResource(getClass().getSimpleName() + "/configuration-as-code.yml").toString(); 26 | ConfigurationAsCode.get().configure(yamlUrl); 27 | 28 | assertEquals(1, globalConfig.getTargets().size()); 29 | 30 | Target target = globalConfig.getTargets().get(0); 31 | assertEquals("some description", target.getDescription()); 32 | assertEquals("http://some/url", target.getUrl()); 33 | 34 | assertEquals("some_id", target.getCredentialsId()); 35 | assertEquals("some_database", target.getDatabase()); 36 | assertEquals("some_policy", target.getRetentionPolicy()); 37 | assertTrue(target.isJobScheduledTimeAsPointsTimestamp()); 38 | assertTrue(target.isExposeExceptions()); 39 | assertTrue(target.isUsingJenkinsProxy()); 40 | assertTrue(target.isGlobalListener()); 41 | assertEquals("some filter", target.getGlobalListenerFilter()); 42 | assertEquals("some_organization", target.getOrganization()); 43 | } 44 | 45 | @Test 46 | void should_support_jcasc_to_yaml(JenkinsRule j) throws Exception { 47 | InfluxDbGlobalConfig globalConfig = InfluxDbGlobalConfig.getInstance(); 48 | 49 | Target target = new Target(); 50 | target.setDescription("some description"); 51 | target.setUrl("http://some/url"); 52 | target.setCredentialsId("some_id"); 53 | target.setDatabase("some_database"); 54 | target.setRetentionPolicy("some_policy"); 55 | target.setJobScheduledTimeAsPointsTimestamp(true); 56 | target.setExposeExceptions(true); 57 | target.setUsingJenkinsProxy(true); 58 | target.setGlobalListener(true); 59 | target.setGlobalListenerFilter("some filter"); 60 | target.setOrganization("some_organization"); 61 | 62 | globalConfig.setTargets(Collections.singletonList(target)); 63 | 64 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 65 | ConfigurationAsCode.get().export(outputStream); 66 | String exportedYaml = outputStream.toString(StandardCharsets.UTF_8); 67 | 68 | InputStream yamlStream = getClass().getResourceAsStream(getClass().getSimpleName() + "/configuration-as-code.yml"); 69 | String expectedYaml = IOUtils.toString(yamlStream, StandardCharsets.UTF_8) 70 | .replaceAll("\r\n?", "\n") 71 | .replace("unclassified:\n", ""); 72 | 73 | assertTrue(exportedYaml.contains(expectedYaml)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/PerformancePointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Run; 4 | import hudson.model.TaskListener; 5 | import hudson.plugins.performance.actions.PerformanceBuildAction; 6 | import hudson.plugins.performance.reports.PerformanceReport; 7 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 8 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class PerformancePointGenerator extends AbstractPointGenerator { 15 | 16 | private static final String PERFORMANCE_ERROR_PERCENT = "error_percent"; // failed / size * 100 17 | private static final String PERFORMANCE_ERROR_COUNT = "error_count"; // Amount of failed samples 18 | private static final String PERFORMANCE_AVERAGE = "average"; // Total duration / size 19 | private static final String PERFORMANCE_90PERCENTILE = "90Percentile"; // 90 Percentile duration 20 | private static final String PERFORMANCE_MEDIAN = "median"; //median duration 21 | private static final String PERFORMANCE_MAX = "max"; // max duration 22 | private static final String PERFORMANCE_MIN = "min"; // min duration 23 | private static final String PERFORMANCE_TOTAL_TRAFFIC = "total_traffic"; 24 | private static final String PERFORMANCE_SIZE = "size"; // Size of all samples 25 | 26 | private final String customPrefix; 27 | private final PerformanceBuildAction performanceBuildAction; 28 | 29 | public PerformancePointGenerator(Run build, TaskListener listener, 30 | ProjectNameRenderer projectNameRenderer, 31 | long timestamp, String jenkinsEnvParameterTag, 32 | String customPrefix) { 33 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 34 | this.customPrefix = customPrefix; 35 | performanceBuildAction = build.getAction(PerformanceBuildAction.class); 36 | } 37 | 38 | public boolean hasReport() { 39 | return performanceBuildAction != null && performanceBuildAction.getPerformanceReportMap() != null; 40 | } 41 | 42 | public AbstractPoint[] generate() { 43 | Map reportMap = performanceBuildAction.getPerformanceReportMap().getPerformanceReportMap(); 44 | 45 | List points = new ArrayList<>(); 46 | 47 | for (PerformanceReport report : reportMap.values()) { 48 | points.add(generateReportPoint(report)); 49 | } 50 | 51 | return points.toArray(new AbstractPoint[0]); 52 | } 53 | 54 | private AbstractPoint generateReportPoint(PerformanceReport performanceReport) { 55 | return buildPoint("performance_data", customPrefix, build) 56 | .addField(PERFORMANCE_ERROR_PERCENT, performanceReport.errorPercent()) 57 | .addField(PERFORMANCE_ERROR_COUNT, performanceReport.countErrors()) 58 | .addField(PERFORMANCE_AVERAGE, performanceReport.getAverage()) 59 | .addField(PERFORMANCE_MAX, performanceReport.getMax()) 60 | .addField(PERFORMANCE_MIN, performanceReport.getMin()) 61 | .addField(PERFORMANCE_TOTAL_TRAFFIC, performanceReport.getTotalTrafficInKb()) 62 | .addField(PERFORMANCE_SIZE, performanceReport.samplesCount()) 63 | .addField(PERFORMANCE_90PERCENTILE, performanceReport.get90Line()) 64 | .addField(PERFORMANCE_MEDIAN, performanceReport.getMedian()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/serenity/SerenityPointGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators.serenity; 2 | 3 | import hudson.model.Job; 4 | import hudson.model.Run; 5 | import hudson.model.TaskListener; 6 | import jenkins.model.Jenkins; 7 | import jenkinsci.plugins.influxdb.generators.PointGeneratorBaseTest; 8 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 9 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import org.mockito.Mockito; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | 21 | class SerenityPointGeneratorTest extends PointGeneratorBaseTest { 22 | 23 | private static final String JOB_NAME = "master"; 24 | private static final int BUILD_NUMBER = 11; 25 | private static final String CUSTOM_PREFIX = "test_prefix"; 26 | AbstractPoint point = null; 27 | private Run build; 28 | private ProjectNameRenderer measurementRenderer; 29 | private long currTime; 30 | private TaskListener listener; 31 | 32 | @BeforeEach 33 | void before() { 34 | build = Mockito.mock(Run.class); 35 | Job job = Mockito.mock(Job.class); 36 | listener = Mockito.mock(TaskListener.class); 37 | measurementRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null); 38 | 39 | Mockito.when(build.getNumber()).thenReturn(BUILD_NUMBER); 40 | Mockito.when(build.getParent()).thenReturn(job); 41 | Mockito.when(job.getName()).thenReturn(JOB_NAME); 42 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + JOB_NAME); 43 | 44 | currTime = System.currentTimeMillis(); 45 | 46 | SerenityCannedJsonSummaryFile serenityCannedJsonSummaryFile = new SerenityCannedJsonSummaryFile(); 47 | // build, listener, measurementRenderer, timestamp, jenkinsEnvParameterTag, customPrefix, 48 | SerenityPointGenerator serenityGen = new SerenityPointGenerator(build, listener, measurementRenderer, currTime, 49 | StringUtils.EMPTY, null, serenityCannedJsonSummaryFile); 50 | 51 | if (serenityGen.hasReport()) { 52 | List pointsToWrite = new ArrayList<>(Arrays.asList(serenityGen.generate())); 53 | // points.fields is private so just get all fields as a single string 54 | point = pointsToWrite.get(0); 55 | } 56 | } 57 | 58 | @Test 59 | void verifyResultsCounts() { 60 | assertTrue(allLineProtocolsContain(point, "serenity_results_counts_total=99")); 61 | assertTrue(allLineProtocolsContain(point, "serenity_results_counts_success=91")); 62 | assertTrue(allLineProtocolsContain(point, "serenity_results_counts_pending=8")); 63 | assertTrue(allLineProtocolsContain(point, "serenity_results_counts_error=0")); 64 | } 65 | 66 | @Test 67 | void verifyResultsPercentages() { 68 | assertTrue(allLineProtocolsContain(point, "serenity_results_percentages_success=92")); 69 | assertTrue(allLineProtocolsContain(point, "serenity_results_percentages_pending=8")); 70 | assertTrue(allLineProtocolsContain(point, "serenity_results_percentages_ignored=0")); 71 | } 72 | 73 | @Test 74 | void verifyResultsTimings() { 75 | assertTrue(allLineProtocolsContain(point, "serenity_results_max_test_duration=199957")); 76 | assertTrue(allLineProtocolsContain(point, "serenity_results_total_clock_duration=489836")); 77 | assertTrue(allLineProtocolsContain(point, "serenity_results_min_test_duration=1714")); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/MetricsPointGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Job; 4 | import hudson.model.Run; 5 | import hudson.model.TaskListener; 6 | import jenkins.metrics.impl.TimeInQueueAction; 7 | import jenkins.model.Jenkins; 8 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 9 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 10 | import org.apache.commons.lang.StringUtils; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import org.mockito.Mockito; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | 17 | class MetricsPointGeneratorTest extends PointGeneratorBaseTest { 18 | 19 | private static final String JOB_NAME = "master"; 20 | private static final int BUILD_NUMBER = 11; 21 | private static final String CUSTOM_PREFIX = "test_prefix"; 22 | 23 | private Run build; 24 | private TaskListener listener; 25 | private ProjectNameRenderer measurementRenderer; 26 | private TimeInQueueAction timeInQueueAction; 27 | 28 | private long currTime; 29 | 30 | @BeforeEach 31 | void before() { 32 | build = Mockito.mock(Run.class); 33 | Job job = Mockito.mock(Job.class); 34 | listener = Mockito.mock(TaskListener.class); 35 | measurementRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null); 36 | timeInQueueAction = Mockito.mock(TimeInQueueAction.class); 37 | 38 | Mockito.when(build.getNumber()).thenReturn(BUILD_NUMBER); 39 | Mockito.when(build.getParent()).thenReturn(job); 40 | Mockito.when(job.getName()).thenReturn(JOB_NAME); 41 | Mockito.when(build.getAction(TimeInQueueAction.class)).thenReturn(timeInQueueAction); 42 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + JOB_NAME); 43 | 44 | currTime = System.currentTimeMillis(); 45 | } 46 | 47 | @Test 48 | void measurement_successfully_generated() { 49 | Mockito.when(timeInQueueAction.getBlockedDurationMillis()).thenReturn((long) 10); 50 | Mockito.when(timeInQueueAction.getBuildableDurationMillis()).thenReturn((long) 20); 51 | Mockito.when(timeInQueueAction.getBuildingDurationMillis()).thenReturn((long) 30); 52 | Mockito.when(timeInQueueAction.getExecutingTimeMillis()).thenReturn((long) 40); 53 | Mockito.when(timeInQueueAction.getExecutorUtilization()).thenReturn(0.5); 54 | Mockito.when(timeInQueueAction.getQueuingDurationMillis()).thenReturn((long) 50); 55 | Mockito.when(timeInQueueAction.getSubTaskCount()).thenReturn(2); 56 | Mockito.when(timeInQueueAction.getTotalDurationMillis()).thenReturn((long) 60); 57 | Mockito.when(timeInQueueAction.getWaitingDurationMillis()).thenReturn((long) 70); 58 | 59 | MetricsPointGenerator generator = new MetricsPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX); 60 | AbstractPoint[] points = generator.generate(); 61 | 62 | assertTrue(allLineProtocolsContain(points[0], "blocked_time=10")); 63 | assertTrue(allLineProtocolsContain(points[0], "buildable_time=20")); 64 | assertTrue(allLineProtocolsContain(points[0], "building_time=30")); 65 | assertTrue(allLineProtocolsContain(points[0], "executing_time=40")); 66 | assertTrue(allLineProtocolsContain(points[0], "executor_utilization=0.5")); 67 | assertTrue(allLineProtocolsContain(points[0], "queue_time=50")); 68 | assertTrue(allLineProtocolsContain(points[0], "subtask_count=2")); 69 | assertTrue(allLineProtocolsContain(points[0], "total_duration=60")); 70 | assertTrue(allLineProtocolsContain(points[0], "waiting_time=70")); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/models/AbstractPoint.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.models; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.util.Map; 5 | 6 | public class AbstractPoint { 7 | private final com.influxdb.client.write.Point v1v2Point; 8 | private final com.influxdb.v3.client.Point v3Point; 9 | 10 | public AbstractPoint(@Nonnull String measurement) { 11 | this.v1v2Point = new com.influxdb.client.write.Point(measurement); 12 | this.v3Point = new com.influxdb.v3.client.Point(measurement); 13 | } 14 | 15 | public String getName() { 16 | String v1v2Name = this.v1v2Point.toLineProtocol().split(",")[0]; 17 | String v3Name = this.v3Point.toLineProtocol().split(",")[0]; 18 | if (!v1v2Name.equals(v3Name)) { 19 | throw new RuntimeException("V1V2 point name '%s' differs from V3 point name '%s'".formatted(v1v2Name, v3Name)); 20 | } 21 | return v1v2Name; 22 | } 23 | 24 | public com.influxdb.client.write.Point getV1v2Point() { 25 | return v1v2Point; 26 | } 27 | 28 | public com.influxdb.v3.client.Point getV3Point() { 29 | return v3Point; 30 | } 31 | 32 | public AbstractPoint addField(String field, boolean value) { 33 | this.v1v2Point.addField(field, value); 34 | this.v3Point.setField(field, value); 35 | return this; 36 | } 37 | 38 | public AbstractPoint addField(String field, int value) { 39 | this.v1v2Point.addField(field, value); 40 | this.v3Point.setField(field, value); 41 | return this; 42 | } 43 | 44 | public AbstractPoint addField(String field, long value) { 45 | this.v1v2Point.addField(field, value); 46 | this.v3Point.setField(field, value); 47 | return this; 48 | } 49 | 50 | public AbstractPoint addField(String field, double value) { 51 | this.v1v2Point.addField(field, value); 52 | this.v3Point.setField(field, value); 53 | return this; 54 | } 55 | 56 | public AbstractPoint addField(String field, Number value) { 57 | this.v1v2Point.addField(field, value); 58 | this.v3Point.setField(field, value); 59 | return this; 60 | } 61 | 62 | public AbstractPoint addField(String field, String value) { 63 | this.v1v2Point.addField(field, value); 64 | this.v3Point.setField(field, value); 65 | return this; 66 | } 67 | 68 | public AbstractPoint addFields(Map fields) { 69 | this.v1v2Point.addFields(fields); 70 | this.v3Point.setFields(fields); 71 | return this; 72 | } 73 | 74 | public AbstractPoint addTag(String name, String value) { 75 | this.v1v2Point.addTag(name, value); 76 | this.v3Point.setTag(name, value); 77 | return this; 78 | } 79 | 80 | public AbstractPoint addTags(Map tags) { 81 | this.v1v2Point.addTags(tags); 82 | this.v3Point.setTags(tags); 83 | return this; 84 | } 85 | 86 | public AbstractPoint time(long time, com.influxdb.v3.client.write.WritePrecision precision) { 87 | com.influxdb.client.domain.WritePrecision v1v2WritePrecision = 88 | com.influxdb.client.domain.WritePrecision.valueOf(precision.name()); 89 | this.v1v2Point.time(time, v1v2WritePrecision); 90 | this.v3Point.setTimestamp(time, precision); 91 | return this; 92 | } 93 | 94 | public AbstractPoint time(long time, com.influxdb.client.domain.WritePrecision precision) { 95 | com.influxdb.v3.client.write.WritePrecision v3WritePrecision = 96 | com.influxdb.v3.client.write.WritePrecision.valueOf(precision.name()); 97 | this.v1v2Point.time(time, precision); 98 | this.v3Point.setTimestamp(time, v3WritePrecision); 99 | return this; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/global/GlobalRunListener.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.global; 2 | 3 | import hudson.EnvVars; 4 | import hudson.Extension; 5 | import hudson.model.AbstractProject; 6 | import hudson.model.Job; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | import hudson.model.listeners.RunListener; 10 | import jenkins.model.Jenkins; 11 | import jenkinsci.plugins.influxdb.InfluxDbPublicationService; 12 | import jenkinsci.plugins.influxdb.InfluxDbPublisher; 13 | import jenkinsci.plugins.influxdb.models.Target; 14 | import org.apache.commons.lang3.StringUtils; 15 | 16 | import javax.annotation.Nonnull; 17 | import java.io.IOException; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.regex.Pattern; 21 | 22 | /** 23 | * Listens to all builds being completed and publishes their metrics to InfluxDB. 24 | */ 25 | @Extension 26 | public class GlobalRunListener extends RunListener> { 27 | 28 | private static final String VARIABLE_PREFIX = "INFLUXDB_PLUGIN_"; 29 | 30 | @Override 31 | public void onCompleted(Run build, @Nonnull TaskListener listener) { 32 | // Gets the full path of the build's project 33 | String path = build.getParent().getRelativeNameFrom(Jenkins.getInstanceOrNull()); 34 | // Gets the list of targets from the configuration 35 | Jenkins jenkins = Jenkins.getInstanceOrNull(); 36 | if (jenkins == null) { 37 | return; 38 | } 39 | List targets = jenkins.getDescriptorByType(InfluxDbPublisher.DescriptorImpl.class).getTargets(); 40 | // Selects the targets eligible as global listeners and which match the build path 41 | List selectedTargets = new ArrayList<>(); 42 | for (Target target : targets) { 43 | // Checks if the target matches the path to the project 44 | // Skip build if it already publishes information on this target 45 | if (isTargetMatchingPath(target, path) && !isPublicationInBuild(target, build)) { 46 | selectedTargets.add(target); 47 | } 48 | } 49 | // If some targets are selected 50 | if (!selectedTargets.isEmpty()) { 51 | 52 | EnvVars env; 53 | try { 54 | env = build.getEnvironment(listener); 55 | } catch (IOException | InterruptedException e) { 56 | env = new EnvVars(); 57 | } 58 | 59 | // Creates the publication service 60 | InfluxDbPublicationService publicationService = new InfluxDbPublicationService( 61 | selectedTargets, 62 | env.get(VARIABLE_PREFIX + "CUSTOM_PROJECT_NAME"), 63 | env.get(VARIABLE_PREFIX + "CUSTOM_PREFIX"), 64 | null, 65 | null, 66 | null, 67 | null, 68 | System.currentTimeMillis() * 1000000, 69 | env.expand(env.get(VARIABLE_PREFIX + "CUSTOM_FIELDS")), 70 | env.expand(env.get(VARIABLE_PREFIX + "CUSTOM_TAGS")), 71 | "jenkins_data" 72 | ); 73 | 74 | // Publication 75 | publicationService.perform(build, listener, env); 76 | } 77 | } 78 | 79 | private boolean isPublicationInBuild(Target target, Run build) { 80 | Job parent = build.getParent(); 81 | if (parent instanceof AbstractProject) { 82 | InfluxDbPublisher publisher = (InfluxDbPublisher) ((AbstractProject) parent).getPublishersList().get(InfluxDbPublisher.class); 83 | if (publisher != null) { 84 | String buildTarget = publisher.getSelectedTarget(); 85 | return buildTarget != null && StringUtils.equals(buildTarget, target.getDescription()); 86 | } 87 | } 88 | return false; 89 | } 90 | 91 | private boolean isTargetMatchingPath(@Nonnull Target target, @Nonnull String path) { 92 | if (target.isGlobalListener()) { 93 | String pattern = target.getGlobalListenerFilter(); 94 | return StringUtils.isBlank(pattern) || Pattern.matches(pattern, path); 95 | } 96 | return false; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/CustomDataPointGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Job; 4 | import hudson.model.Run; 5 | import hudson.model.TaskListener; 6 | import jenkins.model.Jenkins; 7 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 8 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 9 | import org.apache.commons.lang.StringUtils; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.mockito.Mockito; 13 | 14 | import java.util.Collections; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertFalse; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | 21 | class CustomDataPointGeneratorTest extends PointGeneratorBaseTest { 22 | 23 | private static final String JOB_NAME = "master"; 24 | private static final int BUILD_NUMBER = 11; 25 | private static final String CUSTOM_PREFIX = "test_prefix"; 26 | private static final String MEASUREMENT_NAME = "jenkins_data"; 27 | 28 | private Run build; 29 | private TaskListener listener; 30 | 31 | private ProjectNameRenderer measurementRenderer; 32 | 33 | private long currTime; 34 | 35 | @BeforeEach 36 | void before() { 37 | build = Mockito.mock(Run.class); 38 | Job job = Mockito.mock(Job.class); 39 | listener = Mockito.mock(TaskListener.class); 40 | measurementRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null); 41 | 42 | Mockito.when(build.getNumber()).thenReturn(BUILD_NUMBER); 43 | Mockito.doReturn(job).when(build).getParent(); 44 | Mockito.when(job.getName()).thenReturn(JOB_NAME); 45 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + JOB_NAME); 46 | 47 | currTime = System.currentTimeMillis(); 48 | } 49 | 50 | @Test 51 | void hasReport() { 52 | //check with customDataMap = null 53 | CustomDataPointGenerator cdGen1 = new CustomDataPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, null, null, MEASUREMENT_NAME); 54 | assertFalse(cdGen1.hasReport()); 55 | 56 | //check with empty customDataMap 57 | CustomDataPointGenerator cdGen2 = new CustomDataPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, Collections.emptyMap(), null, MEASUREMENT_NAME); 58 | assertFalse(cdGen2.hasReport()); 59 | } 60 | 61 | @Test 62 | void generate() { 63 | Map customData = new HashMap<>(); 64 | customData.put("test1", 11); 65 | customData.put("test2", 22); 66 | 67 | Map customDataTags = new HashMap<>(); 68 | customDataTags.put("tag1", "myTag"); 69 | CustomDataPointGenerator cdGen = new CustomDataPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, customData, customDataTags, MEASUREMENT_NAME); 70 | AbstractPoint[] points = cdGen.generate(); 71 | 72 | assertTrue(allLineProtocolsStartWith(points[0], "jenkins_custom_data,prefix=test_prefix,project_name=test_prefix_master,project_namespace=folder,project_path=folder/master,tag1=myTag build_number=11i,build_time=")); 73 | assertTrue(allLineProtocolsContain(points[0], "jenkins_custom_data,prefix=test_prefix,project_name=test_prefix_master,project_namespace=folder,project_path=folder/master,tag1=myTag build_number=11i")); 74 | } 75 | 76 | @Test 77 | void custom_measurement_included() { 78 | String customMeasurement = "custom_measurement"; 79 | Map customData = new HashMap<>(); 80 | customData.put("test1", 11); 81 | 82 | Map customDataTags = new HashMap<>(); 83 | customDataTags.put("tag1", "myTag"); 84 | 85 | CustomDataPointGenerator cdGen = new CustomDataPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, customData, customDataTags, customMeasurement); 86 | AbstractPoint[] pointsToWrite = cdGen.generate(); 87 | 88 | assertTrue(allLineProtocolsStartWith(pointsToWrite[0], "custom_" + customMeasurement)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/JUnitPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.EnvVars; 4 | import hudson.model.Run; 5 | import hudson.model.TaskListener; 6 | import hudson.tasks.junit.CaseResult; 7 | import hudson.tasks.test.AbstractTestResultAction; 8 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 9 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 10 | import org.apache.commons.collections.iterators.ReverseListIterator; 11 | import org.apache.commons.lang.StringUtils; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class JUnitPointGenerator extends AbstractPointGenerator { 17 | 18 | private static final String JUNIT_SUITE_NAME = "suite_name"; 19 | private static final String JUNIT_TEST_NAME = "test_name"; 20 | private static final String JUNIT_TEST_CLASS_FULL_NAME = "test_class_full_name"; 21 | private static final String JUNIT_PIPELINE_STEP = "pipeline_step"; 22 | private static final String JUNIT_TEST_STATUS = "test_status"; 23 | private static final String JUNIT_TEST_STATUS_ORDINAL = "test_status_ordinal"; 24 | private static final String JUNIT_DURATION = "test_duration"; 25 | private static final String JUNIT_COUNT = "test_count"; 26 | 27 | private final String customPrefix; 28 | private final TaskListener listener; 29 | 30 | private final EnvVars env; 31 | 32 | public JUnitPointGenerator(Run build, TaskListener listener, 33 | ProjectNameRenderer projectNameRenderer, 34 | long timestamp, String jenkinsEnvParameterTag, 35 | String customPrefix, EnvVars env) { 36 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 37 | this.customPrefix = customPrefix; 38 | this.listener = listener; 39 | this.env = env; 40 | } 41 | 42 | /** 43 | * @return true, if environment variable LOG_JUNIT_RESULTS is set to true and JUnit Reports exist 44 | */ 45 | @Override 46 | public boolean hasReport() { 47 | return Boolean.parseBoolean(env.getOrDefault("LOG_JUNIT_RESULTS", "false")) && hasTestResults(build); 48 | } 49 | 50 | private boolean hasTestResults(Run build) { 51 | return build.getAction(AbstractTestResultAction.class) != null; 52 | } 53 | 54 | @Override 55 | public AbstractPoint[] generate() { 56 | 57 | List points = new ArrayList<>(); 58 | 59 | // iterate each caseResult to get suiteName, testName and testStatus 60 | List allTestResults = getAllTestResults(build); 61 | 62 | for (CaseResult caseResult : allTestResults) { 63 | AbstractPoint point = buildPoint("junit_data", customPrefix, build) 64 | .addField(JUNIT_SUITE_NAME, caseResult.getSuiteResult().getName()) 65 | .addField(JUNIT_TEST_NAME, caseResult.getName()) 66 | .addField(JUNIT_TEST_CLASS_FULL_NAME, caseResult.getClassName()) 67 | .addField(JUNIT_PIPELINE_STEP, getCaseResultEnclosingFlowNodeString(caseResult)) 68 | .addField(JUNIT_TEST_STATUS, caseResult.getStatus().toString()) 69 | .addField(JUNIT_TEST_STATUS_ORDINAL, caseResult.getStatus().ordinal()) 70 | .addField(JUNIT_DURATION, caseResult.getDuration()) 71 | .addField(JUNIT_COUNT, 1L); 72 | points.add(point); 73 | } 74 | 75 | return points.toArray(new AbstractPoint[0]); 76 | } 77 | 78 | private List getAllTestResults(Run build) { 79 | //get tests from build 80 | AbstractTestResultAction testResultAction = build.getAction(AbstractTestResultAction.class); 81 | 82 | // create a list that contains all tests 83 | List allTestResults = new ArrayList<>(); 84 | allTestResults.addAll(testResultAction.getFailedTests()); 85 | allTestResults.addAll(testResultAction.getSkippedTests()); 86 | allTestResults.addAll(testResultAction.getPassedTests()); 87 | 88 | return allTestResults; 89 | } 90 | 91 | private String getCaseResultEnclosingFlowNodeString(CaseResult caseResult) { 92 | if (!caseResult.getEnclosingFlowNodeNames().isEmpty()) { 93 | return StringUtils.join(new ReverseListIterator(caseResult.getEnclosingFlowNodeNames()), " / "); 94 | } 95 | return ""; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/ChangeLogPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.Run; 5 | import hudson.model.TaskListener; 6 | import hudson.scm.ChangeLogSet; 7 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 8 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 9 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 10 | 11 | import java.util.Collection; 12 | import java.util.List; 13 | 14 | public class ChangeLogPointGenerator extends AbstractPointGenerator { 15 | 16 | private static final String BUILD_DISPLAY_NAME = "display_name"; 17 | 18 | private final String customPrefix; 19 | 20 | private StringBuilder affectedPaths; 21 | 22 | private StringBuilder messages; 23 | 24 | private StringBuilder culprits; 25 | 26 | private int commitCount = 0; 27 | 28 | public ChangeLogPointGenerator(Run build, TaskListener listener, 29 | ProjectNameRenderer projectNameRenderer, 30 | long timestamp, String jenkinsEnvParameterTag, 31 | String customPrefix) { 32 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 33 | this.customPrefix = customPrefix; 34 | this.affectedPaths = new StringBuilder(); 35 | this.messages = new StringBuilder(); 36 | this.culprits = new StringBuilder(); 37 | } 38 | 39 | public boolean hasReport() { 40 | if (build instanceof AbstractBuild) { // freestyle job 41 | getChangeLogFromAbstractBuild(build); 42 | } else if (build instanceof WorkflowRun) { // pipeline 43 | getChangeLogFromPipeline(build); 44 | } 45 | return this.getCommitCount() > 0; 46 | } 47 | 48 | public AbstractPoint[] generate() { 49 | AbstractPoint point = buildPoint("changelog_data", customPrefix, build); 50 | 51 | point.addField(BUILD_DISPLAY_NAME, build.getDisplayName()) 52 | .addField("commit_messages", this.getMessages()) 53 | .addField("culprits", this.getCulprits()) 54 | .addField("affected_paths", this.getAffectedPaths()) 55 | .addField("commit_count", this.getCommitCount()); 56 | 57 | return new AbstractPoint[]{point}; 58 | } 59 | 60 | private void getChangeLogFromAbstractBuild(Run run) { 61 | AbstractBuild abstractBuild = (AbstractBuild) run; 62 | ChangeLogSet changeset = abstractBuild.getChangeSet(); 63 | addChangeLogData(changeset); 64 | } 65 | 66 | private void getChangeLogFromPipeline(Run run) { 67 | WorkflowRun workflowRun = (WorkflowRun) run; 68 | List> changeLogsSets = workflowRun.getChangeSets(); 69 | for (ChangeLogSet changeLogSet : changeLogsSets) { 70 | addChangeLogData(changeLogSet); 71 | } 72 | } 73 | 74 | private void addChangeLogData(ChangeLogSet changeLogSet) { 75 | for (ChangeLogSet.Entry str : changeLogSet) { 76 | Collection affectedFiles = str.getAffectedFiles(); 77 | for (ChangeLogSet.AffectedFile affectedFile : affectedFiles) { 78 | this.affectedPaths.append(affectedFile.getPath()); 79 | this.affectedPaths.append(", "); 80 | } 81 | this.messages.append(str.getMsg()); 82 | this.messages.append(", "); 83 | 84 | this.culprits.append(str.getAuthor().getFullName()); 85 | this.culprits.append(", "); 86 | 87 | this.commitCount += 1; 88 | } 89 | } 90 | 91 | private String getMessages() { 92 | return this.messages.length() > 0 ? this.messages.substring(0, this.messages.length() - 2) : ""; 93 | } 94 | 95 | private String getCulprits() { 96 | return this.culprits.length() > 0 ? this.culprits.substring(0, this.culprits.length() - 2) : ""; 97 | } 98 | 99 | private String getAffectedPaths() { 100 | return this.affectedPaths.length() > 0 ? this.affectedPaths.substring(0, this.affectedPaths.length() - 2) : ""; 101 | } 102 | 103 | private int getCommitCount() { 104 | return this.commitCount; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/AbstractPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import com.influxdb.client.domain.WritePrecision; 4 | import hudson.EnvVars; 5 | import hudson.model.Run; 6 | import hudson.model.TaskListener; 7 | import jenkins.model.Jenkins; 8 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 9 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.apache.commons.text.StringSubstitutor; 12 | 13 | import java.io.IOException; 14 | import java.io.StringReader; 15 | import java.util.Map; 16 | import java.util.Objects; 17 | import java.util.Properties; 18 | import java.util.stream.Collectors; 19 | 20 | public abstract class AbstractPointGenerator implements PointGenerator { 21 | 22 | public static final String PROJECT_NAMESPACE = "project_namespace"; 23 | public static final String PROJECT_NAME = "project_name"; 24 | public static final String PROJECT_PATH = "project_path"; 25 | public static final String INSTANCE = "instance"; 26 | public static final String BUILD_NUMBER = "build_number"; 27 | public static final String CUSTOM_PREFIX = "prefix"; 28 | 29 | protected final long timestamp; 30 | protected final Run build; 31 | protected final TaskListener listener; 32 | private final ProjectNameRenderer projectNameRenderer; 33 | private final String jenkinsEnvParameterTag; 34 | private final WritePrecision precision = WritePrecision.NS; 35 | 36 | public AbstractPointGenerator(Run build, TaskListener listener, ProjectNameRenderer projectNameRenderer, long timestamp, String jenkinsEnvParameterTag) { 37 | this.build = build; 38 | this.listener = listener; 39 | this.projectNameRenderer = Objects.requireNonNull(projectNameRenderer); 40 | this.timestamp = timestamp; 41 | this.jenkinsEnvParameterTag = jenkinsEnvParameterTag; 42 | } 43 | 44 | public AbstractPoint buildPoint(String name, String customPrefix, Run build) { 45 | return buildPoint(name, customPrefix, build, timestamp); 46 | } 47 | 48 | @Override 49 | public AbstractPoint buildPoint(String name, String customPrefix, Run build, long timestamp) { 50 | Jenkins instance = Jenkins.getInstanceOrNull(); 51 | String projectName = projectNameRenderer.render(build); 52 | String projectPath = build.getParent().getRelativeNameFrom(instance); 53 | AbstractPoint point = new AbstractPoint(name) 54 | .addField(BUILD_NUMBER, build.getNumber()) 55 | .time(timestamp, precision); 56 | 57 | if (customPrefix != null && !customPrefix.isEmpty()) { 58 | point.addTag(CUSTOM_PREFIX, customPrefix); 59 | } 60 | 61 | point.addTag(PROJECT_NAME, projectName); 62 | point.addTag(PROJECT_PATH, projectPath); 63 | point.addTag(INSTANCE, instance != null ? instance.getRootUrl() : ""); 64 | point.addTag(PROJECT_NAMESPACE, projectPath.split("/")[0]); 65 | 66 | 67 | if (StringUtils.isNotBlank(jenkinsEnvParameterTag)) { 68 | Properties tagProperties = parsePropertiesString(jenkinsEnvParameterTag); 69 | Map tagMap = resolveEnvParameterAndTransformToMap(tagProperties); 70 | point.addTags(tagMap); 71 | } 72 | 73 | return point; 74 | } 75 | 76 | protected Properties parsePropertiesString(String propertiesString) { 77 | Properties properties = new Properties(); 78 | try { 79 | StringReader reader = new StringReader(propertiesString); 80 | properties.load(reader); 81 | } catch (IOException e) { 82 | e.printStackTrace(); 83 | } 84 | return properties; 85 | } 86 | 87 | protected Map resolveEnvParameterAndTransformToMap(Properties properties) { 88 | return properties.entrySet().stream().collect( 89 | Collectors.toMap( 90 | e -> e.getKey().toString(), 91 | e -> { 92 | String value = e.getValue().toString(); 93 | return containsEnvParameter(value) ? resolveEnvParameter(value) : value; 94 | } 95 | ) 96 | ); 97 | } 98 | 99 | private boolean containsEnvParameter(String value) { 100 | return StringUtils.length(value) > 3 && StringUtils.contains(value, "${"); 101 | } 102 | 103 | private String resolveEnvParameter(String stringValue) { 104 | try { 105 | EnvVars envVars = build.getEnvironment(listener); 106 | return StringSubstitutor.replace(stringValue, envVars); 107 | } catch (IOException | InterruptedException e) { 108 | e.printStackTrace(); 109 | } 110 | return stringValue; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/GitPointGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.HealthReport; 4 | import hudson.model.Job; 5 | import hudson.model.Run; 6 | import hudson.model.TaskListener; 7 | import hudson.plugins.git.Branch; 8 | import hudson.plugins.git.Revision; 9 | import hudson.plugins.git.util.BuildData; 10 | import jenkins.model.Jenkins; 11 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 12 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | import org.mockito.Mockito; 17 | 18 | import java.util.*; 19 | 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | /** 23 | * @author Mathieu Delrocq 24 | */ 25 | class GitPointGeneratorTest extends PointGeneratorBaseTest { 26 | 27 | private static final String CUSTOM_PREFIX = "test_prefix"; 28 | private static final String JOB_NAME = "job_name"; 29 | private static final String GIT_REPOSITORY = "repository"; 30 | private static final String GIT_REFERENCE = "reference"; 31 | private static final String GIT_REVISION = "revision"; 32 | 33 | private Run build; 34 | private List gitActions; 35 | private BuildData gitAction1; 36 | private BuildData gitAction2; 37 | private TaskListener listener; 38 | private long currTime; 39 | private ProjectNameRenderer measurementRenderer; 40 | private Revision revision1; 41 | private Revision revision2; 42 | private Branch branch1; 43 | private Branch branch2; 44 | 45 | @BeforeEach 46 | void before() { 47 | // Global Mocks 48 | listener = Mockito.mock(TaskListener.class); 49 | currTime = System.currentTimeMillis(); 50 | measurementRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null); 51 | Job job = Mockito.mock(Job.class); 52 | Mockito.when(job.getName()).thenReturn(JOB_NAME); 53 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + JOB_NAME); 54 | Mockito.when(job.getBuildHealth()).thenReturn(new HealthReport()); 55 | 56 | build = Mockito.mock(Run.class); 57 | Mockito.doReturn(job).when(build).getParent(); 58 | gitAction1 = Mockito.mock(BuildData.class); 59 | gitAction2 = Mockito.mock(BuildData.class); 60 | gitActions = new ArrayList<>(); 61 | gitActions.add(gitAction1); 62 | gitActions.add(gitAction2); 63 | branch1 = Mockito.mock(Branch.class); 64 | branch2 = Mockito.mock(Branch.class); 65 | revision1 = Mockito.mock(Revision.class); 66 | revision2 = Mockito.mock(Revision.class); 67 | Mockito.when(build.getActions(BuildData.class)).thenReturn(gitActions); 68 | Mockito.when(gitAction1.getLastBuiltRevision()).thenReturn(revision1); 69 | Mockito.when(gitAction2.getLastBuiltRevision()).thenReturn(revision2); 70 | List branches1 = Collections.singletonList(branch1); 71 | List branches2 = Collections.singletonList(branch2); 72 | Mockito.when(revision1.getBranches()).thenReturn(branches1); 73 | Mockito.when(revision2.getBranches()).thenReturn(branches2); 74 | Mockito.when(branch1.getName()).thenReturn(GIT_REFERENCE); 75 | Mockito.when(branch2.getName()).thenReturn(GIT_REFERENCE + "2"); 76 | Mockito.when(revision1.getSha1String()).thenReturn(GIT_REVISION); 77 | Mockito.when(revision2.getSha1String()).thenReturn(GIT_REVISION + "2"); 78 | Set remoteUrls1 = new HashSet<>(); 79 | remoteUrls1.add(GIT_REPOSITORY); 80 | Set remoteUrls2 = new HashSet<>(); 81 | remoteUrls2.add(GIT_REPOSITORY + "2"); 82 | Mockito.when(gitAction1.getRemoteUrls()).thenReturn(remoteUrls1); 83 | Mockito.when(gitAction2.getRemoteUrls()).thenReturn(remoteUrls2); 84 | } 85 | 86 | @Test 87 | void test_with_datas() { 88 | GitPointGenerator gen = new GitPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, 89 | CUSTOM_PREFIX); 90 | assertTrue(gen.hasReport()); 91 | AbstractPoint[] points = gen.generate(); 92 | assertTrue(points != null && points.length != 0); 93 | assertTrue(points[0].getV1v2Point().hasFields()); 94 | assertTrue(allLineProtocolsContain(points[0], "git_repository=\"repository\"")); 95 | assertTrue(allLineProtocolsContain(points[0], "git_revision=\"revision\"")); 96 | assertTrue(allLineProtocolsContain(points[0], "git_reference=\"reference\"")); 97 | assertTrue(allLineProtocolsContain(points[1], "git_repository=\"repository2\"")); 98 | assertTrue(allLineProtocolsContain(points[1], "git_revision=\"revision2\"")); 99 | assertTrue(allLineProtocolsContain(points[1], "git_reference=\"reference2\"")); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/CustomDataMapPointGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Job; 4 | import hudson.model.Run; 5 | import hudson.model.TaskListener; 6 | import jenkins.model.Jenkins; 7 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 8 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 9 | import org.apache.commons.lang.StringUtils; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.mockito.Mockito; 13 | 14 | import java.util.Collections; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.TreeMap; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertFalse; 21 | 22 | class CustomDataMapPointGeneratorTest extends PointGeneratorBaseTest { 23 | 24 | private static final String JOB_NAME = "master"; 25 | private static final int BUILD_NUMBER = 11; 26 | private static final String CUSTOM_PREFIX = "test_prefix"; 27 | 28 | private Run build; 29 | private TaskListener listener; 30 | 31 | private ProjectNameRenderer measurementRenderer; 32 | 33 | private long currTime; 34 | 35 | @BeforeEach 36 | void before() { 37 | build = Mockito.mock(Run.class); 38 | Job job = Mockito.mock(Job.class); 39 | listener = Mockito.mock(TaskListener.class); 40 | measurementRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null); 41 | 42 | Mockito.when(build.getNumber()).thenReturn(BUILD_NUMBER); 43 | Mockito.doReturn(job).when(build).getParent(); 44 | Mockito.when(job.getName()).thenReturn(JOB_NAME); 45 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + JOB_NAME); 46 | 47 | currTime = System.currentTimeMillis(); 48 | } 49 | 50 | @Test 51 | void hasReport() { 52 | //check with customDataMap = null 53 | 54 | CustomDataMapPointGenerator cdmGen1 = new CustomDataMapPointGenerator(build, listener, measurementRenderer, 55 | currTime, StringUtils.EMPTY, CUSTOM_PREFIX, null, null); 56 | assertFalse(cdmGen1.hasReport()); 57 | 58 | //check with empty customDataMap 59 | CustomDataMapPointGenerator cdmGen2 = new CustomDataMapPointGenerator(build, listener, measurementRenderer, 60 | currTime, StringUtils.EMPTY, CUSTOM_PREFIX, Collections.emptyMap(), Collections.emptyMap()); 61 | assertFalse(cdmGen2.hasReport()); 62 | } 63 | 64 | @Test 65 | void generate() throws NoSuchFieldException, IllegalAccessException { 66 | Map customData1 = new HashMap<>(); 67 | customData1.put("test1", 11); 68 | customData1.put("test2", 22); 69 | 70 | Map customData2 = new HashMap<>(); 71 | customData2.put("test3", 33); 72 | customData2.put("test4", 44); 73 | 74 | Map> customDataMap = new HashMap<>(); 75 | customDataMap.put("series1", customData1); 76 | customDataMap.put("series2", customData2); 77 | 78 | Map> customDataMapTags = new HashMap<>(); 79 | Map customTags = new HashMap<>(); 80 | customTags.put("build_result", "SUCCESS"); 81 | customDataMapTags.put("series1", customTags); 82 | 83 | CustomDataMapPointGenerator cdmGen = new CustomDataMapPointGenerator(build, listener, measurementRenderer, 84 | currTime, StringUtils.EMPTY, CUSTOM_PREFIX, customDataMap, customDataMapTags); 85 | AbstractPoint[] pointsToWrite = cdmGen.generate(); 86 | 87 | assertEquals("series2", pointsToWrite[0].getName()); 88 | assertEquals("series1", pointsToWrite[1].getName()); 89 | 90 | AbstractPoint p1 = pointsToWrite[1]; 91 | AbstractPoint p2 = pointsToWrite[0]; 92 | 93 | TreeMap expectedFieldsP1 = new TreeMap<>(Map.of( 94 | "build_number", 11, 95 | "test1", 11, 96 | "test2", 22 97 | )); 98 | TreeMap expectedTagsP1 = new TreeMap<>(Map.of( 99 | "build_result", "SUCCESS", 100 | "instance", "", 101 | "prefix", "test_prefix", 102 | "project_name", "test_prefix_master", 103 | "project_namespace", "folder", 104 | "project_path", "folder/master" 105 | )); 106 | checkPointTagsAndFields(p1, expectedFieldsP1, expectedTagsP1); 107 | 108 | TreeMap expectedFieldsP2 = new TreeMap<>(Map.of( 109 | "build_number", 11, 110 | "test3", 33, 111 | "test4", 44 112 | )); 113 | 114 | TreeMap expectedTagsP2 = new TreeMap<>(Map.of( 115 | "instance", "", 116 | "prefix", "test_prefix", 117 | "project_name", "test_prefix_master", 118 | "project_namespace", "folder", 119 | "project_path", "folder/master" 120 | )); 121 | checkPointTagsAndFields(p2, expectedFieldsP2, expectedTagsP2); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/PerfPublisherPointGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Job; 4 | import hudson.model.Run; 5 | import hudson.model.TaskListener; 6 | import hudson.plugins.PerfPublisher.PerfPublisherBuildAction; 7 | import hudson.plugins.PerfPublisher.Report.Metric; 8 | import hudson.plugins.PerfPublisher.Report.Report; 9 | import hudson.plugins.PerfPublisher.Report.ReportContainer; 10 | import jenkins.model.Jenkins; 11 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 12 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 13 | import org.apache.commons.lang.StringUtils; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | import org.mockito.Mockito; 17 | import org.mockito.stubbing.Answer; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.TreeMap; 22 | 23 | import static org.junit.jupiter.api.Assertions.*; 24 | 25 | /** 26 | * @author Eugene Schava 27 | */ 28 | class PerfPublisherPointGeneratorTest extends PointGeneratorBaseTest { 29 | 30 | private static final String JOB_NAME = "master"; 31 | private static final int BUILD_NUMBER = 11; 32 | private static final String CUSTOM_PREFIX = "test_prefix"; 33 | 34 | private Run build; 35 | private TaskListener listener; 36 | private ProjectNameRenderer measurementRenderer; 37 | private ReportContainer reports; 38 | 39 | private long currTime; 40 | 41 | @BeforeEach 42 | void before() { 43 | build = Mockito.mock(Run.class); 44 | Job job = Mockito.mock(Job.class); 45 | listener = Mockito.mock(TaskListener.class); 46 | measurementRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null); 47 | PerfPublisherBuildAction buildAction = Mockito.mock(PerfPublisherBuildAction.class); 48 | reports = new ReportContainer(); 49 | 50 | Mockito.when(build.getNumber()).thenReturn(BUILD_NUMBER); 51 | Mockito.doReturn(job).when(build).getParent(); 52 | Mockito.when(job.getName()).thenReturn(JOB_NAME); 53 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + JOB_NAME); 54 | Mockito.when(build.getAction(PerfPublisherBuildAction.class)).thenReturn(buildAction); 55 | 56 | Mockito.when(buildAction.getReport()).thenAnswer((Answer) invocationOnMock -> reports.getReports().isEmpty() ? null : reports.getReports().get(0)); 57 | Mockito.when(buildAction.getReports()).thenReturn(reports); 58 | 59 | currTime = System.currentTimeMillis(); 60 | } 61 | 62 | @Test 63 | void hasReport() { 64 | PerfPublisherPointGenerator generator = new PerfPublisherPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX); 65 | assertFalse(generator.hasReport()); 66 | 67 | reports.addReport(new Report()); 68 | generator = new PerfPublisherPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX); 69 | assertTrue(generator.hasReport()); 70 | } 71 | 72 | @Test 73 | void generate() throws Exception { 74 | Report report = new Report(); 75 | 76 | hudson.plugins.PerfPublisher.Report.Test test = new hudson.plugins.PerfPublisher.Report.Test(); 77 | test.setName("test.txt"); 78 | test.setExecuted(true); 79 | 80 | Map metrics = new HashMap<>(); 81 | Metric metric1 = new Metric(); 82 | metric1.setMeasure(50); 83 | metric1.setRelevant(true); 84 | metric1.setUnit("ms"); 85 | metrics.put("metric1", metric1); 86 | test.setMetrics(metrics); 87 | 88 | report.addTest(test); 89 | reports.addReport(report); 90 | PerfPublisherPointGenerator generator = new PerfPublisherPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX); 91 | AbstractPoint[] points = generator.generate(); 92 | 93 | // Check point/table names 94 | assertEquals("perfpublisher_summary", points[0].getName()); 95 | assertEquals("perfpublisher_metric", points[1].getName()); 96 | assertEquals("perfpublisher_test", points[2].getName()); 97 | assertEquals("perfpublisher_test_metric", points[3].getName()); 98 | 99 | TreeMap p1Fields = getPointFields(points[0]); 100 | assertEquals(1L, p1Fields.get("number_of_executed_tests")); 101 | 102 | TreeMap p2Fields = getPointFields(points[1]); 103 | assertEquals(50.0, p2Fields.get("average")); 104 | assertEquals(50.0, p2Fields.get("best")); 105 | assertEquals(50.0, p2Fields.get("worst")); 106 | assertEquals("metric1", p2Fields.get("metric_name")); 107 | assertEquals(50.0, p2Fields.get("average")); 108 | 109 | TreeMap p3Fields = getPointFields(points[2]); 110 | assertEquals("test.txt", p3Fields.get("test_name")); 111 | assertEquals(true, p3Fields.get("executed")); 112 | 113 | TreeMap p4Fields = getPointFields(points[3]); 114 | assertEquals("test.txt", p4Fields.get("test_name")); 115 | assertEquals(true, p4Fields.get("relevant")); 116 | assertEquals("ms", p4Fields.get("unit")); 117 | assertEquals(50.0, p4Fields.get("value")); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/AgentPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.Node; 5 | import hudson.model.Run; 6 | import hudson.model.TaskListener; 7 | import hudson.model.labels.LabelAtom; 8 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 9 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 10 | import org.apache.commons.collections.CollectionUtils; 11 | import org.jenkinsci.plugins.workflow.actions.WorkspaceAction; 12 | import org.jenkinsci.plugins.workflow.flow.FlowExecution; 13 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 14 | import org.jenkinsci.plugins.workflow.graph.FlowGraphWalker; 15 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 16 | 17 | import java.util.*; 18 | 19 | 20 | /** 21 | * @author Mathieu Delrocq 22 | */ 23 | public class AgentPointGenerator extends AbstractPointGenerator { 24 | 25 | protected static final String AGENT_NAME = "agent_name"; 26 | protected static final String AGENT_LABEL = "agent_label"; 27 | protected static final String UNIQUE_ID = "unique_id"; 28 | 29 | private List> agentPoints; 30 | private String customPrefix; 31 | 32 | public AgentPointGenerator(Run build, TaskListener listener, ProjectNameRenderer projectNameRenderer, 33 | long timestamp, String jenkinsEnvParameterTag, String customPrefix) { 34 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 35 | this.agentPoints = getAgentPoints(build); 36 | this.customPrefix = customPrefix; 37 | } 38 | 39 | @Override 40 | public boolean hasReport() { 41 | return CollectionUtils.isNotEmpty(agentPoints); 42 | } 43 | 44 | @Override 45 | public AbstractPoint[] generate() { 46 | List points = new ArrayList<>(); 47 | Map.Entry agentPoint = null; 48 | for (int i = 0; i < agentPoints.size(); i++) { 49 | agentPoint = agentPoints.get(i); 50 | AbstractPoint point = buildPoint("agent_data", customPrefix, build)// 51 | .addTag(UNIQUE_ID, String.valueOf(i + 1))// 52 | .addField(AGENT_NAME, agentPoint.getKey())// 53 | .addField(AGENT_LABEL, agentPoint.getValue()); 54 | points.add(point); 55 | } 56 | return points.toArray(new AbstractPoint[0]); 57 | } 58 | 59 | public String getFirstAgent() { 60 | return !CollectionUtils.isEmpty(agentPoints) ? agentPoints.get(0).getKey() : ""; 61 | } 62 | 63 | /** 64 | * Retrieve agent(s) used by the build and return {@link AgentPoint} 65 | * 66 | * @param build 67 | * @return list of {@link AgentPoint} 68 | */ 69 | private List> getAgentPoints(Run build) { 70 | if (build instanceof AbstractBuild) { 71 | return getAgentFromAbstractBuild((AbstractBuild) build); 72 | } else if (build instanceof FlowExecutionOwner.Executable) { 73 | return getAgentsFromPipeline((FlowExecutionOwner.Executable) build); 74 | } 75 | return new ArrayList<>(); 76 | } 77 | 78 | /** 79 | * Retrieve agent(s) for traditional jobs 80 | * 81 | * @param build 82 | * @return list of {@link AgentPoint} 83 | */ 84 | private List> getAgentFromAbstractBuild(AbstractBuild build) { 85 | List> agentPointsList = new ArrayList<>(); 86 | Node node = build.getBuiltOn(); 87 | if (node != null) { 88 | agentPointsList 89 | .add(new AbstractMap.SimpleEntry<>(node.getDisplayName(), node.getLabelString())); 90 | } 91 | return agentPointsList; 92 | } 93 | 94 | /** 95 | * Retrieve agent(s) for pipeline jobs 96 | * 97 | * @param build 98 | * @return list of {@link AgentPoint} 99 | */ 100 | private List> getAgentsFromPipeline(FlowExecutionOwner.Executable build) { 101 | List> agentPointsList = new ArrayList<>(); 102 | FlowExecutionOwner flowExecutionOwner = build.asFlowExecutionOwner(); 103 | if (flowExecutionOwner != null) { 104 | FlowExecution flowExecution = flowExecutionOwner.getOrNull(); 105 | if (flowExecution != null) { 106 | FlowGraphWalker graphWalker = new FlowGraphWalker(flowExecution); 107 | for (FlowNode flowNode : graphWalker) { 108 | WorkspaceAction workspaceAction = flowNode.getAction(WorkspaceAction.class); 109 | if (null != workspaceAction) { 110 | Set labels = workspaceAction.getLabels(); 111 | StringJoiner labelString = new StringJoiner(", "); 112 | labelString.setEmptyValue(""); 113 | for (LabelAtom label : labels) { 114 | labelString.add(label.getName()); 115 | } 116 | String nodeName = workspaceAction.getNode(); 117 | agentPointsList 118 | .add(new AbstractMap.SimpleEntry<>(nodeName, labelString.toString())); 119 | } 120 | } 121 | } 122 | } 123 | return agentPointsList; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb; 2 | 3 | import hudson.model.FreeStyleBuild; 4 | import hudson.model.FreeStyleProject; 5 | import hudson.model.Result; 6 | import hudson.tasks.Shell; 7 | import jenkinsci.plugins.influxdb.models.Target; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.condition.DisabledOnOs; 10 | import org.junit.jupiter.api.condition.OS; 11 | 12 | import java.util.*; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | 17 | @DisabledOnOs(OS.WINDOWS) // Docker is not available on Jenkins CI windows build agents 18 | public class IntegrationTest extends IntegrationBaseTest { 19 | @Test 20 | public void testInfluxDBTargetsAreAvailable() { 21 | InfluxDbGlobalConfig globalConfig = InfluxDbGlobalConfig.getInstance(); 22 | List targets = globalConfig.getTargets(); 23 | assertEquals(3, targets.size()); 24 | assertTrue(targets.get(0).isGlobalListener()); 25 | assertTrue(targets.get(1).isGlobalListener()); 26 | assertTrue(targets.get(2).isGlobalListener()); 27 | assertEquals(testEnv.get("INFLUXDB_V1_URL"), targets.get(0).getUrl()); 28 | assertEquals(testEnv.get("INFLUXDB_V2_URL"), targets.get(1).getUrl()); 29 | assertEquals(testEnv.get("INFLUXDB_V3_URL"), targets.get(2).getUrl()); 30 | } 31 | 32 | @Test 33 | public void testInfluxDBReporting() throws Exception { 34 | FreeStyleProject project = jenkinsRule.createFreeStyleProject("influxdb-test-job"); 35 | project.getBuildersList().add(new Shell("echo 'Hello from Integration Tests!'")); 36 | FreeStyleBuild build = project.scheduleBuild2(0).get(); 37 | 38 | jenkinsRule.assertBuildStatus(Result.SUCCESS, build); 39 | List>>> influxData = queryAllInfluxInstances(); 40 | Set ignoreKeys = new HashSet<>(Arrays.asList("_time", "_start", "_stop", "result", "_measurement", "table")); 41 | Map>> expectedValues = Map.ofEntries( 42 | Map.entry("jenkins_data", List.of(Map.ofEntries( 43 | Map.entry("build_result", "SUCCESS"), 44 | Map.entry("instance", DONT_CARE), 45 | Map.entry("project_name", "influxdb-test-job"), 46 | Map.entry("project_namespace", "influxdb-test-job"), 47 | Map.entry("project_path", "influxdb-test-job"), 48 | Map.entry("build_agent_name", "built-in"), 49 | Map.entry("build_branch_name", DONT_CARE), 50 | Map.entry("build_cause", "LegacyCodeCause"), 51 | Map.entry("build_causer", "Legacy code started this job. No cause information is available"), 52 | Map.entry("build_exec_time", greaterThan(0)), 53 | Map.entry("build_measured_time", greaterThan(0)), 54 | Map.entry("build_number", 1L), 55 | Map.entry("build_result_ordinal", 0L), 56 | Map.entry("build_scheduled_time", greaterThanOrEqualTo(0)), 57 | Map.entry("build_status_message", "stable"), 58 | Map.entry("build_successful", true), 59 | Map.entry("build_time", greaterThan(0)), 60 | Map.entry("build_user", DONT_CARE), 61 | Map.entry("last_stable_build", 1L), 62 | Map.entry("last_successful_build", 1L), 63 | Map.entry("project_build_health", 100L), 64 | Map.entry("time_in_queue", greaterThanOrEqualTo(0)) 65 | ))), 66 | Map.entry("agent_data", List.of(Map.ofEntries( 67 | Map.entry("instance", DONT_CARE), 68 | Map.entry("project_name", "influxdb-test-job"), 69 | Map.entry("project_namespace", "influxdb-test-job"), 70 | Map.entry("project_path", "influxdb-test-job"), 71 | Map.entry("unique_id", "1"), 72 | Map.entry("agent_label", DONT_CARE), 73 | Map.entry("agent_name", "Jenkins"), 74 | Map.entry("build_number", 1L) 75 | ))), 76 | Map.entry("metrics_data", List.of(Map.ofEntries( 77 | Map.entry("instance", DONT_CARE), 78 | Map.entry("project_name", "influxdb-test-job"), 79 | Map.entry("project_namespace", "influxdb-test-job"), 80 | Map.entry("project_path", "influxdb-test-job"), 81 | Map.entry("blocked_time", greaterThanOrEqualTo(0)), 82 | Map.entry("build_number", 1L), 83 | Map.entry("buildable_time", greaterThan(0)), 84 | Map.entry("building_time", greaterThan(0)), 85 | Map.entry("executing_time", greaterThan(0)), 86 | Map.entry("executor_utilization", greaterThan(0)), 87 | Map.entry("queue_time", greaterThanOrEqualTo(0)), 88 | Map.entry("subtask_count", greaterThanOrEqualTo(0)), 89 | Map.entry("total_duration", greaterThan(0)), 90 | Map.entry("waiting_time", greaterThanOrEqualTo(0)) 91 | ))) 92 | ); 93 | 94 | this.assertInfluxRecordsAreIdentical(influxData.get(0), expectedValues, ignoreKeys); 95 | this.assertInfluxRecordsAreIdentical(influxData.get(1), expectedValues, ignoreKeys); 96 | this.assertInfluxRecordsAreIdentical(influxData.get(2), expectedValues, ignoreKeys); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /doc/SonarQube_integration.md: -------------------------------------------------------------------------------- 1 | # SonarQube API integration 2 | 3 | ## Summary 4 | A short guideline on integrating Jenkins with SonarQube and verifying their integration. 5 | 6 | The SonarQubePointGenerator is expecting to find a sonar build report (report-task.txt) created by the scanner with the following content: 7 | ``` 8 | file: ${WORKSPACE}/**/sonar/report-task.txt 9 | 10 | projectKey=com.tom:sonarqube-jacoco-code-coverage 11 | serverUrl=http://localhost:9000 12 | serverVersion=8.9.3.48735 13 | dashboardUrl=http://localhost:9000/dashboard?id=com.tom%3Asonarqube-jacoco-code-coverage 14 | ceTaskId=AX0vnvr4_QGKX8b7Yz_v 15 | ceTaskUrl=http://localhost:9000/api/ce/task?id=AX0vnvr4_QGKX8b7Yz_v 16 | ``` 17 | 18 | The actual location of the report file in the workspace depends on the build system used - Maven, Gradle, etc. 19 | 20 | If, for whatever reason, a report file is created with a different name, the `SONARQUBE_BUILD_REPORT_NAME` env var 21 | could be used to specify either the file name or the path pattern ending with the file name. 22 | 23 | Examples: 24 | ``` 25 | stage("InfluxDB v2 publisher") { 26 | environment { 27 | SONARQUBE_BUILD_REPORT_NAME="custom-report.txt" 28 | # SONARQUBE_BUILD_REPORT_NAME="path/custom-report.txt" 29 | } 30 | 31 | steps { 32 | withSonarQubeEnv('SonarQube') { 33 | influxDbPublisher(selectedTarget: 'influxdb_v2',) 34 | } 35 | } 36 | } 37 | ``` 38 | 39 | The information extracted fron this file is used to query about SQ issues, measures and task status. 40 | 41 | # References 42 | 1. [CloudBees Video on SonarQube integration with Jenkins](https://www.youtube.com/watch?v=KsTMy0920go) 43 | 2. SonarQube WEB API: https://sonarcloud.io/web_api/ 44 | 45 | ## Verification 46 | The plugin is using the following API calls 47 | 1. [Issues Search](https://sonarcloud.io/web_api/api/issues/search) 48 | 2. [Measures Search Histhory](https://sonarcloud.io/web_api/api/measures/search_history) 49 | 3. [Task CE status](https://sonarcloud.io/web_api/api/ce/task) 50 | 51 | After seetting up a security token in SonarQube use the following curl calls to verify manually the API integration 52 | with SonarQube. 53 | 54 | ### Measures Search Histhory 55 | export TOKEN=**************** 56 | curl -s -G -u ${TOKEN}: \ 57 | --data-urlencode "componentKey=com.tom:sonarqube-jacoco-code-coverage" \ 58 | --data-urlencode "component=com.tom:sonarqube-jacoco-code-coverage" \ 59 | --data-urlencode "metricKeys=ncloc" \ 60 | http://localhost:9000/api/measures/component | jq . 61 | ``` 62 | { 63 | "component": { 64 | "key": "com.tom:sonarqube-jacoco-code-coverage", 65 | "name": "sonarqube-jacoco-code-coverage", 66 | "qualifier": "TRK", 67 | "measures": [ 68 | { 69 | "metric": "ncloc", 70 | "value": "9" 71 | } 72 | ] 73 | } 74 | } 75 | ``` 76 | 77 | ### Issues Search 78 | export TOKEN=**************** 79 | curl -s -G -u ${TOKEN}: \ 80 | --data-urlencode "componentKeys=com.tom:sonarqube-jacoco-code-coverage" \ 81 | --data-urlencode "resolved=false" \ 82 | --data-urlencode "severities=INFO" \ 83 | http://localhost:9000/api/issues/search?ps=1 | jq . 84 | 85 | ``` 86 | { 87 | "total": 0, 88 | "p": 1, 89 | "ps": 1, 90 | "paging": { 91 | "pageIndex": 1, 92 | "pageSize": 1, 93 | "total": 0 94 | }, 95 | "effortTotal": 0, 96 | "issues": [], 97 | "components": [], 98 | "facets": [] 99 | } 100 | ``` 101 | 102 | ### Task status 103 | export TOKEN=**************** 104 | curl -s -G -u ${TOKEN}: \ 105 | --data-urlencode "id=com.tom:sonarqube-jacoco-code-coverage" \ 106 | http://localhost:9000/api/ce/task?id=AX0vnvr4_QGKX8b7Yz_v | jq . 107 | ``` 108 | { 109 | "task": { 110 | "id": "AX0vnvr4_QGKX8b7Yz_v", 111 | "type": "REPORT", 112 | "componentId": "AX0m-2Bde0ytHUET2DEl", 113 | "componentKey": "com.tom:sonarqube-jacoco-code-coverage", 114 | "componentName": "sonarqube-jacoco-code-coverage", 115 | "componentQualifier": "TRK", 116 | "analysisId": "AX0vnv7gkMePnxoN1IDV", 117 | "status": "SUCCESS", 118 | "submittedAt": "2021-11-17T20:38:07+0000", 119 | "submitterLogin": "admin", 120 | "startedAt": "2021-11-17T20:38:08+0000", 121 | "executedAt": "2021-11-17T20:38:22+0000", 122 | "executionTimeMs": 13679, 123 | "hasScannerContext": true, 124 | "warningCount": 0, 125 | "warnings": [] 126 | } 127 | } 128 | ``` 129 | 130 | # A simple pipeline for testing 131 | ``` 132 | pipeline { 133 | agent any 134 | 135 | stages { 136 | stage('Clone sources') { 137 | steps { 138 | git url: 'https://github.com/dgeorgievski/sonarqube-jacoco-code-coverage.git' 139 | } 140 | } 141 | 142 | stage('Build') { 143 | steps { 144 | sh './gradlew clean test build' 145 | junit 'build/test-results/test/*.xml' 146 | step( [ $class: 'JacocoPublisher' ] ) 147 | } 148 | } 149 | 150 | stage('SonarQube analysis') { 151 | steps { 152 | withSonarQubeEnv('SonarQube') { 153 | sh "./gradlew sonarqube -PsonarToken=****************" 154 | } 155 | } 156 | } 157 | 158 | stage("Quality gate") { 159 | steps { 160 | waitForQualityGate abortPipeline: true 161 | } 162 | } 163 | 164 | stage("InfluxDB v2 publisher") { 165 | environment { 166 | LOG_JUNIT_RESULTS="true" 167 | INFLUXDB_PLUGIN_CUSTOM_PROJECT_NAME = "foo" 168 | } 169 | 170 | steps { 171 | withSonarQubeEnv('SonarQube') { 172 | influxDbPublisher(selectedTarget: 'influxdb_v2',) 173 | } 174 | } 175 | } 176 | 177 | } 178 | } 179 | ``` -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/AgentPointGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.*; 4 | import hudson.model.labels.LabelAtom; 5 | import jenkins.model.Jenkins; 6 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 7 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.jenkinsci.plugins.workflow.actions.WorkspaceAction; 10 | import org.jenkinsci.plugins.workflow.flow.FlowExecution; 11 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 12 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 13 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | import org.mockito.Mockito; 17 | 18 | import java.util.ArrayList; 19 | import java.util.HashSet; 20 | import java.util.List; 21 | import java.util.Set; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertTrue; 24 | 25 | /** 26 | * @author Mathieu Delrocq 27 | */ 28 | class AgentPointGeneratorTest extends PointGeneratorBaseTest { 29 | 30 | private static final String CUSTOM_PREFIX = "test_prefix"; 31 | private static final String JOB_NAME = "job_name"; 32 | 33 | private static final String NODE_NAME = "node_name"; 34 | private static final String NODE_LABEL = "node_label"; 35 | 36 | private Run abstractBuild; 37 | private WorkflowRun pipelineBuild; 38 | private Node node; 39 | private FlowNode flowNode1; 40 | private FlowNode flowNode2; 41 | private List flowNodeList; 42 | private FlowExecutionOwner flowExecutionOwner; 43 | private FlowExecution flowExecution; 44 | private WorkspaceAction workspaceAction1; 45 | private WorkspaceAction workspaceAction2; 46 | private TaskListener listener; 47 | private long currTime; 48 | private ProjectNameRenderer measurementRenderer; 49 | 50 | @BeforeEach 51 | void before() { 52 | // Global Mocks 53 | listener = Mockito.mock(TaskListener.class); 54 | currTime = System.currentTimeMillis(); 55 | measurementRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null); 56 | Job job = Mockito.mock(Job.class); 57 | Mockito.when(job.getName()).thenReturn(JOB_NAME); 58 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + JOB_NAME); 59 | Mockito.when(job.getBuildHealth()).thenReturn(new HealthReport()); 60 | 61 | // Mocks for AbstractBuild 62 | abstractBuild = Mockito.mock(AbstractBuild.class); 63 | Mockito.doReturn(job).when(abstractBuild).getParent(); 64 | node = Mockito.mock(Node.class); 65 | Mockito.when(((AbstractBuild) abstractBuild).getBuiltOn()).thenReturn(node); 66 | Mockito.when(node.getDisplayName()).thenReturn(NODE_NAME); 67 | Mockito.when(node.getLabelString()).thenReturn(NODE_LABEL); 68 | 69 | // Mock for Pipeline 70 | pipelineBuild = Mockito.mock(WorkflowRun.class); 71 | Mockito.doReturn(job).when(pipelineBuild).getParent(); 72 | flowNode1 = Mockito.mock(FlowNode.class); 73 | flowNode2 = Mockito.mock(FlowNode.class); 74 | flowExecutionOwner = Mockito.mock(FlowExecutionOwner.class); 75 | flowExecution = Mockito.mock(FlowExecution.class); 76 | flowNodeList = new ArrayList<>(); 77 | flowNodeList.add(flowNode1); 78 | flowNodeList.add(flowNode2); 79 | workspaceAction1 = Mockito.mock(WorkspaceAction.class); 80 | workspaceAction2 = Mockito.mock(WorkspaceAction.class); 81 | Set labels = new HashSet<>(); 82 | LabelAtom label = new LabelAtom(NODE_LABEL); 83 | labels.add(label); 84 | Mockito.when(pipelineBuild.asFlowExecutionOwner()).thenReturn(flowExecutionOwner); 85 | Mockito.when(flowExecutionOwner.getOrNull()).thenReturn(flowExecution); 86 | Mockito.when(flowExecution.getCurrentHeads()).thenReturn(flowNodeList); 87 | Mockito.when(flowNode1.getAction(WorkspaceAction.class)).thenReturn(workspaceAction1); 88 | Mockito.when(flowNode2.getAction(WorkspaceAction.class)).thenReturn(workspaceAction2); 89 | Mockito.when(workspaceAction1.getNode()).thenReturn(NODE_NAME); 90 | Mockito.when(workspaceAction1.getLabels()).thenReturn(labels); 91 | Mockito.when(workspaceAction2.getNode()).thenReturn(NODE_NAME + "2"); 92 | Mockito.when(workspaceAction2.getLabels()).thenReturn(labels); 93 | } 94 | 95 | @Test 96 | void pipeline_agent_present() { 97 | AgentPointGenerator gen = new AgentPointGenerator(pipelineBuild, listener, measurementRenderer, currTime, 98 | StringUtils.EMPTY, CUSTOM_PREFIX); 99 | assertTrue(gen.hasReport()); 100 | AbstractPoint[] points = gen.generate(); 101 | assertTrue(points != null && points.length != 0); 102 | assertTrue(points[0].getV1v2Point().hasFields()); 103 | assertTrue(allLineProtocolsContain(points[0], "agent_name=\"node_name2\"")); 104 | assertTrue(allLineProtocolsContain(points[0], "agent_label=\"node_label\"")); 105 | assertTrue(allLineProtocolsContain(points[1], "agent_name=\"node_name\"")); 106 | assertTrue(allLineProtocolsContain(points[1], "agent_label=\"node_label\"")); 107 | } 108 | 109 | @Test 110 | void abstractbuild_agent_present() { 111 | AgentPointGenerator gen = new AgentPointGenerator(abstractBuild, listener, measurementRenderer, currTime, 112 | StringUtils.EMPTY, CUSTOM_PREFIX); 113 | assertTrue(gen.hasReport()); 114 | AbstractPoint[] points = gen.generate(); 115 | assertTrue(points != null && points.length != 0); 116 | assertTrue(points[0].getV1v2Point().hasFields()); 117 | assertTrue(allLineProtocolsContain(points[0], "agent_name=\"node_name\"")); 118 | assertTrue(allLineProtocolsContain(points[0], "agent_label=\"node_label\"")); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/PointGeneratorBaseTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.Map; 7 | import java.util.TreeMap; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | public class PointGeneratorBaseTest { 12 | /** 13 | * Asserts that the given AbstractPoint has the requested fields and tags. 14 | * 15 | * @param point The abstract point to check the fields and tags of. 16 | * @param fields The expected fields. 17 | * @param tags The expected tags. 18 | */ 19 | public static void checkPointTagsAndFields(AbstractPoint point, TreeMap fields, TreeMap tags) throws NoSuchFieldException, IllegalAccessException { 20 | TreeMap pointFields = getPointFields(point); 21 | TreeMap pointTags = getPointTags(point); 22 | 23 | assertMapsAreEqualIgnoreNumberTypes(fields, pointFields); 24 | assertMapsAreEqualIgnoreNumberTypes(tags, pointTags); 25 | } 26 | 27 | /** 28 | * Returns the fields of an AbstractPoint. 29 | * 30 | * @param point The point to get the fields from. 31 | * @return The fields of the point. 32 | */ 33 | public static TreeMap getPointFields(AbstractPoint point) throws NoSuchFieldException, IllegalAccessException { 34 | TreeMap v1v2Fields = getPointMap(point.getV1v2Point(), "fields"); 35 | TreeMap v3Fields = getPointMap(point.getV3Point(), "fields"); 36 | 37 | assertMapsAreEqualIgnoreNumberTypes(v1v2Fields, v3Fields); 38 | return v1v2Fields; 39 | } 40 | 41 | /** 42 | * Returns the requested point property as map. The property can be either "fields" or "tags". 43 | * 44 | * @param point The V1V2 point to get the property of. 45 | * @param propertyName The property can be either "fields" or "tags". 46 | * @return The point's fields or tags. 47 | */ 48 | public static TreeMap getPointMap(com.influxdb.client.write.Point point, String propertyName) throws NoSuchFieldException, IllegalAccessException { 49 | Field propertyField = point.getClass().getDeclaredField(propertyName); 50 | propertyField.setAccessible(true); 51 | 52 | @SuppressWarnings("unchecked") 53 | TreeMap data = (TreeMap) propertyField.get(point); 54 | return data; 55 | } 56 | 57 | /** 58 | * Returns the requested point property as map. The property can be either "fields" or "tags". 59 | * 60 | * @param point The V3 point to get the property of. 61 | * @param propertyName The property can be either "fields" or "tags". 62 | * @return The point's fields or tags. 63 | */ 64 | public static TreeMap getPointMap(com.influxdb.v3.client.Point point, String propertyName) throws NoSuchFieldException, IllegalAccessException { 65 | Field privateValues = point.getClass().getDeclaredField("values"); 66 | privateValues.setAccessible(true); 67 | 68 | Object pointValues = privateValues.get(point); 69 | 70 | Field propertyField = pointValues.getClass().getDeclaredField(propertyName); 71 | 72 | propertyField.setAccessible(true); 73 | 74 | @SuppressWarnings("unchecked") 75 | TreeMap data = (TreeMap) propertyField.get(pointValues); 76 | return data; 77 | } 78 | 79 | /** 80 | * Asserts that two maps are equal, ignoring number types, i.e. 11L == 11i. 81 | * 82 | * @param expectedMap The expected map to compare to. 83 | * @param actualMap The actual map. 84 | */ 85 | public static void assertMapsAreEqualIgnoreNumberTypes(TreeMap expectedMap, TreeMap actualMap) { 86 | for (Map.Entry entry : expectedMap.entrySet()) { 87 | String expectedKey = entry.getKey(); 88 | assertValuesEqualIgnoreNumberTypes(entry.getValue(), actualMap.get(expectedKey)); 89 | } 90 | assertEquals(expectedMap.size(), actualMap.size()); 91 | } 92 | 93 | /** 94 | * Asserts that two values are equal, ignoring number types, i.e. 11L == 11i. 95 | * 96 | * @param expected The expected value to compare to. 97 | * @param actual The actual value. 98 | */ 99 | private static void assertValuesEqualIgnoreNumberTypes(Object expected, Object actual) { 100 | if (expected instanceof Number && actual instanceof Number) { 101 | assertEquals(((Number) expected).longValue(), ((Number) actual).longValue()); 102 | } else { 103 | assertEquals(expected, actual); 104 | } 105 | } 106 | 107 | /** 108 | * Returns the tags of an AbstractPoint. 109 | * 110 | * @param point The point to get the tags from. 111 | * @return The tags of the point. 112 | */ 113 | public static TreeMap getPointTags(AbstractPoint point) throws NoSuchFieldException, IllegalAccessException { 114 | TreeMap v1v2Tags = getPointMap(point.getV1v2Point(), "tags"); 115 | TreeMap v3Tags = getPointMap(point.getV3Point(), "tags"); 116 | 117 | assertMapsAreEqualIgnoreNumberTypes(v1v2Tags, v3Tags); 118 | return v1v2Tags; 119 | } 120 | 121 | /** 122 | * Test helper function to check if the line protocol of both, v1v2 and v3 points, contains a given string. 123 | * 124 | * @param match The string to check for. 125 | * @return True if the line protocol of both, v1v2 and v3 points contains the given string, false otherwise. 126 | */ 127 | public static boolean allLineProtocolsContain(AbstractPoint point, String match) { 128 | return point.getV1v2Point().toLineProtocol().contains(match) && point.getV3Point().toLineProtocol().contains(match); 129 | } 130 | 131 | /** 132 | * Test helper function to check if the line protocol of both, v1v2 and v3 points, starts with a given string. 133 | * 134 | * @param match The string to check for. 135 | * @return True if the line protocol of both, v1v2 and v3 points starts with the given string, false otherwise. 136 | */ 137 | public static boolean allLineProtocolsStartWith(AbstractPoint point, String match) { 138 | return point.getV1v2Point().toLineProtocol().startsWith(match) && point.getV3Point().toLineProtocol().startsWith(match); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/JUnitPointGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.EnvVars; 4 | import hudson.model.Job; 5 | import hudson.model.Run; 6 | import hudson.model.TaskListener; 7 | import hudson.tasks.junit.CaseResult; 8 | import hudson.tasks.junit.SuiteResult; 9 | import hudson.tasks.test.AbstractTestResultAction; 10 | import jenkins.model.Jenkins; 11 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 12 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 13 | import org.apache.commons.lang.StringUtils; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | import org.mockito.Mockito; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertFalse; 23 | import static org.junit.jupiter.api.Assertions.assertTrue; 24 | 25 | class JUnitPointGeneratorTest extends PointGeneratorBaseTest { 26 | 27 | private static final String JOB_NAME = "master"; 28 | private static final int BUILD_NUMBER = 11; 29 | private static final String CUSTOM_PREFIX = "test_prefix"; 30 | 31 | private Run build; 32 | private TaskListener listener; 33 | private ProjectNameRenderer measurementRenderer; 34 | 35 | private CaseResult caseResult; 36 | private long currTime; 37 | 38 | @BeforeEach 39 | void before() { 40 | build = Mockito.mock(Run.class); 41 | Job job = Mockito.mock(Job.class); 42 | listener = Mockito.mock(TaskListener.class); 43 | measurementRenderer = new ProjectNameRenderer(CUSTOM_PREFIX, null); 44 | caseResult = Mockito.mock(CaseResult.class); 45 | 46 | Mockito.when(build.getNumber()).thenReturn(BUILD_NUMBER); 47 | Mockito.when(build.getParent()).thenReturn(job); 48 | Mockito.when(job.getName()).thenReturn(JOB_NAME); 49 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn(""); 50 | 51 | currTime = System.currentTimeMillis(); 52 | } 53 | 54 | @Test 55 | void hasReport_tests_exist_and_flag_is_true() { 56 | EnvVars envVars = new EnvVars(); 57 | envVars.put("LOG_JUNIT_RESULTS", "true"); 58 | 59 | Mockito.when(build.getAction(AbstractTestResultAction.class)).thenReturn(Mockito.mock(AbstractTestResultAction.class)); 60 | 61 | JUnitPointGenerator junitGen = new JUnitPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, envVars); 62 | assertTrue(junitGen.hasReport()); 63 | } 64 | 65 | @Test 66 | void hasReport_tests_exist_and_flag_is_false() { 67 | EnvVars envVars = new EnvVars(); 68 | envVars.put("LOG_JUNIT_RESULTS", "false"); 69 | 70 | Mockito.when(build.getAction(AbstractTestResultAction.class)).thenReturn(Mockito.mock(AbstractTestResultAction.class)); 71 | 72 | JUnitPointGenerator junitGen = new JUnitPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, envVars); 73 | assertFalse(junitGen.hasReport()); 74 | } 75 | 76 | @Test 77 | void hasReport_tests_exist_and_flag_is_missing() { 78 | EnvVars envVars = new EnvVars(); 79 | 80 | Mockito.when(build.getAction(AbstractTestResultAction.class)).thenReturn(Mockito.mock(AbstractTestResultAction.class)); 81 | 82 | JUnitPointGenerator junitGen = new JUnitPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, envVars); 83 | assertFalse(junitGen.hasReport()); 84 | } 85 | 86 | @Test 87 | void hasReport_no_tests_and_flag_is_true() { 88 | EnvVars envVars = new EnvVars(); 89 | envVars.put("LOG_JUNIT_RESULTS", "true"); 90 | 91 | JUnitPointGenerator junitGen = new JUnitPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, envVars); 92 | assertFalse(junitGen.hasReport()); 93 | } 94 | 95 | @Test 96 | void hasReport_no_tests_and_flag_is_false() { 97 | EnvVars envVars = new EnvVars(); 98 | envVars.put("LOG_JUNIT_RESULTS", "false"); 99 | 100 | JUnitPointGenerator junitGen = new JUnitPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, envVars); 101 | assertFalse(junitGen.hasReport()); 102 | } 103 | 104 | @Test 105 | void hasReport_no_tests_and_flag_is_missing() { 106 | EnvVars envVars = new EnvVars(); 107 | 108 | JUnitPointGenerator junitGen = new JUnitPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, CUSTOM_PREFIX, envVars); 109 | assertFalse(junitGen.hasReport()); 110 | } 111 | 112 | @Test 113 | void measurement_successfully_generated() { 114 | CaseResult.Status status = Mockito.mock(CaseResult.Status.class); 115 | SuiteResult suiteResult = Mockito.mock(SuiteResult.class); 116 | Mockito.when(caseResult.getStatus()).thenReturn(status); 117 | List passedTests = new ArrayList<>(); 118 | passedTests.add(caseResult); 119 | AbstractTestResultAction testResultAction = Mockito.mock(AbstractTestResultAction.class); 120 | Mockito.when(testResultAction.getFailedTests()).thenReturn(Collections.emptyList()); 121 | Mockito.when(testResultAction.getSkippedTests()).thenReturn(Collections.emptyList()); 122 | Mockito.when(testResultAction.getPassedTests()).thenReturn(passedTests); 123 | Mockito.when(build.getAction(AbstractTestResultAction.class)).thenReturn(testResultAction); 124 | Mockito.when(caseResult.getSuiteResult()).thenReturn(suiteResult); 125 | 126 | Mockito.when(caseResult.getSuiteResult().getName()).thenReturn("my_suite"); 127 | Mockito.when(caseResult.getName()).thenReturn("my_test"); 128 | Mockito.when(caseResult.getClassName()).thenReturn("my_class_name"); 129 | Mockito.when(caseResult.getStatus().toString()).thenReturn("PASSED"); 130 | Mockito.when(caseResult.getStatus().ordinal()).thenReturn(0); 131 | Mockito.when(caseResult.getDuration()).thenReturn(10.0f); 132 | 133 | JUnitPointGenerator generator = new JUnitPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, StringUtils.EMPTY, new EnvVars()); 134 | AbstractPoint[] points = generator.generate(); 135 | 136 | assertTrue(allLineProtocolsContain(points[0], "suite_name=\"my_suite\"")); 137 | assertTrue(allLineProtocolsContain(points[0], "test_name=\"my_test\"")); 138 | assertTrue(allLineProtocolsContain(points[0], "test_class_full_name=\"my_class_name\"")); 139 | assertTrue(allLineProtocolsContain(points[0], "test_status=\"PASSED\"")); 140 | assertTrue(allLineProtocolsContain(points[0], "test_status_ordinal=0")); 141 | assertTrue(allLineProtocolsContain(points[0], "test_duration=10.0")); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/InfluxDbStep.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import hudson.Extension; 5 | import hudson.FilePath; 6 | import hudson.Launcher; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | import hudson.util.ListBoxModel; 10 | import jenkins.model.Jenkins; 11 | import jenkinsci.plugins.influxdb.models.Target; 12 | import org.jenkinsci.plugins.workflow.steps.Step; 13 | import org.jenkinsci.plugins.workflow.steps.StepContext; 14 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 15 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 16 | import org.kohsuke.stapler.DataBoundConstructor; 17 | import org.kohsuke.stapler.DataBoundSetter; 18 | 19 | import javax.annotation.Nonnull; 20 | import java.io.Serializable; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | import java.util.Set; 25 | 26 | public class InfluxDbStep extends Step { 27 | 28 | private String selectedTarget; 29 | private String customProjectName; 30 | private String customPrefix; 31 | private Map customData; 32 | private Map customDataTags; 33 | private Map> customDataMap; 34 | private Map> customDataMapTags; 35 | private String jenkinsEnvParameterField; 36 | private String jenkinsEnvParameterTag; 37 | private String measurementName; 38 | 39 | @DataBoundConstructor 40 | public InfluxDbStep(String selectedTarget) { 41 | this.selectedTarget = selectedTarget; 42 | } 43 | 44 | public String getSelectedTarget() { 45 | String target = selectedTarget; 46 | Jenkins jenkins = Jenkins.getInstanceOrNull(); 47 | if (target == null && jenkins != null) { 48 | List targets = jenkins.getDescriptorByType(DescriptorImpl.class).getTargets(); 49 | if (!targets.isEmpty()) { 50 | target = targets.get(0).getDescription(); 51 | } 52 | } 53 | return target; 54 | } 55 | 56 | public void setSelectedTarget(String target) { 57 | Objects.requireNonNull(target); 58 | this.selectedTarget = target; 59 | } 60 | 61 | public String getCustomProjectName() { 62 | return customProjectName; 63 | } 64 | 65 | @DataBoundSetter 66 | public void setCustomProjectName(String customProjectName) { 67 | this.customProjectName = customProjectName; 68 | } 69 | 70 | public String getCustomPrefix() { 71 | return customPrefix; 72 | } 73 | 74 | @DataBoundSetter 75 | public void setCustomPrefix(String customPrefix) { 76 | this.customPrefix = customPrefix; 77 | } 78 | 79 | public Map getCustomData() { 80 | return customData; 81 | } 82 | 83 | @DataBoundSetter 84 | public void setCustomData(Map customData) { 85 | this.customData = customData; 86 | } 87 | 88 | public Map getCustomDataTags() { 89 | return customDataTags; 90 | } 91 | 92 | @DataBoundSetter 93 | public void setCustomDataTags(Map customDataTags) { 94 | this.customDataTags = customDataTags; 95 | } 96 | 97 | public Map> getCustomDataMap() { 98 | return customDataMap; 99 | } 100 | 101 | @DataBoundSetter 102 | public void setCustomDataMap(Map> customDataMap) { 103 | this.customDataMap = customDataMap; 104 | } 105 | 106 | public Map> getCustomDataMapTags() { 107 | return customDataMapTags; 108 | } 109 | 110 | @DataBoundSetter 111 | public void setCustomDataMapTags(Map> customDataMapTags) { 112 | this.customDataMapTags = customDataMapTags; 113 | } 114 | 115 | public String getJenkinsEnvParameterField() { 116 | return jenkinsEnvParameterField; 117 | } 118 | 119 | @DataBoundSetter 120 | public void setJenkinsEnvParameterField(String jenkinsEnvParameterField) { 121 | this.jenkinsEnvParameterField = jenkinsEnvParameterField; 122 | } 123 | 124 | public String getJenkinsEnvParameterTag() { 125 | return jenkinsEnvParameterTag; 126 | } 127 | 128 | @DataBoundSetter 129 | public void setJenkinsEnvParameterTag(String jenkinsEnvParameterTag) { 130 | this.jenkinsEnvParameterTag = jenkinsEnvParameterTag; 131 | } 132 | 133 | public String getMeasurementName() { 134 | return measurementName; 135 | } 136 | 137 | @DataBoundSetter 138 | public void setMeasurementName(String measurementName) { 139 | this.measurementName = measurementName; 140 | } 141 | 142 | public Target getTarget() { 143 | Jenkins jenkins = Jenkins.getInstanceOrNull(); 144 | if (jenkins != null) { 145 | List targets = jenkins.getDescriptorByType(DescriptorImpl.class).getTargets(); 146 | if (selectedTarget == null && !targets.isEmpty()) { 147 | return targets.get(0); 148 | } 149 | for (Target target : targets) { 150 | String targetInfo = target.getDescription(); 151 | if (targetInfo.equals(selectedTarget)) { 152 | return target; 153 | } 154 | } 155 | } 156 | return null; 157 | } 158 | 159 | @Override 160 | public StepExecution start(StepContext context) throws Exception { 161 | return new InfluxDbStepExecution(this, context); 162 | } 163 | 164 | @Extension(optional = true) 165 | public static final class DescriptorImpl extends StepDescriptor implements Serializable { 166 | 167 | @Nonnull 168 | public List getTargets() { 169 | return InfluxDbGlobalConfig.getInstance().getTargets(); 170 | } 171 | 172 | public void addTarget(Target target) { 173 | InfluxDbGlobalConfig.getInstance().addTarget(target); 174 | } 175 | 176 | public void removeTarget(String targetDescription) { 177 | InfluxDbGlobalConfig.getInstance().removeTarget(targetDescription); 178 | } 179 | 180 | @Override 181 | public String getFunctionName() { 182 | return "influxDbPublisher"; 183 | } 184 | 185 | @Nonnull 186 | @Override 187 | public String getDisplayName() { 188 | return "Publish build data to InfluxDB"; 189 | } 190 | 191 | @Override 192 | public Set> getRequiredContext() { 193 | return ImmutableSet.of(Run.class, FilePath.class, Launcher.class, TaskListener.class); 194 | } 195 | 196 | public ListBoxModel doFillSelectedTargetItems() { 197 | ListBoxModel model = new ListBoxModel(); 198 | for (Target target : getTargets()) { 199 | model.add(target.getDescription()); 200 | } 201 | return model; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/test/java/jenkinsci/plugins/influxdb/generators/ChangeLogGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.*; 4 | import hudson.scm.ChangeLogSet; 5 | import hudson.scm.EditType; 6 | import jenkins.model.Jenkins; 7 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 8 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import org.mockito.Mockito; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | import java.util.Collections; 18 | import java.util.List; 19 | 20 | import static org.junit.jupiter.api.Assertions.assertFalse; 21 | import static org.junit.jupiter.api.Assertions.assertTrue; 22 | 23 | 24 | class ChangeLogGeneratorTest extends PointGeneratorBaseTest { 25 | 26 | private Run build; 27 | private ProjectNameRenderer measurementRenderer; 28 | private TaskListener listener; 29 | private long currTime; 30 | 31 | private Job job; 32 | 33 | 34 | private AbstractBuild mockBuild(String projectFullName, int buildNumber) { 35 | AbstractBuild build = Mockito.mock(AbstractBuild.class); 36 | Mockito.when(build.getParent()).thenReturn(job); 37 | Mockito.when(job.getName()).thenReturn(projectFullName); 38 | Mockito.when(build.getNumber()).thenReturn(buildNumber); 39 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + projectFullName); 40 | return build; 41 | } 42 | 43 | private WorkflowRun mockWorkflow(String projectFullName, int buildNumber) { 44 | WorkflowRun build = Mockito.mock(WorkflowRun.class); 45 | Mockito.doReturn(job).when(build).getParent(); 46 | // Mockito.when(build.getParent()).thenReturn((WorkflowJob) job); 47 | Mockito.when(job.getName()).thenReturn(projectFullName); 48 | Mockito.when(build.getNumber()).thenReturn(buildNumber); 49 | Mockito.when(job.getRelativeNameFrom(Mockito.nullable(Jenkins.class))).thenReturn("folder/" + projectFullName); 50 | return build; 51 | } 52 | 53 | private ChangeLogSet.Entry createEntry(String name, String message, Collection affectedFiles) { 54 | ChangeLogSet.Entry entry = Mockito.mock(ChangeLogSet.Entry.class); 55 | User user = Mockito.mock(User.class); 56 | Mockito.when(user.getFullName()).thenReturn(name); 57 | Mockito.when(entry.getAuthor()).thenReturn(user); 58 | Mockito.when(entry.getMsg()).thenReturn(message); 59 | Mockito.doReturn(affectedFiles).when(entry).getAffectedFiles(); 60 | 61 | return entry; 62 | } 63 | 64 | private ChangeLogSet.AffectedFile createFile(String path) { 65 | ChangeLogSet.AffectedFile affectedFile = Mockito.mock(ChangeLogSet.AffectedFile.class); 66 | Mockito.when(affectedFile.getPath()).thenReturn(path); 67 | Mockito.when(affectedFile.getEditType()).thenReturn(EditType.EDIT); 68 | 69 | return affectedFile; 70 | } 71 | 72 | @BeforeEach 73 | void before() { 74 | build = Mockito.mock(Run.class); 75 | measurementRenderer = new ProjectNameRenderer("", null); 76 | listener = Mockito.mock(TaskListener.class); 77 | job = Mockito.mock(Job.class); 78 | 79 | Mockito.doReturn(job).when(build).getParent(); 80 | Mockito.when(build.getNumber()).thenReturn(1); 81 | Mockito.when(job.getName()).thenReturn("changelog_test"); 82 | 83 | currTime = System.currentTimeMillis(); 84 | } 85 | 86 | @Test 87 | void testHasReportWithoutCommitsShouldReturnFalse() { 88 | ChangeLogPointGenerator generator = new ChangeLogPointGenerator(build, listener, measurementRenderer, currTime, StringUtils.EMPTY, StringUtils.EMPTY); 89 | assertFalse(generator.hasReport()); 90 | } 91 | 92 | @Test 93 | void testAbstractBuildShouldGenerateData() { 94 | String name = "John Doe"; 95 | String message = "Awesome commit message"; 96 | String path = "path/to/file.txt"; 97 | 98 | AbstractBuild abstractBuild = mockBuild("abstract_build", 1); 99 | 100 | ChangeLogSet.AffectedFile affectedFile = createFile(path); 101 | Collection affectedFiles = Collections.singletonList(affectedFile); 102 | 103 | ChangeLogSet.Entry entry = createEntry(name, message, affectedFiles); 104 | 105 | ChangeLogSet changeLogSet = Mockito.mock(ChangeLogSet.class); 106 | List entries = new ArrayList<>(); 107 | entries.add(entry); 108 | Mockito.when(abstractBuild.getChangeSet()).thenReturn(changeLogSet); 109 | Mockito.when(changeLogSet.iterator()).thenReturn(entries.iterator()); 110 | 111 | ChangeLogPointGenerator generator = new ChangeLogPointGenerator(abstractBuild, listener, measurementRenderer, currTime, StringUtils.EMPTY, StringUtils.EMPTY); 112 | assertTrue(generator.hasReport()); 113 | 114 | AbstractPoint[] points = generator.generate(); 115 | String lineProtocol = points[0].getV1v2Point().toLineProtocol(); 116 | 117 | assertTrue(lineProtocol.contains("culprits=\"" + name + "\"")); 118 | assertTrue(lineProtocol.contains("commit_messages=\"" + message + "\"")); 119 | assertTrue(lineProtocol.contains("affected_paths=\"" + path + "\"")); 120 | assertTrue(lineProtocol.contains("commit_count=1")); 121 | } 122 | 123 | @Test 124 | void testWorkflowBuildShouldGenerateData() { 125 | WorkflowRun workflowRun = mockWorkflow("workflow_run", 1); 126 | 127 | // Generate mock data 128 | String name1 = "John Doe"; 129 | String name2 = "Jane Doe"; 130 | String message1 = "Awesome commit message"; 131 | String message2 = "Another message"; 132 | String path1 = "path/to/file.txt"; 133 | String path2 = "another/path/to/file.txt"; 134 | ChangeLogSet.AffectedFile file1 = createFile(path1); 135 | ChangeLogSet.AffectedFile file2 = createFile(path2); 136 | Collection affectedFiles = new ArrayList<>(); 137 | affectedFiles.add(file1); 138 | affectedFiles.add(file2); 139 | ChangeLogSet.Entry entry1 = createEntry(name1, message1, affectedFiles); 140 | ChangeLogSet.Entry entry2 = createEntry(name2, message2, affectedFiles); 141 | List entries = new ArrayList<>(); 142 | entries.add(entry1); 143 | entries.add(entry2); 144 | ChangeLogSet changeLogSet = Mockito.mock(ChangeLogSet.class); 145 | List> changeLogSets = new ArrayList<>(); 146 | changeLogSets.add(changeLogSet); 147 | Mockito.when(workflowRun.getChangeSets()).thenReturn(changeLogSets); 148 | Mockito.when(changeLogSet.iterator()).thenReturn(entries.iterator()); 149 | 150 | // Generate point 151 | ChangeLogPointGenerator generator = new ChangeLogPointGenerator(workflowRun, listener, measurementRenderer, currTime, StringUtils.EMPTY, StringUtils.EMPTY); 152 | assertTrue(generator.hasReport()); 153 | AbstractPoint[] points = generator.generate(); 154 | 155 | String paths = path1 + ", " + path2; 156 | assertTrue(allLineProtocolsContain(points[0], "culprits=\"" + name1 + ", " + name2 + "\"")); 157 | assertTrue(allLineProtocolsContain(points[0], "commit_messages=\"" + message1 + ", " + message2 + "\"")); 158 | assertTrue(allLineProtocolsContain(points[0], "affected_paths=\"" + paths + ", " + paths + "\"")); 159 | assertTrue(allLineProtocolsContain(points[0], "commit_count=2")); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/PerfPublisherPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import com.influxdb.client.domain.WritePrecision; 4 | import hudson.model.Run; 5 | import hudson.model.TaskListener; 6 | import hudson.plugins.PerfPublisher.PerfPublisherBuildAction; 7 | import hudson.plugins.PerfPublisher.Report.Metric; 8 | import hudson.plugins.PerfPublisher.Report.ReportContainer; 9 | import hudson.plugins.PerfPublisher.Report.Test; 10 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 11 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class PerfPublisherPointGenerator extends AbstractPointGenerator { 18 | 19 | private final String customPrefix; 20 | private final PerfPublisherBuildAction performanceBuildAction; 21 | private final TimeGenerator timeGenerator; 22 | 23 | public PerfPublisherPointGenerator(Run build, TaskListener listener, 24 | ProjectNameRenderer projectNameRenderer, 25 | long timestamp, String jenkinsEnvParameterTag, 26 | String customPrefix) { 27 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 28 | this.customPrefix = customPrefix; 29 | performanceBuildAction = build.getAction(PerfPublisherBuildAction.class); 30 | timeGenerator = new TimeGenerator(timestamp); 31 | } 32 | 33 | public boolean hasReport() { 34 | return performanceBuildAction != null && performanceBuildAction.getReport() != null; 35 | } 36 | 37 | @Override 38 | public AbstractPoint buildPoint(String name, String customPrefix, Run build) { 39 | // add unique time to guarantee correct point adding to DB 40 | return super.buildPoint(name, customPrefix, build) 41 | .time(timeGenerator.next(), WritePrecision.NS); 42 | } 43 | 44 | public AbstractPoint[] generate() { 45 | ReportContainer reports = performanceBuildAction.getReports(); 46 | 47 | List points = new ArrayList<>(); 48 | 49 | points.add(generateSummaryPoint(reports)); 50 | points.addAll(generateMetricsPoints(reports)); 51 | 52 | for (Test test : reports.getTests()) { 53 | points.add(generateTestPoint(test)); 54 | points.addAll(generateTestMetricsPoints(test)); 55 | } 56 | 57 | return points.toArray(new AbstractPoint[0]); 58 | } 59 | 60 | private AbstractPoint generateSummaryPoint(ReportContainer reports) { 61 | AbstractPoint point = buildPoint("perfpublisher_summary", customPrefix, build) 62 | .addField("number_of_tests", reports.getNumberOfTest()) 63 | .addField("number_of_executed_tests", reports.getNumberOfExecutedTest()) 64 | .addField("number_of_not_executed_tests", reports.getNumberOfNotExecutedTest()) 65 | .addField("number_of_passed_tests", reports.getNumberOfPassedTest()) 66 | .addField("number_of_failed_tests", reports.getNumberOfFailedTest()) 67 | .addField("number_of_success_tests", reports.getNumberOfSuccessTests()) 68 | .addField("number_of_true_false_tests", reports.getNumberOfTrueFalseTest()); 69 | 70 | // compile time 71 | if (reports.getBestCompileTimeTest().isCompileTime()) { 72 | point.addField("best_compile_time_test_value", reports.getBestCompileTimeTestValue()) 73 | .addField("best_compile_time_test_name", reports.getBestCompileTimeTestName()) 74 | .addField("worst_compile_time_test_value", reports.getWorstCompileTimeTestValue()) 75 | .addField("worst_compile_time_test_name", reports.getWorstCompileTimeTestName()) 76 | .addField("avg_compile_time", reports.getAverageOfCompileTime()); 77 | } 78 | 79 | // performance 80 | if (reports.getBestPerformanceTest().isPerformance()) { 81 | point.addField("best_performance_test_value", reports.getBestPerformanceTestValue()) 82 | .addField("best_performance_test_name", reports.getBestPerformanceTestName()) 83 | .addField("worst_performance_test_value", reports.getWorstPerformanceTestValue()) 84 | .addField("worst_performance_test_name", reports.getWorstPerformanceTestName()) 85 | .addField("average_performance", reports.getAverageOfPerformance()); 86 | } 87 | 88 | // execution time 89 | if (reports.getBestExecutionTimeTest().isExecutionTime()) { 90 | point.addField("best_execution_time_test_value", reports.getBestExecutionTimeTestValue()) 91 | .addField("best_execution_time_test_name", reports.getBestExecutionTimeTestName()) 92 | .addField("worst_execution_time_test_value", reports.getWorstExecutionTimeTestValue()) 93 | .addField("worst_execution_time_test_name", reports.getWorstExecutionTimeTestName()) 94 | .addField("avg_execution_time", reports.getAverageOfExecutionTime()); 95 | } 96 | 97 | return point; 98 | } 99 | 100 | private List generateMetricsPoints(ReportContainer reports) { 101 | List points = new ArrayList<>(); 102 | 103 | for (Map.Entry entry : reports.getAverageValuePerMetrics().entrySet()) { 104 | String metricName = entry.getKey(); 105 | AbstractPoint point = buildPoint("perfpublisher_metric", customPrefix, build) 106 | .addField("metric_name", metricName) 107 | .addField("average", entry.getValue()) 108 | .addField("worst", reports.getWorstValuePerMetrics().get(metricName)) 109 | .addField("best", reports.getBestValuePerMetrics().get(metricName)); 110 | points.add(point); 111 | } 112 | 113 | return points; 114 | } 115 | 116 | private AbstractPoint generateTestPoint(Test test) { 117 | AbstractPoint point = buildPoint("perfpublisher_test", customPrefix, build) 118 | .addField("test_name", test.getName()) 119 | .addField("successful", test.isSuccessfull()) 120 | .addField("executed", test.isExecuted()); 121 | 122 | if (test.getMessage() != null) { 123 | point.addField("message", test.getMessage()); 124 | } 125 | 126 | if (test.isCompileTime()) { 127 | point.addField("compile_time", test.getCompileTime().getMeasure()); 128 | } 129 | 130 | if (test.isExecutionTime()) { 131 | point.addField("execution_time", test.getExecutionTime().getMeasure()); 132 | } 133 | 134 | if (test.isPerformance()) { 135 | point.addField("performance", test.getPerformance().getMeasure()); 136 | } 137 | 138 | return point; 139 | } 140 | 141 | private List generateTestMetricsPoints(Test test) { 142 | List points = new ArrayList<>(); 143 | 144 | for (Map.Entry entry : test.getMetrics().entrySet()) { 145 | String metricName = entry.getKey(); 146 | Metric metric = entry.getValue(); 147 | 148 | AbstractPoint point = buildPoint("perfpublisher_test_metric", customPrefix, build) 149 | .addField("test_name", test.getName()) 150 | .addField("metric_name", metricName) 151 | .addField("value", metric.getMeasure()) 152 | .addField("unit", metric.getUnit()) 153 | .addField("relevant", metric.isRelevant()); 154 | 155 | points.add(point); 156 | } 157 | 158 | return points; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/serenity/SerenityPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators.serenity; 2 | 3 | import hudson.model.Run; 4 | import hudson.model.TaskListener; 5 | import jenkinsci.plugins.influxdb.generators.AbstractPointGenerator; 6 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 7 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 8 | import net.sf.json.JSONArray; 9 | import net.sf.json.JSONObject; 10 | 11 | import java.io.IOException; 12 | 13 | public class SerenityPointGenerator extends AbstractPointGenerator { 14 | 15 | private static final String SERENITY_RESULTS_COUNTS_TOTAL = "serenity_results_counts_total"; 16 | private static final String SERENITY_RESULTS_COUNTS_SUCCESS = "serenity_results_counts_success"; 17 | private static final String SERENITY_RESULTS_COUNTS_PENDING = "serenity_results_counts_pending"; 18 | private static final String SERENITY_RESULTS_COUNTS_IGNORED = "serenity_results_counts_ignored"; 19 | private static final String SERENITY_RESULTS_COUNTS_SKIPPED = "serenity_results_counts_skipped"; 20 | private static final String SERENITY_RESULTS_COUNTS_FAILURE = "serenity_results_counts_failure"; 21 | private static final String SERENITY_RESULTS_COUNTS_ERROR = "serenity_results_counts_error"; 22 | private static final String SERENITY_RESULTS_COUNTS_COMPROMISED = "serenity_results_counts_compromised"; 23 | 24 | private static final String SERENITY_RESULTS_PERCENTAGES_SUCCESS = "serenity_results_percentages_success"; 25 | private static final String SERENITY_RESULTS_PERCENTAGES_PENDING = "serenity_results_percentages_pending"; 26 | private static final String SERENITY_RESULTS_PERCENTAGES_IGNORED = "serenity_results_percentages_ignored"; 27 | private static final String SERENITY_RESULTS_PERCENTAGES_SKIPPED = "serenity_results_percentages_skipped"; 28 | private static final String SERENITY_RESULTS_PERCENTAGES_FAILURE = "serenity_results_percentages_failure"; 29 | private static final String SERENITY_RESULTS_PERCENTAGES_ERROR = "serenity_results_percentages_error"; 30 | private static final String SERENITY_RESULTS_PERCENTAGES_COMPROMISED = "serenity_results_percentages_compromised"; 31 | 32 | private static final String SERENITY_RESULTS_TOTAL_TEST_DURATION = "serenity_results_total_test_duration"; 33 | private static final String SERENITY_RESULTS_TOTAL_CLOCK_DURATION = "serenity_results_total_clock_duration"; 34 | private static final String SERENITY_RESULTS_MIN_TEST_DURATION = "serenity_results_min_test_duration"; 35 | private static final String SERENITY_RESULTS_MAX_TEST_DURATION = "serenity_results_max_test_duration"; 36 | private static final String SERENITY_RESULTS_AVERAGE_TEST_DURATION = "serenity_results_average_test_duration"; 37 | 38 | private final Run build; 39 | private final String customPrefix; 40 | private final TaskListener listener; 41 | 42 | // support for dependency injection 43 | ISerenityJsonSummaryFile serenityJsonSummaryFile = null; 44 | 45 | /* 46 | Run build, TaskListener listener, 47 | MeasurementRenderer> projectNameRenderer, 48 | long timestamp, String jenkinsEnvParameterTag, 49 | String customPrefix) { 50 | */ 51 | /* 52 | public SerenityPointGenerator(MeasurementRenderer> projectNameRenderer, String customPrefix, 53 | Run build, long timestamp, TaskListener listener, 54 | ISerenityJsonSummaryFile serenityJsonSummaryFile) { 55 | */ 56 | 57 | public SerenityPointGenerator(Run build, TaskListener listener, 58 | ProjectNameRenderer projectNameRenderer, 59 | long timestamp, String jenkinsEnvParameterTag, String customPrefix, 60 | ISerenityJsonSummaryFile serenityJsonSummaryFile) { 61 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 62 | this.build = build; 63 | this.customPrefix = customPrefix; 64 | this.listener = listener; 65 | this.serenityJsonSummaryFile = serenityJsonSummaryFile; 66 | } 67 | 68 | public boolean hasReport() { 69 | return serenityJsonSummaryFile.exists(); 70 | } 71 | 72 | public AbstractPoint[] generate() { 73 | String contents; 74 | try { 75 | contents = serenityJsonSummaryFile.getContents(); 76 | } catch (IOException e) { 77 | listener.getLogger().println("Failed to read from file " + serenityJsonSummaryFile.getPath() + ", due to: " + e); 78 | return null; 79 | } 80 | 81 | JSONObject root = JSONObject.fromObject(contents); 82 | JSONObject results = root.getJSONObject("results"); 83 | JSONObject resultsCounts = results.getJSONObject("counts"); 84 | JSONObject resultsPercentages = results.getJSONObject("percentages"); 85 | JSONArray tagTypes = root.getJSONArray("tags"); 86 | 87 | AbstractPoint point = buildPoint("serenity_data", customPrefix, build); 88 | 89 | // include results.counts fields 90 | point 91 | .addField(SERENITY_RESULTS_COUNTS_TOTAL, resultsCounts.getInt("total")) 92 | .addField(SERENITY_RESULTS_COUNTS_SUCCESS, resultsCounts.getInt("success")) 93 | .addField(SERENITY_RESULTS_COUNTS_PENDING, resultsCounts.getInt("pending")) 94 | .addField(SERENITY_RESULTS_COUNTS_IGNORED, resultsCounts.getInt("ignored")) 95 | .addField(SERENITY_RESULTS_COUNTS_SKIPPED, resultsCounts.getInt("skipped")) 96 | .addField(SERENITY_RESULTS_COUNTS_FAILURE, resultsCounts.getInt("failure")) 97 | .addField(SERENITY_RESULTS_COUNTS_ERROR, resultsCounts.getInt("error")) 98 | .addField(SERENITY_RESULTS_COUNTS_COMPROMISED, resultsCounts.getInt("compromised")); 99 | 100 | // include results.percentages fields 101 | point 102 | .addField(SERENITY_RESULTS_PERCENTAGES_SUCCESS, resultsPercentages.getInt("success")) 103 | .addField(SERENITY_RESULTS_PERCENTAGES_PENDING, resultsPercentages.getInt("pending")) 104 | .addField(SERENITY_RESULTS_PERCENTAGES_IGNORED, resultsPercentages.getInt("ignored")) 105 | .addField(SERENITY_RESULTS_PERCENTAGES_SKIPPED, resultsPercentages.getInt("skipped")) 106 | .addField(SERENITY_RESULTS_PERCENTAGES_FAILURE, resultsPercentages.getInt("failure")) 107 | .addField(SERENITY_RESULTS_PERCENTAGES_ERROR, resultsPercentages.getInt("error")) 108 | .addField(SERENITY_RESULTS_PERCENTAGES_COMPROMISED, resultsPercentages.getInt("compromised")); 109 | 110 | // include remaining results fields 111 | point 112 | .addField(SERENITY_RESULTS_TOTAL_TEST_DURATION, results.getLong("totalTestDuration")) 113 | .addField(SERENITY_RESULTS_TOTAL_CLOCK_DURATION, results.getLong("totalClockDuration")) 114 | .addField(SERENITY_RESULTS_MIN_TEST_DURATION, results.getLong("minTestDuration")) 115 | .addField(SERENITY_RESULTS_MAX_TEST_DURATION, results.getLong("maxTestDuration")) 116 | .addField(SERENITY_RESULTS_AVERAGE_TEST_DURATION, results.getLong("averageTestDuration")); 117 | 118 | // include tags fields 119 | for (int iTagType = 0; iTagType < tagTypes.size(); iTagType++) { 120 | JSONObject tagType = tagTypes.getJSONObject(iTagType); 121 | String tagTypeType = tagType.getString("tagType"); 122 | JSONArray tagResults = tagType.getJSONArray("tagResults"); 123 | for (int iTag = 0; iTag < tagResults.size(); iTag++) { 124 | JSONObject tagResult = tagResults.getJSONObject(iTag); 125 | String field = "serenity_tags_" + tagTypeType + ":" + tagResult.getString("tagName"); 126 | point 127 | .addField(field, tagResult.getInt("count")); 128 | } 129 | } 130 | 131 | return new AbstractPoint[]{point}; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/resources/jenkinsci/plugins/influxdb/generators/sonarqube/build-log.txt: -------------------------------------------------------------------------------- 1 | INFO: Scanner configuration file: /var/jenkins_home/tools/hudson.plugins.sonar.SonarRunnerInstallation/my-sonar-scanner/conf/sonar-scanner.properties 2 | INFO: Project root configuration file: /var/jenkins_home/workspace/influxdb-plugin/build-influxdb-plugin/sonar-project.properties 3 | INFO: SonarScanner 4.8.0.2856 4 | INFO: Java 11.0.19 Eclipse Adoptium (64-bit) 5 | INFO: Linux 6.6.12-linuxkit amd64 6 | INFO: User cache: /var/jenkins_home/.sonar/cache 7 | INFO: Analyzing on SonarQube server 9.9.1.69595 8 | INFO: Default locale: "en", source code encoding: "UTF-8" (analysis is platform dependent) 9 | INFO: Load global settings 10 | INFO: Load global settings (done) | time=116ms 11 | INFO: Server id: 147B411E-AY1qOJnrLVom97iUZedE 12 | INFO: User cache: /var/jenkins_home/.sonar/cache 13 | INFO: Load/download plugins 14 | INFO: Load plugins index 15 | INFO: Load plugins index (done) | time=42ms 16 | INFO: Load/download plugins (done) | time=166ms 17 | INFO: Process project properties 18 | INFO: Process project properties (done) | time=27ms 19 | INFO: Execute project builders 20 | INFO: Execute project builders (done) | time=44ms 21 | INFO: Project key: InfluxDBPlugin-log 22 | INFO: Base dir: /var/jenkins_home/workspace/influxdb-plugin/build-influxdb-plugin 23 | INFO: Working dir: /var/jenkins_home/workspace/influxdb-plugin/build-influxdb-plugin/.scannerwork 24 | INFO: Load project settings for component key: 'InfluxDBPlugin-log' 25 | INFO: Load project settings for component key: 'InfluxDBPlugin-log' (done) | time=29ms 26 | INFO: Auto-configuring with CI 'Jenkins' 27 | INFO: Load quality profiles 28 | INFO: Load quality profiles (done) | time=137ms 29 | INFO: Load active rules 30 | INFO: Load active rules (done) | time=2180ms 31 | INFO: Load analysis cache 32 | INFO: Load analysis cache (830 bytes) | time=27ms 33 | INFO: Load project repositories 34 | INFO: Load project repositories (done) | time=23ms 35 | INFO: Indexing files... 36 | INFO: Project configuration: 37 | INFO: 73 files indexed 38 | INFO: 1 file ignored because of scm ignore settings 39 | INFO: Quality profile for java: Sonar way 40 | INFO: Quality profile for json: Sonar way 41 | INFO: Quality profile for web: Sonar way 42 | INFO: Quality profile for yaml: Sonar way 43 | INFO: ------------- Run sensors on module InfluxDBPlugin-log 44 | INFO: Load metrics repository 45 | INFO: Load metrics repository (done) | time=164ms 46 | INFO: Sensor JavaSensor [java] 47 | INFO: Configured Java source version (sonar.java.source): none 48 | INFO: JavaClasspath initialization 49 | INFO: JavaClasspath initialization (done) | time=5ms 50 | INFO: JavaTestClasspath initialization 51 | INFO: JavaTestClasspath initialization (done) | time=1ms 52 | INFO: Server-side caching is enabled. The Java analyzer will not try to leverage data from a previous analysis. 53 | INFO: Using ECJ batch to parse 30 Main java source files with batch size 117 KB. 54 | INFO: Starting batch processing. 55 | INFO: The Java analyzer cannot skip unchanged files in this context. A full analysis is performed for all files. 56 | INFO: 100% analyzed 57 | INFO: Batch processing: Done. 58 | INFO: Did not optimize analysis for any files, performed a full analysis for all 30 files. 59 | WARN: Dependencies/libraries were not provided for analysis of SOURCE files. The 'sonar.java.libraries' property is empty. Verify your configuration, as you might end up with less precise results. 60 | WARN: Dependencies/libraries were not provided for analysis of TEST files. The 'sonar.java.test.libraries' property is empty. Verify your configuration, as you might end up with less precise results. 61 | WARN: Unresolved imports/types have been detected during analysis. Enable DEBUG mode to see them. 62 | WARN: Use of preview features have been detected during analysis. Enable DEBUG mode to see them. 63 | INFO: Using ECJ batch to parse 15 Test java source files with batch size 117 KB. 64 | INFO: Starting batch processing. 65 | INFO: 100% analyzed 66 | INFO: Batch processing: Done. 67 | INFO: Did not optimize analysis for any files, performed a full analysis for all 15 files. 68 | WARN: Unresolved imports/types have been detected during analysis. Enable DEBUG mode to see them. 69 | WARN: Use of preview features have been detected during analysis. Enable DEBUG mode to see them. 70 | INFO: No "Generated" source files to scan. 71 | INFO: Sensor JavaSensor [java] (done) | time=11072ms 72 | INFO: Sensor JaCoCo XML Report Importer [jacoco] 73 | INFO: 'sonar.coverage.jacoco.xmlReportPaths' is not defined. Using default locations: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml,build/reports/jacoco/test/jacocoTestReport.xml 74 | INFO: Importing 1 report(s). Turn your logs in debug mode in order to see the exhaustive list. 75 | INFO: Sensor JaCoCo XML Report Importer [jacoco] (done) | time=147ms 76 | INFO: Sensor IaC CloudFormation Sensor [iac] 77 | INFO: 0 source files to be analyzed 78 | INFO: 0/0 source files have been analyzed 79 | INFO: Sensor IaC CloudFormation Sensor [iac] (done) | time=25ms 80 | INFO: Sensor IaC Kubernetes Sensor [iac] 81 | INFO: 0 source files to be analyzed 82 | INFO: 0/0 source files have been analyzed 83 | INFO: Sensor IaC Kubernetes Sensor [iac] (done) | time=8ms 84 | INFO: Sensor JavaScript inside YAML analysis [javascript] 85 | INFO: No input files found for analysis 86 | INFO: Hit the cache for 0 out of 0 87 | INFO: Miss the cache for 0 out of 0 88 | INFO: Sensor JavaScript inside YAML analysis [javascript] (done) | time=15ms 89 | INFO: Sensor CSS Rules [javascript] 90 | WARN: Error when running: 'node -v'. Is Node.js available during analysis? 91 | INFO: Hit the cache for 0 out of 0 92 | INFO: Miss the cache for 0 out of 0 93 | INFO: Sensor CSS Rules [javascript] (done) | time=10760ms 94 | INFO: Sensor C# Project Type Information [csharp] 95 | INFO: Sensor C# Project Type Information [csharp] (done) | time=1ms 96 | INFO: Sensor C# Analysis Log [csharp] 97 | INFO: Sensor C# Analysis Log [csharp] (done) | time=13ms 98 | INFO: Sensor C# Properties [csharp] 99 | INFO: Sensor C# Properties [csharp] (done) | time=0ms 100 | INFO: Sensor SurefireSensor [java] 101 | INFO: parsing [/var/jenkins_home/workspace/influxdb-plugin/build-influxdb-plugin/target/surefire-reports] 102 | INFO: Sensor SurefireSensor [java] (done) | time=146ms 103 | INFO: Sensor HTML [web] 104 | INFO: Sensor HTML [web] (done) | time=175ms 105 | INFO: Sensor TextAndSecretsSensor [text] 106 | INFO: 67 source files to be analyzed 107 | INFO: 67/67 source files have been analyzed 108 | INFO: Sensor TextAndSecretsSensor [text] (done) | time=173ms 109 | INFO: Sensor VB.NET Project Type Information [vbnet] 110 | INFO: Sensor VB.NET Project Type Information [vbnet] (done) | time=1ms 111 | INFO: Sensor VB.NET Analysis Log [vbnet] 112 | INFO: Sensor VB.NET Analysis Log [vbnet] (done) | time=17ms 113 | INFO: Sensor VB.NET Properties [vbnet] 114 | INFO: Sensor VB.NET Properties [vbnet] (done) | time=0ms 115 | INFO: Sensor IaC Docker Sensor [iac] 116 | INFO: 0 source files to be analyzed 117 | INFO: 0/0 source files have been analyzed 118 | INFO: Sensor IaC Docker Sensor [iac] (done) | time=61ms 119 | INFO: ------------- Run sensors on project 120 | INFO: Sensor Analysis Warnings import [csharp] 121 | INFO: Sensor Analysis Warnings import [csharp] (done) | time=3ms 122 | INFO: Sensor Zero Coverage Sensor 123 | INFO: Sensor Zero Coverage Sensor (done) | time=5ms 124 | INFO: Sensor Java CPD Block Indexer 125 | INFO: Sensor Java CPD Block Indexer (done) | time=92ms 126 | INFO: SCM Publisher SCM provider for this project is: git 127 | INFO: SCM Publisher 2 source files to be analyzed 128 | INFO: SCM Publisher 2/2 source files have been analyzed (done) | time=262ms 129 | INFO: CPD Executor 25 files had no CPD blocks 130 | INFO: CPD Executor Calculating CPD for 25 files 131 | INFO: CPD Executor CPD calculation finished (done) | time=34ms 132 | INFO: Analysis report generated in 161ms, dir size=507.9 kB 133 | INFO: Analysis report compressed in 4801ms, zip size=207.0 kB 134 | INFO: Analysis report uploaded in 342ms 135 | INFO: ANALYSIS SUCCESSFUL, you can find the results at: http://sonarqube:9001/dashboard?id=InfluxDBPlugin-log 136 | INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report 137 | INFO: More about the report processing at http://sonarqube:9001/api/ce/task?id=321EXAMPLE 138 | INFO: Analysis total time: 52.300 s 139 | INFO: ------------------------------------------------------------------------ 140 | INFO: EXECUTION SUCCESS 141 | INFO: ------------------------------------------------------------------------ 142 | INFO: Total time: 54.302s 143 | INFO: Final Memory: 24M/84M 144 | INFO: ------------------------------------------------------------------------ -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/generators/RobotFrameworkPointGenerator.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb.generators; 2 | 3 | import hudson.model.Run; 4 | import hudson.model.TaskListener; 5 | import hudson.plugins.robot.RobotBuildAction; 6 | import hudson.plugins.robot.model.RobotCaseResult; 7 | import hudson.plugins.robot.model.RobotResult; 8 | import hudson.plugins.robot.model.RobotSuiteResult; 9 | import jenkinsci.plugins.influxdb.models.AbstractPoint; 10 | import jenkinsci.plugins.influxdb.renderer.ProjectNameRenderer; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Hashtable; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class RobotFrameworkPointGenerator extends AbstractPointGenerator { 18 | 19 | private static final String RF_NAME = "rf_name"; 20 | private static final String RF_FAILED = "rf_failed"; 21 | private static final String RF_PASSED = "rf_passed"; 22 | private static final String RF_SKIPPED = "rf_skipped"; 23 | private static final String RF_TOTAL = "rf_total"; 24 | private static final String RF_PASS_PERCENTAGE = "rf_pass_percentage"; 25 | private static final String RF_PASS_PERCENTAGE_TOTAL = "rf_pass_percentage_total"; 26 | private static final String RF_SKIP_PERCENTAGE = "rf_skip_percentage"; 27 | private static final String RF_DURATION = "rf_duration"; 28 | private static final String RF_SUITES = "rf_suites"; 29 | private static final String RF_SUITE_NAME = "rf_suite_name"; 30 | private static final String RF_TESTCASES = "rf_testcases"; 31 | private static final String RF_TAG_NAME = "rf_tag_name"; 32 | private static final String RF_AGE = "rf_age"; 33 | 34 | private final String customPrefix; 35 | private final Map tagResults; 36 | 37 | public RobotFrameworkPointGenerator(Run build, TaskListener listener, 38 | ProjectNameRenderer projectNameRenderer, 39 | long timestamp, String jenkinsEnvParameterTag, 40 | String customPrefix) { 41 | super(build, listener, projectNameRenderer, timestamp, jenkinsEnvParameterTag); 42 | this.customPrefix = customPrefix; 43 | tagResults = new Hashtable<>(); 44 | } 45 | 46 | public boolean hasReport() { 47 | RobotBuildAction robotBuildAction = build.getAction(RobotBuildAction.class); 48 | return robotBuildAction != null && robotBuildAction.getResult() != null; 49 | } 50 | 51 | public AbstractPoint[] generate() { 52 | RobotBuildAction robotBuildAction = build.getAction(RobotBuildAction.class); 53 | 54 | List points = new ArrayList<>(); 55 | 56 | points.add(generateOverviewPoint(robotBuildAction)); 57 | points.addAll(generateSubPoints(robotBuildAction.getResult())); 58 | 59 | return points.toArray(new AbstractPoint[0]); 60 | } 61 | 62 | private AbstractPoint generateOverviewPoint(RobotBuildAction robotBuildAction) { 63 | return buildPoint("rf_results", customPrefix, build) 64 | .addField(RF_FAILED, robotBuildAction.getResult().getOverallFailed()) 65 | .addField(RF_PASSED, robotBuildAction.getResult().getOverallPassed()) 66 | .addField(RF_TOTAL, robotBuildAction.getResult().getOverallTotal()) 67 | .addField(RF_SKIPPED, robotBuildAction.getResult().getOverallSkipped()) 68 | .addField(RF_PASS_PERCENTAGE, robotBuildAction.getOverallPassPercentage()) 69 | .addField(RF_PASS_PERCENTAGE_TOTAL, robotBuildAction.getPassPercentageWithSkipped()) 70 | .addField(RF_SKIP_PERCENTAGE, robotBuildAction.getResult().getSkipPercentage()) 71 | .addField(RF_DURATION, robotBuildAction.getResult().getDuration()) 72 | .addField(RF_SUITES, robotBuildAction.getResult().getAllSuites().size()); 73 | } 74 | 75 | private List generateSubPoints(RobotResult robotResult) { 76 | List subPoints = new ArrayList<>(); 77 | TimeGenerator suiteResultTime = new TimeGenerator(timestamp); 78 | 79 | for (RobotSuiteResult suiteResult : robotResult.getAllSuites()) { 80 | long caseTimeStamp = suiteResultTime.next(); 81 | subPoints.add(generateSuitePoint(suiteResult, caseTimeStamp)); 82 | // To preserve the existing functionality of the case being timestamps after the 83 | // suiteResult, seed the new TimeGenerator with the suiteResult's time 84 | TimeGenerator caseResultTime = new TimeGenerator(caseTimeStamp); 85 | for (RobotCaseResult caseResult : suiteResult.getAllCases()) { 86 | AbstractPoint casePoint = generateCasePoint(caseResult, caseResultTime.next()); 87 | if (casePointExists(subPoints, casePoint)) { 88 | continue; 89 | } 90 | subPoints.add(casePoint); 91 | } 92 | } 93 | 94 | TimeGenerator tagTime = new TimeGenerator(timestamp); 95 | for (Map.Entry entry : tagResults.entrySet()) { 96 | subPoints.add(generateTagPoint(entry.getValue(), tagTime.next())); 97 | } 98 | return subPoints; 99 | } 100 | 101 | private boolean casePointExists(List subPoints, AbstractPoint point) { 102 | for (AbstractPoint p : subPoints) { 103 | try { 104 | // CasePoints are the same if all the fields are equal 105 | String pFields = p.toString().substring(p.toString().indexOf("fields=")); 106 | String pointFields = point.toString().substring(point.toString().indexOf("fields=")); 107 | if (pFields.equals(pointFields)) { 108 | return true; 109 | } 110 | } catch (StringIndexOutOfBoundsException e) { 111 | // Handle exception 112 | } 113 | } 114 | return false; 115 | } 116 | 117 | private AbstractPoint generateCasePoint(RobotCaseResult caseResult, long timestamp) { 118 | AbstractPoint point = buildPoint("testcase_point", customPrefix, build, timestamp) 119 | .addField(RF_NAME, caseResult.getName()) 120 | .addField(RF_SUITE_NAME, caseResult.getParent().getName()) 121 | .addField(RF_FAILED, caseResult.getFailed()) 122 | .addField(RF_PASSED, caseResult.getPassed()) 123 | .addField(RF_SKIPPED, caseResult.getSkipped()) 124 | .addField(RF_DURATION, caseResult.getDuration()) 125 | .addField(RF_AGE, caseResult.getAge()); 126 | 127 | for (String tag : caseResult.getTags()) { 128 | markTagResult(tag, caseResult); 129 | } 130 | 131 | return point; 132 | } 133 | 134 | private void markTagResult(String tag, RobotCaseResult caseResult) { 135 | if (tagResults.get(tag) == null) 136 | tagResults.put(tag, new RobotTagResult(tag)); 137 | 138 | RobotTagResult tagResult = tagResults.get(tag); 139 | if (!tagResult.testCases.contains(caseResult.getDuplicateSafeName())) { 140 | tagResult.failed += caseResult.getFailed(); 141 | tagResult.passed += caseResult.getPassed(); 142 | tagResult.skipped += caseResult.getSkipped(); 143 | tagResult.duration += caseResult.getDuration(); 144 | tagResult.testCases.add(caseResult.getDuplicateSafeName()); 145 | } 146 | } 147 | 148 | private AbstractPoint generateTagPoint(RobotTagResult tagResult, long timestamp) { 149 | return buildPoint("tag_point", customPrefix, build, timestamp) 150 | .addField(RF_TAG_NAME, tagResult.name) 151 | .addField(RF_FAILED, tagResult.failed) 152 | .addField(RF_PASSED, tagResult.passed) 153 | .addField(RF_SKIPPED, tagResult.skipped) 154 | .addField(RF_TOTAL, tagResult.passed + tagResult.failed) 155 | .addField(RF_DURATION, tagResult.duration); 156 | } 157 | 158 | private AbstractPoint generateSuitePoint(RobotSuiteResult suiteResult, long timestamp) { 159 | return buildPoint("suite_result", customPrefix, build, timestamp) 160 | .addField(RF_SUITE_NAME, suiteResult.getName()) 161 | .addField(RF_TESTCASES, suiteResult.getAllCases().size()) 162 | .addField(RF_FAILED, suiteResult.getFailed()) 163 | .addField(RF_PASSED, suiteResult.getPassed()) 164 | .addField(RF_SKIPPED, suiteResult.getSkipped()) 165 | .addField(RF_TOTAL, suiteResult.getTotal()) 166 | .addField(RF_DURATION, suiteResult.getDuration()); 167 | } 168 | 169 | private static final class RobotTagResult { 170 | 171 | private final String name; 172 | private final List testCases = new ArrayList<>(); 173 | private int failed = 0; 174 | private int passed = 0; 175 | private int skipped = 0; 176 | private long duration = 0; 177 | 178 | private RobotTagResult(String name) { 179 | this.name = name; 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/jenkinsci/plugins/influxdb/InfluxDbPublisher.java: -------------------------------------------------------------------------------- 1 | package jenkinsci.plugins.influxdb; 2 | 3 | import hudson.EnvVars; 4 | import hudson.Extension; 5 | import hudson.FilePath; 6 | import hudson.Launcher; 7 | import hudson.model.AbstractProject; 8 | import hudson.model.ModelObject; 9 | import hudson.model.Run; 10 | import hudson.model.TaskListener; 11 | import hudson.tasks.BuildStepDescriptor; 12 | import hudson.tasks.BuildStepMonitor; 13 | import hudson.tasks.Notifier; 14 | import hudson.tasks.Publisher; 15 | import hudson.util.ListBoxModel; 16 | import jenkins.model.Jenkins; 17 | import jenkins.tasks.SimpleBuildStep; 18 | import jenkinsci.plugins.influxdb.models.Target; 19 | import org.kohsuke.stapler.DataBoundConstructor; 20 | import org.kohsuke.stapler.DataBoundSetter; 21 | 22 | import javax.annotation.Nonnull; 23 | import java.io.IOException; 24 | import java.util.Collections; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Objects; 28 | import java.util.concurrent.CopyOnWriteArrayList; 29 | 30 | public class InfluxDbPublisher extends Notifier implements SimpleBuildStep { 31 | 32 | public static final String DEFAULT_MEASUREMENT_NAME = "jenkins_data"; 33 | 34 | private String selectedTarget; 35 | private String customProjectName; 36 | private String customPrefix; 37 | private Map customData; 38 | private Map customDataTags; 39 | private Map> customDataMap; 40 | private Map> customDataMapTags; 41 | private String jenkinsEnvParameterField; 42 | private String jenkinsEnvParameterTag; 43 | private String measurementName; 44 | private EnvVars env; 45 | 46 | @DataBoundConstructor 47 | public InfluxDbPublisher(String selectedTarget) { 48 | this.selectedTarget = selectedTarget; 49 | } 50 | 51 | public String getSelectedTarget() { 52 | String target = selectedTarget; 53 | Jenkins jenkins = Jenkins.getInstanceOrNull(); 54 | if (target == null && jenkins != null) { 55 | List targets = jenkins.getDescriptorByType(DescriptorImpl.class).getTargets(); 56 | if (!targets.isEmpty()) { 57 | target = targets.get(0).getDescription(); 58 | } 59 | } 60 | return target; 61 | } 62 | 63 | @DataBoundSetter 64 | public void setSelectedTarget(String target) { 65 | Objects.requireNonNull(target); 66 | this.selectedTarget = target; 67 | } 68 | 69 | public String getCustomProjectName() { 70 | return customProjectName; 71 | } 72 | 73 | @DataBoundSetter 74 | public void setCustomProjectName(String customProjectName) { 75 | this.customProjectName = customProjectName; 76 | } 77 | 78 | public String getCustomPrefix() { 79 | return customPrefix; 80 | } 81 | 82 | @DataBoundSetter 83 | public void setCustomPrefix(String customPrefix) { 84 | this.customPrefix = customPrefix; 85 | } 86 | 87 | public Map getCustomData() { 88 | return customData; 89 | } 90 | 91 | @DataBoundSetter 92 | public void setCustomData(Map customData) { 93 | this.customData = customData; 94 | } 95 | 96 | public Map getCustomDataTags() { 97 | return customDataTags; 98 | } 99 | 100 | @DataBoundSetter 101 | public void setCustomDataTags(Map customDataTags) { 102 | this.customDataTags = customDataTags; 103 | } 104 | 105 | public Map> getCustomDataMap() { 106 | return customDataMap; 107 | } 108 | 109 | @DataBoundSetter 110 | public void setCustomDataMap(Map> customDataMap) { 111 | this.customDataMap = customDataMap; 112 | } 113 | 114 | public Map> getCustomDataMapTags() { 115 | return customDataMapTags; 116 | } 117 | 118 | @DataBoundSetter 119 | public void setCustomDataMapTags(Map> customDataMapTags) { 120 | this.customDataMapTags = customDataMapTags; 121 | } 122 | 123 | public String getJenkinsEnvParameterField() { 124 | return jenkinsEnvParameterField; 125 | } 126 | 127 | @DataBoundSetter 128 | public void setJenkinsEnvParameterField(String jenkinsEnvParameterField) { 129 | this.jenkinsEnvParameterField = jenkinsEnvParameterField; 130 | } 131 | 132 | public String getJenkinsEnvParameterTag() { 133 | return jenkinsEnvParameterTag; 134 | } 135 | 136 | @DataBoundSetter 137 | public void setJenkinsEnvParameterTag(String jenkinsEnvParameterTag) { 138 | this.jenkinsEnvParameterTag = jenkinsEnvParameterTag; 139 | } 140 | 141 | public String getMeasurementName() { 142 | return measurementName; 143 | } 144 | 145 | @DataBoundSetter 146 | public void setMeasurementName(String measurementName) { 147 | this.measurementName = measurementName; 148 | } 149 | 150 | private String getMeasurementNameIfNotBlankOrDefault() { 151 | return measurementName != null ? measurementName : DEFAULT_MEASUREMENT_NAME; 152 | } 153 | 154 | public Target getTarget() { 155 | Jenkins jenkins = Jenkins.getInstanceOrNull(); 156 | if (jenkins != null) { 157 | List targets = jenkins.getDescriptorByType(DescriptorImpl.class).getTargets(); 158 | if (selectedTarget == null && !targets.isEmpty()) { 159 | return targets.get(0); 160 | } 161 | for (Target target : targets) { 162 | String targetInfo = target.getDescription(); 163 | if (targetInfo.equals(selectedTarget)) { 164 | return target; 165 | } 166 | } 167 | } 168 | return null; 169 | } 170 | 171 | public void setEnv(EnvVars env) { 172 | this.env = env; 173 | } 174 | 175 | //@Override 176 | public boolean prebuild(Run build, TaskListener listener) { 177 | return true; 178 | } 179 | 180 | @Override 181 | public boolean needsToRunAfterFinalized() { 182 | return true; 183 | } 184 | 185 | @Override 186 | public BuildStepMonitor getRequiredMonitorService() { 187 | return BuildStepMonitor.NONE; 188 | } 189 | 190 | @Override 191 | public void perform(@Nonnull Run build, @Nonnull FilePath workspace, @Nonnull EnvVars envVars, @Nonnull Launcher launcher, @Nonnull TaskListener listener) 192 | throws InterruptedException, IOException { 193 | 194 | // Gets the target from the job's config 195 | Target target = getTarget(); 196 | if (target == null) { 197 | throw new RuntimeException("Target was null!"); 198 | } 199 | 200 | // Get the current time for timestamping all point generation and convert to nanoseconds 201 | long currTime = resolveTimestampForPointGenerationInNanoseconds(build); 202 | 203 | measurementName = getMeasurementNameIfNotBlankOrDefault(); 204 | if (env == null) { 205 | // env = build.getEnvironment(listener); 206 | env = envVars; 207 | } 208 | 209 | String expandedCustomPrefix = env.expand(customPrefix); 210 | String expandedCustomProjectName = env.expand(customProjectName); 211 | 212 | // Preparing the service 213 | InfluxDbPublicationService publicationService = new InfluxDbPublicationService( 214 | Collections.singletonList(target), 215 | expandedCustomProjectName, 216 | expandedCustomPrefix, 217 | customData, 218 | customDataTags, 219 | customDataMapTags, 220 | customDataMap, 221 | currTime, 222 | jenkinsEnvParameterField, 223 | jenkinsEnvParameterTag, 224 | measurementName); 225 | 226 | // Publishes the metrics 227 | publicationService.perform(build, listener, env); 228 | } 229 | 230 | private long resolveTimestampForPointGenerationInNanoseconds(Run build) { 231 | long timestamp = getTarget().isJobScheduledTimeAsPointsTimestamp() ? build.getTimeInMillis() : System.currentTimeMillis(); 232 | return timestamp * 1000000; 233 | } 234 | 235 | @Extension(optional = true) 236 | public static final class DescriptorImpl extends BuildStepDescriptor implements ModelObject { 237 | 238 | private List targets = new CopyOnWriteArrayList<>(); 239 | 240 | public DescriptorImpl() { 241 | load(); 242 | } 243 | 244 | @Nonnull 245 | @Deprecated 246 | public Target[] getDeprecatedTargets() { 247 | return targets.toArray(new Target[0]); 248 | } 249 | 250 | @DataBoundSetter 251 | @Deprecated 252 | public void setDeprecatedTargets(List targets) { 253 | this.targets = targets; 254 | } 255 | 256 | public List getTargets() { 257 | return InfluxDbGlobalConfig.getInstance().getTargets(); 258 | } 259 | 260 | @Nonnull 261 | @Override 262 | public String getDisplayName() { 263 | return "Publish build data to InfluxDB"; 264 | } 265 | 266 | @Override 267 | public boolean isApplicable(Class jobType) { 268 | return true; 269 | } 270 | 271 | public ListBoxModel doFillSelectedTargetItems() { 272 | ListBoxModel model = new ListBoxModel(); 273 | for (Target target : getTargets()) { 274 | model.add(target.getDescription()); 275 | } 276 | return model; 277 | } 278 | 279 | void removeDeprecatedTargets() { 280 | this.targets = new CopyOnWriteArrayList<>(); 281 | } 282 | } 283 | } 284 | --------------------------------------------------------------------------------