├── .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 | ![logo](/src/main/resources/com/blazemeter/jmeter/debugger/logo.png) 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 | ![travis](https://img.shields.io/travis/Blazemeter/jmeter-debugger.svg) 77 | ![travis](https://img.shields.io/codecov/c/github/Blazemeter/jmeter-debugger.svg) 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 subTrees = new HashMap<>(); 14 | 15 | private final Class searchClass; 16 | 17 | public SearchClass(Class searchClass) { 18 | this.searchClass = searchClass; 19 | } 20 | 21 | public Collection getSearchResults() { // TODO specify collection type without breaking callers 22 | return results; 23 | } 24 | 25 | 26 | @Override 27 | public void addNode(Object node, HashTree subTree) { 28 | if (node instanceof JMeterTreeNode) { 29 | Object userObj = ((JMeterTreeNode) node).getUserObject(); 30 | if (searchClass.isAssignableFrom(userObj.getClass())) { 31 | //noinspection unchecked 32 | results.add((T) userObj); 33 | } 34 | } 35 | } 36 | 37 | @Override 38 | public void subtractNode() { 39 | } 40 | 41 | @Override 42 | public void processPath() { 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/engine/SearchParentClass.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 | 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | public class SearchParentClass implements HashTreeTraverser { 11 | private final List results = new LinkedList<>(); 12 | 13 | private final JMeterTreeNode searchNode; 14 | private final Class searchParentClass; 15 | 16 | public SearchParentClass(JMeterTreeNode searchNode, Class searchParentClass) { 17 | this.searchNode = searchNode; 18 | this.searchParentClass = searchParentClass; 19 | } 20 | 21 | public List getSearchResults() { 22 | return results; 23 | } 24 | 25 | public boolean hasResults() { 26 | return !results.isEmpty(); 27 | } 28 | 29 | @Override 30 | public void addNode(Object node, HashTree subTree) { 31 | if (node instanceof JMeterTreeNode) { 32 | JMeterTreeNode jMeterTreeNode = ((JMeterTreeNode) node); 33 | if (node.equals(searchNode)) { 34 | T parent = findParentByClass(jMeterTreeNode); 35 | if (parent != null) { 36 | results.add(parent); 37 | } 38 | } 39 | } 40 | } 41 | 42 | private T findParentByClass(JMeterTreeNode node) { 43 | if (node != null) { 44 | Object userObject = node.getUserObject(); 45 | return searchParentClass.isAssignableFrom(userObject.getClass()) ? 46 | (T) userObject : 47 | findParentByClass((JMeterTreeNode) node.getParent()); 48 | } 49 | 50 | return null; 51 | } 52 | 53 | @Override 54 | public void subtractNode() { 55 | } 56 | 57 | @Override 58 | public void processPath() { 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/engine/StepTrigger.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.engine; 2 | 3 | import com.blazemeter.jmeter.debugger.elements.Wrapper; 4 | 5 | public interface StepTrigger { 6 | void stepOn(Wrapper t); 7 | } -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/engine/TestTreeProvider.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.engine; 2 | 3 | 4 | import org.apache.jorphan.collections.HashTree; 5 | 6 | public interface TestTreeProvider { 7 | HashTree getTestTree(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/engine/TreeClonerTG.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.engine; 2 | 3 | import com.blazemeter.jmeter.debugger.elements.*; 4 | import org.apache.jmeter.assertions.Assertion; 5 | import org.apache.jmeter.control.*; 6 | import org.apache.jmeter.gui.tree.JMeterTreeNode; 7 | import org.apache.jmeter.processor.PostProcessor; 8 | import org.apache.jmeter.processor.PreProcessor; 9 | import org.apache.jmeter.samplers.SampleListener; 10 | import org.apache.jmeter.samplers.Sampler; 11 | import org.apache.jmeter.testelement.TestElement; 12 | import org.apache.jmeter.testelement.property.JMeterProperty; 13 | import org.apache.jmeter.testelement.property.NullProperty; 14 | import org.apache.jmeter.testelement.property.PropertyIterator; 15 | import org.apache.jmeter.threads.AbstractThreadGroup; 16 | import org.apache.jmeter.timers.Timer; 17 | import org.apache.jorphan.collections.HashTree; 18 | import org.apache.jorphan.collections.HashTreeTraverser; 19 | import org.apache.jorphan.collections.ListedHashTree; 20 | import org.slf4j.LoggerFactory; 21 | import org.slf4j.Logger; 22 | 23 | import java.util.LinkedList; 24 | 25 | public class TreeClonerTG implements HashTreeTraverser { 26 | private static final Logger log = LoggerFactory.getLogger(TreeClonerTG.class); 27 | private AbstractThreadGroup onlyTG; 28 | 29 | private final ListedHashTree newTree = new ListedHashTree(); 30 | private final LinkedList stack = new LinkedList<>(); 31 | private boolean ignoring = false; 32 | 33 | public TreeClonerTG(AbstractThreadGroup tg) { 34 | this.onlyTG = tg; 35 | } 36 | 37 | @Override 38 | public final void addNode(Object node, HashTree subTree) { 39 | if (!ignoring && isIgnored(node)) { 40 | ignoring = true; 41 | } 42 | 43 | if (!ignoring) { 44 | node = addNodeToTree(node); 45 | } 46 | stack.addLast(node); 47 | } 48 | 49 | private boolean isIgnored(Object node) { 50 | if (node instanceof JMeterTreeNode) { 51 | Object te = ((JMeterTreeNode) node).getUserObject(); 52 | return te instanceof AbstractThreadGroup && te != onlyTG; 53 | } 54 | return false; 55 | } 56 | 57 | protected Object addNodeToTree(Object node) { 58 | if (node instanceof JMeterTreeNode) { 59 | node = getClonedNode((JMeterTreeNode) node); 60 | newTree.add(stack, node); 61 | } else { 62 | throw new IllegalArgumentException(); 63 | } 64 | return node; 65 | } 66 | 67 | private JMeterTreeNode getClonedNode(JMeterTreeNode node) { 68 | TestElement orig = getOriginalObject(node); 69 | TestElement cloned = (TestElement) orig.clone(); 70 | TestElement altered = getAlteredElement(cloned); 71 | 72 | if (altered instanceof Wrapper) { 73 | Wrapper wrp = (Wrapper) altered; 74 | //noinspection unchecked 75 | wrp.setWrappedElement(cloned); 76 | PropertyIterator iter = cloned.propertyIterator(); 77 | while (iter.hasNext()) { 78 | JMeterProperty prop = iter.next(); 79 | if (!prop.getName().startsWith("TestElement")) { 80 | wrp.setProperty(prop.clone()); 81 | } 82 | } 83 | } 84 | 85 | if (altered instanceof OriginalLink) { 86 | OriginalLink link = (OriginalLink) altered; 87 | //noinspection unchecked 88 | link.setOriginal(orig); 89 | } else { 90 | log.debug("Not linking original: " + altered); 91 | } 92 | 93 | JMeterTreeNode res = new JMeterTreeNode(); 94 | altered.setName(cloned.getName()); 95 | altered.setEnabled(cloned.isEnabled()); 96 | if (altered.getProperty(TestElement.GUI_CLASS) instanceof NullProperty) { 97 | altered.setProperty(TestElement.GUI_CLASS, ControllerDebugGui.class.getCanonicalName()); 98 | } 99 | res.setUserObject(altered); 100 | return res; 101 | } 102 | 103 | private TestElement getAlteredElement(TestElement cloned) { 104 | boolean isWrappable = !(cloned instanceof TransactionController) && !(cloned instanceof TestFragmentController) && !(cloned instanceof ReplaceableController); 105 | 106 | TestElement userObject = cloned; 107 | if (!isWrappable) { 108 | log.debug("Forcing unwrapped: " + cloned); 109 | } else if (cloned instanceof AbstractThreadGroup) { 110 | userObject = new DebuggingThreadGroup(); 111 | userObject.setProperty(TestElement.GUI_CLASS, DebuggingThreadGroupGui.class.getCanonicalName()); 112 | } else if (cloned instanceof Controller) { 113 | userObject = getController(cloned); 114 | } else if (cloned instanceof PreProcessor) { 115 | userObject = new PreProcessorDebug(); 116 | } else if (cloned instanceof Timer) { 117 | userObject = new TimerDebug(); 118 | } else if (cloned instanceof Sampler) { 119 | userObject = new SamplerDebug(); 120 | } else if (cloned instanceof PostProcessor) { 121 | userObject = new PostProcessorDebug(); 122 | } else if (cloned instanceof Assertion) { 123 | userObject = new AssertionDebug(); 124 | } else if (cloned instanceof SampleListener) { 125 | userObject = new SampleListenerDebug(); 126 | } else { 127 | log.debug("Keeping element unwrapped: " + cloned); 128 | } 129 | return userObject; 130 | } 131 | 132 | private TestElement getOriginalObject(JMeterTreeNode node) { 133 | Object obj = (node).getUserObject(); 134 | if (obj instanceof OriginalLink) { 135 | Object original = ((OriginalLink) obj).getOriginal(); 136 | return (TestElement) original; 137 | } else { 138 | return (TestElement) obj; 139 | } 140 | } 141 | 142 | private TestElement getController(TestElement cloned) { 143 | if (cloned instanceof GenericController) { 144 | if (cloned instanceof ReplaceableController) { // TODO: solve replaceable problem 145 | log.warn("Not supported!: " + cloned); 146 | return new ReplaceableGenericControllerDebug(); 147 | } else { 148 | return new GenericControllerDebug(); 149 | } 150 | } else { 151 | if (cloned instanceof ReplaceableController) { 152 | log.warn("Controller+Replaceable is unsupported: " + cloned); 153 | } 154 | return new ControllerDebug(); 155 | } 156 | } 157 | 158 | @Override 159 | public void subtractNode() { 160 | if (isIgnored(stack.getLast())) { 161 | ignoring = false; 162 | } 163 | stack.removeLast(); 164 | } 165 | 166 | @Override 167 | public void processPath() { 168 | } 169 | 170 | public HashTree getClonedTree() { 171 | return newTree; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/BlazeMeterLogo.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | import java.awt.event.MouseEvent; 6 | import java.awt.event.MouseListener; 7 | import java.io.IOException; 8 | import java.net.URI; 9 | import java.net.URISyntaxException; 10 | 11 | 12 | public class BlazeMeterLogo extends JLabel { 13 | public BlazeMeterLogo() { 14 | super(DebuggerMenuItem.getLogoIcon()); 15 | setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 16 | addMouseListener(new MouseListener() { 17 | @Override 18 | public void mouseClicked(MouseEvent mouseEvent) { 19 | if (java.awt.Desktop.isDesktopSupported()) { 20 | try { 21 | java.awt.Desktop.getDesktop().browse(new URI("https://blazemeter.com/")); 22 | } catch (IOException | URISyntaxException ignored) { 23 | } 24 | } 25 | } 26 | 27 | @Override 28 | public void mousePressed(MouseEvent mouseEvent) { 29 | 30 | } 31 | 32 | @Override 33 | public void mouseReleased(MouseEvent mouseEvent) { 34 | 35 | } 36 | 37 | @Override 38 | public void mouseEntered(MouseEvent mouseEvent) { 39 | 40 | } 41 | 42 | @Override 43 | public void mouseExited(MouseEvent mouseEvent) { 44 | 45 | } 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/ComponentFinder.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.awt.*; 8 | 9 | public class ComponentFinder { 10 | private static final Logger log = LoggerFactory.getLogger(ComponentFinder.class); 11 | 12 | private final Class search; 13 | 14 | public ComponentFinder(Class cls) { 15 | search = cls; 16 | } 17 | 18 | public T findComponentIn(Container container) { 19 | log.debug("Searching in " + container); 20 | for (Component a : container.getComponents()) { 21 | if (search.isAssignableFrom(a.getClass())) { 22 | log.debug("Found " + a); 23 | return (T) a; 24 | } 25 | 26 | if (a instanceof Container) { 27 | T res = findComponentIn((Container) a); 28 | if (res != null) { 29 | return res; 30 | } 31 | } 32 | } 33 | 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/DebuggerDialog.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import com.blazemeter.jmeter.debugger.elements.DebuggingThreadGroup; 4 | import com.blazemeter.jmeter.debugger.elements.OriginalLink; 5 | import com.blazemeter.jmeter.debugger.elements.ThreadGroupWrapper; 6 | import com.blazemeter.jmeter.debugger.elements.Wrapper; 7 | import com.blazemeter.jmeter.debugger.engine.Debugger; 8 | import com.blazemeter.jmeter.debugger.engine.DebuggerFrontend; 9 | import com.blazemeter.jmeter.debugger.engine.SearchClass; 10 | import com.blazemeter.jmeter.debugger.engine.TestTreeProvider; 11 | import org.apache.jmeter.JMeter; 12 | import org.apache.jmeter.control.ReplaceableController; 13 | import org.apache.jmeter.engine.TreeCloner; 14 | import org.apache.jmeter.exceptions.IllegalUserActionException; 15 | import org.apache.jmeter.gui.GuiPackage; 16 | import org.apache.jmeter.gui.JMeterGUIComponent; 17 | import org.apache.jmeter.gui.tree.JMeterTreeModel; 18 | import org.apache.jmeter.gui.tree.JMeterTreeNode; 19 | import org.apache.jmeter.reporters.ResultCollector; 20 | import org.apache.jmeter.samplers.Clearable; 21 | import org.apache.jmeter.samplers.SampleEvent; 22 | import org.apache.jmeter.samplers.Sampler; 23 | import org.apache.jmeter.testelement.TestElement; 24 | import org.apache.jmeter.threads.AbstractThreadGroup; 25 | import org.apache.jmeter.threads.JMeterContext; 26 | import org.apache.jmeter.util.JMeterUtils; 27 | import org.apache.jorphan.collections.HashTree; 28 | import org.apache.jorphan.collections.SearchByClass; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | 32 | import javax.swing.*; 33 | import javax.swing.event.TreeSelectionEvent; 34 | import javax.swing.tree.TreeNode; 35 | import javax.swing.tree.TreePath; 36 | import java.awt.*; 37 | import java.awt.event.ActionEvent; 38 | import java.awt.event.ActionListener; 39 | import java.awt.event.ComponentEvent; 40 | import java.awt.event.ItemEvent; 41 | import java.awt.event.ItemListener; 42 | import java.util.ArrayList; 43 | import java.util.Collection; 44 | import java.util.List; 45 | import java.util.Map; 46 | 47 | public class DebuggerDialog extends DebuggerDialogBase implements DebuggerFrontend, TestTreeProvider { 48 | private static final Logger log = LoggerFactory.getLogger(DebuggerDialog.class); 49 | 50 | private boolean savedDirty = false; 51 | protected Debugger debugger = null; 52 | protected ResultCollector lastResultListener; 53 | 54 | public DebuggerDialog() { 55 | super(); 56 | start.addActionListener(new StartDebugging()); 57 | stop.addActionListener(new StopDebugging()); 58 | step.addActionListener(new StepOver()); 59 | pauseContinue.addActionListener(new PauseContinue()); 60 | tgCombo.addItemListener(new ThreadGroupChoiceChanged()); 61 | lastResultListener = (ResultCollector) lastSamplerResult.createTestElement(); 62 | } 63 | 64 | @Override 65 | public void componentShown(ComponentEvent e) { 66 | log.debug("Showing dialog"); 67 | if (GuiPackage.getInstance() != null) { 68 | savedDirty = GuiPackage.getInstance().isDirty(); 69 | } 70 | this.debugger = new Debugger(this, this); 71 | tgCombo.removeAllItems(); 72 | for (AbstractThreadGroup group : debugger.getThreadGroups()) { 73 | tgCombo.addItem(group); 74 | } 75 | 76 | AbstractThreadGroup selectedThreadGroup = debugger.getSelectedThreadGroup(); 77 | if (selectedThreadGroup != null) { 78 | changeComboValue(selectedThreadGroup); 79 | } 80 | 81 | tgCombo.setEnabled(tgCombo.getItemCount() > 0); 82 | start.setEnabled(tgCombo.getItemCount() > 0); 83 | start.requestFocus(); 84 | clearListeners(); 85 | } 86 | 87 | private void changeComboValue(AbstractThreadGroup selectedThreadGroup) { 88 | ComboBoxModel model = this.tgCombo.getModel(); 89 | for (int i = 0; i < model.getSize(); i++) { 90 | if (model.getElementAt(i).equals(selectedThreadGroup)) { 91 | tgCombo.setSelectedIndex(i); 92 | } 93 | } 94 | } 95 | 96 | @Override 97 | public void componentHidden(ComponentEvent e) { 98 | log.debug("Closing dialog"); 99 | 100 | debugger.stop(); 101 | if (GuiPackage.getInstance() != null) { 102 | GuiPackage.getInstance().setDirty(savedDirty); 103 | } 104 | clearListeners(); 105 | clearStatusPane(); 106 | } 107 | 108 | @Override 109 | public HashTree getTestTree() { 110 | GuiPackage gui = GuiPackage.getInstance(); 111 | return gui.getTreeModel().getTestPlan(); 112 | } 113 | 114 | private void clearListeners() { 115 | GuiPackage guiPackage = GuiPackage.getInstance(); 116 | for (JMeterTreeNode node : guiPackage.getTreeModel().getNodesOfType(Clearable.class)) { 117 | JMeterGUIComponent guiComp = guiPackage.getGui(node.getTestElement()); 118 | if (guiComp instanceof Clearable){ 119 | Clearable item = (Clearable) guiComp; 120 | try { 121 | item.clearData(); 122 | } catch (Exception ex) { 123 | log.error("Can't clear: {} {}", node, guiComp, ex); 124 | } 125 | } 126 | } 127 | } 128 | 129 | private void toggleControls(boolean state) { 130 | tgCombo.setEnabled(state); 131 | start.setEnabled(state); 132 | stop.setEnabled(!state); 133 | pauseContinue.setEnabled(!state); 134 | evaluatePanel.setEnabled(!state); 135 | } 136 | 137 | private void refreshVars(JMeterContext context) { 138 | varsTableModel.clearData(); 139 | for (Map.Entry var : context.getVariables().entrySet()) { 140 | varsTableModel.addRow(new String[]{var.getKey(), var.getValue().toString()}); 141 | } 142 | varsTableModel.fireTableDataChanged(); 143 | if (context.getPreviousResult() != null) { 144 | lastResultListener.sampleOccurred(new SampleEvent(context.getPreviousResult(), context.getThreadGroup().getName())); 145 | } 146 | } 147 | 148 | private void refreshProperties() { 149 | propsTableModel.clearData(); 150 | for (Map.Entry var : JMeterUtils.getJMeterProperties().entrySet()) { 151 | propsTableModel.addRow(new String[]{var.getKey().toString(), var.getValue().toString()}); 152 | } 153 | propsTableModel.fireTableDataChanged(); 154 | } 155 | 156 | private void selectTargetInTree(Wrapper dbgElm) { 157 | TestElement wrpElm = (TestElement) dbgElm.getWrappedElement(); 158 | TreePath treePath = getTreePathFor(wrpElm); 159 | if (treePath == null) { 160 | // case for wrapped controllers 161 | treePath = getTreePathFor(dbgElm); 162 | } 163 | 164 | if (treePath == null) { 165 | log.debug("Did not find tree path for element"); 166 | } else { 167 | if (treePath.equals(tree.getSelectionPath())) { 168 | tree.setSelectionPath(treePath.getParentPath()); 169 | } 170 | tree.setSelectionPath(treePath); 171 | } 172 | 173 | Sampler sampler = debugger.getCurrentSampler(); 174 | if (sampler != null) { 175 | TreePath samplerPath = getTreePathFor(sampler); 176 | if (samplerPath != null) { 177 | log.debug("Expanding: " + samplerPath); 178 | tree.expandPath(samplerPath.getParentPath()); 179 | } 180 | } 181 | 182 | tree.repaint(); 183 | } 184 | 185 | private TreePath getTreePathFor(TestElement te) { 186 | List nodes = new ArrayList<>(); 187 | JMeterTreeModel model = (JMeterTreeModel) tree.getModel(); 188 | 189 | TreeNode treeNode = model.getNodeOf(te); 190 | if (treeNode != null) { 191 | nodes.add(treeNode); 192 | treeNode = treeNode.getParent(); 193 | while (treeNode != null) { 194 | nodes.add(0, treeNode); 195 | treeNode = treeNode.getParent(); 196 | } 197 | } 198 | 199 | return nodes.isEmpty() ? null : new TreePath(nodes.toArray()); 200 | } 201 | 202 | private void selectThreadGroup(AbstractThreadGroup tg) { 203 | debugger.selectThreadGroup(tg); 204 | treeModel.clearTestPlan(); 205 | HashTree origTree = debugger.getSelectedTree(); 206 | TreeCloner cloner = new TreeCloner(); 207 | origTree.traverse(cloner); 208 | HashTree selectedTree = cloner.getClonedTree(); 209 | 210 | // Hack to resolve ModuleControllers from JMeter.java 211 | SearchClass replaceableControllers = new SearchClass<>(ReplaceableController.class); 212 | selectedTree.traverse(replaceableControllers); 213 | Collection replaceableControllersRes = replaceableControllers.getSearchResults(); 214 | for (ReplaceableController replaceableController : replaceableControllersRes) { 215 | replaceableController.resolveReplacementSubTree((JMeterTreeNode) treeModel.getRoot()); 216 | } 217 | 218 | JMeter.convertSubTree(selectedTree); 219 | try { 220 | treeModel.addSubTree(selectedTree, (JMeterTreeNode) treeModel.getRoot()); 221 | } catch (IllegalUserActionException e) { 222 | throw new RuntimeException(e); 223 | } 224 | 225 | // select TG for visual convenience 226 | SearchByClass tgs = new SearchByClass<>(DebuggingThreadGroup.class); 227 | selectedTree.traverse(tgs); 228 | for (DebuggingThreadGroup forSel : tgs.getSearchResults()) { 229 | Wrapper wtg = new ThreadGroupWrapper(); 230 | wtg.setWrappedElement(forSel); 231 | selectTargetInTree(wtg); 232 | } 233 | } 234 | 235 | @Override 236 | public void highlightNode(Component component, JMeterTreeNode node, TestElement mc) { 237 | component.setFont(component.getFont().deriveFont(~Font.BOLD).deriveFont(~Font.ITALIC)); 238 | TestElement userObject = (TestElement) node.getUserObject(); 239 | if (Debugger.isBreakpoint(userObject)) { 240 | component.setForeground(Color.RED); 241 | } 242 | 243 | if (debugger == null) { 244 | return; 245 | } 246 | 247 | Wrapper currentElement = debugger.getCurrentElement(); 248 | if (currentElement == null) { 249 | return; 250 | } 251 | 252 | Font font = component.getFont(); 253 | TestElement currentWrapped = (TestElement) currentElement.getWrappedElement(); 254 | if (mc == currentElement || mc == currentWrapped) { 255 | setComponentStyle(component, font, Font.BOLD, Font.BOLD); 256 | } 257 | 258 | Sampler currentSampler = debugger.getCurrentSampler(); 259 | if (mc == currentSampler) { // can this ever happen? 260 | setComponentStyle(component, font, Font.BOLD + Font.ITALIC, font.getStyle() | Font.ITALIC); 261 | } else if (currentSampler instanceof Wrapper && mc == ((Wrapper) currentSampler).getWrappedElement()) { 262 | setComponentStyle(component, font, Font.BOLD + Font.ITALIC, font.getStyle() | Font.ITALIC); 263 | } 264 | } 265 | 266 | private void setComponentStyle(Component component, Font font, int macStyle, int otherOsStyle) { 267 | if (isMac()) { 268 | component.setFont(new Font(font.getName(), macStyle, font.getSize())); 269 | component.setForeground(Color.BLACK); 270 | } else { 271 | component.setFont(font.deriveFont(otherOsStyle)); 272 | component.setForeground(Color.BLUE); 273 | } 274 | } 275 | 276 | public static boolean isMac() { 277 | return (System.getProperty("os.name").toLowerCase().contains("mac")); 278 | } 279 | 280 | @Override 281 | public void valueChanged(TreeSelectionEvent treeSelectionEvent) { 282 | JMeterTreeNode node = (JMeterTreeNode) treeSelectionEvent.getPath().getLastPathComponent(); 283 | TestElement wrpElm = node.getTestElement(); 284 | if (wrpElm instanceof OriginalLink) { 285 | TestElement te = (TestElement) ((OriginalLink) wrpElm).getOriginal(); 286 | if (!(te instanceof AbstractThreadGroup)) { 287 | wrpElm = te; 288 | } 289 | } 290 | 291 | displayElementGui(wrpElm); 292 | } 293 | 294 | private synchronized void displayElementGui(TestElement wrpElm) { 295 | GuiPackage gui = GuiPackage.getInstance(); 296 | if (gui != null) { 297 | JMeterGUIComponent egui = gui.getGui(wrpElm); 298 | egui.configure(wrpElm); 299 | egui.modifyTestElement(wrpElm); 300 | 301 | elementContainer.removeAll(); 302 | if (egui instanceof Component) { 303 | // egui.setEnabled(false); 304 | elementContainer.add((Component) egui, BorderLayout.CENTER); 305 | } 306 | elementContainer.updateUI(); 307 | } 308 | } 309 | 310 | @Override 311 | public void started() { 312 | loggerPanel.clear(); 313 | toggleControls(false); 314 | } 315 | 316 | @Override 317 | public void stopped() { 318 | toggleControls(true); 319 | elementContainer.removeAll(); 320 | } 321 | 322 | @Override 323 | public void frozenAt(Wrapper wrapper) { 324 | pauseContinue.setText("Continue"); 325 | pauseContinue.setIcon(DebuggerMenuItem.getContinueIcon()); 326 | 327 | step.setEnabled(true); 328 | selectTargetInTree(wrapper); 329 | } 330 | 331 | @Override 332 | public void statusRefresh(JMeterContext context) { 333 | try { 334 | refreshVars(context); 335 | refreshProperties(); 336 | } catch (Throwable e) { 337 | log.warn("Problem refreshing status pane", e); 338 | } 339 | evaluatePanel.refresh(context, debugger.isContinuing()); 340 | SwingUtilities.invokeLater(new Runnable() { 341 | @Override 342 | public void run() { 343 | tree.repaint(); 344 | } 345 | }); 346 | } 347 | 348 | @Override 349 | public void continuing() { 350 | // to prevent buttons "jumping" 351 | pauseContinue.setMinimumSize(pauseContinue.getSize()); 352 | pauseContinue.setPreferredSize(pauseContinue.getSize()); 353 | pauseContinue.setSize(pauseContinue.getSize()); 354 | 355 | pauseContinue.setText("Pause"); 356 | pauseContinue.setIcon(DebuggerMenuItem.getPauseIcon()); 357 | step.setEnabled(false); 358 | } 359 | 360 | private class ThreadGroupChoiceChanged implements ItemListener { 361 | @Override 362 | public void itemStateChanged(ItemEvent event) { 363 | if (event.getStateChange() == ItemEvent.SELECTED) { 364 | log.debug("Item choice changed: " + event.getItem()); 365 | if (event.getItem() instanceof AbstractThreadGroup) { 366 | selectThreadGroup((AbstractThreadGroup) event.getItem()); 367 | } 368 | } 369 | } 370 | } 371 | 372 | private class StartDebugging implements ActionListener { 373 | @Override 374 | public void actionPerformed(ActionEvent e) { 375 | debugger.start(); 376 | } 377 | } 378 | 379 | private class StepOver implements ActionListener { 380 | @Override 381 | public void actionPerformed(ActionEvent e) { 382 | synchronized (this) { 383 | debugger.proceed(); 384 | } 385 | } 386 | } 387 | 388 | private class PauseContinue implements ActionListener { 389 | @Override 390 | public void actionPerformed(ActionEvent actionEvent) { 391 | if (debugger.isContinuing()) { 392 | debugger.pause(); 393 | } else { 394 | debugger.continueRun(); 395 | } 396 | } 397 | } 398 | 399 | private class StopDebugging implements ActionListener { 400 | @Override 401 | public void actionPerformed(ActionEvent e) { 402 | debugger.stop(); 403 | } 404 | } 405 | 406 | } 407 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/DebuggerDialogBase.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import com.blazemeter.jmeter.debugger.engine.Debugger; 4 | import org.apache.jmeter.config.ConfigElement; 5 | import org.apache.jmeter.gui.GuiPackage; 6 | import org.apache.jmeter.gui.LoggerPanel; 7 | import org.apache.jmeter.gui.tree.JMeterTreeModel; 8 | import org.apache.jmeter.gui.tree.JMeterTreeNode; 9 | import org.apache.jmeter.gui.util.PowerTableModel; 10 | import org.apache.jmeter.reporters.ResultCollector; 11 | import org.apache.jmeter.samplers.SampleEvent; 12 | import org.apache.jmeter.testelement.TestElement; 13 | import org.apache.jmeter.testelement.TestPlan; 14 | import org.apache.jmeter.testelement.WorkBench; 15 | import org.apache.jmeter.threads.AbstractThreadGroup; 16 | import org.apache.jmeter.threads.ThreadGroup; 17 | import org.apache.jmeter.visualizers.ViewResultsFullVisualizer; 18 | import org.apache.jmeter.visualizers.gui.AbstractVisualizer; 19 | import org.apache.jorphan.gui.ComponentUtil; 20 | import org.apache.jorphan.logging.LoggingManager; 21 | import org.apache.log.LogTarget; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import javax.swing.*; 26 | import javax.swing.event.TreeSelectionListener; 27 | import javax.swing.tree.TreePath; 28 | import java.awt.*; 29 | import java.awt.event.ActionEvent; 30 | import java.awt.event.ActionListener; 31 | import java.awt.event.ComponentEvent; 32 | import java.awt.event.ComponentListener; 33 | import java.awt.event.InputEvent; 34 | import java.awt.event.KeyEvent; 35 | import java.awt.event.MouseEvent; 36 | import java.awt.event.MouseListener; 37 | import java.io.IOException; 38 | import java.lang.reflect.Constructor; 39 | import java.lang.reflect.Field; 40 | import java.net.URI; 41 | import java.net.URISyntaxException; 42 | 43 | abstract public class DebuggerDialogBase extends JDialog implements ComponentListener, NodeHighlighter, TreeSelectionListener { 44 | private static final Logger log = LoggerFactory.getLogger(DebuggerDialogBase.class); 45 | 46 | 47 | protected JComboBox tgCombo = new JComboBox<>(); 48 | protected JTree tree; 49 | protected JMeterTreeModel treeModel; 50 | protected JButton start = new JButton("Start"); 51 | protected JButton step = new JButton("Step Over"); 52 | protected JButton stop = new JButton("Stop"); 53 | protected JButton pauseContinue = new JButton("Continue"); 54 | protected LoggerPanel loggerPanel; 55 | protected PowerTableModel varsTableModel; 56 | protected PowerTableModel propsTableModel; 57 | protected JPanel elementContainer; 58 | protected EvaluatePanel evaluatePanel; 59 | protected AbstractVisualizer lastSamplerResult; 60 | 61 | public DebuggerDialogBase() { 62 | super((JFrame) null, "Step-by-Step Debugger", true); 63 | setLayout(new BorderLayout()); 64 | setSize(new Dimension(800, 600)); 65 | setPreferredSize(new Dimension(800, 600)); 66 | setIconImage(DebuggerMenuItem.getBugIcon(false).getImage()); 67 | ComponentUtil.centerComponentInWindow(this); 68 | addComponentListener(this); 69 | 70 | add(getToolbar(), BorderLayout.NORTH); 71 | JSplitPane treeAndMain = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); 72 | treeAndMain.setDividerSize(5); 73 | treeAndMain.setLeftComponent(getTreePane()); 74 | treeAndMain.setRightComponent(getMainPane()); 75 | add(treeAndMain, BorderLayout.CENTER); 76 | } 77 | 78 | private Component getMainPane() { 79 | final JSplitPane topAndDown = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 80 | topAndDown.setResizeWeight(.75); 81 | topAndDown.setDividerSize(5); 82 | topAndDown.setTopComponent(new JScrollPane(getElementPane())); 83 | topAndDown.setBottomComponent(getStatusPane()); 84 | 85 | SwingUtilities.invokeLater(new Runnable() { 86 | @Override 87 | public void run() { 88 | topAndDown.setDividerLocation(0.7); 89 | } 90 | }); 91 | return topAndDown; 92 | } 93 | 94 | private Component getStatusPane() { 95 | JTabbedPane tabs = new JTabbedPane(); 96 | tabs.add("Variables", getVariablesTab()); 97 | tabs.add("JMeter Properties", getPropertiesTab()); 98 | tabs.add("Last Sample Result", getLastSamplerResultTab()); 99 | tabs.add("Evaluate", getEvaluateTab()); 100 | tabs.add("Log", getLogTab()); 101 | return tabs; 102 | } 103 | 104 | protected void clearStatusPane() { 105 | lastSamplerResult.clearData(); 106 | varsTableModel.clearData(); 107 | propsTableModel.clearData(); 108 | loggerPanel.clear(); 109 | } 110 | 111 | private Component getEvaluateTab() { 112 | evaluatePanel = new EvaluatePanel(); 113 | evaluatePanel.setEnabled(false); 114 | return evaluatePanel; 115 | } 116 | 117 | private Component getVariablesTab() { 118 | varsTableModel = new HighlightTableModel(new String[]{"Name", "Value"}, new Class[]{String.class, String.class}); 119 | JTable table = new HighlightTable(varsTableModel); 120 | return new JScrollPane(table); 121 | } 122 | 123 | private Component getPropertiesTab() { 124 | propsTableModel = new HighlightTableModel(new String[]{"Name", "Value"}, new Class[]{String.class, String.class}); 125 | JTable table = new HighlightTable(propsTableModel); 126 | return new JScrollPane(table); 127 | } 128 | 129 | 130 | private Component getLogTab() { 131 | loggerPanel = new LoggerPanelWrapping(); 132 | loggerPanel.setMinimumSize(new Dimension(0, 50)); 133 | loggerPanel.setPreferredSize(new Dimension(0, 150)); 134 | try { 135 | if (!isJMeter32orLater()) { 136 | LoggingManager.addLogTargetToRootLogger(new LogTarget[]{loggerPanel}); 137 | } else { 138 | Class cls = Class.forName("com.blazemeter.jmeter.debugger.logging.LoggerPanelAppender"); 139 | Constructor constructor = cls.getConstructor(String.class, LoggerPanelWrapping.class); 140 | constructor.newInstance("debugger-logging-appender", loggerPanel); 141 | } 142 | } catch (Throwable ex) { 143 | log.error("Cannot hook into logging", ex); 144 | } 145 | return loggerPanel; 146 | } 147 | 148 | public static boolean isJMeter32orLater() { 149 | try { 150 | Class cls = DebuggerDialogBase.class.getClassLoader().loadClass("org.apache.jmeter.gui.logging.GuiLogEventBus"); 151 | if (cls != null) { 152 | return true; 153 | } 154 | } catch (ClassNotFoundException ex) { 155 | log.debug("Class 'org.apache.jmeter.gui.logging.GuiLogEventBus' not found", ex); 156 | } catch (Throwable ex) { 157 | log.warn("Fail to detect JMeter version", ex); 158 | } 159 | return false; 160 | } 161 | 162 | private Component getElementPane() { 163 | elementContainer = new JPanel(new BorderLayout()); 164 | return elementContainer; 165 | } 166 | 167 | private Component getTreePane() { 168 | JScrollPane panel = new JScrollPane(getTreeView()); 169 | panel.setMinimumSize(new Dimension(200, 0)); 170 | panel.setPreferredSize(new Dimension(250, 0)); 171 | return panel; 172 | } 173 | 174 | private Component getToolbar() { 175 | JToolBar res = new JToolBar(); 176 | res.setFloatable(false); 177 | JLabel logo = new BlazeMeterLogo(); 178 | res.add(logo); 179 | res.addSeparator(new Dimension(32, 26)); 180 | res.add(new JLabel("Choose: ")); 181 | 182 | res.add(tgCombo); 183 | tgCombo.setRenderer(new ThreadGroupItemRenderer(tgCombo.getRenderer())); 184 | 185 | AbstractAction toggle = new AbstractAction() { 186 | @Override 187 | public void actionPerformed(ActionEvent actionEvent) { 188 | if (start.isEnabled()) { 189 | start.doClick(); 190 | } else { 191 | stop.doClick(); 192 | } 193 | } 194 | }; 195 | KeyStroke f5 = KeyStroke.getKeyStroke(KeyEvent.VK_F5, InputEvent.SHIFT_DOWN_MASK); 196 | 197 | start.setIcon(DebuggerMenuItem.getStartIcon()); 198 | res.add(start); 199 | start.setToolTipText(f5.toString()); 200 | start.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(f5, f5); 201 | start.getActionMap().put(f5, toggle); 202 | 203 | stop.setVisible(false); 204 | /* TODO: revive it 205 | stop.setIcon(DebuggerMenuItem.getStopIcon()); 206 | res.add(stop); 207 | stop.setEnabled(false); 208 | stop.setToolTipText(f5.toString()); 209 | stop.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(f5, f5); 210 | stop.getActionMap().put(f5, toggle); 211 | */ 212 | 213 | res.addSeparator(); 214 | 215 | step.setIcon(DebuggerMenuItem.getStepIcon()); 216 | res.add(step); 217 | step.setEnabled(false); 218 | KeyStroke f8 = KeyStroke.getKeyStroke(KeyEvent.VK_F8, InputEvent.SHIFT_DOWN_MASK); 219 | step.setToolTipText(f8.toString()); 220 | step.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(f8, f8); 221 | step.getActionMap().put(f8, new AbstractAction() { 222 | @Override 223 | public void actionPerformed(ActionEvent actionEvent) { 224 | step.doClick(); 225 | } 226 | }); 227 | 228 | pauseContinue.setIcon(DebuggerMenuItem.getContinueIcon()); 229 | res.add(pauseContinue); 230 | pauseContinue.setEnabled(false); 231 | KeyStroke f9 = KeyStroke.getKeyStroke(KeyEvent.VK_F9, InputEvent.SHIFT_DOWN_MASK); 232 | pauseContinue.setToolTipText(f9.toString()); 233 | pauseContinue.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(f9, f9); 234 | pauseContinue.getActionMap().put(f8, new AbstractAction() { 235 | @Override 236 | public void actionPerformed(ActionEvent actionEvent) { 237 | pauseContinue.doClick(); 238 | } 239 | }); 240 | 241 | res.addSeparator(); 242 | JButton help = new JButton("Help", DebuggerMenuItem.getHelpIcon()); 243 | help.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 244 | help.addActionListener(new ActionListener() { 245 | @Override 246 | public void actionPerformed(ActionEvent actionEvent) { 247 | if (java.awt.Desktop.isDesktopSupported()) { 248 | try { 249 | java.awt.Desktop.getDesktop().browse(new URI("https://github.com/Blazemeter/jmeter-debugger")); 250 | } catch (IOException | URISyntaxException ignored) { 251 | } 252 | } 253 | } 254 | }); 255 | res.add(help); 256 | return res; 257 | } 258 | 259 | @Override 260 | public void componentResized(ComponentEvent e) { 261 | } 262 | 263 | @Override 264 | public void componentMoved(ComponentEvent e) { 265 | } 266 | 267 | private JTree getTreeView() { 268 | treeModel = new JMeterTreeModel(); 269 | tree = new JTree(treeModel); 270 | tree.setCellRenderer(new FixedJMeterTreeCellRenderer(this)); 271 | tree.setRootVisible(false); 272 | tree.setShowsRootHandles(true); 273 | tree.addTreeSelectionListener(this); 274 | tree.addMouseListener(new TreeMouseListener()); 275 | return tree; 276 | } 277 | 278 | public Component getLastSamplerResultTab() { 279 | lastSamplerResult = new ViewResultsFullVisualizer() { 280 | @Override 281 | public TestElement createTestElement() { 282 | this.collector = new ResultCollector() { 283 | @Override 284 | public void sampleOccurred(SampleEvent event) { 285 | lastSamplerResult.clearData(); 286 | getVisualizer().add(event.getResult()); 287 | } 288 | }; 289 | this.modifyTestElement(this.collector); 290 | return collector; 291 | } 292 | }; 293 | lastSamplerResult.setName("Last Sampler Result"); 294 | try { 295 | Field mainSplitField = lastSamplerResult.getClass().getSuperclass().getDeclaredField("mainSplit"); 296 | mainSplitField.setAccessible(true); 297 | return (Component) mainSplitField.get(lastSamplerResult); 298 | } catch (Throwable ex) { 299 | log.warn("Failed to find 'mainSplit' field in visualizer"); 300 | return lastSamplerResult; 301 | } 302 | } 303 | 304 | 305 | private class TreeMouseListener implements MouseListener { 306 | @Override 307 | public void mousePressed(MouseEvent e) { 308 | int selRow = tree.getRowForLocation(e.getX(), e.getY()); 309 | 310 | if (tree.getPathForLocation(e.getX(), e.getY()) != null) { 311 | final TreePath currentPath = tree.getPathForLocation(e.getX(), e.getY()); 312 | 313 | if (selRow != -1 && currentPath != null) { 314 | if (isRightClick(e)) { 315 | if (tree.getSelectionCount() < 2) { 316 | tree.setSelectionPath(currentPath); 317 | } 318 | final JMeterTreeNode node = (JMeterTreeNode) currentPath.getLastPathComponent(); 319 | TestElement te = (TestElement) node.getUserObject(); 320 | if (te instanceof ConfigElement || te instanceof TestPlan || te instanceof ThreadGroup || te instanceof WorkBench) { 321 | log.debug("No breakpoint possible for " + te); 322 | return; 323 | } 324 | JPopupMenu popup = getPopup(te); 325 | popup.pack(); 326 | popup.show(tree, e.getX(), e.getY()); 327 | popup.setVisible(true); 328 | popup.requestFocusInWindow(); 329 | } 330 | } 331 | } 332 | } 333 | 334 | private JPopupMenu getPopup(final TestElement te) { 335 | JPopupMenu popup = new JPopupMenu(); 336 | JCheckBoxMenuItem item = new JCheckBoxMenuItem("Breakpoint", DebuggerMenuItem.getBPIcon()); 337 | item.addActionListener(new ActionListener() { 338 | @Override 339 | public void actionPerformed(ActionEvent actionEvent) { 340 | log.debug("Toggle breakpoint on: " + te); 341 | 342 | Debugger.toggleBreakpoint(te); 343 | 344 | tree.repaint(); 345 | } 346 | }); 347 | 348 | item.setState(Debugger.isBreakpoint(te)); 349 | popup.add(item); 350 | return popup; 351 | } 352 | 353 | private boolean isRightClick(MouseEvent e) { 354 | return e.isPopupTrigger() || (InputEvent.BUTTON2_MASK & e.getModifiers()) > 0 || (InputEvent.BUTTON3_MASK == e.getModifiers()); 355 | } 356 | 357 | @Override 358 | public void mouseClicked(MouseEvent mouseEvent) { 359 | 360 | } 361 | 362 | @Override 363 | public void mouseReleased(MouseEvent mouseEvent) { 364 | 365 | } 366 | 367 | @Override 368 | public void mouseEntered(MouseEvent mouseEvent) { 369 | 370 | } 371 | 372 | @Override 373 | public void mouseExited(MouseEvent mouseEvent) { 374 | 375 | } 376 | } 377 | } 378 | 379 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/DebuggerMenuCreator.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import org.apache.jmeter.gui.plugin.MenuCreator; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.swing.*; 8 | 9 | public class DebuggerMenuCreator implements MenuCreator { 10 | private static final Logger log = LoggerFactory.getLogger(DebuggerMenuCreator.class); 11 | 12 | 13 | @Override 14 | public JMenuItem[] getMenuItemsAtLocation(MENU_LOCATION location) { 15 | if (location == MENU_LOCATION.RUN) { 16 | try { 17 | return new JMenuItem[]{new DebuggerMenuItem()}; 18 | } catch (Throwable e) { 19 | log.error("Failed to load debugger", e); 20 | return new JMenuItem[0]; 21 | } 22 | 23 | } else { 24 | return new JMenuItem[0]; 25 | } 26 | } 27 | 28 | @Override 29 | public JMenu[] getTopLevelMenus() { 30 | return new JMenu[0]; 31 | } 32 | 33 | @Override 34 | public boolean localeChanged(MenuElement menu) { 35 | return false; 36 | } 37 | 38 | @Override 39 | public void localeChanged() { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/DebuggerMenuItem.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import org.apache.jmeter.gui.GuiPackage; 4 | import org.apache.jmeter.gui.MainFrame; 5 | import org.apache.jmeter.gui.util.JMeterToolBar; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import javax.swing.*; 10 | import java.awt.*; 11 | import java.awt.event.ActionEvent; 12 | import java.awt.event.ActionListener; 13 | 14 | public class DebuggerMenuItem extends JMenuItem implements ActionListener { 15 | private static final Logger log = LoggerFactory.getLogger(DebuggerMenuItem.class); 16 | 17 | private static DebuggerDialog dialog; 18 | 19 | public DebuggerMenuItem() { 20 | super("Step-by-Step Debugger", getBugIcon(false)); 21 | addActionListener(this); 22 | addToolbarIcon(); 23 | } 24 | 25 | private void addToolbarIcon() { 26 | GuiPackage instance = GuiPackage.getInstance(); 27 | if (instance != null) { 28 | final MainFrame mf = instance.getMainFrame(); 29 | final ComponentFinder finder = new ComponentFinder<>(JMeterToolBar.class); 30 | SwingUtilities.invokeLater(new Runnable() { 31 | @Override 32 | public void run() { 33 | JMeterToolBar toolbar = null; 34 | while (toolbar == null) { 35 | try { 36 | Thread.sleep(1000); 37 | } catch (InterruptedException e) { 38 | log.debug("Did not add btn to toolbar", e); 39 | } 40 | log.debug("Searching for toolbar"); 41 | toolbar = finder.findComponentIn(mf); 42 | } 43 | 44 | int pos = 21; 45 | Component toolbarButton = getToolbarButton(); 46 | toolbarButton.setSize(toolbar.getComponent(pos).getSize()); 47 | toolbar.add(toolbarButton, pos); 48 | } 49 | }); 50 | } 51 | } 52 | 53 | private Component getToolbarButton() { 54 | JButton button = new JButton(getBugIcon(true)); 55 | button.setToolTipText("Step-by-step Debugger"); 56 | //button.setPressedIcon(new ImageIcon(imageURLPressed)); 57 | button.addActionListener(this); 58 | //button.setActionCommand(iconBean.getActionNameResolve()); 59 | return button; 60 | } 61 | 62 | @Override 63 | public void actionPerformed(ActionEvent e) { 64 | if (dialog == null) { 65 | dialog = new DebuggerDialog(); 66 | } 67 | 68 | dialog.pack(); 69 | 70 | dialog.setVisible(true); 71 | } 72 | 73 | // many from http://www.veryicon.com/icons/system/fugue/ 74 | public static ImageIcon getBugIcon(boolean large) { 75 | if (large) { 76 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/bug22.png")); 77 | } else { 78 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/bug.png")); 79 | } 80 | } 81 | 82 | public static ImageIcon getStartIcon() { 83 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/start.png")); 84 | } 85 | 86 | public static ImageIcon getStopIcon() { 87 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/stop.png")); 88 | } 89 | 90 | public static ImageIcon getStepIcon() { 91 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/step.png")); 92 | } 93 | 94 | public static ImageIcon getLogoIcon() { 95 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/logo.png")); 96 | } 97 | 98 | public static ImageIcon getBPIcon() { 99 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/breakpoint.png")); 100 | } 101 | 102 | public static ImageIcon getContinueIcon() { 103 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/continue.png")); 104 | } 105 | 106 | public static ImageIcon getPauseIcon() { 107 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/pause.png")); 108 | } 109 | 110 | public static Icon getHelpIcon() { 111 | return new ImageIcon(DebuggerMenuItem.class.getResource("/com/blazemeter/jmeter/debugger/help.png")); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/EvaluatePanel.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import org.apache.jmeter.engine.util.CompoundVariable; 4 | import org.apache.jmeter.samplers.SampleResult; 5 | import org.apache.jmeter.samplers.Sampler; 6 | import org.apache.jmeter.threads.JMeterContext; 7 | import org.apache.jmeter.threads.JMeterContextService; 8 | 9 | import javax.swing.*; 10 | import java.awt.*; 11 | import java.awt.event.ActionEvent; 12 | import java.awt.event.ActionListener; 13 | import java.io.ByteArrayOutputStream; 14 | import java.io.PrintStream; 15 | 16 | 17 | public class EvaluatePanel extends JPanel implements ActionListener { 18 | private JTextField exprField = new JTextField(); 19 | private JButton doBtn = new JButton("Evaluate"); 20 | private LoggerPanelWrapping result = new LoggerPanelWrapping(); 21 | private JMeterContext context = JMeterContextService.getContext(); 22 | 23 | public EvaluatePanel() { 24 | super(new BorderLayout()); 25 | 26 | JPanel container = new JPanel(new BorderLayout()); 27 | container.add(new JLabel("JMeter Expression: "), BorderLayout.WEST); 28 | container.add(exprField, BorderLayout.CENTER); 29 | container.add(doBtn, BorderLayout.EAST); 30 | 31 | add(container, BorderLayout.NORTH); 32 | add(new JScrollPane(result), BorderLayout.CENTER); 33 | 34 | exprField.addActionListener(new ActionListener() { 35 | @Override 36 | public void actionPerformed(ActionEvent actionEvent) { 37 | doBtn.doClick(); 38 | } 39 | }); 40 | 41 | doBtn.addActionListener(this); 42 | } 43 | 44 | public void refresh(JMeterContext ctx, boolean continuing) { 45 | result.clear(); 46 | doBtn.setEnabled(!continuing); 47 | actionPerformed(new ActionEvent(this, 0, "")); 48 | this.context = ctx; 49 | } 50 | 51 | @Override 52 | public void setEnabled(boolean enabled) { 53 | super.setEnabled(enabled); 54 | //exprField.setEditable(enabled); 55 | result.setEnabled(enabled); 56 | doBtn.setEnabled(enabled); 57 | } 58 | 59 | @Override 60 | public void actionPerformed(ActionEvent actionEvent) { 61 | result.clear(); 62 | if (exprField.getText().isEmpty()) { 63 | return; 64 | } 65 | 66 | CompoundVariable masterFunction = new CompoundVariable(exprField.getText()); 67 | SampleResult previousResult = context.getPreviousResult(); 68 | Sampler currentSampler = context.getCurrentSampler(); 69 | try { 70 | result.setText(masterFunction.execute(previousResult, currentSampler)); 71 | } catch (Throwable e) { 72 | ByteArrayOutputStream text = new ByteArrayOutputStream(1024); 73 | e.printStackTrace(new PrintStream(text)); 74 | result.setText(text.toString()); 75 | result.scrollToTop(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/FixedJMeterTreeCellRenderer.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import com.blazemeter.jmeter.debugger.elements.OriginalLink; 4 | import org.apache.jmeter.gui.tree.JMeterCellRenderer; 5 | import org.apache.jmeter.gui.tree.JMeterTreeNode; 6 | import org.apache.jmeter.testelement.TestElement; 7 | 8 | import javax.swing.*; 9 | import java.awt.*; 10 | 11 | 12 | public class FixedJMeterTreeCellRenderer extends JMeterCellRenderer { 13 | private final NodeHighlighter hiliter; 14 | 15 | public FixedJMeterTreeCellRenderer(NodeHighlighter hiliter) { 16 | super(); 17 | this.hiliter = hiliter; 18 | } 19 | 20 | @Override 21 | public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean p_hasFocus) { 22 | JMeterTreeNode node = (JMeterTreeNode) value; 23 | TestElement mc = node.getTestElement(); 24 | 25 | JMeterTreeNode fakeNode = (JMeterTreeNode) node.clone(); 26 | if (mc instanceof OriginalLink) { 27 | fakeNode.setUserObject(((OriginalLink) mc).getOriginal()); 28 | } else { 29 | fakeNode.setUserObject(mc); 30 | } 31 | Component treeCellRendererComponent = super.getTreeCellRendererComponent(tree, fakeNode, sel, expanded, leaf, row, p_hasFocus); 32 | hiliter.highlightNode(treeCellRendererComponent, node, mc); 33 | return treeCellRendererComponent; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/HighlightTable.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import javax.swing.*; 7 | import javax.swing.table.TableCellRenderer; 8 | import javax.swing.table.TableModel; 9 | import javax.swing.table.TableRowSorter; 10 | import java.awt.*; 11 | import java.util.LinkedList; 12 | 13 | public class HighlightTable extends JTable { 14 | private static final Logger log = LoggerFactory.getLogger(HighlightTable.class); 15 | 16 | 17 | public HighlightTable(TableModel model) { 18 | super(model); 19 | setDefaultEditor(Object.class, null); 20 | setSorter(model); 21 | } 22 | 23 | private void setSorter(TableModel model) { 24 | TableRowSorter sorter = new TableRowSorter<>(model); 25 | sorter.setSortsOnUpdates(true); 26 | LinkedList sortKeys = new LinkedList<>(); 27 | sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING)); 28 | sorter.setSortKeys(sortKeys); 29 | sorter.sort(); 30 | setRowSorter(sorter); 31 | } 32 | 33 | @Override 34 | public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { 35 | Component comp = super.prepareRenderer(renderer, row, column); 36 | if (getModel() instanceof HighlightTableModel) { 37 | HighlightTableModel model = (HighlightTableModel) getModel(); 38 | try { 39 | Object valueAt = getValueAt(row, 0); 40 | if (valueAt != null && model.isRowHighlighted(valueAt.toString(), getValueAt(row, 1))) { 41 | comp.setFont(comp.getFont().deriveFont(Font.BOLD)); 42 | } else { 43 | comp.setFont(comp.getFont().deriveFont(~Font.BOLD)); 44 | } 45 | } catch (IndexOutOfBoundsException e) { 46 | log.debug("Problems rendering ", e); 47 | } 48 | } 49 | return comp; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/HighlightTableModel.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import org.apache.jmeter.gui.util.PowerTableModel; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class HighlightTableModel extends PowerTableModel { 9 | 10 | private Map oldData = null; 11 | 12 | public HighlightTableModel(String[] strings, Class[] classes) { 13 | super(strings, classes); 14 | } 15 | 16 | public boolean isRowHighlighted(String curName, Object curValue) { 17 | if (oldData == null) { 18 | return false; 19 | } 20 | 21 | Object oldValue = oldData.get(curName); 22 | return curValue != null && !curValue.equals(oldValue); 23 | } 24 | 25 | @Override 26 | public void clearData() { 27 | if (oldData == null && getData().size() == 0) { 28 | super.clearData(); 29 | } else { 30 | oldData = new HashMap<>(); 31 | for (int row = 0; row < getRowCount(); row++) { 32 | Object[] rowData = getRowData(row); 33 | Object o = rowData[0]; 34 | if (o != null) { 35 | oldData.put(o.toString(), rowData[1]); 36 | } 37 | } 38 | super.clearData(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/LoggerPanelWrapping.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | 4 | import org.apache.jmeter.gui.LoggerPanel; 5 | import org.apache.jmeter.gui.util.JSyntaxTextArea; 6 | 7 | public class LoggerPanelWrapping extends LoggerPanel { 8 | private JSyntaxTextArea area; 9 | 10 | public LoggerPanelWrapping() { 11 | super(); 12 | 13 | ComponentFinder finder = new ComponentFinder<>(JSyntaxTextArea.class); 14 | area = finder.findComponentIn(this); 15 | area.setLineWrap(true); 16 | } 17 | 18 | public void setText(String txt) { 19 | if (area != null) { 20 | area.setText(txt); 21 | } 22 | } 23 | 24 | public void scrollToTop() { 25 | area.setCaretPosition(0); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/NodeHighlighter.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import org.apache.jmeter.gui.tree.JMeterTreeNode; 4 | import org.apache.jmeter.testelement.TestElement; 5 | 6 | import java.awt.*; 7 | 8 | public interface NodeHighlighter { 9 | void highlightNode(Component treeCellRendererComponent, JMeterTreeNode node, TestElement mc); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/gui/ThreadGroupItemRenderer.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import org.apache.jmeter.threads.AbstractThreadGroup; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | 8 | public final class ThreadGroupItemRenderer implements ListCellRenderer { 9 | private final ListCellRenderer originalRenderer; 10 | 11 | public ThreadGroupItemRenderer(final ListCellRenderer originalRenderer) { 12 | this.originalRenderer = originalRenderer; 13 | } 14 | 15 | @Override 16 | public Component getListCellRendererComponent(JList list, AbstractThreadGroup value, int index, boolean isSelected, boolean cellHasFocus) { 17 | String name = ""; 18 | if (value != null) { 19 | name = value.getName(); 20 | } 21 | //noinspection unchecked 22 | return originalRenderer.getListCellRendererComponent(list, name, index, isSelected, cellHasFocus); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/blazemeter/jmeter/debugger/logging/LoggerPanelAppender.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.logging; 2 | 3 | import com.blazemeter.jmeter.debugger.gui.LoggerPanelWrapping; 4 | import org.apache.logging.log4j.Level; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.core.LogEvent; 7 | import org.apache.logging.log4j.core.LoggerContext; 8 | import org.apache.logging.log4j.core.StringLayout; 9 | import org.apache.logging.log4j.core.appender.AbstractAppender; 10 | import org.apache.logging.log4j.core.config.Configuration; 11 | import org.apache.logging.log4j.core.config.plugins.Plugin; 12 | import org.apache.logging.log4j.core.layout.PatternLayout; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.lang.reflect.Constructor; 17 | import java.lang.reflect.Method; 18 | 19 | 20 | @Plugin(name = "Logger", category = "Core", elementType = "appender", printObject = true) 21 | public class LoggerPanelAppender extends AbstractAppender { 22 | private static final Logger log = LoggerFactory.getLogger(LoggerPanelAppender.class); 23 | public static final String DEFAULT_PATTERN = "%d %p %c{1.}: %m%n"; 24 | 25 | private final LoggerPanelWrapping panelWrapping; 26 | private Method processLogEventMethod; 27 | private Constructor logEventObjectConstructor; 28 | 29 | public LoggerPanelAppender(String name, LoggerPanelWrapping panelWrapping) { 30 | super(name, null, PatternLayout.newBuilder().withPattern(DEFAULT_PATTERN).build()); 31 | start(); 32 | Configuration configuration = ((LoggerContext) LogManager.getContext(false)).getConfiguration(); 33 | configuration.getRootLogger().addAppender(this, Level.INFO, null); 34 | this.panelWrapping = panelWrapping; 35 | initializeProcessLogEventMethod(); 36 | initializeLogEventObjectConstructor(); 37 | } 38 | 39 | private void initializeProcessLogEventMethod() { 40 | try { 41 | Method[] methods = panelWrapping.getClass().getSuperclass().getMethods(); 42 | for (Method method : methods) { 43 | if ("processLogEvent".equals(method.getName())) { 44 | processLogEventMethod = method; 45 | break; 46 | } 47 | } 48 | } catch (Throwable ex) { 49 | log.error("Cannot find 'processLogEventMethod' method for initialize logging panel", ex); 50 | } 51 | } 52 | 53 | private void initializeLogEventObjectConstructor() { 54 | try { 55 | Class cls = Class.forName("org.apache.jmeter.gui.logging.LogEventObject"); 56 | logEventObjectConstructor = cls.getConstructor(Object.class, String.class); 57 | } catch (Throwable ex) { 58 | log.error("Cannot find constructor for class 'LogEventObject'", ex); 59 | } 60 | } 61 | 62 | @Override 63 | public void append(LogEvent logEvent) { 64 | if (processLogEventMethod != null && logEventObjectConstructor != null) { 65 | final String serializedString = getStringLayout().toSerializable(logEvent); 66 | if (serializedString != null && !serializedString.isEmpty()) { 67 | postLogEventObject(logEvent, serializedString); 68 | } 69 | } 70 | } 71 | 72 | private void postLogEventObject(LogEvent logEvent, String serializedString) { 73 | Object logEventObject = createLogEventObject(logEvent, serializedString); 74 | if (logEventObject != null) { 75 | try { 76 | processLogEventMethod.invoke(panelWrapping, logEventObject); 77 | } catch (Throwable ex) { 78 | log.error("Cannot post logEventObject", ex); 79 | } 80 | } 81 | } 82 | 83 | private Object createLogEventObject(LogEvent logEvent, String serializedString) { 84 | try { 85 | return logEventObjectConstructor.newInstance(logEvent, serializedString); 86 | } catch (Throwable ex) { 87 | log.error("Cannot create instance of class 'LogEventObject'", ex); 88 | return null; 89 | } 90 | } 91 | 92 | public StringLayout getStringLayout() { 93 | return (StringLayout) getLayout(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/apache/jmeter/threads/JMeterContextServiceAccessor.java: -------------------------------------------------------------------------------- 1 | package org.apache.jmeter.threads; 2 | 3 | /** 4 | * Just a way to access the methods 5 | */ 6 | public class JMeterContextServiceAccessor { 7 | 8 | public static void removeContext() { 9 | JMeterContextService.removeContext(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/breakpoint.png -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/bug.png -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/bug22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/bug22.png -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/continue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/continue.png -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/help.png -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/logo.png -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/pause.png -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/start.png -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/step.png -------------------------------------------------------------------------------- /src/main/resources/com/blazemeter/jmeter/debugger/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/jmeter-debugger/e46f95ec53745a0da4d622978b994e3f6cdf1690/src/main/resources/com/blazemeter/jmeter/debugger/stop.png -------------------------------------------------------------------------------- /src/test/java/com/blazemeter/jmeter/debugger/FrontendMock.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger; 2 | 3 | import com.blazemeter.jmeter.debugger.elements.Wrapper; 4 | import com.blazemeter.jmeter.debugger.engine.DebuggerFrontend; 5 | import org.apache.jmeter.threads.JMeterContext; 6 | 7 | 8 | public class FrontendMock implements DebuggerFrontend { 9 | @Override 10 | public void started() { 11 | 12 | } 13 | 14 | @Override 15 | public void stopped() { 16 | 17 | } 18 | 19 | @Override 20 | public void continuing() { 21 | 22 | } 23 | 24 | @Override 25 | public void frozenAt(Wrapper wrapper) { 26 | 27 | } 28 | 29 | @Override 30 | public void statusRefresh(JMeterContext context) { 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/blazemeter/jmeter/debugger/StepTriggerCounter.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger; 2 | 3 | import com.blazemeter.jmeter.debugger.elements.Wrapper; 4 | import com.blazemeter.jmeter.debugger.engine.StepTrigger; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | 9 | public class StepTriggerCounter implements StepTrigger { 10 | private static final Logger log = LoggerFactory.getLogger(StepTriggerCounter.class); 11 | 12 | public int cnt; 13 | 14 | @Override 15 | public void stepOn(Wrapper t) { 16 | log.warn("Stop before: " + t.getWrappedElement()); 17 | cnt += 1; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/blazemeter/jmeter/debugger/TestProvider.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger; 2 | 3 | 4 | import com.blazemeter.jmeter.debugger.engine.SearchClass; 5 | import com.blazemeter.jmeter.debugger.engine.TestTreeProvider; 6 | import kg.apc.emulators.TestJMeterUtils; 7 | import org.apache.jmeter.exceptions.IllegalUserActionException; 8 | import org.apache.jmeter.gui.tree.JMeterTreeModel; 9 | import org.apache.jmeter.gui.tree.JMeterTreeNode; 10 | import org.apache.jmeter.save.SaveService; 11 | import org.apache.jmeter.threads.AbstractThreadGroup; 12 | import org.apache.jorphan.collections.HashTree; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.util.Collection; 17 | 18 | public class TestProvider implements TestTreeProvider { 19 | private JMeterTreeModel mdl = new JMeterTreeModel(); 20 | 21 | public TestProvider() throws IllegalUserActionException, IOException { 22 | File file = new File(this.getClass().getResource("/com/blazemeter/jmeter/debugger/sample1.jmx").getFile()); 23 | String basedir = TestJMeterUtils.fixWinPath(file.getParentFile().getAbsolutePath()); 24 | 25 | File f = new File(basedir + "/sample1.jmx"); 26 | mdl.addSubTree(SaveService.loadTree(f), (JMeterTreeNode) mdl.getRoot()); 27 | } 28 | 29 | public TestProvider(String path, String name) throws IllegalUserActionException, IOException { 30 | File file = new File(this.getClass().getResource(path).getFile()); 31 | String basedir = TestJMeterUtils.fixWinPath(file.getParentFile().getAbsolutePath()); 32 | 33 | File f = new File(basedir + '/' + name); 34 | mdl.addSubTree(SaveService.loadTree(f), (JMeterTreeNode) mdl.getRoot()); 35 | } 36 | 37 | @Override 38 | public HashTree getTestTree() { 39 | return mdl.getTestPlan(); 40 | } 41 | 42 | public AbstractThreadGroup getTG(int i) { 43 | SearchClass searcher = new SearchClass<>(AbstractThreadGroup.class); 44 | mdl.getTestPlan().traverse(searcher); 45 | Collection searchResults = searcher.getSearchResults(); 46 | return searchResults.toArray(new AbstractThreadGroup[0])[i]; 47 | } 48 | 49 | public JMeterTreeModel getTreeModel() { 50 | return mdl; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/blazemeter/jmeter/debugger/elements/DebuggingThreadGroupGuiTest.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.elements; 2 | 3 | import kg.apc.emulators.TestJMeterUtils; 4 | import org.junit.Test; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | 9 | public class DebuggingThreadGroupGuiTest { 10 | @Test 11 | public void test() throws InterruptedException { 12 | TestJMeterUtils.createJmeterEnv(); 13 | DebuggingThreadGroupGui obj = new DebuggingThreadGroupGui(); 14 | 15 | if (!GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance()) { 16 | JFrame frame = new JFrame(); 17 | frame.setSize(800, 600); 18 | frame.add(obj); 19 | frame.setVisible(true); 20 | Thread.sleep(10000); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/java/com/blazemeter/jmeter/debugger/engine/DebuggerEngineTest.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.engine; 2 | 3 | import com.blazemeter.jmeter.debugger.FrontendMock; 4 | import com.blazemeter.jmeter.debugger.StepTriggerCounter; 5 | import com.blazemeter.jmeter.debugger.TestProvider; 6 | import com.blazemeter.jmeter.debugger.elements.DebuggingThreadGroup; 7 | import kg.apc.emulators.TestJMeterUtils; 8 | import org.apache.jmeter.JMeter; 9 | import org.apache.jmeter.assertions.AssertionResult; 10 | import org.apache.jmeter.control.LoopController; 11 | import org.apache.jmeter.engine.StandardJMeterEngine; 12 | import org.apache.jmeter.reporters.ResultCollector; 13 | import org.apache.jmeter.samplers.SampleEvent; 14 | import org.apache.jmeter.samplers.SampleListener; 15 | import org.apache.jmeter.samplers.SampleResult; 16 | import org.apache.jmeter.threads.AbstractThreadGroup; 17 | import org.apache.jmeter.threads.JMeterContextService; 18 | import org.apache.jorphan.collections.HashTree; 19 | import org.junit.BeforeClass; 20 | import org.junit.Test; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collection; 24 | import java.util.List; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertFalse; 28 | import static org.junit.Assert.assertNull; 29 | import static org.junit.Assert.assertTrue; 30 | 31 | 32 | public class DebuggerEngineTest { 33 | @BeforeClass 34 | public static void setup() { 35 | TestJMeterUtils.createJmeterEnv(); 36 | } 37 | 38 | @Test 39 | public void runRealEngine() throws Exception { 40 | TestTreeProvider prov = new TestProvider(); 41 | 42 | HashTree hashTree = prov.getTestTree(); 43 | JMeter.convertSubTree(hashTree); 44 | 45 | StandardJMeterEngine engine = new StandardJMeterEngine(); 46 | engine.configure(hashTree); 47 | engine.runTest(); 48 | while (engine.isActive()) { 49 | Thread.sleep(1000); 50 | } 51 | } 52 | 53 | 54 | @Test 55 | public void runDebugEngine() throws Exception { 56 | TestProvider prov = new TestProvider(); 57 | 58 | Debugger sel = new Debugger(prov, new FrontendMock()); 59 | AbstractThreadGroup tg = prov.getTG(0); 60 | sel.selectThreadGroup(tg); 61 | HashTree testTree = sel.getSelectedTree(); 62 | 63 | DebuggingThreadGroup tg2 = (DebuggingThreadGroup) getFirstTG(testTree); 64 | LoopController samplerController = (LoopController) tg2.getSamplerController(); 65 | samplerController.setLoops(1); 66 | samplerController.setContinueForever(false); 67 | 68 | JMeter.convertSubTree(testTree); 69 | 70 | DebuggerEngine engine = new DebuggerEngine(JMeterContextService.getContext()); 71 | StepTriggerCounter hook = new StepTriggerCounter(); 72 | engine.setStepper(hook); 73 | engine.configure(testTree); 74 | engine.runTest(); 75 | while (engine.isActive()) { 76 | Thread.sleep(1000); 77 | } 78 | assertEquals(88, hook.cnt); 79 | } 80 | 81 | 82 | @Test 83 | public void runVariablesDebugEngine() throws Exception { 84 | TestProvider prov = new TestProvider("/com/blazemeter/jmeter/debugger/vars.jmx", "vars.jmx"); 85 | 86 | Debugger sel = new Debugger(prov, new FrontendMock()); 87 | AbstractThreadGroup tg = prov.getTG(0); 88 | sel.selectThreadGroup(tg); 89 | HashTree testTree = sel.getSelectedTree(); 90 | 91 | TestSampleListener listener = new TestSampleListener(); 92 | testTree.add(testTree.getArray()[0], listener); 93 | 94 | DebuggingThreadGroup tg2 = (DebuggingThreadGroup) getFirstTG(testTree); 95 | LoopController samplerController = (LoopController) tg2.getSamplerController(); 96 | samplerController.setLoops(1); 97 | samplerController.setContinueForever(false); 98 | 99 | JMeter.convertSubTree(testTree); 100 | 101 | DebuggerEngine engine = new DebuggerEngine(JMeterContextService.getContext()); 102 | StepTriggerCounter hook = new StepTriggerCounter(); 103 | engine.setStepper(hook); 104 | engine.configure(testTree); 105 | engine.runTest(); 106 | while (engine.isActive()) { 107 | Thread.sleep(1000); 108 | } 109 | assertEquals(8, hook.cnt); 110 | 111 | assertEquals(3, listener.events.size()); 112 | for (SampleEvent event : listener.events) { 113 | SampleResult res = event.getResult(); 114 | String label = res.getSampleLabel(); 115 | assertTrue("Label: " + label + " must end with '123'", label.endsWith("123")); 116 | assertFalse("Variable ${VAR} must be changed to '123' value. label: " + label, label.contains("${VAR}")); 117 | assertTrue("label: '" + label + "' response: '" + res.getResponseMessage() +"'", res.isSuccessful()); 118 | } 119 | } 120 | 121 | public class TestSampleListener extends ResultCollector implements SampleListener { 122 | public List events = new ArrayList<>(); 123 | 124 | @Override 125 | public void sampleOccurred(SampleEvent e) { 126 | events.add(e); 127 | } 128 | 129 | @Override 130 | public void sampleStarted(SampleEvent e) { 131 | events.add(e); 132 | } 133 | 134 | @Override 135 | public void sampleStopped(SampleEvent e) { 136 | events.add(e); 137 | } 138 | } 139 | 140 | private AbstractThreadGroup getFirstTG(HashTree tree) { 141 | SearchClass searcher = new SearchClass<>(AbstractThreadGroup.class); 142 | tree.traverse(searcher); 143 | Collection searchResults = searcher.getSearchResults(); 144 | return searchResults.toArray(new AbstractThreadGroup[0])[0]; 145 | } 146 | 147 | @Test 148 | public void runVariablesInAssertions() throws Exception { 149 | TestProvider prov = new TestProvider("/com/blazemeter/jmeter/debugger/debug.jmx", "debug.jmx"); 150 | 151 | Debugger sel = new Debugger(prov, new FrontendMock()); 152 | AbstractThreadGroup tg = prov.getTG(0); 153 | sel.selectThreadGroup(tg); 154 | HashTree testTree = sel.getSelectedTree(); 155 | 156 | TestSampleListener listener = new TestSampleListener(); 157 | testTree.add(testTree.getArray()[0], listener); 158 | 159 | DebuggingThreadGroup tg2 = (DebuggingThreadGroup) getFirstTG(testTree); 160 | LoopController samplerController = (LoopController) tg2.getSamplerController(); 161 | samplerController.setLoops(1); 162 | samplerController.setContinueForever(false); 163 | 164 | JMeter.convertSubTree(testTree); 165 | 166 | DebuggerEngine engine = new DebuggerEngine(JMeterContextService.getContext()); 167 | StepTriggerCounter hook = new StepTriggerCounter(); 168 | engine.setStepper(hook); 169 | engine.configure(testTree); 170 | engine.runTest(); 171 | while (engine.isActive()) { 172 | Thread.sleep(1000); 173 | } 174 | assertEquals(4, hook.cnt); 175 | 176 | assertEquals(1, listener.events.size()); 177 | SampleEvent event = listener.events.get(0); 178 | SampleResult result = event.getResult(); 179 | AssertionResult[] assertionResults = result.getAssertionResults(); 180 | assertEquals(1, assertionResults.length); 181 | 182 | AssertionResult assertionRes = assertionResults[0]; 183 | assertNull(assertionRes.getFailureMessage()); 184 | } 185 | 186 | @Test 187 | public void runVariablesInControllers() throws Exception { 188 | TestProvider prov = new TestProvider("/com/blazemeter/jmeter/debugger/loops.jmx", "loops.jmx"); 189 | 190 | Debugger sel = new Debugger(prov, new FrontendMock()); 191 | AbstractThreadGroup tg = prov.getTG(0); 192 | sel.selectThreadGroup(tg); 193 | HashTree testTree = sel.getSelectedTree(); 194 | 195 | TestSampleListener listener = new TestSampleListener(); 196 | testTree.add(testTree.getArray()[0], listener); 197 | 198 | DebuggingThreadGroup tg2 = (DebuggingThreadGroup) getFirstTG(testTree); 199 | LoopController samplerController = (LoopController) tg2.getSamplerController(); 200 | samplerController.setLoops(1); 201 | samplerController.setContinueForever(false); 202 | 203 | JMeter.convertSubTree(testTree); 204 | 205 | DebuggerEngine engine = new DebuggerEngine(JMeterContextService.getContext()); 206 | StepTriggerCounter hook = new StepTriggerCounter(); 207 | engine.setStepper(hook); 208 | engine.configure(testTree); 209 | engine.runTest(); 210 | while (engine.isActive()) { 211 | Thread.sleep(1000); 212 | } 213 | assertEquals(12, hook.cnt); 214 | 215 | assertEquals(3, listener.events.size()); 216 | } 217 | } -------------------------------------------------------------------------------- /src/test/java/com/blazemeter/jmeter/debugger/engine/DebuggerTest.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.engine; 2 | 3 | import com.blazemeter.jmeter.debugger.FrontendMock; 4 | import com.blazemeter.jmeter.debugger.TestProvider; 5 | import kg.apc.emulators.TestJMeterUtils; 6 | import org.apache.jmeter.control.Controller; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | public class DebuggerTest { 11 | @Test 12 | public void testMain() throws Exception { 13 | TestJMeterUtils.createJmeterEnv(); 14 | TestProvider treeProvider = new TestProvider(); 15 | Debugger dbg = new Debugger(treeProvider, new FrontendMock()); 16 | dbg.selectThreadGroup(treeProvider.getTG(0)); 17 | 18 | Debugger.toggleBreakpoint(treeProvider.getTG(0)); 19 | 20 | dbg.start(); 21 | Thread.sleep(5000); 22 | Assert.assertFalse(dbg.isContinuing()); 23 | Assert.assertTrue(dbg.getCurrentElement() instanceof Controller); 24 | dbg.proceed(); 25 | Thread.sleep(1000); 26 | dbg.proceed(); 27 | Assert.assertTrue(dbg.getCurrentSampler() != null); 28 | Thread.sleep(1000); 29 | dbg.continueRun(); 30 | Thread.sleep(2000); 31 | dbg.pause(); 32 | Thread.sleep(1000); 33 | dbg.stop(); 34 | } 35 | } -------------------------------------------------------------------------------- /src/test/java/com/blazemeter/jmeter/debugger/engine/DebuggingThreadTest.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.engine; 2 | 3 | import com.blazemeter.jmeter.debugger.elements.Wrapper; 4 | import kg.apc.emulators.TestJMeterUtils; 5 | import org.apache.jmeter.save.SaveService; 6 | import org.apache.jmeter.threads.AbstractThreadGroup; 7 | import org.apache.jmeter.threads.JMeterContext; 8 | import org.apache.jmeter.threads.JMeterContextService; 9 | import org.apache.jmeter.threads.JMeterThread; 10 | import org.apache.jmeter.threads.JMeterThreadMonitor; 11 | import org.apache.jmeter.threads.ListenerNotifier; 12 | import org.apache.jorphan.collections.HashTree; 13 | import org.apache.jorphan.collections.ListedHashTree; 14 | import org.apache.jorphan.collections.SearchByClass; 15 | import org.junit.BeforeClass; 16 | import org.junit.Test; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.util.Collection; 23 | 24 | public class DebuggingThreadTest { 25 | private static final Logger log = LoggerFactory.getLogger(DebuggingThreadTest.class); 26 | 27 | public static final StepTrigger hook = new StepTrigger() { 28 | @Override 29 | public void stepOn(Wrapper o) { 30 | log.info(">>> Stopping before step: " + o.getWrappedElement() + " <<<"); 31 | try { 32 | Thread.sleep(1000); 33 | } catch (InterruptedException e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | }; 38 | 39 | @BeforeClass 40 | public static void setUp() throws Exception { 41 | TestJMeterUtils.createJmeterEnv(); 42 | } 43 | 44 | @Test 45 | public void testBasic() throws IOException { 46 | File file = new File(this.getClass().getResource("/com/blazemeter/jmeter/debugger/sample1.jmx").getFile()); 47 | String basedir = TestJMeterUtils.fixWinPath(file.getParentFile().getAbsolutePath()); 48 | 49 | File f = new File(basedir + "/sample1.jmx"); 50 | HashTree tree = SaveService.loadTree(f); 51 | JMeterThreadMonitor monitor = new JMeterThreadMonitor() { 52 | @Override 53 | public void threadFinished(JMeterThread thread) { 54 | 55 | } 56 | }; 57 | ListenerNotifier note = new ListenerNotifier(); 58 | 59 | SearchByClass searcher = new SearchByClass<>(AbstractThreadGroup.class); 60 | tree.traverse(searcher); 61 | Collection iter = searcher.getSearchResults(); 62 | for (AbstractThreadGroup tg : iter) { 63 | ListedHashTree tgTree = (ListedHashTree) searcher.getSubTree(tg); 64 | JMeterContext context = JMeterContextService.getContext(); 65 | DebuggerEngine engine = new DebuggerEngine(context); 66 | engine.setStepper(hook); 67 | context.setEngine(engine); 68 | DebuggingThread thread = new DebuggingThread(tgTree, monitor, note, context); 69 | thread.setThreadName("Test"); 70 | thread.setThreadGroup(tg); 71 | thread.run(); 72 | } 73 | 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /src/test/java/com/blazemeter/jmeter/debugger/gui/DebuggerDialogTest.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import com.blazemeter.jmeter.debugger.TestProvider; 4 | import com.blazemeter.jmeter.debugger.elements.SamplerDebug; 5 | import kg.apc.emulators.TestJMeterUtils; 6 | import org.apache.jmeter.exceptions.IllegalUserActionException; 7 | import org.apache.jmeter.functions.TimeFunction; 8 | import org.apache.jmeter.gui.GuiPackage; 9 | import org.apache.jmeter.gui.MainFrame; 10 | import org.apache.jmeter.gui.action.ActionRouter; 11 | import org.apache.jmeter.gui.tree.JMeterTreeListener; 12 | import org.apache.jmeter.gui.tree.JMeterTreeModel; 13 | import org.apache.jmeter.gui.util.JMeterToolBar; 14 | import org.apache.jmeter.threads.JMeterContextService; 15 | import org.apache.jmeter.util.JMeterUtils; 16 | import org.apache.jmeter.visualizers.RenderAsHTML; 17 | import org.apache.jorphan.collections.HashTree; 18 | import org.junit.Before; 19 | import org.junit.BeforeClass; 20 | import org.junit.Test; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import javax.swing.*; 25 | import java.awt.*; 26 | import java.awt.event.ActionEvent; 27 | import java.awt.event.ActionListener; 28 | import java.io.IOException; 29 | import java.text.SimpleDateFormat; 30 | import java.util.Date; 31 | 32 | public class DebuggerDialogTest { 33 | private static final Logger log = LoggerFactory.getLogger(DebuggerDialogTest.class); 34 | 35 | @BeforeClass 36 | public static void setUp() { 37 | TestJMeterUtils.createJmeterEnv(); 38 | } 39 | 40 | @Before 41 | public void setUpMethod() { 42 | } 43 | 44 | @Test 45 | public void testGui() throws Exception { 46 | if (GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance()) { 47 | return; 48 | } 49 | String actions = ActionRouter.class.getProtectionDomain().getCodeSource().getLocation().getFile(); 50 | String renderers = RenderAsHTML.class.getProtectionDomain().getCodeSource().getLocation().getFile(); 51 | JMeterUtils.setProperty("search_paths", actions + ";" + renderers); 52 | TestProvider prov = new TestProvider(); 53 | JMeterTreeModel mdl = prov.getTreeModel(); 54 | JMeterTreeListener a = new JMeterTreeListener(); 55 | a.setActionHandler(new ActionListener() { 56 | @Override 57 | public void actionPerformed(ActionEvent actionEvent) { 58 | log.debug("Action " + actionEvent); 59 | } 60 | }); 61 | a.setModel(mdl); 62 | GuiPackage.getInstance(a, mdl); 63 | 64 | DebuggerDialog obj = new DebuggerDialogMock(prov.getTreeModel()); 65 | obj.componentShown(null); 66 | obj.started(); 67 | obj.statusRefresh(JMeterContextService.getContext()); 68 | obj.frozenAt(new SamplerDebug()); 69 | obj.continuing(); 70 | obj.stopped(); 71 | obj.componentHidden(null); 72 | } 73 | 74 | @Test 75 | public void displayGUI() throws InterruptedException, IOException, IllegalUserActionException { 76 | if (!GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance()) { 77 | TestProvider prov = new TestProvider("/com/blazemeter/jmeter/debugger/vars.jmx", "vars.jmx"); 78 | JMeterTreeModel mdl = prov.getTreeModel(); 79 | JMeterTreeListener a = new JMeterTreeListener(); 80 | a.setActionHandler(new ActionListener() { 81 | @Override 82 | public void actionPerformed(ActionEvent actionEvent) { 83 | log.debug("Action " + actionEvent); 84 | } 85 | }); 86 | a.setModel(mdl); 87 | 88 | GuiPackage.getInstance(a, mdl); 89 | String actions = ActionRouter.class.getProtectionDomain().getCodeSource().getLocation().getFile(); 90 | String renderers = RenderAsHTML.class.getProtectionDomain().getCodeSource().getLocation().getFile(); 91 | JMeterUtils.setProperty("search_paths", actions + ";" + renderers); 92 | MainFrame mf = new MainFrame(mdl, a); // does important stuff inside 93 | ComponentFinder finder = new ComponentFinder<>(JMeterToolBar.class); 94 | JMeterToolBar tb = finder.findComponentIn(mf); 95 | tb.add(new JButton("test")); 96 | 97 | new TimeFunction(); 98 | long now = System.currentTimeMillis(); 99 | JMeterUtils.setProperty("START.MS", Long.toString(now)); 100 | Date today = new Date(now); 101 | JMeterUtils.setProperty("START.YMD", new SimpleDateFormat("yyyyMMdd").format(today)); 102 | JMeterUtils.setProperty("START.HMS", new SimpleDateFormat("HHmmss").format(today)); 103 | 104 | DebuggerDialogMock frame = new DebuggerDialogMock(mdl); 105 | 106 | frame.setPreferredSize(new Dimension(800, 600)); 107 | frame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 108 | frame.pack(); 109 | frame.setVisible(true); 110 | while (frame.isVisible()) { 111 | Thread.sleep(1000); 112 | } 113 | } 114 | } 115 | 116 | private class DebuggerDialogMock extends DebuggerDialog { 117 | private final JMeterTreeModel mdl; 118 | 119 | public DebuggerDialogMock(JMeterTreeModel b) { 120 | mdl = b; 121 | } 122 | 123 | @Override 124 | public HashTree getTestTree() { 125 | return mdl.getTestPlan(); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /src/test/java/com/blazemeter/jmeter/debugger/gui/DebuggerMenuCreatorTest.java: -------------------------------------------------------------------------------- 1 | package com.blazemeter.jmeter.debugger.gui; 2 | 3 | import org.apache.jmeter.gui.plugin.MenuCreator; 4 | import org.junit.Test; 5 | 6 | import javax.swing.*; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | 11 | public class DebuggerMenuCreatorTest { 12 | @Test 13 | public void test() throws Exception { 14 | DebuggerMenuCreator obj = new DebuggerMenuCreator(); 15 | JMenuItem[] res = obj.getMenuItemsAtLocation(MenuCreator.MENU_LOCATION.RUN); 16 | assertEquals(1, res.length); 17 | obj.getTopLevelMenus(); 18 | obj.localeChanged(); 19 | obj.localeChanged(null); 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/resources/com/blazemeter/jmeter/debugger/debug.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | item 12 | JMeterProperties: 13 | = 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | continue 22 | 23 | false 24 | 1 25 | 26 | 1 27 | 1 28 | 1508918846000 29 | 1508918846000 30 | false 31 | 32 | 33 | 34 | 35 | 36 | true 37 | false 38 | false 39 | 40 | 41 | 42 | 43 | ${item} 44 | 45 | Assertion.response_data 46 | false 47 | 2 48 | 49 | 50 | 51 | 52 | false 53 | 54 | saveConfig 55 | 56 | 57 | true 58 | true 59 | true 60 | 61 | true 62 | true 63 | true 64 | true 65 | false 66 | true 67 | true 68 | false 69 | false 70 | false 71 | true 72 | false 73 | false 74 | false 75 | true 76 | 0 77 | true 78 | true 79 | true 80 | true 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | true 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/test/resources/com/blazemeter/jmeter/debugger/loops.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | count 12 | 2 13 | = 14 | 15 | 16 | time 17 | 10000 18 | = 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | continue 27 | 28 | false 29 | 1 30 | 31 | 1 32 | 1 33 | 1508924562000 34 | 1508924562000 35 | false 36 | 37 | 38 | 39 | 40 | 41 | true 42 | ${count} 43 | 44 | 45 | 46 | false 47 | true 48 | false 49 | 50 | 51 | 52 | 53 | false 54 | true 55 | false 56 | 57 | 58 | 59 | false 60 | 61 | saveConfig 62 | 63 | 64 | true 65 | true 66 | true 67 | 68 | true 69 | true 70 | true 71 | true 72 | false 73 | true 74 | true 75 | false 76 | false 77 | false 78 | true 79 | false 80 | false 81 | false 82 | true 83 | 0 84 | true 85 | true 86 | true 87 | true 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | true 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/test/resources/com/blazemeter/jmeter/debugger/sample1.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | startnextloop 16 | 17 | false 18 | 1 19 | 20 | 1 21 | 1 22 | 1462985384000 23 | 1462985384000 24 | false 25 | 26 | 27 | 28 | 29 | 30 | true 31 | 3 32 | 33 | 34 | 35 | false 36 | false 37 | 38 | 39 | 40 | true 41 | true 42 | false 43 | 44 | 45 | 46 | true 47 | true 48 | false 49 | 50 | 51 | 52 | 53 | false 54 | true 55 | false 56 | 57 | 58 | 59 | 60 | Assertion.response_data 61 | false 62 | 16 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | false 71 | 72 | 73 | 74 | 300 75 | 76 | 77 | 78 | 79 | 300 80 | 81 | 82 | 83 | log.info("Set prop to ${cnt}: ${__setProperty(testprop,${cnt},true)}"); 84 | 85 | 86 | 87 | false 88 | 89 | 90 | 91 | false 92 | true 93 | true 94 | false 95 | 96 | 97 | 98 | 1 99 | 100 | 1 101 | cnt 102 | 103 | false 104 | 105 | 106 | 107 | 1 108 | 109 | 1 110 | zcnt 111 | 112 | false 113 | 114 | 115 | 116 | 117 | 118 | WorkBench 119 | Test Plan 120 | Test Fragment 121 | 122 | 123 | 124 | 125 | 126 | startnextloop 127 | 128 | false 129 | 1 130 | 131 | 1 132 | 1 133 | 1463145530000 134 | 1463145530000 135 | false 136 | 137 | 138 | 139 | 140 | 141 | false 142 | true 143 | 144 | 145 | 146 | false 147 | true 148 | false 149 | 150 | 151 | 152 | false 153 | true 154 | false 155 | 156 | 157 | 158 | 159 | 160 | false 161 | 162 | saveConfig 163 | 164 | 165 | true 166 | true 167 | true 168 | 169 | true 170 | true 171 | true 172 | true 173 | false 174 | true 175 | true 176 | false 177 | false 178 | false 179 | true 180 | false 181 | false 182 | false 183 | true 184 | 0 185 | true 186 | true 187 | true 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | false 197 | true 198 | false 199 | 200 | 201 | 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /src/test/resources/com/blazemeter/jmeter/debugger/vars.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | USER_VAR 12 | 123 13 | = 14 | 15 | 16 | ServerIP 17 | blazedemo.com 18 | = 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | continue 27 | 28 | false 29 | 1 30 | 31 | 1 32 | 1 33 | 1499768746000 34 | 1499768746000 35 | false 36 | 37 | 38 | 39 | 40 | 41 | false 42 | false 43 | 44 | 45 | 46 | 47 | 48 | 49 | ${ServerIP} 50 | 51 | 52 | 53 | 54 | GET 55 | true 56 | false 57 | true 58 | false 59 | 60 | 61 | 62 | 63 | 64 | 65 | true 66 | true 67 | true 68 | 69 | 70 | 71 | 72 | false 73 | 74 | saveConfig 75 | 76 | 77 | true 78 | true 79 | true 80 | 81 | true 82 | true 83 | true 84 | true 85 | false 86 | true 87 | true 88 | false 89 | false 90 | false 91 | true 92 | false 93 | false 94 | false 95 | true 96 | 0 97 | true 98 | true 99 | true 100 | true 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | true 110 | 111 | 112 | 113 | 114 | --------------------------------------------------------------------------------