From 04b4af3159f1b3e3214bcb19828c5fd294e95076 Mon Sep 17 00:00:00 2001 From: Michael Cebrian Date: Wed, 23 Sep 2015 11:18:52 -0400 Subject: [PATCH 1/5] Update dependencies Add version to bower Add repository to package.json --- bower.json | 1 + package.json | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/bower.json b/bower.json index 93cca40..4e8caa6 100644 --- a/bower.json +++ b/bower.json @@ -1,5 +1,6 @@ { "name": "bucky", + "version": "0.2.9", "description": "Collect performance data from the client", "main": "bucky.js", "homepage": "http://github.hubspot.com/bucky", diff --git a/package.json b/package.json index 1c1e8a1..74c7194 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,24 @@ { "name": "bucky", - "version": "0.2.8", + "version": "0.2.9", "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 " } From 75c2475ebacdb93a4e00452cec7cbe61ca4a9158 Mon Sep 17 00:00:00 2001 From: Michael Cebrian Date: Wed, 23 Sep 2015 11:27:03 -0400 Subject: [PATCH 2/5] Add conditional to requests.monitor and sendPagePerformance key Set tags for InfluxDB 0.9 Line Protocol when passed key contains a comma --- bucky.coffee | 13 ++++++++----- bucky.js | 4 ++++ bucky.min.js | 4 ++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/bucky.coffee b/bucky.coffee index b5baa9a..6cfad4a 100644 --- a/bucky.coffee +++ b/bucky.coffee @@ -82,9 +82,9 @@ exportDef = -> 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,11 +171,11 @@ exportDef = -> sameOrigin = false else # Relative URL - + sameOrigin = true sendStart = now() - + body = '' for name, val of data body += "#{ name }:#{ val }\n" @@ -370,6 +370,8 @@ exportDef = -> if not path or path is true path = requests.urlToKey(document.location.toString()) + '.page' + else if path.indexOf(",") > -1 + path += "url=" + requests.urlToKey(document.location.toString()) + ',data=page' if document.readyState in ['uninitialized', 'loading'] # The data isn't fully ready until document load @@ -487,7 +489,8 @@ exportDef = -> monitor: (root) -> if not root or root is true root = requests.urlToKey(document.location.toString()) + '.requests' - + else if root.indexOf(",") > -1 + root += "url=" + requests.urlToKey(document.location.toString()) + ',data=requests' self = this done = ({type, url, event, request, readyStateTimes, startTime}) -> if startTime? diff --git a/bucky.js b/bucky.js index 360710a..610ccfd 100644 --- a/bucky.js +++ b/bucky.js @@ -375,6 +375,8 @@ } if (!path || path === true) { path = requests.urlToKey(document.location.toString()) + '.page'; + } else if (path.indexOf(",") > -1) { + path += "url=" + requests.urlToKey(document.location.toString()) + ',data=page'; } if ((_ref4 = document.readyState) === 'uninitialized' || _ref4 === 'loading') { if (typeof window.addEventListener === "function") { @@ -510,6 +512,8 @@ var done, self, _XMLHttpRequest; if (!root || root === true) { root = requests.urlToKey(document.location.toString()) + '.requests'; + } else if (root.indexOf(",") > -1) { + root += "url=" + requests.urlToKey(document.location.toString()) + ',data=requests'; } self = this; done = function(_arg) { diff --git a/bucky.min.js b/bucky.min.js index efd40ce..3309ebe 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.2.9 */ +(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()-1&&(a+="url="+l.urlToKey(document.location.toString())+",data=page"):a=l.urlToKey(document.location.toString())+".page","uninitialized"===(e=document.readyState)||"loading"===e)return"function"==typeof window.addEventListener&&window.addEventListener("load",function(){return setTimeout(function(){return n.call(g,a)},500)},!1),!1;r=!0,b=window.performance.timing.navigationStart,f=window.performance.timing;for(q in f)c=f[q],"number"==typeof c&&t.send(""+a+"."+q,c-b);return!0},l={transforms:{mapping:{guid:/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi,sha1:/\/[0-9a-f]{40}/gi,md5:/\/[0-9a-f]{32}/gi,id:/\/[0-9;_\-]+/g,email:/\/[^/]+@[^/]+/g,domain:[/\/[^/]+\.[a-z]{2,3}\//gi,"/"]},enabled:["guid","sha1","md5","id","email","domain"],enable:function(a,b,c){return null==c&&(c=""),null!=b&&(this.mapping[a]=[b,c]),this.enabled.splice(0,0,a)},disable:function(a){var b,c,d;d=this.enabled;for(b in d)if(c=d[b],c===a)return void this.enabled.splice(b,1)}},sendReadyStateTimes:function(a,b){var c,d,e,f,g,h,i,j;if(null!=b){d={1:"sending",2:"headers",3:"waiting",4:"receiving"},e={},f=null;for(c in b)h=b[c],null!=f&&null!=d[c]&&(e[d[c]]=h-f),f=h;j=[];for(g in e)i=e[g],j.push(t.send(""+a+"."+g,i));return j}},urlToKey:function(a,b,c){var d,e,g,h,i,j,k,m,n,o;for(a=a.replace(/https?:\/\//i,""),h=/([^/:]*)(?::\d+)?(\/[^\?#]*)?.*/i.exec(a),d=h[1],i=null!=(n=h[2])?n:"",o=l.transforms.enabled,k=0,m=o.length;m>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.indexOf(",")>-1&&(a+="url="+l.urlToKey(document.location.toString())+",data=requests"):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,j){var k;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(l){k=l,f.error("Bucky error monitoring XHR open call",k)}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 From 3f21304d0b0f9932ff8e27b01fff8238efa20aa7 Mon Sep 17 00:00:00 2001 From: Michael Cebrian Date: Mon, 28 Sep 2015 09:28:00 -0400 Subject: [PATCH 3/5] Add bucky options for influxLineProtocol --- README.md | 58 ++++++----- bower.json | 2 +- bucky.coffee | 223 ++++++++++++++++++++++++++++++----------- bucky.js | 221 ++++++++++++++++++++++++++++------------ bucky.min.js | 4 +- component.json | 2 +- package.json | 2 +- spec/bucky.spec.coffee | 24 ++++- spec/bucky.spec.js | 26 ++++- 9 files changed, 405 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index c0c05f6..06c4a11 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ It can automatically measure how long your pages take to load, how long AJAX req 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. @@ -103,23 +103,25 @@ Backbone.history.on 'route', (router, route) -> The data collected will look something like this: ```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" +{ + "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" +} ``` A description of what each datapoint represents is included in [the spec](http://www.w3.org/TR/navigation-timing-2/#sec-PerformanceNavigationTiming). @@ -139,13 +141,15 @@ The data collected will look something like this for a GET request to `api.hubapi.com/automation/v2/workflows`: ```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" +{ + "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" +} ``` #### Prefixing @@ -210,7 +214,7 @@ bucky.timer.timeSync 'my.awesome.function', -> 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 @@ -270,7 +274,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 4e8caa6..041e16f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "bucky", - "version": "0.2.9", + "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 6cfad4a..5a970ed 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]') if $tag tagOptions = { host: $tag.getAttribute('data-bucky-host') @@ -75,6 +108,10 @@ 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') } for key in ['pagePerformanceKey', 'requestsKey'] @@ -176,28 +213,39 @@ exportDef = -> 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 +317,7 @@ exportDef = -> time: (path, action, ctx, args...) -> timer.start path - done = => + done = -> timer.stop path args.splice(0, 0, done) @@ -369,17 +417,29 @@ exportDef = -> return false if sentPerformanceData if not path or path is true - path = requests.urlToKey(document.location.toString()) + '.page' - else if path.indexOf(",") > -1 - path += "url=" + requests.urlToKey(document.location.toString()) + ',data=page' + path = requests.urlToKey(document.location.toString()) + ".page" + if options.influxLineProtocol is true and not influxLineProtocolSet + path += ",url=" + (escapeTag document.location.toString()) + ",key=" + 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 @@ -387,7 +447,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 @@ -433,7 +496,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,56 +555,88 @@ exportDef = -> monitor: (root) -> if not root or root is true root = requests.urlToKey(document.location.toString()) + '.requests' - else if root.indexOf(",") > -1 - root += "url=" + requests.urlToKey(document.location.toString()) + ',data=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) },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.url = url + + 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 @@ -546,15 +644,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 610ccfd..2ec752b 100644 --- a/bucky.js +++ b/bucky.js @@ -58,16 +58,21 @@ 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]') : 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') }; _ref = ['pagePerformanceKey', 'requestsKey']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -144,7 +149,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 +167,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 +247,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 +274,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 +386,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,17 +395,31 @@ return false; } if (!path || path === true) { - path = requests.urlToKey(document.location.toString()) + '.page'; - } else if (path.indexOf(",") > -1) { - path += "url=" + requests.urlToKey(document.location.toString()) + ',data=page'; + path = requests.urlToKey(document.location.toString()) + ".page"; + } + if (options.influxLineProtocol === true && !influxLineProtocolSet) { + path += ",url=" + (escapeTag(document.location.toString())) + ",key="; + 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; } @@ -394,7 +429,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; @@ -454,7 +493,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; }, @@ -509,61 +552,97 @@ } }, monitor: function(root) { - var done, self, _XMLHttpRequest; + var done, self, xhr, _XMLHttpRequest; if (!root || root === true) { root = requests.urlToKey(document.location.toString()) + '.requests'; - } else if (root.indexOf(",") > -1) { - root += "url=" + requests.urlToKey(document.location.toString()) + ',data=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)) + ",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.url = url; + 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); @@ -572,7 +651,7 @@ }; _send = req.send; req.send = function() { - startTime = now(); + req._bucky.startTime = now(); return _send.apply(req, arguments); }; } catch (_error) { @@ -581,7 +660,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 3309ebe..b137460 100644 --- a/bucky.min.js +++ b/bucky.min.js @@ -1,2 +1,2 @@ -/*! bucky 0.2.9 */ -(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()-1&&(a+="url="+l.urlToKey(document.location.toString())+",data=page"):a=l.urlToKey(document.location.toString())+".page","uninitialized"===(e=document.readyState)||"loading"===e)return"function"==typeof window.addEventListener&&window.addEventListener("load",function(){return setTimeout(function(){return n.call(g,a)},500)},!1),!1;r=!0,b=window.performance.timing.navigationStart,f=window.performance.timing;for(q in f)c=f[q],"number"==typeof c&&t.send(""+a+"."+q,c-b);return!0},l={transforms:{mapping:{guid:/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi,sha1:/\/[0-9a-f]{40}/gi,md5:/\/[0-9a-f]{32}/gi,id:/\/[0-9;_\-]+/g,email:/\/[^/]+@[^/]+/g,domain:[/\/[^/]+\.[a-z]{2,3}\//gi,"/"]},enabled:["guid","sha1","md5","id","email","domain"],enable:function(a,b,c){return null==c&&(c=""),null!=b&&(this.mapping[a]=[b,c]),this.enabled.splice(0,0,a)},disable:function(a){var b,c,d;d=this.enabled;for(b in d)if(c=d[b],c===a)return void this.enabled.splice(b,1)}},sendReadyStateTimes:function(a,b){var c,d,e,f,g,h,i,j;if(null!=b){d={1:"sending",2:"headers",3:"waiting",4:"receiving"},e={},f=null;for(c in b)h=b[c],null!=f&&null!=d[c]&&(e[d[c]]=h-f),f=h;j=[];for(g in e)i=e[g],j.push(t.send(""+a+"."+g,i));return j}},urlToKey:function(a,b,c){var d,e,g,h,i,j,k,m,n,o;for(a=a.replace(/https?:\/\//i,""),h=/([^/:]*)(?::\d+)?(\/[^\?#]*)?.*/i.exec(a),d=h[1],i=null!=(n=h[2])?n:"",o=l.transforms.enabled,k=0,m=o.length;m>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.indexOf(",")>-1&&(a+="url="+l.urlToKey(document.location.toString())+",data=requests"):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,j){var k;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(l){k=l,f.error("Bucky error monitoring XHR open call",k)}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]"):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")},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=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)+",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.url=e,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 74c7194..d0a5aeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bucky", - "version": "0.2.9", + "version": "0.3.0", "description": "Collect performance data from the client and node", "license": "MIT", "repository": { 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); From 6a99d3b6c74fd3d226fd74321e5e7a6f1a60a9b8 Mon Sep 17 00:00:00 2001 From: Michael Cebrian Date: Tue, 29 Sep 2015 11:49:41 -0400 Subject: [PATCH 4/5] Update monitor for influxLineProtocol to send both endpoint and current url --- bucky.coffee | 5 +++-- bucky.js | 5 +++-- bucky.min.js | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bucky.coffee b/bucky.coffee index 5a970ed..1517ded 100644 --- a/bucky.coffee +++ b/bucky.coffee @@ -564,7 +564,7 @@ exportDef = -> return if options.influxLineProtocol is true - stat = "#{ root },url=#{ (escapeTag req._bucky.url) },method=#{ (escapeTag req._bucky.type) }" + 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 @@ -610,7 +610,8 @@ exportDef = -> try req._bucky.type = type req._bucky.readyStateTimes[0] = now() - req._bucky.url = url + req._bucky.endpoint = url + req._bucky.url = document.location.toString() if !!req.addEventListener req.addEventListener 'readystatechange', (evt) -> diff --git a/bucky.js b/bucky.js index 2ec752b..ce1020a 100644 --- a/bucky.js +++ b/bucky.js @@ -565,7 +565,7 @@ return; } if (options.influxLineProtocol === true) { - stat = "" + root + ",url=" + (escapeTag(req._bucky.url)) + ",method=" + (escapeTag(req._bucky.type)); + 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); @@ -608,7 +608,8 @@ try { req._bucky.type = type; req._bucky.readyStateTimes[0] = now(); - req._bucky.url = url; + req._bucky.endpoint = url; + req._bucky.url = document.location.toString(); if (!!req.addEventListener) { req.addEventListener('readystatechange', function(evt) { if (req._bucky.track === !true) { diff --git a/bucky.min.js b/bucky.min.js index b137460..5aa1528 100644 --- a/bucky.min.js +++ b/bucky.min.js @@ -1,2 +1,2 @@ /*! 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]"):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")},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=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)+",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.url=e,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 +(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]"):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")},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=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 From 6cd6648b57cb0ddc37ed882644355aad40949521 Mon Sep 17 00:00:00 2001 From: Michael Cebrian Date: Tue, 6 Oct 2015 15:07:36 -0400 Subject: [PATCH 5/5] Update README.md Add logic for new tag options Update pagePerformance influxLineProtocol tag from 'key' to 'timing' --- README.md | 151 +++++++++++++++++++++++++++++++++------------------ bucky.coffee | 7 ++- bucky.js | 9 +-- bucky.min.js | 2 +- 4 files changed, 109 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 06c4a11..9cb7516 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ 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. @@ -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,30 +116,44 @@ 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" } ``` @@ -132,23 +165,37 @@ 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" } ``` @@ -157,17 +204,17 @@ The data collected will look something like this for a GET request to 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 ``` @@ -178,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 @@ -187,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: @@ -211,7 +258,7 @@ 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 @@ -224,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: @@ -240,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. @@ -248,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 diff --git a/bucky.coffee b/bucky.coffee index 1517ded..8e5cca8 100644 --- a/bucky.coffee +++ b/bucky.coffee @@ -99,7 +99,7 @@ exportDef = -> tagOptions = {} if not isServer - $tag = document.querySelector?('[data-bucky-host],[data-bucky-page],[data-bucky-requests],[data-bucky-json],[data-bucky-influx-line-protocol]') + $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') @@ -112,9 +112,10 @@ exportDef = -> # 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' @@ -419,7 +420,7 @@ exportDef = -> if not path or path is true path = requests.urlToKey(document.location.toString()) + ".page" if options.influxLineProtocol is true and not influxLineProtocolSet - path += ",url=" + (escapeTag document.location.toString()) + ",key=" + path += ",url=" + (escapeTag document.location.toString()) + ",timing=" influxLineProtocolSet = true if document.readyState in ['uninitialized', 'loading'] diff --git a/bucky.js b/bucky.js index ce1020a..dc0aca6 100644 --- a/bucky.js +++ b/bucky.js @@ -65,16 +65,17 @@ }; tagOptions = {}; if (!isServer) { - $tag = typeof document.querySelector === "function" ? document.querySelector('[data-bucky-host],[data-bucky-page],[data-bucky-requests],[data-bucky-json],[data-bucky-influx-line-protocol]') : 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'), json: $tag.getAttribute('data-bucky-json'), - influxLineProtocol: $tag.getAttribute('data-bucky-influx-line-protocol') + 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] === '') { @@ -398,7 +399,7 @@ path = requests.urlToKey(document.location.toString()) + ".page"; } if (options.influxLineProtocol === true && !influxLineProtocolSet) { - path += ",url=" + (escapeTag(document.location.toString())) + ",key="; + path += ",url=" + (escapeTag(document.location.toString())) + ",timing="; influxLineProtocolSet = true; } if ((_ref4 = document.readyState) === 'uninitialized' || _ref4 === 'loading') { diff --git a/bucky.min.js b/bucky.min.js index 5aa1528..8f5233f 100644 --- a/bucky.min.js +++ b/bucky.min.js @@ -1,2 +1,2 @@ /*! 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]"):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")},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=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 +(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