001: /*
002: * soapUI, copyright (C) 2004-2007 eviware.com
003: *
004: * soapUI is free software; you can redistribute it and/or modify it under the
005: * terms of version 2.1 of the GNU Lesser General Public License as published by
006: * the Free Software Foundation.
007: *
008: * soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
009: * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
010: * See the GNU Lesser General Public License for more details at gnu.org.
011: */
012:
013: package com.eviware.soapui.impl.wsdl.loadtest.data;
014:
015: import java.beans.PropertyChangeEvent;
016: import java.beans.PropertyChangeListener;
017: import java.util.HashMap;
018: import java.util.List;
019: import java.util.Map;
020: import java.util.Stack;
021:
022: import javax.swing.table.AbstractTableModel;
023:
024: import org.apache.log4j.Logger;
025:
026: import com.eviware.soapui.SoapUI;
027: import com.eviware.soapui.impl.wsdl.loadtest.ColorPalette;
028: import com.eviware.soapui.impl.wsdl.loadtest.WsdlLoadTest;
029: import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
030: import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
031: import com.eviware.soapui.model.support.LoadTestRunListenerAdapter;
032: import com.eviware.soapui.model.support.TestSuiteListenerAdapter;
033: import com.eviware.soapui.model.testsuite.LoadTestRunContext;
034: import com.eviware.soapui.model.testsuite.LoadTestRunner;
035: import com.eviware.soapui.model.testsuite.TestCase;
036: import com.eviware.soapui.model.testsuite.TestRunContext;
037: import com.eviware.soapui.model.testsuite.TestRunner;
038: import com.eviware.soapui.model.testsuite.TestStep;
039: import com.eviware.soapui.model.testsuite.TestStepResult;
040:
041: /**
042: * Model holding statistics.. should be refactored into interface for different statistic models
043: *
044: * @author Ole.Matzura
045: */
046:
047: public final class LoadTestStatistics extends AbstractTableModel
048: implements Runnable {
049: private final static Logger log = Logger
050: .getLogger(LoadTestStatistics.class);
051:
052: private final WsdlLoadTest loadTest;
053: private long[][] data;
054:
055: private final static int MIN_COLUMN = 0;
056: private final static int MAX_COLUMN = 1;
057: private final static int AVG_COLUMN = 2;
058: private final static int LAST_COLUMN = 3;
059: private final static int CNT_COLUMN = 4;
060: private final static int TPS_COLUMN = 5;
061: private final static int BYTES_COLUMN = 6;
062: private final static int BPS_COLUMN = 7;
063: private final static int ERR_COLUMN = 8;
064: private final static int SUM_COLUMN = 9;
065: private final static int CURRENT_CNT_COLUMN = 10;
066:
067: public static final int TOTAL = -1;
068:
069: public static final int DEFAULT_SAMPLE_INTERVAL = 250;
070:
071: private InternalTestRunListener testRunListener;
072: private InternalTestSuiteListener testSuiteListener;
073: private InternalPropertyChangeListener propertyChangeListener;
074:
075: private StatisticsHistory history;
076:
077: private boolean changed;
078: private long updateFrequency = DEFAULT_SAMPLE_INTERVAL;
079: private Stack<SamplesHolder> samplesStack = new Stack<SamplesHolder>();
080: private long currentThreadCountStartTime;
081: private long totalAverageSum;
082: private boolean resetStatistics;
083: private boolean running;
084: private boolean adding;
085:
086: public LoadTestStatistics(WsdlLoadTest loadTest) {
087: this .loadTest = loadTest;
088:
089: testRunListener = new InternalTestRunListener();
090: testSuiteListener = new InternalTestSuiteListener();
091: propertyChangeListener = new InternalPropertyChangeListener();
092:
093: WsdlTestCase testCase = loadTest.getTestCase();
094: loadTest.addPropertyChangeListener(propertyChangeListener);
095: loadTest.addLoadTestRunListener(testRunListener);
096: testCase.getTestSuite().addTestSuiteListener(testSuiteListener);
097:
098: for (TestStep testStep : testCase.getTestStepList()) {
099: testStep.addPropertyChangeListener(propertyChangeListener);
100: }
101:
102: history = new StatisticsHistory(this );
103:
104: init();
105: }
106:
107: private void init() {
108: data = new long[getRowCount()][11];
109: }
110:
111: public StatisticsHistory getHistory() {
112: return history;
113: }
114:
115: public int getRowCount() {
116: return loadTest.getTestCase().getTestStepCount() + 1;
117: }
118:
119: public WsdlLoadTest getLoadTest() {
120: return loadTest;
121: }
122:
123: public int getColumnCount() {
124: return 11;
125: }
126:
127: public String getColumnName(int columnIndex) {
128: switch (columnIndex) {
129: case 0:
130: return " ";
131: case 1:
132: return "Test Step";
133: case 2:
134: return Statistic.MININMUM.getName();
135: case 3:
136: return Statistic.MAXIMUM.getName();
137: case 4:
138: return Statistic.AVERAGE.getName();
139: case 5:
140: return Statistic.LAST.getName();
141: case 6:
142: return Statistic.COUNT.getName();
143: case 7:
144: return Statistic.TPS.getName();
145: case 8:
146: return Statistic.BYTES.getName();
147: case 9:
148: return Statistic.BPS.getName();
149: case 10:
150: return Statistic.ERRORS.getName();
151: }
152: return null;
153: }
154:
155: public Class<?> getColumnClass(int columnIndex) {
156: return String.class;
157: }
158:
159: public boolean isCellEditable(int rowIndex, int columnIndex) {
160: return false;
161: }
162:
163: public long getStatistic(int stepIndex, Statistic statistic) {
164: if (stepIndex == TOTAL)
165: stepIndex = data.length - 1;
166:
167: if (statistic == Statistic.TPS
168: || statistic == Statistic.AVERAGE)
169: return data[stepIndex][statistic.getIndex()] / 100;
170: else
171: return data[stepIndex][statistic.getIndex()];
172: }
173:
174: public Object getValueAt(int rowIndex, int columnIndex) {
175: WsdlTestCase testCase = loadTest.getTestCase();
176:
177: switch (columnIndex) {
178: case 0:
179: return rowIndex == testCase.getTestStepCount() ? ""
180: : ColorPalette.getColor(testCase
181: .getTestStepAt(rowIndex));
182: case 1: {
183: if (rowIndex == testCase.getTestStepCount()) {
184: return "TestCase:";
185: } else {
186: WsdlTestStep testStep = testCase
187: .getTestStepAt(rowIndex);
188: if (testStep.isDisabled())
189: return testStep.getName() + " (Disabled)";
190: else
191: return testStep.getName();
192: }
193: }
194: case 4:
195: case 7:
196: return Float
197: .toString((float) data[rowIndex][columnIndex - 2] / 100);
198: default: {
199: return data == null || rowIndex >= data.length ? "0"
200: : data[rowIndex][columnIndex - 2];
201: }
202: }
203: }
204:
205: public void pushSamples(long[] samples, long[] sizes,
206: long[] sampleCounts, long startTime, long timeTaken) {
207: if (samples.length == 0 || sizes.length == 0)
208: return;
209:
210: samplesStack.push(new SamplesHolder(samples, sizes,
211: sampleCounts, startTime, timeTaken));
212: }
213:
214: public void run() {
215: while (running || !samplesStack.isEmpty()) {
216: try {
217: while (!samplesStack.isEmpty()) {
218: SamplesHolder holder = samplesStack.pop();
219: if (holder != null)
220: addSamples(holder);
221: }
222:
223: Thread.sleep(200);
224: } catch (Exception e) {
225: SoapUI.logError(e);
226: }
227: }
228: }
229:
230: private void addSamples(SamplesHolder holder) {
231: if (adding)
232: throw new RuntimeException("Already adding!");
233:
234: adding = true;
235:
236: int totalIndex = data.length - 1;
237: if (holder.samples.length != totalIndex
238: || holder.sizes.length != totalIndex) {
239: adding = false;
240: throw new RuntimeException("Unexpected number of samples: "
241: + holder.samples.length + ", exptected "
242: + (totalIndex));
243: }
244:
245: // discard "old" results
246: if (holder.startTime < currentThreadCountStartTime) {
247: adding = false;
248: return;
249: }
250:
251: // first check that this is not a
252: long timePassed = (holder.startTime + holder.timeTaken)
253: - currentThreadCountStartTime;
254:
255: if (resetStatistics) {
256: for (int c = 0; c < data.length; c++) {
257: data[c][CURRENT_CNT_COLUMN] = 0;
258: data[c][AVG_COLUMN] = 0;
259: data[c][SUM_COLUMN] = 0;
260: data[c][TPS_COLUMN] = 0;
261: data[c][BYTES_COLUMN] = 0;
262: }
263:
264: totalAverageSum = 0;
265: resetStatistics = false;
266: }
267:
268: long totalMin = 0;
269: long totalMax = 0;
270: long totalBytes = 0;
271: long totalAvg = 0;
272: long totalSum = 0;
273: long totalLast = 0;
274:
275: long threadCount = loadTest.getThreadCount();
276:
277: for (int c = 0; c < holder.samples.length; c++) {
278: if (holder.sampleCounts[c] > 0) {
279: long sampleAvg = holder.samples[c]
280: / holder.sampleCounts[c];
281:
282: data[c][LAST_COLUMN] = sampleAvg;
283: data[c][CNT_COLUMN] += holder.sampleCounts[c];
284: data[c][CURRENT_CNT_COLUMN] += holder.sampleCounts[c];
285: data[c][SUM_COLUMN] += holder.samples[c];
286:
287: if (sampleAvg > 0
288: && (sampleAvg < data[c][MIN_COLUMN] || data[c][CNT_COLUMN] == 1))
289: data[c][MIN_COLUMN] = sampleAvg;
290:
291: if (sampleAvg > data[c][MAX_COLUMN])
292: data[c][MAX_COLUMN] = sampleAvg;
293:
294: float average = (float) data[c][SUM_COLUMN]
295: / (float) data[c][CURRENT_CNT_COLUMN];
296:
297: data[c][AVG_COLUMN] = (long) (average * 100);
298: data[c][BYTES_COLUMN] += holder.sizes[c];
299:
300: if (timePassed > 0) {
301: // data[c][BPS_COLUMN] = (data[c][BYTES_COLUMN]*1000) / timePassed;
302:
303: if (loadTest.getCalculateTPSOnTimePassed()) {
304: data[c][TPS_COLUMN] = (data[c][CURRENT_CNT_COLUMN] * 100000)
305: / timePassed;
306: data[c][BPS_COLUMN] = (data[c][BYTES_COLUMN] * 1000)
307: / timePassed;
308: } else {
309: data[c][TPS_COLUMN] = (long) (data[c][AVG_COLUMN] > 0 ? (100000F / average)
310: * threadCount
311: : 0);
312:
313: // long v = (data[c][TPS_COLUMN] * timePassed)/10000000;
314: // data[c][BPS_COLUMN] = v == 0 ? 0 : data[c][BYTES_COLUMN] / v;
315:
316: long avgBytes = data[c][CNT_COLUMN] == 0 ? 0
317: : data[c][BYTES_COLUMN]
318: / data[c][CNT_COLUMN];
319: data[c][BPS_COLUMN] = (avgBytes * data[c][TPS_COLUMN]) / 100;
320: }
321: }
322:
323: totalMin += data[c][MIN_COLUMN]
324: * holder.sampleCounts[c];
325: totalMax += data[c][MAX_COLUMN]
326: * holder.sampleCounts[c];
327: totalBytes += data[c][BYTES_COLUMN]
328: * holder.sampleCounts[c];
329: totalAvg += data[c][AVG_COLUMN]
330: * holder.sampleCounts[c];
331: totalSum += data[c][SUM_COLUMN]
332: * holder.sampleCounts[c];
333: totalLast += data[c][LAST_COLUMN]
334: * holder.sampleCounts[c];
335: } else {
336: totalMin += data[c][MIN_COLUMN];
337: totalMax += data[c][MAX_COLUMN];
338: totalBytes += data[c][BYTES_COLUMN];
339: }
340: }
341:
342: data[totalIndex][CNT_COLUMN]++;
343: data[totalIndex][CURRENT_CNT_COLUMN]++;
344:
345: totalAverageSum += totalLast * 100;
346: data[totalIndex][AVG_COLUMN] = (long) ((float) totalAverageSum / (float) data[totalIndex][CURRENT_CNT_COLUMN]);
347: data[totalIndex][BYTES_COLUMN] = totalBytes;
348:
349: if (timePassed > 0) {
350: if (loadTest.getCalculateTPSOnTimePassed()) {
351: data[totalIndex][TPS_COLUMN] = (data[totalIndex][CURRENT_CNT_COLUMN] * 100000)
352: / timePassed;
353: data[totalIndex][BPS_COLUMN] = (data[totalIndex][BYTES_COLUMN] * 1000)
354: / timePassed;
355: } else {
356: data[totalIndex][TPS_COLUMN] = (long) (data[totalIndex][AVG_COLUMN] > 0 ? (10000000F / data[totalIndex][AVG_COLUMN])
357: * threadCount
358: : 0);
359:
360: long avgBytes = data[totalIndex][CNT_COLUMN] == 0 ? 0
361: : data[totalIndex][BYTES_COLUMN]
362: / data[totalIndex][CNT_COLUMN];
363:
364: data[totalIndex][BPS_COLUMN] = (avgBytes * data[totalIndex][TPS_COLUMN]) / 100;
365: }
366: }
367:
368: data[totalIndex][MIN_COLUMN] = totalMin;
369: data[totalIndex][MAX_COLUMN] = totalMax;
370: data[totalIndex][SUM_COLUMN] = totalSum;
371: data[totalIndex][LAST_COLUMN] = totalLast;
372:
373: if (updateFrequency == 0)
374: fireTableDataChanged();
375: else
376: changed = true;
377:
378: adding = false;
379: }
380:
381: private final class Updater implements Runnable {
382: public void run() {
383: // check all these for catching threading issues
384: while (running || changed || !samplesStack.isEmpty()) {
385: if (changed) {
386: fireTableDataChanged();
387: changed = false;
388: }
389:
390: if (!running && samplesStack.isEmpty())
391: break;
392:
393: try {
394: Thread.sleep(updateFrequency < 1 ? 1000
395: : updateFrequency);
396: } catch (InterruptedException e) {
397: SoapUI.logError(e);
398: }
399: }
400: }
401: }
402:
403: private void stop() {
404: running = false;
405: }
406:
407: /**
408: * Collect testresult samples
409: *
410: * @author Ole.Matzura
411: */
412:
413: private class InternalTestRunListener extends
414: LoadTestRunListenerAdapter {
415: public void beforeLoadTest(LoadTestRunner loadTestRunner,
416: LoadTestRunContext context) {
417: running = true;
418: new Thread(updater, loadTestRunner.getLoadTest().getName()
419: + " LoadTestStatistics Updater").start();
420: new Thread(LoadTestStatistics.this ).start();
421:
422: currentThreadCountStartTime = System.currentTimeMillis();
423: totalAverageSum = 0;
424: }
425:
426: @Override
427: public void afterTestStep(LoadTestRunner loadTestRunner,
428: LoadTestRunContext context, TestRunner testRunner,
429: TestRunContext runContext, TestStepResult testStepResult) {
430: super .afterTestStep(loadTestRunner, context, testRunner,
431: runContext, testStepResult);
432: }
433:
434: public void afterTestCase(LoadTestRunner loadTestRunner,
435: LoadTestRunContext context, TestRunner testRunner,
436: TestRunContext runContext) {
437: List<TestStepResult> results = testRunner.getResults();
438: TestCase testCase = testRunner.getTestCase();
439:
440: long[] samples = new long[testCase.getTestStepCount()];
441: long[] sizes = new long[samples.length];
442: long[] sampleCounts = new long[samples.length];
443:
444: for (int c = 0; c < results.size(); c++) {
445: TestStepResult testStepResult = results.get(c);
446: if (testStepResult == null) {
447: log.warn("Result [" + c + "] is null in TestCase ["
448: + testCase.getName() + "]");
449: continue;
450: }
451:
452: int index = testCase.getIndexOfTestStep(testStepResult
453: .getTestStep());
454: sampleCounts[index]++;
455:
456: samples[index] += testStepResult.getTimeTaken();
457: sizes[index] += testStepResult.getSize();
458: }
459:
460: pushSamples(samples, sizes, sampleCounts, testRunner
461: .getStartTime(), testRunner.getTimeTaken());
462: }
463:
464: @Override
465: public void afterLoadTest(LoadTestRunner loadTestRunner,
466: LoadTestRunContext context) {
467: stop();
468: }
469: }
470:
471: public int getStepCount() {
472: return loadTest.getTestCase().getTestStepCount();
473: }
474:
475: public void reset() {
476: init();
477: fireTableDataChanged();
478: }
479:
480: public void release() {
481: reset();
482:
483: loadTest.removeLoadTestRunListener(testRunListener);
484: loadTest.getTestCase().getTestSuite().removeTestSuiteListener(
485: testSuiteListener);
486:
487: for (TestStep testStep : loadTest.getTestCase()
488: .getTestStepList()) {
489: testStep
490: .removePropertyChangeListener(propertyChangeListener);
491: }
492: }
493:
494: private class InternalTestSuiteListener extends
495: TestSuiteListenerAdapter {
496: public void testStepAdded(TestStep testStep, int index) {
497: if (testStep.getTestCase() == loadTest.getTestCase()) {
498: init();
499: testStep.addPropertyChangeListener(
500: TestStep.NAME_PROPERTY, propertyChangeListener);
501: fireTableDataChanged();
502:
503: history.reset();
504: }
505: }
506:
507: public void testStepRemoved(TestStep testStep, int index) {
508: if (testStep.getTestCase() == loadTest.getTestCase()) {
509: init();
510: testStep
511: .removePropertyChangeListener(propertyChangeListener);
512: fireTableDataChanged();
513:
514: history.reset();
515: }
516: }
517: }
518:
519: private class InternalPropertyChangeListener implements
520: PropertyChangeListener {
521: public void propertyChange(PropertyChangeEvent evt) {
522: if (evt.getSource() == loadTest
523: && evt.getPropertyName().equals(
524: WsdlLoadTest.THREADCOUNT_PROPERTY)) {
525: if (loadTest.getResetStatisticsOnThreadCountChange()) {
526: resetStatistics = true;
527: currentThreadCountStartTime = System
528: .currentTimeMillis();
529: }
530: } else if (evt.getPropertyName().equals(
531: TestStep.NAME_PROPERTY)
532: || evt.getPropertyName().equals(
533: TestStep.DISABLED_PROPERTY)) {
534: if (evt.getSource() instanceof TestStep)
535: fireTableCellUpdated(loadTest.getTestCase()
536: .getIndexOfTestStep(
537: (TestStep) evt.getSource()), 1);
538: } else if (evt.getPropertyName().equals(
539: WsdlLoadTest.HISTORYLIMIT_PROPERTY)) {
540: if (loadTest.getHistoryLimit() == 0)
541: history.reset();
542: }
543: }
544: }
545:
546: public TestStep getTestStepAtRow(int selectedRow) {
547: if (selectedRow < getRowCount() - 1)
548: return loadTest.getTestCase().getTestStepAt(selectedRow);
549: else
550: return null;
551: }
552:
553: public long getUpdateFrequency() {
554: return updateFrequency;
555: }
556:
557: public void setUpdateFrequency(long updateFrequency) {
558: this .updateFrequency = updateFrequency;
559: }
560:
561: public void addError(int stepIndex) {
562: if (stepIndex != -1) {
563: data[stepIndex][ERR_COLUMN]++;
564: }
565:
566: data[data.length - 1][ERR_COLUMN]++;
567: changed = true;
568: }
569:
570: public long[][] getSnapshot() {
571: return data.clone();
572: }
573:
574: private final static Map<Integer, Statistic> statisticIndexMap = new HashMap<Integer, Statistic>();
575:
576: private Updater updater = new Updater();
577:
578: public enum Statistic {
579: MININMUM(MIN_COLUMN, "min",
580: "the minimum measured teststep time"), MAXIMUM(
581: MAX_COLUMN, "max", "the maximum measured testste time"), AVERAGE(
582: AVG_COLUMN, "avg", "the average measured teststep time"), LAST(
583: LAST_COLUMN, "last", "the last measured teststep time"), COUNT(
584: CNT_COLUMN, "cnt",
585: "the number of teststep samples measured"), TPS(
586: TPS_COLUMN, "tps",
587: "the number of transactions per second for this teststep"), BYTES(
588: BYTES_COLUMN, "bytes",
589: "the total number of bytes returned by this teststep"), BPS(
590: BPS_COLUMN, "bps",
591: "the number of bytes per second returned by this teststep"), ERRORS(
592: ERR_COLUMN, "err",
593: "the total number of assertion errors for this teststep");
594:
595: private final String description;
596: private final String name;
597: private final int index;
598:
599: Statistic(int index, String name, String description) {
600: this .index = index;
601: this .name = name;
602: this .description = description;
603:
604: statisticIndexMap.put(index, this );
605: }
606:
607: public String getDescription() {
608: return description;
609: }
610:
611: public int getIndex() {
612: return index;
613: }
614:
615: public String getName() {
616: return name;
617: }
618:
619: public static Statistic forIndex(int column) {
620: return statisticIndexMap.get(column);
621: }
622: }
623:
624: /**
625: * Holds all sample values for a testcase run
626: * @author ole.matzura
627: */
628:
629: private static final class SamplesHolder {
630: private final long[] samples;
631: private final long[] sizes;
632: private final long[] sampleCounts;
633:
634: private long startTime;
635: private long timeTaken;
636:
637: public SamplesHolder(long[] samples, long[] sizes,
638: long[] sampleCounts, long startTime, long timeTaken) {
639: this .samples = samples;
640: this .sizes = sizes;
641: this .startTime = startTime;
642: this .timeTaken = timeTaken;
643: this .sampleCounts = sampleCounts;
644: }
645: }
646:
647: public synchronized void finish() {
648: int sz = samplesStack.size();
649: while (sz > 0) {
650: log.info("Waiting for " + sz + " samples..");
651: try {
652: Thread.sleep(500);
653: } catch (InterruptedException e) {
654: SoapUI.logError(e);
655: }
656: sz = samplesStack.size();
657: }
658: }
659: }
|