diff --git a/README.md b/README.md index c3cef39..8f541b7 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,71 @@ you can use the `instances` key under `cloudwatch` to configure a list of config } } +## Mapping a Graphite key to a metric name + dimension + +You can map a metric key name to a new metricName and to a set of +dimensions to pass to CloudWatch. For example if you have a +graphite-like keys in the form: + +SERVICE_TYPE.INSTANCE_ID.connections.CONNECTION_ID.METRIC_NAME + +you might want to map this name to a corresponding CloudWatch metric +with METRIC with dimensions serviceType, instanceId, connectionId +and the metric value. + +For example if you have: + +prod.i-deadbeef42.connections.123.errors + +You may want to generate a metric with name "errors" and the following +dimensions: + + [ + { Name: "serviceType", Value: "prod" }, + { Name: "instanceId", Value: "i-deadbeef42" }, + { Name: "serviceType", Value: "123" }, + ] + +This can be done by defining a mapping function in the configuration +mapNameToDimensionsFn field. + +The mapNameToDimensionsFn function must accept a function taking a +metricName and a metricType (either "count" or "gauge" or "timer" or +"set"), and returning an object in the form { metricName, +metricDimensions } or null in case you want to skip to send the metric +altogether. +metricDimensions must be an array in the form [ { "Name": ..., +"Value": ...}, ..., { "Name:" ..., "Value: ... } ]. + +Continuing with the former example, a valid implementation would be: + + mapNameToDimensionsFn: function (metricName, metricType) { + // ignore the metricType + + var dimensions = []; + var origMetricName = metricName; + + console.log("matching: " + metricName); + + if ((res = /([^.]+)\.([^.]+)\.connections\.([^.]+)\.(.*)/g.exec(metricName)) != null) { + // match rule: SERVICE_TYPE.INSTANCE_ID.connections.CONNECTION_ID.METRIC_NAME + dimensions = [ + { Name: "serviceType", Value: res[1] }, + { Name: "instanceId", Value: res[2] }, + { Name: "connectionId", Value: res[3] }, + ]; + metricName = res[4]; + } + else { + console.log("discarding metric " + metricName); + return null; + } + + var res = { metricName: metricName, metricDimensions: dimensions }; + console.log(origMetricName + " => " + JSON.stringify(res)); + return res; + } + ## Tutorial diff --git a/lib/aws-cloudwatch-statsd-backend.js b/lib/aws-cloudwatch-statsd-backend.js index 1fff377..560c4f9 100644 --- a/lib/aws-cloudwatch-statsd-backend.js +++ b/lib/aws-cloudwatch-statsd-backend.js @@ -55,6 +55,7 @@ CloudwatchBackend.prototype.flush = function(timestamp, metrics) { var gauges = metrics.gauges; var timers = metrics.timers; var sets = metrics.sets; + var mapFn = this.config.mapNameToDimensionsFn; for (key in counters) { if (key.indexOf('statsd.') == 0) @@ -68,10 +69,22 @@ CloudwatchBackend.prototype.flush = function(timestamp, metrics) { var names = this.config.processKeyForNamespace ? this.processKey(key) : {}; var namespace = this.config.namespace || names.namespace || "AwsCloudWatchStatsdBackend"; var metricName = this.config.metricName || names.metricName || key; + var metricDimensions = []; + var mapRes; + + if (mapFn) { + mapRes = mapFn(metricName, "count"); + if (!mapRes) // skip this + continue; + + metricName = mapRes.metricName; + metricDimensions = mapRes.metricDimensions; + } this.cloudwatch.putMetricData({ MetricData : [{ - MetricName : metricName, + MetricName : metricName, + Dimensions: metricDimensions, Unit : 'Count', Timestamp: new Date(timestamp*1000).toISOString(), Value : counters[key] @@ -105,7 +118,7 @@ CloudwatchBackend.prototype.flush = function(timestamp, metrics) { var sum = min; var mean = min; var maxAtThreshold = max; - + var metricDimensions = []; var message = ""; var key2; @@ -117,9 +130,20 @@ CloudwatchBackend.prototype.flush = function(timestamp, metrics) { var namespace = this.config.namespace || names.namespace || "AwsCloudWatchStatsdBackend"; var metricName = this.config.metricName || names.metricName || key; + if (mapFn) { + mapRes = mapFn(metricName, "timer"); + + if (!mapRes) // skip this + continue; + + metricName = mapRes.metricName; + metricDimensions = mapRes.metricDimensions; + } + this.cloudwatch.putMetricData({ MetricData : [{ MetricName : metricName, + Dimensions: metricDimensions, Unit : 'Milliseconds', Timestamp: new Date(timestamp*1000).toISOString(), StatisticValues: { @@ -148,10 +172,21 @@ CloudwatchBackend.prototype.flush = function(timestamp, metrics) { var names = this.config.processKeyForNamespace ? this.processKey(key) : {}; var namespace = this.config.namespace || names.namespace || "AwsCloudWatchStatsdBackend"; var metricName = this.config.metricName || names.metricName || key; + var metricDimensions = []; + + if (mapFn) { + mapRes = mapFn(metricName, "gauge"); + if (!mapRes) // skip this + continue; + + metricName = mapRes.metricName; + metricDimensions = mapRes.metricDimensions; + } this.cloudwatch.putMetricData({ MetricData : [{ - MetricName : metricName, + MetricName : metricName, + Dimensions: metricDimensions, Unit : 'None', Timestamp: new Date(timestamp*1000).toISOString(), Value : gauges[key] @@ -175,10 +210,21 @@ CloudwatchBackend.prototype.flush = function(timestamp, metrics) { var names = this.config.processKeyForNamespace ? this.processKey(key) : {}; var namespace = this.config.namespace || names.namespace || "AwsCloudWatchStatsdBackend"; var metricName = this.config.metricName || names.metricName || key; + var metricDimensions = []; + + if (mapFn) { + mapRes = mapFn(metricName, "set"); + if (!mapRes) // skip this + continue; + + metricName = mapRes.metricName; + metricDimensions = mapRes.metricDimensions; + } this.cloudwatch.putMetricData({ MetricData : [{ - MetricName : metricName, + MetricName : metricName, + Dimensions: metricDimensions, Unit : 'None', Timestamp: new Date(timestamp*1000).toISOString(), Value : sets[key].values().length