diff --git a/src/app/dashboards/default.json b/src/app/dashboards/default.json index 475f9e6dded..da7e0ccb230 100644 --- a/src/app/dashboards/default.json +++ b/src/app/dashboards/default.json @@ -81,7 +81,7 @@ ], "lines": true, "fill": 1, - "linewidth": 1, + "linewidth": 2, "points": false, "pointradius": 5, "bars": false, diff --git a/src/app/services/graphite/parser.js b/src/app/services/graphite/parser.js index bc72143c033..f263f01e2ac 100644 --- a/src/app/services/graphite/parser.js +++ b/src/app/services/graphite/parser.js @@ -29,12 +29,47 @@ define([ } }, - metricSegment: function() { - if (this.match('identifier')) { - this.index++; + curlyBraceSegment: function() { + if (this.match('identifier', '{') || this.match('{')) { + + var curlySegment = ""; + + while(!this.match('') && !this.match('}')) { + curlySegment += this.consumeToken().value; + } + + if (!this.match('}')) { + this.errorMark("Expected closing '}'"); + } + + curlySegment += this.consumeToken().value; + + // if curly segment is directly followed by identifier + // include it in the segment + if (this.match('identifier')) { + curlySegment += this.consumeToken().value; + } + return { type: 'segment', - value: this.tokens[this.index-1].value + value: curlySegment + }; + } + else { + return null; + } + }, + + metricSegment: function() { + var curly = this.curlyBraceSegment(); + if (curly) { + return curly; + } + + if (this.match('identifier')) { + return { + type: 'segment', + value: this.consumeToken().value }; } @@ -42,7 +77,7 @@ define([ this.errorMark('Expected metric identifier'); } - this.index++; + this.consumeToken(); if (!this.match('identifier')) { this.errorMark('Expected identifier after templateStart'); @@ -50,16 +85,14 @@ define([ var node = { type: 'template', - value: this.tokens[this.index].value + value: this.consumeToken().value }; - this.index++; - if (!this.match('templateEnd')) { this.errorMark('Expected templateEnd'); } - this.index++; + this.consumeToken(); return node; }, @@ -76,7 +109,7 @@ define([ node.segments.push(this.metricSegment()); while(this.match('.')) { - this.index++; + this.consumeToken(); var segment = this.metricSegment(); if (!segment) { @@ -96,10 +129,11 @@ define([ var node = { type: 'function', - name: this.tokens[this.index].value, + name: this.consumeToken().value, }; - this.index += 2; + // consume left paranthesis + this.consumeToken(); node.params = this.functionParameters(); @@ -107,7 +141,7 @@ define([ this.errorMark('Expected closing paranthesis'); } - this.index++; + this.consumeToken(); return node; }, @@ -127,7 +161,7 @@ define([ return [param]; } - this.index++; + this.consumeToken(); return [param].concat(this.functionParameters()); }, @@ -136,11 +170,9 @@ define([ return null; } - this.index++; - return { type: 'number', - value: parseInt(this.tokens[this.index-1].value, 10) + value: parseInt(this.consumeToken().value, 10) }; }, @@ -149,13 +181,11 @@ define([ return null; } - var token = this.tokens[this.index]; + var token = this.consumeToken(); if (token.isUnclosed) { throw { message: 'Unclosed string parameter', pos: token.pos }; } - this.index++; - return { type: 'string', value: token.value @@ -171,6 +201,12 @@ define([ }; }, + // returns token value and incre + consumeToken: function() { + this.index++; + return this.tokens[this.index-1]; + }, + matchToken: function(type, index) { var token = this.tokens[this.index + index]; return (token === undefined && type === '') || diff --git a/src/test/specs/gfunc-specs.js b/src/test/specs/gfunc-specs.js index 4ded6eb87ef..b90b9e64317 100644 --- a/src/test/specs/gfunc-specs.js +++ b/src/test/specs/gfunc-specs.js @@ -60,7 +60,7 @@ define([ it('should return function categories', function() { var catIndex = gfunc.getCategories(); - expect(catIndex.Special.length).to.equal(7); + expect(catIndex.Special.length).to.equal(8); }); }); diff --git a/src/test/specs/lexer-specs.js b/src/test/specs/lexer-specs.js index 7fc62438893..7dd680b7f64 100644 --- a/src/test/specs/lexer-specs.js +++ b/src/test/specs/lexer-specs.js @@ -21,6 +21,16 @@ define([ expect(tokens[4].value).to.be('se1-server-*'); }); + it('should tokenize metric expression with curly braces', function() { + var lexer = new Lexer('metric.se1-{first, second}.count'); + var tokens = lexer.tokenize(); + expect(tokens.length).to.be(10); + expect(tokens[3].type).to.be('{'); + expect(tokens[4].value).to.be('first'); + expect(tokens[5].value).to.be(','); + expect(tokens[6].value).to.be('second'); + }); + it('should tokenize functions and args', function() { var lexer = new Lexer("sum(metric.test, 12, 'test')"); diff --git a/src/test/specs/parser-specs.js b/src/test/specs/parser-specs.js index af3b5376130..79f5a9fae4b 100644 --- a/src/test/specs/parser-specs.js +++ b/src/test/specs/parser-specs.js @@ -13,6 +13,24 @@ define([ expect(rootNode.segments[0].value).to.be('metric'); }); + it('simple metric expression with curly braces', function() { + var parser = new Parser('metric.se1-{count, max}'); + var rootNode = parser.getAst(); + + expect(rootNode.type).to.be('metric'); + expect(rootNode.segments.length).to.be(2); + expect(rootNode.segments[1].value).to.be('se1-{count,max}'); + }); + + it('simple metric expression with curly braces at start of segment and with post chars', function() { + var parser = new Parser('metric.{count, max}-something.count'); + var rootNode = parser.getAst(); + + expect(rootNode.type).to.be('metric'); + expect(rootNode.segments.length).to.be(3); + expect(rootNode.segments[1].value).to.be('{count,max}-something'); + }); + it('simple function', function() { var parser = new Parser('sum(test)'); var rootNode = parser.getAst();