Skip to content

Commit

Permalink
[JENKINS-50412] Fingerprints logs should be less verbose (#3373)
Browse files Browse the repository at this point in the history
* JENKINS-50412 - Fingerprints logs should be less verbose

Eliminate the logging line that says
possibly trimming /var/jenkins_home/fingerprints/[...]
This line fills up the fingerprints log file with repetitive, useless information.
The operation it calls provides its own logging that can be turned on if detailed
logging is actually needed instead of running this one repeatedly.

Also, while in the area, convert a couple of FileFilter inner classes to lambdas
and inline them. This reduces unnecessary boilerplate and improves clarity.

In order to make this change testable, I elected to remove the final restriction on the
class and annotate the class as restricted. This allows me to create unit tests for
this change and also verify existing capability at the appropriate level.

* Watch out for varying line endings.

Use a reliable check.

* Correct a few minor issues caught in review.

* Change to correct access modifier.
  • Loading branch information
jeffret-b authored and dwnusbaum committed May 4, 2018
1 parent a03254b commit a9eafdb
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 20 deletions.
43 changes: 23 additions & 20 deletions core/src/main/java/hudson/model/FingerprintCleanupThread.java
Expand Up @@ -28,9 +28,10 @@
import hudson.Functions;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.regex.Pattern;

Expand All @@ -45,7 +46,11 @@
* @author Kohsuke Kawaguchi
*/
@Extension @Symbol("fingerprintCleanup")
public final class FingerprintCleanupThread extends AsyncPeriodicWork {
@Restricted(NoExternalUse.class)
public class FingerprintCleanupThread extends AsyncPeriodicWork {

static final String FINGERPRINTS_DIR_NAME = "fingerprints";
private static final Pattern FINGERPRINT_FILE_PATTERN = Pattern.compile("[0-9a-f]{28}\\.xml");

public FingerprintCleanupThread() {
super("Fingerprint cleanup");
Expand All @@ -66,13 +71,13 @@ private static FingerprintCleanupThread getInstance() {
public void execute(TaskListener listener) {
int numFiles = 0;

File root = new File(Jenkins.getInstance().getRootDir(),"fingerprints");
File[] files1 = root.listFiles(LENGTH2DIR_FILTER);
File root = new File(getRootDir(), FINGERPRINTS_DIR_NAME);
File[] files1 = root.listFiles(f -> f.isDirectory() && f.getName().length()==2);
if(files1!=null) {
for (File file1 : files1) {
File[] files2 = file1.listFiles(LENGTH2DIR_FILTER);
File[] files2 = file1.listFiles(f -> f.isDirectory() && f.getName().length()==2);
for(File file2 : files2) {
File[] files3 = file2.listFiles(FINGERPRINTFILE_FILTER);
File[] files3 = file2.listFiles(f -> f.isFile() && FINGERPRINT_FILE_PATTERN.matcher(f.getName()).matches());
for(File file3 : files3) {
if(check(file3, listener))
numFiles++;
Expand Down Expand Up @@ -101,16 +106,15 @@ private void deleteIfEmpty(File dir) {
*/
private boolean check(File fingerprintFile, TaskListener listener) {
try {
Fingerprint fp = Fingerprint.load(fingerprintFile);
Fingerprint fp = loadFingerprint(fingerprintFile);
if (fp == null || !fp.isAlive()) {
listener.getLogger().println("deleting obsolete " + fingerprintFile);
fingerprintFile.delete();
return true;
} else {
// get the fingerprint in the official map so have the changes visible to Jenkins
// otherwise the mutation made in FingerprintMap can override our trimming.
listener.getLogger().println("possibly trimming " + fingerprintFile);
fp = Jenkins.getInstance()._getFingerprint(fp.getHashString());
fp = getFingerprint(fp);
return fp.trim();
}
} catch (IOException e) {
Expand All @@ -119,17 +123,16 @@ private boolean check(File fingerprintFile, TaskListener listener) {
}
}

private static final FileFilter LENGTH2DIR_FILTER = new FileFilter() {
public boolean accept(File f) {
return f.isDirectory() && f.getName().length()==2;
}
};
protected Fingerprint loadFingerprint(File fingerprintFile) throws IOException {
return Fingerprint.load(fingerprintFile);
}

private static final FileFilter FINGERPRINTFILE_FILTER = new FileFilter() {
private final Pattern PATTERN = Pattern.compile("[0-9a-f]{28}\\.xml");
protected Fingerprint getFingerprint(Fingerprint fp) throws IOException {
return Jenkins.get()._getFingerprint(fp.getHashString());
}

protected File getRootDir() {
return Jenkins.get().getRootDir();
}

public boolean accept(File f) {
return f.isFile() && PATTERN.matcher(f.getName()).matches();
}
};
}
180 changes: 180 additions & 0 deletions core/src/test/java/hudson/model/FingerprintCleanupThreadTest.java
@@ -0,0 +1,180 @@
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;

import org.junit.Test;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class FingerprintCleanupThreadTest {

private static final Fingerprint.BuildPtr ptr = new Fingerprint.BuildPtr("fred", 23);
private Path tempDirectory;
private Path fpFile;

@Test
public void testDoesNotLogUnimportantExcessiveLogMessage() throws IOException {
createFolderStructure();
TestTaskListener testTaskListener = new TestTaskListener();
FingerprintCleanupThread cleanupThread = new TestFingerprintCleanupThread(new TestFingerprint(true));
cleanupThread.execute(testTaskListener);
String logOutput = testTaskListener.outputStream.toString();
assertFalse("Should not have logged unimportant, excessive message.", logOutput.contains("possibly trimming"));
}

@Test
public void testFingerprintFileIsEmpty() throws IOException {
createFolderStructure();
TestTaskListener testTaskListener = new TestTaskListener();
FingerprintCleanupThread cleanupThread = new TestFingerprintCleanupThread(new TestFingerprint(false));
cleanupThread.execute(testTaskListener);
String logOutput = testTaskListener.outputStream.toString();
assertFalse("Should have deleted obsolete file.", fpFile.toFile().exists());
}

@Test
public void testGetRecurencePeriod() throws IOException {
FingerprintCleanupThread cleanupThread = new TestFingerprintCleanupThread(new TestFingerprint());
assertEquals("Wrong recurrence period.", PeriodicWork.DAY, cleanupThread.getRecurrencePeriod());
}

@Test
public void testNoFingerprintsDir() throws IOException {
createTestDir();
TestTaskListener testTaskListener = new TestTaskListener();
FingerprintCleanupThread cleanupThread = new TestFingerprintCleanupThread(new TestFingerprint());
cleanupThread.execute(testTaskListener);
String logOutput = testTaskListener.outputStream.toString();
assertTrue("Should have done nothing.", logOutput.startsWith("Cleaned up 0 records"));
}

@Test
public void testIOExceptionOnLoad() throws IOException {
createFolderStructure();
TestTaskListener testTaskListener = new TestTaskListener();
FingerprintCleanupThread cleanupThread = new TestFingerprintCleanupThreadThrowsExceptionOnLoad(new TestFingerprint());
cleanupThread.execute(testTaskListener);
String logOutput = testTaskListener.outputStream.toString();
assertTrue("Should have logged IOException.", logOutput.contains("ERROR: Failed to process"));
}

private void createFolderStructure() throws IOException {
createTestDir();
Path fingerprintsPath = tempDirectory.resolve(FingerprintCleanupThread.FINGERPRINTS_DIR_NAME);
Files.createDirectory(fingerprintsPath);
Path aaDir = fingerprintsPath.resolve("aa");
Files.createDirectory(aaDir);
Path bbDir = aaDir.resolve("bb");
Files.createDirectory(bbDir);
fpFile = bbDir.resolve("0123456789012345678901234567.xml");
Files.createFile(fpFile);
}

private void createTestDir() throws IOException {
tempDirectory = Files.createTempDirectory(Paths.get("target"), "fpCleanupThreadTest");
tempDirectory.toFile().deleteOnExit();
}

private class TestTaskListener implements TaskListener {

private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private PrintStream logStream = new PrintStream(outputStream);

@Nonnull
@Override
public PrintStream getLogger() {
return logStream;
}

}

private class TestFingerprintCleanupThread extends FingerprintCleanupThread {

private Fingerprint fingerprintToLoad;

public TestFingerprintCleanupThread(Fingerprint fingerprintToLoad) throws IOException {
this.fingerprintToLoad = fingerprintToLoad;
return;
}

@Override
protected Fingerprint getFingerprint(Fingerprint fp) throws IOException {
return new Fingerprint(ptr, "file", new byte[0]);
}

@Override
protected File getRootDir() {
return tempDirectory.toFile();
}

@Override
protected Fingerprint loadFingerprint(File fingerprintFile) throws IOException {
return fingerprintToLoad;
}

}

private class TestFingerprint extends Fingerprint {

private boolean isAlive = true;

public TestFingerprint() throws IOException {
super(ptr, "fred", new byte[0]);
}

public TestFingerprint(boolean isAlive) throws IOException {
super(ptr, "fred", new byte[0]);
this.isAlive = isAlive;
}

@Override
public synchronized boolean isAlive() {
return isAlive;
}
}

private class TestFingerprintCleanupThreadThrowsExceptionOnLoad extends TestFingerprintCleanupThread {

public TestFingerprintCleanupThreadThrowsExceptionOnLoad(Fingerprint fingerprintToLoad) throws IOException {
super(fingerprintToLoad);
}

@Override
protected Fingerprint loadFingerprint(File fingerprintFile) throws IOException {
throw new IOException("Test exception");
}
}
}

0 comments on commit a9eafdb

Please sign in to comment.