001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jmeter.timers;
020:
021: import java.util.Hashtable;
022: import java.util.Map;
023:
024: import org.apache.jmeter.engine.event.LoopIterationEvent;
025: import org.apache.jmeter.testbeans.TestBean;
026: import org.apache.jmeter.testelement.AbstractTestElement;
027: import org.apache.jmeter.testelement.TestListener;
028: import org.apache.jmeter.threads.JMeterContextService;
029: import org.apache.jmeter.util.JMeterUtils;
030: import org.apache.jorphan.logging.LoggingManager;
031: import org.apache.log.Logger;
032:
033: /**
034: * This class implements a constant throughput timer. A Constant Throughtput
035: * Timer paces the samplers under its influence so that the total number of
036: * samples per unit of time approaches a given constant as much as possible.
037: *
038: * There are two different ways of pacing the requests:
039: * - delay each thread according to when it last ran
040: * - delay each thread according to when any thread last ran
041: */
042: public class ConstantThroughputTimer extends AbstractTestElement
043: implements Timer, TestListener, TestBean {
044: private static final long serialVersionUID = 3;
045:
046: private static class ThroughputInfo {
047: final Object MUTEX = new Object();
048: long lastScheduledTime = 0;
049: }
050:
051: private static final Logger log = LoggingManager
052: .getLoggerForClass();
053:
054: private static final double MILLISEC_PER_MIN = 60000.0;
055:
056: /**
057: * Target time for the start of the next request. The delay provided by the
058: * timer will be calculated so that the next request happens at this time.
059: */
060: private long previousTime = 0;
061:
062: private String calcMode; // String representing the mode
063: // (Locale-specific)
064:
065: private int modeInt; // mode as an integer
066:
067: /**
068: * Desired throughput, in samples per minute.
069: */
070: private double throughput;
071:
072: //For calculating throughput across all threads
073: private final static ThroughputInfo allThreadsInfo = new ThroughputInfo();
074:
075: //For holding the ThrougputInfo objects for all ThreadGroups. Keyed by ThreadGroup objects
076: private final static Map threadGroupsInfoMap = new Hashtable();
077:
078: /**
079: * Constructor for a non-configured ConstantThroughputTimer.
080: */
081: public ConstantThroughputTimer() {
082: }
083:
084: /**
085: * Sets the desired throughput.
086: *
087: * @param throughput
088: * Desired sampling rate, in samples per minute.
089: */
090: public void setThroughput(double throughput) {
091: this .throughput = throughput;
092: }
093:
094: /**
095: * Gets the configured desired throughput.
096: *
097: * @return the rate at which samples should occur, in samples per minute.
098: */
099: public double getThroughput() {
100: return throughput;
101: }
102:
103: public String getCalcMode() {
104: return calcMode;
105: }
106:
107: public void setCalcMode(String mode) {
108: this .calcMode = mode;
109: // TODO find better way to get modeInt
110: this .modeInt = ConstantThroughputTimerBeanInfo
111: .getCalcModeAsInt(calcMode);
112: }
113:
114: /**
115: * Retrieve the delay to use during test execution.
116: *
117: * @see org.apache.jmeter.timers.Timer#delay()
118: */
119: public long delay() {
120: long currentTime = System.currentTimeMillis();
121:
122: /*
123: * If previous time is zero, then target will be in the past.
124: * This is what we want, so first sample is run without a delay.
125: */
126: long currentTarget = previousTime + calculateDelay();
127: if (currentTime > currentTarget) {
128: // We're behind schedule -- try to catch up:
129: previousTime = currentTime;
130: return 0;
131: }
132: previousTime = currentTarget;
133: return currentTarget - currentTime;
134: }
135:
136: /**
137: * @param currentTime
138: * @return new Target time
139: */
140: // TODO - is this used?
141: protected long calculateCurrentTarget(long currentTime) {
142: return currentTime + calculateDelay();
143: }
144:
145: // Calculate the delay based on the mode
146: private long calculateDelay() {
147: long delay = 0;
148: // N.B. we fetch the throughput each time, as it may vary during a test
149: long msPerRequest = (long) (MILLISEC_PER_MIN / getThroughput());
150: switch (modeInt) {
151: case 1: // Total number of threads
152: delay = JMeterContextService.getNumberOfThreads()
153: * msPerRequest;
154: break;
155:
156: case 2: // Active threads in this group
157: delay = JMeterContextService.getContext().getThreadGroup()
158: .getNumberOfThreads()
159: * msPerRequest;
160: break;
161:
162: case 3: // All threads - alternate calculation
163: delay = calculateSharedDelay(allThreadsInfo, msPerRequest);
164: break;
165:
166: case 4: //All threads in this group - alternate calculation
167: final org.apache.jmeter.threads.ThreadGroup group = JMeterContextService
168: .getContext().getThreadGroup();
169: ThroughputInfo groupInfo;
170: synchronized (threadGroupsInfoMap) {
171: groupInfo = (ThroughputInfo) threadGroupsInfoMap
172: .get(group);
173: if (groupInfo == null) {
174: groupInfo = new ThroughputInfo();
175: threadGroupsInfoMap.put(group, groupInfo);
176: }
177: }
178: delay = calculateSharedDelay(groupInfo, msPerRequest);
179: break;
180:
181: default:
182: delay = msPerRequest; // i.e. * 1
183: break;
184: }
185: return delay;
186: }
187:
188: private long calculateSharedDelay(ThroughputInfo info,
189: long milliSecPerRequest) {
190: final long now = System.currentTimeMillis();
191: final long calculatedDelay;
192:
193: //Synchronize on the info object's MUTEX to ensure
194: //Multiple threads don't update the scheduled time simultaneously
195: synchronized (info.MUTEX) {
196: final long nextRequstTime = info.lastScheduledTime
197: + milliSecPerRequest;
198: info.lastScheduledTime = Math.max(now, nextRequstTime);
199: calculatedDelay = info.lastScheduledTime - now;
200: }
201:
202: return Math.max(calculatedDelay, 0);
203: }
204:
205: private synchronized void reset() {
206: allThreadsInfo.lastScheduledTime = 0;
207: threadGroupsInfoMap.clear();
208: previousTime = 0;
209: }
210:
211: /**
212: * Provide a description of this timer class.
213: *
214: * TODO: Is this ever used? I can't remember where. Remove if it isn't --
215: * TODO: or obtain text from bean's displayName or shortDescription.
216: *
217: * @return the description of this timer class.
218: */
219: public String toString() {
220: return JMeterUtils
221: .getResString("constant_throughput_timer_memo"); //$NON-NLS-1$
222: }
223:
224: /**
225: * Get the timer ready to compute delays for a new test.
226: *
227: * @see org.apache.jmeter.testelement.TestListener#testStarted()
228: */
229: public void testStarted() {
230: log.debug("Test started - reset throughput calculation.");
231: reset();
232: }
233:
234: /*
235: * (non-Javadoc)
236: *
237: * @see org.apache.jmeter.testelement.TestListener#testEnded()
238: */
239: public void testEnded() {
240: }
241:
242: /*
243: * (non-Javadoc)
244: *
245: * @see org.apache.jmeter.testelement.TestListener#testStarted(java.lang.String)
246: */
247: public void testStarted(String host) {
248: testStarted();
249: }
250:
251: /*
252: * (non-Javadoc)
253: *
254: * @see org.apache.jmeter.testelement.TestListener#testEnded(java.lang.String)
255: */
256: public void testEnded(String host) {
257: }
258:
259: /*
260: * (non-Javadoc)
261: *
262: * @see org.apache.jmeter.testelement.TestListener#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent)
263: */
264: public void testIterationStart(LoopIterationEvent event) {
265: }
266: }
|