├── .gitignore ├── .github ├── CODEOWNERS ├── release-drafter.yml ├── dependabot.yml └── workflows │ ├── release-drafter.yml │ └── jenkins-security-scan.yml ├── .mvn ├── maven.config └── extensions.xml ├── src ├── main │ ├── resources │ │ ├── io │ │ │ └── jenkins │ │ │ │ └── plugins │ │ │ │ └── genericchart │ │ │ │ ├── GenericChartProjectAction │ │ │ │ ├── declareChartJsClickArray.js │ │ │ │ ├── floatingBox.jelly │ │ │ │ └── chartLogicBox.js │ │ │ │ ├── ChartModel │ │ │ │ ├── help-rangeAroundAlist.html │ │ │ │ ├── help-limit.html │ │ │ │ ├── help-resultDenyList.html │ │ │ │ ├── help-resultAllowList.html │ │ │ │ ├── config.jelly │ │ │ │ └── help-unstableCondition.html │ │ │ │ ├── GenericChartColumn │ │ │ │ ├── help-rangeAroundAlist.html │ │ │ │ ├── help-limit.html │ │ │ │ ├── help-resultDenyList.html │ │ │ │ ├── help-resultAllowList.html │ │ │ │ ├── config.jelly │ │ │ │ ├── column.jelly │ │ │ │ └── chartLogicColumn.js │ │ │ │ ├── GenericChartGlobalConfig │ │ │ │ ├── config.jelly │ │ │ │ └── help-customEmbeddedFunctions.html │ │ │ │ ├── GenericChartPublisher │ │ │ │ └── config.jelly │ │ │ │ └── presetEquations │ │ └── index.jelly │ └── java │ │ └── io │ │ └── jenkins │ │ └── plugins │ │ └── genericchart │ │ ├── GenericChartGlobalConfig.java │ │ ├── ChartPointsWithBlacklist.java │ │ ├── ChartPoint.java │ │ ├── GenericChartProjectAction.java │ │ ├── ReportChart.java │ │ ├── ChartModel.java │ │ ├── GenericChartColumn.java │ │ ├── GenericChartPublisher.java │ │ ├── PresetEquationsManager.java │ │ └── PropertiesParser.java └── test │ └── java │ └── io │ └── jenkins │ └── plugins │ └── genericchart │ └── PresetEquationsManagerTest.java ├── Jenkinsfile ├── LICENSE ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .idea/ 3 | *.iml 4 | work/ 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/report-generic-chart-column-plugin-developers 2 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | tag-template: report-generic-chart-column-$NEXT_MINOR_VERSION 3 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Pconsume-incrementals 4 | -Pmight-produce-incrementals 5 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartProjectAction/declareChartJsClickArray.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/ChartModel/help-rangeAroundAlist.html: -------------------------------------------------------------------------------- 1 |
2 | number of points around whitelist (before and after every point) shown in the results 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartColumn/help-rangeAroundAlist.html: -------------------------------------------------------------------------------- 1 |
2 | number of points around whitelist (before and after every point) shown in the results 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Plugin to draw a generic chart from poperties to projec page or view page.
4 | Next to drawing, it allows you to build mathematical model, which checks the behavior of your charts
5 |
6 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin( 6 | useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests 7 | configurations: [ 8 | [platform: 'linux', jdk: 17], 9 | [platform: 'windows', jdk: 11], 10 | ]) 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: maven 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: monthly 13 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/ChartModel/help-limit.html: -------------------------------------------------------------------------------- 1 |
2 | "Number of builds to show" say how many graph points are shown in the results.
3 | If you haven't black-list empty then it'll show same number of points.
4 | If you haven't white-list empty then it'll show all available points (white-listed) with limit of number selected. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/ChartModel/help-resultDenyList.html: -------------------------------------------------------------------------------- 1 |
2 | Builds denylisted (java regex, space separated) from being shown in results (to keep zoomed reuslts readable) and/or fitler specific brach. For example:
3 | .*2\.6\.6\.0.*
4 | Denylist has always priority over the WhiteList 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartColumn/help-limit.html: -------------------------------------------------------------------------------- 1 |
2 | "Number of builds to show" say how many graph points are shown in the results.
3 | If you haven't black-list empty then it'll show same number of points.
4 | If you haven't white-list empty then it'll show all available points (white-listed) with limit of number selected. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartColumn/help-resultDenyList.html: -------------------------------------------------------------------------------- 1 |
2 | Builds denylisted (java regex, space separated) from being shown in results (to keep zoomed reuslts readable) and/or fitler specific brach. For example:
3 | .*2\.6\.6\.0.*
4 | Denylist has always priority over the Allowlist 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/ChartModel/help-resultAllowList.html: -------------------------------------------------------------------------------- 1 |
2 | Builds allowlisted (java regex, space separated) are shown in results (to allow filtering of individual job IDs) show only specific brach or similarly. For example:
3 | .*2\.6\.6\.0.*
4 | Denylist has always priority over the WhiteList 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartColumn/help-resultAllowList.html: -------------------------------------------------------------------------------- 1 |
2 | Builds allowlist (java regex, space separated) are shown in results (to allow filtering of individual job IDs) show only specific brach or similarly. For example:
3 | .*2\.6\.6\.0.*
4 | Denylist has always priority over the Allowlist 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartGlobalConfig/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.6 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Automates creation of Release Drafts using Release Drafter 2 | # More Info: https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | - main 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/main/resources/io/jenkins/plugins/genericchart/GenericChartPublisher/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | # More information about the Jenkins security scan can be found at the developer docs: https://www.jenkins.io/redirect/jenkins-security-scan/ 2 | 3 | name: Jenkins Security Scan 4 | on: 5 | push: 6 | branches: 7 | - "master" 8 | - "main" 9 | pull_request: 10 | types: [ opened, synchronize, reopened ] 11 | workflow_dispatch: 12 | 13 | permissions: 14 | security-events: write 15 | contents: read 16 | actions: read 17 | 18 | jobs: 19 | security-scan: 20 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 21 | with: 22 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 23 | java-version: 11 # What version of Java to set up for the build. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Stanislav Baiduzhyi 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartColumn/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/ChartModel/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartGlobalConfig/help-customEmbeddedFunctions.html: -------------------------------------------------------------------------------- 1 |
2 | Single line url pointing to file with definitions. eg:
3 | https://raw.githubusercontent.com/judovana/jenkins-report-generic-chart-column/master/src/main/resources/io/jenkins/plugins/genericchart/presetEquations
4 | or
5 | The definitions itself. See 6 | examples 7 | Note, that id is mandatory: 8 | more ifo 9 | The syntax is as you saw 10 | 11 | above: 12 | 22 |
-------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/GenericChartGlobalConfig.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.genericchart; 2 | 3 | import net.sf.json.JSONObject; 4 | 5 | import org.kohsuke.stapler.DataBoundConstructor; 6 | import org.kohsuke.stapler.DataBoundSetter; 7 | import org.kohsuke.stapler.StaplerRequest; 8 | 9 | import java.util.logging.Logger; 10 | 11 | import hudson.Extension; 12 | import jenkins.model.GlobalConfiguration; 13 | 14 | @Extension 15 | public class GenericChartGlobalConfig extends GlobalConfiguration { 16 | private static Logger logger = Logger.getLogger(GenericChartGlobalConfig.class.getName()); 17 | 18 | String customEmbeddedFunctions; 19 | 20 | public static GenericChartGlobalConfig getInstance() { 21 | return GlobalConfiguration.all().get(GenericChartGlobalConfig.class); 22 | } 23 | 24 | public boolean isDiffToolUrlSet() { 25 | return customEmbeddedFunctions != null && !customEmbeddedFunctions.trim().isEmpty(); 26 | } 27 | 28 | public String getCustomEmbeddedFunctions() { 29 | return customEmbeddedFunctions; 30 | } 31 | 32 | @DataBoundSetter 33 | public void setCustomEmbeddedFunctions(String customEmbeddedFunctions) { 34 | PresetEquationsManager.resetCached(); 35 | this.customEmbeddedFunctions = customEmbeddedFunctions; 36 | } 37 | 38 | @DataBoundConstructor 39 | public GenericChartGlobalConfig(String diffToolUrl) { 40 | this.customEmbeddedFunctions = diffToolUrl; 41 | } 42 | 43 | public GenericChartGlobalConfig() { 44 | load(); 45 | } 46 | 47 | 48 | @Override 49 | public boolean configure(StaplerRequest req, JSONObject json) throws FormException { 50 | req.bindJSON(this, json); 51 | save(); 52 | return super.configure(req, json); 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartProjectAction/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 15 | 20 | 23 | 28 |
29 |

${chart.title}

30 | 31 | 32 |
33 |
34 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/ChartPointsWithBlacklist.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 root. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package io.jenkins.plugins.genericchart; 25 | 26 | import java.util.List; 27 | 28 | public class ChartPointsWithBlacklist { 29 | 30 | private final List points; 31 | private final List blacklisted; 32 | private final List whitelisted; 33 | private final int whiteListSizeWithoutSurroundings; 34 | 35 | public ChartPointsWithBlacklist(List points, List blacklisted, List whitelisted, int whiteListSizeWithoutSurroundings) { 36 | this.blacklisted = blacklisted; 37 | this.points = points; 38 | this.whitelisted = whitelisted; 39 | this.whiteListSizeWithoutSurroundings = whiteListSizeWithoutSurroundings; 40 | } 41 | 42 | public List getPoints() { 43 | return points; 44 | } 45 | 46 | public List getBlacklist() { 47 | return blacklisted; 48 | } 49 | 50 | public List getWhitelist() { 51 | return whitelisted; 52 | } 53 | 54 | public int getWhiteListSizeWithoutSurroundings() { 55 | return whiteListSizeWithoutSurroundings; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/ChartPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 user. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package io.jenkins.plugins.genericchart; 25 | 26 | import io.jenkins.plugins.chartjs.Chartjs; 27 | 28 | public class ChartPoint { 29 | 30 | private final String buildName; 31 | private final String buildNameShortened; 32 | private final int buildNumber; 33 | private final String value; 34 | private final String pointColor; 35 | 36 | public ChartPoint(String buildName, int buildNumber, String value, String pointColor) { 37 | this.buildName = buildName; 38 | this.buildNameShortened = Chartjs.getShortName(buildName, buildNumber); 39 | this.buildNumber = buildNumber; 40 | this.value = value; 41 | this.pointColor = pointColor; 42 | } 43 | 44 | public String getBuildName() { 45 | return buildName; 46 | } 47 | 48 | public String getBuildNameShortened() { 49 | return buildNameShortened; 50 | } 51 | 52 | public int getBuildNumber() { 53 | return buildNumber; 54 | } 55 | 56 | public String getValue() { 57 | return value; 58 | } 59 | 60 | public String getPointColor() { 61 | return pointColor; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartColumn/column.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | No data yet 13 | 14 | 15 | 18 | 23 | 26 | 29 | 32 | 37 |
38 | 39 |
40 |
41 |
42 | 43 |
44 | 45 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/GenericChartProjectAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 user. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package io.jenkins.plugins.genericchart; 25 | 26 | import hudson.model.Action; 27 | import hudson.model.Job; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | import java.util.stream.Collectors; 31 | 32 | public class GenericChartProjectAction implements Action { 33 | 34 | private final Job job; 35 | private final List charts; 36 | 37 | public GenericChartProjectAction(Job job, List charts) { 38 | this.job = job; 39 | this.charts = charts; 40 | } 41 | 42 | public List getCharts() { 43 | if (charts == null || charts.isEmpty()) { 44 | return new ArrayList<>(); 45 | } 46 | PropertiesParser parser = new PropertiesParser(); 47 | List list = charts.stream() 48 | .sequential() 49 | .map(m -> ReportChart.createReportChart(m, parser, job)) 50 | .filter(r -> r.getPoints() != null && r.getPoints().size() > 0) 51 | .collect(Collectors.toList()); 52 | return list; 53 | } 54 | 55 | @Override 56 | public String getIconFileName() { 57 | return null; 58 | } 59 | 60 | @Override 61 | public String getDisplayName() { 62 | return null; 63 | } 64 | 65 | @Override 66 | public String getUrlName() { 67 | return null; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.jenkins-ci.plugins 6 | plugin 7 | 4.65 8 | 9 | io.jenkins.plugins 10 | report-generic-chart-column 11 | ${revision}${changelist} 12 | hpi 13 | Chart Column from properties 14 | 15 | 4.0 16 | -SNAPSHOT 17 | jenkinsci/report-generic-chart-column-plugin 18 | 4.2.1.5 19 | 2.361.4 20 | 21 | 2.361.x 22 | 23 | 24 | 25 | 26 | judovana 27 | judovana@gmail.com 28 | 29 | 30 | 31 | 32 | MIT 33 | https://www.opensource.org/licenses/mit-license.php 34 | 35 | 36 | 37 | https://github.com/jenkinsci/${project.artifactId}-plugin 38 | 39 | scm:git:https://github.com/${gitHubRepo}.git 40 | scm:git:git@github.com:${gitHubRepo}.git 41 | https://github.com/${gitHubRepo} 42 | ${scmTag} 43 | 44 | 45 | 46 | 47 | repo.jenkins-ci.org 48 | https://repo.jenkins-ci.org/public/ 49 | 50 | 51 | 52 | 53 | repo.jenkins-ci.org 54 | https://repo.jenkins-ci.org/public/ 55 | 56 | 57 | 58 | 59 | 60 | io.jenkins.tools.bom 61 | bom-${bom.version} 62 | 63 | 2102.v854b_fec19c92 64 | import 65 | pom 66 | 67 | 68 | 69 | 70 | 71 | io.jenkins.plugins 72 | chartjs-api 73 | ${chartjs.version} 74 | 75 | 76 | com.github.gbenroscience 77 | parser-ng 78 | 0.1.9-release 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/ReportChart.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 user. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package io.jenkins.plugins.genericchart; 25 | 26 | import hudson.model.Job; 27 | 28 | import java.util.List; 29 | 30 | public class ReportChart { 31 | 32 | private final String title; 33 | private final String color; 34 | private final List points; 35 | private final List blist; 36 | private final List wlist; 37 | private final int rangeAroundWlist; 38 | private final int whiteListSizeWithoutSurroundings; 39 | private final String unstableCondition; 40 | 41 | private ReportChart(String title, String color, String unstableCondition, List points, List blist, List wlist, int rangeAroundWlist, int whiteListSizeWithoutSurroundings) { 42 | this.blist = blist; 43 | this.title = title; 44 | this.color = color; 45 | this.unstableCondition = unstableCondition; 46 | this.points = points; 47 | this.wlist = wlist; 48 | this.rangeAroundWlist = rangeAroundWlist; 49 | this.whiteListSizeWithoutSurroundings = whiteListSizeWithoutSurroundings; 50 | } 51 | 52 | public static ReportChart createReportChart(ChartModel m, PropertiesParser parser, Job job) { 53 | final ChartPointsWithBlacklist points = parser.getReportPointsWithBlacklist(job, m); 54 | return new ReportChart( 55 | m.getTitle(), 56 | m.getChartColor(), 57 | m.getUnstableCondition(), 58 | points.getPoints(), 59 | points.getBlacklist(), 60 | points.getWhitelist(), 61 | m.getRangeAroundAlist(), 62 | points.getWhiteListSizeWithoutSurroundings()); 63 | } 64 | 65 | public String getTitle() { 66 | return title + " (dennied " + blist.size() + ")" + " (allowed " + whiteListSizeWithoutSurroundings + "+" + Integer.toString(wlist.size() - whiteListSizeWithoutSurroundings) + ")"; 67 | } 68 | 69 | public String getColor() { 70 | return color; 71 | } 72 | 73 | public List getPoints() { 74 | return points; 75 | } 76 | 77 | public int getRangeAroundWlist() { 78 | return rangeAroundWlist; 79 | } 80 | 81 | public int getWhiteListSizeWithoutSurroundings() { 82 | return whiteListSizeWithoutSurroundings; 83 | } 84 | 85 | public String getUnstableCondition() { 86 | return unstableCondition; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartProjectAction/chartLogicBox.js: -------------------------------------------------------------------------------- 1 | // (s.trim())); 3 | if (genericChart_ids != null) { 4 | for (let i = 0; i < genericChart_ids.length; i++) { 5 | var id = genericChart_ids[i] 6 | if (id == null) { 7 | continue; 8 | } 9 | var data_title_element = document.getElementById('genericChart-title-'+id) 10 | if (data_title_element == null){ 11 | continue; 12 | } else { 13 | var data_title = data_title_element.textContent.trim(); 14 | } 15 | var data_builds_element = document.getElementById('genericChart-builds-'+id) 16 | if (data_builds_element == null){ 17 | continue; 18 | } else { 19 | var data_builds = data_builds_element.textContent.split(/\s*,\s*/).flatMap((s) => (s.trim())); 20 | } 21 | var data_color_element = document.getElementById('genericChart-color-'+id) 22 | if (data_color_element == null){ 23 | continue; 24 | } else { 25 | var data_color = data_color_element.textContent.trim(); 26 | } 27 | var data_values_element = document.getElementById('genericChart-values-'+id) 28 | if (data_values_element == null){ 29 | continue; 30 | } else { 31 | var data_values = data_values_element.textContent.split(/\s*,\s*/).flatMap((s) => (s.trim())); 32 | } 33 | 34 | var allPerf = { 35 | type: 'line', 36 | data: { 37 | labels: data_builds, 38 | datasets: [ 39 | { 40 | label: data_title, 41 | fill: true, 42 | backgroundColor: data_color, 43 | borderColor: data_color, 44 | pointBorderColor: "#808080", 45 | pointHoverBackgroundColor: "#fff", 46 | pointHoverBorderColor: "rgba(0,0,0,1)", 47 | pointRadius: 5, 48 | data: data_values 49 | } 50 | ] 51 | }, 52 | options: { 53 | plugins: { 54 | legend: { display: false } 55 | }, 56 | interaction: { 57 | mode: 'index', 58 | intersect: false 59 | }, 60 | onClick: (e) => { 61 | var chart = e.chart; 62 | var activePoints = chart.getElementsAtEventForMode(e, 'index', { intersect: false }, true); 63 | var point = activePoints[0] 64 | var datasetIndex = point.datasetIndex //labels are for all data together, no need to look into exact dataset 65 | var index = point.index 66 | var result = chart.config.data.labels[index] 67 | var buildId = result.substring(result.lastIndexOf(":") + 1) 68 | window.open("" + buildId, "_blank"); 69 | } 70 | } 71 | }; 72 | var ctx = document.getElementById("perChartId"+i).getContext("2d"); 73 | perfChartJsCharts["perChartId"+i] = new Chart(ctx, allPerf); 74 | 75 | } 76 | // ]]> 77 | } -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/GenericChartColumn/chartLogicColumn.js: -------------------------------------------------------------------------------- 1 | // (s.trim())); 28 | } 29 | var data_color_element = document.getElementById('genericChart-color-'+id) 30 | if (data_color_element == null) { 31 | continue; 32 | } else { 33 | var data_color = data_color_element.textContent.trim(); 34 | } 35 | var data_values_element = document.getElementById('genericChart-values-'+id) 36 | if (data_values_element == null) { 37 | continue; 38 | } else { 39 | var data_values = data_values_element.textContent.split(/\s*,\s*/).flatMap((s) => (s.trim())); 40 | } 41 | var data_url_element = document.getElementById('genericChart-url-'+id) 42 | if (data_url_element == null) { 43 | continue; 44 | } else { 45 | var data_url = data_url_element.textContent.trim(); 46 | } 47 | 48 | 49 | if (typeof chartNameVar == 'undefined') { 50 | var chartNameVar = {}; 51 | } 52 | 53 | var dataPerfView = { 54 | type: 'line', 55 | url_from_job: data_url, 56 | data: { 57 | labels: data_builds, 58 | datasets: [{ 59 | label: data_title, 60 | fill: true, 61 | backgroundColor: data_color, 62 | borderColor: data_color, 63 | pointBackgroundColor: data_color, 64 | pointBorderColor: "#fff", 65 | pointHoverBackgroundColor: "#fff", 66 | pointHoverBorderColor: data_color, 67 | pointRadius: 4, 68 | data: data_values 69 | } 70 | ] 71 | }, 72 | options: { 73 | plugins: { 74 | legend: { display: false } 75 | }, 76 | interaction: { 77 | mode: 'index', 78 | intersect: false 79 | }, 80 | onClick: (e) => { 81 | var chart = e.chart; 82 | var activePoints = chart.getElementsAtEventForMode(e, 'index', { intersect: false }, true); 83 | var point = activePoints[0] 84 | var datasetIndex = point.datasetIndex //labels are for all data together, no need to look into exact dataset 85 | var index = point.index 86 | var result = chart.config.data.labels[index] 87 | var buildId = result.substring(result.lastIndexOf(":") + 1) 88 | window.open("/"+chart.config._config.url_from_job+buildId, "_blank"); 89 | } 90 | } 91 | }; 92 | var ctx = document.getElementById(id+"-Chart").getContext("2d"); 93 | chartNameVar[id] = new Chart(ctx, dataPerfView) 94 | } 95 | } 96 | 97 | window.addEventListener('load', readCharts, false); 98 | // ]]> -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/ChartModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 user. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package io.jenkins.plugins.genericchart; 25 | 26 | import io.jenkins.plugins.chartjs.ColorChanger; 27 | import hudson.Extension; 28 | import hudson.model.AbstractDescribableImpl; 29 | import hudson.model.Descriptor; 30 | import org.kohsuke.stapler.DataBoundConstructor; 31 | import org.kohsuke.stapler.DataBoundSetter; 32 | 33 | public class ChartModel extends AbstractDescribableImpl { 34 | 35 | private String title; 36 | private String fileNameGlob; 37 | @SuppressWarnings({"lgtm[jenkins/plaintext-storage]", "This is not a password, it is key in properties file"}) 38 | private String key; 39 | private int limit; 40 | private String resultDenyList; 41 | private String resultAllowList; 42 | private String chartColor; 43 | private int rangeAroundAlist; 44 | private String unstableCondition; //30%< or != or < 1.5 ... 45 | 46 | @DataBoundConstructor 47 | public ChartModel(String title, String fileNameGlob, String key, int limit, String chartColor, int rangeAroundAlist) { 48 | this.title = title; 49 | this.fileNameGlob = fileNameGlob; 50 | this.key = key; 51 | this.limit = limit; 52 | if (chartColor == null || chartColor.isEmpty()) { 53 | this.chartColor = ColorChanger.randomColor(); 54 | } else { 55 | this.chartColor = chartColor; 56 | } 57 | this.rangeAroundAlist = rangeAroundAlist; 58 | } 59 | 60 | public String getTitle() { 61 | return title; 62 | } 63 | 64 | @DataBoundSetter 65 | public void setTitle(String title) { 66 | this.title = title; 67 | } 68 | 69 | public String getFileNameGlob() { 70 | return fileNameGlob; 71 | } 72 | 73 | @DataBoundSetter 74 | public void setFileNameGlob(String fileNameGlob) { 75 | this.fileNameGlob = fileNameGlob; 76 | } 77 | 78 | public String getKey() { 79 | return key; 80 | } 81 | 82 | @DataBoundSetter 83 | public void setKey(String key) { 84 | this.key = key; 85 | } 86 | 87 | public int getLimit() { 88 | return limit; 89 | } 90 | 91 | @DataBoundSetter 92 | public void setLimit(int limit) { 93 | this.limit = limit; 94 | } 95 | 96 | public String getChartColor() { 97 | return chartColor; 98 | } 99 | 100 | @DataBoundSetter 101 | public void setChartColor(String chartColor) { 102 | this.chartColor = chartColor; 103 | } 104 | 105 | @Override 106 | public Descriptor getDescriptor() { 107 | return DESCRIPTOR; 108 | } 109 | 110 | @Extension 111 | public static final ChartDescriptor DESCRIPTOR = new ChartDescriptor(); 112 | 113 | public int getRangeAroundAlist() { 114 | return rangeAroundAlist; 115 | } 116 | 117 | @DataBoundSetter 118 | public void setRangeAroundAlist(int rangeAroundAlist) { 119 | this.rangeAroundAlist = rangeAroundAlist; 120 | } 121 | 122 | public static class ChartDescriptor extends Descriptor { 123 | 124 | @Override 125 | public String getDisplayName() { 126 | return "Chart from properties"; 127 | } 128 | 129 | } 130 | 131 | @DataBoundSetter 132 | public void setResultDenyList(String resultDenyList) { 133 | this.resultDenyList = resultDenyList; 134 | } 135 | 136 | public String getResultDenyList() { 137 | return resultDenyList; 138 | } 139 | 140 | @DataBoundSetter 141 | public void setResultAllowList(String resultAllowList) { 142 | this.resultAllowList = resultAllowList; 143 | } 144 | 145 | public String getResultAllowList() { 146 | return resultAllowList; 147 | } 148 | 149 | public String getPointColor(boolean isInRangeOfAllowListed) { 150 | if (isInRangeOfAllowListed) { 151 | //there is 32 because it slightly change shade of color so graph is more readable 152 | return ColorChanger.shiftColorBy(chartColor, 64, 64, 32); 153 | } 154 | return chartColor; 155 | } 156 | 157 | public String getUnstableCondition() { 158 | return unstableCondition; 159 | } 160 | 161 | @DataBoundSetter 162 | public void setUnstableCondition(String unstableCondition) { 163 | this.unstableCondition = unstableCondition; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/GenericChartColumn.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 user. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package io.jenkins.plugins.genericchart; 25 | 26 | import hudson.Extension; 27 | import hudson.model.Job; 28 | import hudson.views.ListViewColumn; 29 | import hudson.views.ListViewColumnDescriptor; 30 | import java.util.List; 31 | import java.util.UUID; 32 | import org.kohsuke.stapler.DataBoundConstructor; 33 | import org.kohsuke.stapler.DataBoundSetter; 34 | 35 | public class GenericChartColumn extends ListViewColumn { 36 | 37 | private String fileNameGlob; 38 | @SuppressWarnings({"lgtm[jenkins/plaintext-storage]", "This is not a password, it is key in properties file"}) 39 | private String key; 40 | private int limit; 41 | private String columnCaption; 42 | private String chartColor; 43 | private String resultsDenyList; 44 | private String resultsAllowList; 45 | private int rangeAroundAlist; 46 | 47 | @DataBoundConstructor 48 | public GenericChartColumn(String fileNameGlob, String key, int limit, String columnCaption, String chartColor, int rangeAroundAlist) { 49 | this.fileNameGlob = fileNameGlob; 50 | this.key = key; 51 | this.limit = limit; 52 | this.columnCaption = columnCaption; 53 | this.chartColor = chartColor; 54 | this.rangeAroundAlist = rangeAroundAlist; 55 | } 56 | 57 | public List getReportPoints(Job job) { 58 | ChartModel model = new ChartModel(key, fileNameGlob, key, limit, chartColor, rangeAroundAlist); 59 | model.setResultDenyList(resultsDenyList); 60 | model.setResultAllowList(resultsAllowList); 61 | return new PropertiesParser().getReportPointsWithBlacklist(job, model).getPoints(); 62 | } 63 | 64 | public String getLatestResult(final List results) { 65 | if (!results.isEmpty()) { 66 | return results.get(results.size() - 1).getValue(); 67 | } else { 68 | return "0"; 69 | } 70 | } 71 | 72 | public String getFileNameGlob() { 73 | return fileNameGlob; 74 | } 75 | 76 | public String generateChartName() { 77 | return "chart"+UUID.randomUUID().toString().replace("-",""); 78 | } 79 | 80 | @DataBoundSetter 81 | public void setFileNameGlob(String fileNameGlob) { 82 | this.fileNameGlob = fileNameGlob; 83 | } 84 | 85 | public String getKey() { 86 | return key; 87 | } 88 | 89 | @DataBoundSetter 90 | public void setKey(String key) { 91 | this.key = key; 92 | } 93 | 94 | public int getLimit() { 95 | return limit; 96 | } 97 | 98 | @DataBoundSetter 99 | public void setLimit(int limit) { 100 | this.limit = limit; 101 | } 102 | 103 | @Override 104 | public String getColumnCaption() { 105 | return columnCaption; 106 | } 107 | 108 | @DataBoundSetter 109 | public void setColumnCaption(String columnCaption) { 110 | this.columnCaption = columnCaption; 111 | } 112 | 113 | public String getChartColor() { 114 | return chartColor; 115 | } 116 | 117 | @DataBoundSetter 118 | public void setChartColor(String chartColor) { 119 | this.chartColor = chartColor; 120 | } 121 | 122 | @Extension 123 | public static final GenericChartColumnDescriptor DESCRIPTOR = new GenericChartColumnDescriptor(); 124 | 125 | public static class GenericChartColumnDescriptor extends ListViewColumnDescriptor { 126 | 127 | @Override 128 | public boolean shownByDefault() { 129 | return false; 130 | } 131 | 132 | @Override 133 | public String getDisplayName() { 134 | return "Chart"; 135 | } 136 | 137 | } 138 | 139 | @DataBoundSetter 140 | public void setResultDenyList(String resultDenyList) { 141 | this.resultsDenyList = resultDenyList; 142 | } 143 | 144 | public String getResultDenyList() { 145 | return resultsDenyList; 146 | } 147 | 148 | @DataBoundSetter 149 | public void setResultAllowList(String resultAllowList) { 150 | this.resultsAllowList = resultAllowList; 151 | } 152 | 153 | public String getResultAllowList() { 154 | return resultsAllowList; 155 | } 156 | 157 | public int getRangeAroundAlist() { 158 | return rangeAroundAlist; 159 | } 160 | 161 | @DataBoundSetter 162 | public void setRangeAroundAlist(int rangeAroundAlist) { 163 | this.rangeAroundAlist = rangeAroundAlist; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/GenericChartPublisher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 user. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package io.jenkins.plugins.genericchart; 25 | 26 | import hudson.Extension; 27 | import hudson.Launcher; 28 | import hudson.Util; 29 | import hudson.model.AbstractBuild; 30 | import hudson.model.AbstractProject; 31 | import hudson.model.Action; 32 | import hudson.model.BuildListener; 33 | import hudson.model.Result; 34 | import parser.expanding.ExpandingExpressionParser; 35 | import hudson.tasks.BuildStepDescriptor; 36 | import hudson.tasks.BuildStepMonitor; 37 | import hudson.tasks.Publisher; 38 | 39 | import java.io.IOException; 40 | import java.util.Collection; 41 | import java.util.Collections; 42 | import java.util.List; 43 | import java.util.stream.Collectors; 44 | 45 | import jenkins.model.Jenkins; 46 | import org.kohsuke.stapler.DataBoundConstructor; 47 | import org.kohsuke.stapler.DataBoundSetter; 48 | import parser.logical.ExpressionLogger; 49 | 50 | public class GenericChartPublisher extends Publisher { 51 | 52 | private List charts; 53 | 54 | @DataBoundConstructor 55 | public GenericChartPublisher(List charts) { 56 | this.charts = charts; 57 | } 58 | 59 | @Override 60 | public BuildStepMonitor getRequiredMonitorService() { 61 | return BuildStepMonitor.NONE; 62 | } 63 | 64 | @Override 65 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { 66 | for (ReportChart chart : new GenericChartProjectAction(build.getProject(), charts).getCharts()) { 67 | try { 68 | if (chart.getUnstableCondition() != null && !chart.getUnstableCondition().trim().isEmpty()) { 69 | String equation = chart.getUnstableCondition().trim(); 70 | PresetEquationsManager presets = new PresetEquationsManager(GenericChartGlobalConfig.getInstance().getCustomEmbeddedFunctions()); 71 | if (equation.trim().equals("LIST_INTERNALS")) { 72 | presets.print(listener.getLogger()); 73 | equation = "Internal expressions printed"; 74 | } else { 75 | PresetEquationsManager.PresetEquation isPreset = presets.get(equation); 76 | if (isPreset != null) { 77 | listener.getLogger().println(equation + " found as preset queue:"); 78 | listener.getLogger().println(isPreset.getOriginal()); 79 | equation = isPreset.getExpression(); 80 | } 81 | } 82 | List points = chart.getPoints(); 83 | List pointsValues = points.stream().map(a -> a.getValue()).collect(Collectors.toList()); 84 | //the points are returned as first = oldest = 0, last == current == newest == N. 85 | //to prevent constant recalculations, lets revert it, so 0 is latest (as notations of L in help-unstableCondition.html says 86 | Collections.reverse(pointsValues); 87 | ExpandingExpressionParser lep = new ExpandingExpressionParser(equation, pointsValues, new ExpressionLogger() { 88 | @Override 89 | public void log(String s) { 90 | listener.getLogger().println(s); 91 | } 92 | }); 93 | if (lep.evaluate()) { 94 | build.setResult(Result.UNSTABLE); 95 | return true; //you can not go back, nothing is going worse here, so lets quit 96 | } 97 | } 98 | } catch (Exception ex) { 99 | ex.printStackTrace(); 100 | } 101 | } 102 | return true; 103 | } 104 | 105 | @Override 106 | public Collection getProjectActions(AbstractProject project) { 107 | if (/* getAction(Class) produces a StackOverflowError */!Util.filter( 108 | project.getActions(), GenericChartProjectAction.class).isEmpty()) { 109 | // JENKINS-26077: someone like XUnitPublisher already added one 110 | return Collections.emptySet(); 111 | } 112 | return Collections.singleton(new GenericChartProjectAction(project, charts)); 113 | } 114 | 115 | public List getCharts() { 116 | return charts; 117 | } 118 | 119 | @DataBoundSetter 120 | public void setCharts(List charts) { 121 | this.charts = charts; 122 | } 123 | 124 | @Extension 125 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 126 | 127 | public static class DescriptorImpl extends BuildStepDescriptor { 128 | 129 | public List getItemDescriptors() { 130 | return Jenkins.get().getDescriptorList(ChartModel.class); 131 | } 132 | 133 | @Override 134 | public String getDisplayName() { 135 | return "Charts from properties"; 136 | } 137 | 138 | @Override 139 | public boolean isApplicable(Class jobType) { 140 | return true; 141 | } 142 | 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/PresetEquationsManager.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.genericchart; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.ByteArrayInputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.PrintStream; 9 | import java.net.URI; 10 | import java.net.URISyntaxException; 11 | import java.net.URL; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | public class PresetEquationsManager { 19 | 20 | private static final Object lock = new Object(); 21 | private static List internals; 22 | 23 | public PresetEquationsManager() throws IOException, URISyntaxException { 24 | this(null); 25 | } 26 | 27 | public PresetEquationsManager(String anotherUrlOrBody) throws IOException, URISyntaxException { 28 | synchronized (lock) { 29 | if (internals == null) { 30 | internals = readInternals(); 31 | if (anotherUrlOrBody != null && !anotherUrlOrBody.trim().isEmpty()) { 32 | internals.addAll(readExternals(anotherUrlOrBody)); 33 | } 34 | } 35 | } 36 | } 37 | 38 | public static void resetCached() { 39 | synchronized (lock) { 40 | internals = null; 41 | } 42 | } 43 | 44 | private List readExternals(String bodyOrUrl) throws IOException, URISyntaxException { 45 | synchronized (lock) { 46 | if (bodyOrUrl.split("\n").length > 1) { 47 | return readFromStream(new ByteArrayInputStream(bodyOrUrl.getBytes(StandardCharsets.UTF_8))); 48 | } else { 49 | return readFromStream(new URI(bodyOrUrl).toURL().openStream()); 50 | } 51 | } 52 | } 53 | 54 | private List readInternals() throws IOException { 55 | synchronized (lock) { 56 | return readFromStream(this.getClass().getResourceAsStream("presetEquations")); 57 | } 58 | } 59 | 60 | private static List readFromStream(InputStream in) throws IOException { 61 | synchronized (lock) { 62 | List futureComments = new ArrayList<>(); 63 | List futureBody = new ArrayList<>(); 64 | List parsed = new ArrayList<>(); 65 | try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { 66 | while (true) { 67 | String s = br.readLine(); 68 | if (s == null) { 69 | if (!futureBody.isEmpty()) { 70 | parsed.add(new PresetEquationDefinition(futureComments, futureBody)); 71 | } 72 | break; 73 | } 74 | if (s.trim().isEmpty()) { 75 | continue; 76 | } 77 | if (s.trim().startsWith("#")) { 78 | if (!futureBody.isEmpty()) { 79 | parsed.add(new PresetEquationDefinition(futureComments, futureBody)); 80 | futureComments = new ArrayList<>(); 81 | futureBody = new ArrayList<>(); 82 | } 83 | futureComments.add(s.trim()); 84 | } else { 85 | futureBody.add(s.trim()); 86 | } 87 | } 88 | } 89 | return parsed; 90 | } 91 | 92 | } 93 | 94 | public void print(PrintStream logger) { 95 | for (PresetEquationDefinition def : internals) { 96 | logger.println("***" + def.getId() + "***"); 97 | logger.println(def.getComment()); 98 | logger.println(def.getExpression()); 99 | PresetEquation expanded = new PresetEquation(def.getExpression(), "99 88 77 66 55 44 33 22 11".split(" ")); 100 | logger.println("eg: " + expanded.getExpression()); 101 | logger.println(" -- "); 102 | } 103 | logger.println("summary: " + getIds().stream().collect(Collectors.joining(", "))); 104 | } 105 | 106 | public List getIds() { 107 | return internals.stream().map( a->a.getId()).sorted().collect(Collectors.toList()); 108 | } 109 | 110 | public PresetEquation get(String idWithParams) { 111 | String[] fullSplit = idWithParams.split("\\s+"); 112 | String id = fullSplit[0]; 113 | String[] params = idWithParams.replaceFirst(id+"\\s+","").split("\\s+"); 114 | for (PresetEquationDefinition def : internals) { 115 | if (def.getId().equals(id)) { 116 | return new PresetEquation(def.getExpression(), params); 117 | } 118 | } 119 | return null; 120 | } 121 | 122 | public static class PresetEquationDefinition { 123 | private final List comments; 124 | private final List body; 125 | private final String comment; 126 | private final String expression; 127 | private final String id; 128 | 129 | public PresetEquationDefinition(List comments, List body) { 130 | this.comments = Collections.unmodifiableList(comments); 131 | this.body = Collections.unmodifiableList(body); 132 | comment = comments.stream().collect(Collectors.joining("\n")); 133 | expression = body.stream().collect(Collectors.joining(" ")); 134 | id = comments.get(0).replaceFirst("#*", "").trim(); 135 | } 136 | 137 | public String getId() { 138 | return id; 139 | } 140 | 141 | public String getExpression() { 142 | return expression; 143 | } 144 | 145 | public String getComment() { 146 | return comment; 147 | } 148 | } 149 | 150 | public static class PresetEquation { 151 | private final String original; 152 | private final String expression; 153 | 154 | private PresetEquation(String original, String... params) { 155 | this.original = original; 156 | expression = expand(original, params); 157 | } 158 | 159 | private String expand(String original, String[] params) { 160 | String fex = original; 161 | for (int i = 0; i < params.length; i++) { 162 | fex = fex.replaceAll("/\\*" + (i + 1) + "\\*/", params[i]); 163 | } 164 | return fex; 165 | } 166 | 167 | public String getOriginal() { 168 | return original; 169 | } 170 | 171 | public String getExpression() { 172 | return expression; 173 | } 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/ChartModel/help-unstableCondition.html: -------------------------------------------------------------------------------- 1 |
2 | This allows you to specify a mathematical expression which, if true, will turn the job to unstbale
3 | First the expression is split by logical operands of |,or,&,and,impl,imp,eq
4 | Then it it is split by >,<, ==, !=, <=, >=, lt, gt, le, ge
5 | Negation can be used only with logicalbrackets, strictly tied to left one - eg ![0 == 1]
6 | each of the remaining parts is calcualted as mathematical expression. as powerfull as https://github.com/gbenroscience/ParserNG, with few extensions:
7 | you can use L0 to access value of just finished build. L1 as value of last one before, L2 as two before and so on...Negative id on L is not allowed nowhere
8 | points in chart chart with 4 bullets, wehich fifth build jsut finished would be L4 L3 L2 L1 L0
9 | you can use ranges by `..`. Eg ..L1 is all before L0 (thus Ln...L2,L1). Similarly, L3.. would be L3,L2,L1. You can use also both limits - eg: L1..L3 o
10 | Note, that the evaluation counts with what it see - if you denylist/allowlist, the points are filtered before given to formula. Similarly, if you show just last two point, you will get access only to L0 and L1. So show more :)
11 |
12 | Expression : [[ avg(..L1)*1.1 < L0 ] || [L1*1.3 < L0 ]] || [ avgN(count(..L0)/4, ..L1)*1.1 13 | Upon : 60,20,45,70
14 | As : Ln...L1,L0
15 | Expanded as: [[ avg(60,20,45)*1.1 < 70 ] || [45*1.3 < 70 ]] || [ avgN(count(60,20,45,70)/4, 60,20,45)*1.1<70 ] 16 |
17 | brackets: [[ avg(60,20,45)*1.1 < 70 ] || [45*1.3 < 70 ]] || [ avgN(count(60,20,45,70)/4, 60,20,45)*1.1<70 ] 18 |
19 | brackets: [ avg(60,20,45)*1.1 < 70 ] || [45*1.3 < 70 ] 20 |
21 | evaluating: avg(60,20,45)*1.1 < 70 22 |
23 | evaluating: avg(60,20,45)*1.1 < 70 24 |
25 | evaluating: avg(60,20,45)*1.1
26 | is: 45.833333333333336
27 | evaluating: 70
28 | is: 70 29 |
30 | ... 45.833333333333336 < 70
31 | is: true 32 |
33 | is: true 34 |
35 | to: true || [45*1.3 < 70 ] 36 |
37 | evaluating: 45*1.3 < 70 38 |
39 | evaluating: 45*1.3 < 70 40 |
41 | evaluating: 45*1.3
42 | is: 58.5
43 | evaluating: 70
44 | is: 70 45 |
46 | ... 58.5 < 70
47 | is: true 48 |
49 | is: true 50 |
51 | to: true || true 52 |
53 | evaluating: true || true 54 |
55 | evaluating: true
56 | is: true
57 | evaluating: true
58 | is: true 59 |
60 | ... true | true
61 | is: true 62 |
63 | true 64 |
65 | to: true || [ avgN(count(60,20,45,70)/4, 60,20,45)*1.1<70 ] 66 |
67 | evaluating: avgN(count(60,20,45,70)/4, 60,20,45)*1.1<70 68 |
69 | evaluating: avgN(count(60,20,45,70)/4, 60,20,45)*1.1<70 70 |
71 | evaluating: avgN(count(60,20,45,70)/4, 60,20,45)*1.1
72 | is: 45.833333337
73 | evaluating: 70
74 | is: 70 75 |
76 | ... 45.833333337 < 70
77 | is: true 78 |
79 | is: true 80 |
81 | to: true || true 82 |
83 | evaluating: true || true 84 |
85 | evaluating: true
86 | is: true
87 | evaluating: true
88 | is: true 89 |
90 | ... true | true
91 | is: true 92 |
93 | true 94 |
95 | is: true 96 |
97 | TRUE , thus job WILL be turned to unstable
98 | Max number in L (and thus also in functions) is 99
99 | The parser is far from being perfect, be nice to it.
100 | The () brackets are avaiable only in side math expressions. The Logical part is using [] as separators. Eg:
101 |
102 | 1+1 < (2+0)*1 impl [ [5 == 6 || 33<(22-20)*2 ]xor [ [ 5-3 < 2 or 7*(5+2)<=5 ] and 1+1 == 2]] eq [ true && false ] 103 |
104 |
105 | There is a special element MN, which represents count of input points, so you do not need to call `count(..L0)` arround and arround. So...
106 | Expression : avg(..L1)*1.1-MN < L0 | L1*1.3 + MN< L0
107 | Upon : 60,20,80,70
108 | As : Ln...L1,L0
109 | MN = 4 110 | Expanded as: avg(60,20,80)*1.1-4 < 70 | 80*1.3 + 4< 70
111 | ...indeed.
112 |
113 |
114 | Dynamic indexes
115 | You can calculate the Lsomething by expressions. To do that, use L{expression}. Eg:
116 |
117 | avg( ..L{MN/2}) < avg(L{MN/2}..)
118 | Expression : avg( ..L{MN/2}) < avg(L{MN/2}..)
119 | Upon       : 2,4,6
120 | As         : Ln...L1,L0
121 | MN         = 3
122 |   L indexes brackets: avg( ..L{3/2}) < avg(L{3/2}..)
123 |     Expression : 3/2
124 |     Expanded as: 3/2
125 |     is: 1.5
126 |     3/2 = 1 (1.5)
127 |   to: avg( ..L 1 ) < avg(L{3/2}..)
128 |     Expression : 3/2
129 |     Expanded as: 3/2
130 |     is: 1.5
131 |     3/2 = 1 (1.5)
132 |   to: avg( ..L 1 ) < avg(L 1 ..)
133 | Expanded as: avg( 2,4) < avg(4,6)
134 | avg( 2,4) < avg(4,6)
135 |   brackets: avg( 2,4) < avg(4,6)
136 |       evaluating: avg( 2,4) < avg(4,6)
137 |         evaluating: avg( 2,4) < avg(4,6)
138 |           evaluating: avg( 2,4)
139 |           is: 3.0
140 |           evaluating: avg(4,6)
141 |           is: 5.0
142 |         ... 3.0 < 5.0
143 |         is: true
144 |       is: true
145 |   true
146 | is: true
147 | true
148 | 		
149 |
150 |
151 |

Preset equations

152 | There are some preset equations, usable in form of ID arg1 arg2 ... arg9. eg:
153 |
154 | FINAL_DOWN_CUTTING_OK 2 5 5 5
155 | 		
156 | To print them, use LIST_INTERNALS instead of expression or look onto 157 | 158 | pre pprepared availabe embedded functions and exempalr config
159 | You can add yours own preset expressions in main settings as url to file with definitions or as definitions themselves.
160 | The syntax is as you saw 161 | 162 | above: 163 |
    164 |
  • empty lines ignored
  • 165 |
  • behind hash(#) are comments and examples
  • 166 |
  • First comment si mandatory, and is ID
  • 167 |
  • after comments follow expression itself as by 168 | ParserNG rules 169 |
  • 170 |
  • It can be multilined for readability (as ParserNG's --trim)
  • 171 |
  • parameters are included substitued into expression via /*1*/ for first param, /*2*/ for second and os on
  • 172 |
173 |
174 |
175 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/genericchart/PresetEquationsManagerTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.genericchart; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import parser.expanding.ExpandingExpressionParser; 7 | import parser.logical.ExpressionLogger; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.PrintStream; 12 | import java.net.URISyntaxException; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.Arrays; 15 | import java.util.HashSet; 16 | import java.util.List; 17 | 18 | class PresetEquationsManagerTest { 19 | 20 | @BeforeEach 21 | public void cleanCaches(){ 22 | PresetEquationsManager.resetCached(); 23 | } 24 | 25 | @Test 26 | public void listTest() throws IOException, URISyntaxException { 27 | final ByteArrayOutputStream baos1 = new ByteArrayOutputStream(); 28 | final PresetEquationsManager p1 = new PresetEquationsManager(); 29 | try (PrintStream ps = new PrintStream(baos1, true, StandardCharsets.UTF_8)) { 30 | p1.print(ps); 31 | } 32 | String listing1 = baos1.toString(StandardCharsets.UTF_8); 33 | 34 | ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); 35 | PresetEquationsManager p2 = new PresetEquationsManager("# someID\n# some comment\n1+1"); 36 | try (PrintStream ps = new PrintStream(baos2, true, StandardCharsets.UTF_8)) { 37 | p2.print(ps); 38 | } 39 | String listing2 = baos2.toString(StandardCharsets.UTF_8); 40 | Assertions.assertEquals(listing1, listing2); //cahe was not reload 41 | 42 | PresetEquationsManager.resetCached(); 43 | baos2 = new ByteArrayOutputStream(); 44 | p2 = new PresetEquationsManager("# someID\n# some comment\n1+1"); 45 | try (PrintStream ps = new PrintStream(baos2, true, StandardCharsets.UTF_8)) { 46 | p2.print(ps); 47 | } 48 | listing2 = baos2.toString(StandardCharsets.UTF_8); 49 | Assertions.assertNotEquals(listing1, listing2); 50 | } 51 | 52 | @Test 53 | public void getTest() throws IOException, URISyntaxException { 54 | final PresetEquationsManager p1 = new PresetEquationsManager("# someID\n# some comment\n1+1"); 55 | PresetEquationsManager.PresetEquation e0 = p1.get("weird_id weird_params"); 56 | Assertions.assertNull(e0); 57 | PresetEquationsManager.PresetEquation e1 = p1.get("someID"); 58 | Assertions.assertEquals("1+1", e1.getExpression()); 59 | Assertions.assertEquals("1+1", e1.getOriginal()); 60 | PresetEquationsManager.PresetEquation e2 = p1.get("someID uselessParam"); 61 | Assertions.assertEquals("1+1", e2.getExpression()); 62 | Assertions.assertEquals("1+1", e2.getOriginal()); 63 | PresetEquationsManager.PresetEquation e3 = p1.get("IMMEDIATE_UP_OK"); 64 | Assertions.assertNotEquals(e3.getExpression(), e3.getOriginal()); 65 | PresetEquationsManager.PresetEquation e4 = p1.get("IMMEDIATE_UP_OK 1 2 3"); 66 | Assertions.assertNotEquals(e4.getExpression(), e4.getOriginal()); 67 | Assertions.assertEquals(e3.getOriginal(), e4.getOriginal()); 68 | Assertions.assertNotEquals(e3.getExpression(), e4.getExpression()); 69 | } 70 | 71 | @Test 72 | public void noDupes() throws IOException, URISyntaxException { 73 | final PresetEquationsManager p1 = new PresetEquationsManager("# someID\n# some comment\n1+1"); 74 | List ids = p1.getIds(); 75 | Assertions.assertTrue(ids.size() == new HashSet<>(ids).size()); 76 | Assertions.assertTrue(ids.size() > 5); 77 | for (String id1 : ids) { 78 | PresetEquationsManager.PresetEquation e1 = p1.get(id1 + " 2 5 5 5 5"); 79 | for (String id2 : ids) { 80 | PresetEquationsManager.PresetEquation e2 = p1.get(id2 + " 2 5 5 5 5"); 81 | if (!id1.equals(id2)) { 82 | Assertions.assertNotEquals(e1.getExpression(), e2.getExpression(), id1+ " and " + id2 + " have same equation!"); 83 | Assertions.assertNotEquals(e1.getOriginal(), e2.getOriginal(), id1+ " and " + id2 + " have same equation!"); 84 | } else { 85 | Assertions.assertEquals(e1.getExpression(), e2.getExpression(), id1+ " and " + id2 + " have NOT same equation!"); 86 | Assertions.assertEquals(e1.getOriginal(), e2.getOriginal(), id1+ " and " + id2 + " have NOT same equation!"); 87 | } 88 | } 89 | } 90 | } 91 | 92 | @Test 93 | public void buggyIsCought() throws IOException, URISyntaxException { 94 | PresetEquationsManager p1 = new PresetEquationsManager("# someID\n# some comment\nblah=/*1*/; avg(blah"); //missing bracket 95 | StringBuilder sbOne = new StringBuilder(); 96 | PresetEquationsManager.PresetEquation e = p1.get("someID 10"); 97 | Assertions.assertNotNull(e); 98 | evaluate(null, sbOne, e); 99 | checkError(sbOne); 100 | 101 | p1 = new PresetEquationsManager("# someID\n# some comment\n/*1*/+/*2*/"); //unexpanded /*2*/ 102 | sbOne = new StringBuilder(); 103 | e = p1.get("someID 10"); 104 | Assertions.assertNotNull(e); 105 | evaluate(null, sbOne, e); 106 | checkError(sbOne); 107 | 108 | p1 = new PresetEquationsManager("# someID\n# some comment\nblah=/*1*/; avg(blah)"); //unexpanded /**/ in variable 109 | sbOne = new StringBuilder(); 110 | e = p1.get("someID"); 111 | Assertions.assertNotNull(e); 112 | Exception ex = null; 113 | try { 114 | evaluate(null, sbOne, e); 115 | } catch (Exception eex) { 116 | ex = eex; 117 | } 118 | Assertions.assertNotNull(ex); 119 | } 120 | 121 | @Test 122 | public void allValuates() throws IOException, URISyntaxException { 123 | final PresetEquationsManager p1 = new PresetEquationsManager("# someID\n# some comment\n1+1"); 124 | List ids = p1.getIds(); 125 | Assertions.assertTrue(ids.size() > 5); 126 | StringBuilder sbAll = new StringBuilder(); 127 | for (String id : ids) { 128 | StringBuilder sbOne = new StringBuilder(); 129 | PresetEquationsManager.PresetEquation e = p1.get(id + " 2 5 5 5 5"); 130 | Assertions.assertNotNull(e); 131 | evaluate(sbAll, sbOne, e); 132 | checkNoError(sbOne); 133 | } 134 | checkNoError(sbAll); 135 | } 136 | 137 | private boolean evaluate(StringBuilder sbAll, StringBuilder sbOne, PresetEquationsManager.PresetEquation e) { 138 | ExpandingExpressionParser lep = new ExpandingExpressionParser(e.getExpression(), Arrays.asList("10", "10", "10", "10"), new ExpressionLogger() { 139 | @Override 140 | public void log(String s) { 141 | if (sbAll != null) { 142 | sbAll.append(s).append("\n"); 143 | } 144 | sbOne.append(s).append("\n"); 145 | } 146 | }); 147 | return lep.evaluate(); 148 | } 149 | 150 | private void checkNoError(StringBuilder sbOne) { 151 | Assertions.assertFalse(sbOne.toString().toLowerCase().contains("error")); 152 | Assertions.assertFalse(sbOne.toString().toLowerCase().contains("fail")); 153 | Assertions.assertFalse(sbOne.toString().toLowerCase().contains("exception")); 154 | } 155 | 156 | private void checkError(StringBuilder sbOne) { 157 | Assertions.assertTrue( 158 | sbOne.toString().toLowerCase().contains("error") 159 | || sbOne.toString().toLowerCase().contains("fail") 160 | || sbOne.toString().toLowerCase().contains("exception")); 161 | } 162 | } -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/genericchart/presetEquations: -------------------------------------------------------------------------------- 1 | # IMMEDIATE_UP_OK 2 | # checking whether drop (in %) against previous run was not bigger then threshold 3 | # one parameter, threshold number 4 | # eg: IMMEDIATE_UP_OK 5 5 | threshold=/*1*/; 6 | (L1/(L0/100)-100) > threshold 7 | 8 | # IMMEDIATE_DOWN_OK 9 | # checking whether rise (in %) against previous run was not bigger then threshold 10 | # one parameter, threshold number 11 | # eg: IMMEDIATE_DOWN_OK 5 12 | threshold=/*1*/; 13 | (L1/(L0/100)-100) < -threshold 14 | 15 | # SHORT_UP_OK 16 | # checking whether drop (in %) against previous runs was not bigger then threshold 17 | # is using avg of previous points 18 | # one parameter, threshold number 19 | # eg: SHORT_UP_OK 5 20 | threshold=/*1*/; 21 | (avg(..L1)/(L0/100)-100) > threshold 22 | 23 | # SHORT_DOWN_OK 24 | # checking whether rise (in %) against previous runs was not bigger then threshold 25 | # is using avg of previous points 26 | # one parameter, threshold number 27 | # eg: SHORT_DOWN_OK 5 28 | threshold=/*1*/; 29 | (avg(..L1)/(L0/100)-100) < -threshold 30 | 31 | # SHORT_UP_BIG_OK 32 | # checking whether drop (in %) against previous runs was not bigger then threshold 33 | # is using geom of previous points 34 | # one parameter, threshold number 35 | # eg: SHORT_UP_BIG_OK 5 36 | threshold=/*1*/; 37 | (geom(..L1)/(L0/100)-100) > threshold 38 | 39 | # SHORT_DOWN_BIG_OK 40 | # checking whether rise (in %) against previous runs was not bigger then threshold 41 | # is using geom of previous points 42 | # one parameter, threshold number 43 | # eg: SHORT_DOWN_BIG_OK 5 44 | threshold=/*1*/; 45 | (geom(..L1)/(L0/100)-100) < -threshold 46 | 47 | # SHORT_UP_CUT_OK 48 | # checking whether drop (in %) against previous runs with extremes cut-off was not bigger then threshold 49 | # is using avgN - average with cutting - of previous points 50 | # two parameters, cut amount and threshold number 51 | # eg: SHORT_UP_CUT_OK 1 5 52 | cut=/*1*/; threshold=/*2*/; 53 | (avgN(..L1)/(L0/100)-100) > threshold 54 | 55 | # SHORT_DOWN_CUT_OK 56 | # checking whether rise (in %) against previous runs with extremes cut-off was not bigger then threshold 57 | # is using avgN - average with cutting - of previous points 58 | # two parameters, cut amount and threshold number 59 | # eg: SHORT_DOWN_CUT_OK 1 5 60 | cut=/*1*/; threshold=/*2*/; 61 | (avgN(..L1)/(L0/100)-100) < -threshold 62 | 63 | # SHORT_UP_BIG_CUT_OK 64 | # checking whether drop (in %) against previous runs with extremes cut-off was not bigger then threshold 65 | # is using geomN - geometrical average with cutting - of previous points 66 | # two parameters, cut amount and threshold number 67 | # eg: SHORT_UP_BIG_CUT_OK 1 5 68 | cut=/*1*/; threshold=/*2*/; 69 | (geomN(..L1)/(L0/100)-100) > threshold 70 | 71 | # SHORT_DOWN_BIG_CUT_OK 72 | # checking whether rise (in %) against previous runs with extremes cut-off was not bigger then threshold 73 | # is using geomN - geometrical average with cutting - of previous points 74 | # two parameters, cut amount and threshold number 75 | # eg: SHORT_DOWN_BIG_CUT_OK 1 5 76 | cut=/*1*/; threshold=/*2*/; 77 | (geomN(..L1)/(L0/100)-100) < -threshold 78 | 79 | # LONG_UP_OK 80 | # checking whether drop (in %) of newer half of builds against older half of builds is not bigger then threshold 81 | # is using avg - average - of previous points 82 | # one parameter, threshold number 83 | # eg: LONG_UP_OK 5 84 | threshold=/*1*/; 85 | (avg(L{MN/2}..L{MN})/(avg(L0..L{MN/2})/100)-100) > threshold 86 | 87 | # LONG_DOWN_OK 88 | # checking whether rise (in %) of newer half of builds against older half of builds is not bigger then threshold 89 | # is using avg - average - of previous points 90 | # one parameter, threshold number 91 | # eg: LONG_DOWN_OK 5 92 | threshold=/*1*/; 93 | (avg(L{MN/2}..L{MN})/(avg(L0..L{MN/2})/100)-100) < -threshold 94 | 95 | # LONG_UP_BIG_OK 96 | # checking whether drop (in %) of newer half of builds against older half of builds is not bigger then threshold 97 | # is using geom - geometrical average - of previous points 98 | # one parameter, threshold number 99 | # eg: LONG_UP_BIG_OK 5 100 | threshold=/*1*/; 101 | (geom(L{MN/2}..L{MN})/(geom(L0..L{MN/2})/100)-100) > threshold 102 | 103 | # LONG_DOWN_BIG_OK 104 | # checking whether rise (in %) of newer half of builds against older half of builds is not bigger then threshold 105 | # is using geom - geometrical average - of previous points 106 | # one parameter, threshold number 107 | # eg: LONG_DOWN_BIG_OK 5 108 | threshold=/*1*/; 109 | (geom(L{MN/2}..L{MN})/(geom(L0..L{MN/2})/100)-100) < -threshold 110 | 111 | # LONG_UP_CUT_OK 112 | # checking whether drop (in %) of newer half of builds against older half of builds (both with extremes cut-off) is not bigger then threshold 113 | # is using avgN - average with cutting - of previous points 114 | # two parameters, cut amount and threshold number 115 | # eg: LONG_UP_CUT_OK 1 5 116 | cut=/*1*/; threshold=/*2*/; 117 | (avgN(cut,L{MN/2}..L{MN})/(avgN(cut,L0..L{MN/2})/100)-100) > threshold 118 | 119 | # LONG_DOWN_CUT_OK 120 | # checking whether rise (in %) of newer half of builds against older half of builds (both with extremes cut-off) is not bigger then threshold 121 | # is using avgN - average with cutting - of previous points 122 | # two parameters, cut amount and threshold number 123 | # eg: LONG_DOWN_CUT_OK 1 5 124 | cut=/*1*/; threshold=/*2*/; 125 | (avgN(cut,L{MN/2}..L{MN})/(avgN(cut,L0..L{MN/2})/100)-100) < -threshold 126 | 127 | # LONG_UP_BIG_CUT_OK 128 | # checking whether drop (in %) of newer half of builds against older half of builds (both with extremes cut-off) is not bigger then threshold 129 | # is using geomN - geometrical average with cutting - of previous points 130 | # two parameters, cut amount and threshold number 131 | # eg: LONG_UP_BIG_CUT_OK 1 5 132 | cut=/*1*/; threshold=/*2*/; 133 | (geomN(cut,L{MN/2}..L{MN})/(geomN(cut,L0..L{MN/2})/100)-100) > threshold 134 | 135 | # LONG_DOWN_BIG_CUT_OK 136 | # checking whether rise (in %) of newer half of builds against older half of builds (both with extremes cut-off) is not bigger then threshold 137 | # is using geomN - geometrical average with cutting - of previous points 138 | # two parameters, cut amount and threshold number 139 | # eg: LONG_DOWN_BIG_CUT_OK 1 5 140 | cut=/*1*/; threshold=/*2*/; 141 | (geomN(cut,L{MN/2}..L{MN})/(geomN(cut,L0..L{MN/2})/100)-100) < -threshold 142 | 143 | # FINAL_UP_CUTTING_OK 144 | # combination:LONG_UP_CUT_OK or SHORT_UP_CUT_OK or IMMEDIATE_UP_OK 145 | # four parameters 146 | # cut, immediate threshold, short time threshold, long time threshold 147 | # eg: FINAL_UP_CUTTING_OK 2 5 5 5 148 | cut=/*1*/;thresholdA=/*2*/; 149 | -1*(L1/(L0/100)-100) < -thresholdA 150 | || 151 | thresholdB=/*3*/; 152 | -1*(avgN(cut,L{MN/2}..L{MN})/(avgN(cut,L0..L{MN/2})/100)-100) < -thresholdB 153 | || 154 | thresholdC=/*4*/; 155 | -1*(avgN(cut,..L1)/(L0/100)-100) < -thresholdC 156 | 157 | # FINAL_DOWN_CUTTING_OK 158 | # combination:LONG_DOWN_CUT_OK or SHORT_DOWN_CUT_OK or IMMEDIATE_DOWN_OK 159 | # four parameters 160 | # cut, immediate threshold, short time threshold, long time threshold 161 | # eg: FINAL_DOWN_CUTTING_OK 2 5 5 5 162 | cut=/*1*/;thresholdA=/*2*/; 163 | (L1/(L0/100)-100) < -thresholdA 164 | || 165 | thresholdB=/*3*/; 166 | (avgN(cut,L{MN/2}..L{MN})/(avgN(cut,L0..L{MN/2})/100)-100) < -thresholdB 167 | || 168 | thresholdC=/*4*/; 169 | (avgN(cut,..L1)/(L0/100)-100) < -thresholdC 170 | 171 | # FINAL_UP_OK 172 | # combination:LONG_UP_OK or SHORT_UP_OK or IMMEDIATE_UP_OK 173 | # three parameters 174 | # immediate threshold, short time threshold, long time threshold 175 | # eg: FINAL_UP_OK 5 5 5 176 | thresholdA=/*1*/; 177 | -1*(L1/(L0/100)-100) < -thresholdA 178 | || 179 | thresholdB=/*2*/; 180 | -1*(avg(L{MN/2}..L{MN})/(avg(L0..L{MN/2})/100)-100) < -thresholdB 181 | || 182 | thresholdC=/*3*/; 183 | -1*(avg(..L1)/(L0/100)-100) < -thresholdC 184 | 185 | # FINAL_DOWN_OK 186 | # combination:LONG_DOWN__OK or SHORT_DOWN__OK or IMMEDIATE_DOWN__OK 187 | # three parameters 188 | # immediate threshold, short time threshold, long time threshold 189 | # eg: FINAL_DOWN_OK 5 5 5 190 | thresholdA=/*1*/; 191 | (L1/(L0/100)-100) < -thresholdA 192 | || 193 | thresholdB=/*2*/; 194 | (avg(L{MN/2}..L{MN})/(avg(L0..L{MN/2})/100)-100) < -thresholdB 195 | || 196 | thresholdC=/*3*/; 197 | (avg(..L1)/(L0/100)-100) < -thresholdC 198 | 199 | # FINAL_UP_BIG_CUTTING_OK 200 | # combination:LONG_UP_BIG_CUT_OK or SHORT_UP_BIG_CUT_OK or IMMEDIATE_UP_OK 201 | # four parameters 202 | # cut, immediate threshold, short time threshold, long time threshold 203 | # eg: FINAL_UP_BIG_CUTTING_OK 2 5 5 5 204 | cut=/*1*/;thresholdA=/*2*/; 205 | -1*(L1/(L0/100)-100) < -thresholdA 206 | || 207 | thresholdB=/*3*/; 208 | -1*(geomN(cut,L{MN/2}..L{MN})/(geomN(cut,L0..L{MN/2})/100)-100) < -thresholdB 209 | || 210 | thresholdC=/*4*/; 211 | -1*(geomN(cut,..L1)/(L0/100)-100) < -thresholdC 212 | 213 | # FINAL_DOWN_BIG_CUTTING_OK 214 | # combination:LONG_DOWN_BIG_CUT_OK or SHORT_DOWN_BIG_CUT_OK or IMMEDIATE_DOWN_OK 215 | # four parameters 216 | # cut, immediate threshold, short time threshold, long time threshold 217 | # eg: FINAL_DOWN_BIG_CUTTING_OK 2 5 5 5 218 | cut=/*1*/;thresholdA=/*2*/; 219 | (L1/(L0/100)-100) < -thresholdA 220 | || 221 | thresholdB=/*3*/; 222 | (geomN(cut,L{MN/2}..L{MN})/(geomN(cut,L0..L{MN/2})/100)-100) < -thresholdB 223 | || 224 | thresholdC=/*4*/; 225 | (geomN(cut,..L1)/(L0/100)-100) < -thresholdC 226 | 227 | # FINAL_UP_BIG_OK 228 | # combination:LONG_UP_BIG_OK or SHORT_UP_BIG_OK or IMMEDIATE_UP_OK 229 | # three parameters 230 | # immediate threshold, short time threshold, long time threshold 231 | # eg: FINAL_UP_BIG_OK 5 5 5 232 | thresholdA=/*1*/; 233 | -1*(L1/(L0/100)-100) < -thresholdA 234 | || 235 | thresholdB=/*2*/; 236 | -1*(geom(L{MN/2}..L{MN})/(geom(L0..L{MN/2})/100)-100) < -thresholdB 237 | || 238 | thresholdC=/*3*/; 239 | -1*(geom(..L1)/(L0/100)-100) < -thresholdC 240 | 241 | # FINAL_DOWN_BIG_OK 242 | # combination:LONG_DOWN_BIG_OK or SHORT_DOWN_BIG_OK or IMMEDIATE_DOWN_OK 243 | # three parameters 244 | # immediate threshold, short time threshold, long time threshold 245 | # eg: FINAL_DOWN_BIG_OK 5 5 5 246 | thresholdA=/*1*/; 247 | (L1/(L0/100)-100) < -thresholdA 248 | || 249 | thresholdB=/*2*/; 250 | (geom(L{MN/2}..L{MN})/(geom(L0..L{MN/2})/100)-100) < -thresholdB 251 | || 252 | thresholdC=/*3*/; 253 | (geom(..L1)/(L0/100)-100) < -thresholdC -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/genericchart/PropertiesParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 user. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package io.jenkins.plugins.genericchart; 25 | 26 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 27 | import hudson.model.Job; 28 | import hudson.model.Result; 29 | import hudson.model.Run; 30 | 31 | import java.nio.file.FileSystems; 32 | import java.nio.file.Files; 33 | import java.nio.file.Path; 34 | import java.nio.file.PathMatcher; 35 | import java.util.ArrayList; 36 | import java.util.Collections; 37 | import java.util.List; 38 | import java.util.Optional; 39 | import java.util.function.Predicate; 40 | import java.util.stream.Stream; 41 | 42 | public class PropertiesParser { 43 | 44 | private static interface ListProvider { 45 | 46 | String getList(); 47 | 48 | int getSurrounding(); 49 | } 50 | 51 | List getBlacklisted(Job job, final ChartModel chart) { 52 | return getList(job, chart, new ListProvider() { 53 | @Override 54 | public String getList() { 55 | return chart.getResultDenyList(); 56 | } 57 | 58 | @Override 59 | public int getSurrounding() { 60 | return 0; 61 | } 62 | 63 | }); 64 | 65 | } 66 | 67 | List getWhitelisted(Job job, ChartModel chart) { 68 | return getList(job, chart, new ListProvider() { 69 | @Override 70 | public String getList() { 71 | return chart.getResultAllowList(); 72 | } 73 | 74 | @Override 75 | public int getSurrounding() { 76 | return chart.getRangeAroundAlist(); 77 | } 78 | }); 79 | 80 | } 81 | 82 | /* 83 | Counting white list size without surroundings which is needed in title over the graph 84 | */ 85 | List getWhiteListWithoutSurroundings(Job job, ChartModel chart) { 86 | return getList(job, chart, new ListProvider() { 87 | @Override 88 | public String getList() { 89 | return chart.getResultAllowList(); 90 | } 91 | 92 | @Override 93 | public int getSurrounding() { 94 | return 0; 95 | } 96 | }); 97 | 98 | } 99 | 100 | @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", 101 | justification = "run.getResult().isWorseThan(Result.UNSTABLE) claims to have NPE, but I'm failing to see it") 102 | private List getList(Job job, ChartModel chart, ListProvider provider) { 103 | if (provider.getList() == null || provider.getList().trim().isEmpty()) { 104 | return Collections.emptyList(); 105 | } 106 | int limit = chart.getLimit(); 107 | Run[] builds = job.getBuilds().toArray(new Run[0]); 108 | List result = new ArrayList<>(limit); 109 | for (int i = 0; i < builds.length; i++) { 110 | Run run = builds[i]; 111 | if (run == null 112 | || run.getResult() == null 113 | || run.getResult().isWorseThan(Result.UNSTABLE)) { 114 | continue; 115 | } 116 | String[] items = provider.getList().split("\\s+"); 117 | for (String item : items) { 118 | if (run.getDisplayName().matches(item)) { 119 | int numberOfFailedBuilds = 0; 120 | for (int j = 0; j <= provider.getSurrounding() + numberOfFailedBuilds; j++) { 121 | if (addNotFailedBuild(i + j, result, builds)) { 122 | numberOfFailedBuilds++; 123 | } 124 | } 125 | numberOfFailedBuilds = 0; 126 | for (int j = -1; j >= -(provider.getSurrounding() + numberOfFailedBuilds); j--) { 127 | if (addNotFailedBuild(i + j, result, builds)) { 128 | numberOfFailedBuilds++; 129 | } 130 | } 131 | } 132 | } 133 | } 134 | return result; 135 | } 136 | 137 | @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", 138 | justification = "run.getResult().isWorseThan(Result.UNSTABLE) claims to have NPE, but I'm failing to see it") 139 | private boolean addNotFailedBuild(int position, List result, Run[] builds) { 140 | if (position >= 0 && position < builds.length) { 141 | boolean crashed = 142 | builds[position] == null 143 | || builds[position].getResult() == null 144 | || builds[position].getResult().isWorseThan(Result.UNSTABLE); 145 | if (crashed) { 146 | return true; 147 | } 148 | /*Preventing duplicates in whitelist. Not because of the graph, there is 149 | already chunk of code preventing from showing duplicity in the graph. 150 | (The final list are recreated again with help of these lists) 151 | Its because lenght of whitelist which is shown over the graph.*/ 152 | if (!result.contains(builds[position].getDisplayName())) { 153 | result.add(builds[position].getDisplayName()); 154 | } 155 | } 156 | return false; 157 | } 158 | 159 | @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", 160 | justification = "run.getResult().isWorseThan(Result.UNSTABLE) claims to have NPE, but I'm failing to see it") 161 | public ChartPointsWithBlacklist getReportPointsWithBlacklist(Job job, ChartModel chart) { 162 | List list = new ArrayList<>(); 163 | 164 | Predicate lineValidator = str -> { 165 | if (str == null || str.trim().isEmpty()) { 166 | return false; 167 | } 168 | int index = getBestDelimiterIndex(str); 169 | if (index == Integer.MAX_VALUE) { 170 | return false; 171 | } 172 | if (!str.substring(0, index).trim().equals(chart.getKey().trim())) { 173 | return false; 174 | } 175 | try { 176 | Double.parseDouble(str.substring(index + 1).trim()); 177 | return true; 178 | } catch (Exception ignore) { 179 | } 180 | return false; 181 | }; 182 | 183 | PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + chart.getFileNameGlob()); 184 | List blacklisted = getBlacklisted(job, chart); 185 | List whitelisted = getWhitelisted(job, chart); 186 | List whiteListWithoutSurroundings = getWhiteListWithoutSurroundings(job, chart); 187 | List pointsInRangeOfwhitelisted = new ArrayList<>(whitelisted); 188 | int whiteListSizeWithoutSurroundings = whiteListWithoutSurroundings.toArray().length; 189 | pointsInRangeOfwhitelisted.removeAll(whiteListWithoutSurroundings); 190 | for (Run run : job.getBuilds()) { 191 | if (run == null 192 | || run.getResult() == null 193 | || run.getResult().isWorseThan(Result.UNSTABLE)) { 194 | continue; 195 | } 196 | if (blacklisted.contains(run.getDisplayName())) { 197 | continue; 198 | } 199 | if (!whitelisted.contains(run.getDisplayName()) && !whitelisted.isEmpty()) { 200 | continue; 201 | } 202 | 203 | try (Stream filesStream = Files.walk(run.getRootDir().toPath()).sequential()) { 204 | Optional optPoint = filesStream 205 | .filter((p) -> matcher.matches(p.getFileName())) 206 | .map((p) -> pathToLine(p, lineValidator)) 207 | .filter((o) -> o.isPresent()) 208 | .map(o -> o.get()) 209 | .map(s -> new ChartPoint( 210 | run.getDisplayName(), 211 | run.getNumber(), 212 | extractValue(s), 213 | chart.getPointColor(pointsInRangeOfwhitelisted.contains(run.getDisplayName())))) 214 | .findFirst(); 215 | if (optPoint.isPresent()) { 216 | list.add(optPoint.get()); 217 | } 218 | } catch (Exception ex) { 219 | ex.printStackTrace(); 220 | } 221 | if (list.size() == chart.getLimit()) { 222 | break; 223 | } 224 | } 225 | 226 | Collections.reverse(list); 227 | 228 | return new ChartPointsWithBlacklist(list, blacklisted, whitelisted, whiteListSizeWithoutSurroundings); 229 | } 230 | 231 | private int getBestDelimiterIndex(String str) { 232 | int index1 = str.indexOf('='); 233 | int index2 = str.indexOf(':'); 234 | if (index1 < 0) { 235 | index1 = Integer.MAX_VALUE; 236 | } 237 | if (index2 < 0) { 238 | index2 = Integer.MAX_VALUE; 239 | } 240 | int index = Math.min(index1, index2); 241 | return index; 242 | } 243 | 244 | private Optional pathToLine(Path path, Predicate lineValidator) { 245 | try (Stream stream = Files.lines(path)) { 246 | return stream.filter(lineValidator).findFirst(); 247 | } catch (Exception ex) { 248 | ex.printStackTrace(); 249 | } 250 | return Optional.empty(); 251 | } 252 | 253 | private String extractValue(String s) { 254 | return s.substring(getBestDelimiterIndex(s) + 1).trim(); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jenkins-report-generic-chart-column 2 | Generic reusable plugin that will show a chart in column based on properties file. 3 | 4 | The plugin reads properties file in your archive, specified by glob, and use one value, defined by key, to draw a chart for both project and view. The plugin was originally designed to show results of benchmarks, but can be misused for anything key-number what desire chart. Eg total and failed tests summaries, watching over size of package and so on. The graph is scaled, so you will never miss smallest change. 5 | 6 | * [Properties file](#properties-file) 7 | * [Project summary](#project-summary) 8 | * [View summary](#view-summary) 9 | * [Changing build result](#changing-build-result) 10 | * [Testing the expressions](#testing-the-expression) 11 | * [Most common expressions](#most-common-expressions) 12 | * [Named queries](#named-queries) 13 | * [Denylist and Allowlist](#denylist-and-allowlist) 14 | * [Project Settings](#project-settings) 15 | * [View Settings](#view-settings) 16 | * [Limitations](#limitations) 17 | * [Future work](#future-work) 18 | 19 | ## Properties file 20 | To make plugin work, you need a [properties](https://en.wikipedia.org/wiki/.properties) file with results form your job, archived. The properties file is eg.: 21 | ``` 22 | lastSuccessfulBuild/artifact/jbb-report/result/specjbb2015-C-20180717-00001/report-00001/specjbb2015-C-20180717-00001.raw 23 | ``` 24 | ``` 25 | # garbage 26 | jbb2015.result.metric.max-jOPS = 22523 27 | jbb2015.result.metric.critical-jOPS = 8902 28 | jbb2015.result.SLA-10000-jOPS = 4774 29 | jbb2015.result.SLA-25000-jOPS = 7442 30 | jbb2015.result.SLA-50000-jOPS = 9643 31 | jbb2015.result.SLA-75000-jOPS = 11833 32 | jbb2015.result.SLA-100000-jOPS = 13791 33 | other garbage 34 | ``` 35 | The parser is quite forgiving, and will skip garabge. Supports both : and = delimiters. 36 | 37 | ## Project summary 38 | Hugest graphs are shown in project summary. You can have as much graphs as you wish, and have detailed tool-tip: 39 | ![project](https://user-images.githubusercontent.com/2904395/43015881-2747cb3a-8c51-11e8-9ccf-c6b4a0189e61.png) 40 | Comparing individual jobs was never more simple:) 41 | 42 | ## View summary 43 | You can include the graphs to the view: 44 | ![view](https://user-images.githubusercontent.com/2904395/43015883-278a339e-8c51-11e8-8656-5165b455d8ef.png) 45 | Comparing individual projects was never more simple:) 46 | 47 | You can of course mix it with other properties or other plugins 48 | ![view](https://user-images.githubusercontent.com/2904395/43015875-21c739fc-8c51-11e8-9026-c84127628634.png) 49 | 50 | The results in view are sort-able - they are sort by last valid result shown in chart. 51 | 52 | Comparing individual projects was never ever more simple:) 53 | 54 | ## Changing build result 55 | Each chart (there can be several by project) can have its own condition, on which result it can turn the build to unstable, if the condition is met. 56 | The mathematic part is handled by https://github.com/gbenroscience/ParserNG/, the logic part is internal. Exemplar expression: 57 | ``` 58 | avg(..L1)*1.1 < L0 | L1*1.3 < L0 59 | ``` 60 | The expression can be read as: If value of key in last build is bigger then average value of all builds before multiplied by 1.1 , or the last build is bigger then previous build multiplied by 1.3, turn the build to unstable 61 | 62 | You can read how it is evaluated here: https://github.com/judovana/jenkins-report-generic-chart-column/blob/master/src/main/resources/io/jenkins/plugins/genericchart/ChartModel/help-unstableCondition.html#L10 63 | 64 | The built which just ended is L0. Previous build is L1 and so on... You can use ranges - eg L5..L1 will return values of given **key** for build N-5,N-4-N-3,N-2,N-1 where N is current build - L0. Ranges can go withot limit - eg L3.. will exapnd as L3,L2,L1,L0. So obviously mmost used is ..L1 which returns you values of all except latests (L0) build. Count of points is MN. 65 | 66 | The L indexes, can be calcualted. To do so, use `L{expression}`. Eg L{MN/2} upon 1,2,3, will expand as L{3/2} -> L{1,5} -> L1 -> 2. The brackets can be cumulative (eg L{{1+1}}) and can contain Other L or MN. eg L{L0}. The only reason why this was created was to beable wrote ..L{MN/2} and L{MN/2}.. 67 | 68 | See the logic at: https://github.com/judovana/jenkins-report-generic-chart-column/blob/master/src/main/resources/io/jenkins/plugins/genericchart/ChartModel/help-unstableCondition.html#L2 69 | 70 | 71 | ### testing the expression 72 | 73 | Next to the cmdline/library of ParserNG where you can try yours expressions, you can do similarly with jenkins-report-generic-chart-column.jar; but it is not exactly straightforward to compose classpath. eg: 74 | ``` 75 | VALUES_PNG="1 2 3" java -cp jenkins-report-generic-chart-column.jar:parser-ng-0.1.8.jar io/jenkins/plugins/genericchart/math/ExpandingExpression "sum(..L0) < avg(..L0)" 76 | ``` 77 | or 78 | ``` 79 | VALUES_PNG="1 2 3" java -cp parser-ng-0.1.8.jar:jenkins-report-generic-chart-column.jar parser.ExpandingExpression "avg(..L{MN/2}) < avg(L{MN/2}..)" 80 | ``` 81 | or 82 | ``` 83 | VALUES_PNG="1 2 3" java -cp jenkins-report-generic-chart-column.jar:parser-ng-0.1.8.jar io/jenkins/plugins/genericchart/math/ExpandingExpression "sum(..L0) < avg(..L0)" 84 | ``` 85 | Currently all necessary changes were moved to ParserNG, including the `VALUES_PNG` variable. [ParserNG have powerfull CLI](https://github.com/gbenroscience/ParserNG#using-parserng-as-commandline-tool) and since `0.1.9` this expanding parser is here, so you can run it simply as java -jar: 86 | ``` 87 | VALUES_PNG='235000 232500 233000 236000 210000' java parser-ng-0.1.9.jar -e " echo(L{MN}..L0) " 88 | ``` 89 | or via its interactive CLI 90 | ``` 91 | $ VALUES_PNG='235000 232500 233000 236000 210000' java -jar parser-ng-0.1.9.jar -e -i 92 | ``` 93 |
Output 94 | 95 | ``` 96 | Welcome To ParserNG Command Line 97 | Math Question 1: 98 | ______________________________________________________ 99 | echo(L0..L{MN}) 100 | Answer 101 | ______________________________________________________ 102 | 210000 236000 233000 232500 235000 103 | Math Question 2: 104 | ______________________________________________________ 105 | echo(L{MN}..L0) 106 | Answer 107 | ______________________________________________________ 108 | 235000 232500 233000 236000 210000 109 | ``` 110 |
111 | 112 | ``` 113 | VALUES_PNG='235000 232500 233000 236000 210000' java -jar parser-ng-0.1.9.jar -e -i -v 114 | ``` 115 |
Output 116 | 117 | ``` 118 | Welcome To ParserNG Command Line 119 | 120 | Math Question 1: 121 | ______________________________________________________ 122 | L1 146 | 147 | ### Most common expressions 148 | #### Divergence from exact pivot 149 | If something should be some exact result, or must not be an exact result is most easy usage 150 | * `L0 == 5` if last result is 5, then the job will become unstable 151 | * `L0 != 5` which is same as 152 | * `![L0 == 5]` if last result is NOT 5, then the job will become unstable 153 | #### Immediate regression: 154 | * `threshold=5;-1*(L1/(L0/100)-100) < -threshold` which is same as 155 | * `threshold=5; (L1/(L0/100)-100) > threshold` for classical benchmark, like score, where more is better. The threshold is how much % is maximal drop it can bear, and 156 | * `threshold=5; (L1/(L0/100)-100) < -threshold` for eg size (where smaller is better) benchmark, or time-based where less is better . The threshold is how much % is maximal increase it can bear. 157 | * For stable things 5% should be the biggest regression rate. For unstable once usually 10% is OK to cover usual oscillation 158 | * Note, that those equation works fine for both big numbers and small numbers 159 | #### Short term regression 160 | Such last run against previous run can not catch constant degradation. To avoid that you may simply extends of [Immediate regression](#immediate-regression), only `L0` will compared against all previous runs - L1 will become something lile `..L1` (all except last run) 161 | 162 | You can then call `avg` or `avgN` functions above it or `geom` or `geomN` if you have to diverse data with huge thresholds. See parserNG help for descriptions of functions (you can type `help` also to the Jenkins settings for this equation) 163 | * `threshold=5;-1*(avg(..L1)/(L0/100)-100) < -threshold` which is same as 164 | * `threshold=5; (avg(..L1)/(L0/100)-100) > threshold` for classical benchmark, like score, where more is better. The threshold is how much % is maximal drop it can bear, and 165 | * `threshold=5; (avg(..L1)/(L0/100)-100) < -threshold` for eg.: size (where smaller is better) benchmark, or time-based where less is better . The threshold is how much % is maximal increase it can bear. 166 | #### Longer term regression 167 | * In basic comparison, you can compare any Lx with any Ly. Eg `(L2/(L0/100)-100) > threshold` or `(L{MN}/(L0/100)-100) > threshold` and so on. 168 | * The underlying evaluation is lenient, and eg L4 in size in set of two numbers, will have simply value of **last valid** (second in this case) number. 169 | * That's also why `L{MN}` works, although you should be explicitly writing `L{MN-1}` 170 | * example: ` VALUES_PNG='3 2 1' java -jar parser-ng-0.1.9.jar -e "echo(L5,L6,L7)"` will give you `3 3 3` 171 | * another,more generic solution, may achieved by simply extension of [Immediate regression](#immediate-regression), only `L0` will be replaced by something like `L0..L{MN/2}` (newer half of the set) and L1 by `L{MN/2}..L{MN}` (older half of the set) 172 | * You can then call `avg` or `avgN` functions above it or `geom` or `geomN` if you have to diverse data with huge thresholds. See parserNG help for descriptions of functions (you can type `help` also to the Jenkins settings for this equation) 173 | * `threshold=5;-1*(avg(L{MN/2}..L{MN})/(avg(L0..L{MN/2})/100)-100) < -threshold` which is same as 174 | * `threshold=5; (avg(L{MN/2}..L{MN})/(avg(L0..L{MN/2})/100)-100) > threshold` for classical benchmark, where more is better. The threshold is how much % is maximal drop it can bear
175 | * `threshold=5; (avg(L{MN/2}..L{MN})/(avg(L0..L{MN/2})/100)-100) < -threshold` for eg time-based or size benchmark, where less is better. The threshold is how much % is maximal increase it can bear. 176 | #### Gluing it all together 177 | You usually have more expressions which are catching your regressions, to connect them, you can use logical operators: 178 | ``` 179 | java -jar parser-ng-0.1.9.jar -l "help" 180 | Comparing operators - allowed with spaces:!=, ==, >=, <=, <, >; not allowed with spaces:le, ge, lt, gt, 181 | Logical operators - allowed with spaces:, |, &; not allowed with spaces:impl, xor, imp, eq, or, and 182 | As Mathematical parts are using () as brackets, Logical parts must be grouped by [] 183 | ``` 184 | Eg: 185 | * for classical benchmarks like score: 186 | ``` 187 | threshold=5;-1*(L1/(L0/100)-100) < -threshold || threshold=5;-1*(avg(L{MN/2}..L{MN})/(avg(L0..L{MN/2})/100)-100) < -threshold || threshold=5;-1*(avg(..L1)/(L0/100)-100) < -threshold 188 | ``` 189 | * for inverted benchmarks like time or size 190 | ``` 191 | threshold=5; (L1/(L0/100)-100) < -threshold || threshold=5; (avg(L{MN/2}..L{MN})/(avg(L0..L{MN/2})/100)-100) < -threshold || threshold=5; (avg(..L1)/(L0/100)-100) < -threshold 192 | ``` 193 | Note, that if the variables (eg my threshold above) are filled as they come to end. If you set it in first logical half, it can be reused in second without declaring it again (as I did in above example). Unluckily, you usually have thresholds different. You can re-declare (as I did) the variable or have different one (eg thresholdA and thresholdB) by ParserNG rules. 194 | 195 | 'avgN' and 'geomN' are usually producing better results, as they are getting rid of random extreme spikes by sorting the input, and removing `N lowest` and `N highest` values. N is first parameter. `avgN(0,...)` is identical to simply `avg(...)`: 196 | ``` 197 | VALUES_PNG='5 5 1 8 5 5' java -jar parser-ng-0.1.9.jar -e "avgN(0,..L0)" 198 | 4.833333333 199 | ``` 200 | but 201 | ``` 202 | VALUES_PNG='5 5 1 8 5 5' java -jar parser-ng-0.1.9.jar -e "avgN(1,..L0)" 203 | 5 204 | ``` 205 | as 8 and 1 were removed from list. So: 206 | * for classical benchmarks like score: 207 | ``` 208 | cut=2;threshold=5;-1*(L1/(L0/100)-100) < -threshold || threshold=5;-1*(avgN(cut,L{MN/2}..L{MN})/(avgN(cut,L0..L{MN/2})/100)-100) < -threshold || threshold=5;-1*(avgN(cut,..L1)/(L0/100)-100) < -threshold 209 | ``` 210 | * for inverted benchmarks like time or size 211 | ``` 212 | cut=2;threshold=5; (L1/(L0/100)-100) < -threshold || threshold=5; (avgN(cut,L{MN/2}..L{MN})/(avgN(cut,L0..L{MN/2})/100)-100) < -threshold || threshold=5; (avgN(cut,..L1)/(L0/100)-100) < -threshold 213 | ``` 214 | Is what yoy usually end with 215 | 216 | #### Named queries 217 | As it maybe boring and error prone to keep repeating compelx equations, you can set the equation in the global settings and then just call it via its name - even with different parameters. 218 | 219 | There are same named queries already embedded: https://github.com/jenkinsci/report-generic-chart-column-plugin/blob/master/src/main/resources/io/jenkins/plugins/genericchart/presetEquations 220 | 221 | ``` 222 | # FIRST_LINE_IS_ALWAYS_NAME 223 | # then soem documentation 224 | Then, on multiple lines without hash (as parserng -t) 225 | there is the expression with /*1*/ /*2*/ 226 | upto /*9*/ as placeholders for your arguments 227 | 228 | # empty line then ends up the expression. 229 | # you ca then call FIRST_LINE_IS_ALWAYS_NAME arg1 arg2 ... 230 | ``` 231 | Well see [examples](https://github.com/jenkinsci/report-generic-chart-column-plugin/blob/master/src/main/resources/io/jenkins/plugins/genericchart/presetEquations) and dont forget you can set up yor own in settings. 232 | 233 | ## Denylist and Allowlist 234 | you could noted, that the graphs are scaled. If you have run, which escapes the normality, the scale get corrupted, and you can easily miss regression. To fix this, you have denylist (and allowlist). This is list of regexes, which filters (first) out and (second) in the (un)desired builds. It works both with custom_built_name and `#build_number` (note the hash). Empty denylist/allowlist means it is not used at all. 235 | 236 | ## Project Settings 237 | Project settings and view settings are separate - with both pros and cons! 238 | 239 | ![selection_012](https://user-images.githubusercontent.com/11722903/48773059-8b53ba00-ecc6-11e8-84eb-c0bbdc7774c4.png) 240 | Most important is **Glob pattern for the report file to parse**, which lets you specify not absolute (glob) path to your properties file and of course **Key to look for in the report file** which tells chart what value to render. **Chart name** and **color** are cosmetic, **denylist** and **allowlist** were already described. **Number of data points to show** is how many successful builds (counted from end) should be displayed. If you are in doubts, each suspicious field have help. 241 | 242 | ## View Settings 243 | Project settings and view settings are separate - with both pros and cons! 244 | 245 | ![selection_011](https://user-images.githubusercontent.com/11722903/48773095-a292a780-ecc6-11e8-9759-f0d4900fdc33.png) 246 | You can see that the settings of view are same - thus duplicated with all its pros and cons... 247 | 248 | ## Limitations 249 | 250 | The limitations flows from double settings and from fact that each chart can show only only one value. The non-shared denylist/allowlist is a negative which we are working on to improve. One line only is considered as - due to scaled graph - definitely positive. 251 | 252 | ## Range around allowlisted 253 | 254 | Number of points before and after chosen point using allowlist. For example if you have allowlisted 3 and 4 and range is 2 graph will show points 1 2 3 4 5 6. 255 | ![selection_008](https://user-images.githubusercontent.com/11722903/48713596-08bcf300-ec11-11e8-9894-4e8445d612f9.png) 256 | Up is without allowlisted and range. Down is with allowlist (1.8.0.172.\*) and range (3). 257 | ![selection_008](https://user-images.githubusercontent.com/11722903/48712892-37d26500-ec0f-11e8-92be-62acf31c6bdd.png) 258 | Up is with allowlist (1.8.0.172.\*) and range (3). Down is with allowlist (1.8.0.172.\*) and without range. 259 | ![selection_007](https://user-images.githubusercontent.com/11722903/48713581-fe9af480-ec10-11e8-898f-3ac208b809a8.png) 260 | ## Future work 261 | We wish to improve allowlist/denylist feature, so it can be used to generate views comparing selected runs across jobs with some kind of neighborhood 262 | 263 | # Dependencies 264 | This plugin depends on [chartjs-api](https://github.com/jenkinsci/chartjs-api-plugin) library plugin and on [parser-ng](https://github.com/gbenroscience/ParserNG/) math library 1.9 or up 265 | --------------------------------------------------------------------------------