Coverage Report - kg.apc.jmeter.timers.VariableThroughputTimer
 
Classes in this File Line Coverage Branch Coverage Complexity
VariableThroughputTimer
92%
130/141
79%
46/58
3.438
 
 1  
 // TODO: fight with lagging on start
 2  
 // TODO: create a thread which will wake up at least one sampler to provide rps
 3  
 package kg.apc.jmeter.timers;
 4  
 
 5  
 import java.util.ArrayList;
 6  
 
 7  
 import kg.apc.jmeter.JMeterPluginsUtils;
 8  
 import org.apache.jmeter.engine.StandardJMeterEngine;
 9  
 import org.apache.jmeter.engine.util.NoThreadClone;
 10  
 import org.apache.jmeter.gui.util.PowerTableModel;
 11  
 import org.apache.jmeter.testelement.AbstractTestElement;
 12  
 import org.apache.jmeter.testelement.TestStateListener;
 13  
 import org.apache.jmeter.testelement.property.CollectionProperty;
 14  
 import org.apache.jmeter.testelement.property.JMeterProperty;
 15  
 import org.apache.jmeter.testelement.property.NullProperty;
 16  
 import org.apache.jmeter.testelement.property.PropertyIterator;
 17  
 import org.apache.jmeter.threads.JMeterContextService;
 18  
 import org.apache.jmeter.timers.ConstantThroughputTimer;
 19  
 import org.apache.jmeter.timers.Timer;
 20  
 import org.apache.jmeter.util.JMeterUtils;
 21  
 import org.apache.jorphan.logging.LoggingManager;
 22  
 import org.apache.log.Logger;
 23  
 
 24  
 /**
 25  
  * @see ConstantThroughputTimer
 26  
  */
 27  
 public class VariableThroughputTimer
 28  
         extends AbstractTestElement
 29  
         implements Timer, NoThreadClone, TestStateListener {
 30  
 
 31  1
     public static final String[] columnIdentifiers = new String[]{
 32  
             "Start RPS", "End RPS", "Duration, sec"
 33  
     };
 34  1
     public static final Class[] columnClasses = new Class[]{
 35  
             String.class, String.class, String.class
 36  
     };
 37  
     // TODO: eliminate magic property
 38  
     public static final String DATA_PROPERTY = "load_profile";
 39  
     public static final int DURATION_FIELD_NO = 2;
 40  
     public static final int FROM_FIELD_NO = 0;
 41  
     public static final int TO_FIELD_NO = 1;
 42  1
     private static final Logger log = LoggingManager.getLoggerForClass();
 43  
     /* put this in fields because we don't want create variables in tight loops */
 44  
     private long cntDelayed;
 45  21
     private double time = 0;
 46  
     private double msecPerReq;
 47  
     private long cntSent;
 48  
     private double rps;
 49  21
     private double startSec = 0;
 50  
     private CollectionProperty overrideProp;
 51  
     private int stopTries;
 52  
     private double lastStopTry;
 53  
     private boolean stopping;
 54  
 
 55  
     public VariableThroughputTimer() {
 56  21
         super();
 57  21
         trySettingLoadFromProperty();
 58  21
     }
 59  
 
 60  
     public long delay() {
 61  87
         synchronized (this) {
 62  
 
 63  
             while (true) {
 64  
                 int delay;
 65  243
                 long curTime = System.currentTimeMillis();
 66  243
                 long msecs = curTime % 1000;
 67  243
                 long secs = curTime - msecs;
 68  243
                 checkNextSecond(secs);
 69  241
                 delay = getDelay(msecs);
 70  
 
 71  241
                 if (stopping) {
 72  79
                     delay = delay > 0 ? 10 : 0;
 73  79
                     notify();
 74  
                 }
 75  
 
 76  241
                 if (delay < 1) {
 77  85
                     notify();
 78  85
                     break;
 79  
                 }
 80  156
                 cntDelayed++;
 81  
                 try {
 82  156
                     wait(delay);
 83  0
                 } catch (InterruptedException ex) {
 84  0
                     log.error("Waiting thread was interrupted", ex);
 85  156
                 }
 86  156
                 cntDelayed--;
 87  156
             }
 88  85
             cntSent++;
 89  85
         }
 90  85
         return 0;
 91  
     }
 92  
 
 93  
     private synchronized void checkNextSecond(double secs) {
 94  
         // next second
 95  243
         if (time == secs) {
 96  230
             return;
 97  
         }
 98  
 
 99  13
         if (startSec == 0) {
 100  2
             startSec = secs;
 101  
         }
 102  13
         time = secs;
 103  
 
 104  13
         double nextRps = getRPSForSecond((secs - startSec) / 1000);
 105  13
         if (nextRps < 0) {
 106  3
             stopping = true;
 107  3
             rps = rps > 0 ? rps * (stopTries > 10 ? 2 : 1) : 1;
 108  3
             stopTest();
 109  1
             notifyAll();
 110  
         } else {
 111  10
             rps = nextRps;
 112  
         }
 113  
 
 114  11
         if (log.isDebugEnabled()) {
 115  0
             log.debug("Second changed " + ((secs - startSec) / 1000) + ", sleeping: " + cntDelayed + ", sent " + cntSent + ", RPS: " + rps);
 116  
         }
 117  
 
 118  11
         if (cntDelayed < 1) {
 119  11
             log.warn("No free threads left in worker pool, made  " + cntSent + '/' + rps + " samples");
 120  
         }
 121  
 
 122  11
         cntSent = 0;
 123  11
         msecPerReq = 1000d / rps;
 124  11
     }
 125  
 
 126  
     private int getDelay(long msecs) {
 127  
         //log.info("Calculating "+msecs + " " + cntSent * msecPerReq+" "+cntSent);
 128  241
         if (msecs < (cntSent * msecPerReq)) {
 129  156
             return (int) (1 + 1000.0 * (cntDelayed + 1) / rps);
 130  
         }
 131  85
         return 0;
 132  
     }
 133  
 
 134  
     void setData(CollectionProperty rows) {
 135  12
         setProperty(rows);
 136  12
     }
 137  
 
 138  
     JMeterProperty getData() {
 139  32
         if (overrideProp != null) {
 140  1
             return overrideProp;
 141  
         }
 142  31
         return getProperty(DATA_PROPERTY);
 143  
     }
 144  
 
 145  
     public double getRPSForSecond(double sec) {
 146  28
         JMeterProperty data = getData();
 147  28
         if (data instanceof NullProperty) return -1;
 148  26
         CollectionProperty rows = (CollectionProperty) data;
 149  26
         PropertyIterator scheduleIT = rows.iterator();
 150  
 
 151  53
         while (scheduleIT.hasNext()) {
 152  46
             ArrayList<Object> curProp = (ArrayList<Object>) scheduleIT.next().getObjectValue();
 153  
 
 154  46
             int duration = getIntValue(curProp, DURATION_FIELD_NO);
 155  46
             double from = getDoubleValue(curProp, FROM_FIELD_NO);
 156  46
             double to = getDoubleValue(curProp, TO_FIELD_NO);
 157  46
             if (sec - duration <= 0) {
 158  19
                 return from + sec * (to - from) / (double) duration;
 159  
             } else {
 160  27
                 sec -= duration;
 161  
             }
 162  27
         }
 163  7
         return -1;
 164  
     }
 165  
 
 166  
     private double getDoubleValue(ArrayList<Object> prop, int colID) throws NumberFormatException {
 167  92
         JMeterProperty val = (JMeterProperty) prop.get(colID);
 168  92
         return val.getDoubleValue();
 169  
     }
 170  
 
 171  
     private int getIntValue(ArrayList<Object> prop, int colID) throws NumberFormatException {
 172  46
         JMeterProperty val = (JMeterProperty) prop.get(colID);
 173  46
         return val.getIntValue();
 174  
     }
 175  
 
 176  
     private void trySettingLoadFromProperty() {
 177  21
         String loadProp = JMeterUtils.getProperty(DATA_PROPERTY);
 178  21
         log.debug("Load prop: " + loadProp);
 179  21
         if (loadProp != null && loadProp.length() > 0) {
 180  1
             log.info("GUI load profile will be ignored");
 181  1
             PowerTableModel dataModel = new PowerTableModel(VariableThroughputTimer.columnIdentifiers, VariableThroughputTimer.columnClasses);
 182  
 
 183  1
             String[] chunks = loadProp.split("\\)");
 184  
 
 185  4
             for (String chunk : chunks) {
 186  
                 try {
 187  3
                     parseChunk(chunk, dataModel);
 188  0
                 } catch (RuntimeException e) {
 189  0
                     log.warn("Wrong load chunk ignored: " + chunk, e);
 190  3
                 }
 191  
             }
 192  
 
 193  1
             log.info("Setting load profile from property " + DATA_PROPERTY + ": " + loadProp);
 194  1
             overrideProp = JMeterPluginsUtils.tableModelRowsToCollectionProperty(dataModel, VariableThroughputTimer.DATA_PROPERTY);
 195  
         }
 196  21
     }
 197  
 
 198  
     private static void parseChunk(String chunk, PowerTableModel model) {
 199  3
         log.debug("Parsing chunk: " + chunk);
 200  3
         String[] parts = chunk.split("[(,]");
 201  3
         String loadVar = parts[0].trim();
 202  
 
 203  3
         if (loadVar.equalsIgnoreCase("const")) {
 204  1
             int const_load = Integer.parseInt(parts[1].trim());
 205  1
             Integer[] row = new Integer[3];
 206  1
             row[FROM_FIELD_NO] = const_load;
 207  1
             row[TO_FIELD_NO] = const_load;
 208  1
             row[DURATION_FIELD_NO] = JMeterPluginsUtils.getSecondsForShortString(parts[2]);
 209  1
             model.addRow(row);
 210  
 
 211  1
         } else if (loadVar.equalsIgnoreCase("line")) {
 212  1
             Integer[] row = new Integer[3];
 213  1
             row[FROM_FIELD_NO] = Integer.parseInt(parts[1].trim());
 214  1
             row[TO_FIELD_NO] = Integer.parseInt(parts[2].trim());
 215  1
             row[DURATION_FIELD_NO] = JMeterPluginsUtils.getSecondsForShortString(parts[3]);
 216  1
             model.addRow(row);
 217  
 
 218  1
         } else if (loadVar.equalsIgnoreCase("step")) {
 219  
             // FIXME: float (from-to)/inc will be stepped wrong
 220  1
             int from = Integer.parseInt(parts[1].trim());
 221  1
             int to = Integer.parseInt(parts[2].trim());
 222  1
             int inc = Integer.parseInt(parts[3].trim()) * (from > to ? -1 : 1);
 223  
             //log.info(from + " " + to + " " + inc);
 224  6
             for (int n = from; (inc > 0 ? n <= to : n > to); n += inc) {
 225  
                 //log.info(" " + n);
 226  5
                 Integer[] row = new Integer[3];
 227  5
                 row[FROM_FIELD_NO] = n;
 228  5
                 row[TO_FIELD_NO] = n;
 229  5
                 row[DURATION_FIELD_NO] = JMeterPluginsUtils.getSecondsForShortString(parts[4]);
 230  5
                 model.addRow(row);
 231  
             }
 232  
 
 233  1
         } else {
 234  0
             throw new RuntimeException("Unknown load type: " + parts[0]);
 235  
         }
 236  3
     }
 237  
 
 238  
     // TODO: resolve shutdown problems. Patch JMeter if needed
 239  
     // TODO: make something with test stopping in JMeter. Write custom plugin that tries to kill all threads? Guillotine Stopper! 
 240  
     protected void stopTest() {
 241  2
         if (stopTries > 30) {
 242  0
             throw new RuntimeException("More than 30 seconds - stopping by exception");
 243  
         }
 244  
 
 245  2
         if (lastStopTry == time) {
 246  1
             return;
 247  
         }
 248  1
         log.info("No further RPS schedule, asking threads to stop...");
 249  1
         lastStopTry = time;
 250  1
         stopTries++;
 251  1
         if (stopTries > 10) {
 252  0
             log.info("Tries more than 10, stop it NOW!");
 253  0
             StandardJMeterEngine.stopEngineNow();
 254  1
         } else if (stopTries > 5) {
 255  0
             log.info("Tries more than 5, stop it!");
 256  0
             StandardJMeterEngine.stopEngine();
 257  
         } else {
 258  1
             JMeterContextService.getContext().getEngine().askThreadsToStop();
 259  
         }
 260  1
     }
 261  
 
 262  
     @Override
 263  
     public void testStarted() {
 264  2
         stopping = false;
 265  2
         stopTries = 0;
 266  2
     }
 267  
 
 268  
     @Override
 269  
     public void testStarted(String string) {
 270  1
         testStarted();
 271  1
     }
 272  
 
 273  
     @Override
 274  
     public void testEnded() {
 275  2
     }
 276  
 
 277  
     @Override
 278  
     public void testEnded(String string) {
 279  1
         testEnded();
 280  1
     }
 281  
 
 282  
 
 283  
 }