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

Througput Shaping Timer, Througput Controller and Concurrency Thread Group #26

Closed
kirillyu opened this issue Oct 6, 2021 · 22 comments
Closed

Comments

@kirillyu
Copy link
Contributor

kirillyu commented Oct 6, 2021

We in our company prefere the rps-based tests. In the start of proccess we have the profile table:

  1. /api/v1/auth - 20 rps
  2. /api/getProduct - 10 rps
  3. /api/basket - 5 rps
    etc.
    You can see the example here
    template.zip

So to release this script in your DSL we need this guys: Througput Shaping Timer, Througput Controller and Concurrency Thread Group

@rabelenda
Copy link
Contributor

rabelenda commented Oct 6, 2021

Hello,

Thank you very much to try the DSL and take the time for asking for features and giving really interesting info about your use case! This really helps.

We will consider adding the support for future release. Stay tuned :)

Regards

@kirillyu
Copy link
Contributor Author

kirillyu commented Oct 6, 2021

It would be cool if you could make the guide. How to add plugins or features to DSL

@rabelenda
Copy link
Contributor

rabelenda commented Oct 6, 2021

Have you checked the Contributing guide?

In this case it seems that the final implementation wouldn't be that linear and some items may need a "redesign". For instance, throughput controller has a misleading name (re-afirmed by jmeter documentation itself) which might need to be re thought to ease DSL users with no JMeter knowledge. Also, the combination of throughput shaping timer with concurrency thread group with target concurrency using __tstFeedback function looks like a common pattern that may be nice for the DSL to abstract/simplify.

In any case we are eager to receive PRs and contributions, and we can iterate/improve from there.

Thank you again for asking.

@kirillyu
Copy link
Contributor Author

kirillyu commented Oct 7, 2021

The peak of my dreams is a super smart timer based only on the load profile. He sees only requests and their intensities and sets them up individually for everyone. Operate at the input only with the multiplier of the entire profile. Well, or Rump-up table.

It seems like killer feature. This is because people suffer a lot with paicing and profile interpetation.

The guide seem's good. I will try to help :)

@rabelenda
Copy link
Contributor

rabelenda commented Oct 8, 2021

Hello, I don't know if I properly understood your proposal for the "killer feature" :). How it differs from the Throughput Shaping Timer?

Additionally, I have a question with the JMX you sent: Why are you using a Throughput Shaping Timer that only evaluates once per iteration? Is this intentional? Do you actually want to control the number of times per second the inner loop (with all requests executing a certain percent of the times) executes? Or are you more interested on actually control the number of requests per second (the actual load imposed to the target system)? If you are more interested on the later, why have you disabled the timer at thread group level and had added a flow action with a timer on it?

Thank you very much for all the information, and proposal. It is really interesting. We are planning to work on this so let me know if you plan to work in some part avoid rework :-).

Initial draft of the design would look something like:

rpsThreadGroup()
   .maxThreads(1000)
   .rampToAndHold(10, Duration.ofSeconds(10), Duration.ofMinutes(10))
   .rampToAndHold(20, Duration.ofMinutes(1), Duration.ofMinutes(10))
   .rampToAndHold(30, Duration.ofMinutes(1), Duration.ofMinutes(10))
   .rampToAndHold(40, Duration.ofMinutes(1), Duration.ofMinutes(10))
   .children(
      request1,
      percentSelector(80, request2),
      percentSelector(60, request3),
      percentSelector(40, request4),
      percentSelector(20, request5),
   )

Let us know if this makes sense, or if you have any other ideas.

@kirillyu
Copy link
Contributor Author

kirillyu commented Oct 9, 2021

Hello again! In this template there are two differenet ways. All of them I using. If I need to control the total load i create the timer in the root. So, In this case I don't need to control RPS per operations and if I add the sampler in the test plan intensity will redistribute.
Another way when I need to fix intensity for existing requests. In this way I create the timer as child of Flow Action Control and all of sampler will get a part of specified intensity or get it all. Than I can to add the new sampler and noting change for anothers.

It's hard to support, because the intensity or distribution of requests changing. So I need to go and fix the timer and values in Throughput Controllers. Because of this I would like to set the load profile without iteration layer -> but in requests and their intensities. In this case we see another problem - transactions that need to follow the sequence of requests (google translate part, hope you understand and sorry for my English!). the solution to this problem is to pass the profile directly, like, probable structure. I'll try to imagine by pseudo code.

struct profile ApiGatewayLoad {
request("POST /api/authorization/code", 40) //method rps
 .childrens{
            request ("GET /api/profile", 80)         //this guy can't execute whiot authoristion ("method",rps) 
             request("POST /api/avatar", 10)
}
request("GET /api/main_page", 50) // independent guys
request("GET /redirect_link", 20)
}

rpsThreadGroup()
     .maxThreads(1000) //in this place maybe use the __tstFeddback settings, but optional
     .useProfile(ApiGatewayLoad)
     .rampToAndHold(0.5, Duration.ofSeconds(10), Duration.ofMinutes(10)) // 0.5 is multiply of base profile
     .rampToAndHold(1, Duration.ofMinutes(1), Duration.ofMinutes(10))
     .rampToAndHold(1.5, Duration.ofMinutes(1), Duration.ofMinutes(10))
     .rampToAndHold(2, Duration.ofMinutes(1), Duration.ofMinutes(10))

In this case only one problem, childrens that have more intensity than parents can be achievable only by loop realisation, but in a some app logic it can be unrepeatable with same params (create an order with with same id, etc.). This case probable need to add "repetable" flag in profile struct. If sampler is not repatable it can't has more intensity than parent (can be implemented
by parallel controller). But this probleme is not ruine the MVP of this idea.

If you need to explain something in more detail - I will help.

In the next month I will not be able to participate in the development of DSL. I need to improve my java skill. And decide what is the minimum set of features we need to switch to DSL

@rabelenda
Copy link
Contributor

rabelenda commented Oct 19, 2021

With latest release you can now use percentController to implement part of the logic you need.

@kirillyu
Copy link
Contributor Author

Seem's good. I will try!

@rabelenda
Copy link
Contributor

rabelenda commented Oct 22, 2021

Hello,

We have just released a new version which adds rpsThreadGroup. Please try it and let us know how it works or if you find any other missing features to implement scenarios as the one you proposed.

I think it might not directly fit the use case you described with profiles, but I don't know yet if I fully understood the idea behind what you described. Maybe what you propose can be implemented a little different like:

RpsThreadGroup buildThreadGroup(int baseRps) {
     return rpsThreadGroup()
          .maxThreads(100)
          .rampToAndHold(0.5 * baseRps, Duration.ofSeconds(10), Duration.ofMinutes(10))
          .rampToAndHold(1 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
          .rampToAndHold(1.5 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
          .rampToAndHold(2 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
}

testplan(
     buildThreadGroup(40)
          .counting(EventType.ITERATIONS)
          .children(
               httpSampler("/api/authorization/code").post(..., ...),
               loopController(2, //this is not yet implemented, but easy to implement and for the meantime you might just duplicate the sampler
                    httpSampler("/api/profile")
               )
               percentController(25, 
                    httpSampler("/api/avatar").post(..., ...)
               )
          ),
     buildThreadGroup(50)
          .children(
               httpSampler("/api/main_page") // as this is in a separate thread group, this will run in parallel with previous requests
          ),
     buildThreadGroup(20)
          .children(
               httpSampler("/redirect_link") // same comment regarding parallel execution
          )
).run()

I don't really know if there are parts that may run in parallel (as I modeled above), or if in fact they have to run sequentially (or how each request relates to other). Let me know if this adapts to what you proposed and need.

Regards.

@rabelenda
Copy link
Contributor

rabelenda commented Oct 22, 2021

You may also parameterize it to make it easer for edition according to your settings. Here is an example of how it could look like:

RpsThreadGroup buildThreadGroup(int baseRps) {
     return rpsThreadGroup()
          .maxThreads(100)
          .rampToAndHold(0.5 * baseRps, Duration.ofSeconds(10), Duration.ofMinutes(10))
          .rampToAndHold(1 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
          .rampToAndHold(1.5 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
          .rampToAndHold(2 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10));
}

ThreadGroupChild rpsController(int targetRps, int parentRps, ThreadGroupChild... children) {
     double factor = (double) targetRps / parentRps;
     if (factor > 1) {
          double fraction = factor - Math.floor(factor)
          long loops = Math.ceil(factor)
          return loopController(loops, fraction == 0.0 ? children : percentController((double) factor / loops * 100, children))
     } else {
          return percentController(factor * 100, children);
     }
}

int authRps = 40;
int profileRps = 80;
int avatarRps = 10;
int mainRps = 50;
int redirectRps = 20;

testplan(
     buildThreadGroup(authRps)
          .counting(EventType.ITERATIONS)
          .children(
               httpSampler("/api/authorization/code").post(..., ...),
               rpsController(profileRps, authRps,
                    httpSampler("/api/profile")
               ),
               rpsController(avatarRps, authRps,
                    httpSampler("/api/avatar").post(..., ...)
               )
          ),
     buildThreadGroup(mainRps)
          .children(
               httpSampler("/api/main_page") // as this is in a separate thread group, this will run in parallel with previous requests
          ),
     buildThreadGroup(redirectRps)
          .children(
               httpSampler("/redirect_link") // same comment regarding parallel execution
          )
).run()

@kirillyu
Copy link
Contributor Author

kirillyu commented Oct 25, 2021

Seems interesting but this code cannot be compiled:

RpsThreadGroup buildThreadGroup(int baseRps) { return rpsThreadGroup() .maxThreads(100) .rampToAndHold(0.5 * baseRps, Duration.ofSeconds(10), Duration.ofMinutes(10)) .rampToAndHold(1 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10)) .rampToAndHold(1.5 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10)) .rampToAndHold(2 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10)) }

I not understand how it implements in Jmeter tree. In this case will be 3 separeted thread groups? Or how the buildThreadGroup works?

@rabelenda
Copy link
Contributor

rabelenda commented Oct 25, 2021

The code was just an example pseudo code. Now it compiles with the exception of loopController that is not yet implemented:

import static us.abstracta.jmeter.javadsl.JmeterDsl.*;

import java.time.Duration;
import org.eclipse.jetty.http.MimeTypes.Type;
import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild;
import us.abstracta.jmeter.javadsl.core.threadgroups.RpsThreadGroup;

public class Test {

  private RpsThreadGroup buildThreadGroup(int baseRps) {
    return rpsThreadGroup()
        .maxThreads(100)
        .rampToAndHold(0.5 * baseRps, Duration.ofSeconds(10), Duration.ofMinutes(10))
        .rampToAndHold(1 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
        .rampToAndHold(1.5 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
        .rampToAndHold(2 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10));
  }

  private ThreadGroupChild rpsController(int targetRps, int parentRps, ThreadGroupChild... children) {
    double factor = (double) targetRps / parentRps;
    if (factor > 1) {
      double fraction = factor - Math.floor(factor);
      long loops = (long) Math.ceil(factor);
      return loopController(loops,
          fraction == 0.0 ? children : percentController((float) factor / loops * 100, children));
    } else {
      return percentController((float) factor * 100, children);
    }
  }

  @Test
  public void test() throws Exception {
    int authRps = 40;
    int profileRps = 80;
    int avatarRps = 10;
    int mainRps = 50;
    int redirectRps = 20;

    testPlan(
        buildThreadGroup(authRps)
            .counting(RpsThreadGroup.EventType.ITERATIONS)
            .children(
                httpSampler("/api/authorization/code").post("test", Type.TEXT_PLAIN),
                rpsController(profileRps, authRps,
                    httpSampler("/api/profile")
                ),
                rpsController(avatarRps, authRps,
                    httpSampler("/api/avatar").post("test", Type.TEXT_PLAIN)
                )
            ),
        buildThreadGroup(mainRps)
            .children(
                httpSampler("/api/main_page")
                // as this is in a separate thread group, this will run in parallel with previous requests
            ),
        buildThreadGroup(redirectRps)
            .children(
                httpSampler("/redirect_link") // same comment regarding parallel execution
            )
    ).run();
  }

}

yes, buildThreadGroup is just an auxiliary custom method (you can use any java modularity and code constructs, which are not nativelly supported by jmeter, or are not the msae) to reuse the logic. It will end up creating 3 thread groups as it is, yes.

@kirillyu
Copy link
Contributor Author

kirillyu commented Oct 25, 2021

Sorry, didn't read the comment. Thread groups are separeted. The separation is not need here. If i wiil need the parallel logic, I will just create the thread group with basic methods :)
It will seem's much better without this.
So if there will be:

  1. RpsThreadGroup structures for differents profiles like:
    private RpsThreadGroup searchMax(int baseRps)
    private RpsThreadGroup stabilityTests(int baseRps)
    private RpsThreadGroup anyCustomTest(int baseRps)
    for test type choosen
  2. RPS block like you wrote:
    int authRps = 40; int profileRps = 80; int avatarRps = 10; int mainRps = 50; int redirectRps = 20;
    as profile wich can be specified in test plas. (In this case I will use the map sctruct probable like avatarRps = [10, "api/avatar"])
  3. And the test plan:
    testPlan(stabilityTests) .children( httpSampler("/api/authorization/code").post("test", Type.TEXT_PLAIN), rpsController(profileRps, authRps, httpSampler("/api/profile") ), rpsController(avatarRps, authRps, httpSampler("/api/avatar").post("test", Type.TEXT_PLAIN) ) ).run()
    with all nessesary logic

will be extreamelly good!

@kirillyu
Copy link
Contributor Author

I am not understanding what is missing for this. Or is there a way how to implement it?

@rabelenda
Copy link
Contributor

rabelenda commented Oct 25, 2021

The only thing missing implementation from the example I sent is the loopController, which should be included in an upcoming release. The rest is already available with the latest release.

Here is another alternative (which I haven't tested, and might need some tuning) that would be more similar to what you originally requested and maybe is more similar to what you expect in general:

import static us.abstracta.jmeter.javadsl.JmeterDsl.*;

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.junit.jupiter.api.Test;
import us.abstracta.jmeter.javadsl.core.DslSampler;
import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild;
import us.abstracta.jmeter.javadsl.core.TestPlanStats;
import us.abstracta.jmeter.javadsl.core.threadgroups.RpsThreadGroup;

public class PerformanceTest {

  private static class RpsFragmentProfile {

    private final int rps;
    private final DslSampler sampler;
    private final List<RpsFragmentProfile> children;

    private RpsFragmentProfile(int rps, DslSampler sampler, RpsFragmentProfile... children) {
      this.rps = rps;
      this.sampler = sampler;
      this.children = Arrays.asList(children);
    }

    private List<ThreadGroupChild> buildTestPlanPartWithBaseRps(int parentRps) {
      List<ThreadGroupChild> ret = new ArrayList<>();
      ret.add(sampler);
      ret.addAll(children.stream()
          .flatMap(c -> c.buildTestPlanPartWithBaseRps(rps).stream())
          .collect(Collectors.toList()));
      if (rps == parentRps) {
        return ret;
      }
      double factor = (double) rps / parentRps;
      ThreadGroupChild[] retArr = ret.toArray(new ThreadGroupChild[0]);
      if (factor > 1) {
        double fraction = factor - Math.floor(factor);
        long loops = (long) Math.ceil(factor);
        return Collections.singletonList(loopController(loops,
            fraction == 0.0 ? retArr : percentController((float) factor / loops * 100, retArr)));
      } else {
        return Collections.singletonList(percentController((float) factor * 100, retArr));
      }
    }

  }

  private static class RpsTestPlanProfile {

    private final List<RpsFragmentProfile> fragments = new ArrayList<>();

    private RpsTestPlanProfile add(int rps, DslSampler sampler,
        RpsFragmentProfile... children) {
      fragments.add(new RpsFragmentProfile(rps, sampler, children));
      return this;
    }

    private TestPlanStats run(Function<Integer, RpsThreadGroup> threadGroupBuilder)
        throws IOException {
      int maxRps = fragments.stream().mapToInt(c -> c.rps).max().orElse(0);
      return testPlan(threadGroupBuilder.apply(maxRps)
          .counting(RpsThreadGroup.EventType.ITERATIONS)
          .children(
              fragments.stream()
                  .flatMap(r -> r.buildTestPlanPartWithBaseRps(maxRps).stream())
                  .toArray(value -> new ThreadGroupChild[0])
          )).run();
    }

  }

  private static RpsThreadGroup buildThreadGroup(int baseRps) {
    return rpsThreadGroup()
        .maxThreads(100)
        .rampToAndHold(0.5 * baseRps, Duration.ofSeconds(10), Duration.ofMinutes(10))
        .rampToAndHold(1 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
        .rampToAndHold(1.5 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
        .rampToAndHold(2 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10));
  }

  @Test
  public void test() throws Exception {
    new RpsTestPlanProfile()
        .add(40, httpSampler("/api/authorization/code").post("test", Type.TEXT_PLAIN),
            new RpsFragmentProfile(80, httpSampler("/api/profile")),
            new RpsFragmentProfile(10, httpSampler("/api/avatar").post("test", Type.TEXT_PLAIN)))
        .add(50, httpSampler("/api/main_page"))
        .add(20, httpSampler("/redirect_link"))
        .run(PerformanceTest::buildThreadGroup);
  }

}

@rabelenda
Copy link
Contributor

rabelenda commented Oct 26, 2021

Hello,

We don't plan in the short term to integrate the previous approach directly into de DSL, since sounds to cover pretty specific need, and the basic building blocks are already built into the DSL. If the community in general manifest more interest in such kind of built-in feature, it could make sense to create an extension module either in this repo or separate repo which users may include when they want to use such feature (you may create a repo on your own if you want with such extension and we may reference from this one). Another alternative might just be putting this example code in some place as reference examples of extensibility and ease of creating additional abstractions on top of what the DSL already provides.

Let me know what do you think. In the scenario that you think might be nice for community to manifest interest on this, I would say to create a separate issue for it (separating from the initial need of the components already released).

If you don't have more comments related to this issue, we can just close it.

Regards

@rabelenda
Copy link
Contributor

We have just released a new version which include forLoopController, supporting previous examples (with slight naming variation).

@kirillyu
Copy link
Contributor Author

This is complitely closed my issue with my "stange" view on test plans.
I tried to migrate my projects on it and see only one issue which blocked me to start total migration. This issue about Extractor, so wait for it. And sorry, really have no time for contributing right now.

@rabelenda
Copy link
Contributor

rabelenda commented Oct 27, 2021

Hello,

I don't think your view on test plans is "strange", but I am not sure is a common case either. We greatly appreciate new ideas and takes, and understanding and providing also an example for this has been very interesting for us. Maybe in the future makes sense to implement such an abstraction, but at this point doesn't sound like something we should include in DSL (maybe something to re consider in the future). In any case, it is an awesome example and exploration of capabilities about what can we do with the DSL which would be more difficult to do with the JMeter GUI.

We greatly appreciate that you have brought this into picture. If you have any similar views, comments, ideas or takes we would love to hear them.

Regarding the extractor and contribution time, don't worry, we totally understand. We love getting PR, but just requesting needs with proper justification and giving ideas is a contribution to the project as well, so thank you again for that.

Regards

@rabelenda
Copy link
Contributor

The final example with some fix and adapting to the name of loop controller (just for the record):

import static us.abstracta.jmeter.javadsl.JmeterDsl.*;

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.junit.jupiter.api.Test;
import us.abstracta.jmeter.javadsl.core.DslSampler;
import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild;
import us.abstracta.jmeter.javadsl.core.TestPlanStats;
import us.abstracta.jmeter.javadsl.core.threadgroups.RpsThreadGroup;

public class PerformanceTest {

  private static class RpsFragmentProfile {

    private final int rps;
    private final DslSampler sampler;
    private final List<RpsFragmentProfile> children;

    private RpsFragmentProfile(int rps, DslSampler sampler, RpsFragmentProfile... children) {
      this.rps = rps;
      this.sampler = sampler;
      this.children = Arrays.asList(children);
    }

    private List<ThreadGroupChild> buildTestPlanPartWithBaseRps(int parentRps) {
      List<ThreadGroupChild> ret = new ArrayList<>();
      ret.add(sampler);
      ret.addAll(children.stream()
          .flatMap(c -> c.buildTestPlanPartWithBaseRps(rps).stream())
          .collect(Collectors.toList()));
      if (rps == parentRps) {
        return ret;
      }
      double factor = (double) rps / parentRps;
      ThreadGroupChild[] retArr = ret.toArray(new ThreadGroupChild[0]);
      if (factor > 1) {
        double fraction = factor - Math.floor(factor);
        int loops = (int) Math.ceil(factor);
        return Collections.singletonList(forLoopController(loops, fraction == 0.0 ? retArr
            : new ThreadGroupChild[]{percentController((float) factor / loops * 100, retArr)}));
      } else {
        return Collections.singletonList(percentController((float) factor * 100, retArr));
      }
    }

  }

  private static class RpsTestPlanProfile {

    private final List<RpsFragmentProfile> fragments = new ArrayList<>();

    private RpsTestPlanProfile add(int rps, DslSampler sampler,
        RpsFragmentProfile... children) {
      fragments.add(new RpsFragmentProfile(rps, sampler, children));
      return this;
    }

    private TestPlanStats run(Function<Integer, RpsThreadGroup> threadGroupBuilder)
        throws IOException {
      int maxRps = fragments.stream().mapToInt(c -> c.rps).max().orElse(0);
      return testPlan(threadGroupBuilder.apply(maxRps)
          .counting(RpsThreadGroup.EventType.ITERATIONS)
          .children(
              fragments.stream()
                  .flatMap(r -> r.buildTestPlanPartWithBaseRps(maxRps).stream())
                  .toArray(value -> new ThreadGroupChild[0])
          )).run();
    }

  }

  private static RpsThreadGroup buildThreadGroup(int baseRps) {
    return rpsThreadGroup()
        .maxThreads(100)
        .rampToAndHold(0.5 * baseRps, Duration.ofSeconds(10), Duration.ofMinutes(10))
        .rampToAndHold(1 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
        .rampToAndHold(1.5 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
        .rampToAndHold(2 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10));
  }

  @Test
  public void test() throws Exception {
    new RpsTestPlanProfile()
        .add(40, httpSampler("/api/authorization/code").post("test", Type.TEXT_PLAIN),
            new RpsFragmentProfile(80, httpSampler("/api/profile")),
            new RpsFragmentProfile(10, httpSampler("/api/avatar").post("test", Type.TEXT_PLAIN)))
        .add(50, httpSampler("/api/main_page"))
        .add(20, httpSampler("/redirect_link"))
        .run(PerformanceTest::buildThreadGroup);
  }

}

@rabelenda
Copy link
Contributor

From 0.33 on, the example needs to be adapted into this due to classes relocation:

import static us.abstracta.jmeter.javadsl.JmeterDsl.forLoopController;
import static us.abstracta.jmeter.javadsl.JmeterDsl.httpSampler;
import static us.abstracta.jmeter.javadsl.JmeterDsl.percentController;
import static us.abstracta.jmeter.javadsl.JmeterDsl.rpsThreadGroup;
import static us.abstracta.jmeter.javadsl.JmeterDsl.testPlan;

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jetty.http.MimeTypes.Type;
import org.junit.jupiter.api.Test;
import us.abstracta.jmeter.javadsl.core.TestPlanStats;
import us.abstracta.jmeter.javadsl.core.testelements.DslSampler;
import us.abstracta.jmeter.javadsl.core.threadgroups.BaseThreadGroup.ThreadGroupChild;
import us.abstracta.jmeter.javadsl.core.threadgroups.RpsThreadGroup;

public class PerformanceTest {

  private static class RpsFragmentProfile {

    private final int rps;
    private final DslSampler sampler;
    private final List<RpsFragmentProfile> children;

    private RpsFragmentProfile(int rps, DslSampler sampler, RpsFragmentProfile... children) {
      this.rps = rps;
      this.sampler = sampler;
      this.children = Arrays.asList(children);
    }

    private List<ThreadGroupChild> buildTestPlanPartWithBaseRps(int parentRps) {
      List<ThreadGroupChild> ret = new ArrayList<>();
      ret.add(sampler);
      ret.addAll(children.stream()
          .flatMap(c -> c.buildTestPlanPartWithBaseRps(rps).stream())
          .collect(Collectors.toList()));
      if (rps == parentRps) {
        return ret;
      }
      double factor = (double) rps / parentRps;
      ThreadGroupChild[] retArr = ret.toArray(new ThreadGroupChild[0]);
      if (factor > 1) {
        double fraction = factor - Math.floor(factor);
        int loops = (int) Math.ceil(factor);
        return Collections.singletonList(forLoopController(loops, fraction == 0.0 ? retArr
            : new ThreadGroupChild[]{percentController((float) factor / loops * 100, retArr)}));
      } else {
        return Collections.singletonList(percentController((float) factor * 100, retArr));
      }
    }

  }

  private static class RpsTestPlanProfile {

    private final List<RpsFragmentProfile> fragments = new ArrayList<>();

    private RpsTestPlanProfile add(int rps, DslSampler sampler,
        RpsFragmentProfile... children) {
      fragments.add(new RpsFragmentProfile(rps, sampler, children));
      return this;
    }

    private TestPlanStats run(Function<Integer, RpsThreadGroup> threadGroupBuilder)
        throws IOException {
      int maxRps = fragments.stream().mapToInt(c -> c.rps).max().orElse(0);
      return testPlan(threadGroupBuilder.apply(maxRps)
          .counting(RpsThreadGroup.EventType.ITERATIONS)
          .children(
              fragments.stream()
                  .flatMap(r -> r.buildTestPlanPartWithBaseRps(maxRps).stream())
                  .toArray(value -> new ThreadGroupChild[0])
          )).run();
    }

  }

  private static RpsThreadGroup buildThreadGroup(int baseRps) {
    return rpsThreadGroup()
        .maxThreads(100)
        .rampToAndHold(0.5 * baseRps, Duration.ofSeconds(10), Duration.ofMinutes(10))
        .rampToAndHold(1 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
        .rampToAndHold(1.5 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10))
        .rampToAndHold(2 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(10));
  }

  @Test
  public void test() throws Exception {
    new RpsTestPlanProfile()
        .add(40, httpSampler("/api/authorization/code").post("test", Type.TEXT_PLAIN),
            new RpsFragmentProfile(80, httpSampler("/api/profile")),
            new RpsFragmentProfile(10, httpSampler("/api/avatar").post("test", Type.TEXT_PLAIN)))
        .add(50, httpSampler("/api/main_page"))
        .add(20, httpSampler("/redirect_link"))
        .run(PerformanceTest::buildThreadGroup);
  }

}

@kirillyu
Copy link
Contributor Author

I already wrote my implementation for this. I will change it. thanks

package com.mechanitis.demo.junit5;

import static us.abstracta.jmeter.javadsl.JmeterDsl.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;


import us.abstracta.jmeter.javadsl.core.testelements.DslSampler;
import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild;
import us.abstracta.jmeter.javadsl.core.configs.DslCsvDataSet;
import us.abstracta.jmeter.javadsl.core.listeners.InfluxDbBackendListener;
import us.abstracta.jmeter.javadsl.core.listeners.JtlWriter;
import us.abstracta.jmeter.javadsl.core.DslThreadGroup;
import us.abstracta.jmeter.javadsl.core.TestPlanStats;
import us.abstracta.jmeter.javadsl.core.DslTestPlan.TestPlanChild;
import us.abstracta.jmeter.javadsl.core.DslTestPlan;
import us.abstracta.jmeter.javadsl.core.threadgroups.RpsThreadGroup;



public class TestBuilder {
    protected static class RpsFragmentProfile {

        private final int rps;
        private final DslSampler sampler;
        private final List<RpsFragmentProfile> children;
    
        protected RpsFragmentProfile(int rps, DslSampler sampler, RpsFragmentProfile... children) {
            this.rps = rps;
            this.sampler = sampler;
            this.children = Arrays.asList(children);
        }
    
        protected List<ThreadGroupChild> buildTestPlanPartWithBaseRps(int parentRps) {
            List<ThreadGroupChild> ret = new ArrayList<>();
            ret.add(sampler);
            ret.addAll(children.stream()
                .flatMap(c -> c.buildTestPlanPartWithBaseRps(rps).stream())
                .collect(Collectors.toList()));
            if (rps == parentRps) {
                return ret;
            }
            double factor = (double) rps / parentRps;
            ThreadGroupChild[] retArr = ret.toArray(new ThreadGroupChild[0]);
            if (factor > 1) {
                double fraction = factor - Math.floor(factor);
                int loops = (int) Math.ceil(factor);
                if (fraction == 0.0) {
                    return Collections.singletonList(forLoopController(loops,retArr));
                } else {
                    return Collections.singletonList(forLoopController(loops,percentController((float) factor / loops * 100, retArr)));
                }
            } else {
                return Collections.singletonList(percentController((float) factor * 100, retArr));
            }
        }
    }

    protected static class SimpleFragmentProfile {

        private final DslSampler sampler;
        private final List<SimpleFragmentProfile> children;

        protected SimpleFragmentProfile(DslSampler sampler, SimpleFragmentProfile... children) {
            this.sampler = sampler;
            this.children = Arrays.asList(children);
        }
        protected List<ThreadGroupChild> buildTestSimplePlanPart() {
            List<ThreadGroupChild> ret = new ArrayList<>();
            ret.add(sampler);
            ret.addAll(children.stream()
                .flatMap(c -> c.buildTestSimplePlanPart().stream())
                .collect(Collectors.toList()));
                return ret;
        }
    }

    protected static class RpsTestPlanProfile {

        protected final static List<RpsFragmentProfile> fragments = new ArrayList<>();

        protected RpsTestPlanProfile add(int rps, DslSampler sampler,
        RpsFragmentProfile... children) {
            fragments.add(new RpsFragmentProfile(rps, sampler, children));
            return this;
        }
        protected static  List<RpsFragmentProfile> getFragments() {
            return fragments;
        }
        protected  RpsThreadGroup RpsThreadGroupCreate(Function<Integer, RpsThreadGroup> threadGroupBuilder) 
        throws IOException {
        int maxRps = RpsTestPlanProfile.getFragments().stream().mapToInt(c -> c.rps).max().orElse(0);
        return threadGroupBuilder.apply(maxRps)
          .counting(RpsThreadGroup.EventType.ITERATIONS)
          .children(
            RpsTestPlanProfile.getFragments().stream()
                  .flatMap(r -> r.buildTestPlanPartWithBaseRps(maxRps).stream())
                  .toArray(value -> new ThreadGroupChild[value])
          );    
        }
    }

    protected static class SimpleTestPlanProfile {

        protected final static List<SimpleFragmentProfile> fragments = new ArrayList<>();

        protected SimpleTestPlanProfile add(DslSampler sampler,
        SimpleFragmentProfile... children) {
            fragments.add(new SimpleFragmentProfile(sampler, children));
            return this;
        }
        protected static  List<SimpleFragmentProfile> getFragments() {
            return fragments;
        }

        protected DslThreadGroup SimpleThreadGroupCreate(Function<Integer,DslThreadGroup> threadGroupBuilder) 
        throws IOException {
        return  threadGroupBuilder.apply(1)
          .children(
            SimpleTestPlanProfile.getFragments().stream()
                  .flatMap(r -> r.buildTestSimplePlanPart().stream())
                  .toArray(value -> new ThreadGroupChild[value])
          );    
        }
    }


    protected static class TestPlanHashMap {
        static List<Object> TestPlanElements = new ArrayList <Object>();
        
        protected static void add(RpsThreadGroup RpsThreadGroup){
            TestPlanElements.add(RpsThreadGroup);
        }

        protected static void add(DslThreadGroup DslThreadGroup){
            TestPlanElements.add(DslThreadGroup);
        }
        
        protected static void add(DslCsvDataSet DslCsvDataSet){
            TestPlanElements.add(DslCsvDataSet);
        }

        protected static void add(JtlWriter jtlWriter){
            TestPlanElements.add(jtlWriter);
        }
        
        protected static void add(InfluxDbBackendListener influxDbListener){
            TestPlanElements.add(influxDbListener);
        }

        protected static DslTestPlan buildTestPlan()
            throws IOException {
            return testPlan(
                TestPlanElements.stream()
                .toArray(value -> new TestPlanChild[value])
            );
        }

        protected static TestPlanStats run(DslTestPlan FinalTestPlan)
            throws IOException {
                return FinalTestPlan.run();
            }
        
        protected static void saveAsJmx(DslTestPlan FinalTestPlan, String Path)
        throws IOException {
            FinalTestPlan.saveAsJmx(Path);
        }
    }
}

The result:

package com.mechanitis.demo.junit5;

import static us.abstracta.jmeter.javadsl.JmeterDsl.*;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import us.abstracta.jmeter.javadsl.core.DslThreadGroup;
import us.abstracta.jmeter.javadsl.core.threadgroups.RpsThreadGroup;


public class PerfTestNew {

  private static DslThreadGroup buildSimpleThreadGroup(int threads) {
    return threadGroup(1,1);
  }

  private static RpsThreadGroup buildThreadGroup(int baseRps) {
    return rpsThreadGroup()
        .maxThreads(100)
        .rampToAndHold(0.5 * baseRps, Duration.ofSeconds(10), Duration.ofMinutes(1))
        .rampToAndHold(1 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(1))
        .rampToAndHold(1.5 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(1))
        .rampToAndHold(2 * baseRps, Duration.ofMinutes(1), Duration.ofMinutes(1));
  } 

  @Test
  public void test() throws Exception {
    String host = "https://host.io";
    TestBuilder.SimpleTestPlanProfile SimpleThreadGroup = new TestBuilder.SimpleTestPlanProfile()
        .add(Samplers.ShowcaseList.get(host)
        .children(
          jsr223PreProcessor(Utils.UtilsGet()),
          jsonExtractor("SHOWCASE_ID", "[0].showcaseId")
        ))
        .add(Samplers.GetShowcaseById.get(host)
        .children(
          jsr223PostProcessor(Utils.showcaseProductGet())
        ));
        
    host = "https:/host.io";
    TestBuilder.RpsTestPlanProfile RpsThreadGroup = new TestBuilder.RpsTestPlanProfile()
        .add(30, Samplers.ProductSearch.get(host)
            .children(
              jsr223PostProcessor("vars.put('PRODUCT_PAYLOAD',props.get('PRODUCTS_PAYLOAD')); System.out.println(props.get('PRODUCTS_PAYLOAD'))")
            ))
        .add(6, Samplers.ProductDictionary.get(host))
        .add(4, Samplers.ProductBatch.get(host));
    
    
    TestBuilder.TestPlanHashMap.add(csvDataSet("showcase.csv"));
    TestBuilder.TestPlanHashMap.add(SimpleThreadGroup.SimpleThreadGroupCreate(PerfTestNew::buildSimpleThreadGroup));
    TestBuilder.TestPlanHashMap.add(csvDataSet("products.csv"));
    TestBuilder.TestPlanHashMap.add(RpsThreadGroup.RpsThreadGroupCreate(PerfTestNew::buildThreadGroup));
    TestBuilder.TestPlanHashMap.saveAsJmx(TestBuilder.TestPlanHashMap.buildTestPlan(), "test5.jmx");
  }
}

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

No branches or pull requests

2 participants