smicyk / groovy-jmeter

A Groovy-based DSL for building and running JMeter test plans from command line and more.
Apache License 2.0
13 stars 1 forks source link

groovy expression in jmeter file #57

Closed AntonioSun closed 2 years ago

AntonioSun commented 2 years ago

Ah ok, so you should look at https://github.com/smicyk/groovy-jmeter#groovy-as-dsl.

So basically you have two types of variables substitutions, the one for groovy variables and one for jmeter variables. Since jmeter and has same syntax to substitute it can be confusing. In general the rule is if you want groovy substitution you should use double quotes, in any other case you should use single quotes.

In your case when you have:

argument(name: 'var_host', value: "${jmt_host}")

you define var_host variable based on groovy variable jmt_host and this substitution is only available during test plan build phase.

then

defaults(protocol: 'http', domain: '${var_host}', port: 1080) 

you use __var_host__ variable define before during test plan execution.

Note, that groovy substitution you don't have use string interplation to get variable and just use the variable name. It depends what you want to actually create as a variable.

Originally posted by @smicyk in https://github.com/smicyk/groovy-jmeter/issues/53#issuecomment-1023982378

AntonioSun commented 2 years ago

Also,

    // using single quotes (for Java plain String)
    plan (name: 'Test name')
    // using single quotes is recommended in most situation (should be used when you want use JMeter variable substitution in the script)
    plan (name: '${var_variable}')
    // using double quotes (for GString, interpolation available during test build but not execution by JMeter engine)
    plan (name: "${var_param}")

I want to let my jmeter file to use groovy expression directly in it, so I put this in:

        argument(name: 'application', value: '${__groovy(("${__TestPlanName}").replace('.jmx'\,''),)}')

However, that will cause --jmx-out to choke:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/home/groovy/myscript.groovy: 6: Unexpected input: '{' @ line 6, column 7.
   start {
         ^

1 error

Only when I replace it with the following, the --jmx-out will run fine.

        argument(name: 'application', value: 'THE_APPLICATION')

PS. Here is how the line should be showing up in the .jmx file:

            <elementProp name="application" elementType="Argument">
              <stringProp name="Argument.name">application</stringProp>
              <stringProp name="Argument.value">${__groovy((&quot;${__TestPlanName}&quot;).replace(&apos;.jmx&apos;\,&apos;&apos;),)}</stringProp>
              <stringProp name="Argument.metadata">=</stringProp>
            </elementProp>

PPS.

Full script ```groovy @GrabConfig(systemClassLoader = true) @Grab('net.simonix.scripts:groovy-jmeter') @groovy.transform.BaseScript net.simonix.dsl.jmeter.TestScript script start { plan { arguments { argument(name: 'var_host', value: "${jmt_host}") argument(name: 'var_user_nm', value: "${jmt_user_nm}") argument(name: 'var_user_pw', value: "${jmt_user_pw}") } defaults(protocol: 'http', domain: '${var_host}', port: 1080) group(users: jmt_users, rampUp: jmt_ramp) { cookies() } summary(file: 'result.log', enabled: true) backend(name: 'InfluxDb Backend', enabled: true) { arguments { argument(name: 'influxdbMetricsSender', value: 'org.apache.jmeter.visualizers.backend.influxdb.HttpMetricsSender') argument(name: 'influxdbUrl', value: 'http://influx:8086/write?db=jmeter') argument(name: 'application', value: '${__groovy(("${__TestPlanName}").replace('.jmx'\,''),)}') argument(name: 'measurement', value: 'jmeter') argument(name: 'summaryOnly', value: 'false') argument(name: 'samplersRegex', value: '.*') argument(name: 'percentiles', value: '90;95;99') argument(name: 'testTitle', value: "my app - : ${jmt_users}, rampup: ${jmt_ramp}") argument(name: "eventTags", value: '') } } } } ```
smicyk commented 2 years ago

:). Ok So you should use groovy triple quotes.

argument(name: 'application', value: '''${groovy(("${TestPlanName}").replace('.jmx',''),)}''')

I think it should work in this way. You might check other types of groovy quotes in https://groovy-lang.org/syntax.html#all-strings

AntonioSun commented 2 years ago

Yep, it works perfectly.

TBH, I did read through the groovy quotes you posted before in https://groovy-lang.org/syntax.html#all-strings, and I only thought that triple single quotes are for span multiple lines, :)

Thanks for helping!

AntonioSun commented 2 years ago

Hi @smicyk, I have to reopen this as it is not getting want I want.

Here is how the line should be showing up in the .jmx file:

            <elementProp name="application" elementType="Argument">
              <stringProp name="Argument.name">application</stringProp>
              <stringProp name="Argument.value">${__groovy((&quot;${__TestPlanName}&quot;).replace(&apos;.jmx&apos;\,&apos;&apos;),)}</stringProp>
              <stringProp name="Argument.metadata">=</stringProp>
            </elementProp>

And within the JMeter, it reads:

${__groovy(("${__TestPlanName}").replace('.jmx'\,''),)}

This is the only way it works. and these are what I've tried within groovy-jmeter script:

Please find a solution, as it is becoming a show-stopper for me to recommend it to our colleagues, since this is our standard way of doing reporting into the influxdb. thanks!

smicyk commented 2 years ago

Hi,

the version which works for me

argument(name: 'application', value: '${__groovy("${__TestPlanName}".replace(".jmx"\\,""))}')

It produces:

<stringProp name="Argument.value">${__groovy(&quot;${__TestPlanName}&quot;.replace(&quot;.jmx&quot;\,&quot;&quot;))}</stringProp>

Check it out and let me know.

AntonioSun commented 2 years ago

Thanks!

AntonioSun commented 2 years ago

Hmm.. I tried it and it works with the generated .jmx file. however, directly running the .groovy file didn't work. Did directly running the .groovy file work for you?

smicyk commented 2 years ago

I think there is a problem with __TestPlanName. It can't be used in groovy script when running because it is based on the .jmx file name. I need to investigate this problem.

AntonioSun commented 2 years ago

Reusing the old thread so that related info are at the same place.

I'm defining my Think Time in the arguments as:

  argument(name: 'c_tt_range', value: '${__P(c_tt_range, 6000)}') // Maximum random number of ms to delay
  argument(name: 'c_tt_delay', value: '${__P(c_tt_delay, 2000)}') // Ms to delay in addition to random time

then use them to define the uniform_timer as:

  uniform_timer (name: 'Think Time', delay: '${c_tt_delay}', range: '${c_tt_range}')

However, if I don't use the single quote in the uniform_timer as:

  uniform_timer (name: 'Think Time', delay: ${c_tt_delay}, range: ${c_tt_range})

It'll error out with:

WARNING: Could not find match for name '$' Caught: net.simonix.dsl.jmeter.model.ValidationException: The keyword '$' is not valid. Did you misspell any of valid keywords [execute_if, argument, before,...

if I use the single quote, then I'll get:

Caught: java.lang.NumberFormatException: For input string: "${c_tt_delay}"

Tried the following as well which didn't work:

  uniform_timer (name: 'Think Time', delay: \${c_tt_delay}, range: \${c_tt_range})

How can I make it work?

AntonioSun commented 2 years ago

please, I'm blocked on this. thx.

smicyk commented 2 years ago

Unfortunately, this is JMeter shortcomming. The uniform_timer doesn't allow to pass expressions as delay and range values. It must be parsable number value.

The alternative is to use command line variables if you want to change them dynamically for each run.

AntonioSun commented 2 years ago

Hmm... I did some more experiments with it, and found that this might not be JMeter's shortcoming.

Here is my arguments:

      argument(name: 'c_lt_users', value: '${__P(c_lt_users, {{ coalesce (ENV "GJS_LT_USERS") 10}})}') // loadtest users
      argument(name: 'c_lt_ramp', value: '${__P(c_lt_ramp, {{ coalesce (ENV "GJS_LT_RAMP") 5}})}') // loadtest ramp up in seconds

And here is how I'm trying to use it:

    //group(users: ${c_lt_users}, rampUp: ${c_lt_ramp}) {
    group(users: 1, rampUp: 1) {

If using the commented one, I'll be getting the exact same problem as reported above -- Could not find match for name '$' Caught.

However, after the conversion, if I change it back in JMeter directly, it saves the file just fine:

@@ -76,3 +76,3 @@
         <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
-        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.num_threads">${c_lt_users}</stringProp>
         <stringProp name="ThreadGroup.ramp_time">1</stringProp>

So, jmeter allows using ${c_lt_users} as-is, it is groovy-jmeter the converter that is preventing me doing it.

Please double-check. thx

AntonioSun commented 2 years ago

Yep, confirmed -- adding The uniform_timer in JMeter directly allow to pass expressions as delay and range values:

@@ -76,3 +76,3 @@
         <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
-        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.num_threads">${c_lt_users}</stringProp>
         <stringProp name="ThreadGroup.ramp_time">1</stringProp>
@@ -202,2 +202,7 @@
         </hashTree>
+        <UniformRandomTimer guiclass="UniformRandomTimerGui" testclass="UniformRandomTimer" testname="Uniform Random Timer" enabled="true">
+          <stringProp name="ConstantTimer.delay">${c_tt_delay}</stringProp>
+          <stringProp name="RandomTimer.range">${c_tt_range}</stringProp>
+        </UniformRandomTimer>
+        <hashTree/>
         <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="TR0_SendMqMessage.php" enabled="true">
smicyk commented 2 years ago

OK. So some explanation about using variables.

You can use groovy variables in the script e.g.:

def rampUpValue = 100

plan {
    group users: 1, rampUp: rampUpValue, {
       // other stuff
    }
}

So you can use variables define in the script and use them where you want in the script but they are used during building the script not during execution.

plan {
    variables {
       variable name: 'rampUpValue', value, '100'
    }
    group users: 1, rampUp: '${rampUpValue}', {
       // other stuff
    }
}

So the rampUpValue is not jmeter variable and is used during script execution not building. Note the single quotes.

So your example

group(users: ${c_lt_users}, rampUp: ${c_lt_ramp}) {

Groovy doesn't know what is $ because it is not part of the language. But if you do:

group(users: '${c_lt_users}', rampUp: '${c_lt_ramp}') {

It shoudl work since you define c_lt_users as test plan variable, so jmeter would be able to evaluate it during script execution.

The example with jmx file which you send are fine if you didn't run them in JMeter GUI, the groovy script can be serialize to jmx with what every you put there but when your run it it will fail. The expressions are evaluated during runtime not during building jmx.

AntonioSun commented 2 years ago

Thanks for the explanation.

The question is closely related to #81, so I'll follow up there.

AntonioSun commented 2 years ago

Reopen it as it seems I'm getting the same result for groovy-jmeter-0.18.0.

Here is the full groovy script for you to try with, @smicyk:

@GrabConfig(systemClassLoader=true)
@Grab('net.simonix.scripts:groovy-jmeter')

@groovy.transform.BaseScript net.simonix.dsl.jmeter.TestScript script

start {
  plan {
    variables {
      variable(name: 'c_lt_users', value: '${__P(c_lt_users, 10)}', description: 'loadtest users')
      variable(name: 'c_lt_ramp', value: '${__P(c_lt_ramp, 5)}', description: 'loadtest ramp up in seconds')
      }

    defaults(protocol: 'https', domain: 'www.example.com', port: 443)

    group(users: ${c_lt_users}, rampUp: ${c_lt_ramp}) {
      http 'GET https://www.example.com'
      // to define own sample name you must use long version
      http name: 'Custom Name 1', protocol: 'http', domain: 'localhost', path: '/', method: 'GET'
      http (name: 'Custom Name 2',  path: '/', method: 'GET')
    }

  }
}
smicyk commented 2 years ago

Hi, it should be:

@GrabConfig(systemClassLoader=true)
@Grab('net.simonix.scripts:groovy-jmeter')

@groovy.transform.BaseScript net.simonix.dsl.jmeter.TestScript script

start {
  plan {
    variables {
      variable(name: 'c_lt_users', value: '${__P(c_lt_users, 10)}', description: 'loadtest users')
      variable(name: 'c_lt_ramp', value: '${__P(c_lt_ramp, 5)}', description: 'loadtest ramp up in seconds')
      }

    defaults(protocol: 'https', domain: 'www.example.com', port: 443)

    group(users: '${c_lt_users}', rampUp: '${c_lt_ramp}') {
      http 'GET https://www.example.com'
      // to define own sample name you must use long version
      http name: 'Custom Name 1', protocol: 'http', domain: 'localhost', path: '/', method: 'GET'
      http (name: 'Custom Name 2',  path: '/', method: 'GET')
    }

  }
}

Then you need to run it with (local):

groovy script.groovy -Jc_lt_users=20 -Jc_lt_ramp=10

or if running in distributed env:

groovy script.groovy -c -r worker1:1099 -r worker2:1099 -Gc_lt_users=20 -Gc_lt_ramp=10

You can actually look at examples distributed or parameters

AntonioSun commented 2 years ago

Ah, using single quote!

Yes, indeed, now point # 3 works too -- having the source groovy-jmeter script works with JMeter without modification, even when converting it into JMeter.

Thanks!