Skip to content

Commit

Permalink
Graphite tagged metrics support (#697)
Browse files Browse the repository at this point in the history
* support for tagged metrics
* decode datadog tag format
* don't replace tagging characters when sanitizing graphite metric names
  • Loading branch information
DanCech committed Aug 25, 2020
1 parent b7f1d9d commit 2041f6f
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 26 deletions.
45 changes: 22 additions & 23 deletions backends/graphite.js
Expand Up @@ -14,7 +14,7 @@
* graphitePort: Port for the graphite text collector. Defaults to 2003.
* graphitePicklePort: Port for the graphite pickle collector. Defaults to 2004.
* graphiteProtocol: Either 'text' or 'pickle'. Defaults to 'text'.
*
*
* If graphiteHost is not specified, metrics are processed but discarded.
*/

Expand Down Expand Up @@ -108,7 +108,7 @@ function Metric(key, value, ts) {
this.value = value;
this.ts = ts;

// return a string representation of this metric appropriate
// return a string representation of this metric appropriate
// for sending to the graphite collector. does not include
// a trailing newline.
this.toText = function() {
Expand Down Expand Up @@ -167,10 +167,18 @@ var flush_stats = function graphite_flush(ts, metrics) {
} else {
return key.replace(/\s+/g, '_')
.replace(/\//g, '-')
.replace(/[^a-zA-Z_\-0-9\.]/g, '');
.replace(/[^a-zA-Z_\-0-9\.;=]/g, '');
}
};

function format(namespace, key) {
var splitName = key.split(';');
var keyName = sk(splitName[0]);
var tags = splitName.length > 1 ? (';' + splitName.slice(1).join(';')) : '';

return namespace.concat(keyName, [].slice.call(arguments, 2)).join('.') + globalSuffix + tags;
}

// Flatten all the different types of metrics into a single
// collection so we can allow serialization to either the graphite
// text and pickle formats.
Expand All @@ -179,37 +187,32 @@ var flush_stats = function graphite_flush(ts, metrics) {
for (key in counters) {
var value = counters[key];
var valuePerSecond = counter_rates[key]; // pre-calculated "per second" rate
var keyName = sk(key);
var namespace = counterNamespace.concat(keyName);

if (legacyNamespace === true) {
stats.add(namespace.join(".") + globalSuffix, valuePerSecond, ts);
stats.add(format(counterNamespace, key), valuePerSecond, ts);
if (flush_counts) {
stats.add('stats_counts.' + keyName + globalSuffix, value, ts);
stats.add(format(['stats_counts'], key), value, ts);
}
} else {
stats.add(namespace.concat('rate').join(".") + globalSuffix, valuePerSecond, ts);
stats.add(format(counterNamespace, key, 'rate'), valuePerSecond, ts);
if (flush_counts) {
stats.add(namespace.concat('count').join(".") + globalSuffix, value, ts);
stats.add(format(counterNamespace, key, 'count'), value, ts);
}
}

numStats += 1;
}

for (key in timer_data) {
var namespace = timerNamespace.concat(sk(key));
var the_key = namespace.join(".");

for (timer_data_key in timer_data[key]) {
if (typeof(timer_data[key][timer_data_key]) === 'number') {
stats.add(the_key + '.' + timer_data_key + globalSuffix, timer_data[key][timer_data_key], ts);
stats.add(format(timerNamespace, key, timer_data_key), timer_data[key][timer_data_key], ts);
} else {
for (var timer_data_sub_key in timer_data[key][timer_data_key]) {
if (debug) {
l.log(timer_data[key][timer_data_key][timer_data_sub_key].toString());
}
stats.add(the_key + '.' + timer_data_key + '.' + timer_data_sub_key + globalSuffix,
stats.add(format(timerNamespace, key, timer_data_key, timer_data_sub_key),
timer_data[key][timer_data_key][timer_data_sub_key], ts);
}
}
Expand All @@ -218,14 +221,12 @@ var flush_stats = function graphite_flush(ts, metrics) {
}

for (key in gauges) {
var namespace = gaugesNamespace.concat(sk(key));
stats.add(namespace.join(".") + globalSuffix, gauges[key], ts);
stats.add(format(gaugesNamespace, key), gauges[key], ts);
numStats += 1;
}

for (key in sets) {
var namespace = setsNamespace.concat(sk(key));
stats.add(namespace.join(".") + '.count' + globalSuffix, sets[key].size(), ts);
stats.add(format(setsNamespace, key, 'count'), sets[key].size(), ts);
numStats += 1;
}

Expand All @@ -236,12 +237,10 @@ var flush_stats = function graphite_flush(ts, metrics) {
stats.add('stats.' + prefixStats + '.' + key + globalSuffix, statsd_metrics[key], ts);
}
} else {
var namespace = globalNamespace.concat(prefixStats);
stats.add(namespace.join(".") + '.numStats' + globalSuffix, numStats, ts);
stats.add(namespace.join(".") + '.graphiteStats.calculationtime' + globalSuffix, (Date.now() - starttime) , ts);
stats.add(format(globalNamespace, prefixStats, 'numStats'), numStats, ts);
stats.add(format(globalNamespace, prefixStats, 'graphiteStats', 'calculationtime'), (Date.now() - starttime) , ts);
for (key in statsd_metrics) {
var the_key = namespace.concat(key);
stats.add(the_key.join(".") + globalSuffix,+ statsd_metrics[key], ts);
stats.add(format(globalNamespace, prefixStats, key), statsd_metrics[key], ts);
}
}
post_stats(stats);
Expand Down
18 changes: 15 additions & 3 deletions stats.js
Expand Up @@ -165,7 +165,7 @@ function sanitizeKeyName(key) {
if (keyNameSanitize) {
return key.replace(/\s+/g, '_')
.replace(/\//g, '-')
.replace(/[^a-zA-Z_\-0-9\.]/g, '');
.replace(/[^a-zA-Z_\-0-9\.;=]/g, '');
} else {
return key;
}
Expand Down Expand Up @@ -252,8 +252,20 @@ config.configFile(process.argv[2], function (config) {
if (config.dumpMessages) {
l.log(metrics[midx].toString());
}
const bits = metrics[midx].toString().split(':');
const key = sanitizeKeyName(bits.shift());

let bits = metrics[midx].toString().split('|#');
let tags = [];
if (bits.length > 1 && bits[1].length > 0) {
tags = bits[1].split(',');
}
bits = bits[0].split(':');
let key = bits.shift();
if (tags.length > 0) {
key += ';' + tags.map(function(tag) {
return tag.replace(';', '_').replace(':', '=');
}).join(';');
}
key = sanitizeKeyName(key);

if (keyFlushInterval > 0) {
if (! keyCounter[key]) {
Expand Down
38 changes: 38 additions & 0 deletions test/graphite_tests.js
Expand Up @@ -377,5 +377,43 @@ module.exports = {
});
});
});
},

graphite_tags_are_supported: function(test) {
var me = this;
this.acceptor.once('connection', function(c) {
statsd_send('fo/o;tag1=val1:250|c',me.sock,'127.0.0.1',8125,function(){
statsd_send('b ar;tag1=val1;tag2=val2:250|c',me.sock,'127.0.0.1',8125,function(){
statsd_send('foo+bar;tag1=val1;tag3=val3;tag2=val2:250|c',me.sock,'127.0.0.1',8125,function(){
collect_for(me.acceptor, me.myflush * 2, function(strings){
var str = strings.join();
test.ok(str.indexOf('fo-o.count;tag1=val1') !== -1, "Did not map 'fo/o;tag1=val1' => 'fo-o.count;tag1=val1'");
test.ok(str.indexOf('b_ar.count;tag1=val1;tag2=val2') !== -1, "Did not map 'b ar;tag1=val1;tag2=val2' => 'b_ar.count;tag1=val1;tag2=val2'");
test.ok(str.indexOf('foobar.count;tag1=val1;tag3=val3;tag2=val2') !== -1, "Did not map 'foo+bar;tag1=val1;tag3=val3;tag2=val2' => 'foobar.count;tag1=val1;tag3=val3;tag2=val2'");
test.done();
});
});
});
});
});
},

dogstatsd_tags_are_supported: function(test) {
var me = this;
this.acceptor.once('connection', function(c) {
statsd_send('fo/o:250|c|#tag1:val1',me.sock,'127.0.0.1',8125,function(){
statsd_send('b ar:250|c|#tag1:val1,tag2:val2',me.sock,'127.0.0.1',8125,function(){
statsd_send('foo+bar:250|c|#tag1:val;1,tag3:val3,tag2:val2',me.sock,'127.0.0.1',8125,function(){
collect_for(me.acceptor, me.myflush * 2, function(strings){
var str = strings.join();
test.ok(str.indexOf('fo-o.count;tag1=val1') !== -1, "Did not map 'fo/o:250|c|#tag1:val1' => 'fo-o.count;tag1=val1'");
test.ok(str.indexOf('b_ar.count;tag1=val1;tag2=val2') !== -1, "Did not map 'b ar:250|c|#tag1:val1,tag2:val2' => 'b_ar.count;tag1=val1;tag2=val2'");
test.ok(str.indexOf('foobar.count;tag1=val_1;tag3=val3;tag2=val2') !== -1, "Did not map 'foo+bar:250|c|#tag1:val;1,tag3:val3,tag2:val2' => 'foobar.count;tag1=val_1;tag3=val3;tag2=val2'");
test.done();
});
});
});
});
});
}
}

0 comments on commit 2041f6f

Please sign in to comment.