Skip to content

Commit cb823d3

Browse files
DainerxChadiEM
authored andcommitted
Unstable rate as percentage
1 parent c8136d3 commit cb823d3

File tree

9 files changed

+248
-7
lines changed

9 files changed

+248
-7
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Provides additional metrics via columns in Jenkins' List View.
1010
### Provided Metrics
1111
- Minimum, Maximum, Average, and Standard Deviation build times for all, or only successful builds.
1212
- Minimum, Maximum, and Average checkout times for Pipeline builds.
13-
- Success and Failure rates.
13+
- Success, Failure, and Unstable rates.
1414
- Success and Failure time rates (ie Uptime and Downtime).
1515

1616
![](images/screenshot.png)
@@ -53,6 +53,7 @@ Using XPath, it is possible to get the metrics for one project. You may need to
5353
<standardDeviationSuccessDuration>493</standardDeviationSuccessDuration>
5454
<successRate>0.875</successRate>
5555
<successTimeRate>0.9999928829722644</successTimeRate>
56+
<unstableRate>0.875</unstableRate>
5657
</jobMetrics>
5758
```
5859

@@ -88,7 +89,8 @@ You will get an output similar to the below (modified for clarity):
8889
"standardDeviationDuration": 42447,
8990
"standardDeviationSuccessDuration": 71238,
9091
"successRate": 0.25,
91-
"successTimeRate": 0.9050665090992799
92+
"successTimeRate": 0.9050665090992799,
93+
"unstableRate": 0
9294
}
9395
}
9496
],
@@ -114,7 +116,8 @@ You will get an output similar to the below (modified for clarity):
114116
"standardDeviationDuration": 8089,
115117
"standardDeviationSuccessDuration": 3014,
116118
"successRate": 1,
117-
"successTimeRate": 1
119+
"successTimeRate": 1,
120+
"unstableRate": 0
118121
}
119122
}
120123
],

src/main/java/org/jenkinsci/plugins/additionalmetrics/Helpers.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Helpers {
3737
static final ToLongFunction<Run> RUN_CHECKOUT_DURATION = CheckoutDuration::checkoutDurationOf;
3838

3939
static final Predicate<Run> SUCCESS = run -> run.getResult() == Result.SUCCESS;
40+
static final Predicate<Run> UNSTABLE = run -> run.getResult() == Result.UNSTABLE;
4041
static final Predicate<Run> NOT_SUCCESS = SUCCESS.negate();
4142
static final Predicate<Run> COMPLETED = run -> !run.isBuilding();
4243

src/main/java/org/jenkinsci/plugins/additionalmetrics/JobMetrics.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,14 @@ public long getStandardDeviationSuccessDuration() {
187187

188188
return 0;
189189
}
190+
191+
@Exported
192+
public double getUnstableRate() {
193+
UnstableRateColumn unstableRateColumn = new UnstableRateColumn();
194+
Rate unstableRate = unstableRateColumn.getUnstableRate(job);
195+
if (unstableRate != null) {
196+
return unstableRate.getAsDouble();
197+
}
198+
return 0.0;
199+
}
190200
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2023 Chadi El Masri
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 all
14+
* 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 THE
22+
* SOFTWARE.
23+
*/
24+
25+
package org.jenkinsci.plugins.additionalmetrics;
26+
27+
import static org.jenkinsci.plugins.additionalmetrics.Helpers.COMPLETED;
28+
import static org.jenkinsci.plugins.additionalmetrics.Helpers.UNSTABLE;
29+
import static org.jenkinsci.plugins.additionalmetrics.Utils.rateOf;
30+
31+
import hudson.Extension;
32+
import hudson.model.Job;
33+
import hudson.model.Run;
34+
import hudson.views.ListViewColumn;
35+
import org.jenkinsci.Symbol;
36+
import org.kohsuke.stapler.DataBoundConstructor;
37+
38+
public class UnstableRateColumn extends ListViewColumn {
39+
40+
@DataBoundConstructor
41+
public UnstableRateColumn() {
42+
super();
43+
}
44+
45+
@Metric
46+
public Rate getUnstableRate(Job<? extends Job, ? extends Run> job) {
47+
return rateOf(job.getBuilds(), COMPLETED, UNSTABLE).orElse(null);
48+
}
49+
50+
@Extension
51+
@Symbol("unstableRate")
52+
public static class DescriptorImpl extends AdditionalMetricColumnDescriptor {
53+
54+
public DescriptorImpl() {
55+
super(Messages.UnstableRateColumn_DisplayName());
56+
}
57+
}
58+
}

src/main/resources/index.jelly

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
-->
2424
<?jelly escape-by-default='true'?>
2525
<div>
26-
Provides additional metrics for List Views: Success/Failure Rates, Min/Max/Avg run and checkout
26+
Provides additional metrics for List Views: Success/Failure/Unstable Rates, Min/Max/Avg run and checkout
2727
durations, Success/Failure
2828
Time Rates.
2929
</div>

src/main/resources/org/jenkinsci/plugins/additionalmetrics/Messages.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# MIT License
33
#
4-
# Copyright (c) 2019 Chadi El Masri
4+
# Copyright (c) 2022 Chadi El Masri, Oussama Ben Ghorbel
55
#
66
# Permission is hereby granted, free of charge, to any person obtaining a copy
77
# of this software and associated documentation files (the "Software"), to deal
@@ -27,6 +27,7 @@ AvgSuccessDurationColumn.DisplayName=Average Success Duration
2727
StdevSuccessDurationColumn.DisplayName=Standard Deviation Success Duration
2828
StdevDurationColumn.DisplayName=Standard Deviation Duration
2929
FailureRateColumn.DisplayName=Failure Rate
30+
UnstableRateColumn.DisplayName=Unstable Rate
3031
FailureTimeRateColumn.DisplayName=Failure Time Rate
3132
MaxDurationColumn.DisplayName=Max Duration
3233
MaxCheckoutDurationColumn.DisplayName=Max Checkout Duration
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?jelly escape-by-default='true'?>
2+
<!--
3+
~ MIT License
4+
~
5+
~ Copyright (c) 2022 Chadi El Masri, Oussama Ben Ghorbel
6+
~
7+
~ Permission is hereby granted, free of charge, to any person obtaining a copy
8+
~ of this software and associated documentation files (the "Software"), to deal
9+
~ in the Software without restriction, including without limitation the rights
10+
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
~ copies of the Software, and to permit persons to whom the Software is
12+
~ furnished to do so, subject to the following conditions:
13+
~
14+
~ The above copyright notice and this permission notice shall be included in all
15+
~ copies or substantial portions of the Software.
16+
~
17+
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
~ SOFTWARE.
24+
-->
25+
26+
<j:jelly xmlns:j="jelly:core">
27+
<j:set var="unstableRate" value="${it.getUnstableRate(job)}"/>
28+
<td data="${unstableRate.asDouble ?: '0.0'}">
29+
<j:choose>
30+
<j:when test="${unstableRate!=null}">
31+
${unstableRate.asString}
32+
</j:when>
33+
<j:otherwise>
34+
${%N/A}
35+
</j:otherwise>
36+
</j:choose>
37+
</td>
38+
</j:jelly>

src/test/java/org/jenkinsci/plugins/additionalmetrics/MetricsActionFactoryTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ public void no_runs_metrics_should_be_zeros() throws IOException, SAXException {
9090
"standardDeviationDuration",
9191
isEqualTo(0),
9292
"standardDeviationSuccessDuration",
93-
isEqualTo(0)));
93+
isEqualTo(0),
94+
"unstableRate",
95+
isEqualTo(0.0)));
9496
}
9597
}
9698

@@ -133,7 +135,9 @@ public void one_run_should_have_appropriate_metrics()
133135
"standardDeviationDuration",
134136
isEqualTo(0),
135137
"standardDeviationSuccessDuration",
136-
isEqualTo(0)));
138+
isEqualTo(0),
139+
"unstableRate",
140+
isEqualTo(0.0)));
137141
}
138142
}
139143

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2023 Chadi El Masri
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 all
14+
* 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 THE
22+
* SOFTWARE.
23+
*/
24+
25+
package org.jenkinsci.plugins.additionalmetrics;
26+
27+
import static org.jenkinsci.plugins.additionalmetrics.PipelineDefinitions.failingDefinition;
28+
import static org.jenkinsci.plugins.additionalmetrics.PipelineDefinitions.successDefinition;
29+
import static org.jenkinsci.plugins.additionalmetrics.PipelineDefinitions.unstableDefinition;
30+
import static org.jenkinsci.plugins.additionalmetrics.UIHelpers.createAndAddListView;
31+
import static org.jenkinsci.plugins.additionalmetrics.UIHelpers.dataOf;
32+
import static org.jenkinsci.plugins.additionalmetrics.UIHelpers.getListViewCell;
33+
import static org.junit.Assert.assertEquals;
34+
35+
import com.gargoylesoftware.htmlunit.html.DomNode;
36+
import hudson.model.ListView;
37+
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
38+
import org.junit.Before;
39+
import org.junit.ClassRule;
40+
import org.junit.Test;
41+
import org.jvnet.hudson.test.JenkinsRule;
42+
43+
public class UnstableRateColumnTest {
44+
@ClassRule
45+
public static final JenkinsRule jenkinsRule = new JenkinsRule();
46+
47+
private UnstableRateColumn unstableRateColumn;
48+
49+
@Before
50+
public void before() {
51+
unstableRateColumn = new UnstableRateColumn();
52+
}
53+
54+
@Test
55+
public void no_unstable_job_should_be_0_percent() throws Exception {
56+
WorkflowJob project = jenkinsRule.createProject(WorkflowJob.class, "ProjectWithNoUnstableBuilds");
57+
project.setDefinition(failingDefinition());
58+
project.scheduleBuild2(0).get();
59+
project.setDefinition(successDefinition());
60+
project.scheduleBuild2(0).get();
61+
Rate unstableRate = unstableRateColumn.getUnstableRate(project);
62+
assertEquals(0.0, unstableRate.getAsDouble(), 0);
63+
}
64+
65+
@Test
66+
public void one_unstable_job_over_two_failed_should_be_50_percent() throws Exception {
67+
WorkflowJob project = jenkinsRule.createProject(WorkflowJob.class, "ProjectWithOneUnstableJob");
68+
project.setDefinition(unstableDefinition());
69+
project.scheduleBuild2(0).get();
70+
project.setDefinition(failingDefinition());
71+
project.scheduleBuild2(0).get();
72+
Rate unstableRate = unstableRateColumn.getUnstableRate(project);
73+
assertEquals(0.5, unstableRate.getAsDouble(), 0);
74+
}
75+
76+
@Test
77+
public void no_runs_should_display_as_NA_in_UI() throws Exception {
78+
WorkflowJob project = jenkinsRule.createProject(WorkflowJob.class, "ProjectWithZeroBuildsForUI");
79+
80+
ListView listView =
81+
createAndAddListView(jenkinsRule.getInstance(), "MyListNoRuns", unstableRateColumn, project);
82+
83+
DomNode columnNode;
84+
try (JenkinsRule.WebClient webClient = jenkinsRule.createWebClient()) {
85+
columnNode = getListViewCell(
86+
webClient.getPage(listView), listView, project.getName(), unstableRateColumn.getColumnCaption());
87+
}
88+
89+
assertEquals("N/A", columnNode.asNormalizedText());
90+
assertEquals("0.0", columnNode.getAttributes().getNamedItem("data").getNodeValue());
91+
}
92+
93+
@Test
94+
public void one_unstable_over_one_failed_should_display_percentage_in_UI() throws Exception {
95+
WorkflowJob project = jenkinsRule.createProject(WorkflowJob.class, "ProjectWithOneBuildForUI");
96+
project.setDefinition(unstableDefinition());
97+
project.scheduleBuild2(0).get();
98+
99+
ListView listView =
100+
createAndAddListView(jenkinsRule.getInstance(), "MyListOneRun", unstableRateColumn, project);
101+
102+
DomNode columnNode;
103+
try (JenkinsRule.WebClient webClient = jenkinsRule.createWebClient()) {
104+
columnNode = getListViewCell(
105+
webClient.getPage(listView), listView, project.getName(), unstableRateColumn.getColumnCaption());
106+
}
107+
108+
assertEquals("100.00%", columnNode.asNormalizedText());
109+
assertEquals("1.0", dataOf(columnNode));
110+
}
111+
112+
@Test
113+
public void one_unstable_job_over_four_jobs_should_be_25_percent() throws Exception {
114+
WorkflowJob project = jenkinsRule.createProject(WorkflowJob.class, "ProjectWithFourJobs");
115+
project.setDefinition(unstableDefinition());
116+
project.scheduleBuild2(0).get();
117+
project.setDefinition(failingDefinition());
118+
project.scheduleBuild2(0).get();
119+
project.scheduleBuild2(0).get();
120+
project.setDefinition(successDefinition());
121+
project.scheduleBuild2(0).get();
122+
123+
Rate unstableRate = unstableRateColumn.getUnstableRate(project);
124+
assertEquals(0.25, unstableRate.getAsDouble(), 0);
125+
}
126+
}

0 commit comments

Comments
 (0)