Skip to content

Commit 7a74f72

Browse files
authored
Merge pull request #138 from jenkinsci/filters
Add status filter for agent history
2 parents 965936a + aed041a commit 7a74f72

File tree

15 files changed

+481
-267
lines changed

15 files changed

+481
-267
lines changed

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
<properties>
3636
<changelist>999999-SNAPSHOT</changelist>
3737
<!-- https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/ -->
38-
<jenkins.baseline>2.479</jenkins.baseline>
38+
<jenkins.baseline>2.492</jenkins.baseline>
3939
<jenkins.version>${jenkins.baseline}.3</jenkins.version>
4040
<gitHubRepo>jenkinsci/pipeline-agent-build-history-plugin</gitHubRepo>
4141
<ban-junit4-imports.skip>false</ban-junit4-imports.skip>
@@ -46,7 +46,7 @@
4646
<dependency>
4747
<groupId>io.jenkins.tools.bom</groupId>
4848
<artifactId>bom-${jenkins.baseline}.x</artifactId>
49-
<version>5054.v620b_5d2b_d5e6</version>
49+
<version>5473.vb_9533d9e5d88</version>
5050
<type>pom</type>
5151
<scope>import</scope>
5252
</dependency>

src/main/java/io/jenkins/plugins/agent_build_history/AgentBuildHistory.java

Lines changed: 68 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import hudson.model.Computer;
77
import hudson.model.Job;
88
import hudson.model.Node;
9+
import hudson.model.Result;
910
import hudson.model.Run;
1011
import hudson.util.RunList;
1112
import jakarta.servlet.http.Cookie;
@@ -56,6 +57,18 @@ public static String getCookieValue(StaplerRequest2 req, String name, String def
5657
return defaultValue; // Fallback to default if cookie not found
5758
}
5859

60+
private static int getRequestInteger(StaplerRequest2 req, String name, int defaultValue) {
61+
try {
62+
String value = req.getParameter(name);
63+
if (value == null) {
64+
return defaultValue;
65+
}
66+
return Integer.parseInt(req.getParameter(name));
67+
} catch (NumberFormatException e) {
68+
return defaultValue;
69+
}
70+
}
71+
5972
/*
6073
* used by jelly
6174
*/
@@ -88,25 +101,23 @@ public RunListTable getHandler() {
88101
RunListTable runListTable = new RunListTable(computer.getName());
89102
//Get Parameters from URL
90103
StaplerRequest2 req = Stapler.getCurrentRequest2();
91-
int page = req.getParameter("page") != null ? Integer.parseInt(req.getParameter("page")) : 1;
92-
int pageSize = req.getParameter("pageSize") != null ? Integer.parseInt(req.getParameter("pageSize")) : Integer.parseInt(getCookieValue(req, "pageSize", "20"));
104+
int page = getRequestInteger(req, "page", 1);
105+
int pageSize = getRequestInteger(req, "pageSize", Integer.parseInt(getCookieValue(req, "pageSize", "20")));
93106
String sortColumn = req.getParameter("sortColumn") != null ? req.getParameter("sortColumn") : getCookieValue(req, "sortColumn", "startTime");
94107
String sortOrder = req.getParameter("sortOrder") != null ? req.getParameter("sortOrder") : getCookieValue(req, "sortOrder", "desc");
95-
//Update totalPages depending on pageSize
96-
int totalEntries = BuildHistoryFileManager.readIndexFile(computer.getName(), AgentBuildHistoryConfig.get().getStorageDir()).size();
97-
totalPages = (int) Math.ceil((double) totalEntries / pageSize);
108+
String statusFilter = req.getParameter("status") != null ? req.getParameter("status") : "all";
109+
List<String> indexLines = BuildHistoryFileManager.readIndexFile(computer.getName(), AgentBuildHistoryConfig.get().getStorageDir());
98110

99111
LOGGER.finer("Getting runs for node: " + computer.getName() + " page: " + page + " pageSize: " + pageSize + " sortColumn: " + sortColumn + " sortOrder: " + sortOrder);
112+
LOGGER.finer("Found " + indexLines.size() + " entries for node " + computer.getName());
100113

101-
int start = (page - 1) * pageSize;
102-
runListTable.setRuns(getExecutionsForNode(computer.getName(), start, pageSize, sortColumn, sortOrder));
114+
runListTable.setRuns(getExecutionsForNode(indexLines, computer.getName(), page, pageSize, sortColumn, sortOrder, statusFilter));
103115
return runListTable;
104116
}
105117

106-
public List<AgentExecution> getExecutionsForNode(String nodeName, int start, int limit, String sortColumn, String sortOrder) {
107-
String storageDir = AgentBuildHistoryConfig.get().getStorageDir();
108-
List<String> indexLines = BuildHistoryFileManager.readIndexFile(nodeName, storageDir);
109-
LOGGER.finer("Found " + indexLines.size() + " entries for node " + nodeName);
118+
private record Execution(Job<?, ?> job, Run<?, ?> run, int buildNumber) {}
119+
120+
public List<AgentExecution> getExecutionsForNode(List<String> indexLines, String nodeName, int page, int pageSize, String sortColumn, String sortOrder, String statusFilter) {
110121
if (indexLines.isEmpty()) {
111122
return List.of();
112123
}
@@ -129,37 +140,66 @@ public List<AgentExecution> getExecutionsForNode(String nodeName, int start, int
129140
}
130141
break;
131142
default:
132-
comparison = 0;
133143
}
134144
return sortOrder.equals("asc") ? comparison : -comparison;
135145
});
136-
// Apply pagination
137-
int end = Math.min(start + limit, indexLines.size());
138-
List<String> page = indexLines.subList(start, end);
139-
List<AgentExecution> result = new ArrayList<>();
140146

141-
for (String line : page) {
147+
List<AgentExecution> executions = new ArrayList<>();
148+
List<Execution> filtered = new ArrayList<>();
149+
150+
for (String line : indexLines) {
142151
String[] parts = line.split(BuildHistoryFileManager.separator);
143152
String jobName = parts[0];
153+
Job<?, ?> job = Jenkins.get().getItemByFullName(jobName, Job.class);
154+
if (job == null) {
155+
continue;
156+
}
144157
int buildNumber = Integer.parseInt(parts[1]);
145-
// Load execution using deserialization
146-
AgentExecution execution = loadSingleExecution(jobName, buildNumber);
158+
Result result;
159+
Run<?, ?> run = null;
160+
if (parts.length > 3) {
161+
result = Result.fromString(parts[3]);
162+
} else {
163+
run = job.getBuildByNumber(buildNumber);
164+
if (run == null) {
165+
continue;
166+
}
167+
result = run.getResult();
168+
}
169+
if (!Utils.includeRun(result, statusFilter)) {
170+
continue;
171+
}
172+
filtered.add(new Execution(job, run, buildNumber));
173+
}
174+
175+
// Apply pagination
176+
int start = Math.min(filtered.size(), (page - 1) * pageSize);
177+
int end = Math.min(start + pageSize, filtered.size());
178+
179+
int totalEntries = filtered.size();
180+
totalPages = (int) Math.ceil((double) totalEntries / pageSize);
181+
182+
List<Execution> paginated = filtered.subList(start, end);
183+
// Load execution using deserialization
184+
for (Execution exec : paginated) {
185+
AgentExecution execution = loadSingleExecution(exec);
147186
if (execution != null) {
148-
result.add(execution);
187+
executions.add(execution);
149188
}
150189
}
151-
LOGGER.finer("Returning " + result.size() + " entries for node " + nodeName);
152-
return result;
190+
LOGGER.finer("Returning " + executions.size() + " entries for node " + nodeName);
191+
return executions;
153192
}
154193

155-
public static AgentExecution loadSingleExecution(String jobName, int buildNumber) {
156-
Job<?, ?> job = Jenkins.get().getItemByFullName(jobName, Job.class);
157-
Run<?, ?> run = null;
158-
if (job != null) {
159-
run = job.getBuildByNumber(buildNumber);
194+
private static AgentExecution loadSingleExecution(Execution exec) {
195+
Run<?, ?> run;
196+
if (exec.run != null) {
197+
run = exec.run;
198+
} else {
199+
run = exec.job.getBuildByNumber(exec.buildNumber);
160200
}
161201
if (run == null) {
162-
LOGGER.info("Run not found for " + jobName + " #" + buildNumber);
202+
LOGGER.fine("Run not found for " + exec.job.getFullName() + " #" + exec.buildNumber);
163203
return null;
164204
}
165205
LOGGER.finer("Loading run " + run.getFullDisplayName());

src/main/java/io/jenkins/plugins/agent_build_history/AgentBuildHistoryListeners.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ public void onDeleted(Run run) {
3434
BuildHistoryFileManager.deleteExecution(nodeName, jobName, buildNumber, AgentBuildHistoryConfig.get().getStorageDir());
3535
}
3636
}
37+
38+
@Override
39+
public void onFinalized(Run<?, ?> run) {
40+
Set<String> nodeNames = BuildHistoryFileManager.getAllSavedNodeNames(AgentBuildHistoryConfig.get().getStorageDir());
41+
for (String nodeName : nodeNames) {
42+
BuildHistoryFileManager.updateResult(nodeName, run, AgentBuildHistoryConfig.get().getStorageDir());
43+
}
44+
}
3745
}
3846

3947
@Extension

src/main/java/io/jenkins/plugins/agent_build_history/BuildHistoryFileManager.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.jenkins.plugins.agent_build_history;
22

3+
import hudson.model.Result;
34
import hudson.model.Run;
45

56
import java.io.BufferedReader;
@@ -58,17 +59,47 @@ public static List<String> readIndexFile(String nodeName, String storageDir) {
5859
}
5960
}
6061

62+
public static void updateResult(String nodeName, Run<?, ?> run, String storageDir) {
63+
Object lock = getNodeLock(nodeName);
64+
synchronized (lock) {
65+
List<String> indexLines = readIndexFile(nodeName, storageDir);
66+
File indexFile = new File(storageDir + "/" + nodeName + "_index.txt");
67+
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFile), StandardCharsets.UTF_8))) {
68+
boolean found = false;
69+
for (String line : indexLines) {
70+
if (line.startsWith(run.getParent().getFullName() + separator + run.getNumber() + separator)) {
71+
if (!line.endsWith(separator + run.getResult())) {
72+
writeLine(writer, run);
73+
} else {
74+
writer.write(line);
75+
}
76+
found = true;
77+
} else {
78+
writer.write(line);
79+
}
80+
writer.newLine();
81+
}
82+
if (!found) {
83+
writeLine(writer, run);
84+
writer.newLine();
85+
}
86+
} catch (IOException e) {
87+
LOGGER.log(Level.WARNING, "Failed to update result in index-file for node " + nodeName, e);
88+
}
89+
}}
90+
6191
// Appends a new run entry and updates the index
6292
public static void addRunToNodeIndex(String nodeName, Run<?, ?> run, String storageDir) {
6393
Object lock = getNodeLock(nodeName);
6494
synchronized (lock) {
6595
// Update index for the node
6696
File indexFile = new File(storageDir + "/" + nodeName + "_index" + ".txt");
6797
List<String> lines = readIndexFile(nodeName, storageDir);
68-
boolean exists = lines.contains(run.getParent().getFullName() + separator + run.getNumber() + separator + run.getStartTimeInMillis());
98+
String lineMatch = run.getParent().getFullName() + separator + run.getNumber() + separator;
99+
boolean exists = lines.stream().anyMatch(line -> line.startsWith(lineMatch));
69100
if (!exists) {
70101
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFile, true), StandardCharsets.UTF_8))) {
71-
writer.write(run.getParent().getFullName() + separator + run.getNumber() + separator + run.getStartTimeInMillis());
102+
writeLine(writer, run);
72103
writer.newLine();
73104
} catch (IOException e) {
74105
LOGGER.log(Level.WARNING, "Failed to update index for node " + nodeName, e);
@@ -77,6 +108,16 @@ public static void addRunToNodeIndex(String nodeName, Run<?, ?> run, String stor
77108
}
78109
}
79110

111+
private static void writeLine(BufferedWriter writer, Run<?, ?> run) throws IOException {
112+
writer.write(run.getParent().getFullName() + separator + run.getNumber() + separator + run.getStartTimeInMillis());
113+
if (!run.isLogUpdated()) {
114+
Result result = run.getResult();
115+
if (result != null) {
116+
writer.write(separator + result.toString());
117+
}
118+
}
119+
}
120+
80121
// Method for getting all saved node names
81122
public static Set<String> getAllSavedNodeNames(String storageDir) {
82123
Set<String> nodeNames = new HashSet<>();

src/main/java/io/jenkins/plugins/agent_build_history/RunListTable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ private JSONObject calculateFlowNode(AgentExecution.FlowNodeExecution node) {
8989
}
9090

9191
private String formatStartTime(long startTimeInMillis) {
92-
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss:SSS");
92+
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
9393
return formatter.format(new Date(startTimeInMillis));
9494
}
9595

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.jenkins.plugins.agent_build_history;
2+
3+
import hudson.model.Result;
4+
import org.kohsuke.accmod.Restricted;
5+
import org.kohsuke.accmod.restrictions.NoExternalUse;
6+
7+
@Restricted(NoExternalUse.class)
8+
public class Utils {
9+
10+
static boolean includeRun(Result result, String statusFilter) {
11+
if (!"all".equals(statusFilter) && result != null) {
12+
if ("success".equals(statusFilter) && result != Result.SUCCESS) {
13+
return false;
14+
}
15+
if ("unstable".equals(statusFilter) && result != Result.UNSTABLE) {
16+
return false;
17+
}
18+
if ("failure".equals(statusFilter) && result != Result.FAILURE) {
19+
return false;
20+
}
21+
if ("aborted".equals(statusFilter) && result != Result.ABORTED) {
22+
return false;
23+
}
24+
}
25+
return true;
26+
}
27+
28+
}

src/main/java/io/jenkins/plugins/agent_build_history/WorkflowJobTrend.java

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
import hudson.Util;
44
import hudson.model.Cause;
55
import hudson.model.Node;
6-
import hudson.model.Result;
76
import hudson.model.Run;
87
import java.util.ArrayList;
9-
import java.util.Collections;
108
import java.util.List;
119
import jenkins.console.ConsoleUrlProvider;
1210
import jenkins.model.Jenkins;
@@ -36,25 +34,22 @@ public class WorkflowJobTrend {
3634
private static final double MAX_LIKELY_RUNS = 20;
3735
private static final int MAX_PER_PAGE = 40;
3836
private final List<WorkflowRunResult> results = new ArrayList<>();
39-
private final List<WorkflowRun> runs;
40-
private final String statusFilter;
4137
private final String agentFilter;
4238
private final boolean filterByAgent;
4339
private int startNewer;
4440
private int startOlder;
4541
private int oldestBuild;
4642
private int newestBuild;
4743
private int startBuild;
44+
private boolean hasMoreRuns = false;
4845

4946
public WorkflowJobTrend(WorkflowJob job, String statusFilter, String agentFilter, String startBuildString) {
50-
this.statusFilter = statusFilter;
5147
this.agentFilter = agentFilter;
5248
this.filterByAgent = Util.fixEmptyAndTrim(agentFilter) != null;
5349
startBuild = Integer.parseInt(startBuildString);
5450
Run<WorkflowJob, WorkflowRun> lastRun = job.getLastBuild();
5551
if (lastRun != null) {
5652
Run<WorkflowJob, WorkflowRun> firstRun = job.getFirstBuild();
57-
this.runs = new ArrayList<>();
5853
newestBuild = lastRun.getNumber();
5954
if (startBuild == -1) {
6055
startBuild = newestBuild;
@@ -69,10 +64,14 @@ public WorkflowJobTrend(WorkflowJob job, String statusFilter, String agentFilter
6964
WorkflowRun n = job.getNearestBuild(startBuild);
7065
int i = 0;
7166
while (n != null && i < MAX_PER_PAGE) {
72-
if (includeRun(n)) {
73-
runs.add(n);
74-
i++;
67+
if (Utils.includeRun(n.getResult(), statusFilter)) {
68+
WorkflowRunResult result = calculate(n);
69+
if (result != null) {
70+
results.add(result);
71+
i++;
72+
}
7573
}
74+
7675
endBuild = n.getNumber() - 1;
7776
n = n.getPreviousBuild();
7877
}
@@ -86,8 +85,8 @@ public WorkflowJobTrend(WorkflowJob job, String statusFilter, String agentFilter
8685
if (startNewer > newestBuild) {
8786
startNewer = newestBuild;
8887
}
88+
hasMoreRuns = n != null;
8989
} else {
90-
this.runs = Collections.emptyList();
9190
startOlder = -1;
9291
startNewer = Integer.MAX_VALUE;
9392
oldestBuild = -1;
@@ -115,36 +114,15 @@ public int getStartBuild() {
115114
return startBuild;
116115
}
117116

118-
public WorkflowJobTrend run() {
119-
return this;
117+
public boolean isHasMoreRuns() {
118+
return hasMoreRuns;
120119
}
121120

122-
private boolean includeRun(WorkflowRun run) {
123-
Result result = run.getResult();
124-
if (!"all".equals(statusFilter) && result != null) {
125-
if ("success".equals(statusFilter) && result != Result.SUCCESS) {
126-
return false;
127-
}
128-
if ("unstable".equals(statusFilter) && result != Result.UNSTABLE) {
129-
return false;
130-
}
131-
if ("failure".equals(statusFilter) && result != Result.FAILURE) {
132-
return false;
133-
}
134-
if ("aborted".equals(statusFilter) && result != Result.ABORTED) {
135-
return false;
136-
}
137-
}
138-
return true;
121+
public WorkflowJobTrend run() {
122+
return this;
139123
}
140124

141125
public List<WorkflowRunResult> getResults() throws Exception {
142-
for (WorkflowRun run : runs) {
143-
WorkflowRunResult result = calculate(run);
144-
if (result != null) {
145-
results.add(result);
146-
}
147-
}
148126
return results;
149127
}
150128

0 commit comments

Comments
 (0)