artilleryio / artillery

The complete load testing platform. Everything you need for production-grade load tests. Serverless & distributed. Load test with Playwright. Load test HTTP APIs, GraphQL, WebSocket, and more. Use any Node.js module.
https://www.artillery.io
Mozilla Public License 2.0
8.04k stars 511 forks source link

test crushes while running artillery-2.0.0-10 with plugin #1290

Closed savvagen closed 1 year ago

savvagen commented 2 years ago

Environment:

Preconditions:

Plugin script:


const debug = require('debug')('plugin:my-plugin');

module.exports = { Plugin: MyPlugin }

function MyPlugin(script, events) {
    this.script = script;
    this.events = events;
    const self = this;

    // Create processor object if needed to hold our custom function:
    script.config.processor = script.config.processor || {};

    script.config.processor.printEndpointMetrics = printEndpointMetrics

    script.scenarios.forEach((scenario) => {
        scenario.afterResponse = scenario.afterResponse || [];
        scenario.afterResponse.push('printEndpointMetrics');
    });

    return this;
}

function printEndpointMetrics(req, res, userContext, events, next) {
    const metricName = `${userContext.vars.statsdPrefix}.plugins.endpoint_metrics.${req.name}`
    debug(metricName)
    debug(req.url)
    debug(req.name)
    return next();
}

Test script:

config:
  target: "http://localhost:3001"
  phases:
    - duration: 120
      arrivalCount: 60
      name: "My Load Stage"
  processor: "../processors/main.js"
  defaults:
    headers:
      Content-Type: "application/json"
      Accept: "application/json"
  plugins:
    expect: {}
    metrics-by-endpoint:
      useOnlyRequestNames: true
    my-plugin: {}

scenarios:
  - name: Post Writer Scenario
    weight: 1
    flow:
      - post:
          url: "/users"
          name: "POST/users"
          headers:
            Authorization: "Bearer {{ token }}"
          expect:
            - statusCode: 201
            - hasProperty: id
          beforeRequest: "userBody"
          afterResponse: "saveUserId"

Artillery throws an error while running the script: command:

 ARTILLERY_PLUGIN_PATH=`pwd`/src/plugins/ DEBUG=plugin:my-plugin artillery run src/scenarios/system.yaml

error:

(node:118325) UnhandledPromiseRejectionWarning: TypeError: Cannot create property 'printEndpointMetrics' on string '../processors/main.js'
    at new ArtilleryDatadogAdvancedMetricsPlugin (/home/user/WebstormProjects/artillery-poc-project/src/plugins/artillery-plugin-datadog-advanced-metrics/index.js:75:50)
    at Launcher.initPlugins (/usr/lib/node_modules/artillery/lib/launch-local.js:111:31)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async Launcher.run (/usr/lib/node_modules/artillery/lib/launch-local.js:217:5)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:118325) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:118325) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

The main reason why it fails, I gues, that in artillery@2.0.0 plugin variable script.config.processor is String but not object where we can assign new functions

savvagen commented 2 years ago

I printed script.config.rocessor variable type and value, and I got the results for both versions: console.log(typeof script.config.rocessor) console.log(script.config.rocessor) I am getting an error because in the version 1.7.9 we have

object
{
  createPeopleCsv: [Function: createPeopleCsv],
  saveUsersData: [Function: saveUsersData],
  writeUsersDataToCsv: [Function: writeUsersDataToCsv],
  userBody: [Function: userBody],
  saveUserId: [Function: saveUserId],
  postBody: [Function: postBody],
  savePost: [Function: savePost],
  commentBody: [Function: commentBody],
  commentsUpdateBody: [Function: commentsUpdateBody],
  getPosts: [Function: getPosts],
  getRandomEmail: [Function: getRandomEmail],
  getComments: [Function: getComments],
  expectationsPluginCheckExpectations: [Function: expectationsPluginCheckExpectations],
  expectationsPluginOnError: [Function: expectationsPluginOnError],
  expectationsPluginMaybeFlushDatadog: [Function: expectationsPluginMaybeFlushDatadog],
  expectationsPluginSetExpectOptions: [Function (anonymous)],
  metricsByEndpoint_afterResponse: [Function: metricsByEndpoint_afterResponse]
}

But in artillery@2.0.0-10 we have:

string
../precessors/main.js
hassy commented 2 years ago

thanks for a very detailed issue report @savvagen!

that bit of the plugin API has changed between v1.x and v2.x - Artillery v2 is multi-threaded, but function objects cannot be sent from the main thread to worker threads. That means that plugin need to detect when they're running inside a worker thread, and may only attach code objects (ie functions) directly to the script object then.

Detecting when the plugin is running inside a worker thread is straightforward, that's how the two official plugins you're using do it:

We'll update the docs!

savvagen commented 2 years ago

@hassy Can you help me to bypass the error? With what flags or variables should I set to run artillery inside a worker thread?

hassy commented 2 years ago

@savvagen you need to add a snippet of code to your custom plugin:

  if (typeof process.env.LOCAL_WORKER_ID === 'undefined') {
    debug('Not running in a worker, exiting');
    return;
  }

as early as possible, e.g. in the constructor, like the examples I linked earlier

bernardobridge commented 1 year ago

This is available in our documentation now: https://www.artillery.io/docs/reference/extension-apis#attaching-function-objects-to-script-from-a-plugin