Skip to content

Commit 04c211f

Browse files
committed
Initial working plugin
Posts latency observations to cloudwatch under a configured namespace with a metric "ResultLatency". Very basic minimum viable offering. Tests written but pending small corrections.
1 parent 9abf8b3 commit 04c211f

8 files changed

+343
-1
lines changed

.editorconfig

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
; EditorConfig is awesome: http://EditorConfig.org
2+
3+
root = true ; top-most EditorConfig file
4+
5+
; Unix-style newlines with a newline ending every file
6+
[*]
7+
charset = utf-8
8+
end_of_line = lf
9+
indent_size = 4
10+
indent_style = space
11+
insert_final_newline = true
12+
trim_trailing_whitespace = true
13+
14+
; 4 space indentation
15+
[*.py]
16+
indent_size = 4
17+
indent_style = space

.gitignore

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
### Node template
2+
# Logs
3+
logs
4+
*.log
5+
npm-debug.log*
6+
7+
# Runtime data
8+
pids
9+
*.pid
10+
*.seed
11+
12+
# Directory for instrumented libs generated by jscoverage/JSCover
13+
lib-cov
14+
15+
# Coverage directory used by tools like istanbul
16+
coverage
17+
18+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19+
.grunt
20+
21+
# node-waf configuration
22+
.lock-wscript
23+
24+
# Compiled binary addons (http://nodejs.org/api/addons.html)
25+
build/Release
26+
27+
# Dependency directories
28+
node_modules
29+
jspm_packages
30+
31+
# Optional npm cache directory
32+
.npm
33+
34+
# Optional REPL history
35+
.node_repl_history
36+
37+
# Created by .ignore support plugin (hsz.mobi)
38+
39+
# JetBrains
40+
.idea

.jscsrc

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"requireCurlyBraces": [
3+
"if",
4+
"else",
5+
"for",
6+
"while",
7+
"do",
8+
"try",
9+
"catch",
10+
"finally",
11+
"switch"
12+
],
13+
"requireSpaceAfterKeywords": [
14+
"if",
15+
"else",
16+
"for",
17+
"while",
18+
"do",
19+
"switch",
20+
"case",
21+
"return",
22+
"try",
23+
"catch",
24+
"finally",
25+
"in",
26+
"var",
27+
"typeof",
28+
"void"
29+
],
30+
"disallowSpacesInNamedFunctionExpression": {
31+
"beforeOpeningRoundBrace": true
32+
},
33+
"disallowSpacesInFunctionDeclaration": {
34+
"beforeOpeningRoundBrace": true
35+
},
36+
"disallowEmptyBlocks": true,
37+
"disallowKeywords": [
38+
"with"
39+
],
40+
"disallowKeywordsOnNewLine": [
41+
"else"
42+
],
43+
"disallowMixedSpacesAndTabs": "smart",
44+
"disallowMultipleLineBreaks": true,
45+
"disallowNewlineBeforeBlockStatements": true,
46+
"disallowPaddingNewlinesInBlocks": true,
47+
"disallowQuotedKeysInObjects": true,
48+
"disallowSpaceAfterObjectKeys": true,
49+
"disallowSpaceAfterPrefixUnaryOperators": true,
50+
"disallowSpaceBeforeBinaryOperators": [
51+
","
52+
],
53+
"disallowSpaceBeforePostfixUnaryOperators": true,
54+
"disallowSpaceBeforeSemicolon": true,
55+
"disallowSpacesInCallExpression": true,
56+
"disallowSpacesInsideArrayBrackets": true,
57+
"disallowSpacesInsideParentheses": true,
58+
"disallowTrailingComma": true,
59+
"disallowTrailingWhitespace": true,
60+
"requireBlocksOnNewline": 1,
61+
"requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
62+
"requireCapitalizedConstructors": true,
63+
"requireCommaBeforeLineBreak": true,
64+
"requireLineFeedAtFileEnd": true,
65+
"requireMultipleVarDecl": "onevar",
66+
"requireParenthesesAroundIIFE": true,
67+
"requireSpaceAfterBinaryOperators": true,
68+
"requireSpaceAfterLineComment": true,
69+
"requireSpaceBeforeBinaryOperators": true,
70+
"requireSpaceBeforeBlockStatements": true,
71+
"requireSpaceBeforeObjectValues": true,
72+
"requireSpacesInConditionalExpression": true,
73+
"requireSpacesInsideObjectBrackets": "all",
74+
"validateIndentation": 4,
75+
"validateParameterSeparator": ", ",
76+
"validateQuoteMarks": "'"
77+
}

.jshintrc

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"bitwise": true,
3+
"curly": true,
4+
"eqeqeq": true,
5+
"latedef": true,
6+
"noarg": true,
7+
"node": true,
8+
"quotmark": "single",
9+
"smarttabs": true,
10+
"strict": true,
11+
"trailing": true,
12+
"undef": true,
13+
"unused": false,
14+
"mocha": true,
15+
"globals": {
16+
"define": true,
17+
"describe": true,
18+
"it": true
19+
}
20+
}

README.md

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,28 @@
11
# artillery-plugin-cloudwatch
2-
A plugin for artillery.io that records response data into cloudwatch. See https://github.com/shoreditch-ops/artillery. See http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudWatch.html.
2+
A plugin for artillery.io that records response data into cloudwatch.
3+
4+
To use:
5+
1. `npm install -g artillery`
6+
2. `npm install artillery-plugin-cloudwatch` (add `-g` if you like)
7+
3. Add `cloudwatch` plugin config to your "`hello.json`" Artillery script
8+
```
9+
{
10+
"config": {
11+
"plugins": [
12+
"cloudwatch": {
13+
"namespace": "[INSERT_NAMESPACE]"
14+
}
15+
]
16+
}
17+
}
18+
```
19+
4. `artillery run hello.json`
20+
21+
This will cause every latency to be published to the given CloudWatch namespace with the metric "ResultLatency".
22+
23+
For more information, see:
24+
25+
* https://github.com/shoreditch-ops/artillery
26+
* http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/WhatIsCloudWatch.html
27+
28+
Enjoy!

index.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
module.exports = require(__dirname + '/lib/cloudwatch');

lib/cloudwatch.js

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
'use strict';
2+
3+
var aws = require('aws-sdk'),
4+
cloudWatch = new aws.CloudWatch(),
5+
constants = {
6+
PLUGIN_NAME: 'cloudwatch',
7+
PLUGIN_PARAM_NAMESPACE: 'namespace',
8+
// PLUGIN_PARAM_METRICS: 'metrics',
9+
THE: 'The "',
10+
CONFIG_REQUIRED: '" plugin requires configuration under [script].config.plugins.',
11+
PARAM_REQUIRED: '" parameter is required',
12+
PARAM_MUST_BE_STRING: '" param must have a string value',
13+
PARAM_MUST_BE_ARRAY: '" param must have an array value',
14+
// Report Array Positions
15+
TIMESTAMP: 0,
16+
REQUEST_ID: 1,
17+
LATENCY: 2,
18+
STATUS_CODE: 3
19+
},
20+
messages = {
21+
pluginConfigRequired: constants.THE + constants.PLUGIN_NAME + constants.CONFIG_REQUIRED + constants.PLUGIN_NAME,
22+
pluginParamNamespaceRequired: constants.THE + constants.PLUGIN_PARAM_NAMESPACE + constants.PARAM_REQUIRED,
23+
pluginParamNamespaceMustBeString: constants.THE + constants.PLUGIN_PARAM_NAMESPACE + constants.PARAM_MUST_BE_STRING//,
24+
// pluginParamMetricsRequired: constants.THE + constants.PLUGIN_PARAM_METRICS + constants.PARAM_REQUIRED,
25+
// pluginParamMetricsMustBeArray: constants.THE + constants.PLUGIN_PARAM_METRICS + constants.PARAM_MUST_BE_ARRAY
26+
},
27+
impl = {
28+
validateConfig: function(scriptConfig) {
29+
// Validate that plugin config exists
30+
if (!(scriptConfig && scriptConfig.plugins && constants.PLUGIN_NAME in scriptConfig.plugins)) {
31+
throw new Error(messages.pluginConfigRequired);
32+
}
33+
// Validate NAMESPACE
34+
if (!(constants.PLUGIN_PARAM_NAMESPACE in scriptConfig.plugins[constants.PLUGIN_NAME])) {
35+
throw new Error(messages.pluginParamNamespaceRequired);
36+
} else if (!('string' === typeof scriptConfig.plugins[constants.PLUGIN_NAME][constants.PLUGIN_PARAM_NAMESPACE] ||
37+
scriptConfig.plugins[constants.PLUGIN_NAME][constants.PLUGIN_PARAM_NAMESPACE] instanceof String)) {
38+
throw new Error(messages.pluginParamNamespaceMustBeString);
39+
}
40+
// // Validate METRICS
41+
// if (!(messages.PLUGIN_PARAM_METRICS in pluginConfig)) {
42+
// throw new Error(messages.pluginParamMetricsRequired)
43+
// } else if (!Array.isArray(pluginConfig[messages.PLUGIN_PARAM_METRICS])) {
44+
// throw new Error(messages.pluginParamMetricsMustBeArray);
45+
// }
46+
// for(var i = 0; pluginConfig[messages.PLUGIN_PARAM_METRICS].length; i++) {
47+
// validateMetric(pluginConfig[messages.PLUGIN_PARAM_METRICS][i]);
48+
// }
49+
},
50+
buildCloudWatchParams: function(namespace, latency, latencies) {
51+
var cloudWatchParams = {
52+
Namespace: namespace,
53+
MetricData: []
54+
},
55+
lastLatency = Math.min(latency + 20, latencies.length);
56+
for(var i = latency; i < lastLatency; i++) {
57+
cloudWatchParams.MetricData.push({
58+
MetricName: 'ResultLatency',
59+
Dimensions: [],
60+
Timestamp: (new Date(latencies[i][constants.TIMESTAMP])).toISOString(),
61+
Value: latencies[i][constants.LATENCY] / 1000000,
62+
Unit: 'Milliseconds'
63+
});
64+
}
65+
return cloudWatchParams;
66+
}
67+
},
68+
api = {
69+
init: function(scriptConfig, eventEmitter) {
70+
return new CloudWatchPlugin(scriptConfig, eventEmitter);
71+
}
72+
};
73+
74+
function CloudWatchPlugin(scriptConfig, eventEmitter) {
75+
var self = this;
76+
impl.validateConfig(scriptConfig);
77+
self.config = JSON.parse(JSON.stringify(scriptConfig.plugins[constants.PLUGIN_NAME]));
78+
eventEmitter.on('done', function(report) {
79+
var latency = 0,
80+
latencies = report.aggregate.latencies,
81+
cloudWatchParams;
82+
while(latency < latencies.length) {
83+
cloudWatchParams = impl.buildCloudWatchParams(self.config[constants.PLUGIN_PARAM_NAMESPACE], latency, latencies);
84+
cloudWatch.putMetricData(cloudWatchParams, function (err) {
85+
if (err) {
86+
console.log('Error reporting metrics to CloudWatch via putMetricData:', err);
87+
}
88+
});
89+
latency += cloudWatchParams.MetricData.length;
90+
}
91+
console.log('Metrics reported to CloudWatch');
92+
});
93+
}
94+
95+
CloudWatchPlugin.prototype.report = function() {
96+
var reports = null; // an array, if returning any reports
97+
// build reports and add them to reports
98+
// enact custom reporting actions
99+
return reports;
100+
};
101+
102+
/**
103+
* Configuration:
104+
* {
105+
* "config": {
106+
* "plugins": {
107+
* "cloudwatch": {
108+
* "namespace": "[INSERT_NAMESPACE]",
109+
// * "metrics": [
110+
// * {
111+
// * "name": "[METRIC_NAME]",
112+
// * "dimensions": [...],
113+
// *
114+
// * }
115+
// * ]
116+
* }
117+
* }
118+
* }
119+
* }
120+
*/
121+
module.exports = api.init;
122+
123+
/* test-code */
124+
module.exports.constants = constants;
125+
module.exports.messages = messages;
126+
module.exports.impl = impl;
127+
module.exports.api = api;
128+
/* end-test-code */

package.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "artillery-plugin-cloudwatch",
3+
"version": "0.0.1",
4+
"description": "A plugin for artillery.io that records response data into cloudwatch. See https://github.com/shoreditch-ops/artillery. Also see http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/WhatIsCloudWatch.html.",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/nordstrom/artillery-plugin-cloudwatch.git"
12+
},
13+
"keywords": [
14+
"artillery",
15+
"cloudwatch",
16+
"plugin"
17+
],
18+
"author": "Erik Erikson <[email protected]>",
19+
"license": "Apache-2.0",
20+
"bugs": {
21+
"url": "https://github.com/nordstrom/artillery-plugin-cloudwatch/issues"
22+
},
23+
"homepage": "https://github.com/nordstrom/artillery-plugin-cloudwatch#readme",
24+
"dependencies": {
25+
"aws-sdk": "^2.3.19"
26+
},
27+
"devDependencies": {
28+
"chai": "^3.5.0",
29+
"mocha": "^2.5.3"
30+
}
31+
}

0 commit comments

Comments
 (0)