├── .gitignore
├── .travis.yml
├── LICENSE
├── NOTICE
├── README.md
├── pom.xml
└── src
├── main
├── java
│ ├── com
│ │ └── blazemeter
│ │ │ └── jmeter
│ │ │ └── debugger
│ │ │ ├── elements
│ │ │ ├── AbstractDebugElement.java
│ │ │ ├── AssertionDebug.java
│ │ │ ├── ControllerDebug.java
│ │ │ ├── ControllerDebugGui.java
│ │ │ ├── DebuggingThreadGroup.java
│ │ │ ├── DebuggingThreadGroupGui.java
│ │ │ ├── FullController.java
│ │ │ ├── GenericControllerDebug.java
│ │ │ ├── OriginalLink.java
│ │ │ ├── PostProcessorDebug.java
│ │ │ ├── PreProcessorDebug.java
│ │ │ ├── ReplaceableGenericControllerDebug.java
│ │ │ ├── SampleListenerDebug.java
│ │ │ ├── SamplerDebug.java
│ │ │ ├── ThreadGroupWrapper.java
│ │ │ ├── TimerDebug.java
│ │ │ └── Wrapper.java
│ │ │ ├── engine
│ │ │ ├── Debugger.java
│ │ │ ├── DebuggerEngine.java
│ │ │ ├── DebuggerFrontend.java
│ │ │ ├── DebuggingThread.java
│ │ │ ├── SearchClass.java
│ │ │ ├── SearchParentClass.java
│ │ │ ├── StepTrigger.java
│ │ │ ├── TestTreeProvider.java
│ │ │ └── TreeClonerTG.java
│ │ │ ├── gui
│ │ │ ├── BlazeMeterLogo.java
│ │ │ ├── ComponentFinder.java
│ │ │ ├── DebuggerDialog.java
│ │ │ ├── DebuggerDialogBase.java
│ │ │ ├── DebuggerMenuCreator.java
│ │ │ ├── DebuggerMenuItem.java
│ │ │ ├── EvaluatePanel.java
│ │ │ ├── FixedJMeterTreeCellRenderer.java
│ │ │ ├── HighlightTable.java
│ │ │ ├── HighlightTableModel.java
│ │ │ ├── LoggerPanelWrapping.java
│ │ │ ├── NodeHighlighter.java
│ │ │ └── ThreadGroupItemRenderer.java
│ │ │ └── logging
│ │ │ └── LoggerPanelAppender.java
│ └── org
│ │ └── apache
│ │ └── jmeter
│ │ └── threads
│ │ └── JMeterContextServiceAccessor.java
└── resources
│ └── com
│ └── blazemeter
│ └── jmeter
│ └── debugger
│ ├── breakpoint.png
│ ├── bug.png
│ ├── bug22.png
│ ├── continue.png
│ ├── help.png
│ ├── logo.png
│ ├── pause.png
│ ├── start.png
│ ├── step.png
│ └── stop.png
└── test
├── java
└── com
│ └── blazemeter
│ └── jmeter
│ └── debugger
│ ├── FrontendMock.java
│ ├── StepTriggerCounter.java
│ ├── TestProvider.java
│ ├── elements
│ └── DebuggingThreadGroupGuiTest.java
│ ├── engine
│ ├── DebuggerEngineTest.java
│ ├── DebuggerTest.java
│ └── DebuggingThreadTest.java
│ └── gui
│ ├── DebuggerDialogTest.java
│ └── DebuggerMenuCreatorTest.java
└── resources
└── com
└── blazemeter
└── jmeter
└── debugger
├── debug.jmx
├── loops.jmx
├── sample1.jmx
└── vars.jmx
/.gitignore:
--------------------------------------------------------------------------------
1 | /jmeter.log
2 | /.idea/
3 | /target/
4 | *.iml
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: java
3 | jdk:
4 | - openjdk8
5 | install: "mvn -Dmaven.test.skip=true clean install --batch-mode"
6 | script: "mvn -Djava.awt.headless=true -Dmaven.test.redirectTestOutputToFile=true -Dcobertura.report.format=xml --fail-at-end --batch-mode cobertura:cobertura test "
7 | after_success:
8 | - bash <(curl -s https://codecov.io/bash)
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Step-by-step Debugger for Apache JMeter™
2 |
3 | Copyright 2016 BlazeMeter Inc.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Step-by-step debugger for Apache JMeter
2 |
3 | 
4 |
5 | Implemented by [BlazeMeter.com](http://blazemeter.com/), released under Apache 2.0 License
6 |
7 | ## Features
8 | - step over single component
9 | - breakpoints and running until paused
10 | - current values for variables and properties shown
11 | - evaluate expressions pane
12 | - resolved execution tree displayed
13 | - current element and current sampler scope highlighted in the tree
14 | - element UI shown with resolved variables and functions
15 |
16 | ## Installation
17 |
18 | Install it through [JMeter Plugins Manager](http://jmeter-plugins.org/wiki/PluginsManager/). Or checkout the source and build with `mvn clean package`, then take the JAR from `target` directory.
19 |
20 | ## Usage
21 |
22 | - Find it under "Run" item of main menu. Consider saving your JMX before starting debugger.
23 | - Choose the thread group to debug from combo-box.
24 | - Press "Start" to start debugging, "Stop" to abort it.
25 | - Use "Step Over" or "Continue/Pause" to perform debugging.
26 | - Right-click on the element in tree allows to set breakpoint (if appliable to the element).
27 | - Type expression on "Evaluate" tab and execute it, you can even set variables and properties by using appropriate JMeter functions.
28 |
29 | ## Known Limitations
30 | - Only one thread group at a time can be debugged (evaluate panel allows to mitigate the limitation)
31 | - Module Controllers and Include Controllers are not supported
32 |
33 | ## Support
34 |
35 | The best place to report issues and discuss the debugger is jmeter-plugins [support forums](https://groups.google.com/forum/#!forum/jmeter-plugins/).
36 |
37 | # Changelog
38 |
39 | __v0.6__ (upcoming)
40 | - fix variables in assertions and controllers
41 |
42 | __v0.5__ 22 sep 2017
43 | - add last sampler result tab
44 | - choose TG from context of tree
45 | - fix modification thread groups
46 | - fix italic font in tree not shown on Mac
47 | - fix blue color for current element is blue-on-blue for Mac
48 | - fix logging lab for old jmeter versions
49 | - fix clear data in info tabs and listeners
50 |
51 | __v0.4__ 24 jul 2017
52 | - migrate logging to SLF4J
53 | - fix variables in TestElement.name
54 | - fix variable in propMap
55 | - fix disable original components
56 | - fix changing tree model in debbugger after test started
57 | - fix changing original tree model after closing debbugger
58 |
59 | __v0.3__, 21 jun 2016
60 | - handle Transaction Controller
61 | - expand sampler path in tree to reveal it
62 | - logic to stop on first Sampler Scope element for Sampler Breakpoint
63 |
64 | __v0.2__, 13 jun 2016
65 | - disable variables/properties/evaluate update on continue, to prevent deadlock
66 | - fix breakpoint functioning
67 | - remove controls from DBG TG UI (action on error)
68 | - add icon on toolbar (keyboard shortcut is not possible, sorry)
69 |
70 | __v0.1__, 7 jun 2016
71 | - initial release
72 | - disabled variables change highlight
73 |
74 | ## Roadmap
75 |
76 | 
77 | 
78 |
79 | - logic to stop on Listener bp only once after sample
80 | - choose TG from context of tree and show debugger started?
81 | - revive Stop button?
82 | - Maximize on Windows / Non modal window (what to do with all the collision cases?)
83 |
84 | - italic font in tree not shown on Mac
85 | - blue color for current element is blue-on-blue for Mac
86 | - shortcut to toggle BP
87 |
88 |
89 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.blazemeter
8 | jmeter-debugger
9 | 0.6
10 |
11 | Step-by-Step Debugger for JMeter
12 | Step-by-Step Debugger for JMeter
13 |
14 |
15 | The Apache Software License, Version 2.0
16 | http://www.apache.org/licenses/LICENSE-2.0.txt
17 | repo
18 |
19 |
20 | http://blazemeter.com/
21 |
22 | https://github.com/Blazemeter/jmeter-debugger
23 | https://github.com/Blazemeter/jmeter-debugger.git
24 | git@github.com:Blazemeter/jmeter-debugger.git
25 |
26 |
27 |
28 | team
29 | blazemeter.com
30 | support@blazemeter.com
31 |
32 |
33 |
34 |
35 | UTF-8
36 | 1.7
37 | 1.7
38 |
39 |
40 |
41 |
42 | org.apache.jmeter
43 | ApacheJMeter_core
44 | 2.13
45 |
46 |
47 | commons-math3
48 | commons-math3
49 |
50 |
51 | commons-pool2
52 | commons-pool2
53 |
54 |
55 |
56 |
57 | org.apache.jmeter
58 | ApacheJMeter_http
59 | 2.13
60 | test
61 |
62 |
63 | commons-math3
64 | commons-math3
65 |
66 |
67 | commons-pool2
68 | commons-pool2
69 |
70 |
71 |
72 |
73 | org.apache.jmeter
74 | ApacheJMeter_java
75 | 2.13
76 | test
77 |
78 |
79 | commons-math3
80 | commons-math3
81 |
82 |
83 | commons-pool2
84 | commons-pool2
85 |
86 |
87 |
88 |
89 | org.apache.jmeter
90 | ApacheJMeter_components
91 | 2.13
92 |
93 |
94 | commons-math3
95 | commons-math3
96 |
97 |
98 | commons-pool2
99 | commons-pool2
100 |
101 |
102 |
103 |
104 | org.apache.jmeter
105 | ApacheJMeter_functions
106 | 2.13
107 | test
108 |
109 |
110 | commons-math3
111 | commons-math3
112 |
113 |
114 | commons-pool2
115 | commons-pool2
116 |
117 |
118 |
119 |
120 | kg.apc
121 | jmeter-plugins-emulators
122 | 0.2
123 | test
124 |
125 |
126 |
127 | org.apache.logging.log4j
128 | log4j-api
129 | 2.8.2
130 |
131 |
132 | org.apache.logging.log4j
133 | log4j-core
134 | 2.15.0
135 |
136 |
137 | org.apache.logging.log4j
138 | log4j-slf4j-impl
139 | 2.8.2
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/AbstractDebugElement.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 |
4 | import com.blazemeter.jmeter.debugger.engine.DebuggerEngine;
5 | import com.blazemeter.jmeter.debugger.engine.StepTrigger;
6 | import org.apache.jmeter.engine.StandardJMeterEngine;
7 | import org.apache.jmeter.engine.event.LoopIterationEvent;
8 | import org.apache.jmeter.engine.event.LoopIterationListener;
9 | import org.apache.jmeter.testbeans.TestBean;
10 | import org.apache.jmeter.testbeans.TestBeanHelper;
11 | import org.apache.jmeter.testelement.AbstractTestElement;
12 | import org.apache.jmeter.testelement.TestElement;
13 | import org.apache.jmeter.testelement.TestIterationListener;
14 | import org.apache.jmeter.testelement.ThreadListener;
15 | import org.apache.jmeter.threads.JMeterContextService;
16 |
17 | public abstract class AbstractDebugElement extends AbstractTestElement implements Wrapper, OriginalLink, LoopIterationListener, TestIterationListener, ThreadListener {
18 | protected T wrapped;
19 | private T original;
20 |
21 | @Override
22 | public void setWrappedElement(T wrapped) {
23 | this.wrapped = wrapped;
24 | }
25 |
26 | public T getWrappedElement() {
27 | return wrapped;
28 | }
29 |
30 | protected StepTrigger getHook() {
31 | StandardJMeterEngine engine = JMeterContextService.getContext().getEngine();
32 | if (engine instanceof DebuggerEngine) {
33 | return ((DebuggerEngine) engine).getStepper();
34 | }
35 | throw new IllegalStateException();
36 | }
37 |
38 | @Override
39 | public void addTestElement(TestElement el) {
40 | if (wrapped instanceof TestElement) {
41 | ((TestElement) wrapped).addTestElement(el);
42 | }
43 | }
44 |
45 | protected void prepareBean() {
46 | if (wrapped instanceof TestBean) {
47 | //noinspection deprecation
48 | TestBeanHelper.prepare((TestElement) wrapped); // the deprecation reason is not sufficient
49 | }
50 | }
51 |
52 | @Override
53 | public void setOriginal(T orig) {
54 | original = orig;
55 | }
56 |
57 | @Override
58 | public T getOriginal() {
59 | return original;
60 | }
61 |
62 | @Override
63 | public void iterationStart(LoopIterationEvent iterEvent) {
64 | if (wrapped instanceof LoopIterationListener) {
65 | ((LoopIterationListener) wrapped).iterationStart(iterEvent);
66 | }
67 | }
68 |
69 | @Override
70 | public void testIterationStart(LoopIterationEvent event) {
71 | if (wrapped instanceof TestIterationListener) {
72 | ((TestIterationListener) wrapped).testIterationStart(event);
73 | }
74 | }
75 |
76 | @Override
77 | public void threadStarted() {
78 | if (wrapped instanceof ThreadListener) {
79 | ((ThreadListener) wrapped).threadStarted();
80 | }
81 | }
82 |
83 | @Override
84 | public void threadFinished() {
85 | if (wrapped instanceof ThreadListener) {
86 | ((ThreadListener) wrapped).threadFinished();
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/AssertionDebug.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.assertions.Assertion;
4 | import org.apache.jmeter.assertions.AssertionResult;
5 | import org.apache.jmeter.samplers.SampleResult;
6 | import org.apache.jmeter.testelement.TestElement;
7 | import org.apache.jmeter.testelement.property.JMeterProperty;
8 |
9 |
10 | public class AssertionDebug extends AbstractDebugElement implements Assertion {
11 |
12 | @Override
13 | public void setProperty(JMeterProperty property) {
14 | super.setProperty(property);
15 | if (wrapped != null && wrapped instanceof TestElement) {
16 | ((TestElement) wrapped).setProperty(property);
17 | }
18 | }
19 |
20 | @Override
21 | public AssertionResult getResult(SampleResult sampleResult) {
22 | prepareBean();
23 | getHook().stepOn(this);
24 | return wrapped.getResult(sampleResult);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/ControllerDebug.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.control.Controller;
4 | import org.apache.jmeter.engine.event.LoopIterationEvent;
5 | import org.apache.jmeter.engine.event.LoopIterationListener;
6 | import org.apache.jmeter.samplers.Sampler;
7 | import org.apache.jmeter.testelement.TestElement;
8 | import org.apache.jmeter.testelement.TestIterationListener;
9 | import org.apache.jmeter.testelement.TestStateListener;
10 | import org.apache.jmeter.testelement.property.JMeterProperty;
11 | import org.apache.jmeter.threads.TestCompilerHelper;
12 |
13 |
14 | public class ControllerDebug extends AbstractDebugElement implements FullController {
15 |
16 |
17 | @Override
18 | public Sampler next() {
19 | getHook().stepOn(this);
20 | return wrapped.next();
21 | }
22 |
23 | @Override
24 | public boolean isDone() {
25 | return wrapped.isDone();
26 | }
27 |
28 | @Override
29 | public void addIterationListener(LoopIterationListener listener) {
30 | prepareBean();
31 | wrapped.addIterationListener(listener);
32 | }
33 |
34 | @Override
35 | public void initialize() {
36 | wrapped.initialize();
37 | }
38 |
39 | @Override
40 | public void removeIterationListener(LoopIterationListener iterationListener) {
41 | wrapped.removeIterationListener(iterationListener);
42 | }
43 |
44 | @Override
45 | public void triggerEndOfLoop() {
46 | wrapped.triggerEndOfLoop();
47 | }
48 |
49 | @Override
50 | public boolean addTestElementOnce(TestElement child) {
51 | if (wrapped instanceof TestCompilerHelper) {
52 | TestCompilerHelper wrapped = (TestCompilerHelper) this.wrapped;
53 | return wrapped.addTestElementOnce(child);
54 | }
55 |
56 | return false;
57 | }
58 |
59 | @Override
60 | public void iterationStart(LoopIterationEvent iterEvent) {
61 | if (wrapped instanceof LoopIterationListener) {
62 | ((LoopIterationListener) wrapped).iterationStart(iterEvent);
63 | }
64 | }
65 |
66 | @Override
67 | public void testIterationStart(LoopIterationEvent event) {
68 | if (wrapped instanceof TestIterationListener) {
69 | ((TestIterationListener) wrapped).testIterationStart(event);
70 | }
71 | }
72 |
73 | @Override
74 | public void testStarted() {
75 | if (wrapped instanceof TestStateListener) {
76 | ((TestStateListener) wrapped).testStarted();
77 | }
78 | }
79 |
80 | @Override
81 | public void testStarted(String host) {
82 | if (wrapped instanceof TestStateListener) {
83 | ((TestStateListener) wrapped).testStarted(host);
84 | }
85 | }
86 |
87 | @Override
88 | public void testEnded() {
89 | if (wrapped instanceof TestStateListener) {
90 | ((TestStateListener) wrapped).testEnded();
91 | }
92 | }
93 |
94 | @Override
95 | public void testEnded(String host) {
96 | if (wrapped instanceof TestStateListener) {
97 | ((TestStateListener) wrapped).testEnded(host);
98 | }
99 | }
100 |
101 | @Override
102 | public void setProperty(JMeterProperty property) {
103 | super.setProperty(property);
104 | wrapped.setProperty(property);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/ControllerDebugGui.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.control.Controller;
4 | import org.apache.jmeter.control.gui.AbstractControllerGui;
5 | import org.apache.jmeter.gui.util.MenuFactory;
6 | import org.apache.jmeter.testelement.TestElement;
7 |
8 | import javax.swing.*;
9 | import java.util.ArrayList;
10 | import java.util.Collection;
11 |
12 | public class ControllerDebugGui extends AbstractControllerGui implements OriginalLink {
13 | private Controller original;
14 |
15 | @Override
16 | public String getLabelResource() {
17 | return getClass().getCanonicalName();
18 | }
19 |
20 | @Override
21 | public String getStaticLabel() {
22 | return getClass().getCanonicalName();
23 | }
24 |
25 | @Override
26 | public TestElement createTestElement() {
27 | return new DebuggingThreadGroup();
28 | }
29 |
30 | @Override
31 | public void modifyTestElement(TestElement element) {
32 | }
33 |
34 | @Override
35 | public JPopupMenu createPopupMenu() {
36 | return MenuFactory.getDefaultMenu();
37 | }
38 |
39 | @Override
40 | public Collection getMenuCategories() {
41 | return new ArrayList<>();
42 | }
43 |
44 | @Override
45 | public Controller getOriginal() {
46 | return original;
47 | }
48 |
49 | @Override
50 | public void setOriginal(Controller orig) {
51 | original = orig;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/DebuggingThreadGroup.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import com.blazemeter.jmeter.debugger.engine.DebuggerEngine;
4 | import com.blazemeter.jmeter.debugger.engine.DebuggingThread;
5 | import org.apache.jmeter.control.LoopController;
6 | import org.apache.jmeter.engine.StandardJMeterEngine;
7 | import org.apache.jmeter.threads.JMeterContext;
8 | import org.apache.jmeter.threads.JMeterContextService;
9 | import org.apache.jmeter.threads.ListenerNotifier;
10 | import org.apache.jmeter.threads.ThreadGroup;
11 | import org.apache.jmeter.util.JMeterUtils;
12 | import org.apache.jorphan.collections.ListedHashTree;
13 | import org.slf4j.Logger;
14 | import org.slf4j.LoggerFactory;
15 |
16 | public class DebuggingThreadGroup extends ThreadGroup implements OriginalLink {
17 | private static final Logger log = LoggerFactory.getLogger(DebuggingThreadGroup.class);
18 |
19 | private Thread osThread;
20 | private DebuggingThread jmeterThread;
21 | private final long waitTime = JMeterUtils.getPropDefault("jmeterengine.threadstop.wait", 5 * 1000);
22 | private boolean stopping = false;
23 | private ThreadGroup original;
24 |
25 |
26 | public DebuggingThreadGroup() {
27 | super();
28 | setDelay(0);
29 | setNumThreads(1);
30 | setRampUp(0);
31 | LoopController ctl = new LoopController();
32 | ctl.setContinueForever(true);
33 | ctl.setLoops(-1);
34 | setSamplerController(ctl);
35 | }
36 |
37 | @Override
38 | public void start(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine) {
39 | JMeterContext context = JMeterContextService.getContext();
40 | DebuggingThread jmThread = makeThread(groupCount, notifier, threadGroupTree, engine, 0, context);
41 | Thread newThread = new Thread(jmThread, jmThread.getThreadName());
42 | if (engine instanceof DebuggerEngine) {
43 | DebuggerEngine dbgEngine = (DebuggerEngine) engine;
44 | dbgEngine.setTarget(jmThread);
45 | dbgEngine.setThread(newThread);
46 |
47 | this.jmeterThread = jmThread;
48 | this.osThread = newThread;
49 | }
50 | newThread.start();
51 | }
52 |
53 | private DebuggingThread makeThread(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine, int i, JMeterContext context) {
54 | // had to copy whole method because of this line
55 | DebuggingThread jmeterThread = new DebuggingThread(threadGroupTree, this, notifier, context);
56 |
57 | boolean onErrorStopTest = getOnErrorStopTest();
58 | boolean onErrorStopTestNow = getOnErrorStopTestNow();
59 | boolean onErrorStopThread = getOnErrorStopThread();
60 | boolean onErrorStartNextLoop = getOnErrorStartNextLoop();
61 | String groupName = getName();
62 |
63 | jmeterThread.setThreadNum(i);
64 | jmeterThread.setThreadGroup(this);
65 | jmeterThread.setInitialContext(context);
66 | String threadName = groupName + " " + (groupCount) + "-" + (i + 1);
67 | jmeterThread.setThreadName(threadName);
68 | jmeterThread.setEngine(engine);
69 | jmeterThread.setOnErrorStopTest(onErrorStopTest);
70 | jmeterThread.setOnErrorStopTestNow(onErrorStopTestNow);
71 | jmeterThread.setOnErrorStopThread(onErrorStopThread);
72 | jmeterThread.setOnErrorStartNextLoop(onErrorStartNextLoop);
73 | return jmeterThread;
74 | }
75 |
76 | @Override
77 | public void waitThreadsStopped() {
78 | super.waitThreadsStopped();
79 | if (osThread != null) {
80 | while (osThread.isAlive()) {
81 | if (stopping) {
82 | log.debug("Interrupting thread: " + osThread);
83 | osThread.interrupt();
84 | }
85 |
86 | log.debug("Joining thread 1: " + osThread + " " + stopping + " " + osThread.isInterrupted());
87 |
88 | try {
89 | osThread.join(waitTime);
90 | } catch (InterruptedException e) {
91 | log.debug("Interrupted: " + e);
92 | break;
93 | }
94 | }
95 | log.debug("Thread is done: " + osThread);
96 | }
97 | }
98 |
99 | @Override
100 | public void tellThreadsToStop() {
101 | stopping = true;
102 | super.tellThreadsToStop();
103 | if (jmeterThread != null) {
104 | log.debug("Interrupting JMeter thread: " + jmeterThread);
105 | jmeterThread.interrupt();
106 | }
107 |
108 | if (osThread != null) {
109 | log.debug("Interrupting OS thread: " + osThread);
110 | osThread.interrupt();
111 | }
112 | }
113 |
114 | @Override
115 | public boolean verifyThreadsStopped() {
116 | boolean stopped = super.verifyThreadsStopped();
117 | if (osThread != null) {
118 | if (osThread.isAlive()) {
119 | log.debug("Joining thread 2: " + osThread);
120 | try {
121 | osThread.join(waitTime);
122 | } catch (InterruptedException e) {
123 | log.debug("Interrupted: " + e);
124 | }
125 | if (osThread.isAlive()) {
126 | stopped = false;
127 | log.warn("Thread didn't exit: " + osThread);
128 | }
129 | }
130 | }
131 | return stopped;
132 | }
133 |
134 | @Override
135 | public ThreadGroup getOriginal() {
136 | return original;
137 | }
138 |
139 | @Override
140 | public void setOriginal(ThreadGroup orig) {
141 | original = orig;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/DebuggingThreadGroupGui.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.gui.util.MenuFactory;
4 | import org.apache.jmeter.testelement.TestElement;
5 | import org.apache.jmeter.threads.gui.AbstractThreadGroupGui;
6 |
7 | import javax.swing.*;
8 | import java.awt.*;
9 | import java.util.ArrayList;
10 | import java.util.Collection;
11 |
12 | public class DebuggingThreadGroupGui extends AbstractThreadGroupGui {
13 | public DebuggingThreadGroupGui() {
14 | super();
15 |
16 | removeAll();
17 |
18 | setLayout(new BorderLayout(0, 5));
19 | setBorder(makeBorder());
20 |
21 | Box box = Box.createVerticalBox();
22 | box.add(makeTitlePanel());
23 | add(box, BorderLayout.NORTH);
24 | }
25 |
26 | @Override
27 | public String getLabelResource() {
28 | return getClass().getCanonicalName();
29 | }
30 |
31 | @Override
32 | public String getStaticLabel() {
33 | return "Debugging Thread Group";
34 | }
35 |
36 | @Override
37 | public TestElement createTestElement() {
38 | return new DebuggingThreadGroup();
39 | }
40 |
41 | @Override
42 | public void modifyTestElement(TestElement element) {
43 | }
44 |
45 | @Override
46 | public JPopupMenu createPopupMenu() {
47 | return MenuFactory.getDefaultMenu();
48 | }
49 |
50 | @Override
51 | public Collection getMenuCategories() {
52 | return new ArrayList<>();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/FullController.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 |
4 | import org.apache.jmeter.control.Controller;
5 | import org.apache.jmeter.engine.event.LoopIterationListener;
6 | import org.apache.jmeter.testelement.TestIterationListener;
7 | import org.apache.jmeter.testelement.TestStateListener;
8 | import org.apache.jmeter.threads.TestCompilerHelper;
9 |
10 | public interface FullController extends
11 | Controller,
12 | TestCompilerHelper,
13 | LoopIterationListener,
14 | TestIterationListener,
15 | TestStateListener {
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/GenericControllerDebug.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 |
4 | import org.apache.jmeter.control.GenericController;
5 | import org.apache.jmeter.engine.event.LoopIterationEvent;
6 | import org.apache.jmeter.engine.event.LoopIterationListener;
7 | import org.apache.jmeter.samplers.Sampler;
8 | import org.apache.jmeter.testelement.TestElement;
9 | import org.apache.jmeter.testelement.TestIterationListener;
10 | import org.apache.jmeter.testelement.TestStateListener;
11 | import org.apache.jmeter.testelement.property.JMeterProperty;
12 | import org.apache.jmeter.threads.TestCompilerHelper;
13 |
14 | public class GenericControllerDebug extends GenericController implements FullController, Wrapper, OriginalLink {
15 | private final AbstractDebugElement helper = new AbstractDebugElement() {
16 | };
17 | protected GenericController wrapped;
18 | private GenericController original;
19 |
20 | @Override
21 | public Sampler next() {
22 | helper.getHook().stepOn(this);
23 | return wrapped.next();
24 | }
25 |
26 | @Override
27 | public boolean isDone() {
28 | return wrapped.isDone();
29 | }
30 |
31 | @Override
32 | public void addIterationListener(LoopIterationListener listener) {
33 | helper.prepareBean();
34 | wrapped.addIterationListener(listener);
35 | }
36 |
37 | @Override
38 | public void initialize() {
39 | wrapped.initialize();
40 | }
41 |
42 | @Override
43 | public void removeIterationListener(LoopIterationListener iterationListener) {
44 | wrapped.removeIterationListener(iterationListener);
45 | }
46 |
47 | @Override
48 | public void triggerEndOfLoop() {
49 | wrapped.triggerEndOfLoop();
50 | }
51 |
52 | @Override
53 | public void addTestElement(TestElement child) {
54 | StackTraceElement[] stack = Thread.currentThread().getStackTrace();
55 | if (stack[2].getMethodName().equals("addTestElementOnce")) { // final method with no reason, again
56 | TestCompilerHelper wrapped = this.wrapped;
57 | wrapped.addTestElementOnce(child);
58 | } else {
59 | wrapped.addTestElement(child);
60 | }
61 | }
62 |
63 | @Override
64 | public void iterationStart(LoopIterationEvent iterEvent) {
65 | if (wrapped instanceof LoopIterationListener) {
66 | ((LoopIterationListener) wrapped).iterationStart(iterEvent);
67 | }
68 | }
69 |
70 | @Override
71 | public void testIterationStart(LoopIterationEvent event) {
72 | if (wrapped instanceof TestIterationListener) {
73 | ((TestIterationListener) wrapped).testIterationStart(event);
74 | }
75 | }
76 |
77 | @Override
78 | public void testStarted() {
79 | if (wrapped instanceof TestStateListener) {
80 | ((TestStateListener) wrapped).testStarted();
81 | }
82 | }
83 |
84 | @Override
85 | public void testStarted(String host) {
86 | if (wrapped instanceof TestStateListener) {
87 | ((TestStateListener) wrapped).testStarted(host);
88 | }
89 | }
90 |
91 | @Override
92 | public void testEnded() {
93 | if (wrapped instanceof TestStateListener) {
94 | ((TestStateListener) wrapped).testEnded();
95 | }
96 | }
97 |
98 | @Override
99 | public void testEnded(String host) {
100 | if (wrapped instanceof TestStateListener) {
101 | ((TestStateListener) wrapped).testEnded(host);
102 | }
103 | }
104 |
105 | @Override
106 | public GenericController getWrappedElement() {
107 | return wrapped;
108 | }
109 |
110 | @Override
111 | public void setWrappedElement(GenericController te) {
112 | this.wrapped = te;
113 | }
114 |
115 | @Override
116 | public GenericController getOriginal() {
117 | return original;
118 |
119 | }
120 |
121 | @Override
122 | public void setOriginal(GenericController orig) {
123 | original = orig;
124 | }
125 |
126 | @Override
127 | public void setProperty(JMeterProperty property) {
128 | super.setProperty(property);
129 | wrapped.setProperty(property);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/OriginalLink.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | public interface OriginalLink {
4 | T getOriginal();
5 |
6 | void setOriginal(T orig);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/PostProcessorDebug.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.processor.PostProcessor;
4 |
5 | public class PostProcessorDebug extends AbstractDebugElement implements PostProcessor {
6 |
7 |
8 | @Override
9 | public void process() {
10 | prepareBean();
11 | getHook().stepOn(this);
12 | wrapped.process();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/PreProcessorDebug.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.processor.PreProcessor;
4 |
5 | public class PreProcessorDebug extends AbstractDebugElement implements PreProcessor {
6 |
7 | @Override
8 | public void process() {
9 | prepareBean();
10 | getHook().stepOn(this);
11 | wrapped.process();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/ReplaceableGenericControllerDebug.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.control.ReplaceableController;
4 | import org.apache.jmeter.gui.tree.JMeterTreeNode;
5 | import org.apache.jorphan.collections.HashTree;
6 |
7 | public class ReplaceableGenericControllerDebug extends GenericControllerDebug implements ReplaceableController {
8 |
9 | @Override
10 | public HashTree getReplacementSubTree() {
11 | if (wrapped instanceof ReplaceableController) {
12 | return ((ReplaceableController) wrapped).getReplacementSubTree();
13 | }
14 | return null;
15 | }
16 |
17 | @Override
18 | public void resolveReplacementSubTree(JMeterTreeNode context) {
19 | if (wrapped instanceof ReplaceableController) {
20 | ((ReplaceableController) wrapped).resolveReplacementSubTree(context);
21 | }
22 | }
23 |
24 | @SuppressWarnings("CloneDoesntCallSuperClone")
25 | @Override
26 | public Object clone() {
27 | if (wrapped.getClass().getName().equals("org.apache.jmeter.control.ModuleController")) {
28 | return this;
29 | }
30 | GenericControllerDebug gcd = new GenericControllerDebug();
31 | gcd.setWrappedElement(wrapped);
32 | return gcd;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/SampleListenerDebug.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.samplers.SampleEvent;
4 | import org.apache.jmeter.samplers.SampleListener;
5 | import org.apache.jmeter.samplers.SampleResult;
6 | import org.apache.jmeter.visualizers.Visualizer;
7 |
8 | public class SampleListenerDebug extends AbstractDebugElement implements SampleListener, Visualizer {
9 | @Override
10 | public void sampleOccurred(SampleEvent e) {
11 | prepareBean();
12 | getHook().stepOn(this);
13 | wrapped.sampleOccurred(e);
14 | getHook().stepOn(this); // special case for reporters to see the result
15 | }
16 |
17 | @Override
18 | public void sampleStarted(SampleEvent e) {
19 | wrapped.sampleStarted(e);
20 | }
21 |
22 | @Override
23 | public void sampleStopped(SampleEvent e) {
24 | wrapped.sampleStopped(e);
25 | }
26 |
27 | @Override
28 | public void add(SampleResult sample) {
29 | if (wrapped instanceof Visualizer) {
30 | ((Visualizer) wrapped).add(sample);
31 | }
32 | }
33 |
34 | @Override
35 | public boolean isStats() {
36 | if (wrapped instanceof Visualizer) {
37 | return ((Visualizer) wrapped).isStats();
38 | }
39 | return false;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/SamplerDebug.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.samplers.Entry;
4 | import org.apache.jmeter.samplers.SampleResult;
5 | import org.apache.jmeter.samplers.Sampler;
6 | import org.apache.jmeter.testelement.TestElement;
7 | import org.apache.jmeter.testelement.property.JMeterProperty;
8 | import org.apache.jmeter.threads.JMeterContext;
9 |
10 |
11 | public class SamplerDebug extends AbstractDebugElement implements Sampler {
12 |
13 | @Override
14 | public SampleResult sample(Entry e) {
15 | prepareBean();
16 | getHook().stepOn(this);
17 | return wrapped.sample(e);
18 | }
19 |
20 | @Override
21 | public void setThreadContext(JMeterContext inthreadContext) {
22 | wrapped.setThreadContext(inthreadContext);
23 | }
24 |
25 | @Override
26 | public void setThreadName(String inthreadName) {
27 | wrapped.setThreadName(inthreadName);
28 | }
29 |
30 | @Override
31 | public void setProperty(JMeterProperty property) {
32 | super.setProperty(property);
33 | if (wrapped != null && !TestElement.GUI_CLASS.equals(property.getName())) {
34 | wrapped.setProperty(property);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/ThreadGroupWrapper.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.threads.AbstractThreadGroup;
4 |
5 | public class ThreadGroupWrapper extends AbstractDebugElement {
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/TimerDebug.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 | import org.apache.jmeter.timers.Timer;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | public class TimerDebug extends AbstractDebugElement implements Timer {
8 | private static final Logger log = LoggerFactory.getLogger(TimerDebug.class);
9 |
10 |
11 |
12 | private boolean isDelaying = false;
13 |
14 | @Override
15 | public long delay() {
16 | prepareBean();
17 | getHook().stepOn(this);
18 | long delay = wrapped.delay();
19 | if (isDelaying) {
20 | return delay;
21 | } else {
22 | log.debug("Drop delay because of debug: " + delay);
23 | return 0;
24 | }
25 | }
26 |
27 | public void setDelaying(boolean delaying) {
28 | isDelaying = delaying;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/elements/Wrapper.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.elements;
2 |
3 |
4 | import org.apache.jmeter.testelement.TestElement;
5 |
6 | public interface Wrapper extends TestElement {
7 | T getWrappedElement();
8 |
9 | void setWrappedElement(T wrapped);
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/engine/Debugger.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.engine;
2 |
3 |
4 | import com.blazemeter.jmeter.debugger.elements.OriginalLink;
5 | import com.blazemeter.jmeter.debugger.elements.TimerDebug;
6 | import com.blazemeter.jmeter.debugger.elements.Wrapper;
7 | import org.apache.jmeter.JMeter;
8 | import org.apache.jmeter.engine.JMeterEngineException;
9 | import org.apache.jmeter.engine.StandardJMeterEngine;
10 | import org.apache.jmeter.gui.GuiPackage;
11 | import org.apache.jmeter.gui.tree.JMeterTreeNode;
12 | import org.apache.jmeter.samplers.Sampler;
13 | import org.apache.jmeter.testelement.TestElement;
14 | import org.apache.jmeter.testelement.TestStateListener;
15 | import org.apache.jmeter.threads.AbstractThreadGroup;
16 | import org.apache.jmeter.threads.JMeterContext;
17 | import org.apache.jmeter.threads.JMeterContextService;
18 | import org.apache.jmeter.threads.JMeterContextServiceAccessor;
19 | import org.apache.jorphan.collections.HashTree;
20 | import org.apache.jorphan.util.JMeterStopThreadException;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | public class Debugger implements StepTrigger {
25 | private static final Logger log = LoggerFactory.getLogger(Debugger.class);
26 | private final DebuggerFrontend frontend;
27 | private final TestTreeProvider treeProvider;
28 | private TreeClonerTG cloner;
29 | private boolean stopping;
30 | private Wrapper currentElement;
31 | private boolean isContinuing = false;
32 | protected DebuggerEngine engine;
33 | private Sampler lastKnownSampler;
34 |
35 | public Debugger(TestTreeProvider treeProvider, DebuggerFrontend frontend) {
36 | this.treeProvider = treeProvider;
37 | this.frontend = frontend;
38 | }
39 |
40 | public AbstractThreadGroup getSelectedThreadGroup() {
41 | AbstractThreadGroup[] grps = getThreadGroups();
42 | if (grps.length > 0) {
43 | return getSelectedThreadGroup(grps);
44 | } else {
45 | log.debug("Empty test plan");
46 | return null;
47 | }
48 | }
49 |
50 | private AbstractThreadGroup getSelectedThreadGroup(AbstractThreadGroup[] grps) {
51 | JMeterTreeNode currentElement = GuiPackage.getInstance().getCurrentNode();
52 | if (currentElement != null) {
53 | SearchParentClass searcher = new SearchParentClass(currentElement, AbstractThreadGroup.class);
54 | treeProvider.getTestTree().traverse(searcher);
55 | return searcher.hasResults() ? searcher.getSearchResults().get(0) : grps[0];
56 | }
57 | return grps[0];
58 | }
59 |
60 | public AbstractThreadGroup[] getThreadGroups() {
61 | SearchClass searcher = new SearchClass<>(AbstractThreadGroup.class);
62 | treeProvider.getTestTree().traverse(searcher);
63 | return searcher.getSearchResults().toArray(new AbstractThreadGroup[0]);
64 | }
65 |
66 | public void selectThreadGroup(AbstractThreadGroup tg) {
67 | log.debug("Selecting thread group " + tg.getName() + ": " + tg);
68 | cloner = new TreeClonerTG(tg);
69 | treeProvider.getTestTree().traverse(cloner);
70 | }
71 |
72 | public HashTree getSelectedTree() {
73 | if (cloner == null) {
74 | throw new IllegalStateException();
75 | }
76 | return cloner.getClonedTree();
77 | }
78 |
79 | public void start() {
80 | log.debug("Start debugging");
81 | frontend.started();
82 |
83 | HashTree hashTree = getSelectedTree();
84 | StandardJMeterEngine.register(new StateListener()); // oh, dear, they use static field then clean it...
85 | engine = new DebuggerEngine(JMeterContextService.getContext());
86 | engine.setStepper(this);
87 | JMeter.convertSubTree(hashTree);
88 | engine.configure(hashTree);
89 | try {
90 | engine.runTest();
91 | } catch (JMeterEngineException e) {
92 | log.error("Failed to pauseContinue debug", e);
93 | stop();
94 | }
95 | }
96 |
97 | public void stop() {
98 | log.debug("Stop debugging");
99 |
100 | if (stopping) {
101 | throw new IllegalStateException("Already stopping");
102 | }
103 |
104 | stopping = true;
105 | try {
106 | if (engine != null && engine.isActive()) {
107 | engine.stopTest(true);
108 | }
109 | } finally {
110 | stopping = false;
111 | frontend.stopped();
112 | JMeterContextServiceAccessor.removeContext();
113 | currentElement = null;
114 | }
115 | }
116 |
117 | @Override
118 | public synchronized void stepOn(Wrapper wrapper) {
119 | if (stopping) {
120 | throw new JMeterStopThreadException();
121 | }
122 |
123 | TestElement wrappedElement = (TestElement) wrapper.getWrappedElement();
124 |
125 | if (wrapper instanceof TimerDebug) {
126 | ((TimerDebug) wrapper).setDelaying(isContinuing);
127 | }
128 |
129 | currentElement = wrapper;
130 |
131 | if (!isContinuing) {
132 | JMeterContext context = engine.getThreadContext();
133 | frontend.statusRefresh(context);
134 | }
135 |
136 | try {
137 | boolean isBP = isBreakpoint(wrapper) || isSamplerBreakpoint();
138 | if (isContinuing && isBP) {
139 | pause();
140 | }
141 |
142 | if (!isContinuing) {
143 | frontend.frozenAt(wrapper);
144 | log.debug("Stopping before: " + wrappedElement);
145 | this.wait();
146 | frontend.continuing();
147 | }
148 | } catch (InterruptedException e) {
149 | log.debug("Interrupted", e);
150 | throw new JMeterStopThreadException(e);
151 | }
152 | }
153 |
154 | private boolean isSamplerBreakpoint() {
155 | boolean res = false;
156 | Sampler sampler = getCurrentSampler();
157 | if (sampler != lastKnownSampler) {
158 | res = isBreakpoint(sampler);
159 | lastKnownSampler = sampler;
160 | }
161 | return res;
162 | }
163 |
164 | public void pause() {
165 | isContinuing = false;
166 | }
167 |
168 | public void continueRun() {
169 | isContinuing = true;
170 | proceed();
171 | }
172 |
173 | public Wrapper getCurrentElement() {
174 | return currentElement;
175 | }
176 |
177 | public Sampler getCurrentSampler() {
178 | if (engine == null) {
179 | return null;
180 | }
181 |
182 | JMeterContext threadContext = engine.getThreadContext();
183 | return threadContext.getCurrentSampler();
184 | }
185 |
186 | public boolean isContinuing() {
187 | return isContinuing;
188 | }
189 |
190 | public static boolean isBreakpoint(TestElement te) {
191 | if (te instanceof OriginalLink) {
192 | te = (TestElement) ((OriginalLink) te).getOriginal();
193 | }
194 | return te.getPropertyAsBoolean(Debugger.class.getCanonicalName(), false);
195 | }
196 |
197 | public static void toggleBreakpoint(TestElement te) {
198 | if (te instanceof OriginalLink) {
199 | te = (TestElement) ((OriginalLink) te).getOriginal();
200 | }
201 | boolean isBP = te.getPropertyAsBoolean(Debugger.class.getCanonicalName(), false);
202 | te.setProperty(Debugger.class.getCanonicalName(), !isBP);
203 | }
204 |
205 | public synchronized void proceed() {
206 | notifyAll();
207 | }
208 |
209 | private class StateListener implements TestStateListener {
210 | @Override
211 | public void testEnded() {
212 | stop();
213 | }
214 |
215 | @Override
216 | public void testStarted() {
217 |
218 | }
219 |
220 | @Override
221 | public void testStarted(String host) {
222 |
223 | }
224 |
225 | @Override
226 | public void testEnded(String host) {
227 |
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/engine/DebuggerEngine.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.engine;
2 |
3 | import com.blazemeter.jmeter.debugger.elements.Wrapper;
4 | import org.apache.jmeter.engine.StandardJMeterEngine;
5 | import org.apache.jmeter.threads.JMeterContext;
6 | import org.apache.jmeter.threads.JMeterContextService;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 |
11 | public class DebuggerEngine extends StandardJMeterEngine {
12 | private static final Logger log = LoggerFactory.getLogger(DebuggerEngine.class);
13 |
14 | private final JMeterContext context;
15 |
16 | private Thread thread;
17 | private DebuggingThread target;
18 | private StepTrigger stepper = new StepTrigger() {
19 | @Override
20 | public void stepOn(Wrapper t) {
21 | throw new RuntimeException("Not initialized stepper");
22 | }
23 | };
24 |
25 | public DebuggerEngine(JMeterContext context) {
26 | this.context = context;
27 | }
28 |
29 | public JMeterContext getThreadContext() {
30 | return context;
31 | }
32 |
33 | public void setStepper(StepTrigger stepper) {
34 | this.stepper = stepper;
35 | }
36 |
37 | public StepTrigger getStepper() {
38 | return stepper;
39 | }
40 |
41 | public void setTarget(DebuggingThread target) {
42 | if (this.target != null) {
43 | throw new IllegalStateException();
44 | }
45 | this.target = target;
46 | }
47 |
48 | public void setThread(Thread thread) {
49 | if (this.thread != null) {
50 | throw new IllegalStateException();
51 | }
52 | this.thread = thread;
53 | }
54 |
55 |
56 | @Override
57 | public synchronized void stopTest(boolean now) {
58 | super.stopTest(now);
59 |
60 | if (thread != null && thread.isAlive()) {
61 | log.debug("Joining thread: " + thread);
62 | try {
63 | thread.join(10000); // last resort wait
64 | } catch (InterruptedException e) {
65 | throw new RuntimeException(e);
66 | }
67 |
68 | if (thread.isAlive()) {
69 | log.warn("Thread not finished: " + thread);
70 | } else {
71 | log.debug("Thread finished: " + thread);
72 | }
73 | }
74 | }
75 |
76 | @Override
77 | public void run() {
78 | JMeterContextService.replaceContext(context);
79 | super.run();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/engine/DebuggerFrontend.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.engine;
2 |
3 | import com.blazemeter.jmeter.debugger.elements.Wrapper;
4 | import org.apache.jmeter.threads.JMeterContext;
5 |
6 | public interface DebuggerFrontend {
7 | void started();
8 |
9 | void stopped();
10 |
11 | void continuing();
12 |
13 | void frozenAt(Wrapper wrapper);
14 |
15 | void statusRefresh(JMeterContext context);
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/engine/DebuggingThread.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.engine;
2 |
3 | import org.apache.jmeter.threads.*;
4 | import org.apache.jorphan.collections.HashTree;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | public class DebuggingThread extends JMeterThread {
9 | private static final Logger log = LoggerFactory.getLogger(DebuggingThread.class);
10 |
11 | private JMeterContext threadContext;
12 |
13 |
14 | public DebuggingThread(HashTree test, JMeterThreadMonitor monitor, ListenerNotifier note, JMeterContext ctx) {
15 | super(test, monitor, note);
16 | threadContext = ctx;
17 | }
18 |
19 | @Override
20 | public void run() {
21 | log.debug("Replacing thread context with " + threadContext);
22 | JMeterContextService.replaceContext(threadContext);
23 | super.run();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/blazemeter/jmeter/debugger/engine/SearchClass.java:
--------------------------------------------------------------------------------
1 | package com.blazemeter.jmeter.debugger.engine;
2 |
3 | import org.apache.jmeter.gui.tree.JMeterTreeNode;
4 | import org.apache.jorphan.collections.HashTree;
5 | import org.apache.jorphan.collections.HashTreeTraverser;
6 | import org.apache.jorphan.collections.ListedHashTree;
7 |
8 | import java.util.*;
9 |
10 | public class SearchClass implements HashTreeTraverser {
11 | private final List results = new LinkedList<>();
12 |
13 | private final Map