| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- package parser
- import (
- "fmt"
- "io"
- "strconv"
- "strings"
- "time"
- )
- // MetricRecord is the definition of a single metric instance in time.
- type MetricRecord struct {
- Name string
- Labels map[string]string
- Value float64
- Timestamp *time.Time
- }
- // Parse reads the input reader containing the raw metric format, and returns a slice of MetricRecord instances
- // containing the data parsed from the input.
- func Parse(reader io.Reader) ([]*MetricRecord, error) {
- return newParser(reader).parse()
- }
- // Parses Metrics from raw metric format.
- //
- // metric_name ["{" label_name "=" `"` label_value `"` { "," label_name"=" `"` label_value `"` } [ "," ] "}"] value [ timestamp ]
- //
- // In the sample syntax:
- // - metric_name and label_name carry the usual Prometheus expression language
- // restrictions.
- // - label_value can be any sequence of UTF-8 characters, but the backslash
- // (\), double-quote ("), and line feed (\n) characters have to be escaped as
- // \\, \", and \n, respectively.
- // - value is a float represented as required by Go's ParseFloat() function. In
- // addition to standard numerical values, NaN, +Inf, and -Inf are valid
- // values representing not a number, positive infinity, and negative
- // infinity, respectively.
- // - The timestamp is an int64 (milliseconds since epoch, i.e. 1970-01-01
- // 00:00:00 UTC, excluding leap seconds), represented as required by Go's
- // ParseInt() function.
- type parser struct {
- lex *lexer
- current token
- }
- // creates a new parser, which is meant to be used once and discarded
- func newParser(r io.Reader) *parser {
- return &parser{
- lex: newLexer(r),
- }
- }
- func (p *parser) advance() token {
- p.current = p.lex.next()
- return p.current
- }
- func (p *parser) parse() ([]*MetricRecord, error) {
- var metrics []*MetricRecord
- p.advance()
- for {
- if p.current.Type == Eof {
- break
- }
- if p.current.Type == Comment {
- p.advance()
- continue
- }
- metric, err := p.parseMetric()
- if err != nil {
- return nil, err
- }
- metrics = append(metrics, metric)
- }
- if len(p.lex.errors) > 0 {
- var sb strings.Builder
- for _, err := range p.lex.errors {
- sb.WriteString(" * ")
- sb.WriteString(err.Error())
- sb.WriteRune('\n')
- }
- return nil, fmt.Errorf("lexical errors: \n%s", sb.String())
- }
- return metrics, nil
- }
- func (p *parser) parseMetric() (*MetricRecord, error) {
- // expected to be advanced to current token
- if p.current.Type != Literal {
- return nil, fmt.Errorf("[metric parse error] expected literal, got unexpected token %v", p.current)
- }
- metric := &MetricRecord{
- Name: p.current.Value,
- }
- p.advance()
- // No Bracket: Parse Value/Timestamp
- if p.current.Type != OpenBracket {
- v, ts, err := p.parseValueAndTimestamp()
- if err != nil {
- return nil, err
- }
- metric.Value = v
- metric.Timestamp = ts
- return metric, nil
- }
- // Parse Label Pairs
- p.advance()
- for {
- if p.current.Type == Comma {
- p.advance()
- continue
- }
- if p.current.Type == CloseBracket {
- break
- }
- labelName, labelValue, err := p.parseLabelValuePair()
- if err != nil {
- return nil, err
- }
- if metric.Labels == nil {
- metric.Labels = make(map[string]string)
- }
- metric.Labels[labelName] = labelValue
- }
- p.advance()
- // Value and Timestamp
- v, ts, err := p.parseValueAndTimestamp()
- if err != nil {
- return nil, err
- }
- metric.Value = v
- metric.Timestamp = ts
- return metric, nil
- }
- func (p *parser) parseValueAndTimestamp() (float64, *time.Time, error) {
- var ts *time.Time
- if p.current.Type != Value {
- return 0.0, nil, fmt.Errorf("[value and time parse error] expected value, got unexpected token %v", p.current)
- }
- v, err := strconv.ParseFloat(p.current.Value, 64)
- if err != nil {
- return 0.0, nil, fmt.Errorf("failed to parse value %v: %v", p.current.Value, err)
- }
- p.advance()
- if p.current.Type == Value {
- t, err := strconv.ParseInt(p.current.Value, 10, 64)
- if err != nil {
- return 0.0, nil, fmt.Errorf("failed to parse timestamp %v: %v", p.current.Value, err)
- }
- epoch := time.Unix(0, t*int64(time.Millisecond))
- ts = &epoch
- p.advance()
- }
- return v, ts, nil
- }
- func (p *parser) parseLabelValuePair() (string, string, error) {
- if p.current.Type != Literal {
- return "", "", fmt.Errorf("[label parse error] expected literal, got unexpected token %v", p.current)
- }
- // start with label name literal
- labelName := p.current.Value
- p.advance()
- // must be '='
- if p.current.Type != Equal {
- return "", "", fmt.Errorf("[label parse error] expected '=', got unexpected token %v", p.current)
- }
- p.advance()
- // must be string type
- if p.current.Type != String {
- return "", "", fmt.Errorf("[label parse error] expected string, got unexpected token %v", p.current)
- }
- // label value string
- labelValue := p.current.Value
- p.advance()
- return labelName, labelValue, nil
- }
|