import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import org.apache.tools.ant.BuildEvent; import org.apache.tools.ant.BuildListener; import org.apache.tools.ant.Project; import org.apache.tools.ant.Target; import org.apache.tools.ant.Task; /** * This listener attaches to the build events and generates times for each * target. This can help detect which targets take a long time. * * @author Chris Webster */ public class TimingBuildListener implements BuildListener { private List activeTargets = new ArrayList(); private SortedSet longestRunningTargets = new TreeSet(); private static final int DEFAULT_MAX_NUMBER_OF_TARGETS = 30; private final int maxTargets = DEFAULT_MAX_NUMBER_OF_TARGETS; private static class TargetExecution implements Comparable { private final Project project; private final Target target; private final long startTime; private long endTime = 0; private List tasks = new ArrayList(); private Map tasksInProgress = new HashMap(); public TargetExecution(Project p, Target t) { assert p != null : "project must not be null"; assert t != null : "target must not be null"; project = p; target = t; startTime = System.currentTimeMillis(); } public void targetFinished() { assert endTime == 0; endTime = System.currentTimeMillis(); } public long getElapsedTime() { assert endTime != 0 : "targetFinished not invoked"; return endTime - startTime; } public void startTask(Task t) { long taskStartTime = System.currentTimeMillis(); tasksInProgress.put(t, taskStartTime); } public void endTask(Task t) { long taskEndTime = System.currentTimeMillis(); Long taskStartTime = tasksInProgress.remove(t); tasks.add(t.getTaskName() + " elapsedTime: " + (taskEndTime - taskStartTime)); } /** * * @return project and target format of display; */ public String getNormalDisplay(String prefix, boolean includePath) { return prefix + "Project: " + project.getName() + (includePath ? "base dir = " + project.getBaseDir().toString() : "") + " " + getElapsedTimeDisplay(); } public String getElapsedTimeDisplay() { return "Target: " + target.getName() + " elapsedTime: " + getElapsedTime(); } /** * * @return getNormalDisplay and a detailed list of subtasks */ public String getDetailedDisplay(String prefix, boolean includePath) { StringBuilder sb = new StringBuilder(); for (String s : tasks) { sb.append(prefix + "\t"); sb.append(s); sb.append("\n"); } return getNormalDisplay(prefix, includePath) + "\n" + sb.toString(); } @Override public int compareTo(Object o) { TargetExecution other = TargetExecution.class.cast(o); long diff = getElapsedTime() - other.getElapsedTime(); if (diff > 0) { return 1; } else if (diff < 0) { return -1; } return 0; } } @Override public synchronized void buildStarted(BuildEvent buildEvent) { } @Override public synchronized void buildFinished(BuildEvent buildEvent) { buildEvent.getProject().log("Longest running targets:"); StringBuilder sb = new StringBuilder(); List descTargets = new ArrayList(longestRunningTargets); Collections.reverse(descTargets); for (TargetExecution t : descTargets) { sb.append(t.getDetailedDisplay("\t", true)); } log(buildEvent, sb.toString()); } @Override public synchronized void targetStarted(BuildEvent buildEvent) { log(buildEvent, buildEvent.getTarget().getName()); activeTargets.add(0, new TargetExecution(buildEvent.getProject(), buildEvent.getTarget())); } private void log(BuildEvent be, String message) { be.getProject().log(message); } @Override public synchronized void targetFinished(BuildEvent buildEvent) { TargetExecution currentTarget = activeTargets.remove(0); currentTarget.targetFinished(); log(buildEvent, currentTarget.getElapsedTimeDisplay()); longestRunningTargets.add(currentTarget); if (longestRunningTargets.size() > maxTargets) { longestRunningTargets.remove(longestRunningTargets.first()); } } @Override public synchronized void taskStarted(BuildEvent buildEvent) { if (activeTargets.isEmpty()) { //log(buildEvent,buildEvent.getTask().getTaskName()); return; } getCurrentTarget(buildEvent).startTask(buildEvent.getTask()); } /** * This method searches through the list of active targets to find the * target which is referenced in the build event. This is required instead * of a stack because of the parallel nature * @param be * @return */ private TargetExecution getCurrentTarget(BuildEvent be) { return activeTargets.get(0); } @Override public synchronized void taskFinished(BuildEvent buildEvent) { if (activeTargets.isEmpty()) { return; } getCurrentTarget(buildEvent).endTask(buildEvent.getTask()); } @Override public void messageLogged(BuildEvent buildEvent) { } }