提交 b1bb3f66 编写于 作者: K Kohsuke Kawaguchi

Added code to evenly distribute tokens like @daily to arbitrary time of the day.

This prevents the load spike at midnight and other known time/date.
上级 0b088e80
......@@ -47,31 +47,31 @@ throws ANTLRException
(
"yearly"
{
table.set("0 0 1 1 *");
table.set("H H H H *",hash);
}
| "annually"
{
table.set("0 0 1 1 *");
table.set("H H H H *",hash);
}
| "monthly"
{
table.set("0 0 1 * *");
table.set("H H H * *",hash);
}
| "weekly"
{
table.set("0 0 * * 0");
table.set("H H * * H",hash);
}
| "daily"
{
table.set("0 0 * * *");
table.set("H H * * *",hash);
}
| "midnight"
{
table.set("0 0 * * *");
table.set("H H * * *",hash);
}
| "hourly"
{
table.set("0 * * * *");
table.set("H * * * *",hash);
}
)
)
......@@ -108,6 +108,14 @@ throws ANTLRException
{
bits = doRange(d,field);
}
| ("H" "(")=> "H" "(" s=token "-" e=token ")"
{
bits = doHash(s,e);
}
| "H"
{
bits = doHash(field);
}
;
token
......@@ -143,6 +151,9 @@ STAR: '*';
DIV: '/';
OR: ',';
AT: '@';
H: 'H';
LPAREN: '(';
RPAREN: ')';
YEARLY: "yearly";
ANNUALLY: "annually";
......
......@@ -39,6 +39,11 @@ abstract class BaseParser extends LLkParser {
private static final int[] LOWER_BOUNDS = new int[] {0,0,1,0,0};
private static final int[] UPPER_BOUNDS = new int[] {59,23,31,12,7};
/**
* Used to pick a value from within the range
*/
protected Hash hash = Hash.zero();
protected BaseParser(int i) {
super(i);
}
......@@ -55,6 +60,11 @@ abstract class BaseParser extends LLkParser {
super(tokenStream, i);
}
public void setHash(Hash hash) {
if (hash==null) hash = Hash.zero();
this.hash = hash;
}
protected long doRange(int start, int end, int step, int field) throws ANTLRException {
rangeCheck(start, field);
rangeCheck(end, field);
......@@ -74,6 +84,20 @@ abstract class BaseParser extends LLkParser {
return doRange( LOWER_BOUNDS[field], UPPER_BOUNDS[field], step, field );
}
/**
* Uses {@link Hash} to choose a random (but stable) value from within this field.
*/
protected long doHash( int field ) {
int u = UPPER_BOUNDS[field];
if (field==2) u = 28; // day of month can vary depending on month, so to make life simpler, just use [1,28] that's always safe
int h = hash.next(u+1 - LOWER_BOUNDS[field]); // upper bound is inclusive
return 1L << (h+LOWER_BOUNDS[field]);
}
protected long doHash( int s, int e ) {
return 1L << (s+hash.next(e+1-s));
}
protected void rangeCheck(int value, int field) throws ANTLRException {
if( value<LOWER_BOUNDS[field] || UPPER_BOUNDS[field]<value ) {
error(Messages.BaseParser_OutOfRange(value,LOWER_BOUNDS[field],UPPER_BOUNDS[field]));
......
......@@ -24,6 +24,7 @@
package hudson.scheduler;
import antlr.ANTLRException;
import org.codehaus.groovy.ast.expr.SpreadExpression;
import java.io.StringReader;
import java.util.Calendar;
......@@ -56,17 +57,35 @@ public final class CronTab {
private String spec;
public CronTab(String format) throws ANTLRException {
this(format,1);
this(format,null);
}
public CronTab(String format, Hash hash) throws ANTLRException {
this(format,1,hash);
}
/**
* @deprecated as of 1.448
* Use {@link #CronTab(String, int, Hash)}
*/
public CronTab(String format, int line) throws ANTLRException {
set(format, line);
set(format, line, null);
}
private void set(String format, int line) throws ANTLRException {
/**
* @param hash
* Used to spread out token like "@daily". Null to preserve the legacy behaviour
* of not spreading it out at all.
*/
public CronTab(String format, int line, Hash hash) throws ANTLRException {
set(format, line, hash);
}
private void set(String format, int line, Hash hash) throws ANTLRException {
CrontabLexer lexer = new CrontabLexer(new StringReader(format));
lexer.setLine(line);
CrontabParser parser = new CrontabParser(lexer);
parser.setHash(hash);
spec = format;
parser.startRule(this);
......@@ -358,8 +377,8 @@ public final class CronTab {
}
}
void set(String format) throws ANTLRException {
set(format,1);
void set(String format, Hash hash) throws ANTLRException {
set(format,1,hash);
}
/**
......
......@@ -70,6 +70,10 @@ public final class CronTabList {
}
public static CronTabList create(String format) throws ANTLRException {
return create(format,null);
}
public static CronTabList create(String format, Hash hash) throws ANTLRException {
Vector<CronTab> r = new Vector<CronTab>();
int lineNumber = 0;
for (String line : format.split("\\r?\\n")) {
......@@ -78,7 +82,7 @@ public final class CronTabList {
if(line.length()==0 || line.startsWith("#"))
continue; // ignorable line
try {
r.add(new CronTab(line,lineNumber));
r.add(new CronTab(line,lineNumber,hash));
} catch (ANTLRException e) {
throw new ANTLRException(Messages.CronTabList_InvalidInput(line,e.toString()),e);
}
......
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.scheduler;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
/**
* Generates a pseudo-random sequence of integers in the specified range.
*
* <p>
* {@link CronTab} supports tokens like '@daily', which means "do it once a day".
* Exactly which time of the day this gets scheduled is randomized --- randomized
* in the sense that it's spread out when many jobs choose @daily, but it's at
* the same time stable so that every job sticks to a specific time of the day
* even after the configuration is updated.
*
* <p>
*
*
* @author Kohsuke Kawaguchi
* @since 1.448
*/
public abstract class Hash {
/*package*/ Hash() {}
/**
* Produces an integer in [0,n)
*/
public abstract int next(int n);
public static Hash from(String seed) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(seed.getBytes("UTF-8"));
byte[] digest = md5.digest();
for (int i=8; i<digest.length; i++)
digest[i%8] ^= digest[i];
long l = 0;
for (int i=0; i<8; i++)
l = (l<<8)+(digest[i]&0xFF);
final Random rnd = new Random(l);
return new Hash() {
@Override
public int next(int n) {
return rnd.nextInt(n);
}
};
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e); // MD5 is a part of JRE
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // UTF-8 is mandatory
}
}
/**
* Creates a hash that always return 0.
*/
public static Hash zero() {
return ZERO;
}
private static final Hash ZERO = new Hash() {
@Override
public int next(int n) {
return 0;
}
};
}
......@@ -36,6 +36,7 @@ import hudson.model.AperiodicWork;
import hudson.model.Build;
import hudson.model.ComputerSet;
import hudson.model.Describable;
import hudson.scheduler.Hash;
import jenkins.model.Jenkins;
import hudson.model.Item;
import hudson.model.PeriodicWork;
......@@ -84,6 +85,14 @@ public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>
*/
public void start(J project, boolean newInstance) {
this.job = project;
try {// reparse the tabs with the job as the hash
this.tabs = CronTabList.create(spec, HASH ? Hash.from(project.getFullName()) : Hash.zero());
} catch (ANTLRException e) {
// this shouldn't fail because we've already parsed stuff in the constructor,
// so if it fails, use whatever 'tabs' that we already have.
LOGGER.log(Level.FINE, "Failed to parse crontab spec: "+spec,e);
}
}
/**
......@@ -317,4 +326,10 @@ public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>
}
return r;
}
/**
* To be removed. Do not rely on this.
* This property hashes tokens in the cron tab syntax like @daily so that they spread evenly.
*/
public static boolean HASH = Boolean.getBoolean(Trigger.class.getName()+".hash");
}
......@@ -24,7 +24,7 @@ THE SOFTWARE.
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<st:documentation>
<st:documentation>on
Radio button with a label that hides additional controls.
When checked, those additional controls are displayed. This is useful
for presenting mutually exclusive options, where each option comes
......
......@@ -160,4 +160,25 @@ public class CronTabTest extends TestCase {
assertEquals(a,b);
}
public void testHash1() throws Exception {
CronTab x = new CronTab("H H(5-8) * * *",new Hash() {
public int next(int n) {
return n-1;
}
});
assertEquals(x.bits[0],1L<<59);
assertEquals(x.bits[1],1L<<8);
}
public void testHash2() throws Exception {
CronTab x = new CronTab("H H(5-8) * * *",new Hash() {
public int next(int n) {
return 1;
}
});
assertEquals(x.bits[0],1L<<1);
assertEquals(x.bits[1],1L<<6);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册