From 1cb4f3459e05e753f751733a4ca580ed6519b502 Mon Sep 17 00:00:00 2001 From: Michael Isgro Date: Thu, 22 Oct 2015 15:50:28 +1100 Subject: [PATCH 1/3] fixed is server check to work with commonjs modules --- bucky.coffee | 10 +++++----- bucky.js | 2 +- bucky.min.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bucky.coffee b/bucky.coffee index b5baa9a..be52e45 100644 --- a/bucky.coffee +++ b/bucky.coffee @@ -1,4 +1,4 @@ -isServer = module? and not window?.module +isServer = module? and not window? if isServer {XMLHttpRequest} = require('xmlhttprequest') @@ -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" diff --git a/bucky.js b/bucky.js index 360710a..178cd6c 100644 --- a/bucky.js +++ b/bucky.js @@ -2,7 +2,7 @@ var XMLHttpRequest, exportDef, extend, initTime, isServer, log, now, __slice = [].slice; - isServer = (typeof module !== "undefined" && module !== null) && !(typeof window !== "undefined" && window !== null ? window.module : void 0); + isServer = (typeof module !== "undefined" && module !== null) && (typeof window === "undefined" || window === null); if (isServer) { XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; diff --git a/bucky.min.js b/bucky.min.js index efd40ce..3ffba69 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 +(function(){var a,b,c,d,e,f,g,h=[].slice;e="undefined"!=typeof module&&null!==module&&("undefined"==typeof window||null===window),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,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 84e2bdd172151784450813db8a0fbabf3192131c Mon Sep 17 00:00:00 2001 From: Andrew Browne Date: Thu, 21 Jan 2016 17:17:34 +1100 Subject: [PATCH 2/3] Fix formatting of statsd sample rate --- bucky.coffee | 2 +- bucky.js | 2 +- spec/bucky.spec.coffee | 9 +++++++++ spec/bucky.spec.js | 9 ++++++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/bucky.coffee b/bucky.coffee index f6b1142..2e3f9fa 100644 --- a/bucky.coffee +++ b/bucky.coffee @@ -227,7 +227,7 @@ exportDef = -> out[key] = "#{ value }|#{ TYPE_MAP[point.type] }" if point.count isnt 1 - out[key] += "@#{ round(1 / point.count, 5) }" + out[key] += "|@#{ round(1 / point.count, 5) }" makeRequest out diff --git a/bucky.js b/bucky.js index beae338..75bfe93 100644 --- a/bucky.js +++ b/bucky.js @@ -208,7 +208,7 @@ } out[key] = "" + value + "|" + TYPE_MAP[point.type]; if (point.count !== 1) { - out[key] += "@" + (round(1 / point.count, 5)); + out[key] += "|@" + (round(1 / point.count, 5)); } } makeRequest(out); diff --git a/spec/bucky.spec.coffee b/spec/bucky.spec.coffee index ba73dbe..b0a3248 100644 --- a/spec/bucky.spec.coffee +++ b/spec/bucky.spec.coffee @@ -112,3 +112,12 @@ 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 aggregate timers', -> + Bucky.send 'data.1', 5, 'timer' + Bucky.send 'data.1', 10, 'timer' + Bucky.flush() + + expect(server.requests.length).toBe(1) + + expect(server.requests[0].requestBody).toBe("data.1:7.5|ms|@0.5\n") diff --git a/spec/bucky.spec.js b/spec/bucky.spec.js index 2d195ba..df2ceab 100644 --- a/spec/bucky.spec.js +++ b/spec/bucky.spec.js @@ -116,13 +116,20 @@ 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"); }); + return it('should aggregate timers', function() { + Bucky.send('data.1', 5, 'timer'); + Bucky.send('data.1', 10, 'timer'); + Bucky.flush(); + expect(server.requests.length).toBe(1); + return expect(server.requests[0].requestBody).toBe("data.1:7.5|ms|@0.5\n"); + }); }); }).call(this); From 3c5fa4ec29fa39b667e637b0df07d1a7a06927c1 Mon Sep 17 00:00:00 2001 From: Ray Hua Date: Thu, 7 Apr 2016 16:57:48 +1000 Subject: [PATCH 3/3] 1. add dogstatsd tagging support 2. use path+type+tag as the key when enqueue metrics 3. send performance properties as tags --- bucky.coffee | 93 ++++++++++++++----------- bucky.js | 151 ++++++++++++++++++++++++++++------------- bucky.min.js | 2 +- spec/bucky.spec.coffee | 21 +++++- spec/bucky.spec.js | 17 ++++- 5 files changed, 195 insertions(+), 89 deletions(-) diff --git a/bucky.coffee b/bucky.coffee index 441897f..89a0c27 100644 --- a/bucky.coffee +++ b/bucky.coffee @@ -108,24 +108,31 @@ exportDef = -> Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision) queue = {} - enqueue = (path, value, type) -> + enqueue = (path, value, type, tags=[]) -> return unless ACTIVE count = 1 + key = '' - if path of queue + if tags and tags instanceof Array and tags.length + tagStr = tags.join(',') + key = "#{ path }+#{ type }+#{ tagStr }" + else + key = "#{ path }+#{ type }" + + if key of queue # We have multiple of the same datapoint in this queue if type is 'counter' # Sum the queue - value += queue[path].value + value += queue[key].value else # If it's a timer or a gauge, calculate a running average - count = queue[path].count ? count + count = queue[key].count ? count count++ - value = queue[path].value + (value - queue[path].value) / count + value = queue[key].value + (value - queue[key].value) / count - queue[path] = {value, type, count} + queue[key] = {path, value, type, count, tags} do considerSending @@ -178,7 +185,7 @@ exportDef = -> body = '' for name, val of data - body += "#{ name }:#{ val }\n" + body += "#{ val.path }:#{ val.value }\n" if not sameOrigin and not corsSupport and window?.XDomainRequest? # CORS support for IE9 @@ -211,10 +218,11 @@ exportDef = -> out = {} for key, point of queue HISTORY.push - path: key + path: point.path count: point.count type: point.type value: point.value + tags: point.tags unless TYPE_MAP[point.type]? log.error "Type #{ point.type } not understood by Bucky" @@ -224,10 +232,14 @@ exportDef = -> if point.type in ['gauge', 'timer'] value = round(value) - out[key] = "#{ value }|#{ TYPE_MAP[point.type] }" + out[key] = { path: point.path, value: "#{ value }|#{ TYPE_MAP[point.type] }"} if point.count isnt 1 - out[key] += "|@#{ round(1 / point.count, 5) }" + out[key].value += "|@#{ round(1 / point.count, 5) }" + + if point.tags and point.tags instanceof Array and point.tags.length + tagStr = point.tags.join(',') + out[key].value += "|##{ tagStr }" makeRequest out @@ -253,50 +265,50 @@ exportDef = -> else path - send = (path, value, type='gauge') -> + send = (path, value, type='gauge', tags=[]) -> if not value? or not path? log.error "Can't log #{ path }:#{ value }" return - enqueue buildPath(path), value, type + enqueue buildPath(path), value, type, tags timer = { TIMES: {} - send: (path, duration) -> - send path, duration, 'timer' + send: (path, duration, tags=[]) -> + send path, duration, 'timer', tags - time: (path, action, ctx, args...) -> + time: (path, tags=[], action, ctx, args...) -> timer.start path done = => - timer.stop path + timer.stop path, tags args.splice(0, 0, done) action.apply(ctx, args) - timeSync: (path, action, ctx, args...) -> + timeSync: (path, tags=[], action, ctx, args...) -> timer.start path ret = action.apply(ctx, args) - timer.stop path + timer.stop path, tags ret - wrap: (path, action) -> + wrap: (path, tags=[], action) -> if action? return (args...) -> - timer.timeSync path, action, @, args... + timer.timeSync path, tags, action, @, args... else return (action) -> return (args...) -> - timer.timeSync path, action, @, args... + timer.timeSync path, tags, action, @, args... start: (path) -> timer.TIMES[path] = now() - stop: (path) -> + stop: (path, tags) -> if not timer.TIMES[path]? log.error "Timer #{ path } ended without having been started" return @@ -305,9 +317,9 @@ exportDef = -> timer.TIMES[path] = undefined - timer.send path, duration + timer.send path, duration, tags - stopwatch: (prefix, start) -> + stopwatch: (prefix, start, tags=[]) -> # A timer that can be stopped multiple times # If a start time is passed in, it's assumed @@ -322,13 +334,13 @@ exportDef = -> last = start { - mark: (path, offset=0) -> + mark: (path, offset=0, tags) -> end = _now() if prefix path = prefix + '.' + path - timer.send path, (end - start + offset) + timer.send path, (end - start + offset), tags split: (path, offset=0) -> end = _now() @@ -336,19 +348,19 @@ exportDef = -> if prefix path = prefix + '.' + path - timer.send path, (end - last + offset) + timer.send path, (end - last + offset), tags last = end } - mark: (path, time) -> + mark: (path, time, tags=[]) -> # A timer which always begins at page load time ?= +new Date start = timer.navigationStart() - timer.send path, (time - start) + timer.send path, (time - start), tags navigationStart: -> window?.performance?.timing?.navigationStart ? initTime @@ -360,11 +372,11 @@ exportDef = -> now() } - count = (path, count=1) -> - send(path, count, 'counter') + count = (path, tags=[], count=1) -> + send(path, count, 'counter', tags) sentPerformanceData = false - sendPagePerformance = (path) -> + sendPagePerformance = (path, tags=[]) -> return false unless window?.performance?.timing? return false if sentPerformanceData @@ -375,7 +387,7 @@ exportDef = -> # The data isn't fully ready until document load window.addEventListener? 'load', => setTimeout => - sendPagePerformance.call(@, path) + sendPagePerformance.call(@, path, tags) , 500 , false @@ -385,7 +397,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) + tagWithPerfProps = tags.slice(0) + tagWithPerfProps.push "perf-prop:#{ key }" + timer.send path, (time - start), tagWithPerfProps + tagWithPerfProps = [] return true @@ -413,7 +428,7 @@ exportDef = -> @enabled.splice i, 1 return - sendReadyStateTimes: (path, times) -> + sendReadyStateTimes: (path, times, tags=[]) -> return unless times? codeMapping = @@ -431,7 +446,7 @@ exportDef = -> last = time for status, val of diffs - timer.send "#{ path }.#{ status }", val + timer.send "#{ path }.#{ status }", val, tags urlToKey: (url, type, root) -> url = url.replace /https?:\/\//i, '' @@ -484,7 +499,7 @@ exportDef = -> else url - monitor: (root) -> + monitor: (root, tags=[]) -> if not root or root is true root = requests.urlToKey(document.location.toString()) + '.requests' @@ -498,9 +513,9 @@ exportDef = -> url = self.getFullUrl url stat = self.urlToKey url, type, root - send(stat, dur, 'timer') + send(stat, dur, 'timer', tags) - self.sendReadyStateTimes stat, readyStateTimes + self.sendReadyStateTimes stat, readyStateTimes, tags if request?.status? if request.status > 12000 diff --git a/bucky.js b/bucky.js index 19fa7b4..b49a16e 100644 --- a/bucky.js +++ b/bucky.js @@ -105,25 +105,37 @@ return Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision); }; queue = {}; - enqueue = function(path, value, type) { - var count, _ref3; + enqueue = function(path, value, type, tags) { + var count, tagStr, _ref3; + if (tags == null) { + tags = []; + } if (!ACTIVE) { return; } count = 1; - if (path in queue) { + key = ''; + if (tags && tags instanceof Array && tags.length) { + tagStr = tags.join(','); + key = "" + path + "+" + type + "+" + tagStr; + } else { + key = "" + path + "+" + type; + } + if (key in queue) { if (type === 'counter') { - value += queue[path].value; + value += queue[key].value; } else { - count = (_ref3 = queue[path].count) != null ? _ref3 : count; + count = (_ref3 = queue[key].count) != null ? _ref3 : count; count++; - value = queue[path].value + (value - queue[path].value) / count; + value = queue[key].value + (value - queue[key].value) / count; } } - queue[path] = { + queue[key] = { + path: path, value: value, type: type, - count: count + count: count, + tags: tags }; return considerSending(); }; @@ -165,7 +177,7 @@ body = ''; for (name in data) { val = data[name]; - body += "" + name + ":" + val + "\n"; + body += "" + val.path + ":" + val.value + "\n"; } if (!sameOrigin && !corsSupport && ((typeof window !== "undefined" && window !== null ? window.XDomainRequest : void 0) != null)) { req = new window.XDomainRequest; @@ -184,7 +196,7 @@ return req; }; sendQueue = function() { - var out, point, value, _ref3; + var out, point, tagStr, value, _ref3; if (!ACTIVE) { log("Would send bucky queue"); return; @@ -193,10 +205,11 @@ for (key in queue) { point = queue[key]; HISTORY.push({ - path: key, + path: point.path, count: point.count, type: point.type, - value: point.value + value: point.value, + tags: point.tags }); if (TYPE_MAP[point.type] == null) { log.error("Type " + point.type + " not understood by Bucky"); @@ -206,9 +219,16 @@ if ((_ref3 = point.type) === 'gauge' || _ref3 === 'timer') { value = round(value); } - out[key] = "" + value + "|" + TYPE_MAP[point.type]; + out[key] = { + path: point.path, + value: "" + value + "|" + TYPE_MAP[point.type] + }; if (point.count !== 1) { - out[key] += "|@" + (round(1 / point.count, 5)); + out[key].value += "|@" + (round(1 / point.count, 5)); + } + if (point.tags && point.tags instanceof Array && point.tags.length) { + tagStr = point.tags.join(','); + out[key].value += "|#" + tagStr; } } makeRequest(out); @@ -236,53 +256,68 @@ return path; } }; - send = function(path, value, type) { + send = function(path, value, type, tags) { if (type == null) { type = 'gauge'; } + if (tags == null) { + tags = []; + } if ((value == null) || (path == null)) { log.error("Can't log " + path + ":" + value); return; } - return enqueue(buildPath(path), value, type); + return enqueue(buildPath(path), value, type, tags); }; timer = { TIMES: {}, - send: function(path, duration) { - return send(path, duration, 'timer'); + send: function(path, duration, tags) { + if (tags == null) { + tags = []; + } + return send(path, duration, 'timer', tags); }, time: function() { - var action, args, ctx, done, path, + var action, args, ctx, done, path, tags, _this = this; - path = arguments[0], action = arguments[1], ctx = arguments[2], args = 4 <= arguments.length ? __slice.call(arguments, 3) : []; + path = arguments[0], tags = arguments[1], action = arguments[2], ctx = arguments[3], args = 5 <= arguments.length ? __slice.call(arguments, 4) : []; + if (tags == null) { + tags = []; + } timer.start(path); done = function() { - return timer.stop(path); + return timer.stop(path, tags); }; args.splice(0, 0, done); return action.apply(ctx, args); }, timeSync: function() { - var action, args, ctx, path, ret; - path = arguments[0], action = arguments[1], ctx = arguments[2], args = 4 <= arguments.length ? __slice.call(arguments, 3) : []; + var action, args, ctx, path, ret, tags; + path = arguments[0], tags = arguments[1], action = arguments[2], ctx = arguments[3], args = 5 <= arguments.length ? __slice.call(arguments, 4) : []; + if (tags == null) { + tags = []; + } timer.start(path); ret = action.apply(ctx, args); - timer.stop(path); + timer.stop(path, tags); return ret; }, - wrap: function(path, action) { + wrap: function(path, tags, action) { + if (tags == null) { + tags = []; + } if (action != null) { return function() { var args; args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - return timer.timeSync.apply(timer, [path, action, this].concat(__slice.call(args))); + return timer.timeSync.apply(timer, [path, tags, action, this].concat(__slice.call(args))); }; } else { return function(action) { return function() { var args; args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - return timer.timeSync.apply(timer, [path, action, this].concat(__slice.call(args))); + return timer.timeSync.apply(timer, [path, tags, action, this].concat(__slice.call(args))); }; }; } @@ -290,7 +325,7 @@ start: function(path) { return timer.TIMES[path] = now(); }, - stop: function(path) { + stop: function(path, tags) { var duration; if (timer.TIMES[path] == null) { log.error("Timer " + path + " ended without having been started"); @@ -298,10 +333,13 @@ } duration = now() - timer.TIMES[path]; timer.TIMES[path] = void 0; - return timer.send(path, duration); + return timer.send(path, duration, tags); }, - stopwatch: function(prefix, start) { + stopwatch: function(prefix, start, tags) { var last, _now; + if (tags == null) { + tags = []; + } if (start != null) { _now = function() { return +(new Date); @@ -312,7 +350,7 @@ } last = start; return { - mark: function(path, offset) { + mark: function(path, offset, tags) { var end; if (offset == null) { offset = 0; @@ -321,7 +359,7 @@ if (prefix) { path = prefix + '.' + path; } - return timer.send(path, end - start + offset); + return timer.send(path, end - start + offset, tags); }, split: function(path, offset) { var end; @@ -332,18 +370,21 @@ if (prefix) { path = prefix + '.' + path; } - timer.send(path, end - last + offset); + timer.send(path, end - last + offset, tags); return last = end; } }; }, - mark: function(path, time) { + mark: function(path, time, tags) { var start; + if (tags == null) { + tags = []; + } if (time == null) { time = +(new Date); } start = timer.navigationStart(); - return timer.send(path, time - start); + return timer.send(path, time - start, tags); }, navigationStart: function() { var _ref3, _ref4, _ref5; @@ -357,16 +398,22 @@ return now(); } }; - count = function(path, count) { + count = function(path, tags, count) { + if (tags == null) { + tags = []; + } if (count == null) { count = 1; } - return send(path, count, 'counter'); + return send(path, count, 'counter', tags); }; sentPerformanceData = false; - sendPagePerformance = function(path) { - var start, time, _ref3, _ref4, _ref5, + sendPagePerformance = function(path, tags) { + var start, tagWithPerfProps, time, _ref3, _ref4, _ref5, _this = this; + if (tags == null) { + tags = []; + } if ((typeof window !== "undefined" && window !== null ? (_ref3 = window.performance) != null ? _ref3.timing : void 0 : void 0) == null) { return false; } @@ -380,7 +427,7 @@ if (typeof window.addEventListener === "function") { window.addEventListener('load', function() { return setTimeout(function() { - return sendPagePerformance.call(_this, path); + return sendPagePerformance.call(_this, path, tags); }, 500); }, false); } @@ -391,9 +438,13 @@ _ref5 = window.performance.timing; for (key in _ref5) { time = _ref5[key]; - if (typeof time === 'number') { - timer.send("" + path + "." + key, time - start); + if (!(typeof time === 'number')) { + continue; } + tagWithPerfProps = tags.slice(0); + tagWithPerfProps.push("perf-prop:" + key); + timer.send(path, time - start, tagWithPerfProps); + tagWithPerfProps = []; } return true; }; @@ -429,8 +480,11 @@ } } }, - sendReadyStateTimes: function(path, times) { + sendReadyStateTimes: function(path, times, tags) { var code, codeMapping, diffs, last, status, time, val, _results; + if (tags == null) { + tags = []; + } if (times == null) { return; } @@ -452,7 +506,7 @@ _results = []; for (status in diffs) { val = diffs[status]; - _results.push(timer.send("" + path + "." + status, val)); + _results.push(timer.send("" + path + "." + status, val, tags)); } return _results; }, @@ -506,8 +560,11 @@ return url; } }, - monitor: function(root) { + monitor: function(root, tags) { var done, self, _XMLHttpRequest; + if (tags == null) { + tags = []; + } if (!root || root === true) { root = requests.urlToKey(document.location.toString()) + '.requests'; } @@ -522,8 +579,8 @@ } url = self.getFullUrl(url); stat = self.urlToKey(url, type, root); - send(stat, dur, 'timer'); - self.sendReadyStateTimes(stat, readyStateTimes); + send(stat, dur, 'timer', tags); + self.sendReadyStateTimes(stat, readyStateTimes, tags); if ((request != null ? request.status : void 0) != null) { if (request.status > 12000) { count("" + stat + ".0"); diff --git a/bucky.min.js b/bucky.min.js index 289beda..53993d9 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),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,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 +(function(){var a,b,c,d,e,f,g,h=[].slice;e="undefined"!=typeof module&&null!==module&&("undefined"==typeof window||null===window),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,b){var d,e,h;return null==b&&(b=[]),a&&a!==!0||(a=l.urlToKey(document.location.toString())+".requests"),e=this,d=function(d){var f,h,i,j,k,l,n,o;return n=d.type,o=d.url,h=d.event,j=d.request,i=d.readyStateTimes,k=d.startTime,null!=k?(f=g()-k,o=e.getFullUrl(o),l=e.urlToKey(o,n,a),m(l,f,"timer",b),e.sendReadyStateTimes(l,i,b),null!=(null!=j?j.status:void 0)?(j.status>12e3?c(""+l+".0"):0!==j.status&&c(""+l+"."+j.status.toString().charAt(0)+"xx"),c(""+l+"."+j.status)):void 0):void 0},h=window.XMLHttpRequest,window.XMLHttpRequest=function(){var a,b,c,e,i,j;c=new h;try{e=null,b={},i=c.open,c.open=function(a,h,j){var k;try{b[0]=g(),c.addEventListener("readystatechange",function(){return b[c.readyState]=g()},!1),c.addEventListener("loadend",function(f){return null==c.bucky||c.bucky.track!==!1?d({type:a,url:h,event:f,startTime:e,readyStateTimes:b,request:c}):void 0},!1)}catch(l){k=l,f.error("Bucky error monitoring XHR open call",k)}return i.apply(c,arguments)},j=c.send,c.send=function(){return e=g(),j.apply(c,arguments)}}catch(k){a=k,f.error("Bucky error monitoring XHR",a)}return c}}},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 diff --git a/spec/bucky.spec.coffee b/spec/bucky.spec.coffee index b0a3248..0016d1b 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') @@ -121,3 +121,22 @@ describe 'send', -> expect(server.requests.length).toBe(1) expect(server.requests[0].requestBody).toBe("data.1:7.5|ms|@0.5\n") + + it 'should send timers with tags', -> + Bucky.send 'data.1', 5, 'timer', ['tag:1', 'tag:2'] + Bucky.timer.send 'data.2', 3, ['tag:3'] + Bucky.send 'data.3', 9, 'timer', ['tag:4'] + Bucky.flush() + + expect(server.requests.length).toBe(1) + + expect(server.requests[0].requestBody).toBe("data.1:5|ms|#tag:1,tag:2\ndata.2:3|ms|#tag:3\ndata.3:9|ms|#tag:4\n") + + it 'should send counts with tags', -> + Bucky.count 'ray', ['tag:3'] + Bucky.count 'ray', ['tag:3'] + Bucky.flush() + + expect(server.requests.length).toBe(1) + + expect(server.requests[0].requestBody).toBe("ray:2|c|#tag:3\n") diff --git a/spec/bucky.spec.js b/spec/bucky.spec.js index df2ceab..d80e89a 100644 --- a/spec/bucky.spec.js +++ b/spec/bucky.spec.js @@ -123,13 +123,28 @@ expect(server.requests.length).toBe(1); return expect(server.requests[0].requestBody).toBe("data.1:5|ms\ndata.2:3|ms\n"); }); - return it('should aggregate timers', function() { + it('should aggregate timers', function() { Bucky.send('data.1', 5, 'timer'); Bucky.send('data.1', 10, 'timer'); Bucky.flush(); expect(server.requests.length).toBe(1); return expect(server.requests[0].requestBody).toBe("data.1:7.5|ms|@0.5\n"); }); + it('should send timers with tags', function() { + Bucky.send('data.1', 5, 'timer', ['tag:1', 'tag:2']); + Bucky.timer.send('data.2', 3, ['tag:3']); + Bucky.send('data.3', 9, 'timer', ['tag:4']); + Bucky.flush(); + expect(server.requests.length).toBe(1); + return expect(server.requests[0].requestBody).toBe("data.1:5|ms|#tag:1,tag:2\ndata.2:3|ms|#tag:3\ndata.3:9|ms|#tag:4\n"); + }); + return it('should send counts with tags', function() { + Bucky.count('ray', ['tag:3']); + Bucky.count('ray', ['tag:3']); + Bucky.flush(); + expect(server.requests.length).toBe(1); + return expect(server.requests[0].requestBody).toBe("ray:2|c|#tag:3\n"); + }); }); }).call(this);