package hudson.plugins.disk_usage;
import hudson.FilePath;
import hudson.Util;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.AsyncPeriodicWork;
import hudson.model.Hudson;
import hudson.model.ItemGroup;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import hudson.remoting.Callable;
import java.io.File;
import java.io.IOException;
import java.lang.Math;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
/**
* A Thread responsible for gathering disk usage information
*
* @author dvrzalik
*/
@Extension
public class DiskUsageThread extends AsyncPeriodicWork {
//trigger disk usage thread each 6 hours
public static final int COUNT_INTERVAL_MINUTES = 60*6;
public DiskUsageThread() {
super("Project disk usage");
}
public long getRecurrencePeriod() {
return 1000*60*COUNT_INTERVAL_MINUTES;
}
@Override
protected void execute(TaskListener listener) throws IOException, InterruptedException {
List items = Hudson.getInstance().getItems();
//Include nested projects as well
//TODO fix MatrixProject and use getAllJobs()
for (TopLevelItem item : Hudson.getInstance().getItems()) {
if(item instanceof ItemGroup) {
items.addAll(((ItemGroup)item).getItems());
}
}
for (Object item : items) {
if (item instanceof AbstractProject) {
AbstractProject project = (AbstractProject) item;
if (project.getAction(ProjectDiskUsageAction.class) == null) {
try {
project.addProperty(new DiskUsageProperty());
project.save();
} catch (IOException ex) {
logger.log(Level.WARNING, "Error when adding disk usage property for " + project.getName(), ex);
break;
}
}
//well, this is not absolutely thread-safe, but in the worst case we get invalid result for one build
//(which will be rewritten next time)
if (!project.isBuilding()) {
List<AbstractBuild> builds = project.getBuilds();
Iterator<AbstractBuild> buildIterator = builds.iterator();
try {
while (buildIterator.hasNext()) {
calculateDiskUsageForBuild(buildIterator.next());
}
//Assign workspace size to the last build
calculateWorkspaceDiskUsage(project);
} catch (Exception ex) {
logger.log(Level.WARNING, "Error when recording disk usage for " + project.getName(), ex);
}
}
}
}
}
private static void calculateDiskUsageForBuild(AbstractBuild build)
throws IOException {
//Build disk usage has to be always recalculated to be kept up-to-date
//- artifacts might be kept only for the last build and users sometimes delete files manually as well.
long buildSize = DiskUsageCallable.getFileSize(build.getRootDir());
BuildDiskUsageAction action = build.getAction(BuildDiskUsageAction.class);
boolean updateBuild = false;
if (action == null) {
action = new BuildDiskUsageAction(build, 0, buildSize);
build.addAction(action);
updateBuild = true;
} else {
if (( action.diskUsage.buildUsage <= 0 ) ||
( Math.abs(action.diskUsage.buildUsage - buildSize) > 1024 )) {
action.diskUsage.buildUsage = buildSize;
updateBuild = true;
}
}
if ( updateBuild ) {
build.save();
}
}
private static void calculateWorkspaceDiskUsage(AbstractProject project) throws IOException, InterruptedException {
AbstractBuild lastBuild = (AbstractBuild) project.getLastBuild();
if (lastBuild != null) {
BuildDiskUsageAction bdua = lastBuild.getAction(BuildDiskUsageAction.class);
//also recalculate workspace - deleting workspace by e.g. scripts is also quite common
boolean updateWs = false;
if (bdua == null) {
bdua = new BuildDiskUsageAction(lastBuild, 0, 0);
lastBuild.addAction(bdua);
updateWs = true;
}
FilePath workspace = project.getSomeWorkspace();
//slave might be offline...or have been deleted - set to 0
if (workspace != null) {
long oldWsUsage = bdua.diskUsage.wsUsage;
bdua.diskUsage.wsUsage = workspace.act(new DiskUsageCallable(workspace));
if (Math.abs(bdua.diskUsage.wsUsage - oldWsUsage) > 1024 ) {
updateWs = true;
}
}
else{
bdua.diskUsage.wsUsage = 0; //workspace have been delete or is not reachable
}
if(updateWs){
lastBuild.save();
}
}
}
/**
* A {@link Callable} which computes disk usage of remote file object
*/
public static class DiskUsageCallable implements Callable<Long, IOException> {
private FilePath path;
public DiskUsageCallable(FilePath filePath) {
this.path = filePath;
}
public Long call() throws IOException {
File f = new File(path.getRemote());
return getFileSize(f);
}
public static Long getFileSize(File f) throws IOException {
long size = 0;
if (f.isDirectory() && !Util.isSymlink(f)) {
for (File child : f.listFiles()) {
size += getFileSize(child);
}
}
return size + f.length();
}
}
}