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
1842var net = require ( 'net' ) ,
@@ -28,6 +52,7 @@ var datadogApiKey;
2852var datadogStats = { } ;
2953var datadogPrefix ;
3054var datadogTags ;
55+ var datadogMetricTagsByRegExp ;
3156
3257var 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+
194316var 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