Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-72009, JENKINS-72200, JENKINS-24947] various fixes and improvements around disk space monitoring #8593

Merged
merged 17 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions core/src/main/java/hudson/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2299,13 +2299,17 @@ public static String humanReadableByteSize(long size) {
double number = size;
if (number >= 1024) {
number = number / 1024;
measure = "KB";
measure = "KiB";
MarkEWaite marked this conversation as resolved.
Show resolved Hide resolved
if (number >= 1024) {
number = number / 1024;
measure = "MB";
measure = "MiB";
if (number >= 1024) {
number = number / 1024;
measure = "GB";
measure = "GiB";
if (number >= 1024) {
number = number / 1024;
measure = "TiB";
}
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/hudson/model/Computer.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
import hudson.model.Queue.FlyweightTask;
import hudson.model.labels.LabelAtom;
import hudson.model.queue.WorkUnit;
import hudson.node_monitors.AbstractDiskSpaceMonitor;
import hudson.node_monitors.DiskSpaceMonitorNodeProperty;
import hudson.node_monitors.NodeMonitor;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
Expand Down Expand Up @@ -1515,6 +1517,14 @@
Node result = node.reconfigure(req, req.getSubmittedForm());
Jenkins.get().getNodesObject().replaceNode(this.getNode(), result);

if (result.getNodeProperty(DiskSpaceMonitorNodeProperty.class) != null) {

Check warning on line 1520 in core/src/main/java/hudson/model/Computer.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 1520 is only partially covered, one branch is missing
for (NodeMonitor monitor : NodeMonitor.getAll()) {
if (monitor instanceof AbstractDiskSpaceMonitor) {
monitor.data(this);
}
}

Check warning on line 1525 in core/src/main/java/hudson/model/Computer.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 1521-1525 are not covered by tests
}

// take the user back to the agent top page.
rsp.sendRedirect2("../" + result.getNodeName() + '/');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ private void error(Computer c, Throwable x) {
/**
* Result object for {@link AbstractAsyncNodeMonitorDescriptor#monitorDetailed()} to facilitate extending information
* returned in the future.
*
* <p>
* The {@link #getMonitoringData()} provides the results of the monitoring as {@link #monitor()} does. Note the value
* in the map can be {@code null} for several reasons:
* <ul>
Expand All @@ -149,8 +149,8 @@ private void error(Computer c, Throwable x) {
* <li>The {@link AbstractAsyncNodeMonitorDescriptor#createCallable} has returned null.</li>
* </ul>
*
* Clients can distinguishing among these states based on the additional data attached to this object. {@link #getSkipped()}
* returns computers that was not monitored as they ware either offline or monitor produced {@code null} {@link Callable}.
* Clients can distinguish among these states based on the additional data attached to this object. {@link #getSkipped()}
* returns computers that were not monitored as they were either offline or monitor produced {@code null} {@link Callable}.
*/
protected static final class Result<T> {
private static final long serialVersionUID = -7671448355804481216L;
Expand All @@ -163,15 +163,15 @@ private Result(@NonNull Map<Computer, T> data, @NonNull Collection<Computer> ski
this.skipped = new ArrayList<>(skipped);
}

protected @NonNull Map<Computer, T> getMonitoringData() {
public @NonNull Map<Computer, T> getMonitoringData() {
return data;
}

/**
* Computers that ware skipped during monitoring as they either do not have a a channel (offline) or the monitor
* have not produced the Callable. Computers that caused monitor to throw exception are not returned here.
* Computers that were skipped during monitoring as they either do not have a channel (offline) or the monitor
* has not produced the Callable. Computers that caused monitor to throw exception are not returned here.
*/
protected @NonNull List<Computer> getSkipped() {
public @NonNull List<Computer> getSkipped() {
return skipped;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import hudson.node_monitors.DiskSpaceMonitorDescriptor.DiskSpace;
import java.text.ParseException;
import java.util.logging.Logger;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.kohsuke.stapler.DataBoundSetter;

/**
* @author Kohsuke Kawaguchi
Expand All @@ -17,54 +18,80 @@
* This is a human readable string representation as entered by the user, so that we can retain the original notation.
*/
public final String freeSpaceThreshold;
private String freeSpaceWarningThreshold;

protected AbstractDiskSpaceMonitor(String threshold) throws ParseException {
this.freeSpaceThreshold = threshold;
DiskSpace.parse(threshold); // make sure it parses
}

protected AbstractDiskSpaceMonitor() {
this.freeSpaceThreshold = "1GB";
this.freeSpaceThreshold = "1GiB";
Comment on lines -27 to +29
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this is one of the unusual cases where a settings format change is not forward-compatible. So if you run a controller from a version after this PR and then downgrade to a version prior to it and try to save monitor config, you will get an error

java.lang.NumberFormatException: For input string: "1GI"
	at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2054)
	at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.base/java.lang.Double.parseDouble(Double.java:792)
	at hudson.node_monitors.DiskSpaceMonitorDescriptor$DiskSpace.parse(DiskSpaceMonitorDescriptor.java:163)
	at hudson.node_monitors.AbstractDiskSpaceMonitor.<init>(AbstractDiskSpaceMonitor.java:23)
	at hudson.node_monitors.DiskSpaceMonitor.<init>(DiskSpaceMonitor.java:49)
	at …
Caused: java.lang.IllegalArgumentException: Failed to instantiate class hudson.node_monitors.DiskSpaceMonitor from {"freeSpaceThreshold":"1GiB"}
	at org.kohsuke.stapler.RequestImpl$TypePair.convertJSON(RequestImpl.java:771)
	at org.kohsuke.stapler.RequestImpl.bindJSON(RequestImpl.java:551)
	at org.kohsuke.stapler.RequestImpl.bindJSON(RequestImpl.java:546)
	at hudson.model.Descriptor.bindJSON(Descriptor.java:623)
	at hudson.model.Descriptor.newInstance(Descriptor.java:593)
Caused: java.lang.LinkageError: Failed to instantiate class hudson.node_monitors.DiskSpaceMonitor from {"freeSpaceThreshold":"1GiB"}
	at hudson.model.Descriptor.newInstance(Descriptor.java:596)
	at hudson.util.DescribableList.rebuild(DescribableList.java:183)
	at hudson.model.ComputerSet.doConfigSubmit(ComputerSet.java:355)
	at …

this.freeSpaceWarningThreshold = "2GiB";
}

public Object readResolve() {
if (freeSpaceWarningThreshold == null) {

Check warning on line 34 in core/src/main/java/hudson/node_monitors/AbstractDiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 34 is only partially covered, one branch is missing
if (freeSpaceThreshold != null) {
Pattern p = Pattern.compile("(\\d+)(.*)");
Matcher m = p.matcher(freeSpaceThreshold);
if (m.matches()) {
String digits = m.group(1);
String unit = m.group(2);
try {
int wt = Integer.parseInt(digits) * 2;
freeSpaceWarningThreshold = wt + unit;
} catch (NumberFormatException nfe) {
// unreachable
freeSpaceWarningThreshold = "2GiB";
}
}
} else {
freeSpaceWarningThreshold = "2GiB";

Check warning on line 50 in core/src/main/java/hudson/node_monitors/AbstractDiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 35-50 are not covered by tests
}
}
return this;
}

@DataBoundSetter
public void setFreeSpaceWarningThreshold(String freeSpaceWarningThreshold) {
this.freeSpaceWarningThreshold = freeSpaceWarningThreshold;
}

public String getFreeSpaceWarningThreshold() {
return freeSpaceWarningThreshold;
}

public long getThresholdBytes() {
if (freeSpaceThreshold == null)
return DEFAULT_THRESHOLD; // backward compatibility with the data format that didn't have 'freeSpaceThreshold'
try {
return DiskSpace.parse(freeSpaceThreshold).size;
} catch (ParseException e) {
return DEFAULT_THRESHOLD;
}
}

@Override
public Object data(Computer c) {
DiskSpace size = markNodeOfflineIfDiskspaceIsTooLow(c);
protected long getThresholdBytes(Computer c) {
return getThresholdBytes();

Check warning on line 74 in core/src/main/java/hudson/node_monitors/AbstractDiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 74 is not covered by tests
}

// mark online (again), if free space is over threshold
if (size != null && size.size > getThresholdBytes() && c.isOffline() && c.getOfflineCause() instanceof DiskSpace)
if (this.getClass().equals(((DiskSpace) c.getOfflineCause()).getTrigger()))
if (getDescriptor().markOnline(c)) {
LOGGER.info(Messages.DiskSpaceMonitor_MarkedOnline(c.getDisplayName()));
}
return size;
protected long getWarningThresholdBytes() {
if (freeSpaceWarningThreshold == null)

Check warning on line 78 in core/src/main/java/hudson/node_monitors/AbstractDiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 78 is only partially covered, one branch is missing
return DEFAULT_THRESHOLD * 2;

Check warning on line 79 in core/src/main/java/hudson/node_monitors/AbstractDiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 79 is not covered by tests
try {
return DiskSpace.parse(freeSpaceWarningThreshold).size;
} catch (ParseException e) {
return DEFAULT_THRESHOLD * 2;
}
}

/**
* Marks the given node as offline if free disk space is below the configured threshold.
* @param c the node
* @return the free space
* @since 1.521
*/
@Restricted(NoExternalUse.class)
public DiskSpace markNodeOfflineIfDiskspaceIsTooLow(Computer c) {
protected long getWarningThresholdBytes(Computer c) {
return getWarningThresholdBytes();

Check warning on line 88 in core/src/main/java/hudson/node_monitors/AbstractDiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 82-88 are not covered by tests
}

@Override
public Object data(Computer c) {
DiskSpace size = (DiskSpace) super.data(c);
if (size != null && size.size < getThresholdBytes()) {
size.setTriggered(this.getClass(), true);
if (getDescriptor().markOffline(c, size)) {
LOGGER.warning(Messages.DiskSpaceMonitor_MarkedOffline(c.getDisplayName()));
}
}
((DiskSpaceMonitorDescriptor) getDescriptor()).markNodeOfflineOrOnline(c, size, this);
return size;
}

Expand Down
32 changes: 32 additions & 0 deletions core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,38 @@

public DiskSpaceMonitor() {}

@Override
public long getThresholdBytes(Computer c) {
Node node = c.getNode();
if (node != null) {

Check warning on line 57 in core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 57 is only partially covered, one branch is missing
DiskSpaceMonitorNodeProperty nodeProperty = node.getNodeProperty(DiskSpaceMonitorNodeProperty.class);
if (nodeProperty != null) {

Check warning on line 59 in core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 59 is only partially covered, one branch is missing
try {
return DiskSpace.parse(nodeProperty.getFreeDiskSpaceThreshold()).size;
} catch (ParseException e) {
return getThresholdBytes();

Check warning on line 63 in core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 61-63 are not covered by tests
}
}
}
return getThresholdBytes();
}

@Override
protected long getWarningThresholdBytes(Computer c) {
Node node = c.getNode();
if (node != null) {

Check warning on line 73 in core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 73 is only partially covered, one branch is missing
DiskSpaceMonitorNodeProperty nodeProperty = node.getNodeProperty(DiskSpaceMonitorNodeProperty.class);
if (nodeProperty != null) {

Check warning on line 75 in core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 75 is only partially covered, one branch is missing
try {
return DiskSpace.parse(nodeProperty.getFreeDiskSpaceWarningThreshold()).size;
} catch (ParseException e) {
return getWarningThresholdBytes();

Check warning on line 79 in core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 77-79 are not covered by tests
}
}
}
return getWarningThresholdBytes();
}

public DiskSpace getFreeSpace(Computer c) {
return DESCRIPTOR.get(c);
}
Expand Down
Loading
Loading