Move stat key name sanitization to Graphite backend. #155

Merged
merged 1 commit into from Feb 6, 2015
View
@@ -34,6 +34,7 @@ var prefixTimer;
var prefixGauge;
var prefixSet;
var globalSuffix;
+var globalKeySanitize = true;
// set up namespaces
var legacyNamespace = true;
@@ -98,15 +99,27 @@ var flush_stats = function graphite_flush(ts, metrics) {
var timer_data = metrics.timer_data;
var statsd_metrics = metrics.statsd_metrics;
+ // Sanitize key for graphite if not done globally
+ function sk(key) {
+ if (globalKeySanitize) {
+ return key;
+ } else {
+ return key.replace(/\s+/g, '_')
+ .replace(/\//g, '-')
+ .replace(/[^a-zA-Z_\-0-9\.]/g, '');
+ }
+ };
+
for (key in counters) {
- var namespace = counterNamespace.concat(key);
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) {
statString += namespace.join(".") + globalSuffix + valuePerSecond + ts_suffix;
if (flush_counts) {
- statString += 'stats_counts.' + key + globalSuffix + value + ts_suffix;
+ statString += 'stats_counts.' + keyName + globalSuffix + value + ts_suffix;
}
} else {
statString += namespace.concat('rate').join(".") + globalSuffix + valuePerSecond + ts_suffix;
@@ -119,8 +132,9 @@ var flush_stats = function graphite_flush(ts, metrics) {
}
for (key in timer_data) {
- var namespace = timerNamespace.concat(key);
+ 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') {
statString += the_key + '.' + timer_data_key + globalSuffix + timer_data[key][timer_data_key] + ts_suffix;
@@ -138,13 +152,13 @@ var flush_stats = function graphite_flush(ts, metrics) {
}
for (key in gauges) {
- var namespace = gaugesNamespace.concat(key);
+ var namespace = gaugesNamespace.concat(sk(key));
statString += namespace.join(".") + globalSuffix + gauges[key] + ts_suffix;
numStats += 1;
}
for (key in sets) {
- var namespace = setsNamespace.concat(key);
+ var namespace = setsNamespace.concat(sk(key));
statString += namespace.join(".") + '.count' + globalSuffix + sets[key].values().length + ts_suffix;
numStats += 1;
}
@@ -238,6 +252,10 @@ exports.init = function graphite_init(startup_time, config, events) {
graphiteStats.flush_time = 0;
graphiteStats.flush_length = 0;
+ if (config.keyNameSanitize !== undefined) {
+ globalKeySanitize = config.keyNameSanitize;
+ }
+
flushInterval = config.flushInterval;
flush_counts = typeof(config.flush_counts) === "undefined" ? true : config.flush_counts;
View
@@ -52,6 +52,9 @@ Optional Variables:
deleteCounters: don't send values to graphite for inactive counters, as opposed to sending 0 [default: false]
prefixStats: prefix to use for the statsd statistics data for this running instance of statsd [default: statsd]
applies to both legacy and new namespacing
+ keyNameSanitize: sanitize all stat names on ingress [default: true]
+ If disabled, it is up to the backends to sanitize keynames
+ as appropriate per their storage requirements.
console:
prettyprint: whether to prettyprint the console backend
View
@@ -28,6 +28,7 @@ var flushInterval, keyFlushInt, server, mgmtServer;
var startup_time = Math.round(new Date().getTime() / 1000);
var backendEvents = new events.EventEmitter();
var healthStatus = config.healthStatus || 'up';
+var keyNameSanitize = true;
// Load and init the backend from the backends/ directory.
function loadBackend(config, name) {
@@ -135,6 +136,16 @@ var stats = {
}
};
+function sanitizeKeyName(key) {
+ if (keyNameSanitize) {
+ return key.replace(/\s+/g, '_')
+ .replace(/\//g, '-')
+ .replace(/[^a-zA-Z_\-0-9\.]/g, '');
+ } else {
+ return key;
+ }
+}
+
// Global for the logger
var l;
@@ -156,6 +167,10 @@ config.configFile(process.argv[2], function (config, oldConfig) {
counters[bad_lines_seen] = 0;
counters[packets_received] = 0;
+ if (config.keyNameSanitize !== undefined) {
+ keyNameSanitize = config.keyNameSanitize;
+ }
+
if (server === undefined) {
// key counting
@@ -180,10 +195,7 @@ config.configFile(process.argv[2], function (config, oldConfig) {
l.log(metrics[midx].toString());
}
var bits = metrics[midx].toString().split(':');
- var key = bits.shift()
- .replace(/\s+/g, '_')
- .replace(/\//g, '-')
- .replace(/[^a-zA-Z_\-0-9\.]/g, '');
+ var key = sanitizeKeyName(bits.shift());
if (keyFlushInterval > 0) {
if (! keyCounter[key]) {
@@ -358,5 +358,24 @@ module.exports = {
});
});
});
+ },
+
+ metric_names_are_sanitized: function(test) {
+ var me = this;
+ this.acceptor.once('connection', function(c) {
+ statsd_send('fo/o:250|c',me.sock,'127.0.0.1',8125,function(){
+ statsd_send('b ar:250|c',me.sock,'127.0.0.1',8125,function(){
+ statsd_send('foo+bar:250|c',me.sock,'127.0.0.1',8125,function(){
+ collect_for(me.acceptor, me.myflush, function(strings){
+ var str = strings.join();
+ test.ok(str.indexOf('fo-o') !== -1, "Did not map 'fo/o' => 'fo-o'");
+ test.ok(str.indexOf('b_ar') !== -1, "Did not map 'b ar' => 'b_ar'");
+ test.ok(str.indexOf('foobar') !== -1, "Did not map 'foo+bar' => 'foobar'");
+ test.done();
+ });
+ });
+ });
+ });
+ });
}
}