You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2017/12/26 20:40:33 UTC

svn commit: r1819293 - in /jmeter/trunk: bin/ src/components/org/apache/jmeter/timers/poissonarrivals/ src/core/org/apache/jmeter/save/ test/src/org/apache/jmeter/timers/poissonarrivals/ xdocs/ xdocs/usermanual/

Author: pmouawad
Date: Tue Dec 26 20:40:32 2017
New Revision: 1819293

URL: http://svn.apache.org/viewvc?rev=1819293&view=rev
Log:
Bug 61931 - Exponential Timer : timer that produces poisson arrivals with given constant throughput 
This closes #231
Bugzilla Id: 61931

Added:
    jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/
    jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/DurationProvider.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/EventProducer.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimer.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerBeanInfo.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources.properties   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources_fr.properties   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ThroughputProvider.java   (with props)
    jmeter/trunk/test/src/org/apache/jmeter/timers/poissonarrivals/
    jmeter/trunk/test/src/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerTest.java   (with props)
Modified:
    jmeter/trunk/bin/saveservice.properties
    jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java
    jmeter/trunk/xdocs/changes.xml
    jmeter/trunk/xdocs/usermanual/component_reference.xml

Modified: jmeter/trunk/bin/saveservice.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/bin/saveservice.properties?rev=1819293&r1=1819292&r2=1819293&view=diff
==============================================================================
--- jmeter/trunk/bin/saveservice.properties (original)
+++ jmeter/trunk/bin/saveservice.properties Tue Dec 26 20:40:32 2017
@@ -65,7 +65,7 @@
 # 3.1 = 3.1
 # 3.2 = 3.2
 # 3.4 = 3.4
-_version=3.4
+_version=4.0
 #
 #
 # Character set encoding used to read and write JMeter XML files and CSV results
@@ -133,6 +133,7 @@ DNSCacheManager=org.apache.jmeter.protoc
 DNSCachePanel=org.apache.jmeter.protocol.http.gui.DNSCachePanel
 DurationAssertion=org.apache.jmeter.assertions.DurationAssertion
 DurationAssertionGui=org.apache.jmeter.assertions.gui.DurationAssertionGui
+ExponentialTimer=org.apache.jmeter.timers.poissonarrivals.ExponentialTimer
 # Should really have been defined as floatProp to agree with other properties
 # No point changing this now
 FloatProperty=org.apache.jmeter.testelement.property.FloatProperty

Added: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java?rev=1819293&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java Tue Dec 26 20:40:32 2017
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.timers.poissonarrivals;
+
+import java.nio.DoubleBuffer;
+import java.util.Random;
+
+import org.apache.jmeter.testelement.AbstractTestElement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Generates events for poisson processes, ensuring throughput*duration events will be present in each "duration"
+ * @since 4.0
+ */
+public class ConstantPoissonProcessGenerator implements EventProducer {
+    private static final Logger log = LoggerFactory.getLogger(ConstantPoissonProcessGenerator.class);
+
+    private Random rnd = new Random();
+    public ThroughputProvider throughput;
+    private int batchSize;
+    private int batchThreadDelay;
+    public DurationProvider duration;
+    private double lastThroughput;
+    private int exactLimit;
+    private double allowedThroughputSurplus;
+    private DoubleBuffer events;
+    private double lastEvent;
+    private final boolean logFirstSamples;
+
+    public ConstantPoissonProcessGenerator(
+            ThroughputProvider throughput, int batchSize, int batchThreadDelay,
+            DurationProvider duration, int exactLimit, double allowedThroughputSurplus, 
+            Long seed, boolean logFirstSamples) {
+        this.throughput = throughput;
+        this.batchSize = batchSize;
+        this.batchThreadDelay = batchThreadDelay;
+        this.duration = duration;
+        this.exactLimit = exactLimit;
+        this.allowedThroughputSurplus = allowedThroughputSurplus;
+        this.logFirstSamples = logFirstSamples;
+        if (seed != null && seed.intValue() != 0) {
+            rnd.setSeed(seed);
+        }
+        ensureCapacity();
+    }
+
+    private void ensureCapacity() {
+        int size = (int) Math.round((throughput.getThroughput() * duration.getDuration() + 1) * 3);
+        if (events != null && events.capacity() >= size) {
+            return;
+        }
+        events = DoubleBuffer.allocate(size);
+    }
+
+    public void generateNext() {
+        double throughput = this.throughput.getThroughput();
+        lastThroughput = throughput;
+        if (batchSize > 1) {
+            throughput /= batchSize;
+        }
+        long duration = this.duration.getDuration();
+        ensureCapacity();
+        int samples = (int) Math.ceil(throughput * duration);
+        double time;
+        int i = 0;
+        long t = System.currentTimeMillis();
+        int loops = 0;
+        double allowedThroughputSurplus = samples < exactLimit ? 0.0d : this.allowedThroughputSurplus / 100;
+        do {
+            time = 0;
+            events.clear();
+            if (throughput < 1e-5) {
+                log.info("Throughput should exceed zero");
+                break;
+            }
+            if (duration < 5) {
+                log.info("Duration should exceed 5 seconds");
+                break;
+            }
+            for (i = 0; time < duration; i++) {
+                double u = rnd.nextDouble();
+                // https://en.wikipedia.org/wiki/Exponential_distribution#Generating_exponential_variates
+                double delay = -Math.log(1 - u) / throughput;
+                time += delay;
+                events.put(time + lastEvent);
+            }
+            loops++;
+        } while (System.currentTimeMillis() - t < 5000 &&
+                (i < samples + 1 // not enough samples
+                        || (i - 1 - samples) * 1.0f / samples > allowedThroughputSurplus));
+        t = System.currentTimeMillis() - t;
+        if (t > 1000) {
+            log.warn("Spent {} ms while generating sequence of delays for {} samples, {} throughput, {} duration",
+                    t, samples, throughput, duration);
+        }
+        if (logFirstSamples) {
+            if (log.isDebugEnabled()) {
+                log.debug("Generated {} events ({} required, rate {}) in {} ms, restart was issued {} times",
+                        events.position(), samples, throughput, t, loops);
+            }
+            if(log.isInfoEnabled()) {
+                StringBuilder sb = new StringBuilder();
+                sb.append("Generated ").append(events.position()).append(" timings (");
+                if (this.duration instanceof AbstractTestElement) {
+                    sb.append(((AbstractTestElement) this.duration).getName());
+                }
+                sb.append(" ").append(samples).append(" required, rate ").append(throughput).append(", duration ").append(duration);
+                sb.append(", exact lim ").append(exactLimit).append(", i").append(i);
+                sb.append(") in ").append(t).append(" ms, restart was issued ").append(loops).append(" times. ");
+                sb.append("First 15 events will be fired at: ");
+                double prev = 0;
+                for (i = 0; i < events.position() && i < 15; i++) {
+                    if (i > 0) {
+                        sb.append(", ");
+                    }
+                    double ev = events.get(i);
+                    sb.append(ev);
+                    sb.append(" (+").append(ev - prev).append(")");
+                    prev = ev;
+                }
+                log.info(sb.toString());
+            }
+
+        }
+        events.flip();
+        if (batchSize > 1) {
+            // If required to generate "pairs" of events, then just duplicate events in the buffer
+            // TODO: for large batchSizes it makes sense to use counting instead
+            DoubleBuffer tmpBuffer = DoubleBuffer.allocate(batchSize * events.remaining());
+            while (events.hasRemaining()) {
+                double curTime = events.get();
+                for (int j = 0; j < batchSize; j++) {
+                    tmpBuffer.put(curTime + j * batchThreadDelay);
+                }
+            }
+            tmpBuffer.flip();
+            events = tmpBuffer;
+        }
+    }
+
+    @Override
+    public double next() {
+        if (!events.hasRemaining() || throughput.getThroughput() != lastThroughput) {
+            generateNext();
+        }
+        lastEvent = events.get();
+        return lastEvent;
+    }
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/DurationProvider.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/DurationProvider.java?rev=1819293&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/DurationProvider.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/DurationProvider.java Tue Dec 26 20:40:32 2017
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.timers.poissonarrivals;
+
+/**
+ * @since 4.0
+ */
+@FunctionalInterface
+interface DurationProvider {
+    long getDuration();
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/DurationProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/DurationProvider.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/EventProducer.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/EventProducer.java?rev=1819293&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/EventProducer.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/EventProducer.java Tue Dec 26 20:40:32 2017
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.timers.poissonarrivals;
+
+/**
+ * @since 4.0
+ *
+ */
+@FunctionalInterface
+interface EventProducer {
+    double next();
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/EventProducer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/EventProducer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimer.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimer.java?rev=1819293&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimer.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimer.java Tue Dec 26 20:40:32 2017
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.timers.poissonarrivals;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.jmeter.testbeans.TestBean;
+import org.apache.jmeter.testelement.AbstractTestElement;
+import org.apache.jmeter.testelement.TestStateListener;
+import org.apache.jmeter.threads.AbstractThreadGroup;
+import org.apache.jmeter.timers.Timer;
+import org.apache.jorphan.util.JMeterStopThreadException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This timer generates poisson arrivals with constant throughput.
+ * On top of that, it tries to maintain the exact amount of arrivals for a given timeframe ({@link #throughputPeriod}.
+ * @since 4.0
+ */
+public class ExponentialTimer extends AbstractTestElement implements Cloneable, Timer, TestStateListener, TestBean, ThroughputProvider, DurationProvider {
+    private static final Logger log = LoggerFactory.getLogger(ExponentialTimer.class);
+
+    private static final long serialVersionUID = 3;
+    private static final ConcurrentMap<AbstractThreadGroup, EventProducer> groupEvents = new ConcurrentHashMap<>();
+
+    /**
+     * Desired throughput configured as {@code throughput/throughputPeriod} per second.
+     */
+    private double throughput;
+    private int throughputPeriod;
+
+    /**
+     * This is used to ensure you'll get {@code duration*throughput/throughputPeriod} samples during "test duration" timeframe.
+     * Even though arrivals are random, business users want to see round numbers in reports like "100 samples per hour",
+     * so the timer picks only those random arrivals that end up with round total numbers.
+     */
+    private long duration;
+
+    private long testStarted;
+
+    /**
+     * When number of required samples exceeds {@code exactLimit}, random generator would resort to approximate match of
+     * number of generated samples.
+     */
+    private int exactLimit;
+    private double allowedThroughputSurplus;
+
+    /**
+     * This enables to reproduce exactly the same sequence of delays by reusing the same seed.
+     */
+    private Long randomSeed;
+
+    /**
+     * This enables to generate events in batches (e.g. pairs of events with {@link #batchThreadDelay} sec in between)
+     * TODO: this should be either rewritten to double / ms, or dropped in favour of other approach
+     */
+    private int batchSize;
+    private int batchThreadDelay;
+
+    @Override
+    public Object clone() {
+        final ExponentialTimer newTimer = (ExponentialTimer) super.clone();
+        newTimer.testStarted = testStarted; // JMeter cloning does not clone fields
+        return newTimer;
+    }
+
+    @Override
+    public void testStarted() {
+        testStarted(null);
+    }
+
+    @Override
+    public void testStarted(String host) {
+        groupEvents.clear();
+        testStarted = System.currentTimeMillis();
+    }
+
+    @Override
+    public void testEnded() {
+        // NOOP
+    }
+
+    @Override
+    public void testEnded(String s) {
+        // NOOP
+    }
+
+    @Override
+    public long delay() {
+        double nextEvent;
+        EventProducer events = getEventProducer();
+        synchronized (events) {
+            nextEvent = events.next();
+        }
+        long delay = (long) (nextEvent * TimeUnit.SECONDS.toMillis(1) + testStarted - System.currentTimeMillis());
+        if (log.isDebugEnabled()) {
+            log.debug("Calculated delay is {}", delay);
+        }
+        delay = Math.max(0, delay);
+        long endTime = getThreadContext().getThread().getEndTime();
+        if (endTime > 0 && System.currentTimeMillis() + delay > endTime) {
+            throw new JMeterStopThreadException("The thread is scheduled to stop in " +
+                    (System.currentTimeMillis() - endTime) + " ms" +
+                    " and the throughput timer generates a delay of " + delay + "." +
+                    " JMeter (as of 4.0) does not support interrupting of sleeping threads, thus terminating the thread manually."
+            );
+        }
+        return delay;
+    }
+
+    private EventProducer getEventProducer() {
+        AbstractThreadGroup tg = getThreadContext().getThreadGroup();
+        Long seed = randomSeed == null || randomSeed == 0 ? null : randomSeed;
+        return 
+                groupEvents.computeIfAbsent(tg, x -> new ConstantPoissonProcessGenerator(
+                        () -> ExponentialTimer.this.getThroughput() / throughputPeriod, 
+                        batchSize, batchThreadDelay, this, exactLimit, allowedThroughputSurplus, seed, true));
+    }
+
+    /**
+     * Returns number of generated samples per {@link #getThroughputPeriod}
+     * @return number of samples per {@link #getThroughputPeriod}
+     */
+    public double getThroughput() {
+        return throughput;
+    }
+
+    /**
+     * Sets number of generated samples per {@link #getThroughputPeriod}
+     * @param throughput number of samples per {@link #getThroughputPeriod}
+     */
+    public void setThroughput(double throughput) {
+        this.throughput = throughput;
+    }
+
+    /**
+     * Allows to use business values for throughput configuration.
+     * For instance, 100 samples per hour vs 100 samples per minute.
+     * @return
+     */
+    public int getThroughputPeriod() {
+        return throughputPeriod;
+    }
+
+    public void setThroughputPeriod(int throughputPeriod) {
+        this.throughputPeriod = throughputPeriod;
+    }
+
+    public long getDuration() {
+        return duration;
+    }
+
+    public void setDuration(long duration) {
+        this.duration = duration;
+    }
+
+    public int getExactLimit() {
+        return exactLimit;
+    }
+
+    public void setExactLimit(int exactLimit) {
+        this.exactLimit = exactLimit;
+    }
+
+    public double getAllowedThroughputSurplus() {
+        return allowedThroughputSurplus;
+    }
+
+    public void setAllowedThroughputSurplus(double allowedThroughputSurplus) {
+        this.allowedThroughputSurplus = allowedThroughputSurplus;
+    }
+
+    public Long getRandomSeed() {
+        return randomSeed;
+    }
+
+    public void setRandomSeed(Long randomSeed) {
+        this.randomSeed = randomSeed;
+    }
+
+    public int getBatchSize() {
+        return batchSize;
+    }
+
+    public void setBatchSize(int batchSize) {
+        this.batchSize = batchSize;
+    }
+
+    public int getBatchThreadDelay() {
+        return batchThreadDelay;
+    }
+
+    public void setBatchThreadDelay(int batchThreadDelay) {
+        this.batchThreadDelay = batchThreadDelay;
+    }
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerBeanInfo.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerBeanInfo.java?rev=1819293&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerBeanInfo.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerBeanInfo.java Tue Dec 26 20:40:32 2017
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.timers.poissonarrivals;
+
+import java.beans.PropertyDescriptor;
+
+import org.apache.jmeter.testbeans.BeanInfoSupport;
+
+/**
+ * @since 4.0
+ */
+public class ExponentialTimerBeanInfo extends BeanInfoSupport {
+    public ExponentialTimerBeanInfo() {
+        super(ExponentialTimer.class);
+        createPropertyGroup(
+                "delay", //$NON-NLS-1$
+                new String[]{
+                        "throughput", //$NON-NLS-1$
+                        "throughputPeriod",    //$NON-NLS-1$
+                        "duration",    //$NON-NLS-1$
+                }
+        );
+
+        PropertyDescriptor p;
+        p = property("throughput"); //$NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, Double.valueOf(100));
+
+        p = property("throughputPeriod"); //$NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, 3600);
+
+        p = property("duration"); //$NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, Long.valueOf(3600));
+
+        createPropertyGroup(
+                "batching", //$NON-NLS-1$
+                new String[] {
+                        "batchSize"
+                        , "batchThreadDelay"
+                }
+        );
+
+        p = property("batchSize"); //$NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, 1);
+
+        p = property("batchThreadDelay"); //$NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, 0);
+
+
+        createPropertyGroup(
+                "accuracy", //$NON-NLS-1$
+                new String[]{
+                        "exactLimit", //$NON-NLS-1$
+                        "allowedThroughputSurplus"    //$NON-NLS-1$
+                }
+        );
+
+        p = property("exactLimit"); //$NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, Integer.valueOf(10000));
+
+        p = property("allowedThroughputSurplus"); //$NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, Double.valueOf(1.0d));
+
+        createPropertyGroup(
+                "repeatability", //$NON-NLS-1$
+                new String[]{
+                        "randomSeed" //$NON-NLS-1$
+                }
+        );
+
+        p = property("randomSeed"); //$NON-NLS-1$
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, Long.valueOf(0));
+    }
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerBeanInfo.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerBeanInfo.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources.properties?rev=1819293&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources.properties (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources.properties Tue Dec 26 20:40:32 2017
@@ -0,0 +1,36 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+displayName=Exponential Timer
+delay.displayName=Delay threads to ensure target throughput
+throughput.displayName=Target throughput (in samples per "throughput period")
+throughput.shortDescription=Maximum number of samples you want to obtain per "throughput period", including all threads in group, from all affected samplers
+throughputPeriod.displayName=Throughput period (seconds)
+throughputPeriod.shortDescription=Throughput period. For example, if "throughput" is set to 42 and "throughput period" to 21 sec, then you'll get 2 samples per second.
+duration.displayName=Test duration (seconds)
+duration.shortDescription=This is used to ensure you'll get throughput*duration samples during "test duration" timeframe
+accuracy.displayName=Accuracy of generated delays
+exactLimit.displayName=Use approximate throughput when sequence length exceeds (samples)
+exactLimit.shortDescription=When the required number of samples is less than this limit, timer will generate exact number of samples
+allowedThroughputSurplus.displayName=Allowed throughput surplus (percents)
+allowedThroughputSurplus.shortDescription=When more than "max exact samples" samples is required, timer might generate slightly more events than specified by throughput
+repeatability.displayName=Setting to ensure repeatable sequence
+randomSeed.displayName=Random seed (change from 0 to random)
+randomSeed.shortDescription=Note: different timers should better have different seed values. Constant seed ensures timer generates the same delays each test start. The value of "0" means the timer is truly random (non-repeatable from one execution to another).
+batching.displayName=Batched departures
+batchSize.displayName=Number of threads in the batch (threads)
+batchSize.shortDescription=If the value exceeds 1, then multiple threads depart from the timer simultaneously. Average throughput still meets "throughput" value
+batchThreadDelay.displayName=Delay between threads in the batch (ms)
+batchThreadDelay.shortDescription=For instance, if set to 42, and the batch size is 3, then threads will depart at x, x+42ms, x+84ms

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources.properties
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources.properties
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources_fr.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources_fr.properties?rev=1819293&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources_fr.properties (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources_fr.properties Tue Dec 26 20:40:32 2017
@@ -0,0 +1,36 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+displayName=Exponential Timer
+delay.displayName=Delay threads to ensure target throughput
+throughput.displayName=Target throughput (in samples per "throughput period")
+throughput.shortDescription=Maximum number of samples you want to obtain per "throughput period", including all threads in group, from all affected samplers
+throughputPeriod.displayName=Throughput period (seconds)
+throughputPeriod.shortDescription=Throughput period. For example, if "throughput" is set to 42 and "throughput period" to 21 sec, then you'll get 2 samples per second.
+duration.displayName=Test duration (seconds)
+duration.shortDescription=This is used to ensure you'll get throughput*duration samples during "test duration" timeframe
+accuracy.displayName=Accuracy of generated delays
+exactLimit.displayName=Use approximate throughput when sequence length exceeds (samples)
+exactLimit.shortDescription=When the required number of samples is less than this limit, timer will generate exact number of samples
+allowedThroughputSurplus.displayName=Allowed throughput surplus (percents)
+allowedThroughputSurplus.shortDescription=When more than "max exact samples" samples is required, timer might generate slightly more events than specified by throughput
+repeatability.displayName=Setting to ensure repeatable sequence
+randomSeed.displayName=Random seed (change from 0 to random)
+randomSeed.shortDescription=Note: different timers should better have different seed values. Constant seed ensures timer generates the same delays each test start. The value of "0" means the timer is truly random (non-repeatable from one execution to another).
+batching.displayName=Batched departures
+batchSize.displayName=Number of threads in the batch (threads)
+batchSize.shortDescription=If the value exceeds 1, then multiple threads depart from the timer simultaneously. Average throughput still meets "throughput" value
+batchThreadDelay.displayName=Delay between threads in the batch (ms)
+batchThreadDelay.shortDescription=For instance, if set to 42, and the batch size is 3, then threads will depart at x, x+42ms, x+84ms

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources_fr.properties
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerResources_fr.properties
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ThroughputProvider.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ThroughputProvider.java?rev=1819293&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ThroughputProvider.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ThroughputProvider.java Tue Dec 26 20:40:32 2017
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.timers.poissonarrivals;
+
+/**
+ * @since 4.0
+ *
+ */
+@FunctionalInterface
+interface ThroughputProvider {
+    double getThroughput();
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ThroughputProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/components/org/apache/jmeter/timers/poissonarrivals/ThroughputProvider.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java?rev=1819293&r1=1819292&r2=1819293&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java Tue Dec 26 20:40:32 2017
@@ -149,13 +149,13 @@ public class SaveService {
     
     // Must match _version property value in saveservice.properties
     // used to ensure saveservice.properties and SaveService are updated simultaneously
-    static final String PROPVERSION = "3.4";// Expected version $NON-NLS-1$
+    static final String PROPVERSION = "4.0";// Expected version $NON-NLS-1$
 
     // Internal information only
     private static String fileVersion = ""; // computed from saveservice.properties file// $NON-NLS-1$
     // Must match the sha1 checksum of the file saveservice.properties (without newline character),
     // used to ensure saveservice.properties and SaveService are updated simultaneously
-    static final String FILEVERSION = "0acd8200bf252acf41d5eeca6aa56b0eaee06f0f"; // Expected value $NON-NLS-1$
+    static final String FILEVERSION = "c616712e26251d15588fbe615d1c31a34dbbf854"; // Expected value $NON-NLS-1$
 
     private static String fileEncoding = ""; // read from properties file// $NON-NLS-1$
 

Added: jmeter/trunk/test/src/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerTest.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerTest.java?rev=1819293&view=auto
==============================================================================
--- jmeter/trunk/test/src/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerTest.java (added)
+++ jmeter/trunk/test/src/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerTest.java Tue Dec 26 20:40:32 2017
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.timers.poissonarrivals;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ExponentialTimerTest {
+	private static final Logger LOG = LoggerFactory.getLogger(ExponentialTimerTest.class);
+
+	@Test
+	public void testTimer1() throws Exception {
+		ConstantPoissonProcessGenerator gen = getConstantPoissonProcessGenerator(2, 5, 42L);
+		gen.generateNext();
+		assertEquals(0.6501751901910952, gen.next(), 0.01);
+		assertEquals(1.2249545461599474, gen.next(), 0.01);
+		assertEquals(1.409559315928937, gen.next(), 0.01);
+		assertEquals(1.5717866281130652, gen.next(), 0.01);
+		assertEquals(2.1194190047658874, gen.next(), 0.01);
+		assertEquals(3.2878637366551384, gen.next(), 0.01);
+		assertEquals(3.517916456559849, gen.next(), 0.01);
+		assertEquals(3.679224444929692, gen.next(), 0.01);
+		assertEquals(3.9907119513763165, gen.next(), 0.01);
+		assertEquals(4.754414649148714, gen.next(), 0.01);
+		// ^^ 10 samples for 5 seconds
+		assertEquals(6.013095167372755, gen.next(), 0.01);
+	}
+
+	@Test
+	public void testExactNumberOfSamples() throws Exception {
+		java.util.Random rnd = new java.util.Random();
+		long seed = rnd.nextLong();
+		// Log seed, so the test can be reproduced in case of failure
+		LOG.info("testExactNumberOfSamples is using seed " + seed);
+		rnd.setSeed(seed);
+
+		int testDuration = 5;
+		for (int i = 0; i < 1000; i++) {
+			ConstantPoissonProcessGenerator gen = getConstantPoissonProcessGenerator(2, testDuration, rnd.nextLong());
+			gen.generateNext();
+			for (int j = 0; j < 10; j++) {
+				double next = gen.next();
+				assertTrue("Delay #" + j + " (0-based) exceeds " + testDuration + " seconds", next < 5.0);
+			}
+		}
+	}
+
+	protected ConstantPoissonProcessGenerator getConstantPoissonProcessGenerator(
+			final double throughput, final int duration, long seed) {
+		return new ConstantPoissonProcessGenerator(
+				new ThroughputProvider() {
+					@Override
+					public double getThroughput() {
+						return throughput; // samples per second
+					}
+				},
+				1,
+				0,
+				new DurationProvider() {
+					@Override
+					public long getDuration() {
+						return duration; // "expected" test duration: 3 seconds
+					}
+				},
+				10000,
+				0.1,
+				seed, // Seed
+				false
+		);
+	}
+
+}

Propchange: jmeter/trunk/test/src/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/test/src/org/apache/jmeter/timers/poissonarrivals/ExponentialTimerTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1819293&r1=1819292&r2=1819293&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Tue Dec 26 20:40:32 2017
@@ -128,7 +128,8 @@ Summary
 
 <h3>Timers, Assertions, Config, Pre- &amp; Post-Processors</h3>
 <ul>
-    <li><bug>60213</bug>Boundary based extractor</li>
+    <li><bug>60213</bug>New component : Boundary based extractor</li>
+    <li><bug>61931</bug>New Component : Exponential Timer, timer that produces poisson arrivals with given constant throughput </li>
     <li><bug>61644</bug>HTTP Cache Manager: "Use Cache-Control/Expires header when processing GET requests" should be checked by default</li>
     <li><bug>61645</bug>Response Assertion: Add ability to assert on Request Data</li>
     <li><bug>61534</bug>Convert AssertionError to a failed assertion in the JSR223Assertion allowing users to use assert in their code</li>
@@ -136,6 +137,7 @@ Summary
     <li><bug>61758</bug><code>Apply to:</code> field in Extractors, Assertions : When entering a value in <code>JMeter Variable Name</code>, the radio box <code>JMeter Variable Name</code> should be selected by default. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li>
     <li><bug>61845</bug>New Component JSON Assertion based on AtlanBH JSON Path Assertion donated to JMeter-Plugins and migrated into JMeter core by Artem Fedorov (artem at blazemeter.com)</li>
     <li><bug>61846</bug>Scoped Assertion should follow same order of evaluation as Post Processors</li>
+   
 </ul>
 
 <h3>Functions</h3>

Modified: jmeter/trunk/xdocs/usermanual/component_reference.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/usermanual/component_reference.xml?rev=1819293&r1=1819292&r2=1819293&view=diff
==============================================================================
--- jmeter/trunk/xdocs/usermanual/component_reference.xml (original)
+++ jmeter/trunk/xdocs/usermanual/component_reference.xml Tue Dec 26 20:40:32 2017
@@ -5310,6 +5310,39 @@ to the random delay.</property>
 
 </component>
 
+<component name="Exponential Timer" index="&sect-num;.6.10" width="341" height="182" screenshot="timers/exponential_timer.png">
+<description><p>This timer produces poisson arrivals with given constant throughput. It provides the following features:
+<ul>
+    <li><i>Specific number of iterations per hour</i>.
+    The very basic requirement is to ensure you end up exactly 50
+    iterations per hour.</li>
+    <li><i>Bursty load</i> simulation. There is no easy way to test "50
+    iterations per hour as 10 bursts of 5 items".</li>
+    <li><i>Repeatable test profile</i>. All the random timers produce different
+    pattern on each test run. This is not good for low-level analysis
+    (e.g. compare of CPU% charts, etc).</li>
+    <li>
+    <li>Avoid all thread groups</li> to fire at 00:00:00. By default, all
+    thread groups would fire at 0, so there would be noticeable spike at
+    the start of the test.</li>
+</ul>
+</p></description>
+
+
+<properties>
+    <property name="Name" required="No">Descriptive name for this timer that is shown in the tree</property>
+    <property name="Target throughput (in samples per 'throughput period')" required="Yes">Maximum number of samples you want to obtain per "throughput period", including all threads in group, from all affected samplers.</property>
+    <property name="Throughput period (seconds)" required="Yes">Throughput period. For example, if "throughput" is set to 42 and "throughput period" to 21 sec, then you'll get 2 samples per second.</property>
+    <property name="Test duration (seconds)" required="Yes">This is used to ensure you'll get throughput*duration samples during "test duration" timeframe.</property>
+    <property name="Number of threads in the batch (threads)" required="Yes">If the value exceeds 1, then multiple threads depart from the timer simultaneously. Average throughput still meets "throughput" value.</property>
+    <property name="Delay between threads in the batch (ms)" required="Yes">For instance, if set to 42, and the batch size is 3, then threads will depart at x, x+42ms, x+84ms.</property>
+    <property name="Use approximate throughput when sequence length exceeds (samples)" required="Yes">When the required number of samples is less than this limit, timer will generate exact number of samples.</property>
+    <property name="Allowed throughput surplus (percents)" required="Yes">When more than "max exact samples" samples is required, timer might generate slightly more events than specified by throughput.</property>
+    <property name="Random seed (change from 0 to random)" required="Yes">Note: different timers should better have different seed values. Constant seed ensures timer generates the same delays each test start. The value of "0" means the timer is truly random (non-repeatable from one execution to another)..</property>
+</properties>
+
+</component>
+
 <a href="#">^</a>
 
 </section>