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

feat(cli): set nontemplated url as req name by default in metrics #3264

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions packages/artillery-plugin-metrics-by-endpoint/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const url = require('url');

module.exports = { Plugin: MetricsByEndpoint };
Expand All @@ -12,6 +11,7 @@ let useOnlyRequestNames;
let stripQueryString;
let ignoreUnnamedRequests;
let metricsPrefix;
let groupDynamicURLs;

// NOTE: Will not work with `parallel` - need request UIDs for that
function MetricsByEndpoint(script, events) {
Expand Down Expand Up @@ -43,20 +43,26 @@ function MetricsByEndpoint(script, events) {
metricsPrefix =
script.config.plugins['metrics-by-endpoint'].metricsNamespace ||
'plugins.metrics-by-endpoint';
groupDynamicURLs =
script.config.plugins['metrics-by-endpoint'].groupDynamicURLs ?? true;

script.config.processor.metricsByEndpoint_afterResponse =
metricsByEndpoint_afterResponse;
script.config.processor.metricsByEndpoint_onError = metricsByEndpoint_onError;
script.config.processor.metricsByEndpoint_beforeRequest =
metricsByEndpoint_beforeRequest;

script.scenarios.forEach(function (scenario) {
scenario.afterResponse = [].concat(scenario.afterResponse || []);
scenario.afterResponse.push('metricsByEndpoint_afterResponse');
scenario.onError = [].concat(scenario.onError || []);
scenario.onError.push('metricsByEndpoint_onError');
scenario.beforeRequest = [].concat(scenario.beforeRequest || []);
scenario.beforeRequest.push('metricsByEndpoint_beforeRequest');
});
}

function getReqName(target, originalRequestUrl, requestName) {
function calculateBaseUrl(target, originalRequestUrl) {
const targetUrl = target && url.parse(target);
const requestUrl = url.parse(originalRequestUrl);

Expand All @@ -73,20 +79,33 @@ function getReqName(target, originalRequestUrl, requestName) {
}
baseUrl += stripQueryString ? requestUrl.pathname : requestUrl.path;

let reqName = '';
if (useOnlyRequestNames && requestName) {
reqName += requestName;
} else if (requestName) {
reqName += `${baseUrl} (${requestName})`;
} else if (!ignoreUnnamedRequests) {
reqName += baseUrl;
return decodeURIComponent(baseUrl);
}

function getReqName(target, originalRequestUrl, requestName) {
const baseUrl = calculateBaseUrl(target, originalRequestUrl);

if (!requestName) {
return ignoreUnnamedRequests ? '' : baseUrl;
}

return reqName;
return useOnlyRequestNames ? requestName : `${baseUrl} (${requestName})`;
}

function metricsByEndpoint_beforeRequest(req, userContext, events, done) {
if (groupDynamicURLs) {
req.defaultName = getReqName(userContext.vars.target, req.url, req.name);
}

return done();
}

function metricsByEndpoint_onError(err, req, userContext, events, done) {
const reqName = getReqName(userContext.vars.target, req.url, req.name);
//if groupDynamicURLs is true, then req.defaultName is set in beforeRequest
//otherwise, we must calculate the reqName here as req.url is the non-templated version
const reqName = groupDynamicURLs
? req.defaultName
: getReqName(userContext.vars.target, req.url, req.name);

if (reqName === '') {
return done();
Expand All @@ -102,7 +121,11 @@ function metricsByEndpoint_onError(err, req, userContext, events, done) {
}

function metricsByEndpoint_afterResponse(req, res, userContext, events, done) {
const reqName = getReqName(userContext.vars.target, req.url, req.name);
//if groupDynamicURLs is true, then req.defaultName is set in beforeRequest
//otherwise, we must calculate the reqName here as req.url is the non-templated version
const reqName = groupDynamicURLs
? req.defaultName
: getReqName(userContext.vars.target, req.url, req.name);

if (reqName === '') {
return done();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
config:
target: http://asciiart.artillery.io:8080
phases:
- duration: 2
arrivalRate: 2
plugins:
metrics-by-endpoint:
stripQueryString: true

scenarios:
- flow:
- get:
url: "/dino/{{ $randomString() }}?potato=1&tomato=2"
name: "GET /dino"
- get:
url: "/armadillo/{{ $randomString() }}"
- get:
url: "/pony"
104 changes: 104 additions & 0 deletions packages/artillery-plugin-metrics-by-endpoint/test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,107 @@ test("Reports correctly when 'parallel' is used", async (t) => {
);
}
});

test('Reports correctly when `groupDynamicURLs` is set to true (default)', async (t) => {
const reportPath =
os.tmpdir() + '/artillery-plugin-metrics-by-endpoint-use-path-as-name.json';
const output =
await $`../artillery/bin/run run ./test/fixtures/scenario-templated-url.yml -o ${reportPath}`;

const report = require(reportPath);

t.equal(output.exitCode, 0, 'CLI Exit Code should be 0');
t.equal(
report.aggregate.counters[
'plugins.metrics-by-endpoint./armadillo/{{ $randomString() }}.codes.200'
],
4,
'should have counter metrics including templated url and no query strings'
);
t.equal(
report.aggregate.counters[
'plugins.metrics-by-endpoint./dino/{{ $randomString() }} (GET /dino).codes.200'
],
4,
'should have counter metrics including templated url with request name specified'
);
t.equal(
report.aggregate.counters['plugins.metrics-by-endpoint./pony.codes.200'],
4
),
'should display counter metrics for /pony as normal';

t.ok(
Object.keys(report.aggregate.summaries).includes(
'plugins.metrics-by-endpoint.response_time./armadillo/{{ $randomString() }}'
),
'should have summary metrics including templated url'
);
t.ok(
Object.keys(report.aggregate.summaries).includes(
'plugins.metrics-by-endpoint.response_time./dino/{{ $randomString() }} (GET /dino)'
),
'should have summary metrics including templated url with request name specified'
);
t.ok(
Object.keys(report.aggregate.summaries).includes(
'plugins.metrics-by-endpoint.response_time./pony'
),
'should display summary metrics for /pony as normal'
);
});

test('Reports correctly when `groupDynamicURLs` is explicitly set to false', async (t) => {
const reportPath =
os.tmpdir() +
'/artillery-plugin-metrics-by-endpoint-use-path-without-name-test.json';
const overrides = {
config: {
plugins: {
'metrics-by-endpoint': {
groupDynamicURLs: false,
stripQueryString: false
}
}
}
};
const output =
await $`../artillery/bin/run run ./test/fixtures/scenario-templated-url.yml -o ${reportPath} --overrides ${JSON.stringify(
overrides
)}`;

const report = require(reportPath);

t.equal(output.exitCode, 0, 'CLI Exit Code should be 0');

const aggregateCounters = Object.keys(report.aggregate.counters);

const countersWithName = aggregateCounters.filter((counter) => {
return new RegExp(
/plugins\.metrics-by-endpoint\.\/dino\/[a-zA-Z0-9]+\.?\w+\?potato=1&tomato=2 \(GET \/dino\)\.codes\.200/
).test(counter);
});

const countersWithoutName = aggregateCounters.filter((counter) => {
return new RegExp(
/plugins\.metrics-by-endpoint\.\/armadillo\/[a-zA-Z0-9]+\.?\w+\.codes\.200/
).test(counter);
});

const regularPonyCounter = aggregateCounters.filter(
(counter) => counter == 'plugins.metrics-by-endpoint./pony.codes.200'
);

t.ok(
countersWithName.length > 0,
`should have counter metrics without the templated url, got ${countersWithName}`
);
t.ok(
countersWithoutName.length > 0,
`should have counter metrics without the templated url and request name specified, got ${countersWithoutName}`
);
t.ok(
regularPonyCounter.length == 1,
`should display counter metrics for /pony as normal, got ${regularPonyCounter}`
);
});
Loading
Loading