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

Performance affected by Config Elements #5875

Closed
octaviand2 opened this issue May 3, 2023 · 24 comments
Closed

Performance affected by Config Elements #5875

octaviand2 opened this issue May 3, 2023 · 24 comments
Labels
Milestone

Comments

@octaviand2
Copy link

Expected behavior

I am expecting that JMeter performance is not affected by Config Elements when using 100 threads (min)

Actual behavior

When my jmx has config elements - large in size - performance is significantly affected.
I need to understand if this is normal or not - would appreciate insights on how jmeter works.
Perhaps this is a memmory issue?

Steps to reproduce the problem

I am attaching a simplified jmx that can be used.

If the HTTP header manager is enabled, performance is reduced by 75%

  • with HTTP header enabled on my laptop I get about 200k TPS
  • with HTTP header disabled on my laptop I get about 800k TPS
    Replicate.jmx.zip

JMeter Version

5.5

Java Version

java version "9.0.4"

OS Version

macos

@FSchumacher
Copy link
Contributor

Well, to get a real insight, look at the source :)
That said, I looked at the source (not for the first time) and am always a bit confused by it :)
So, the header element has to be prepared by the thread group, as it doesn't know, whether it will be used, or not. That preparation is eating up a lot of CPU cycles, especially, as the list of headers is quite big.
The iteration over all those entries is stressing the memory system (GC) a bit, so that is consuming CPU cycles, too.
In my shallow profiling attempt for this test plan I saw, that CollectionProperty#recoverRunningVersion crept up in the methods.

@vlsi
Copy link
Collaborator

vlsi commented May 3, 2023

Frankly speaking, I do not understand the reason to have recoverRunningVersion there.

Apparently, the default implementation in AbstractTestElement#recoverRunningVersion iterates over all the properties and calls org.apache.jmeter.testelement.property.JMeterProperty#recoverRunningVersion.
The test case in the attachment has HTTP Header Manager with ~240 headers.

Of course, it takes time to walk over those 240 properties to reset them.
Unfortunataly, recoverRunningVersion is there from the beginning: it was added in 2003 in 2e6f30d#diff-436813e2e3a0903a899ea4c44396e9a24ccd045c90e9de50dda8030ec0c44128R78

@vlsi
Copy link
Collaborator

vlsi commented May 3, 2023

Here are the CPU profiling results:

JMeter profiling results showing that recoverRunningVersion takes almost all the time

@vlsi
Copy link
Collaborator

vlsi commented May 3, 2023

The allocation profling shows we allocate a lot of strings when concatenating property names in

JMeterUtils.formatJMeterExportedVariableName(elementName+GenericController.INDEX_VAR_NAME_SUFFIX), iterCount);

Another interesting case is Integer.valueOf(IntegerProperty.getStringValue) in

nbLoops = Integer.valueOf(prop.getStringValue());

I think we should just replace it with prop.getIntValue() and avoid Integer for the field type Integer nbLoops.

JMeter allocation profile results showing string concatenation in IteratingController

@vlsi
Copy link
Collaborator

vlsi commented May 3, 2023

Another finding is that we instantiate a new ConcurrentHashMap for every SampleResult:

private final Set<String> files = ConcurrentHashMap.newKeySet(2);

I'm not sure we really need that map.

@octaviand2
Copy link
Author

let me also provide more insight on the use case.

I am using custom plugins where the config elements are rather large (this is the reason why in the replicate script I have put so many HTTP headers).

When I run the load tests with these custom plugins, I am seeing high CPU usage and severe performance limitations, especially when the number of threads is more than 150

Moreover, with the script I have provided, I see performance degraded 10 times on my laptop if I have only 5 threads

  • 1M TPS with config element disabled
  • 100k TPS with config element enabled

Would really appreciate support.

@FSchumacher
Copy link
Contributor

Do you know if the plugin uses a CollectionProperty, as the HeaderManager does?
Can you do some profiling of a run with your workload? For example with something like https://github.com/async-profiler/async-profiler or https://jdk.java.net/jmc/8/ (that is using flightrecorder)

@vlsi
Copy link
Collaborator

vlsi commented May 3, 2023

I agree it would make sense to profile the case with custom plugins, then you could figure out the key bottlenecks.

@octaviand2
Copy link
Author

octaviand2 commented May 4, 2023

our development has told me that we are not using CollectionProperty but StringProperty.

Also, with the replicate script already provided (standard config/sampler), I am noticing the CPU is very high when the config element is enabled, even if the throughput is much lower - first part is element enabled, second part is with it disabled.

as if JMeter is doing something extra for each sampler executed due to the config element being enabled

image

@vlsi
Copy link
Collaborator

vlsi commented May 4, 2023

The CPU load does not clarify much. I would suggest profiling at method level

@octaviand2
Copy link
Author

profiling with our custom plugins is not really relevant - I think if you find the issue/fix it for the large config elements with the replicate script I had provided, it will fix also our issue.

something is happening in Jmeter that consumes a lot of CPU when there is a large config element and multiple threads - performance is severely affected. As if it's reloaded with each sampler executed.

reducing to the minimum the config element in my case led to improved results - but still CPU is much higher than expected.

would appreciate support - we have use cases where we need to reach 50k+ TPS and I am bit stuck.

@FSchumacher
Copy link
Contributor

That might be true (and I suspect it is), but it would be even better to see those values in a chart. If you want to hide your company name, pixelate it.

@octaviand2
Copy link
Author

it's not about company name - R&D does feel comfortable sharing these profiling when we run with our plugins.

they say - and I agree - that the same issue happens with the Replicate script I had provided - so that can be used for investigations

@vlsi
Copy link
Collaborator

vlsi commented May 5, 2023

Do you think you could share the files in private?

For instance, as I run replicate.xml, I get 315'000 / sec in total.
I guess it should be enough to cover 50K TPS.

@octaviand2
Copy link
Author

you get 315k/sec with the config element enabled or disabled? can you run a comparison test and also look at the CPU on your laptop?

@vlsi
Copy link
Collaborator

vlsi commented May 5, 2023

I run with header manager enabled.
I have Apple M1 Max, Java 11.0.13.

Header manager on: 315K
Header manager off: 830K

@octaviand2
Copy link
Author

octaviand2 commented May 5, 2023 via email

@FSchumacher
Copy link
Contributor

On my machine setup (Dell XPS 13 (i7-8550U), Java 1.8) I get 180K with Header manager.
And yes, the numbers for the given test are bad, so we have to investigate further. But I think it would be better to have more numbers, even if you share them privately, only.
What I don't understand (but I don't have to) is what information would be leaked by pixelating/striking out all company information from those profile numbers.

@octaviand2
Copy link
Author

octaviand2 commented May 8, 2023

in my view it's not relevant to share this information as the case reproduces with JMeter standard samplers.
and it's definately an issue since the CPU is very intensive while the throughput decreases when the config element is enabled.

would appreciate support - in our case, the more threads we have the worse the performance :(

@vlsi
Copy link
Collaborator

vlsi commented May 8, 2023

@octaviand2 , Pull requests that resolve the issue are welcome

@octaviand2
Copy link
Author

Frankly speaking, I do not understand the reason to have recoverRunningVersion there.

Apparently, the default implementation in AbstractTestElement#recoverRunningVersion iterates over all the properties and calls org.apache.jmeter.testelement.property.JMeterProperty#recoverRunningVersion. The test case in the attachment has HTTP Header Manager with ~240 headers.

Of course, it takes time to walk over those 240 properties to reset them. Unfortunataly, recoverRunningVersion is there from the beginning: it was added in 2003 in 2e6f30d#diff-436813e2e3a0903a899ea4c44396e9a24ccd045c90e9de50dda8030ec0c44128R78

apparently this is a huge bottleneck for us - our config elements have 300+ properties and the performance is severely limited by these iterations.

Any clue on the functional reasoning for it - can it be disabled by some configuration?

@vlsi
Copy link
Collaborator

vlsi commented May 8, 2023

You can override recoverRunningVersion with a no-op method in your config element

@vlsi
Copy link
Collaborator

vlsi commented May 9, 2023

One of the immediate actions I suggest is that we skip recoverRunningVersion calls for elements that implement NoThreadClone.

In other words, if an element is safe for concurrent calls, then recoverRunningVersion makes no sense for it as it would reset the values at random when the threads are calling the component.

vlsi added a commit to vlsi/jmeter that referenced this issue May 9, 2023
…ts that are shared between threads (the ones that implement NoThreadClone

See apache#5875
vlsi added a commit to vlsi/jmeter that referenced this issue May 9, 2023
…ts that are shared between threads (the ones that implement NoThreadClone

See apache#5875
vlsi added a commit that referenced this issue May 10, 2023
…ts that are shared between threads (the ones that implement NoThreadClone (#5899)

See #5875
vlsi added a commit to vlsi/jmeter that referenced this issue May 18, 2023
…aders: skip reinitialization on each iteration

AbstractTestElement#recoverRunningVersion resets all the properties on each iteration,
and it is used for loop-like elements mostly.
However, HTTP HeaderManager does not modify its state during the execution,
so there's no need to reset its state.

See also apache#5875 (comment)
vlsi added a commit to vlsi/jmeter that referenced this issue May 18, 2023
…aders: skip reinitialization on each iteration

AbstractTestElement#recoverRunningVersion resets all the properties on each iteration,
and it is used for loop-like elements mostly.
However, HTTP HeaderManager does not modify its state during the execution,
so there's no need to reset its state.

See also apache#5875 (comment)
@vlsi
Copy link
Collaborator

vlsi commented May 18, 2023

I've added recoverRunningVersion implementation to HTTP Header Manager in d13f120, so the benchmark makes both "with header manager" and "without header manager" have the same performance.

Thanks for providing the test case.


Rampup 0 (e.g. to make all the threads produce load faster)
Java 17.0.17
Apple M1

TitleBefore: requests per secondAfter: requests per second
HTTP Header enabled0.49M1.08M
HTTP Header disabled1.08M1.08M

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

No branches or pull requests

3 participants