diff --git a/README.md b/README.md
index c0c05f6..9cb7516 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,14 @@
Bucky
=====
-Bucky is a client and server for sending performance data from the client into statsd+graphite, OpenTSDB, or any
+Bucky is a client and server for sending performance data from the client into statsd+graphite, OpenTSDB, InfluxDB, or any
other stats aggregator of your choice.
-It can automatically measure how long your pages take to load, how long AJAX requests take and how long
+Bucky can measure how long your pages take to load, how long AJAX requests take, and how long
various functions take to run. Most importantly, it's taking the measurements on actual page loads,
so the data has the potential to be much more valuable than in vitro measurements.
-If you already use statsd or OpenTSDB, you can get started in just a few minutes. If you're not
+If you already use statsd, OpenTSDB or InfluxDB, you can get started in just a few minutes. If you're not
collecting stats, you [should start](http://github.hubspot.com/BuckyServer/getting_started/choosing_a_stats_service/)!
What gets measured gets managed.
@@ -21,20 +21,37 @@ You can play with Bucky just using the client, but if you'd like to start collec
#### From The Client
-Include [bucky.js](https://raw.github.com/HubSpot/BuckyClient/v0.2.3/bucky.min.js) on your page, the only required config can be done right in the script tag:
+Include [bucky.js](https://raw.github.com/HubSpot/BuckyClient/v0.3.0/bucky.min.js) on your page. All of the required configuration can be done right in the script tag:
```html
```
-That config will automatically log the performance of your page and all the requests you make to a server running at
-/bucky. It will automatically decide the right key name based on the url of the page. If you'd like, you can also specify it manually:
+The example above sets up Bucky to report on page load performance and all XMLHttpRequests. The [data-bucky-host] attribute configures Bucky to send the reporting data to a server that's listening at /bucky. Since no key string was listed Bucky will automatically generate a key name based on the url of the page. If you'd like, you can also specify it manually:
```html
```
-The `Bucky` object will be available globally, but there is nothing you need to call for basic usage.
+Here are the full list of options available as attributes with example values:
+- `data-bucky-host="/bucky"`: Where we can reach your [Bucky server](http://github.com/HubSpot/BuckyServer), including the
+ APP_ROOT.
+
+ The Bucky server has a very liberal CORS config, so we should be able to connect to it even if
+ it's on a different domain, but hosting it on the same domain and port will save you some preflight requests.
+
+- `data-bucky-page="page"`: Turns on page performance logging, this example assigns the key "page".
+
+- `data-bucky-requests="xhr"`: Turns on XMLHttpRequest monitoring, this example assigns the key "xhr".
+
+- `data-bucky-json="true"`: Setting this flag to true changes the output to json. By default Bucky outputs using a line format similar to what's used by statsd.
+
+- `data-bucky-influx-line-protocol="true"`: Change the output to use InfluxDB Line Protocol. The data-bucky-json attribute must setting must be set to true for this option.
+
+- `data-bucky-query-string="escape|replace"`: This option only applies to InfluxDB Line Protocol. The two accepted values for this attribute are "escape" and "replace". When InfluxDB Line protocol is used the full URL will be attached as a tag. If the URL contains a query string, due to InfluxDB Line Protocol restrictions, either the = in the query string will need to be escaped to make the query string part of the URL, or the ? and & need to be replaced with commas to create additional tags.
+
+
+This will create a `Bucky` object with the set attributes. This object will be available globally but there is nothing you need to call for basic usage.
Bucky can also be loaded with AMD or Browserify (see the methods below).
@@ -45,16 +62,16 @@ npm install bucky
```
```coffeescript
-bucky = require('bucky')
+Bucky = require 'bucky'
```
### Configuring
-Before sending any data, call `setOptions` if you're not using the data- attribute based configuration:
+Before sending any data you can call the `setOptions` to set any configuration options. Be aware that configuration set using `setOptions` will override any data attributes that were set in the script tag.
```javascript
Bucky.setOptions({
- host: 'http://myweb.site:9999/bucky'
+ host: 'http://my.web.hostname:5999/bucky'
});
```
@@ -69,7 +86,9 @@ Some options you might be interested in:
- `active`: Should Bucky actually send data? Use this to disable Bucky during local dev for example.
- `sample`: What fraction of clients should actually send data? Use to subsample your clients if you have
too much data coming in.
-
+- `json`: Boolean flag for sending data to the server as json instead of statsd line protocol. Default is false (to use statsd line protocol).
+- `influxLineProtocol`: Boolean flag to change the format of the output to optimize for InfluxDB Line Protocol. Default is False. (influxLineProtocol requires the json flag to be set to true).
+- `queryString`: This option only applies to InfluxDB Line Protocol. The two accepted values are "escape" and "replace". When InfluxDB Line protocol is used the full URL will be attached as a tag. If the URL contains a query string, due to InfluxDB Line Protocol restrictions, either the = in the query string will need to be escaped to make the query string part of the URL, or the ? and & need to be replaced with commas to create additional tags. The default is null.
Take a look at [the source](http://github.com/HubSpot/BuckyClient/blob/master/bucky.coffee#L34) for a
full list of options.
@@ -80,10 +99,10 @@ one go. It won't do anything on browsers which don't support the performance.ti
it will bind an event if the data isn't ready yet.
```coffeescript
-Bucky.sendPagePerformance('where.the.data.should.go')
+Bucky.sendPagePerformance 'key.for.the.data'
```
-Setting `data-bucky-page` triggers this automatically.
+This function gets called by default if the `data-bucky-page` attribute is set in the script tag.
The two most relevant stats provided are `responseEnd` which is the amount of time it took for the
original page to be loaded and `domInteractive` which is the amount of time before the page has
@@ -97,29 +116,45 @@ If you're using Backbone, it might be a good idea to send your data based on rou
```coffeescript
Backbone.history.on 'route', (router, route) ->
# Will only send on the initial page load:
- Bucky.sendPagePerformance("some.location.page.#{ route }")
+ Bucky.sendPagePerformance "key.page.#{ route }"
+```
+
+This is how the data will be sent to the server after page has fully loaded:
+
+With the default statsd line protocol style syntax:
+
+```text
+key.page.routeName.connectEnd:172.000|ms
+key.page.routeName.connectStart:106.000|ms
+key.page.routeName.domComplete:1029.000|ms
+key.page.routeName.domContentLoadedEventEnd:1019.000|ms
+key.page.routeName.domContentLoadedEventStart:980.000|ms
+key.page.routeName.domInteractive:980.000|ms
```
-The data collected will look something like this:
+With JSON turned on:
```javascript
-pages.contactDetail.timing.connectEnd: "172.000|ms"
-pages.contactDetail.timing.connectStart: "106.000|ms"
-pages.contactDetail.timing.domComplete: "1029.000|ms"
-pages.contactDetail.timing.domContentLoadedEventEnd: "1019.000|ms"
-pages.contactDetail.timing.domContentLoadedEventStart: "980.000|ms"
-pages.contactDetail.timing.domInteractive: "980.000|ms"
-pages.contactDetail.timing.domLoading: "254.000|ms"
-pages.contactDetail.timing.domainLookupEnd: "106.000|ms"
-pages.contactDetail.timing.domainLookupStart: "106.000|ms"
-pages.contactDetail.timing.fetchStart: "103.000|ms"
-pages.contactDetail.timing.loadEventEnd: "1030.000|ms"
-pages.contactDetail.timing.loadEventStart: "1029.000|ms"
-pages.contactDetail.timing.navigationStart: "0.000|ms"
-pages.contactDetail.timing.requestStart: "173.000|ms"
-pages.contactDetail.timing.responseEnd: "243.000|ms"
-pages.contactDetail.timing.responseStart: "235.000|ms"
-pages.contactDetail.timing.secureConnectionStart: "106.000|ms"
+{
+ "key.page.routeName.domLoading": "254.000|ms",
+ "key.page.routeName.domainLookupEnd": "106.000|ms",
+ "key.page.routeName.domainLookupStart": "106.000|ms",
+ "key.page.routeName.fetchStart": "103.000|ms",
+ "key.page.routeName.loadEventEnd": "1030.000|ms",
+ "key.page.routeName.loadEventStart": "1029.000|ms"
+}
+```
+
+And with JSON and Influx Line Protocol:
+
+```javascript
+{
+ "key.page.routeName,url=http://window.location.string,timing=navigationStart": "0.000|ms",
+ "key.page.routeName,url=http://window.location.string,timing=requestStart": "173.000|ms",
+ "key.page.routeName,url=http://window.location.string,timing=responseEnd": "243.000|ms",
+ "key.page.routeName,url=http://window.location.string,timing=responseStart": "235.000|ms",
+ "key.page.routeName,url=http://window.location.string,timing=secureConnectionStart": "106.000|ms"
+}
```
A description of what each datapoint represents is included in [the spec](http://www.w3.org/TR/navigation-timing-2/#sec-PerformanceNavigationTiming).
@@ -130,22 +165,38 @@ Bucky can automatically log all ajax requests made by hooking into XMLHttpReques
on the url to try and create a graphite key from it. Enable it as early in your app's load as is possible:
```coffeescript
-Bucky.requests.monitor('my.project.requests')
+Bucky.requests.monitor 'my.project.requests'
```
-Setting `data-bucky-requests` calls this automatically.
+This function gets called by default if the `data-bucky-requests` attribute is set in the script tag.
-The data collected will look something like this for a GET request to
+This is how the data will be sent to the server for a GET request to
`api.hubapi.com/automation/v2/workflows`:
+With the default statsd line protocol style syntax:
+
+```text
+my.project.requests.api.hubapi.automation.v2.workflows.get:656.794|ms
+my.project.requests.api.hubapi.automation.v2.workflows.get.2xx:1|c
+my.project.requests.api.hubapi.automation.v2.workflows.get.200:1|c
+```
+
+With JSON turned on:
+
+```javascript
+{
+ "my.project.requests.api.hubapi.automation.v2.workflows.get.headers": "436.737|ms",
+ "my.project.requests.api.hubapi.automation.v2.workflows.get.receiving": "0.182|ms"
+}
+```
+
+And with JSON and Influx Line Protocol:
+
```javascript
-contacts.web.prod.requests.api.hubapi.automation.v2.workflows.get: "656.794|ms"
-contacts.web.prod.requests.api.hubapi.automation.v2.workflows.get.2xx: "1|c"
-contacts.web.prod.requests.api.hubapi.automation.v2.workflows.get.200: "1|c"
-contacts.web.prod.requests.api.hubapi.automation.v2.workflows.get.headers: "436.737|ms"
-contacts.web.prod.requests.api.hubapi.automation.v2.workflows.get.receiving: "0.182|ms"
-contacts.web.prod.requests.api.hubapi.automation.v2.workflows.get.sending: "0.059|ms"
-contacts.web.prod.requests.api.hubapi.automation.v2.workflows.get.waiting: "206.035|ms"
+{
+ "my.project.requests,url=http://window.location,endpoint=http://api.hubapi.com/automation/v2/workflows,method=get,status=sending": "0.059|ms",
+ "my.project.requests,url=http://window.location,endpoint=http://api.hubapi.com/automation/v2/workflows,method=get,status=waiting": "206.035|ms"
+}
```
#### Prefixing
@@ -153,17 +204,17 @@ contacts.web.prod.requests.api.hubapi.automation.v2.workflows.get.waiting: "206.
You can build a client which will prefix all of your datapoints by calling bucky as a function:
```coffeescript
-myBucky = Bucky('awesome.app.view')
+myBucky = Bucky 'awesome.app.view'
# You can then use all of the normal methods:
-myBucky.send('data.point', 5)
+myBucky.send 'data.point', 5
```
You can repeatedly call clients to add more prefixes:
```coffeescript
-contactsBucky = bucky('contacts')
-cwBucky = contactsBucky('web')
+contactsBucky = bucky 'contacts'
+cwBucky = contactsBucky 'web'
cwBucky.send('x', 1) # Data goes in contacts.web.x
```
@@ -174,8 +225,8 @@ By default `send` sends absolute values, this is rarely what you want when worki
a counter is usually more helpful:
```coffeescript
-bucky.count('my.awesome.thing')
-bucky.count('number.of.chips.eaten', 5)
+bucky.count 'my.awesome.thing'
+bucky.count 'number.of.chips.eaten', 5
```
#### Timing Things
@@ -183,7 +234,7 @@ bucky.count('number.of.chips.eaten', 5)
You can manually send ms durations using `timer.send`:
```coffeescript
-bucky.timer.send('timed.thing', 55)
+bucky.timer.send 'timed.thing', 55
```
Bucky includes a method to time async functions:
@@ -207,10 +258,10 @@ You can time synchronous functions as well:
```coffeescript
bucky.timer.timeSync 'my.awesome.function', ->
- Math.sqrt(100)
+ Math.sqrt 100
```
-The `time` and `timeSync` functions also accept a context and arguments to pass to the
+The `time` and `timeSync` functions also accept a context and arguments to pass to the
called function:
```coffeescript
@@ -220,7 +271,7 @@ bucky.timer.timeSync 'my.render.function', @render, @, arg1, arg2
You can wrap existing functions using `wrap`:
```coffeescript
-func = bucky.timer.wrap('func.time', func)
+func = bucky.timer.wrap 'func.time', func
```
It also supports a special syntax for methods:
@@ -236,7 +287,7 @@ Note that this wrapping does not play nice with CoffeeScript `super` calls.
Bucky also includes a function for measuring the time since the navigationStart event was fired (the beginning of the request):
```coffeescript
-bucky.timer.mark('my.thing.happened')
+bucky.timer.mark 'my.thing.happened'
```
It acts like a timer where the start is always navigation start.
@@ -244,7 +295,7 @@ It acts like a timer where the start is always navigation start.
The stopwatch method allows you to begin a timer which can be stopped multiple times:
```coffeescript
-watch = bucky.stopwatch('some.prefix.if.you.want')
+watch = bucky.stopwatch 'some.prefix.if.you.want'
```
You can then call `watch.mark('key')` to send the time since the stopwatch started, or
@@ -270,7 +321,7 @@ You can find your stats in the `stats` and `stats.timing` folders in graphite, o
Bucky will send your data in bulk from the client either five seconds after the last datapoint is added, or thirty seconds after
the last send, whichever comes first. If you log multiple datapoints within this send frequency, the points will
be averaged (and the appropriate frequency information will be sent to statsd) (with the exception of counters, they
-are incremented). This means that the max and min numbers you get from statsd actually represent the max and min
+are incremented). This means that the max and min numbers you get from statsd actually represent the max and min
5-30 second bucket. Note that this is per-client, not for the entire bucky process (it's generally only important
on the server where you might be pushing out many points with the same key).
diff --git a/bower.json b/bower.json
index 93cca40..041e16f 100644
--- a/bower.json
+++ b/bower.json
@@ -1,5 +1,6 @@
{
"name": "bucky",
+ "version": "0.3.0",
"description": "Collect performance data from the client",
"main": "bucky.js",
"homepage": "http://github.hubspot.com/bucky",
diff --git a/bucky.coffee b/bucky.coffee
index b5baa9a..8e5cca8 100644
--- a/bucky.coffee
+++ b/bucky.coffee
@@ -64,9 +64,42 @@ exportDef = ->
# Set to false to disable sends (in dev mode for example)
active: true
+ # Setting json to true will set the script's output to JSON format instead of statsd line protocol
+ # This will allow for metrics that contain a colin to be contained in the key
+ json: false
+
+ # Setting influxLineProtocol to true will format the metrics keys to influxdb line protocol
+ # Note: The json option must be set to true for influxLineProtocol
+ # The measurement and the tags will be treated as the key
+ # The outbound key be formatted as: measurement,tag1=value,tag2=value
+ # Additional tags and values that you want tracked can be added and passed in with the measurement
+ # Ex: If you're tracking a specific action and you want to be able to distinguish the action based
+ # on user role you can add a role tag to the bucky function call
+ # Bucky.count("actionName,role=" + user.role);
+ # The output will be:
+ # {"actionName,role=admin":"1|c",
+ # "actionName,role=user": "5|c",
+ # "actionName,role=undefined": "50|c"}
+ #
+ # When using requests.monitor or sendPagePerformance the url will be added as a tag
+ # Example use case:
+ # Bucky.sendPagePerformance("page");
+ # Bucky.requests.monitor("ajax");
+ # Example output:
+ # {"page,url=http://localhost:3000/example,data=requestStart": "397|ms",
+ # "ajax,url=http://localhost:3000/example,method=get,status=200": "54|c"}
+ # Note: commas and spaces in tags will be escaped to conform with influxdb line protocol
+ # Ex:
+ # {"page,url=http://localhost:3000/example/#/hash\ with\ spaces\,\ and\ commas,data=requestStart": "223|ms"}
+ # See more about influxdb line format here: https://influxdb.com/docs/v0.9/write_protocols/write_syntax.html
+ influxLineProtocol: false
+
+ # When using influxLineProtocol, determines how query strings are handled, because keys cannot contain an equals
+ queryString: null
+
tagOptions = {}
if not isServer
- $tag = document.querySelector?('[data-bucky-host],[data-bucky-page],[data-bucky-requests]')
+ $tag = document.querySelector?('[data-bucky-host],[data-bucky-page],[data-bucky-requests],[data-bucky-json],[data-bucky-influx-line-protocol],[data-bucky-query-string]')
if $tag
tagOptions = {
host: $tag.getAttribute('data-bucky-host')
@@ -75,16 +108,21 @@ exportDef = ->
# the methods.
pagePerformanceKey: $tag.getAttribute('data-bucky-page')
requestsKey: $tag.getAttribute('data-bucky-requests')
+
+ # These are the change the format of the client output without having to manually call setOptions
+ json: $tag.getAttribute('data-bucky-json')
+ influxLineProtocol: $tag.getAttribute('data-bucky-influx-line-protocol')
+ queryString: $tag.getAttribute('data-bucky-query-string')
}
- for key in ['pagePerformanceKey', 'requestsKey']
+ for key in ['pagePerformanceKey', 'requestsKey', 'json', 'influxLineProtocol', 'queryString']
if tagOptions[key]?.toString().toLowerCase() is 'true' or tagOptions[key] is ''
tagOptions[key] = true
else if tagOptions[key]?.toString().toLowerCase is 'false'
tagOptions[key] = null
-
+
options = extend {}, defaults, tagOptions
-
+
TYPE_MAP =
'timer': 'ms'
'gauge': 'g'
@@ -171,33 +209,44 @@ exportDef = ->
sameOrigin = false
else
# Relative URL
-
+
sameOrigin = true
sendStart = now()
-
- body = ''
- for name, val of data
- body += "#{ name }:#{ val }\n"
+
+ if options.json is true
+ body = JSON.stringify data
+ else
+ body = ''
+ for name, val of data
+ body += "#{ name }:#{ val }\n"
if not sameOrigin and not corsSupport and window?.XDomainRequest?
- # CORS support for IE9
+ # CORS support for IE8/9
req = new window.XDomainRequest
else
req = new (window?.XMLHttpRequest ? XMLHttpRequest)
- # Don't track this request with Bucky, as we'd be tracking our own
- # sends forever. The latency of this request is independently tracked
- # by updateLatency.
- req.bucky = {track: false}
+ # Set flag to not track this request if requests monitoring is turned on,
+ # otherwise the monitoring will enter an infinite loop.
+ # The latency of this request is independently tracked by updateLatency.
+ req._bucky.track = false if req._bucky
- req.open 'POST', "#{ options.host }/v1/send", true
+ endpoint = "#{ options.host }/v1/send"
+ endpoint += "/json" if options.json is true
- req.setRequestHeader 'Content-Type', 'text/plain'
+ req.open 'POST', endpoint, true
- req.addEventListener 'load', ->
- updateLatency(now() - sendStart)
- , false
+ if req.addEventListener
+ req.addEventListener 'load', ->
+ updateLatency(now() - sendStart)
+ , false
+ else if req.attachEvent
+ req.attachEvent 'onload', ->
+ updateLatency(now() - sendStart)
+ else
+ req.onload = ->
+ updateLatency(now() - sendStart)
req.send body
@@ -269,7 +318,7 @@ exportDef = ->
time: (path, action, ctx, args...) ->
timer.start path
- done = =>
+ done = ->
timer.stop path
args.splice(0, 0, done)
@@ -369,15 +418,29 @@ exportDef = ->
return false if sentPerformanceData
if not path or path is true
- path = requests.urlToKey(document.location.toString()) + '.page'
+ path = requests.urlToKey(document.location.toString()) + ".page"
+ if options.influxLineProtocol is true and not influxLineProtocolSet
+ path += ",url=" + (escapeTag document.location.toString()) + ",timing="
+ influxLineProtocolSet = true
if document.readyState in ['uninitialized', 'loading']
# The data isn't fully ready until document load
- window.addEventListener? 'load', =>
- setTimeout =>
- sendPagePerformance.call(@, path)
- , 500
- , false
+ if window.addEventListener
+ window.addEventListener 'load', =>
+ setTimeout =>
+ sendPagePerformance.call(@, path)
+ , 500
+ , false
+ else if window.attachEvent
+ window.attachEvent 'onload', =>
+ setTimeout =>
+ sendPagePerformance.call(@, path)
+ , 500
+ else
+ window.onload = =>
+ setTimeout =>
+ sendPagePerformance.call(@, path)
+ , 500
return false
@@ -385,7 +448,10 @@ exportDef = ->
start = window.performance.timing.navigationStart
for key, time of window.performance.timing when typeof time is 'number'
- timer.send "#{ path }.#{ key }", (time - start)
+ if options.influxLineProtocol is true
+ timer.send (path + key), (time - start)
+ else
+ timer.send "#{ path }.#{ key }", (time - start)
return true
@@ -431,7 +497,10 @@ exportDef = ->
last = time
for status, val of diffs
- timer.send "#{ path }.#{ status }", val
+ if options.influxLineProtocol is true
+ timer.send "#{ path },status=#{ escapeTag status }", val
+ else
+ timer.send "#{ path }.#{ status }", val
urlToKey: (url, type, root) ->
url = url.replace /https?:\/\//i, ''
@@ -489,53 +558,87 @@ exportDef = ->
root = requests.urlToKey(document.location.toString()) + '.requests'
self = this
- done = ({type, url, event, request, readyStateTimes, startTime}) ->
- if startTime?
- dur = now() - startTime
+ done = (req, evt) ->
+ if req._bucky.startTime?
+ dur = now() - req._bucky.startTime
else
return
- url = self.getFullUrl url
- stat = self.urlToKey url, type, root
+ if options.influxLineProtocol is true
+ stat = "#{ root },url=#{ (escapeTag req._bucky.url) },endpoint=#{ (escapeTag req._bucky.endpoint) },method=#{ (escapeTag req._bucky.type) }"
+ else
+ req._bucky.url = self.getFullUrl req._bucky.url
+ stat = self.urlToKey req._bucky.url, req._bucky.type, root
send(stat, dur, 'timer')
- self.sendReadyStateTimes stat, readyStateTimes
+ self.sendReadyStateTimes stat, req._bucky.readyStateTimes
- if request?.status?
- if request.status > 12000
+ if req?.status?
+ if req.status > 12000
# Most browsers return status code 0 for aborted/failed requests. IE returns
# special status codes over 12000: http://msdn.microsoft.com/en-us/library/aa383770%28VS.85%29.aspx
#
# We'll track the 12xxx code, but also store it as a 0
- count("#{ stat }.0")
-
- else if request.status isnt 0
- count("#{ stat }.#{ request.status.toString().charAt(0) }xx")
-
- count("#{ stat }.#{ request.status }")
-
- _XMLHttpRequest = window.XMLHttpRequest
- window.XMLHttpRequest = ->
+ if options.influxLineProtocol is true
+ count("#{ stat },status=0")
+ else
+ count("#{ stat }.0")
+
+ else if req.status isnt 0
+ if options.influxLineProtocol is true
+ count("#{ stat },status=#{ req.status.toString().charAt(0) }xx")
+ else
+ count("#{ stat }.#{ req.status.toString().charAt(0) }xx")
+
+ if options.influxLineProtocol is true
+ count("#{ stat },status=#{ req.status }")
+ else
+ count("#{ stat }.#{ req.status }")
+
+ xhr = ->
req = new _XMLHttpRequest
try
- startTime = null
- readyStateTimes = {}
+ req._bucky = {}
+ req._bucky.startTime = null
+ req._bucky.readyStateTimes = {}
+ req._bucky.isDone = false
+ req._bucky.track = true
_open = req.open
req.open = (type, url, async) ->
try
- readyStateTimes[0] = now()
-
- req.addEventListener 'readystatechange', ->
- readyStateTimes[req.readyState] = now()
- , false
-
- req.addEventListener 'loadend', (event) ->
- if not req.bucky? or req.bucky.track isnt false
- done {type, url, event, startTime, readyStateTimes, request: req}
- , false
+ req._bucky.type = type
+ req._bucky.readyStateTimes[0] = now()
+ req._bucky.endpoint = url
+ req._bucky.url = document.location.toString()
+
+ if !!req.addEventListener
+ req.addEventListener 'readystatechange', (evt) ->
+ if req._bucky.track is not true
+ return
+ req._bucky.readyStateTimes[req.readyState] = now()
+ if req.readyState == 4 and req._bucky.isDone isnt true
+ req._bucky.isDone = true
+ done req, evt
+ , false
+ else if !!req.attachEvent
+ req.attachEvent 'onreadystatechange', (evt) ->
+ if req._bucky.track is not true
+ return
+ req._bucky.readyStateTimes[req.readyState] = now()
+ if req.readyState == 4 and req._bucky.isDone isnt true
+ req._bucky.isDone = true
+ done req, evt
+ else
+ req.onreadystatechange = (evt) ->
+ if req._bucky.track is not true
+ return
+ req._bucky.readyStateTimes[req.readyState] = now()
+ if req.readyState == 4 and req._bucky.isDone isnt true
+ req._bucky.isDone = true
+ done req, evt
catch e
log.error "Bucky error monitoring XHR open call", e
@@ -543,15 +646,26 @@ exportDef = ->
_send = req.send
req.send = ->
- startTime = now()
+ req._bucky.startTime = now()
_send.apply req, arguments
catch e
log.error "Bucky error monitoring XHR", e
req
+
+ _XMLHttpRequest = window.XMLHttpRequest
+ window.XMLHttpRequest = xhr
}
+ escapeTag = (tag) ->
+ tag = tag.replace /\\?( |,)/g, "\\$1"
+ if options.queryString == 'replace'
+ tag = tag.replace /(\?|&)/g, ","
+ if options.queryString == 'escape'
+ tag = tag.replace /\\=/g, "\\="
+ tag
+
nextMakeClient = (nextPrefix='') ->
path = prefix ? ''
path += '.' if path and nextPrefix
diff --git a/bucky.js b/bucky.js
index 360710a..dc0aca6 100644
--- a/bucky.js
+++ b/bucky.js
@@ -58,18 +58,24 @@
decimalPrecision: 3,
sendLatency: false,
sample: 1,
- active: true
+ active: true,
+ json: false,
+ influxLineProtocol: false,
+ queryString: null
};
tagOptions = {};
if (!isServer) {
- $tag = typeof document.querySelector === "function" ? document.querySelector('[data-bucky-host],[data-bucky-page],[data-bucky-requests]') : void 0;
+ $tag = typeof document.querySelector === "function" ? document.querySelector('[data-bucky-host],[data-bucky-page],[data-bucky-requests],[data-bucky-json],[data-bucky-influx-line-protocol],[data-bucky-query-string]') : void 0;
if ($tag) {
tagOptions = {
host: $tag.getAttribute('data-bucky-host'),
pagePerformanceKey: $tag.getAttribute('data-bucky-page'),
- requestsKey: $tag.getAttribute('data-bucky-requests')
+ requestsKey: $tag.getAttribute('data-bucky-requests'),
+ json: $tag.getAttribute('data-bucky-json'),
+ influxLineProtocol: $tag.getAttribute('data-bucky-influx-line-protocol'),
+ queryString: $tag.getAttribute('data-bucky-query-string')
};
- _ref = ['pagePerformanceKey', 'requestsKey'];
+ _ref = ['pagePerformanceKey', 'requestsKey', 'json', 'influxLineProtocol', 'queryString'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
key = _ref[_i];
if (((_ref1 = tagOptions[key]) != null ? _ref1.toString().toLowerCase() : void 0) === 'true' || tagOptions[key] === '') {
@@ -144,7 +150,7 @@
}
};
makeRequest = function(data) {
- var body, corsSupport, match, name, origin, req, sameOrigin, sendStart, val, _ref3;
+ var body, corsSupport, endpoint, match, name, origin, req, sameOrigin, sendStart, val, _ref3;
corsSupport = isServer || (window.XMLHttpRequest && (window.XMLHttpRequest.defake || 'withCredentials' in new window.XMLHttpRequest()));
if (isServer) {
sameOrigin = true;
@@ -162,24 +168,41 @@
}
}
sendStart = now();
- body = '';
- for (name in data) {
- val = data[name];
- body += "" + name + ":" + val + "\n";
+ if (options.json === true) {
+ body = JSON.stringify(data);
+ } else {
+ body = '';
+ for (name in data) {
+ val = data[name];
+ body += "" + name + ":" + val + "\n";
+ }
}
if (!sameOrigin && !corsSupport && ((typeof window !== "undefined" && window !== null ? window.XDomainRequest : void 0) != null)) {
req = new window.XDomainRequest;
} else {
req = new ((_ref3 = typeof window !== "undefined" && window !== null ? window.XMLHttpRequest : void 0) != null ? _ref3 : XMLHttpRequest);
}
- req.bucky = {
- track: false
- };
- req.open('POST', "" + options.host + "/v1/send", true);
- req.setRequestHeader('Content-Type', 'text/plain');
- req.addEventListener('load', function() {
- return updateLatency(now() - sendStart);
- }, false);
+ if (req._bucky) {
+ req._bucky.track = false;
+ }
+ endpoint = "" + options.host + "/v1/send";
+ if (options.json === true) {
+ endpoint += "/json";
+ }
+ req.open('POST', endpoint, true);
+ if (req.addEventListener) {
+ req.addEventListener('load', function() {
+ return updateLatency(now() - sendStart);
+ }, false);
+ } else if (req.attachEvent) {
+ req.attachEvent('onload', function() {
+ return updateLatency(now() - sendStart);
+ });
+ } else {
+ req.onload = function() {
+ return updateLatency(now() - sendStart);
+ };
+ }
req.send(body);
return req;
};
@@ -225,7 +248,7 @@
}
};
makeClient = function(prefix) {
- var buildPath, count, exports, nextMakeClient, requests, send, sendPagePerformance, sentPerformanceData, timer, val;
+ var buildPath, count, escapeTag, exports, nextMakeClient, requests, send, sendPagePerformance, sentPerformanceData, timer, val;
if (prefix == null) {
prefix = '';
}
@@ -252,8 +275,7 @@
return send(path, duration, 'timer');
},
time: function() {
- var action, args, ctx, done, path,
- _this = this;
+ var action, args, ctx, done, path;
path = arguments[0], action = arguments[1], ctx = arguments[2], args = 4 <= arguments.length ? __slice.call(arguments, 3) : [];
timer.start(path);
done = function() {
@@ -365,7 +387,7 @@
};
sentPerformanceData = false;
sendPagePerformance = function(path) {
- var start, time, _ref3, _ref4, _ref5,
+ var influxLineProtocolSet, start, time, _ref3, _ref4, _ref5,
_this = this;
if ((typeof window !== "undefined" && window !== null ? (_ref3 = window.performance) != null ? _ref3.timing : void 0 : void 0) == null) {
return false;
@@ -374,15 +396,31 @@
return false;
}
if (!path || path === true) {
- path = requests.urlToKey(document.location.toString()) + '.page';
+ path = requests.urlToKey(document.location.toString()) + ".page";
+ }
+ if (options.influxLineProtocol === true && !influxLineProtocolSet) {
+ path += ",url=" + (escapeTag(document.location.toString())) + ",timing=";
+ influxLineProtocolSet = true;
}
if ((_ref4 = document.readyState) === 'uninitialized' || _ref4 === 'loading') {
- if (typeof window.addEventListener === "function") {
+ if (window.addEventListener) {
window.addEventListener('load', function() {
return setTimeout(function() {
return sendPagePerformance.call(_this, path);
}, 500);
}, false);
+ } else if (window.attachEvent) {
+ window.attachEvent('onload', function() {
+ return setTimeout(function() {
+ return sendPagePerformance.call(_this, path);
+ }, 500);
+ });
+ } else {
+ window.onload = function() {
+ return setTimeout(function() {
+ return sendPagePerformance.call(_this, path);
+ }, 500);
+ };
}
return false;
}
@@ -392,7 +430,11 @@
for (key in _ref5) {
time = _ref5[key];
if (typeof time === 'number') {
- timer.send("" + path + "." + key, time - start);
+ if (options.influxLineProtocol === true) {
+ timer.send(path + key, time - start);
+ } else {
+ timer.send("" + path + "." + key, time - start);
+ }
}
}
return true;
@@ -452,7 +494,11 @@
_results = [];
for (status in diffs) {
val = diffs[status];
- _results.push(timer.send("" + path + "." + status, val));
+ if (options.influxLineProtocol === true) {
+ _results.push(timer.send("" + path + ",status=" + (escapeTag(status)), val));
+ } else {
+ _results.push(timer.send("" + path + "." + status, val));
+ }
}
return _results;
},
@@ -507,59 +553,98 @@
}
},
monitor: function(root) {
- var done, self, _XMLHttpRequest;
+ var done, self, xhr, _XMLHttpRequest;
if (!root || root === true) {
root = requests.urlToKey(document.location.toString()) + '.requests';
}
self = this;
- done = function(_arg) {
- var dur, event, readyStateTimes, request, startTime, stat, type, url;
- type = _arg.type, url = _arg.url, event = _arg.event, request = _arg.request, readyStateTimes = _arg.readyStateTimes, startTime = _arg.startTime;
- if (startTime != null) {
- dur = now() - startTime;
+ done = function(req, evt) {
+ var dur, stat;
+ if (req._bucky.startTime != null) {
+ dur = now() - req._bucky.startTime;
} else {
return;
}
- url = self.getFullUrl(url);
- stat = self.urlToKey(url, type, root);
+ if (options.influxLineProtocol === true) {
+ stat = "" + root + ",url=" + (escapeTag(req._bucky.url)) + ",endpoint=" + (escapeTag(req._bucky.endpoint)) + ",method=" + (escapeTag(req._bucky.type));
+ } else {
+ req._bucky.url = self.getFullUrl(req._bucky.url);
+ stat = self.urlToKey(req._bucky.url, req._bucky.type, root);
+ }
send(stat, dur, 'timer');
- self.sendReadyStateTimes(stat, readyStateTimes);
- if ((request != null ? request.status : void 0) != null) {
- if (request.status > 12000) {
- count("" + stat + ".0");
- } else if (request.status !== 0) {
- count("" + stat + "." + (request.status.toString().charAt(0)) + "xx");
+ self.sendReadyStateTimes(stat, req._bucky.readyStateTimes);
+ if ((req != null ? req.status : void 0) != null) {
+ if (req.status > 12000) {
+ if (options.influxLineProtocol === true) {
+ count("" + stat + ",status=0");
+ } else {
+ count("" + stat + ".0");
+ }
+ } else if (req.status !== 0) {
+ if (options.influxLineProtocol === true) {
+ count("" + stat + ",status=" + (req.status.toString().charAt(0)) + "xx");
+ } else {
+ count("" + stat + "." + (req.status.toString().charAt(0)) + "xx");
+ }
+ }
+ if (options.influxLineProtocol === true) {
+ return count("" + stat + ",status=" + req.status);
+ } else {
+ return count("" + stat + "." + req.status);
}
- return count("" + stat + "." + request.status);
}
};
- _XMLHttpRequest = window.XMLHttpRequest;
- return window.XMLHttpRequest = function() {
- var e, readyStateTimes, req, startTime, _open, _send;
+ xhr = function() {
+ var e, req, _open, _send;
req = new _XMLHttpRequest;
try {
- startTime = null;
- readyStateTimes = {};
+ req._bucky = {};
+ req._bucky.startTime = null;
+ req._bucky.readyStateTimes = {};
+ req._bucky.isDone = false;
+ req._bucky.track = true;
_open = req.open;
req.open = function(type, url, async) {
var e;
try {
- readyStateTimes[0] = now();
- req.addEventListener('readystatechange', function() {
- return readyStateTimes[req.readyState] = now();
- }, false);
- req.addEventListener('loadend', function(event) {
- if ((req.bucky == null) || req.bucky.track !== false) {
- return done({
- type: type,
- url: url,
- event: event,
- startTime: startTime,
- readyStateTimes: readyStateTimes,
- request: req
- });
- }
- }, false);
+ req._bucky.type = type;
+ req._bucky.readyStateTimes[0] = now();
+ req._bucky.endpoint = url;
+ req._bucky.url = document.location.toString();
+ if (!!req.addEventListener) {
+ req.addEventListener('readystatechange', function(evt) {
+ if (req._bucky.track === !true) {
+ return;
+ }
+ req._bucky.readyStateTimes[req.readyState] = now();
+ if (req.readyState === 4 && req._bucky.isDone !== true) {
+ req._bucky.isDone = true;
+ return done(req, evt);
+ }
+ }, false);
+ } else if (!!req.attachEvent) {
+ req.attachEvent('onreadystatechange', function(evt) {
+ if (req._bucky.track === !true) {
+ return;
+ }
+ req._bucky.readyStateTimes[req.readyState] = now();
+ if (req.readyState === 4 && req._bucky.isDone !== true) {
+ req._bucky.isDone = true;
+ return done(req, evt);
+ }
+ });
+ } else {
+ req.onreadystatechange = function(evt) {
+ if (req._bucky.track === !true) {
+ return;
+ }
+ req._bucky.readyStateTimes[req.readyState] = now();
+ if (req.readyState === 4 && req._bucky.isDone !== true) {
+ req._bucky.isDone = true;
+ return done(req, evt);
+ }
+ };
+ }
} catch (_error) {
e = _error;
log.error("Bucky error monitoring XHR open call", e);
@@ -568,7 +653,7 @@
};
_send = req.send;
req.send = function() {
- startTime = now();
+ req._bucky.startTime = now();
return _send.apply(req, arguments);
};
} catch (_error) {
@@ -577,7 +662,19 @@
}
return req;
};
+ _XMLHttpRequest = window.XMLHttpRequest;
+ return window.XMLHttpRequest = xhr;
+ }
+ };
+ escapeTag = function(tag) {
+ tag = tag.replace(/\\?( |,)/g, "\\$1");
+ if (options.queryString === 'replace') {
+ tag = tag.replace(/(\?|&)/g, ",");
+ }
+ if (options.queryString === 'escape') {
+ tag = tag.replace(/\\=/g, "\\=");
}
+ return tag;
};
nextMakeClient = function(nextPrefix) {
var path;
diff --git a/bucky.min.js b/bucky.min.js
index efd40ce..8f5233f 100644
--- a/bucky.min.js
+++ b/bucky.min.js
@@ -1,2 +1,2 @@
-/*! bucky 0.2.8 */
-(function(){var a,b,c,d,e,f,g,h=[].slice;e="undefined"!=typeof module&&null!==module&&!("undefined"!=typeof window&&null!==window?window.module:void 0),e?(a=require("xmlhttprequest").XMLHttpRequest,g=function(){var a;return a=process.hrtime(),1e3*(a[0]+a[1]/1e9)}):g=function(){var a,b;return null!=(a=null!=(b=window.performance)&&"function"==typeof b.now?b.now():void 0)?a:+new Date},d=+new Date,c=function(){var a,b,c,d,e,f,g;for(a=arguments[0],d=2<=arguments.length?h.call(arguments,1):[],f=0,g=d.length;g>f;f++){c=d[f];for(b in c)e=c[b],a[b]=e}return a},f=function(){var a,b;return a=1<=arguments.length?h.call(arguments,0):[],null!=("undefined"!=typeof console&&null!==console&&null!=(b=console.log)?b.call:void 0)?console.log.apply(console,a):void 0},f.error=function(){var a,b;return a=1<=arguments.length?h.call(arguments,0):[],null!=("undefined"!=typeof console&&null!==console&&null!=(b=console.error)?b.call:void 0)?console.error.apply(console,a):void 0},b=function(){var b,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I;if(n={host:"/bucky",maxInterval:3e4,aggregationInterval:5e3,decimalPrecision:3,sendLatency:!1,sample:1,active:!0},B={},!e&&(b="function"==typeof document.querySelector?document.querySelector("[data-bucky-host],[data-bucky-page],[data-bucky-requests]"):void 0))for(B={host:b.getAttribute("data-bucky-host"),pagePerformanceKey:b.getAttribute("data-bucky-page"),requestsKey:b.getAttribute("data-bucky-requests")},G=["pagePerformanceKey","requestsKey"],E=0,F=G.length;F>E;E++)q=G[E],"true"===(null!=(H=B[q])?H.toString().toLowerCase():void 0)||""===B[q]?B[q]=!0:"false"===(null!=(I=B[q])?I.toString().toLowerCase:void 0)&&(B[q]=null);return v=c({},n,B),k={timer:"ms",gauge:"g",counter:"c"},i=v.active,(C=function(){return i=v.active&&Math.random()k;k++)g=o[k],e=l.transforms.mapping[g],null!=e?"function"!=typeof e?(e instanceof RegExp&&(e=[e,""]),i=i.replace(e[0],e[1])):i=e(i,a,b,c):f.error("Bucky Error: Attempted to enable a mapping which is not defined: "+g);return i=decodeURIComponent(i),i=i.replace(/[^a-zA-Z0-9\-\.\/ ]+/g,"_"),j=d+i.replace(/[\/ ]/g,"."),j=j.replace(/(^\.)|(\.$)/g,""),j=j.replace(/\.com/,""),j=j.replace(/www\./,""),c&&(j=c+"."+j),b&&(j=j+"."+b.toLowerCase()),j=j.replace(/\.\./g,".")},getFullUrl:function(a,b){return null==b&&(b=document.location),/^\//.test(a)?b.hostname+a:/https?:\/\//i.test(a)?a:b.toString()+a},monitor:function(a){var b,d,e;return a&&a!==!0||(a=l.urlToKey(document.location.toString())+".requests"),d=this,b=function(b){var e,f,h,i,j,k,l,n;return l=b.type,n=b.url,f=b.event,i=b.request,h=b.readyStateTimes,j=b.startTime,null!=j?(e=g()-j,n=d.getFullUrl(n),k=d.urlToKey(n,l,a),m(k,e,"timer"),d.sendReadyStateTimes(k,h),null!=(null!=i?i.status:void 0)?(i.status>12e3?c(""+k+".0"):0!==i.status&&c(""+k+"."+i.status.toString().charAt(0)+"xx"),c(""+k+"."+i.status)):void 0):void 0},e=window.XMLHttpRequest,window.XMLHttpRequest=function(){var a,c,d,h,i,j;d=new e;try{h=null,c={},i=d.open,d.open=function(a,e){var j;try{c[0]=g(),d.addEventListener("readystatechange",function(){return c[d.readyState]=g()},!1),d.addEventListener("loadend",function(f){return null==d.bucky||d.bucky.track!==!1?b({type:a,url:e,event:f,startTime:h,readyStateTimes:c,request:d}):void 0},!1)}catch(k){j=k,f.error("Bucky error monitoring XHR open call",j)}return i.apply(d,arguments)},j=d.send,d.send=function(){return h=g(),j.apply(d,arguments)}}catch(k){a=k,f.error("Bucky error monitoring XHR",a)}return d}}},k=function(b){var c;return null==b&&(b=""),c=null!=a?a:"",c&&b&&(c+="."),b&&(c+=b),s(c)},e={send:m,count:c,timer:t,now:g,requests:l,sendPagePerformance:n,flush:p,setOptions:A,options:v,history:j,active:i};for(q in e)u=e[q],k[q]=u;return k},l=s(),v.pagePerformanceKey&&l.sendPagePerformance(v.pagePerformanceKey),v.requestsKey&&l.requests.monitor(v.requestsKey),l},"function"==typeof define&&define.amd?define(b):"object"==typeof exports?module.exports=b():window.Bucky=b()}).call(this);
\ No newline at end of file
+/*! bucky 0.3.0 */
+(function(){var a,b,c,d,e,f,g,h=[].slice;e="undefined"!=typeof module&&null!==module&&!("undefined"!=typeof window&&null!==window?window.module:void 0),e?(a=require("xmlhttprequest").XMLHttpRequest,g=function(){var a;return a=process.hrtime(),1e3*(a[0]+a[1]/1e9)}):g=function(){var a,b;return null!=(a=null!=(b=window.performance)&&"function"==typeof b.now?b.now():void 0)?a:+new Date},d=+new Date,c=function(){var a,b,c,d,e,f,g;for(a=arguments[0],d=2<=arguments.length?h.call(arguments,1):[],f=0,g=d.length;g>f;f++){c=d[f];for(b in c)e=c[b],a[b]=e}return a},f=function(){var a,b;return a=1<=arguments.length?h.call(arguments,0):[],null!=("undefined"!=typeof console&&null!==console&&null!=(b=console.log)?b.call:void 0)?console.log.apply(console,a):void 0},f.error=function(){var a,b;return a=1<=arguments.length?h.call(arguments,0):[],null!=("undefined"!=typeof console&&null!==console&&null!=(b=console.error)?b.call:void 0)?console.error.apply(console,a):void 0},b=function(){var b,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I;if(n={host:"/bucky",maxInterval:3e4,aggregationInterval:5e3,decimalPrecision:3,sendLatency:!1,sample:1,active:!0,json:!1,influxLineProtocol:!1,queryString:null},B={},!e&&(b="function"==typeof document.querySelector?document.querySelector("[data-bucky-host],[data-bucky-page],[data-bucky-requests],[data-bucky-json],[data-bucky-influx-line-protocol],[data-bucky-query-string]"):void 0))for(B={host:b.getAttribute("data-bucky-host"),pagePerformanceKey:b.getAttribute("data-bucky-page"),requestsKey:b.getAttribute("data-bucky-requests"),json:b.getAttribute("data-bucky-json"),influxLineProtocol:b.getAttribute("data-bucky-influx-line-protocol"),queryString:b.getAttribute("data-bucky-query-string")},G=["pagePerformanceKey","requestsKey","json","influxLineProtocol","queryString"],E=0,F=G.length;F>E;E++)q=G[E],"true"===(null!=(H=B[q])?H.toString().toLowerCase():void 0)||""===B[q]?B[q]=!0:"false"===(null!=(I=B[q])?I.toString().toLowerCase:void 0)&&(B[q]=null);return v=c({},n,B),k={timer:"ms",gauge:"g",counter:"c"},i=v.active,(C=function(){return i=v.active&&Math.random()k;k++)g=o[k],e=m.transforms.mapping[g],null!=e?"function"!=typeof e?(e instanceof RegExp&&(e=[e,""]),i=i.replace(e[0],e[1])):i=e(i,a,b,c):f.error("Bucky Error: Attempted to enable a mapping which is not defined: "+g);return i=decodeURIComponent(i),i=i.replace(/[^a-zA-Z0-9\-\.\/ ]+/g,"_"),j=d+i.replace(/[\/ ]/g,"."),j=j.replace(/(^\.)|(\.$)/g,""),j=j.replace(/\.com/,""),j=j.replace(/www\./,""),c&&(j=c+"."+j),b&&(j=j+"."+b.toLowerCase()),j=j.replace(/\.\./g,".")},getFullUrl:function(a,b){return null==b&&(b=document.location),/^\//.test(a)?b.hostname+a:/https?:\/\//i.test(a)?a:b.toString()+a},monitor:function(a){var b,d,h,i;return a&&a!==!0||(a=m.urlToKey(document.location.toString())+".requests"),d=this,b=function(b,f){var h,i;if(null!=b._bucky.startTime)return h=g()-b._bucky.startTime,v.influxLineProtocol===!0?i=""+a+",url="+e(b._bucky.url)+",endpoint="+e(b._bucky.endpoint)+",method="+e(b._bucky.type):(b._bucky.url=d.getFullUrl(b._bucky.url),i=d.urlToKey(b._bucky.url,b._bucky.type,a)),n(i,h,"timer"),d.sendReadyStateTimes(i,b._bucky.readyStateTimes),null!=(null!=b?b.status:void 0)?(b.status>12e3?c(v.influxLineProtocol===!0?""+i+",status=0":""+i+".0"):0!==b.status&&c(v.influxLineProtocol===!0?""+i+",status="+b.status.toString().charAt(0)+"xx":""+i+"."+b.status.toString().charAt(0)+"xx"),c(v.influxLineProtocol===!0?""+i+",status="+b.status:""+i+"."+b.status)):void 0},h=function(){var a,c,d,e;c=new i;try{c._bucky={},c._bucky.startTime=null,c._bucky.readyStateTimes={},c._bucky.isDone=!1,c._bucky.track=!0,d=c.open,c.open=function(a,e,h){var i;try{c._bucky.type=a,c._bucky.readyStateTimes[0]=g(),c._bucky.endpoint=e,c._bucky.url=document.location.toString(),c.addEventListener?c.addEventListener("readystatechange",function(a){return c._bucky.track!==!1?(c._bucky.readyStateTimes[c.readyState]=g(),4===c.readyState&&c._bucky.isDone!==!0?(c._bucky.isDone=!0,b(c,a)):void 0):void 0},!1):c.attachEvent?c.attachEvent("onreadystatechange",function(a){return c._bucky.track!==!1?(c._bucky.readyStateTimes[c.readyState]=g(),4===c.readyState&&c._bucky.isDone!==!0?(c._bucky.isDone=!0,b(c,a)):void 0):void 0}):c.onreadystatechange=function(a){return c._bucky.track!==!1?(c._bucky.readyStateTimes[c.readyState]=g(),4===c.readyState&&c._bucky.isDone!==!0?(c._bucky.isDone=!0,b(c,a)):void 0):void 0}}catch(j){i=j,f.error("Bucky error monitoring XHR open call",i)}return d.apply(c,arguments)},e=c.send,c.send=function(){return c._bucky.startTime=g(),e.apply(c,arguments)}}catch(h){a=h,f.error("Bucky error monitoring XHR",a)}return c},i=window.XMLHttpRequest,window.XMLHttpRequest=h}},e=function(a){return a=a.replace(/\\?( |,)/g,"\\$1"),"replace"===v.queryString&&(a=a.replace(/(\?|&)/g,",")),"escape"===v.queryString&&(a=a.replace(/\\=/g,"\\=")),a},l=function(b){var c;return null==b&&(b=""),c=null!=a?a:"",c&&b&&(c+="."),b&&(c+=b),s(c)},k={send:n,count:c,timer:u,now:g,requests:m,sendPagePerformance:r,flush:p,setOptions:A,options:v,history:j,active:i};for(q in k)w=k[q],l[q]=w;return l},l=s(),v.pagePerformanceKey&&l.sendPagePerformance(v.pagePerformanceKey),v.requestsKey&&l.requests.monitor(v.requestsKey),l},"function"==typeof define&&define.amd?define(b):"object"==typeof exports?module.exports=b():window.Bucky=b()}).call(this);
\ No newline at end of file
diff --git a/component.json b/component.json
index c1bc39b..b034979 100644
--- a/component.json
+++ b/component.json
@@ -2,7 +2,7 @@
"name": "bucky",
"repo": "HubSpot/bucky",
"description": "Collect performance data from the client",
- "version": "0.2.8",
+ "version": "0.3.0",
"homepage": "http://github.hubspot.com/BuckyClient",
"license": "MIT",
"keywords": [
diff --git a/package.json b/package.json
index 1c1e8a1..d0a5aeb 100644
--- a/package.json
+++ b/package.json
@@ -1,20 +1,24 @@
{
"name": "bucky",
- "version": "0.2.8",
+ "version": "0.3.0",
"description": "Collect performance data from the client and node",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git@github.com:HubSpot/BuckyClient.git"
+ },
"main": "./bucky.js",
"dependencies": {
- "xmlhttprequest": "~1.6.0"
+ "xmlhttprequest": "~1.7.0"
},
"devDependencies": {
- "grunt-contrib-concat": "~0.3.0",
- "grunt-contrib-coffee": "~0.7.0",
- "grunt-cli": "~0.1.9",
- "grunt-contrib-uglify": "~0.2.4",
- "grunt": "~0.4.1",
- "grunt-contrib-jasmine": "~0.5.2",
- "grunt-contrib-watch": "~0.5.3",
- "coffee-script": "~1.6.3"
+ "grunt-contrib-concat": "~0.5.1",
+ "grunt-contrib-coffee": "~0.13.0",
+ "grunt-contrib-uglify": "~0.9.2",
+ "grunt": "~0.4.5",
+ "grunt-contrib-jasmine": "~0.9.1",
+ "grunt-contrib-watch": "~0.6.1",
+ "coffee-script": "~1.10.0"
},
"author": "Zack Bloom "
}
diff --git a/spec/bucky.spec.coffee b/spec/bucky.spec.coffee
index ba73dbe..5aabd2e 100644
--- a/spec/bucky.spec.coffee
+++ b/spec/bucky.spec.coffee
@@ -39,7 +39,7 @@ describe 'urlToKey', ->
it 'should add the method', ->
expect(utk('x', 'GET')).toBe('x.get')
-
+
it 'should strip leading and trailing slashes', ->
expect(utk('/a/b/c/')).toBe('a.b.c')
@@ -93,6 +93,8 @@ describe 'send', ->
beforeEach ->
server = sinon.fakeServer.create()
server.autoRespond = true
+ Bucky.setOptions
+ json: false
afterEach ->
server.restore()
@@ -112,3 +114,23 @@ describe 'send', ->
expect(server.requests.length).toBe(1)
expect(server.requests[0].requestBody).toBe("data.1:5|ms\ndata.2:3|ms\n")
+
+ it 'should send a datapoint as JSON', ->
+ Bucky.setOptions
+ json: true
+ Bucky.send 'data.point', 4
+ Bucky.flush()
+
+ expect(server.requests.length).toBe(1)
+ expect(server.requests[0].requestBody).toBe('{"data.point":"4|g"}')
+
+ it 'should send timers as JSON', ->
+ Bucky.setOptions
+ json: true
+ Bucky.send 'data.1', 5, 'timer'
+ Bucky.send 'data.2', 3, 'timer'
+ Bucky.flush()
+
+ expect(server.requests.length).toBe(1)
+
+ expect(server.requests[0].requestBody).toBe('{"data.1":"5|ms","data.2":"3|ms"}')
diff --git a/spec/bucky.spec.js b/spec/bucky.spec.js
index 2d195ba..6ed9d8e 100644
--- a/spec/bucky.spec.js
+++ b/spec/bucky.spec.js
@@ -105,7 +105,10 @@
server = null;
beforeEach(function() {
server = sinon.fakeServer.create();
- return server.autoRespond = true;
+ server.autoRespond = true;
+ return Bucky.setOptions({
+ json: false
+ });
});
afterEach(function() {
return server.restore();
@@ -116,13 +119,32 @@
expect(server.requests.length).toBe(1);
return expect(server.requests[0].requestBody).toBe("data.point:4|g\n");
});
- return it('should send timers', function() {
+ it('should send timers', function() {
Bucky.send('data.1', 5, 'timer');
Bucky.send('data.2', 3, 'timer');
Bucky.flush();
expect(server.requests.length).toBe(1);
return expect(server.requests[0].requestBody).toBe("data.1:5|ms\ndata.2:3|ms\n");
});
+ it('should send a datapoint as JSON', function() {
+ Bucky.setOptions({
+ json: true
+ });
+ Bucky.send('data.point', 4);
+ Bucky.flush();
+ expect(server.requests.length).toBe(1);
+ return expect(server.requests[0].requestBody).toBe('{"data.point":"4|g"}');
+ });
+ return it('should send timers as JSON', function() {
+ Bucky.setOptions({
+ json: true
+ });
+ Bucky.send('data.1', 5, 'timer');
+ Bucky.send('data.2', 3, 'timer');
+ Bucky.flush();
+ expect(server.requests.length).toBe(1);
+ return expect(server.requests[0].requestBody).toBe('{"data.1":"5|ms","data.2":"3|ms"}');
+ });
});
}).call(this);