From 2d66f66f6853f26932da9dec349c0485252cfe26 Mon Sep 17 00:00:00 2001 From: Calle Pettersson Date: Tue, 15 May 2018 09:13:19 +0200 Subject: [PATCH] Support carriage-returns in expfmt --- expfmt/text_parse.go | 25 ++++++++++++++++------ expfmt/text_parse_test.go | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index b86290afa..5cbd97843 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -152,6 +152,8 @@ func (p *TextParser) startOfLine() stateFn { return p.startComment case '\n': return p.startOfLine // Empty line, start the next one. + case '\r': + return p.startOfLine // Empty line, start the next one. } return p.readingMetricName } @@ -162,7 +164,7 @@ func (p *TextParser) startComment() stateFn { if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } - if p.currentByte == '\n' { + if p.currentByte == '\n' || p.currentByte == '\r' { return p.startOfLine } if p.readTokenUntilWhitespace(); p.err != nil { @@ -170,13 +172,13 @@ func (p *TextParser) startComment() stateFn { } // If we have hit the end of line already, there is nothing left // to do. This is not considered a syntax error. - if p.currentByte == '\n' { + if p.currentByte == '\n' || p.currentByte == '\r' { return p.startOfLine } keyword := p.currentToken.String() if keyword != "HELP" && keyword != "TYPE" { // Generic comment, ignore by fast forwarding to end of line. - for p.currentByte != '\n' { + for p.currentByte != '\n' || p.currentByte == '\r' { if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil { return nil // Unexpected end of input. } @@ -190,7 +192,7 @@ func (p *TextParser) startComment() stateFn { if p.readTokenAsMetricName(); p.err != nil { return nil // Unexpected end of input. } - if p.currentByte == '\n' { + if p.currentByte == '\n' || p.currentByte == '\r' { // At the end of the line already. // Again, this is not considered a syntax error. return p.startOfLine @@ -203,7 +205,7 @@ func (p *TextParser) startComment() stateFn { if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } - if p.currentByte == '\n' { + if p.currentByte == '\n' || p.currentByte == '\r' { // At the end of the line already. // Again, this is not considered a syntax error. return p.startOfLine @@ -446,7 +448,7 @@ func (p *TextParser) readingValue() stateFn { default: p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName()) } - if p.currentByte == '\n' { + if p.currentByte == '\n' || p.currentByte == '\r' { return p.startOfLine } return p.startTimestamp @@ -546,7 +548,7 @@ func (p *TextParser) skipBlankTabIfCurrentBlankTab() { // into p.currentToken. func (p *TextParser) readTokenUntilWhitespace() { p.currentToken.Reset() - for p.err == nil && !isBlankOrTab(p.currentByte) && p.currentByte != '\n' { + for p.err == nil && !isBlankOrTab(p.currentByte) && p.currentByte != '\n' && p.currentByte != '\r' { p.currentToken.WriteByte(p.currentByte) p.currentByte, p.err = p.buf.ReadByte() } @@ -568,6 +570,8 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) { p.currentToken.WriteByte(p.currentByte) case 'n': p.currentToken.WriteByte('\n') + case 'r': + p.currentToken.WriteByte('\r') default: p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) return @@ -577,6 +581,8 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) { switch p.currentByte { case '\n': return + case '\r': + return case '\\': escaped = true default: @@ -641,6 +647,8 @@ func (p *TextParser) readTokenAsLabelValue() { p.currentToken.WriteByte(p.currentByte) case 'n': p.currentToken.WriteByte('\n') + case 'r': + p.currentToken.WriteByte('\r') default: p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) return @@ -654,6 +662,9 @@ func (p *TextParser) readTokenAsLabelValue() { case '\n': p.parseError(fmt.Sprintf("label value %q contains unescaped new-line", p.currentToken.String())) return + case '\r': + p.parseError(fmt.Sprintf("label value %q contains unescaped carriage-return", p.currentToken.String())) + return case '\\': escaped = true default: diff --git a/expfmt/text_parse_test.go b/expfmt/text_parse_test.go index 76c951185..ab4c0f306 100644 --- a/expfmt/text_parse_test.go +++ b/expfmt/text_parse_test.go @@ -384,6 +384,51 @@ request_duration_microseconds_count 2693 }, }, }, + // 5: Minimal, with carriage returns. + { + in: "\r\n" + + "minimal_metric 1.234\r\n" + + "another_metric -3e3 103948\r\n" + + "# Even that:\r\n" + + "no_labels{} 3\r\n" + + "# HELP line for non-existing metric will be ignored.\r\n", + out: []*dto.MetricFamily{ + &dto.MetricFamily{ + Name: proto.String("minimal_metric"), + Type: dto.MetricType_UNTYPED.Enum(), + Metric: []*dto.Metric{ + &dto.Metric{ + Untyped: &dto.Untyped{ + Value: proto.Float64(1.234), + }, + }, + }, + }, + &dto.MetricFamily{ + Name: proto.String("another_metric"), + Type: dto.MetricType_UNTYPED.Enum(), + Metric: []*dto.Metric{ + &dto.Metric{ + Untyped: &dto.Untyped{ + Value: proto.Float64(-3e3), + }, + TimestampMs: proto.Int64(103948), + }, + }, + }, + &dto.MetricFamily{ + Name: proto.String("no_labels"), + Type: dto.MetricType_UNTYPED.Enum(), + Metric: []*dto.Metric{ + &dto.Metric{ + Untyped: &dto.Untyped{ + Value: proto.Float64(3), + }, + }, + }, + }, + }, + }, } for i, scenario := range scenarios {