Skip to content

Commit d9aaa3a

Browse files
committed
Add support for extracting datadog tags from metric by RegExp
Add 'datadogMetricTagsByRegExp' configuration, which receives an object of { RegExpString: [TagNameString1, TagNameString2] } where each tag name corresponds to a capture group in the regular expression. For example: datadogMetricTagsByRegExp: { '^interface\\.([\\w\\-]+?)\\.[\\w\\-]+?\\.[\\w\\-]+?\\.[\\w\\-]+?$': ['interface_name'], '^cpu\\.(\\d+)\\.cpu\\.(\\w+)$': ['cpu_cpu', 'cpu_time'], '^tcpconns\\.([\\w-]+)\\.tcp_connections\\.([\\w-]+)$': ['tcpconns_port', 'tcpconns_state'], '^load\\.load\\.([\\w-]+)$': ['load_term'], '^memory\\.memory\\.([\\w-]+)$': ['memory_type'], '^df\\.([\\w-]+?)\\.(?:\\w+?\\.)+([\\w-]+)$': ['df_partition', 'df_type'], } NOTE the strings will be read as a RegExp object, thus backslashes must be escaped. The captured groups will be extracted and removed from the metric name and be reported as datadog tags along with the metric e.g. 'interface.br-e5f9ab1037e7.if_packets.packets.tx' will be transformed into 'interface.if_packets.packets.tx|#interface_name:br-e5f9ab1037e7' This allows for adding tag dimensions to untagged (e.g. collectd) metrics.
1 parent b63e136 commit d9aaa3a

File tree

1 file changed

+154
-14
lines changed

1 file changed

+154
-14
lines changed

lib/datadog.js

Lines changed: 154 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,30 @@
1313
* datadogApiKey: Your DataDog API key
1414
* datadogPrefix: A global prefix for all metrics
1515
* datadogTags: A global set of tags for all metrics
16+
*
17+
* datadogMetricTagsByRegExp: An object of { RegExpString: [TagNameString1, TagNameString2] }
18+
* where each tag name correspond to a capture group in the regular expression.
19+
* NOTE the strings will be read as a RegExp object, thus backslashes must be escaped.
20+
*
21+
* The following is are example RegExps for popular collectd plugins:
22+
* interface, cpu, tcpconns, load, memory, df.
23+
*
24+
* datadogMetricTagsByRegExp: {
25+
* '^interface\\.([\\w\\-]+?)\\.[\\w\\-]+?\\.[\\w\\-]+?\\.[\\w\\-]+?$': ['interface_name'],
26+
* '^cpu\\.(\\d+)\\.cpu\\.(\\w+)$': ['cpu_cpu', 'cpu_time'],
27+
* '^tcpconns\\.([\\w-]+)\\.tcp_connections\\.([\\w-]+)$': ['tcpconns_port', 'tcpconns_state'],
28+
* '^load\\.load\\.([\\w-]+)$': ['load_term'],
29+
* '^memory\\.memory\\.([\\w-]+)$': ['memory_type'],
30+
* '^df\\.([\\w-]+?)\\.(?:\\w+?\\.)+([\\w-]+)$': ['df_partition', 'df_type'],
31+
* },
32+
*
33+
* The captured groups will be extracted and truncated from the metric name and be
34+
* reported as datadog tags along with the metric e.g.
35+
*
36+
* 'interface.br-e5f9ab1037e7.if_packets.packets.tx' will be transformed into
37+
* 'interface.if_packets.packets.tx|#interface_name:br-e5f9ab1037e7'
38+
*
39+
* This allows for adding tag dimensions to untagged (e.g. collectd) metrics.
1640
*/
1741

1842
var net = require('net'),
@@ -28,6 +52,7 @@ var datadogApiKey;
2852
var datadogStats = {};
2953
var datadogPrefix;
3054
var datadogTags;
55+
var datadogMetricTagsByRegExp;
3156

3257
var Datadog = function(api_key, options) {
3358
options = options || {};
@@ -91,31 +116,42 @@ var flush_stats = function datadog_post_stats(ts, metrics) {
91116
var value;
92117

93118
var key;
119+
var metricNameAndTags;
120+
var metricName;
121+
var metricTags;
94122

95123
// Send counters
96124
for (key in counters) {
97125
value = counters[key];
98126
var valuePerSecond = value / (flushInterval / 1000); // calculate 'per second' rate
99127

128+
metricNameAndTags = get_metric_name_and_tags(key);
129+
metricName = metricNameAndTags[0];
130+
metricTags = metricNameAndTags[1];
131+
100132
payload.push({
101-
metric: get_prefix(key),
133+
metric: metricName,
102134
points: [[ts, valuePerSecond]],
103135
type: 'gauge',
104136
host: host,
105-
tags: datadogTags
137+
tags: datadogTags.concat(metricTags)
106138
});
107139
}
108140

109141
// Send gauges
110142
for (key in gauges) {
111143
value = gauges[key];
112144

145+
metricNameAndTags = get_metric_name_and_tags(key);
146+
metricName = metricNameAndTags[0];
147+
metricTags = metricNameAndTags[1];
148+
113149
payload.push({
114-
metric: get_prefix(key),
150+
metric: metricName,
115151
points: [[ts, value]],
116152
type: 'gauge',
117153
host: host,
118-
tags: datadogTags
154+
tags: datadogTags.concat(metricTags)
119155
});
120156
}
121157

@@ -146,51 +182,137 @@ var flush_stats = function datadog_post_stats(ts, metrics) {
146182
mean = sum / numInThreshold;
147183
}
148184

185+
metricNameAndTags = get_metric_name_and_tags(key + '.mean');
186+
metricName = metricNameAndTags[0];
187+
metricTags = metricNameAndTags[1];
188+
149189
payload.push({
150-
metric: get_prefix(key + '.mean'),
190+
metric: metricName,
151191
points: [[ts, mean]],
152192
type: 'gauge',
153193
host: host,
154-
tags: datadogTags
194+
tags: datadogTags.concat(metricTags)
155195
});
156196

197+
metricNameAndTags = get_metric_name_and_tags(key);
198+
metricName = metricNameAndTags[0];
199+
metricTags = metricNameAndTags[1];
200+
157201
payload.push({
158-
metric: get_prefix(key + '.upper'),
202+
metric: metricName + '.upper',
159203
points: [[ts, max]],
160204
type: 'gauge',
161205
host: host,
162-
tags: datadogTags
206+
tags: datadogTags.concat(metricTags)
163207
});
164208

209+
metricNameAndTags = get_metric_name_and_tags(key);
210+
metricName = metricNameAndTags[0];
211+
metricTags = metricNameAndTags[1];
212+
165213
payload.push({
166-
metric: get_prefix(key + '.upper_' + pctThreshold),
214+
metric: metricName + '.upper_' + pctThreshold,
167215
points: [[ts, maxAtThreshold]],
168216
type: 'gauge',
169217
host: host,
170-
tags: datadogTags
218+
tags: datadogTags.concat(metricTags)
171219
});
172220

221+
metricNameAndTags = get_metric_name_and_tags(key);
222+
metricName = metricNameAndTags[0];
223+
metricTags = metricNameAndTags[1];
224+
173225
payload.push({
174-
metric: get_prefix(key + '.lower'),
226+
metric: metricName + '.lower',
175227
points: [[ts, min]],
176228
type: 'gauge',
177229
host: host,
178-
tags: datadogTags
230+
tags: datadogTags.concat(metricTags)
179231
});
180232

233+
metricNameAndTags = get_metric_name_and_tags(key);
234+
metricName = metricNameAndTags[0];
235+
metricTags = metricNameAndTags[1];
236+
181237
payload.push({
182-
metric: get_prefix(key + '.count'),
238+
metric: metricName + '.count',
183239
points: [[ts, count]],
184240
type: 'gauge',
185241
host: host,
186-
tags: datadogTags
242+
tags: datadogTags.concat(metricTags)
187243
});
188244
}
189245
}
190246

191247
post_stats(payload);
192248
};
193249

250+
// get_metric_name_and_tags extracts and truncates
251+
// metric-specific tags from the metric name.
252+
var get_metric_name_and_tags = function datadog_get_metric_name_and_tags(key) {
253+
var metricNameAndTags = get_metric_tags_by_regexp(key);
254+
var metricName = metricNameAndTags[0];
255+
var metricTags = metricNameAndTags[1];
256+
257+
// Add prefix if given in configuration.
258+
metricName = get_prefix(metricName);
259+
260+
return [metricName, metricTags];
261+
}
262+
263+
// get_metric_tags_by_regexp attempts to match the given metric key
264+
// with all metric RegExps given in the configuration.
265+
//
266+
// When a RegExp is matched, its captured groups are truncated from the metric
267+
// name and pushed into the metric's specific datadog tags array.
268+
//
269+
// Returns the truncated metric key and its metric-specific tag array.
270+
// If no match is found, the key is returned as-is with an empty tag array.
271+
var get_metric_tags_by_regexp = function datadog_get_metric_tags_by_regexp(key) {
272+
var match;
273+
274+
for (var i = 0; i < datadogMetricTagsByRegExp.length; i++) {
275+
var regExpTags = datadogMetricTagsByRegExp[i];
276+
var re = regExpTags['regExp'];
277+
// RegExp matches start from index i=1 instead of i=0,
278+
// so the tag names that will be matched are offset accordingly.
279+
var tagNames = [ null ].concat(regExpTags['tagNames']);
280+
281+
// Attempt to match current RegExp with given key.
282+
match = re.exec(key);
283+
284+
// If a match is found,
285+
// add the captured groups to the metric's datadog tag array,
286+
// and truncate them from the metric key.
287+
if (match && match.length > 1) {
288+
var mutatedKey = key;
289+
var metricTags = [];
290+
291+
match.forEach(function(groups, j) {
292+
if (j === 0) {
293+
return;
294+
}
295+
296+
var tag = match[j];
297+
var tagName = tagNames[j];
298+
299+
// Add current captured group to tag array.
300+
metricTags.push(tagName + ':' + tag);
301+
302+
// Truncate the captured group from the metric key along with
303+
// it's prefixed or suffixed period character.
304+
mutatedKey = mutatedKey.replace(new RegExp('(' + tag + '\\.?)|(\\.' + tag + '$)'), "");
305+
});
306+
307+
return [mutatedKey, metricTags]
308+
}
309+
}
310+
311+
// If we reached this line, it means no RegExps were matched,
312+
// so we return the original key with no metric-specific datadog tags.
313+
return [key, []]
314+
}
315+
194316
var get_prefix = function datadog_get_prefix(key) {
195317
if (datadogPrefix !== undefined) {
196318
return [datadogPrefix, key].join('.');
@@ -216,6 +338,7 @@ exports.init = function datadog_init(startup_time, config, events, log) {
216338
datadogApiHost = config.datadogApiHost;
217339
datadogPrefix = config.datadogPrefix;
218340
datadogTags = config.datadogTags;
341+
datadogMetricTagsByRegExp = [];
219342

220343
if (datadogTags === undefined || datadogTags.constructor !== Array || datadogTags.length < 1) {
221344
datadogTags = [];
@@ -225,6 +348,23 @@ exports.init = function datadog_init(startup_time, config, events, log) {
225348
datadogApiHost = 'https://app.datadoghq.com';
226349
}
227350

351+
// Read metric regexps string into RegExp form.
352+
if (config.datadogMetricTagsByRegExp &&
353+
config.datadogMetricTagsByRegExp.constructor === Object &&
354+
Object.keys(config.datadogMetricTagsByRegExp).length > 0) {
355+
356+
for (var re in config.datadogMetricTagsByRegExp) {
357+
if (config.datadogMetricTagsByRegExp.hasOwnProperty(re)) {
358+
datadogMetricTagsByRegExp.push(
359+
{
360+
'regExp': new RegExp(re),
361+
'tagNames': config.datadogMetricTagsByRegExp[re]
362+
}
363+
);
364+
}
365+
}
366+
}
367+
228368
datadogStats.last_flush = startup_time;
229369
datadogStats.last_exception = startup_time;
230370

0 commit comments

Comments
 (0)