/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package hudson.plugins.disk_usage; import com.google.common.collect.Maps; import hudson.BulkChange; import hudson.XmlFile; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Item; import hudson.model.ItemGroup; import hudson.model.Job; import hudson.model.Node; import hudson.model.Run; import hudson.model.Saveable; import hudson.model.listeners.SaveableListener; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FileUtils; /** * * @author Lucie Votypkova */ public class ProjectDiskUsage implements Saveable{ protected transient Job job; private Long diskUsageWithoutBuilds = 0l; protected Map<String,Map<String,Long>> slaveWorkspacesUsage = new ConcurrentHashMap<String,Map<String,Long>>(); private Set<DiskUsageBuildInformation> buildDiskUsage = new CopyOnWriteArraySet<DiskUsageBuildInformation>(); private Map<String,Long> notLoadedBuilds = new ConcurrentHashMap<String, Long>(); private boolean allBuildsLoaded; private Map<String,Long> cashedBuildDiskUsage = new HashMap<String,Long>(); private Long cashedDiskUsageWithoutBuilds = 0L; private Long cashedDiskUsageWorkspace = 0L; private Long cashedDiskUsageNonSlaveWorkspace = 0L; public ProjectDiskUsage(){ } public void removeNode(Node node){ slaveWorkspacesUsage.remove(node.getDisplayName()); } public Map<String,Map<String,Long>> getSlaveWorkspacesUsage(){ return Maps.newHashMap(slaveWorkspacesUsage); } public Long getDiskUsageWithoutBuilds(){ if(diskUsageWithoutBuilds == null){ //in older versions diskUsageWithoutBuilds = 0L; } return diskUsageWithoutBuilds + getDiskUsageUnloadedBuilds(); } public Long getBuildLoadedBuildDiskUsage(){ Long size = 0l; for(DiskUsageBuildInformation info : buildDiskUsage){ size += info.getSize(); } return size; } public Long getDiskUsageWithoutBuildDirectory(){ return diskUsageWithoutBuilds; } public void setDiskUsageWithoutBuilds(Long diskUsage){ this.diskUsageWithoutBuilds = diskUsage; save(); } public Long getDiskUsageUnloadedBuilds(){ Long size = 0L; for(Long s: notLoadedBuilds.values()){ size += s; } return size; } /** * Size of all builds - loaded and not loaded. It should be equals to the size of directories in builds directory */ public int getCountOfAllBuilds(){ return notLoadedBuilds.size() + buildDiskUsage.size(); } public Set<String> getNotLoadedBuilds(){ Set<String> set = new HashSet<String>(); set.addAll(notLoadedBuilds.keySet()); return set; } public String getPath(String buildDirName){ File file = new File(job.getBuildDir(), buildDirName); return file.getAbsolutePath(); } public boolean isBuildXmlExists(String buildDirName){ File file = new File(new File(job.getBuildDir(), buildDirName),"build.xml"); return file.exists(); } public void addNotLoadedBuild(File file, Long size){ notLoadedBuilds.put(file.getName(), size); DiskUsageBuildInformation information = getDiskUsageBuildInformation(file.getName()); if(information!=null){ buildDiskUsage.remove(information); } save(); } public int countLoadedBuilds(){ return buildDiskUsage.size(); } public void moveToUnloadedBuilds(DiskUsageBuildInformation information){ buildDiskUsage.remove(information); notLoadedBuilds.put(information.getId(), information.getSize()); save(); } public List<File> getDirectoriesOfUnloadableBuilds(){ List<File> files = new ArrayList<File>(); for(String name : notLoadedBuilds.keySet()){ files.add(new File(job.getBuildDir(),name)); } return files; } public void moveToLoadedBuilds(AbstractBuild build, Long size){ DiskUsageBuildInformation information = new DiskUsageBuildInformation(build.getId(), build.getTimeInMillis(), build.getNumber(), size, build.isKeepLog()); notLoadedBuilds.remove(build.getId()); addBuildInformation(information, build, size); save(); } public Long getSizeOfNotLoadedBuild(String directoryName){ return notLoadedBuilds.get(directoryName); } public void removeDeletedNotLoadedBuild(File file){ notLoadedBuilds.remove(file.getName()); save(); } private boolean containsDiskUsageBuildInformationForDirectory(File file){ for(DiskUsageBuildInformation info : buildDiskUsage){ if(info.getId().equals(file.getName())){ return true; } } return false; } public boolean containDataForBuildDirectory(File file){ String name = file.getName(); return (notLoadedBuilds.get(name)!= null || containsDiskUsageBuildInformationForDirectory(file)); } public void removeDeletedNotLoadedBuild(String fileName){ notLoadedBuilds.remove(fileName); save(); } public XmlFile getConfigFile(){ return new XmlFile(new File(job.getRootDir(), "disk-usage.xml")); } public void setProject(Job job){ this.job = job; } public boolean isBuildsLoaded(){ return buildDiskUsage!=null; } public Set<DiskUsageBuildInformation> getBuildDiskUsage(boolean needAll){ if(needAll){ try{ ProjectBuildChecker.checkValidityOfBuildData(this); } catch(Exception e){ Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to load builds "+getConfigFile(),e); } } Set<DiskUsageBuildInformation> information = new HashSet<DiskUsageBuildInformation>(); information.addAll(buildDiskUsage); return information; } @Override public synchronized void save() { if(BulkChange.contains(this)) return; try { getConfigFile().write(this); SaveableListener.fireOnChange(this, getConfigFile()); } catch (IOException e) { Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to save "+getConfigFile(),e); } } public void removeBuild(DiskUsageBuildInformation information){ buildDiskUsage.remove(information); save(); } private int numberOfBuildFolders() throws IOException{ File file = job.getBuildDir(); int count = 0; if(file!=null && file.exists() && file.isDirectory()){ for(File f : file.listFiles()){ //file.exists() is called because symlinks to not existed files are not considered as symlinks if(!FileUtils.isSymlink(f) && f.exists()){ count++; } } } return count; } public void putSlaveWorkspaceSize(Node node, String path, Long size){ Map<String,Long> workspacesInfo = slaveWorkspacesUsage.get(node.getNodeName()); if(workspacesInfo==null) workspacesInfo = new ConcurrentHashMap<String,Long>(); //worksace with 0 are only initiative (are not counted yet) or does not exists //no nexist workspaces are removed in method checkWorkspaces in class DiskUsageProperty if(workspacesInfo.get(path)==null || size>0l ){ workspacesInfo.put(path, size); } slaveWorkspacesUsage.put(node.getNodeName(), workspacesInfo); } public Map<String,Long> getUnusedBuilds(){ Map<String,Long> unusedBuilds = new HashMap<String,Long>(); unusedBuilds.putAll(notLoadedBuilds); return unusedBuilds; } public Long getSizeOfNotLoadedBuilds(){ Long size = 0L; for(Long s : notLoadedBuilds.values()){ size += s; } return size; } public boolean containsBuildWithId(String id){ for(DiskUsageBuildInformation inf : buildDiskUsage){ if(inf.getId().equals(id)){ return true; } } return false; } public List<File> getFilesOfNotLoadedBuilds(){ List<File> files = new ArrayList<File>(); for(String dirName : notLoadedBuilds.keySet()){ files.add(new File(job.getBuildDir(),dirName)); } return files; } public boolean allBuildsExists(){ boolean exist = true; for(DiskUsageBuildInformation info : getBuildDiskUsage(false)){ File file = new File(job.getBuildDir(),info.getId()); if(!file.exists()){ removeBuild(info); exist = false; } } return exist; } public void loadAllBuilds() throws IOException{ loadAllBuilds(false); } public void loadAllBuilds(boolean complete) throws IOException{ load(); int loadedBuildInformation = getCountOfAllBuilds(); if(loadedBuildInformation==numberOfBuildFolders() && allBuildsExists()){ return; } if(complete && job instanceof AbstractProject){ ProjectBuildChecker.valideBuildData((AbstractProject)job); allBuildsLoaded = true; } else{ ProjectBuildChecker.checkValidityOfBuildData(this); } DiskUsageProperty property = (DiskUsageProperty) job.getProperty(DiskUsageProperty.class); property.checkWorkspaces(true); save(); } public synchronized void load(){ XmlFile file = getConfigFile(); if(!file.getFile().exists()){ return; } try { file.unmarshal(this); if(!(buildDiskUsage instanceof CopyOnWriteArraySet)){ //saved collection is not serialized in previous versions. Set<DiskUsageBuildInformation> informations = new CopyOnWriteArraySet<DiskUsageBuildInformation>(); informations.addAll(buildDiskUsage); buildDiskUsage = informations; } } catch (IOException e) { Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to load "+file, e); } // if(buildDiskUsage==null){ // //seems like it needs load old data // loadOldData(); // } // removeDeletedBuilds(); } /** * IT is only for backward compatibility to load old data. It breaks lazy loading. * Should be used only one times - updating of plugin * * @deprecated * */ public void loadOldData(){ buildDiskUsage = new CopyOnWriteArraySet<DiskUsageBuildInformation>(); List<Run> list = job.getBuilds(); for(Run run : list){ if(run instanceof AbstractBuild){ AbstractBuild build = (AbstractBuild) run; BuildDiskUsageAction usage = run.getAction(BuildDiskUsageAction.class); DiskUsageBuildInformation information = new DiskUsageBuildInformation(build.getId(), build.getTimeInMillis(), build.number, 0l, build.isKeepLog()); addBuildInformation(information , build); if(usage!=null){ information.setSize(usage.buildDiskUsage); run.getAllActions().remove(usage); } } } save(); } public DiskUsageBuildInformation getDiskUsageBuildInformation(int number){ for(DiskUsageBuildInformation information : buildDiskUsage){ if(information.getNumber()==number){ return information; } } return null; } public DiskUsageBuildInformation getDiskUsageBuildInformation(String id){ for(DiskUsageBuildInformation information : buildDiskUsage){ if(information.getId().equals(id)){ return information; } } return null; } public void addBuild(AbstractBuild build){ DiskUsageBuildInformation information = new DiskUsageBuildInformation(build.getId(), build.getTimeInMillis(), build.getNumber(), 0L, build.isKeepLog()); addBuildInformation(information, build); } public void addBuildInformation(DiskUsageBuildInformation info, AbstractBuild build){ addBuildInformation(info, build, 0L); } public void addBuildInformation(DiskUsageBuildInformation info, AbstractBuild build, Long size){ if(!containsBuildWithId(info.getId())){ buildDiskUsage.add(info); if(build!=null && build.getWorkspace()!=null){ putSlaveWorkspaceSize(build.getBuiltOn(), build.getWorkspace().getRemote(), size); } } if(notLoadedBuilds.containsKey(info.getId())){ notLoadedBuilds.remove(info.getId()); } save(); } /** * @return the cashedBuildDiskUsage */ public Map<String,Long> getCashedBuildDiskUsage() { return cashedBuildDiskUsage; } /** * @param cashedBuildDiskUsage the cashedBuildDiskUsage to set */ public void setCashedBuildDiskUsage(Map<String,Long> cashedBuildDiskUsage) { this.cashedBuildDiskUsage = cashedBuildDiskUsage; } /** * @return the cashedDiskUsageWithoutBuilds */ public Long getCashedDiskUsageWithoutBuilds() { return cashedDiskUsageWithoutBuilds; } /** * @param cashedDiskUsageWithoutBuilds the cashedDiskUsageWithoutBuilds to set */ public void setCashedDiskUsageWithoutBuilds(Long cashedDiskUsageWithoutBuilds) { this.cashedDiskUsageWithoutBuilds = cashedDiskUsageWithoutBuilds; } /** * @return the cashedDiskUsageWorkspace */ public Long getCashedDiskUsageWorkspace() { return cashedDiskUsageWorkspace; } /** * @param cashedDiskUsageWorkspace the cashedDiskUsageWorkspace to set */ public void setCashedDiskUsageWorkspace(Long cashedDiskUsageWorkspace) { this.cashedDiskUsageWorkspace = cashedDiskUsageWorkspace; } /** * @return the cashedDiskUsageNonSlaveWorkspace */ public Long getCashedDiskUsageNonSlaveWorkspace() { return cashedDiskUsageNonSlaveWorkspace; } /** * @param cashedDiskUsageNonSlaveWorkspace the cashedDiskUsageNonSlaveWorkspace to set */ public void setCashedDiskUsageNonSlaveWorkspace(Long cashedDiskUsageNonSlaveWorkspace) { this.cashedDiskUsageNonSlaveWorkspace = cashedDiskUsageNonSlaveWorkspace; } }