diff --git a/config.py b/config.py index 23ae647735..c5da02ab8a 100644 --- a/config.py +++ b/config.py @@ -12,7 +12,6 @@ # CONSTANTS DATADOG_CONF = "datadog.conf" DEFAULT_CHECK_FREQUENCY = 15 # seconds -STATSD_FREQUENCY = 2 # seconds def get_parsed_args(): parser = OptionParser() @@ -145,11 +144,7 @@ def get_config(parse_args = True, cfg_path=None, init_logging=False): agentConfig['pup_url'] = config.get('Main', 'pup_url') else: agentConfig['pup_url'] = 'http://localhost:17125' - - # Increases the frequency of statsd metrics when only sending to Pup - if not agentConfig['use_dd'] and agentConfig['use_pup']: - dogstatsd_interval = STATSD_FREQUENCY - + if not agentConfig['use_dd'] and not agentConfig['use_pup']: sys.stderr.write("Please specify at least one endpoint to send metrics to. This can be done in datadog.conf.") exit(2) diff --git a/ddagent.py b/ddagent.py index 636aea1869..58dde5aa82 100755 --- a/ddagent.py +++ b/ddagent.py @@ -105,21 +105,21 @@ def get_url(self, endpoint): return self._application._agentConfig[endpoint] + '/intake/' def flush(self): - for current_idx, endpoint in enumerate(self._endpoints): - logging.debug("End point: %s" % endpoint) - req = tornado.httpclient.HTTPRequest(self.get_url(endpoint), - method = "POST", body = self.get_data() ) + for endpoint in self._endpoints: + url = self.get_url(endpoint) + logging.info("Sending metrics to endpoint %s at %s" % (endpoint, url)) + req = tornado.httpclient.HTTPRequest(url, method="POST", body=self.get_data()) + # Send Transaction to the endpoint http = tornado.httpclient.AsyncHTTPClient() - logging.info(req.url) - - # Check response for last endpoint - # 1st priority, dd_url; 2nd priority, pup_url. + # The success of this metric transaction should only depend on + # whether or not it's successfully sent to datadoghq. If it fails + # getting sent to pup, it's not a big deal. if endpoint == 'dd_url': - http.fetch(req, callback=lambda(x): None) + http.fetch(req, callback=self.on_response) else: - http.fetch(req, callback=lambda(x): self.on_response(x)) + http.fetch(req, callback=lambda(x): None) def on_response(self, response): if response.error: diff --git a/dogstatsd.py b/dogstatsd.py index 39c5689303..131e925d4e 100755 --- a/dogstatsd.py +++ b/dogstatsd.py @@ -325,7 +325,6 @@ def main(config_path=None): target = c['dogstatsd_target'] interval = c['dogstatsd_interval'] api_key = c['api_key'] - host = 'localhost' hostname = gethostname(c) diff --git a/packaging/datadog-agent/source/setup_agent.sh b/packaging/datadog-agent/source/setup_agent.sh index 47d226d430..924f2c2fbe 100644 --- a/packaging/datadog-agent/source/setup_agent.sh +++ b/packaging/datadog-agent/source/setup_agent.sh @@ -31,7 +31,7 @@ pip install argparse # set up the agent mkdir -p $dd_base/agent -$dl_cmd $dd_base/agent.tar.gz https://github.com/DataDog/dd-agent/tarball/add-pup +$dl_cmd $dd_base/agent.tar.gz https://github.com/DataDog/dd-agent/tarball/pup tar -xz -C $dd_base/agent --strip-components 1 -f $dd_base/agent.tar.gz if [ $apikey ]; then sed "s/api_key:.*/api_key: $apikey/" $dd_base/agent/datadog.conf.example > $dd_base/agent/datadog.conf.1 diff --git a/pup/README.markdown b/pup/README.markdown index d714d3bcff..829d23a9be 100644 --- a/pup/README.markdown +++ b/pup/README.markdown @@ -10,6 +10,10 @@ First install the [Datadog agent](https://github.com/DataDog/dd-agent) by runnin That's it! Now navigate to **localhost:17125**. Within ten seconds, system metrics should start streaming. +### Upgrade + +It's easy, just re-install. No files will be lost. + ### Custom metrics with statsd If you would like to add custom metrics to your applications, use the dogstatsd-python or dogstatsd-ruby libraries to instrument your code. Thorough documentation on using DogStatsd and Statsd can be found at [Datadog HQ](http://api.datadoghq.com/guides/dogstatsd/). Below is an abridged introduction to Statsd, and an walkthrough on getting custom metrics viewed on Pup. diff --git a/pup/assets/javascript/Constants.js b/pup/assets/javascript/Constants.js index 1bc08e4b63..4aed5ca1c4 100644 --- a/pup/assets/javascript/Constants.js +++ b/pup/assets/javascript/Constants.js @@ -5,35 +5,35 @@ */ var C = (function() { - return { - WIDTH : "width", - HEIGHT : "height", - G : "g", - TRANSFORM : "transform", - CLASS : "class", - ID : "id", - RECT : "rect", - TEXT : "text", - X : "x", - Y : "y", - D : "d", - PATH : "path", - LINE : "line", - AREA : "area", - PERIOD : ".", - HASH : "#", - TRANSLATE : "translate", - OPENPAREN : "(", - CLOSEPAREN: ")", - DASH : "-", - COMMA : ",", - CLIP : "clip", - URL : "url", - ZERO : "0", - HIDDEN : "hidden", - TIME : "time", - VALUE : "value", - NAME : "name", - TIME : "time" - }; + return { + WIDTH : "width", + HEIGHT : "height", + G : "g", + TRANSFORM : "transform", + CLASS : "class", + ID : "id", + RECT : "rect", + TEXT : "text", + X : "x", + Y : "y", + D : "d", + PATH : "path", + LINE : "line", + AREA : "area", + PERIOD : ".", + HASH : "#", + TRANSLATE : "translate", + OPENPAREN : "(", + CLOSEPAREN: ")", + DASH : "-", + COMMA : ",", + CLIP : "clip", + URL : "url", + ZERO : "0", + HIDDEN : "hidden", + TIME : "time", + VALUE : "value", + NAME : "name", + TIME : "time" + }; }()); diff --git a/pup/assets/javascript/Metric.js b/pup/assets/javascript/Metric.js index 506e022a8e..80d403481a 100644 --- a/pup/assets/javascript/Metric.js +++ b/pup/assets/javascript/Metric.js @@ -1,230 +1,230 @@ /* Metric.js * Defines Metric data objects, such as Line and Histogram. * - * updateMostRecent() : updates the Metric's mostRecent object - * pushRecent() : pushes the Metric's mostRecent object onto data - * shiftOld() : shifts off the Metric's oldest datapoint - * isTimedOut() : returns whether a metric has timed out + * updateMostRecent() : updates the Metric's mostRecent object + * pushRecent() : pushes the Metric's mostRecent object onto data + * shiftOld() : shifts off the Metric's oldest datapoint + * isTimedOut() : returns whether a metric has timed out */ // global variables non-specific to any instance of a metric var allTags = [], - metricId = 0; + metricId = 0; var PupController; var Metric = function(options) { - this.n = PupController.n(); // defines number of datapoints in a graph - this.createdAt = new Date(); // creation timestamp. used in sorting by time added - this.uuid = metricId++; // used in selection. - this.name = options.metric; // name of metric. used for sorting by name - this.type = options.type; // type of metric - this.freq = options.freq * 1000; // estimated frequency of sending in milliseconds - this.tags = options.tags; // tags. used for listing tags - this.max = 0.0; // maximum value. used for determining the y range - this.data = []; // data series for a metric - this.timedOut = {at: +options.now, is: false}; // if timedOut - - for (var i = 0; i < this.tags.length; i++) { - var tag = this.tags[i]; - if (-1 === allTags.indexOf(tag)) { - allTags[allTags.length] = (tag); // used for tag filtering - } - } + this.n = PupController.n(); // defines number of datapoints in a graph + this.createdAt = new Date(); // creation timestamp. used in sorting by time added + this.uuid = metricId++; // used in selection. + this.name = options.metric; // name of metric. used for sorting by name + this.type = options.type; // type of metric + this.freq = options.freq * 1000; // estimated frequency of sending in milliseconds + this.tags = options.tags; // tags. used for listing tags + this.max = 0.0; // maximum value. used for determining the y range + this.data = []; // data series for a metric + this.timedOut = {at: +options.now, is: false}; // if timedOut + + for (var i = 0; i < this.tags.length; i++) { + var tag = this.tags[i]; + if (-1 === allTags.indexOf(tag)) { + allTags[allTags.length] = (tag); // used for tag filtering + } + } }; // Histogram ----------------------------------------------------------- function Histogram(options) { - Metric.call(this, options); - - // allow access from a closure - var n = this.n; - - this.data = d3.range(options.points.length).map(function(d, i) { - return { - "name" : options.points[i].stackName, - "values" : [{"time": +options.now, "value": null}] - }; - }); - - this.mostRecent = options.points.map(function(stk) { - return { - "name" : stk.stackName, - "values" : {time: +options.now, value: null} - }; - }); + Metric.call(this, options); + + // allow access from a closure + var n = this.n; + + this.data = d3.range(options.points.length).map(function(d, i) { + return { + "name" : options.points[i].stackName, + "values" : [{"time": +options.now, "value": null}] + }; + }); + + this.mostRecent = options.points.map(function(stk) { + return { + "name" : stk.stackName, + "values" : {time: +options.now, value: null} + }; + }); } Histogram.prototype.updateMostRecent = function(incomingMetric, metric) { - var max = this.max; - var average = this.average; - this.mostRecent = incomingMetric.points.map(function(stk) { - return { - "name" : stk.stackName, - "values" : stk.values.map(function(d) { - if (d[1] > max) { max = d[1]; } - if (stk.stackName === "avg") { average = d[1]; } - return { - "time" : d[0] * 1000, - "value" : d[1] - }; - })[0] - }; - }); - this.max = max; - this.average = average; + var max = this.max; + var average = this.average; + this.mostRecent = incomingMetric.points.map(function(stk) { + return { + "name" : stk.stackName, + "values" : stk.values.map(function(d) { + if (d[1] > max) { max = d[1]; } + if (stk.stackName === "avg") { average = d[1]; } + return { + "time" : d[0] * 1000, + "value" : d[1] + }; + })[0] + }; + }); + this.max = max; + this.average = average; }; Histogram.prototype.pushRecent = function() { - for (var i = 0; i < this.data.length; i++) { - var mostRecent = {time: this.mostRecent[i].values.time, value: this.mostRecent[i].values.value}; - this.data[i].values[this.data[i].values.length] = mostRecent; - } + for (var i = 0; i < this.data.length; i++) { + var mostRecent = {time: this.mostRecent[i].values.time, value: this.mostRecent[i].values.value}; + this.data[i].values[this.data[i].values.length] = mostRecent; + } }; Histogram.prototype.pushNull = function(now) { - this.data = this.data.map(function(stk) { - stk.values[stk.values.length] = {time: +now, value: null}; - return stk; - }); + this.data = this.data.map(function(stk) { + stk.values[stk.values.length] = {time: +now, value: null}; + return stk; + }); }; Histogram.prototype.shiftOld = function(timeWindow) { - for (var i = 0; i < this.data.length; i++) { - while (this.data[i].values[0].time < timeWindow) { - if (this.max === this.data[i].values[0].value) { - this.resetMax(); - } - this.data[i].values.shift(); - } - } + for (var i = 0; i < this.data.length; i++) { + while (this.data[i].values[0].time < timeWindow) { + if (this.max === this.data[i].values[0].value) { + this.resetMax(); + } + this.data[i].values.shift(); + } + } }; Histogram.prototype.setIfTimedOut = function(now) { - this.timedOut.is = +now - d3.min(this.mostRecent, function(stk) { - return stk.values.time; - }) > this.freq * 2 ? true : false; - if (this.timedOut.is) { this.timedOut.at = +now; } + this.timedOut.is = +now - d3.min(this.mostRecent, function(stk) { + return stk.values.time; + }) > this.freq * 2 ? true : false; + if (this.timedOut.is) { this.timedOut.at = +now; } }; Histogram.prototype.hasNewData = function() { - return d3.min(this.mostRecent, function(stk) { - return stk.values.time; - }) > d3.min(this.data, function(stk) { - return stk.values[stk.values.length-1].time; - }); + return d3.min(this.mostRecent, function(stk) { + return stk.values.time; + }) > d3.min(this.data, function(stk) { + return stk.values[stk.values.length-1].time; + }); }; Histogram.prototype.resetMax = function() { - var max = this.max; - this.data.map(function(stk) { - stk.values.map(function(d) { - if (d.value > max) { max = d.value; } - }); - }); - this.max = max; + var max = this.max; + this.data.map(function(stk) { + stk.values.map(function(d) { + if (d.value > max) { max = d.value; } + }); + }); + this.max = max; }; Histogram.prototype.toCSV = function() { - var csv = 'time,'; - var sampleStack = this.data[0]; - a = sampleStack; - for (var i = -1, dataCount = sampleStack.values.length; i < dataCount; i++) { - var line = ''; - if (i === -1) { - // headers - for (var stkI = 0, stackCount = this.data.length; stkI < stackCount; stkI++) { - if (line !== '') { line += ","; } - line += this.data[stkI].name; - } - } else { - // data - if (sampleStack.values[i] - && sampleStack.values[i].value == null) { continue; } - line += sampleStack.values[i].time; - for (var stkI = 0, stackCount = this.data.length; stkI < stackCount; stkI++) { - if (line !== '') { line += ","; } - line += this.data[stkI].values[i].value; - } - } - csv += line + '
'; - } - return csv; + var csv = 'time,'; + var sampleStack = this.data[0]; + a = sampleStack; + for (var i = -1, dataCount = sampleStack.values.length; i < dataCount; i++) { + var line = ''; + if (i === -1) { + // headers + for (var stkI = 0, stackCount = this.data.length; stkI < stackCount; stkI++) { + if (line !== '') { line += ","; } + line += this.data[stkI].name; + } + } else { + // data + if (sampleStack.values[i] + && sampleStack.values[i].value == null) { continue; } + line += sampleStack.values[i].time; + for (var stkI = 0, stackCount = this.data.length; stkI < stackCount; stkI++) { + if (line !== '') { line += ","; } + line += this.data[stkI].values[i].value; + } + } + csv += line + '
'; + } + return csv; }; // Line ----------------------------------------------------------- function Line(options) { - Metric.call(this, options); - this.data = [{"time": +options.now, "value": null}]; - this.mostRecent = [{"time": +options.now, "value": null}]; + Metric.call(this, options); + this.data = [{"time": +options.now, "value": null}]; + this.mostRecent = [{"time": +options.now, "value": null}]; } Line.prototype.updateMostRecent = function(incomingMetric, metric) { - var max = this.max; - this.mostRecent[0] = incomingMetric.points.map(function(d) { - if (d[1] > max) { max = d[1]; } - return { - "time" : d[0] * 1000, - "value" : d[1] - }; - })[0]; // should make continuous - this.max = max; + var max = this.max; + this.mostRecent[0] = incomingMetric.points.map(function(d) { + if (d[1] > max) { max = d[1]; } + return { + "time" : d[0] * 1000, + "value" : d[1] + }; + })[0]; // should make continuous + this.max = max; }; Line.prototype.pushNull = function(now) { - this.data[this.data.length] = {time: +now, value: null}; + this.data[this.data.length] = {time: +now, value: null}; }; Line.prototype.pushRecent = function() { - var mostRecent = {time: this.mostRecent[0].time, value: this.mostRecent[0].value}; - this.data[this.data.length] = mostRecent; + var mostRecent = {time: this.mostRecent[0].time, value: this.mostRecent[0].value}; + this.data[this.data.length] = mostRecent; }; Line.prototype.shiftOld = function(timeWindow) { - while (this.data[0].time < timeWindow) { - if (this.max === this.data[0].value) { this.resetMax(); } - this.data.shift(); - } + while (this.data[0].time < timeWindow) { + if (this.max === this.data[0].value) { this.resetMax(); } + this.data.shift(); + } }; Line.prototype.setIfTimedOut = function(now) { - this.timedOut.is = +now - this.mostRecent[0].time > this.freq * 2 ? true : false; - if (this.timedOut.is) { this.timedOut.at = +now; } + this.timedOut.is = +now - this.mostRecent[0].time > this.freq * 2 ? true : false; + if (this.timedOut.is) { this.timedOut.at = +now; } }; Line.prototype.hasNewData = function() { - return this.mostRecent[0].time > this.data[this.data.length-1].time; + return this.mostRecent[0].time > this.data[this.data.length-1].time; }; Line.prototype.resetMax = function() { - var max = this.max; - this.data.map(function(d, i) { - if (d.value > max && i > 0) { - max = d.value; - } - }); - this.max = max; + var max = this.max; + this.data.map(function(d, i) { + if (d.value > max && i > 0) { + max = d.value; + } + }); + this.max = max; }; Line.prototype.toCSV = function() { - var csv = ''; - var data = this.data; - for (var i = -1, len = data.length; i < len; i++) { - var line = ''; - if (i === -1) { - for (var index in data[0]) { - if (line !== '') { line += ","; } - line += index; - } - } else { - if (data[i].value == null) { continue; } - for (var index in data[i]) { - if (data[i].hasOwnProperty(index)) { - if (line !== '') { line += ","; } - line += data[i][index]; - } - } - } - csv += line += '
'; - } - return csv; + var csv = ''; + var data = this.data; + for (var i = -1, len = data.length; i < len; i++) { + var line = ''; + if (i === -1) { + for (var index in data[0]) { + if (line !== '') { line += ","; } + line += index; + } + } else { + if (data[i].value == null) { continue; } + for (var index in data[i]) { + if (data[i].hasOwnProperty(index)) { + if (line !== '') { line += ","; } + line += data[i][index]; + } + } + } + csv += line += '
'; + } + return csv; }; diff --git a/pup/assets/javascript/MetricGraph.js b/pup/assets/javascript/MetricGraph.js index fd1cb7a2e9..f220a78acc 100644 --- a/pup/assets/javascript/MetricGraph.js +++ b/pup/assets/javascript/MetricGraph.js @@ -9,343 +9,343 @@ var MetricGraph = function(options) { /* - * now - n * duration now - duration now - |---------------------------------|--------------------| - VISIBLE INVISIBLE - - Having new points append in the invisible section allows - the transitions to appear smoothly and not jerky. + * now - n * duration now - duration now + |---------------------------------|--------------------| + VISIBLE INVISIBLE + + Having new points append in the invisible section allows + the transitions to appear smoothly and not jerky. */ - var margin = {top: 10, right: 24, bottom: 18, left: 45}, - latestBuff = 10, - width = 470 - margin.right, - height = 140 - margin.top - margin.bottom, - yBuffer = 1.3; - - this.n = options.n; - this.duration = options.duration; - this.metric = options.metric; - this.element = options.element; - this.height = height; - this.width = width; - this.finishedProgress = false; - - var then = options.now - (this.n - 2) * this.duration, - now = options.now - this.duration, - interpolation = "basis", - metric = this.metric; - - // create and initialize scales - this.x = d3.time.scale() - .domain([then, now]) - .range([0, width]); - - this.y = d3.scale.linear() - .range([height, 0]); - - var x = this.x, - y = this.y; - - // graph-specific format - this.format = d3.format(".3s"); - - // create svg - this.svg = this.element.select(".plot") - .append("svg") - .attr(C.WIDTH, width + margin.left + margin.right + latestBuff) - .attr(C.HEIGHT, height + margin.top + margin.bottom) - .append(C.G) - .attr(C.WIDTH, width + margin.left - margin.right) - .attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + margin.left + C.COMMA + margin.top + C.CLOSEPAREN); - - // configure axes - this.xAxis = this.svg.append(C.G) - .attr(C.CLASS, "x axis") - .attr(C.TRANSFORM, "translate(0," + height + C.CLOSEPAREN) - .call(this.x.axis = d3.svg.axis().scale(this.x) - .orient("bottom") - .ticks(5) - .tickSize(-this.height) - .tickPadding(4) - .tickSubdivide(true)); - - this.yAxis = this.svg.append(C.G) - .attr(C.CLASS, "y axis") - .call(this.y.axis = d3.svg.axis().scale(this.y) - .orient("left") - .ticks(5) - .tickFormat(this.format)); - - // avoids awkward on-the-pixel-divider issues - var ANTIALIAS = 0.5; - - // configure the line - this.line = d3.svg.line() - .interpolate(interpolation) - .defined(function(d) { return d.value != null; }) - .x(function(d) { return x(d.time) + ANTIALIAS; }) - .y(function(d) { return y(d.value) + ANTIALIAS; }); - - // configure line generator - this.area = d3.svg.area() - .interpolate(interpolation) - .defined(this.line.defined()) - .x(this.line.x()) - .y0(function(d) { return height; }) - .y1(this.line.y()); - - // clipped so transitions occur smoothly - this.clippedWidth = x(now - metric.freq); - - // add clipPath - this.svg.append("defs").append("clipPath") - .attr(C.ID, function(d,i) { - return "clip" + metric.uuid + C.DASH + i; - }) - .append("rect") - .attr(C.WIDTH, this.clippedWidth) - .attr(C.HEIGHT, height); - - // latest value - this.latest = this.svg.selectAll("text.label") - .data(metric.mostRecent) - .enter().append("text") - .attr(C.CLASS, "latest-val") - .attr(C.ID, function(d, i) { - return C.TEXT + metric.uuid + C.DASH + i; - }) - .attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + this.clippedWidth + C.COMMA + height + C.CLOSEPAREN); - - // add loading bar - var progressBarWidth = 40, - progressBarHeight = 10; - - this.progressBar = this.svg.append(C.G) - .attr(C.ID, "progress-wrapper" + metric.uuid); - - this.progressBar.append("rect") - .attr(C.CLASS, "progress-container") - .attr(C.X, width * 0.5 - progressBarWidth * 0.5) - .attr(C.Y, height * 0.5 - progressBarHeight * 0.5) - .attr(C.WIDTH, progressBarWidth) - .attr(C.HEIGHT, progressBarHeight); - - this.progressBar.append("rect") - .attr(C.CLASS, "progress") - .attr(C.ID, "progress" + metric.uuid) - .attr(C.X, width * 0.5 - progressBarWidth * 0.5) - .attr(C.Y, height * 0.5 - progressBarHeight * 0.5) - .attr(C.WIDTH, 0) - .attr(C.HEIGHT, progressBarHeight); - - // graph tags - this.element.append("ul") - .attr(C.CLASS, "graph-tags") - .text("tags: ") - .selectAll("li") - .data(metric.tags) - .enter().append("li") - .attr(C.CLASS, "graph-tag") - .attr("tag", function(d) { return d; }) - .text(function(d) { return d; }); - - // adds to tag list if there are more tags - d3.select("#tag-list").selectAll("li") - .data(allTags) - .enter().append("li") - .attr("tag", function(d) { return d; }) - .attr(C.CLASS, "tag") - .text(function(d) { return d; }); - - // updates scales - this.updateScales = function(now) { - this.x.domain([now - (this.n - 2) * this.duration, now - this.duration]); - this.y.domain([0, yBuffer * metric.max]); - if (metric.max === 0) { - this.y.domain([0, 1]); - } - }; - - this.tryDrawProgress = function(now) { - if (!this.finishedProgress) { - var timePassed = +now - metric.createdAt; - // multiply freq by 2 to allow for the extra control points - if (timePassed > metric.freq * 2) { - d3.select("#progress-wrapper" + metric.uuid) - .classed("hidden", true); - this.finishedProgress = true; - } else { - d3.select("#progress" + metric.uuid).transition() - .duration(100) - .ease("linear") - .attr(C.WIDTH, timePassed * 20 / metric.freq); - } - } - }; + var margin = {top: 10, right: 24, bottom: 18, left: 45}, + latestBuff = 10, + width = 470 - margin.right, + height = 140 - margin.top - margin.bottom, + yBuffer = 1.3; + + this.n = options.n; + this.duration = options.duration; + this.metric = options.metric; + this.element = options.element; + this.height = height; + this.width = width; + this.finishedProgress = false; + + var then = options.now - (this.n - 2) * this.duration, + now = options.now - this.duration, + interpolation = "basis", + metric = this.metric; + + // create and initialize scales + this.x = d3.time.scale() + .domain([then, now]) + .range([0, width]); + + this.y = d3.scale.linear() + .range([height, 0]); + + var x = this.x, + y = this.y; + + // graph-specific format + this.format = d3.format(".3s"); + + // create svg + this.svg = this.element.select(".plot") + .append("svg") + .attr(C.WIDTH, width + margin.left + margin.right + latestBuff) + .attr(C.HEIGHT, height + margin.top + margin.bottom) + .append(C.G) + .attr(C.WIDTH, width + margin.left - margin.right) + .attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + margin.left + C.COMMA + margin.top + C.CLOSEPAREN); + + // configure axes + this.xAxis = this.svg.append(C.G) + .attr(C.CLASS, "x axis") + .attr(C.TRANSFORM, "translate(0," + height + C.CLOSEPAREN) + .call(this.x.axis = d3.svg.axis().scale(this.x) + .orient("bottom") + .ticks(5) + .tickSize(-this.height) + .tickPadding(4) + .tickSubdivide(true)); + + this.yAxis = this.svg.append(C.G) + .attr(C.CLASS, "y axis") + .call(this.y.axis = d3.svg.axis().scale(this.y) + .orient("left") + .ticks(5) + .tickFormat(this.format)); + + // avoids awkward on-the-pixel-divider issues + var ANTIALIAS = 0.5; + + // configure the line + this.line = d3.svg.line() + .interpolate(interpolation) + .defined(function(d) { return d.value != null; }) + .x(function(d) { return x(d.time) + ANTIALIAS; }) + .y(function(d) { return y(d.value) + ANTIALIAS; }); + + // configure line generator + this.area = d3.svg.area() + .interpolate(interpolation) + .defined(this.line.defined()) + .x(this.line.x()) + .y0(function(d) { return height; }) + .y1(this.line.y()); + + // clipped so transitions occur smoothly + this.clippedWidth = x(now - metric.freq); + + // add clipPath + this.svg.append("defs").append("clipPath") + .attr(C.ID, function(d,i) { + return "clip" + metric.uuid + C.DASH + i; + }) + .append("rect") + .attr(C.WIDTH, this.clippedWidth) + .attr(C.HEIGHT, height); + + // latest value + this.latest = this.svg.selectAll("text.label") + .data(metric.mostRecent) + .enter().append("text") + .attr(C.CLASS, "latest-val") + .attr(C.ID, function(d, i) { + return C.TEXT + metric.uuid + C.DASH + i; + }) + .attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + this.clippedWidth + C.COMMA + height + C.CLOSEPAREN); + + // add loading bar + var progressBarWidth = 40, + progressBarHeight = 10; + + this.progressBar = this.svg.append(C.G) + .attr(C.ID, "progress-wrapper" + metric.uuid); + + this.progressBar.append("rect") + .attr(C.CLASS, "progress-container") + .attr(C.X, width * 0.5 - progressBarWidth * 0.5) + .attr(C.Y, height * 0.5 - progressBarHeight * 0.5) + .attr(C.WIDTH, progressBarWidth) + .attr(C.HEIGHT, progressBarHeight); + + this.progressBar.append("rect") + .attr(C.CLASS, "progress") + .attr(C.ID, "progress" + metric.uuid) + .attr(C.X, width * 0.5 - progressBarWidth * 0.5) + .attr(C.Y, height * 0.5 - progressBarHeight * 0.5) + .attr(C.WIDTH, 0) + .attr(C.HEIGHT, progressBarHeight); + + // graph tags + this.element.append("ul") + .attr(C.CLASS, "graph-tags") + .text("tags: ") + .selectAll("li") + .data(metric.tags) + .enter().append("li") + .attr(C.CLASS, "graph-tag") + .attr("tag", function(d) { return d; }) + .text(function(d) { return d; }); + + // adds to tag list if there are more tags + d3.select("#tag-list").selectAll("li") + .data(allTags) + .enter().append("li") + .attr("tag", function(d) { return d; }) + .attr(C.CLASS, "tag") + .text(function(d) { return d; }); + + // updates scales + this.updateScales = function(now) { + this.x.domain([now - (this.n - 2) * this.duration, now - this.duration]); + this.y.domain([0, yBuffer * metric.max]); + if (metric.max === 0) { + this.y.domain([0, 1]); + } + }; + + this.tryDrawProgress = function(now) { + if (!this.finishedProgress) { + var timePassed = +now - metric.createdAt; + // multiply freq by 2 to allow for the extra control points + if (timePassed > metric.freq * 2) { + d3.select("#progress-wrapper" + metric.uuid) + .classed("hidden", true); + this.finishedProgress = true; + } else { + d3.select("#progress" + metric.uuid).transition() + .duration(100) + .ease("linear") + .attr(C.WIDTH, timePassed * 20 / metric.freq); + } + } + }; }; var LineGraph = function(options) { - MetricGraph.call(this, options); - - var graph = this, - metric = this.metric; - - graph.element.select(".type-symbol") - .append("img") - .attr("src", "/pup-line.png");; - - graph.path = graph.svg.append(C.G) - .attr("clip-path", function(d, i) { - return "url(#clip" + metric.uuid + C.DASH + i + C.CLOSEPAREN; - }) - .append(C.PATH) - .data([metric.data]) - .attr(C.CLASS, C.AREA) - .attr(C.ID, C.AREA + metric.uuid) - .attr(C.D, graph.area); - - graph.stroke = graph.svg.append(C.G) - .attr("clip-path", function(d, i) { - return "url(#clip" + metric.uuid + C.DASH + i + C.CLOSEPAREN; - }) - .append(C.PATH) - .data([metric.data]) - .attr(C.CLASS, C.LINE) - .attr(C.ID, C.LINE + metric.uuid) - .attr(C.D, graph.line); - - graph.updateLatestVal = function(now) { - if (metric.timedOut.is || +now - metric.timedOut.at < metric.freq * 2) { - graph.latest.classed("hidden", true); - } else { - graph.latest.text(graph.format(metric.mostRecent[0].value)) - .classed("hidden", false); - } - }; - - graph.redraw = function(now) { - var g_element = graph.element; - - g_element.select(C.HASH + C.AREA + metric.uuid) - .attr(C.D, graph.area) - .attr(C.TRANSFORM, null); - - g_element.select(C.HASH + C.LINE + metric.uuid) - .attr(C.D, graph.line) - .attr(C.TRANSFORM, null); - - g_element.select(C.HASH + C.TEXT + metric.uuid + C.DASH + C.ZERO).transition() - .attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + (graph.clippedWidth + 4) - + C.COMMA + (graph.y(metric.mostRecent[0].value) + 3) + C.CLOSEPAREN); - - graph.xAxis.call(graph.x.axis); - - graph.yAxis.call(graph.y.axis); - - var xTransition = graph.x(now - (graph.n - 1) * graph.duration); - graph.path.attr(C.TRANSFORM, - C.TRANSLATE + C.OPENPAREN + xTransition + C.CLOSEPAREN); - graph.stroke.attr(C.TRANSFORM, - C.TRANSLATE + C.OPENPAREN + xTransition + C.CLOSEPAREN); - }; + MetricGraph.call(this, options); + + var graph = this, + metric = this.metric; + + graph.element.select(".type-symbol") + .append("img") + .attr("src", "/pup-line.png");; + + graph.path = graph.svg.append(C.G) + .attr("clip-path", function(d, i) { + return "url(#clip" + metric.uuid + C.DASH + i + C.CLOSEPAREN; + }) + .append(C.PATH) + .data([metric.data]) + .attr(C.CLASS, C.AREA) + .attr(C.ID, C.AREA + metric.uuid) + .attr(C.D, graph.area); + + graph.stroke = graph.svg.append(C.G) + .attr("clip-path", function(d, i) { + return "url(#clip" + metric.uuid + C.DASH + i + C.CLOSEPAREN; + }) + .append(C.PATH) + .data([metric.data]) + .attr(C.CLASS, C.LINE) + .attr(C.ID, C.LINE + metric.uuid) + .attr(C.D, graph.line); + + graph.updateLatestVal = function(now) { + if (metric.timedOut.is || +now - metric.timedOut.at < metric.freq * 2) { + graph.latest.classed("hidden", true); + } else { + graph.latest.text(graph.format(metric.mostRecent[0].value)) + .classed("hidden", false); + } + }; + + graph.redraw = function(now) { + var g_element = graph.element; + + g_element.select(C.HASH + C.AREA + metric.uuid) + .attr(C.D, graph.area) + .attr(C.TRANSFORM, null); + + g_element.select(C.HASH + C.LINE + metric.uuid) + .attr(C.D, graph.line) + .attr(C.TRANSFORM, null); + + g_element.select(C.HASH + C.TEXT + metric.uuid + C.DASH + C.ZERO).transition() + .attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + (graph.clippedWidth + 4) + + C.COMMA + (graph.y(metric.mostRecent[0].value) + 3) + C.CLOSEPAREN); + + graph.xAxis.call(graph.x.axis); + + graph.yAxis.call(graph.y.axis); + + var xTransition = graph.x(now - (graph.n - 1) * graph.duration); + graph.path.attr(C.TRANSFORM, + C.TRANSLATE + C.OPENPAREN + xTransition + C.CLOSEPAREN); + graph.stroke.attr(C.TRANSFORM, + C.TRANSLATE + C.OPENPAREN + xTransition + C.CLOSEPAREN); + }; }; var HistogramGraph = function(options) { - MetricGraph.call(this, options); - - var graph = this; - - graph.element.select(".type-symbol") - .append("img") - .attr("src", "/pup-histo.png");; - - var stackData = function(metricData) { - var stack = d3.layout.stack() - .x(function(d) { return d.time; }) - .y(function(d) { return d.value; }) - .out(function(d, y0, y) { - d.y0 = y0; - d.y = d.value; - }); - var values = metricData.map(function(layer) { return layer.values; }) - .sort(function(a, b) { - a = a[a.length-1].value; - b = b[b.length-1].value; - return a === b ? 0 : - (a < b) ? 1 : -1; - }); - return stack(values); - }; - - var height = graph.height, - data = stackData(graph.metric.data), - metric = graph.metric; - - graph.path = this.svg.append(C.G) - .attr("clip-path", function(d, i) { - return "url(#clip" + metric.uuid + C.DASH + i + C.CLOSEPAREN; - }) - .selectAll(C.PATH) - .data(data) - .enter().append(C.PATH) - .attr(C.ID, function(d, i) { - return C.AREA + metric.uuid + C.DASH + i; - }) - .attr(C.CLASS, C.AREA) - .attr(C.D, graph.area); - - graph.stroke = this.svg.append(C.G) - .attr("clip-path", function(d,i) { - return "url(#clip" + metric.uuid + C.DASH + i + C.CLOSEPAREN; - }) - .selectAll(C.PATH) - .data(data) - .enter().append(C.PATH) - .attr(C.ID, function(d, i) { - return C.LINE + metric.uuid + C.DASH + i; - }) - .attr(C.CLASS, C.LINE) - .attr(C.D, graph.line); - - graph.updateLatestVal = function(now) { - if (metric.timedOut.is || +now - metric.timedOut.at < metric.freq * 2) { - graph.latest.classed(C.HIDDEN, true); - } else { - graph.latest.data(metric.mostRecent).text(function(d, i) { - return graph.format(d.values.value); - }).classed(C.HIDDEN, false); - } - }; - - graph.redraw = function(now) { - var g_element = graph.element, - g_metric = graph.metric; - - for (var i = 0, len = metric.data.length; i < len; i++) { - g_element.select(C.HASH + C.AREA + g_metric.uuid + C.DASH + i) - .attr(C.D, graph.area) - .attr(C.TRANSFORM, null); // reverts any transition - - g_element.select(C.HASH + C.LINE + g_metric.uuid + C.DASH + i) - .attr(C.D, graph.line) - .attr(C.TRANSFORM, null); - - g_element.select(C.HASH + C.TEXT + g_metric.uuid + C.DASH + i).transition().attr(C.TRANSFORM, - C.TRANSLATE + C.OPENPAREN + (graph.clippedWidth + 4) + C.COMMA + (graph.y(g_metric.mostRecent[i].values.value) + 3) + C.CLOSEPAREN); - } - - graph.xAxis.call(graph.x.axis); - graph.yAxis.call(graph.y.axis); - - var xTransition = graph.x(now - (graph.n - 1) * graph.duration); - for (var _ = 0, len = graph.metric.data.length; _ < len; _++) { - graph.path.attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + xTransition + C.CLOSEPAREN); - graph.stroke.attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + xTransition + C.CLOSEPAREN); - } - }; + MetricGraph.call(this, options); + + var graph = this; + + graph.element.select(".type-symbol") + .append("img") + .attr("src", "/pup-histo.png");; + + var stackData = function(metricData) { + var stack = d3.layout.stack() + .x(function(d) { return d.time; }) + .y(function(d) { return d.value; }) + .out(function(d, y0, y) { + d.y0 = y0; + d.y = d.value; + }); + var values = metricData.map(function(layer) { return layer.values; }) + .sort(function(a, b) { + a = a[a.length-1].value; + b = b[b.length-1].value; + return a === b ? 0 : + (a < b) ? 1 : -1; + }); + return stack(values); + }; + + var height = graph.height, + data = stackData(graph.metric.data), + metric = graph.metric; + + graph.path = this.svg.append(C.G) + .attr("clip-path", function(d, i) { + return "url(#clip" + metric.uuid + C.DASH + i + C.CLOSEPAREN; + }) + .selectAll(C.PATH) + .data(data) + .enter().append(C.PATH) + .attr(C.ID, function(d, i) { + return C.AREA + metric.uuid + C.DASH + i; + }) + .attr(C.CLASS, C.AREA) + .attr(C.D, graph.area); + + graph.stroke = this.svg.append(C.G) + .attr("clip-path", function(d,i) { + return "url(#clip" + metric.uuid + C.DASH + i + C.CLOSEPAREN; + }) + .selectAll(C.PATH) + .data(data) + .enter().append(C.PATH) + .attr(C.ID, function(d, i) { + return C.LINE + metric.uuid + C.DASH + i; + }) + .attr(C.CLASS, C.LINE) + .attr(C.D, graph.line); + + graph.updateLatestVal = function(now) { + if (metric.timedOut.is || +now - metric.timedOut.at < metric.freq * 2) { + graph.latest.classed(C.HIDDEN, true); + } else { + graph.latest.data(metric.mostRecent).text(function(d, i) { + return graph.format(d.values.value); + }).classed(C.HIDDEN, false); + } + }; + + graph.redraw = function(now) { + var g_element = graph.element, + g_metric = graph.metric; + + for (var i = 0, len = metric.data.length; i < len; i++) { + g_element.select(C.HASH + C.AREA + g_metric.uuid + C.DASH + i) + .attr(C.D, graph.area) + .attr(C.TRANSFORM, null); // reverts any transition + + g_element.select(C.HASH + C.LINE + g_metric.uuid + C.DASH + i) + .attr(C.D, graph.line) + .attr(C.TRANSFORM, null); + + g_element.select(C.HASH + C.TEXT + g_metric.uuid + C.DASH + i).transition().attr(C.TRANSFORM, + C.TRANSLATE + C.OPENPAREN + (graph.clippedWidth + 4) + C.COMMA + (graph.y(g_metric.mostRecent[i].values.value) + 3) + C.CLOSEPAREN); + } + + graph.xAxis.call(graph.x.axis); + graph.yAxis.call(graph.y.axis); + + var xTransition = graph.x(now - (graph.n - 1) * graph.duration); + for (var _ = 0, len = graph.metric.data.length; _ < len; _++) { + graph.path.attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + xTransition + C.CLOSEPAREN); + graph.stroke.attr(C.TRANSFORM, C.TRANSLATE + C.OPENPAREN + xTransition + C.CLOSEPAREN); + } + }; }; diff --git a/pup/assets/javascript/PupController.js b/pup/assets/javascript/PupController.js index e08868e784..49be80a05a 100644 --- a/pup/assets/javascript/PupController.js +++ b/pup/assets/javascript/PupController.js @@ -2,468 +2,468 @@ * Coordinates UI for pup * * Public interface: - * tryStart() : starts controller if not already created - * isRunning() : accessor returns whether or not the controller is running - * n() : accessor returns number of datapoints + * tryStart() : starts controller if not already created + * isRunning() : accessor returns whether or not the controller is running + * n() : accessor returns number of datapoints */ var PupController = function(isWSClosed, Store, $) { - var minutes = 10, // window period - duration = Math.sqrt(minutes * 60 * 1000), // transitions work best if duration and n are close in value - // duration represents the buffer time window for transitions - n = Math.ceil(duration), // number of data points - step = 0, // if smooth transitions are enabled, this signifies the lag - now = new Date(Date.now() - duration), // now set to current time minus a transition period - running = false, // determines whether PupController is running - metrics = [], // an array of all the Metric objects - graphsByName= {}, // an object of all the graph objects, keyed by metric name - sideByName = {}, // an object of all the entries objects, keyed by metric name - format = d3.format(".2s"); // defines format. rounding to two significant digits. - - // private helpers -------------------------------------------------- - - var addEntry = function(metric, creationTime) { - var entry = d3.select("#metric-list") - .append("li") - .attr(C.ID, "li" + metric.uuid) - .attr(C.NAME, metric.name) - .attr(C.TIME, +creationTime); - - entry.append("span") - .attr(C.CLASS, "li-metric") - .text(metric.name); - - entry.append("span") - .attr(C.CLASS, "li-val"); - - sideByName[metric.name] = entry; - }; - - var addGraph = function(metric, creationTime) { - var container = d3.select("#graphs").append("div") - .attr(C.ID, metric.name) - .attr(C.NAME, metric.name) - .attr(C.TIME, +creationTime) - .attr(C.CLASS, "plot-box"); - - var metricHead = container.append("div") - .attr(C.CLASS, "metric-head"); - - metricHead.append("span") - .attr(C.CLASS, "type-symbol"); - - metricHead.append("h5") - .attr(C.CLASS, "metric-name") - .text(metric.name); - - metricHead.append("a") - .attr("href", metric.name); - - metricHead.append("a") - .attr(C.CLASS, "csv") - .attr(C.NAME, metric.name) - .text("CSV"); - - var div = container.append("div") - .attr(C.CLASS, "plot"); - - if (metric.type === "histogram") { - graphsByName[metric.name] = new HistogramGraph({ - metric : metric, - element : container, - n : n, - duration: duration, - now : +now - }); - } else if (metric.type !== "histogram") { - graphsByName[metric.name] = new LineGraph({ - metric : metric, - element : container, - n : n, - duration: duration, - now : +now - }); - } // may be more types - - //sort by the active sorting filter - pub.interact().sort().byActive(); - - // remove the directions and show graphs - $("#waiting, #no-metrics").addClass("hidden"); - $("#graphs, #data-streaming").removeClass("hidden"); - }; - - var clearScreen = function() { - $('#graphs').empty(); - $('#waiting').addClass("hidden"); - $('#data-streaming').addClass("hidden"); - $('#disconnected').removeClass("hidden"); - $('#listening').html("Not " + $('#listening').html()); - window.scrollTo(0,0); - }; - - var run = function() { - var interval = setInterval(function() { - // check if connection closed - if (isWSClosed()) { - running = false; - clearScreen(); - clearTimeout(interval); - } - - now = new Date(); - - // fetch metrics - metrics = Store.getMetrics(); - - var i = metrics.length; - while (i--) { - var metric = metrics[i]; - - // set metric's timedOut value and time - metric.setIfTimedOut(now); - - if (!graphsByName.hasOwnProperty(metric.name)) { - var creationTime = new Date(); - addEntry(metric, creationTime); - addGraph(metric, creationTime); - } - - graph = graphsByName[metric.name]; - graph.updateScales(now); - - // push most recent data point on - if (metric.hasNewData()) { - metric.pushRecent(); - } else if (metric.timedOut.is) { - metric.pushNull(new Date()); - } - - // check if progress bar needs to be drawn - graph.tryDrawProgress(now); - - // redraw the area/line - graph.redraw(now); - - // shift off the old - var timeWindow = +now - (minutes * 60000 + metric.freq); - metric.shiftOld(timeWindow); - - // update entry - graph.updateLatestVal(now); - - // update sidebar - if (metric.type === "histogram") { - sideByName[metric.name].select(".li-val").text(format(metric.average)) - .classed("timed-out", false); - } else { - sideByName[metric.name].select(".li-val").text(format(metric.mostRecent[0].value)) - .classed("timed-out", false); - } - - if (metric.timedOut.is) { - sideByName[metric.name].select(".li-val").html("♦") - .classed("timed-out", true); - } - - if (metric.tags.length) { - $("#tags").removeClass("hidden"); - } - } - }, duration + step + 0.5); - }; - - // public interface -------------------------------------------------- - var pub = {}; - - // attempt to start the controller if it isn't running. - pub.tryStart = function() { - if (!running) { - running = true; - setTimeout(function() { - run(); - }, 0); - return 1; - } else { return 0; } - }; - - // interaction interface - pub.interact = function() { - var graphCount, - totalGraphCount; - - var showLeft = function() { - $("#if-more").removeClass("hidden").detach().insertBefore("#by"); - $("#num-more").html(totalGraphCount - graphCount); - $("#dot").addClass("hidden"); - }; - - var downloadCSV = function(metric) { - var CSVWindow = window.open(); - CSVWindow.document.title = metric.name; - var csv = metric.toCSV(); - CSVWindow.document.write(csv); - CSVWindow.document.body.style.fontFamily = "monospace"; - }; - - // interact public interface ----------------------------------------- - var intPub = {}; - - // internally updates plot counts - intPub.updatePlotCount = function() { - var graphDivs = document.getElementById('graphs').children, - shownCount = 0; - - $(graphDivs).each(function() { - if ( $(this).is(':visible') ) { - shownCount++; - } - }); - - graphCount = shownCount; - totalGraphCount = graphDivs.length; - }; - - // filter graphs and their corresponding sidebar entries by term - intPub.filterBy = function(term) { - var graphDivs = document.getElementById('graphs').children, - entryLis = document.getElementById('metric-list').children, - lowerTerm = term.toLowerCase(), - i = graphDivs.length; - - while (i--) { - var id = graphDivs[i].id, - lowerId = id.toLowerCase(), - re = new RegExp(lowerTerm, 'gi'); - if (lowerId.match(re)) { - graphDivs[i].style.display = ""; - entryLis[i].style.display = ""; - } else { - graphDivs[i].style.display = "none"; - entryLis[i].style.display = "none"; - } - } - - intPub.updatePlotCount(); - if (graphCount < totalGraphCount) { - showLeft(); - } else { - $("#if-more").addClass("hidden"); - $("#dot").removeClass("hidden"); - } - }; - - // sorting interface - intPub.sort = function() { - var graphsRoot = document.getElementById('graphs'), - graphsRootChildren = graphsRoot.children, - entriesRoot = document.getElementById('metric-list'), - entriesRootChildren = entriesRoot.children, - graphsArray = [], - entriesArray = [], - i = graphsRootChildren.length; - - // private sorting helpers ------------------------------------- - var loadArrays = function() { - var j = 0; - while (j < i) { - graphsArray[graphsArray.length] = graphsRootChildren[j]; - entriesArray[graphsArray.length] = entriesRootChildren[j]; - j++; - } - }; - - var emptyArrays = function() { - graphsArray.length = 0; - entriesArray.length = 0; - }; - - var appendSortedArrays = function() { - for (var j = 0; j < i; j++) { - graphsRoot.appendChild(graphsArray[j]); - entriesRoot.appendChild(entriesArray[j]); - } - }; - - var byName = function(array) { - return array.sort(function(a, b) { - a = a.getAttribute(C.NAME).toLowerCase(); - b = b.getAttribute(C.NAME).toLowerCase(); - return a === b ? 0 - : (a < b) ? -1 : 1; - }); - }; - - var byTimeAdded = function(array) { - return array.sort(function(a, b) { - a = parseInt(a.getAttribute(C.TIME), 10); - b = parseInt(b.getAttribute(C.TIME), 10); - return a === b ? 0 - : (a < b) ? -1: 1; - }); - }; - - // sort public interface --------------------------------------- - var sortPub = {}; - - // sort graphs and entries by their names, descending - sortPub.byName = function() { - loadArrays(); - graphsArray = byName(graphsArray); - entriesArray = byName(entriesArray); - appendSortedArrays(); - emptyArrays(); - }; - - // sort graphs and entries by the time they were added - sortPub.byTimeAdded = function() { - loadArrays(); - graphsArray = byTimeAdded(graphsArray); - entriesArray = byTimeAdded(entriesArray); - appendSortedArrays(); - emptyArrays(); - }; - - // find which is active - sortPub.byActive = function() { - var active = $(".sort-active")[0].getAttribute(C.ID); - if (active === "by-name") { - sortPub.byName(); - } else { sortPub.byTimeAdded(); } - }; - - return sortPub; - }; - - // filter shown metrics by active tags - intPub.filterByTags = function(t) { - var graphDivs = document.getElementById('graphs').children, - entries = document.getElementById('metric-list').children, - tags = document.getElementById('tag-list').children, - i = graphDivs.length, - tagSelected = t[0], - key; - - if ($(tagSelected).hasClass("tag-active")) { - $(tagSelected).removeClass("tag-active"); - - // remove highlighted graph tags - $(".graph-tag").each(function() { - if ($(this).html() === $(tagSelected).html()) { - $(this).css("color", "#999"); - } - }); - - // get active tags - var activeTags = []; - for (var j = 0, len = tags.length; j < len; j++) { - if ($(tags[j]).hasClass("tag-active")) { - activeTags[activeTags.length] = tags[j].getAttribute("tag"); - } - } - - // for each plot - while (i--) { - key = graphDivs[i].getAttribute(C.NAME); - var graphTags = Store.getMetricByName(key).tags, - k = activeTags.length; - - // if no tags are selected, show all - if (!activeTags.length) { - graphDivs[i].style.display = ""; - entries[i].style.display = ""; - } else { - // else, check if current graph's tags match active tags - while (k--) { - if (graphTags.indexOf(activeTags[k]) > -1) { break; } - } - - // if current graph's tags match active tags, show them - if (!k) { - graphDivs[i].style.display = ""; - entries[i].style.display = ""; - } - } - } - - var query = $("#query").val(); - if (query.length) { intPub.filterBy(query); } - - intPub.updatePlotCount(); - if (graphCount < totalGraphCount) { - showLeft(); - } else { - $("#if-more").addClass("hidden"); - $("#dot").removeClass("hidden"); - } - } else { - $(tagSelected).addClass("tag-active"); - - // add highlighted graph tags - $(".graph-tag").each(function() { - if ($(this).html() === $(tagSelected).html()) { - $(this).css("color", "#6f56a2"); - } - }); - - while (i--) { - key = graphDivs[i].getAttribute(C.NAME); - - // hide those plots that don't match the tag just selected - if (-1 === Store.getMetricByName(key).tags.indexOf(tagSelected.getAttribute("tag"))) { - graphDivs[i].style.display = "none"; - entries[i].style.display = "none"; - } - } - - intPub.updatePlotCount(); - if (graphCount < totalGraphCount) { - showLeft(); - } - } - return intPub; - }; - - // highlights graph - intPub.highlightGraph = function(metricName) { - var graph = $(".plot-box[name=\"" + metricName + '\"]'); - var graphHeader = $(graph).find('h5'); - $(graphHeader).addClass("highlight-graph-header"); - $(graph).addClass("highlight-graph"); - var entry = $('li[name=\"' + metricName + '\"]'); - $(entry).addClass("highlight-metric"); - return intPub; - }; - - // scroll to the graph just clicked on in the entries list - intPub.scrollToGraph = function(metricName) { - var graph = $('.plot-box[name=\"' + metricName + '\"]'); - var y = $(graph).offset().top; - window.scrollTo(0, y - 15); - return intPub; - }; - - // fade the graph when the mouse leaves the entry - intPub.fadeGraph = function(metricName) { - var graph = $(".plot-box[name=\"" + metricName + '\"]'); - var graphHeader = $(graph).find('h5'); - $(graphHeader).removeClass("highlight-graph-header"); - $(graph).removeClass("highlight-graph"); - var entry = $('li[name=\"' + metricName + '\"]'); - $(entry).removeClass("highlight-metric"); - return intPub; - }; - - intPub.downloadCSV = function(name) { - var metric = Store.getMetricByName(name); - downloadCSV(metric); - }; - - return intPub; - }; - - // accessor for the number of datapoints in each graph - pub.n = function() { return n; }; - - return pub; + var minutes = 10, // window period + duration = Math.sqrt(minutes * 60 * 1000), // transitions work best if duration and n are close in value + // duration represents the buffer time window for transitions + n = Math.ceil(duration), // number of data points + step = 0, // if smooth transitions are enabled, this signifies the lag + now = new Date(Date.now() - duration), // now set to current time minus a transition period + running = false, // determines whether PupController is running + metrics = [], // an array of all the Metric objects + graphsByName= {}, // an object of all the graph objects, keyed by metric name + sideByName = {}, // an object of all the entries objects, keyed by metric name + format = d3.format(".2s"); // defines format. rounding to two significant digits. + + // private helpers -------------------------------------------------- + + var addEntry = function(metric, creationTime) { + var entry = d3.select("#metric-list") + .append("li") + .attr(C.ID, "li" + metric.uuid) + .attr(C.NAME, metric.name) + .attr(C.TIME, +creationTime); + + entry.append("span") + .attr(C.CLASS, "li-metric") + .text(metric.name); + + entry.append("span") + .attr(C.CLASS, "li-val"); + + sideByName[metric.name] = entry; + }; + + var addGraph = function(metric, creationTime) { + var container = d3.select("#graphs").append("div") + .attr(C.ID, metric.name) + .attr(C.NAME, metric.name) + .attr(C.TIME, +creationTime) + .attr(C.CLASS, "plot-box"); + + var metricHead = container.append("div") + .attr(C.CLASS, "metric-head"); + + metricHead.append("span") + .attr(C.CLASS, "type-symbol"); + + metricHead.append("h5") + .attr(C.CLASS, "metric-name") + .text(metric.name); + + metricHead.append("a") + .attr("href", metric.name); + + metricHead.append("a") + .attr(C.CLASS, "csv") + .attr(C.NAME, metric.name) + .text("CSV"); + + var div = container.append("div") + .attr(C.CLASS, "plot"); + + if (metric.type === "histogram") { + graphsByName[metric.name] = new HistogramGraph({ + metric : metric, + element : container, + n : n, + duration: duration, + now : +now + }); + } else if (metric.type !== "histogram") { + graphsByName[metric.name] = new LineGraph({ + metric : metric, + element : container, + n : n, + duration: duration, + now : +now + }); + } // may be more types + + //sort by the active sorting filter + pub.interact().sort().byActive(); + + // remove the directions and show graphs + $("#waiting, #no-metrics").addClass("hidden"); + $("#graphs, #data-streaming").removeClass("hidden"); + }; + + var clearScreen = function() { + $('#graphs').empty(); + $('#waiting').addClass("hidden"); + $('#data-streaming').addClass("hidden"); + $('#disconnected').removeClass("hidden"); + $('#listening').html("Not " + $('#listening').html()); + window.scrollTo(0,0); + }; + + var run = function() { + var interval = setInterval(function() { + // check if connection closed + if (isWSClosed()) { + running = false; + clearScreen(); + clearTimeout(interval); + } + + now = new Date(); + + // fetch metrics + metrics = Store.getMetrics(); + + var i = metrics.length; + while (i--) { + var metric = metrics[i]; + + // set metric's timedOut value and time + metric.setIfTimedOut(now); + + if (!graphsByName.hasOwnProperty(metric.name)) { + var creationTime = new Date(); + addEntry(metric, creationTime); + addGraph(metric, creationTime); + } + + graph = graphsByName[metric.name]; + graph.updateScales(now); + + // push most recent data point on + if (metric.hasNewData()) { + metric.pushRecent(); + } else if (metric.timedOut.is) { + metric.pushNull(new Date()); + } + + // check if progress bar needs to be drawn + graph.tryDrawProgress(now); + + // redraw the area/line + graph.redraw(now); + + // shift off the old + var timeWindow = +now - (minutes * 60000 + metric.freq); + metric.shiftOld(timeWindow); + + // update entry + graph.updateLatestVal(now); + + // update sidebar + if (metric.type === "histogram") { + sideByName[metric.name].select(".li-val").text(format(metric.average)) + .classed("timed-out", false); + } else { + sideByName[metric.name].select(".li-val").text(format(metric.mostRecent[0].value)) + .classed("timed-out", false); + } + + if (metric.timedOut.is) { + sideByName[metric.name].select(".li-val").html("♦") + .classed("timed-out", true); + } + + if (metric.tags.length) { + $("#tags").removeClass("hidden"); + } + } + }, duration + step + 0.5); + }; + + // public interface -------------------------------------------------- + var pub = {}; + + // attempt to start the controller if it isn't running. + pub.tryStart = function() { + if (!running) { + running = true; + setTimeout(function() { + run(); + }, 0); + return 1; + } else { return 0; } + }; + + // interaction interface + pub.interact = function() { + var graphCount, + totalGraphCount; + + var showLeft = function() { + $("#if-more").removeClass("hidden").detach().insertBefore("#by"); + $("#num-more").html(totalGraphCount - graphCount); + $("#dot").addClass("hidden"); + }; + + var downloadCSV = function(metric) { + var CSVWindow = window.open(); + CSVWindow.document.title = metric.name; + var csv = metric.toCSV(); + CSVWindow.document.write(csv); + CSVWindow.document.body.style.fontFamily = "monospace"; + }; + + // interact public interface ----------------------------------------- + var intPub = {}; + + // internally updates plot counts + intPub.updatePlotCount = function() { + var graphDivs = document.getElementById('graphs').children, + shownCount = 0; + + $(graphDivs).each(function() { + if ( $(this).is(':visible') ) { + shownCount++; + } + }); + + graphCount = shownCount; + totalGraphCount = graphDivs.length; + }; + + // filter graphs and their corresponding sidebar entries by term + intPub.filterBy = function(term) { + var graphDivs = document.getElementById('graphs').children, + entryLis = document.getElementById('metric-list').children, + lowerTerm = term.toLowerCase(), + i = graphDivs.length; + + while (i--) { + var id = graphDivs[i].id, + lowerId = id.toLowerCase(), + re = new RegExp(lowerTerm, 'gi'); + if (lowerId.match(re)) { + graphDivs[i].style.display = ""; + entryLis[i].style.display = ""; + } else { + graphDivs[i].style.display = "none"; + entryLis[i].style.display = "none"; + } + } + + intPub.updatePlotCount(); + if (graphCount < totalGraphCount) { + showLeft(); + } else { + $("#if-more").addClass("hidden"); + $("#dot").removeClass("hidden"); + } + }; + + // sorting interface + intPub.sort = function() { + var graphsRoot = document.getElementById('graphs'), + graphsRootChildren = graphsRoot.children, + entriesRoot = document.getElementById('metric-list'), + entriesRootChildren = entriesRoot.children, + graphsArray = [], + entriesArray = [], + i = graphsRootChildren.length; + + // private sorting helpers ------------------------------------- + var loadArrays = function() { + var j = 0; + while (j < i) { + graphsArray[graphsArray.length] = graphsRootChildren[j]; + entriesArray[graphsArray.length] = entriesRootChildren[j]; + j++; + } + }; + + var emptyArrays = function() { + graphsArray.length = 0; + entriesArray.length = 0; + }; + + var appendSortedArrays = function() { + for (var j = 0; j < i; j++) { + graphsRoot.appendChild(graphsArray[j]); + entriesRoot.appendChild(entriesArray[j]); + } + }; + + var byName = function(array) { + return array.sort(function(a, b) { + a = a.getAttribute(C.NAME).toLowerCase(); + b = b.getAttribute(C.NAME).toLowerCase(); + return a === b ? 0 + : (a < b) ? -1 : 1; + }); + }; + + var byTimeAdded = function(array) { + return array.sort(function(a, b) { + a = parseInt(a.getAttribute(C.TIME), 10); + b = parseInt(b.getAttribute(C.TIME), 10); + return a === b ? 0 + : (a < b) ? -1: 1; + }); + }; + + // sort public interface --------------------------------------- + var sortPub = {}; + + // sort graphs and entries by their names, descending + sortPub.byName = function() { + loadArrays(); + graphsArray = byName(graphsArray); + entriesArray = byName(entriesArray); + appendSortedArrays(); + emptyArrays(); + }; + + // sort graphs and entries by the time they were added + sortPub.byTimeAdded = function() { + loadArrays(); + graphsArray = byTimeAdded(graphsArray); + entriesArray = byTimeAdded(entriesArray); + appendSortedArrays(); + emptyArrays(); + }; + + // find which is active + sortPub.byActive = function() { + var active = $(".sort-active")[0].getAttribute(C.ID); + if (active === "by-name") { + sortPub.byName(); + } else { sortPub.byTimeAdded(); } + }; + + return sortPub; + }; + + // filter shown metrics by active tags + intPub.filterByTags = function(t) { + var graphDivs = document.getElementById('graphs').children, + entries = document.getElementById('metric-list').children, + tags = document.getElementById('tag-list').children, + i = graphDivs.length, + tagSelected = t[0], + key; + + if ($(tagSelected).hasClass("tag-active")) { + $(tagSelected).removeClass("tag-active"); + + // remove highlighted graph tags + $(".graph-tag").each(function() { + if ($(this).html() === $(tagSelected).html()) { + $(this).css("color", "#999"); + } + }); + + // get active tags + var activeTags = []; + for (var j = 0, len = tags.length; j < len; j++) { + if ($(tags[j]).hasClass("tag-active")) { + activeTags[activeTags.length] = tags[j].getAttribute("tag"); + } + } + + // for each plot + while (i--) { + key = graphDivs[i].getAttribute(C.NAME); + var graphTags = Store.getMetricByName(key).tags, + k = activeTags.length; + + // if no tags are selected, show all + if (!activeTags.length) { + graphDivs[i].style.display = ""; + entries[i].style.display = ""; + } else { + // else, check if current graph's tags match active tags + while (k--) { + if (graphTags.indexOf(activeTags[k]) > -1) { break; } + } + + // if current graph's tags match active tags, show them + if (!k) { + graphDivs[i].style.display = ""; + entries[i].style.display = ""; + } + } + } + + var query = $("#query").val(); + if (query.length) { intPub.filterBy(query); } + + intPub.updatePlotCount(); + if (graphCount < totalGraphCount) { + showLeft(); + } else { + $("#if-more").addClass("hidden"); + $("#dot").removeClass("hidden"); + } + } else { + $(tagSelected).addClass("tag-active"); + + // add highlighted graph tags + $(".graph-tag").each(function() { + if ($(this).html() === $(tagSelected).html()) { + $(this).css("color", "#6f56a2"); + } + }); + + while (i--) { + key = graphDivs[i].getAttribute(C.NAME); + + // hide those plots that don't match the tag just selected + if (-1 === Store.getMetricByName(key).tags.indexOf(tagSelected.getAttribute("tag"))) { + graphDivs[i].style.display = "none"; + entries[i].style.display = "none"; + } + } + + intPub.updatePlotCount(); + if (graphCount < totalGraphCount) { + showLeft(); + } + } + return intPub; + }; + + // highlights graph + intPub.highlightGraph = function(metricName) { + var graph = $(".plot-box[name=\"" + metricName + '\"]'); + var graphHeader = $(graph).find('h5'); + $(graphHeader).addClass("highlight-graph-header"); + $(graph).addClass("highlight-graph"); + var entry = $('li[name=\"' + metricName + '\"]'); + $(entry).addClass("highlight-metric"); + return intPub; + }; + + // scroll to the graph just clicked on in the entries list + intPub.scrollToGraph = function(metricName) { + var graph = $('.plot-box[name=\"' + metricName + '\"]'); + var y = $(graph).offset().top; + window.scrollTo(0, y - 15); + return intPub; + }; + + // fade the graph when the mouse leaves the entry + intPub.fadeGraph = function(metricName) { + var graph = $(".plot-box[name=\"" + metricName + '\"]'); + var graphHeader = $(graph).find('h5'); + $(graphHeader).removeClass("highlight-graph-header"); + $(graph).removeClass("highlight-graph"); + var entry = $('li[name=\"' + metricName + '\"]'); + $(entry).removeClass("highlight-metric"); + return intPub; + }; + + intPub.downloadCSV = function(name) { + var metric = Store.getMetricByName(name); + downloadCSV(metric); + }; + + return intPub; + }; + + // accessor for the number of datapoints in each graph + pub.n = function() { return n; }; + + return pub; }(PupSocket.isClosed, Store, $); diff --git a/pup/assets/javascript/PupSocket.js b/pup/assets/javascript/PupSocket.js index c8933ad922..470158ffde 100644 --- a/pup/assets/javascript/PupSocket.js +++ b/pup/assets/javascript/PupSocket.js @@ -3,82 +3,82 @@ * Creates WebSocket connection and redirects incoming data to storage. * * Public interface: - * tryStart() : creates WebSocket connection if unestablished. - * isEstablished() : returns a boolean value representing whether a WebSocket - * connection was tried and established. - * isClosed() : returns a boolean value representing whether the WebSocket - * connection was closed. + * tryStart() : creates WebSocket connection if unestablished. + * isEstablished() : returns a boolean value representing whether a WebSocket + * connection was tried and established. + * isClosed() : returns a boolean value representing whether the WebSocket + * connection was closed. */ var PupSocket = function(port, save) { - var connEstablished = false, - hitLimitOnce = false; + var connEstablished = false, + hitLimitOnce = false; - // private methods -------------------------------------------------------------- - var setEstablished = function(bool) { - connEstablished = bool; - }; + // private methods -------------------------------------------------------------- + var setEstablished = function(bool) { + connEstablished = bool; + }; - // public interface ------------------------------------------------------------- - var pub = {}; + // public interface ------------------------------------------------------------- + var pub = {}; - // accessor to whether WebSocket connection to pup server is established - pub.isEstablished = function() { - return connEstablished; - }; + // accessor to whether WebSocket connection to pup server is established + pub.isEstablished = function() { + return connEstablished; + }; - // tries to create a connection if not already established - pub.tryStart = function(port) { - if (this.isEstablished()) { return; } - var ws = new WebSocket("ws://" + window.location.hostname + ":" + port + "/pupsocket"); - - setEstablished(true); + // tries to create a connection if not already established + pub.tryStart = function(port) { + if (this.isEstablished()) { return; } + var ws = new WebSocket("ws://" + window.location.hostname + ":" + port + "/pupsocket"); + + setEstablished(true); - ws.onmessage = function(evt) { - var incoming; - try { incoming = JSON.parse(evt.data); } - catch (err) { - setEstablished(false); - throw "There was an error parsing the incoming data: " + err; - } + ws.onmessage = function(evt) { + var incoming; + try { incoming = JSON.parse(evt.data); } + catch (err) { + setEstablished(false); + throw "There was an error parsing the incoming data: " + err; + } - var attempt = save(incoming); - switch(attempt) { - case 0: - // normal - break; - case 1: - // malformed data - setEstablished(false); - throw "Malformed data sent to client"; - case 2: - // display graph limit hit notice for 5 seconds - if (!hitLimitOnce) { - var hitLimit = document.getElementById("limit-error"); - hitLimit.innerHTML = "You have reached the graph count limit. This limit is enforced for reasons of performance."; - setTimeout(function() { - hitLimit.innerHTML = ""; - }, 5000); - hitLimitOnce = true; - } - break; - } - }; + var attempt = save(incoming); + switch(attempt) { + case 0: + // normal + break; + case 1: + // malformed data + setEstablished(false); + throw "Malformed data sent to client"; + case 2: + // display graph limit hit notice for 5 seconds + if (!hitLimitOnce) { + var hitLimit = document.getElementById("limit-error"); + hitLimit.innerHTML = "You have reached the graph count limit. This limit is enforced for reasons of performance."; + setTimeout(function() { + hitLimit.innerHTML = ""; + }, 5000); + hitLimitOnce = true; + } + break; + } + }; - ws.onclose = function() { - setEstablished(false); - }; + ws.onclose = function() { + setEstablished(false); + }; - ws.onerror = function() { - setEstablished(false); - // perhaps restart - }; + ws.onerror = function() { + setEstablished(false); + // perhaps restart + }; - return pub; - }; + return pub; + }; - // for PupController to determine whether it should run - pub.isClosed = function() { return false === connEstablished; }; + // for PupController to determine whether it should run + pub.isClosed = function() { return false === connEstablished; }; - return pub; + return pub; }(port, Store.save); diff --git a/pup/assets/javascript/Store.js b/pup/assets/javascript/Store.js index b9c520209c..e529f1802b 100644 --- a/pup/assets/javascript/Store.js +++ b/pup/assets/javascript/Store.js @@ -2,98 +2,98 @@ * A datastore which stores all the metric objects for MetricGraph to later graph. * * Public interface: - * save() : iterates through incoming metric data, creates new metrics, - * and updates data. Returns success and error codes. - * 0 - normal - * 1 - malformed incoming data - * 2 - hit graph limit - * getMetrics() : returns all metrics in datastore as an array - * getMetricByName() : returns a metric by name. Returns undefined if the metric - * doesn't exist. + * save() : iterates through incoming metric data, creates new metrics, + * and updates data. Returns success and error codes. + * 0 - normal + * 1 - malformed incoming data + * 2 - hit graph limit + * getMetrics() : returns all metrics in datastore as an array + * getMetricByName() : returns a metric by name. Returns undefined if the metric + * doesn't exist. */ var Store = function() { - var metricsByName = {}, - limit = 20, - limitErrorShown = false; + var metricsByName = {}, + limit = 20, + limitErrorShown = false; - // private helpers --------------------------------------------------------- - - // creates and return a certain type of Metric based on incoming metric type - var createMetric = function(incoming, metric) { - var MetricClass; - if (incoming[metric].type === "histogram") { - MetricClass = Histogram; - } else if (incoming[metric].type !== "histogram") { - MetricClass = Line; - } // may be more types + // private helpers --------------------------------------------------------- + + // creates and return a certain type of Metric based on incoming metric type + var createMetric = function(incoming, metric) { + var MetricClass; + if (incoming[metric].type === "histogram") { + MetricClass = Histogram; + } else if (incoming[metric].type !== "histogram") { + MetricClass = Line; + } // may be more types - return new MetricClass({ - now: new Date(), - metric: metric, - type: incoming[metric].type, - tags: incoming[metric].tags || [], - freq: incoming[metric].freq, - points: incoming[metric].points - }); - }; + return new MetricClass({ + now: new Date(), + metric: metric, + type: incoming[metric].type, + tags: incoming[metric].tags || [], + freq: incoming[metric].freq, + points: incoming[metric].points + }); + }; - // runs checks on incoming data to verify its format and source - var isReady = function(incoming) { - if ("Waiting" in incoming) { return false; } - return true; - }; + // runs checks on incoming data to verify its format and source + var isReady = function(incoming) { + if ("Waiting" in incoming) { return false; } + return true; + }; - // verifies data - var verify = function(incomingMetric) { - if (incomingMetric.type - && incomingMetric.freq - && incomingMetric.points) { return true; } - return false; - }; + // verifies data + var verify = function(incomingMetric) { + if (incomingMetric.type + && incomingMetric.freq + && incomingMetric.points) { return true; } + return false; + }; - // public interface --------------------------------------------------------- - var pub = {}; + // public interface --------------------------------------------------------- + var pub = {}; - // iterates through incoming metric data, creates new metrics, and updates data - pub.save = function(incoming) { - if (isReady(incoming)) { - for (var metric in incoming) { - if (incoming.hasOwnProperty(metric) && verify(incoming[metric])) { - if ( !(metric in metricsByName) ) { - if ( Object.keys(metricsByName).length < limit) { - metricsByName[metric] = createMetric(incoming, metric); - } else if (!limitErrorShown) { - limitErrorShown = true; - return 2; - } - } - if (metric in metricsByName) { - metricsByName[metric].updateMostRecent(incoming[metric], metric); - } - } else { - return 1; - } - } - return 0; - } - }; + // iterates through incoming metric data, creates new metrics, and updates data + pub.save = function(incoming) { + if (isReady(incoming)) { + for (var metric in incoming) { + if (incoming.hasOwnProperty(metric) && verify(incoming[metric])) { + if ( !(metric in metricsByName) ) { + if ( Object.keys(metricsByName).length < limit) { + metricsByName[metric] = createMetric(incoming, metric); + } else if (!limitErrorShown) { + limitErrorShown = true; + return 2; + } + } + if (metric in metricsByName) { + metricsByName[metric].updateMostRecent(incoming[metric], metric); + } + } else { + return 1; + } + } + return 0; + } + }; - // accessor for all the metrics. Returns an array of all metrics in plotsByName - pub.getMetrics = function() { - var metrics = []; - for (var metric in metricsByName) { - if (metricsByName.hasOwnProperty(metric)) { - metrics.push(metricsByName[metric]); - } - } - return metrics; - }; + // accessor for all the metrics. Returns an array of all metrics in plotsByName + pub.getMetrics = function() { + var metrics = []; + for (var metric in metricsByName) { + if (metricsByName.hasOwnProperty(metric)) { + metrics.push(metricsByName[metric]); + } + } + return metrics; + }; - // accessor for a particular metric by name - pub.getMetricByName = function(name) { - return metricsByName[name]; - }; + // accessor for a particular metric by name + pub.getMetricByName = function(name) { + return metricsByName[name]; + }; - return pub; + return pub; }(); diff --git a/pup/pup.py b/pup/pup.py index 23a1ff3b7f..af1fe522b1 100644 --- a/pup/pup.py +++ b/pup/pup.py @@ -2,26 +2,33 @@ """ Pup.py - Datadog - www.datadoghq.com - --- - Make sense of your IT Data + Datadog + www.datadoghq.com + --- + Make sense of your IT Data - (C) Datadog, Inc. 2012 all rights reserved + (C) Datadog, Inc. 2012 all rights reserved """ +# std lib imports +from collections import defaultdict +import sys +import os +import json +import re +import logging + +# 3p imports +import argparse import tornado from tornado import ioloop from tornado import web from tornado import websocket -from collections import defaultdict +# project imports + +logger = logging.getLogger('pup') -import sys -import os -import json -import argparse -import re AGENT_TRANSLATION = { 'cpuUser' : 'CPU user (%)', @@ -76,26 +83,26 @@ # Comes along with the histogram series. Only min/avg/max are plotted. HISTOGRAM_IGNORE = [ - "count", + "count", "50percentile", - "75percentile", - "85percentile", - "95percentile", - "99percentile" + "75percentile", + "85percentile", + "95percentile", + "99percentile" ] # Ignored namespaces for agent and other Datadog software AGENT_IGNORE = [ - 'dd', - 'app', - 'events' + 'dd', + 'app', + 'events' ] # Check if using old version of Python. Pup's usage of defaultdict requires 2.5 or later, # and tornado only supports 2.5 or later. The agent supports 2.6 onwards it seems. if int(sys.version_info[1]) <= 5: - sys.stderr.write("Pup requires python 2.6 or later.\n") - sys.exit(2) + sys.stderr.write("Pup requires python 2.6 or later.\n") + sys.exit(2) metrics = defaultdict(lambda : defaultdict(list)) listeners = {} @@ -116,13 +123,15 @@ def is_histogram(s): return False def flush(message): + logger.debug("Flushing to %s listeners" % len(listeners)) for listener in listeners: listener.write_message(message) def send_metrics(): if metrics == {}: flush(dict({"Waiting":1})) - else: flush(metrics) + else: + flush(metrics) metrics.clear() def update(series): @@ -169,7 +178,7 @@ def post(self): body = json.loads(self.request.body) series = body['series'] except: - #log.exception("Error parsing the POST request body") + logger.exception("Error parsing the POST request body") return update(series) @@ -178,7 +187,7 @@ def post(self): try: payload = json.loads(self.get_argument('payload')) except: - #log.exception("Error parsing the agent's POST request body") + logger.exception("Error parsing the agent's POST request body") return agent_update(payload) @@ -210,6 +219,9 @@ def on_close(self): def main(): """ Parses arguments and starts Pup server """ + + logger.info("Starting pup") + global port parser = argparse.ArgumentParser(description='Pup server to collect and display metrics at localhost (default port 17125) from dogapi, StatsD, and dd-agent.') parser.add_argument('-p', dest='port', default=17125, type=int, nargs='?', @@ -218,11 +230,12 @@ def main(): port = args.port application.listen(port) - interval_ms = 2000 + interval_ms = 10000 io_loop = ioloop.IOLoop.instance() scheduler = ioloop.PeriodicCallback(send_metrics, interval_ms, io_loop=io_loop) scheduler.start() io_loop.start() + logger.info("Stopping pup") if __name__ == "__main__": main()