diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index 00c8841a..0cd06fc4 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -602,15 +602,21 @@ func (p *TextParser) readingValue() stateFn { if p.currentByte == '\n' { return p.startOfLine } - return p.startTimestamp + if isBlankOrTab(p.currentByte) { + p.skipBlankTabIfCurrentBlankTab() + if p.err != nil { + return nil // Unexpected end of input. + } + if p.currentByte == '\n' { + return p.startOfLine + } + } + return p.readingTimestamp } -// startTimestamp represents the state where the next byte read from p.buf is -// the start of the timestamp (or whitespace leading up to it). -func (p *TextParser) startTimestamp() stateFn { - if p.skipBlankTab(); p.err != nil { - return nil // Unexpected end of input. - } +// readingTimestamp represents the state where the last byte read (now in +// p.currentByte) is the first byte of the timestamp. +func (p *TextParser) readingTimestamp() stateFn { if p.readTokenUntilWhitespace(); p.err != nil { return nil // Unexpected end of input. } @@ -621,6 +627,18 @@ func (p *TextParser) startTimestamp() stateFn { return nil } p.currentMetric.TimestampMs = proto.Int64(timestamp) + if p.currentByte == '\n' { + return p.startOfLine + } + if isBlankOrTab(p.currentByte) { + p.skipBlankTabIfCurrentBlankTab() + if p.err != nil { + return nil // Unexpected end of input. + } + if p.currentByte == '\n' { + return p.startOfLine + } + } if p.readTokenUntilNewline(false); p.err != nil { return nil // Unexpected end of input. } diff --git a/expfmt/text_parse_test.go b/expfmt/text_parse_test.go index edcee13a..920ecf75 100644 --- a/expfmt/text_parse_test.go +++ b/expfmt/text_parse_test.go @@ -957,6 +957,35 @@ request_duration_microseconds_count 2693 }, }, }, + // 18: Trailing blanks and tabs after sample values and timestamps. + { + in: "trailing_value_space 1 \t \ntrailing_timestamp_space 2 123 \t \n", + out: []*dto.MetricFamily{ + { + Name: proto.String("trailing_value_space"), + Type: dto.MetricType_UNTYPED.Enum(), + Metric: []*dto.Metric{ + { + Untyped: &dto.Untyped{ + Value: proto.Float64(1), + }, + }, + }, + }, + { + Name: proto.String("trailing_timestamp_space"), + Type: dto.MetricType_UNTYPED.Enum(), + Metric: []*dto.Metric{ + { + Untyped: &dto.Untyped{ + Value: proto.Float64(2), + }, + TimestampMs: proto.Int64(123), + }, + }, + }, + }, + }, } for i, scenario := range scenarios {