/
UsageComputation.java
130 lines (108 loc) · 4.87 KB
/
UsageComputation.java
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package com.cloudbees.simplediskusage;
import hudson.FilePath;
import jenkins.model.Jenkins;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Compute disk usage of a list of paths. Results are published using
* listeners registered for interesting paths, so we only walk the disk once.
*
* The walker process is throttled to prevent IO starvation for other Jenkins
* tasks.
*/
public class UsageComputation {
public interface CompletionListener {
void onCompleted(Path dir, long usage);
}
private final Map<Path, CompletionListener> listenerMap;
private final List<Path> pathsToScan;
public UsageComputation(List<Path> pathsToScan) {
this.pathsToScan = pathsToScan;
this.listenerMap = new HashMap<>();
}
public void addListener(Path path, CompletionListener listener) {
listenerMap.put(path.toAbsolutePath(), listener);
}
public int getItemsCount() {
return listenerMap.size();
}
public void compute() throws IOException {
for (Path path : pathsToScan) {
computeUsage(path.toAbsolutePath());
}
}
protected void computeUsage(Path path) throws IOException {
// we don't really need AtomicLong here, walking the tree is synchronous, but
// it's convenient for the operations it provides
// used to throttle IO
final AtomicLong chunkStartTime = new AtomicLong(System.currentTimeMillis());
// used to lock this thread if there's a FS freeze ongoing
final AtomicLong writableLastCheckTime = new AtomicLong(System.currentTimeMillis());
final Stack<AtomicLong> computeStack = new Stack<>();
computeStack.push(new AtomicLong(0));
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
computeStack.push(new AtomicLong(0));
// check every 10 seconds that the process can write on JENKINS_HOME
// this will lock this thread if the filesystem is frozen
// this is to speed up the FS freeze operation which is otherwise slowed down
if (System.currentTimeMillis() - writableLastCheckTime.get() > 10000) {
writableLastCheckTime.set(System.currentTimeMillis());
FilePath jenkinsHome = Jenkins.get().getRootPath();
try {
jenkinsHome.touch(System.currentTimeMillis());
} catch (InterruptedException e) {
logger.log(Level.INFO, "Exception while touching JENKINS_HOME", e);
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
computeStack.peek().addAndGet(attrs.size());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
if (exc != null) {
logger.log(Level.INFO, "Exception thrown while walking {}: {}", new Object[] {dir, exc });
}
// throttle the walking process so it only consumes at most half of the available IO bandwidth
// only pause every 100ms to ensure the walk is efficient anyway
long runTimeInMillis = System.currentTimeMillis() - chunkStartTime.get();
if (runTimeInMillis > 100) {
try {
Thread.sleep(runTimeInMillis);
chunkStartTime.set(System.currentTimeMillis());
} catch (InterruptedException e) {
return FileVisitResult.TERMINATE;
}
}
long pathDiskUsage = computeStack.pop().get();
CompletionListener listener = listenerMap.get(dir);
if (listener != null) {
listener.onCompleted(dir, pathDiskUsage);
}
computeStack.peek().addAndGet(pathDiskUsage);
return FileVisitResult.CONTINUE;
}
});
}
private static final Logger logger = Logger.getLogger(UsageComputation.class.getName());
}