提交 2d486947 编写于 作者: K kohsuke

PeriodicWork is made an extension point, and existing manual registration code...

PeriodicWork is made an extension point, and existing manual registration code is modified to use this registration mechanism.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@16354 71c3de6d-444a-0410-be80-ed276b4c234a
上级 f5e60d5c
package hudson.model;
import hudson.util.StreamTaskListener;
import hudson.util.NullStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
/**
* {@link PeriodicWork} that takes a long time to run.
*
* <p>
* Subclasses will implement the {@link #execute(TaskListener)} method and can carry out a long-running task.
* This runs in a separate thread so as not to block the timer thread, and this class handles
* all those details.
*
* @author Kohsuke Kawaguchi
*/
public abstract class AsyncPeriodicWork extends PeriodicWork {
/**
* Name of the work.
*/
public final String name;
private Thread thread;
protected AsyncPeriodicWork(String name) {
this.name = name;
}
/**
* Schedules this periodic work now in a new thread, if one isn't already running.
*/
public final void doRun() {
try {
if(thread!=null && thread.isAlive()) {
logger.log(Level.INFO, name+" thread is still running. Execution aborted.");
return;
}
thread = new Thread(new Runnable() {
public void run() {
logger.log(Level.INFO, "Started "+name);
long startTime = System.currentTimeMillis();
StreamTaskListener l = createListener();
try {
execute(l);
} catch (IOException e) {
e.printStackTrace(l.fatalError(e.getMessage()));
} catch (InterruptedException e) {
e.printStackTrace(l.fatalError("aborted"));
} finally {
l.close();
}
logger.log(Level.INFO, "Finished "+name+". "+
(System.currentTimeMillis()-startTime)+" ms");
}
},name+" thread");
thread.start();
} catch (Throwable t) {
logger.log(Level.SEVERE, name+" thread failed with error", t);
}
}
protected StreamTaskListener createListener() {
try {
return new StreamTaskListener(getLogFile());
} catch (FileNotFoundException e) {
return new StreamTaskListener(new NullStream());
}
}
/**
* Determines the log file that records the result of this task.
*/
protected File getLogFile() {
return new File(Hudson.getInstance().getRootDir(),name+".log");
}
/**
* Executes the task.
*
* @param listener
* Output sent will be reported to the users. (this work is TBD.)
* @throws InterruptedException
* The caller will record the exception and moves on.
* @throws IOException
* The caller will record the exception and moves on.
*/
protected abstract void execute(TaskListener listener) throws IOException, InterruptedException;
}
......@@ -23,6 +23,8 @@
*/
package hudson.model;
import hudson.Extension;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
......@@ -39,7 +41,8 @@ import java.util.regex.Pattern;
*
* @author Kohsuke Kawaguchi
*/
public final class FingerprintCleanupThread extends PeriodicWork {
@Extension
public final class FingerprintCleanupThread extends AsyncPeriodicWork {
private static FingerprintCleanupThread theInstance;
......@@ -48,11 +51,15 @@ public final class FingerprintCleanupThread extends PeriodicWork {
theInstance = this;
}
public long getRecurrencePeriod() {
return DAY;
}
public static void invoke() {
theInstance.run();
}
protected void execute() {
protected void execute(TaskListener listener) {
int numFiles = 0;
File root = new File(Hudson.getInstance().getRootDir(),"fingerprints");
......
......@@ -546,9 +546,6 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
for (ItemListener l : ItemListener.all())
l.onLoaded();
LoadStatistics.register();
NodeProvisioner.launch();
// run the initialization script, if it exists.
File initScript = new File(getRootDir(),"init.groovy");
if(initScript.exists()) {
......
......@@ -24,11 +24,10 @@
package hudson.model;
import hudson.model.MultiStageTimeSeries.TimeScale;
import hudson.triggers.SafeTimerTask;
import hudson.triggers.Trigger;
import hudson.util.ChartUtil;
import hudson.util.ColorPalette;
import hudson.util.NoOverlapCategoryAxis;
import hudson.Extension;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
......@@ -56,7 +55,7 @@ import java.util.List;
* <h2>Implementation Note</h2>
* <p>
* Instances of this class is not capable of updating the statistics itself
* &mdash; instead, it's done by the single {@link #register()} method.
* &mdash; instead, it's done by the {@link LoadStatisticsUpdater} timer.
* This is more efficient (as it allows us a single pass to update all stats),
* but it's not clear to me if the loss of autonomy is worth it.
*
......@@ -200,46 +199,6 @@ public abstract class LoadStatistics {
ChartUtil.generateGraph(req, rsp, createChart(createDataset(scale)), 500, 400);
}
/**
* Start updating the load average.
*/
/*package*/ static void register() {
Trigger.timer.scheduleAtFixedRate(
new SafeTimerTask() {
protected void doRun() {
Hudson h = Hudson.getInstance();
List<Queue.BuildableItem> bis = h.getQueue().getBuildableItems();
// update statistics on slaves
for( Label l : h.getLabels() ) {
l.loadStatistics.totalExecutors.update(l.getTotalExecutors());
l.loadStatistics.busyExecutors .update(l.getBusyExecutors());
int q=0;
for (Queue.BuildableItem bi : bis) {
if(bi.task.getAssignedLabel()==l)
q++;
}
l.loadStatistics.queueLength.update(q);
}
// update statistics of the entire system
ComputerSet cs = h.getComputer();
h.overallLoad.totalExecutors.update(cs.getTotalExecutors());
h.overallLoad.busyExecutors .update(cs.getBusyExecutors());
int q=0;
for (Queue.BuildableItem bi : bis) {
if(bi.task.getAssignedLabel()==null)
q++;
}
h.overallLoad.queueLength.update(q);
h.overallLoad.totalQueueLength.update(bis.size());
}
}, CLOCK, CLOCK
);
}
/**
* With 0.90 decay ratio for every 10sec, half reduction is about 1 min.
*/
......@@ -248,4 +207,44 @@ public abstract class LoadStatistics {
* Load statistics clock cycle in milliseconds. Specify a small value for quickly debugging this feature and node provisioning through cloud.
*/
public static int CLOCK = Integer.getInteger(LoadStatistics.class.getName()+".clock",10*1000);
/**
* Periodically update the load statistics average.
*/
@Extension
public static class LoadStatisticsUpdater extends PeriodicWork {
public long getRecurrencePeriod() {
return CLOCK;
}
protected void doRun() {
Hudson h = Hudson.getInstance();
List<hudson.model.Queue.BuildableItem> bis = h.getQueue().getBuildableItems();
// update statistics on slaves
for( Label l : h.getLabels() ) {
l.loadStatistics.totalExecutors.update(l.getTotalExecutors());
l.loadStatistics.busyExecutors .update(l.getBusyExecutors());
int q=0;
for (hudson.model.Queue.BuildableItem bi : bis) {
if(bi.task.getAssignedLabel()==l)
q++;
}
l.loadStatistics.queueLength.update(q);
}
// update statistics of the entire system
ComputerSet cs = h.getComputer();
h.overallLoad.totalExecutors.update(cs.getTotalExecutors());
h.overallLoad.busyExecutors .update(cs.getBusyExecutors());
int q=0;
for (hudson.model.Queue.BuildableItem bi : bis) {
if(bi.task.getAssignedLabel()==null)
q++;
}
h.overallLoad.queueLength.update(q);
h.overallLoad.totalQueueLength.update(bis.size());
}
}
}
......@@ -24,54 +24,74 @@
package hudson.model;
import hudson.triggers.SafeTimerTask;
import hudson.triggers.Trigger;
import hudson.ExtensionPoint;
import hudson.Extension;
import hudson.DescriptorExtensionList;
import hudson.ExtensionList;
import hudson.scm.SCMDescriptor;
import hudson.scm.SCM;
import hudson.util.StreamTaskListener;
import hudson.util.NullStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Random;
import java.util.Timer;
/**
* Abstract base class for a periodic work.
* Extension point to perform a periodic task in Hudson (through {@link Timer}.)
*
* <p>
* This extension point is useful if your plugin needs to perform some work in the background periodically
* (for example, monitoring, batch processing, garbage collection, etc.)
*
* <p>
* Put {@link Extension} on your class to have it picked up and registered automatically, or
* manually insert this to {@link Trigger#timer}.
*
* <p>
* This class is designed to run a short task. Implementations whose periodic work takes a long time
* to run should extend from {@link AsyncPeriodicWork} instead.
*
* @author Kohsuke Kawaguchi
* @see AsyncPeriodicWork
*/
public abstract class PeriodicWork extends SafeTimerTask {
public abstract class PeriodicWork extends SafeTimerTask implements ExtensionPoint {
protected final Logger logger = Logger.getLogger(getClass().getName());
/**
* Name of the work.
* Gets the number of milliseconds between successive executions.
*
* <p>
* Hudson calls this method once to set up a recurring timer, instead of
* calling this each time after the previous execution completed. So this class cannot be
* used to implement a non-regular recurring timer.
*
* <p>
* IOW, the method should always return the same value.
*/
private final String name;
private Thread thread;
protected final Logger logger = Logger.getLogger(getClass().getName());
public abstract long getRecurrencePeriod();
protected PeriodicWork(String name) {
this.name = name;
/**
* Gets the number of milliseconds til the first execution.
*
* <p>
* By default it chooses the value randomly between 0 and {@link #getRecurrencePeriod()}
*/
public long getInitialDelay() {
return new Random().nextLong()%getRecurrencePeriod();
}
/**
* Schedules this periodic work now in a new thread, if one isn't already running.
* Returns all the registered {@link PeriodicWork}s.
*/
public final void doRun() {
try {
if(thread!=null && thread.isAlive()) {
logger.log(Level.INFO, name+" thread is still running. Execution aborted.");
return;
}
thread = new Thread(new Runnable() {
public void run() {
logger.log(Level.INFO, "Started "+name);
long startTime = System.currentTimeMillis();
execute();
logger.log(Level.INFO, "Finished "+name+". "+
(System.currentTimeMillis()-startTime)+" ms");
}
},name+" thread");
thread.start();
} catch (Throwable t) {
logger.log(Level.SEVERE, name+" thread failed with error", t);
}
public static ExtensionList<PeriodicWork> all() {
return Hudson.getInstance().getExtensionList(PeriodicWork.class);
}
protected abstract void execute();
// time constants
protected static final long MIN = 1000*60;
protected static final long HOUR =60*MIN;
protected static final long DAY = 24*HOUR;
}
......@@ -25,6 +25,7 @@ package hudson.model;
import hudson.FilePath;
import hudson.Util;
import hudson.Extension;
import hudson.util.StreamTaskListener;
import java.io.File;
......@@ -41,7 +42,8 @@ import java.util.logging.Logger;
*
* @author Kohsuke Kawaguchi
*/
public class WorkspaceCleanupThread extends PeriodicWork {
@Extension
public class WorkspaceCleanupThread extends AsyncPeriodicWork {
private static WorkspaceCleanupThread theInstance;
public WorkspaceCleanupThread() {
......@@ -49,32 +51,28 @@ public class WorkspaceCleanupThread extends PeriodicWork {
theInstance = this;
}
public long getRecurrencePeriod() {
return DAY;
}
public static void invoke() {
theInstance.run();
}
private StreamTaskListener listener;
// so that this can be easily accessed from sub-routine.
private TaskListener listener;
protected void execute() {
Hudson h = Hudson.getInstance();
protected void execute(TaskListener listener) throws InterruptedException, IOException {
try {
// don't buffer this, so that the log shows what the worker thread is up to in real time
try {
listener = new StreamTaskListener(new File(h.getRootDir(),"workspace-cleanup.log"));
for (Slave s : h.getSlaves())
process(s);
process(h);
} catch (InterruptedException e) {
e.printStackTrace(listener.fatalError("aborted"));
} finally {
if(listener!=null)
listener.close();
listener = null;
}
} catch (IOException e) {
logger.log(Level.SEVERE, "Failed to access log file",e);
this.listener = listener;
Hudson h = Hudson.getInstance();
for (Slave s : h.getSlaves())
process(s);
process(h);
} finally {
this.listener = null;
}
}
......
......@@ -28,7 +28,9 @@ import java.util.WeakHashMap;
import hudson.model.Computer;
import hudson.model.Hudson;
import hudson.model.PeriodicWork;
import hudson.triggers.SafeTimerTask;
import hudson.Extension;
/**
* Periodically checks the slaves and try to reconnect dead slaves.
......@@ -36,13 +38,18 @@ import hudson.triggers.SafeTimerTask;
* @author Kohsuke Kawaguchi
* @author Stephen Connolly
*/
public class ComputerRetentionWork extends SafeTimerTask {
@Extension
public class ComputerRetentionWork extends PeriodicWork {
/**
* Use weak hash map to avoid leaking {@link Computer}.
*/
private final Map<Computer, Long> nextCheck = new WeakHashMap<Computer, Long>();
public long getRecurrencePeriod() {
return MIN;
}
/**
* {@inheritDoc}
*/
......
......@@ -28,10 +28,12 @@ import hudson.model.Node;
import hudson.model.Hudson;
import hudson.model.MultiStageTimeSeries;
import hudson.model.Label;
import hudson.model.PeriodicWork;
import static hudson.model.LoadStatistics.DECAY;
import hudson.model.MultiStageTimeSeries.TimeScale;
import hudson.triggers.SafeTimerTask;
import hudson.triggers.Trigger;
import hudson.Extension;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;
......@@ -186,21 +188,31 @@ public class NodeProvisioner {
}
}
public static void launch() {
// periodically invoke NodeProvisioners
Trigger.timer.scheduleAtFixedRate(new SafeTimerTask() {
@Override
protected void doRun() {
Hudson h = Hudson.getInstance();
h.overallNodeProvisioner.update();
for( Label l : h.getLabels() )
l.nodeProvisioner.update();
}
},
// give some initial warm up time so that statically connected slaves
// can be brought online before we start allocating more.
LoadStatistics.CLOCK*10,
LoadStatistics.CLOCK);
/**
* Periodically invoke NodeProvisioners
*/
@Extension
public static class NodeProvisionerInvoker extends PeriodicWork {
/**
* Give some initial warm up time so that statically connected slaves
* can be brought online before we start allocating more.
*/
@Override
public long getInitialDelay() {
return LoadStatistics.CLOCK*10;
}
public long getRecurrencePeriod() {
return LoadStatistics.CLOCK;
}
@Override
protected void doRun() {
Hudson h = Hudson.getInstance();
h.overallNodeProvisioner.update();
for( Label l : h.getLabels() )
l.nodeProvisioner.update();
}
}
private static final float MARGIN = 0.1f;
......
......@@ -28,17 +28,16 @@ import hudson.DependencyRunner;
import hudson.DependencyRunner.ProjectRunnable;
import hudson.ExtensionPoint;
import hudson.DescriptorExtensionList;
import hudson.slaves.ComputerRetentionWork;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Build;
import hudson.model.ComputerSet;
import hudson.model.Describable;
import hudson.model.FingerprintCleanupThread;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Project;
import hudson.model.WorkspaceCleanupThread;
import hudson.model.PeriodicWork;
import hudson.scheduler.CronTab;
import hudson.scheduler.CronTabList;
import hudson.util.DoubleLaunchChecker;
......@@ -160,9 +159,14 @@ public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>
/**
* Runs every minute to check {@link TimerTrigger} and schedules build.
*/
private static class Cron extends SafeTimerTask {
@Extension
public static class Cron extends PeriodicWork {
private final Calendar cal = new GregorianCalendar();
public long getRecurrencePeriod() {
return MIN;
}
public void doRun() {
while(new Date().getTime()-cal.getTimeInMillis()>1000) {
LOGGER.fine("cron checking "+cal.getTime().toLocaleString());
......@@ -233,22 +237,17 @@ public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>
* some work.
*
* Initialized and cleaned up by {@link Hudson}, but value kept here for compatibility.
*
* If plugins want to run periodic jobs, they should implement {@link PeriodicWork}.
*/
public static Timer timer;
public static void init() {
long MIN = 1000*60;
long HOUR =60*MIN;
long DAY = 24*HOUR;
timer.scheduleAtFixedRate(new Cron(), MIN, MIN);
new DoubleLaunchChecker().schedule();
// clean up fingerprint once a day
timer.scheduleAtFixedRate(new FingerprintCleanupThread(),DAY,DAY);
timer.scheduleAtFixedRate(new WorkspaceCleanupThread(),DAY+4*HOUR,DAY);
timer.scheduleAtFixedRate(new ComputerRetentionWork(), MIN, MIN);
// start all PeridocWorks
for(PeriodicWork p : PeriodicWork.all())
timer.scheduleAtFixedRate(p,p.getInitialDelay(),p.getRecurrencePeriod());
// start monitoring nodes, although there's no hurry.
timer.schedule(new SafeTimerTask() {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册