Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowing custom Thread Groups #2343

Closed
asfimport opened this issue Feb 16, 2010 · 8 comments
Closed

Allowing custom Thread Groups #2343

asfimport opened this issue Feb 16, 2010 · 8 comments

Comments

@asfimport
Copy link
Collaborator

Andrey Pohilko (Bug 48749):
One unable to write custom plugin for Thread Groups to have advanced thread start/stop scheduling. Attached patch adds such ability to JMeter.

Responsibility for threads scheduling moved from StandardJMeterEngine to implementations of AbstractThreadGroup.

OS: Windows XP

Blocks:

@asfimport
Copy link
Collaborator Author

Andrey Pohilko (migrated from Bugzilla):
Created attachment 48749.diff: Changes to source files

@asfimport
Copy link
Collaborator Author

Andrey Pohilko (migrated from Bugzilla):
Created attachment AbstractThreadGroup.java: new abstract ThreadGroup

AbstractThreadGroup.java
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.apache.jmeter.threads;

import java.io.Serializable;

import org.apache.jmeter.control.Controller;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.engine.event.LoopIterationListener;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.property.IntegerProperty;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.TestElementProperty;

/**
 * ThreadGroup holds the settings for a JMeter thread group.
 * 
 * This class is intended to be ThreadSafe.
 */
public abstract class AbstractThreadGroup extends AbstractTestElement implements Serializable, Controller {
    /** Action to be taken when a Sampler error occurs */
    public final static String ON_SAMPLE_ERROR = "ThreadGroup.on_sample_error"; // int

    /** Continue, i.e. ignore sampler errors */
    public final static String ON_SAMPLE_ERROR_CONTINUE = "continue";

    /** Stop current thread if sampler error occurs */
    public final static String ON_SAMPLE_ERROR_STOPTHREAD = "stopthread";

    /** Stop test (all threads) if sampler error occurs */
    public final static String ON_SAMPLE_ERROR_STOPTEST = "stoptest";

    /** Stop test NOW (all threads) if sampler error occurs */
    public final static String ON_SAMPLE_ERROR_STOPTEST_NOW = "stoptestnow";

    /** Number of threads in the thread group */
    public final static String NUM_THREADS = "ThreadGroup.num_threads";

    public final static String MAIN_CONTROLLER = "ThreadGroup.main_controller";

    // @GuardedBy("this")
    private int numberOfThreads = 0; // Number of active threads in this group

    /** {@inheritDoc} */
    public boolean isDone() {
        return getSamplerController().isDone();
    }

    /** {@inheritDoc} */
    public Sampler next() {
        return getSamplerController().next();
    }

    /**
     * Get the sampler controller.
     *
     * @return the sampler controller.
     */
    public Controller getSamplerController() {
        Controller c = (Controller) getProperty(MAIN_CONTROLLER).getObjectValue();
        return c;
    }

    /**
     * Set the sampler controller.
     *
     * @param c
     *            the sampler controller.
     */
    public void setSamplerController(LoopController c) {
        c.setContinueForever(false);
        setProperty(new TestElementProperty(MAIN_CONTROLLER, c));
    }

    /**
     * Add a test element.
     *
     * @param child
     *            the test element to add.
     */
    @Override
    public void addTestElement(TestElement child) {
        getSamplerController().addTestElement(child);
    }

    /** {@inheritDoc} */
    public void addIterationListener(LoopIterationListener lis) {
        getSamplerController().addIterationListener(lis);
    }

    /** {@inheritDoc} */
    public void initialize() {
        Controller c = getSamplerController();
        JMeterProperty property = c.getProperty(TestElement.NAME);
        property.setObjectValue(getName()); // Copy our name into that of the controller
        property.setRunningVersion(property.isRunningVersion());// otherwise name reverts
        c.initialize();
    }

    /**
     * Set the total number of threads to start
     *
     * @param numThreads
     *            the number of threads.
     */
    public void setNumThreads(int numThreads) {
        setProperty(new IntegerProperty(NUM_THREADS, numThreads));
    }

    /**
     * Increment the number of active threads
     */
    synchronized void incrNumberOfThreads() {
        numberOfThreads++;
    }

    /**
     * Decrement the number of active threads
     */
    synchronized void decrNumberOfThreads() {
        numberOfThreads--;
    }

    /**
     * Get the number of active threads
     */
    public synchronized int getNumberOfThreads() {
        return numberOfThreads;
    }
    
    /**
     * Get the number of threads.
     *
     * @return the number of threads.
     */
    public int getNumThreads() {
        return this.getPropertyAsInt(AbstractThreadGroup.NUM_THREADS);
    }

    /**
     * Check if a sampler error should cause thread to stop.
     *
     * @return true if thread should stop
     */
    public boolean getOnErrorStopThread() {
        return getPropertyAsString(ThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTHREAD);
    }

    /**
     * Check if a sampler error should cause test to stop.
     *
     * @return true if test (all threads) should stop
     */
    public boolean getOnErrorStopTest() {
        return getPropertyAsString(ThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTEST);
    }

    /**
     * Check if a sampler error should cause test to stop now.
     *
     * @return true if test (all threads) should stop immediately
     */
    public boolean getOnErrorStopTestNow() {
        return getPropertyAsString(ThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTEST_NOW);
    }

    public abstract void scheduleThread(JMeterThread thread);
}

@asfimport
Copy link
Collaborator Author

Andrey Pohilko (migrated from Bugzilla):
Created attachment AbstractThreadGroupGui.java: new abstract ThreadGroupGui

AbstractThreadGroupGui.java
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.apache.jmeter.threads.gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ItemListener;
import java.util.Arrays;
import java.util.Collection;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.JPanel;

import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;

import org.apache.jmeter.gui.AbstractJMeterGuiComponent;
import org.apache.jmeter.gui.action.ActionNames;
import org.apache.jmeter.gui.tree.JMeterTreeNode;
import org.apache.jmeter.gui.util.MenuFactory;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.property.StringProperty;
import org.apache.jmeter.threads.AbstractThreadGroup;
import org.apache.jmeter.util.JMeterUtils;

public abstract class AbstractThreadGroupGui extends AbstractJMeterGuiComponent implements ItemListener {
    // Sampler error action buttons
    private JRadioButton continueBox;

    private JRadioButton stopThrdBox;

    private JRadioButton stopTestBox;

    private JRadioButton stopTestNowBox;

    public Collection<String> getMenuCategories() {
        return Arrays.asList(new String[] { MenuFactory.THREADS });
    }

    public JPopupMenu createPopupMenu() {
        JPopupMenu pop = new JPopupMenu();
        pop.add(MenuFactory.makeMenus(new String[] {
                MenuFactory.CONTROLLERS,
                MenuFactory.CONFIG_ELEMENTS,
                MenuFactory.TIMERS,
                MenuFactory.PRE_PROCESSORS,
                MenuFactory.SAMPLERS,
                MenuFactory.POST_PROCESSORS,
                MenuFactory.ASSERTIONS,
                MenuFactory.LISTENERS,
                },
                JMeterUtils.getResString("add"), // $NON-NLS-1$
                ActionNames.ADD));
        MenuFactory.addEditMenu(pop, true);
        MenuFactory.addFileMenu(pop);
        return pop;
    }

    public void setNode(JMeterTreeNode node) {
        getNamePanel().setNode(node);
    }

    @Override
    public Dimension getPreferredSize() {
        return getMinimumSize();
    }

    protected void init() {
        setLayout(new BorderLayout(0, 5));
        setBorder(makeBorder());

        Box box = Box.createVerticalBox();
        box.add(makeTitlePanel());
        box.add(createOnErrorPanel());
        add(box, BorderLayout.NORTH);
        
        continueBox.setSelected(true);
    }

    private JPanel createOnErrorPanel() {
        JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("sampler_on_error_action"))); // $NON-NLS-1$

        ButtonGroup group = new ButtonGroup();

        continueBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_continue")); // $NON-NLS-1$
        group.add(continueBox);
        panel.add(continueBox);

        stopThrdBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_thread")); // $NON-NLS-1$
        group.add(stopThrdBox);
        panel.add(stopThrdBox);

        stopTestBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test")); // $NON-NLS-1$
        group.add(stopTestBox);
        panel.add(stopTestBox);

        stopTestNowBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test_now")); // $NON-NLS-1$
        group.add(stopTestNowBox);
        panel.add(stopTestNowBox);

        return panel;
    }

    private void setSampleErrorBoxes(AbstractThreadGroup te) {
        if (te.getOnErrorStopTest()) {
            stopTestBox.setSelected(true);
        } else if (te.getOnErrorStopTestNow()) {
            stopTestNowBox.setSelected(true);
        } else if (te.getOnErrorStopThread()) {
            stopThrdBox.setSelected(true);
        } else {
            continueBox.setSelected(true);
        }
    }

    private String onSampleError() {
        if (stopTestBox.isSelected()) {
            return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTEST;
        }
        if (stopTestNowBox.isSelected()) {
            return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTEST_NOW;
        }
        if (stopThrdBox.isSelected()) {
            return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTHREAD;
        }

        // Defaults to continue
        return AbstractThreadGroup.ON_SAMPLE_ERROR_CONTINUE;
    }

   @Override
    public void configure(TestElement tg) {
        super.configure(tg);
        setSampleErrorBoxes((AbstractThreadGroup) tg);
    }
    
   @Override
    protected void configureTestElement(TestElement tg) {
        tg.setProperty(new StringProperty(AbstractThreadGroup.ON_SAMPLE_ERROR, onSampleError()));
    }

    @Override
    public void clearGui(){
        super.clearGui();
        continueBox.setSelected(true);
    }
}

@asfimport
Copy link
Collaborator Author

Andrey Pohilko (migrated from Bugzilla):
There's issue with icons after this patch, JMeter seems to fail finding icon for thread groups... I failed to find a fix for this issue.

@asfimport
Copy link
Collaborator Author

Andrey Pohilko (migrated from Bugzilla):
Maybe it could be better also move total threads number GUI from ThreadGroupGui to AbstractThreadGroupGui because it's common property as "on error" buttons

@asfimport
Copy link
Collaborator Author

Andrey Pohilko (migrated from Bugzilla):
Created attachment 48749.diff: Proposed source code changes

@asfimport
Copy link
Collaborator Author

Andrey Pohilko (migrated from Bugzilla):
Issue with icons solved

Created attachment 48749.diff: Proposed source code changes

@asfimport
Copy link
Collaborator Author

Sebb (migrated from Bugzilla):
Thanks very much!

The code has been applied:

URL: http://svn.apache.org/viewvc?rev=915587&view=rev
Log:
#2343 - Allowing custom Thread Groups

Added:
jakarta/jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java (with props)
jakarta/jmeter/trunk/src/core/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java (with props)
Modified:
jakarta/jmeter/trunk/src/components/org/apache/jmeter/control/gui/ModuleControllerGui.java
jakarta/jmeter/trunk/src/components/org/apache/jmeter/timers/ConstantThroughputTimer.java
jakarta/jmeter/trunk/src/core/org/apache/jmeter/control/gui/TestPlanGui.java
jakarta/jmeter/trunk/src/core/org/apache/jmeter/engine/ConvertListeners.java
jakarta/jmeter/trunk/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java
jakarta/jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuFactory.java
jakarta/jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties
jakarta/jmeter/trunk/src/core/org/apache/jmeter/testelement/TestPlan.java
jakarta/jmeter/trunk/src/core/org/apache/jmeter/threads/JMeterContext.java
jakarta/jmeter/trunk/src/core/org/apache/jmeter/threads/JMeterThread.java
jakarta/jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java
jakarta/jmeter/trunk/src/core/org/apache/jmeter/threads/gui/ThreadGroupGui.java
jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/proxy/ProxyControl.java
jakarta/jmeter/trunk/src/reports/org/apache/jmeter/testelement/ReportPlan.java
jakarta/jmeter/trunk/xdocs/changes.xml

There were a few changes needed:

  • AbstractThreadGroupGui.configureTestElement() needed to call super.configureTestElement()
  • Constructors should not call overridable methods; changed init() and added initGui() to AbstractThreadGroupGui
  • patch included a change to TCPSampler which had already been made (and was irrelevant)
  • no need to patch JMeterVersion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant