192 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			192 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| package graphite
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"math"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/influxdata/telegraf/internal/templating"
 | |
| 
 | |
| 	"github.com/influxdata/telegraf"
 | |
| 	"github.com/influxdata/telegraf/metric"
 | |
| )
 | |
| 
 | |
| // Minimum and maximum supported dates for timestamps.
 | |
| var (
 | |
| 	MinDate = time.Date(1901, 12, 13, 0, 0, 0, 0, time.UTC)
 | |
| 	MaxDate = time.Date(2038, 1, 19, 0, 0, 0, 0, time.UTC)
 | |
| )
 | |
| 
 | |
| // Parser encapsulates a Graphite Parser.
 | |
| type GraphiteParser struct {
 | |
| 	Separator      string
 | |
| 	Templates      []string
 | |
| 	DefaultTags    map[string]string
 | |
| 	templateEngine *templating.Engine
 | |
| }
 | |
| 
 | |
| func (p *GraphiteParser) SetDefaultTags(tags map[string]string) {
 | |
| 	p.DefaultTags = tags
 | |
| }
 | |
| 
 | |
| func NewGraphiteParser(
 | |
| 	separator string,
 | |
| 	templates []string,
 | |
| 	defaultTags map[string]string,
 | |
| ) (*GraphiteParser, error) {
 | |
| 	var err error
 | |
| 
 | |
| 	if separator == "" {
 | |
| 		separator = DefaultSeparator
 | |
| 	}
 | |
| 	p := &GraphiteParser{
 | |
| 		Separator: separator,
 | |
| 		Templates: templates,
 | |
| 	}
 | |
| 
 | |
| 	if defaultTags != nil {
 | |
| 		p.DefaultTags = defaultTags
 | |
| 	}
 | |
| 	defaultTemplate, _ := templating.NewDefaultTemplateWithPattern("measurement*")
 | |
| 	p.templateEngine, err = templating.NewEngine(p.Separator, defaultTemplate, p.Templates)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return p, fmt.Errorf("exec input parser config is error: %s ", err.Error())
 | |
| 	}
 | |
| 	return p, nil
 | |
| }
 | |
| 
 | |
| func (p *GraphiteParser) Parse(buf []byte) ([]telegraf.Metric, error) {
 | |
| 	// parse even if the buffer begins with a newline
 | |
| 	buf = bytes.TrimPrefix(buf, []byte("\n"))
 | |
| 	// add newline to end if not exists:
 | |
| 	if len(buf) > 0 && !bytes.HasSuffix(buf, []byte("\n")) {
 | |
| 		buf = append(buf, []byte("\n")...)
 | |
| 	}
 | |
| 
 | |
| 	metrics := make([]telegraf.Metric, 0)
 | |
| 
 | |
| 	var errStr string
 | |
| 	buffer := bytes.NewBuffer(buf)
 | |
| 	reader := bufio.NewReader(buffer)
 | |
| 	for {
 | |
| 		// Read up to the next newline.
 | |
| 		buf, err := reader.ReadBytes('\n')
 | |
| 		if err == io.EOF {
 | |
| 			break
 | |
| 		}
 | |
| 		if err != nil && err != io.EOF {
 | |
| 			return metrics, err
 | |
| 		}
 | |
| 
 | |
| 		// Trim the buffer, even though there should be no padding
 | |
| 		line := strings.TrimSpace(string(buf))
 | |
| 		if line == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		metric, err := p.ParseLine(line)
 | |
| 		if err == nil {
 | |
| 			metrics = append(metrics, metric)
 | |
| 		} else {
 | |
| 			errStr += err.Error() + "\n"
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if errStr != "" {
 | |
| 		return metrics, fmt.Errorf(strings.TrimSpace(errStr))
 | |
| 	}
 | |
| 	return metrics, nil
 | |
| }
 | |
| 
 | |
| // Parse performs Graphite parsing of a single line.
 | |
| func (p *GraphiteParser) ParseLine(line string) (telegraf.Metric, error) {
 | |
| 	// Break into 3 fields (name, value, timestamp).
 | |
| 	fields := strings.Fields(line)
 | |
| 	if len(fields) != 2 && len(fields) != 3 {
 | |
| 		return nil, fmt.Errorf("received %q which doesn't have required fields", line)
 | |
| 	}
 | |
| 
 | |
| 	// decode the name and tags
 | |
| 	measurement, tags, field, err := p.templateEngine.Apply(fields[0])
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Could not extract measurement, use the raw value
 | |
| 	if measurement == "" {
 | |
| 		measurement = fields[0]
 | |
| 	}
 | |
| 
 | |
| 	// Parse value.
 | |
| 	v, err := strconv.ParseFloat(fields[1], 64)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf(`field "%s" value: %s`, fields[0], err)
 | |
| 	}
 | |
| 
 | |
| 	if math.IsNaN(v) || math.IsInf(v, 0) {
 | |
| 		return nil, &UnsupposedValueError{Field: fields[0], Value: v}
 | |
| 	}
 | |
| 
 | |
| 	fieldValues := map[string]interface{}{}
 | |
| 	if field != "" {
 | |
| 		fieldValues[field] = v
 | |
| 	} else {
 | |
| 		fieldValues["value"] = v
 | |
| 	}
 | |
| 
 | |
| 	// If no 3rd field, use now as timestamp
 | |
| 	timestamp := time.Now().UTC()
 | |
| 
 | |
| 	if len(fields) == 3 {
 | |
| 		// Parse timestamp.
 | |
| 		unixTime, err := strconv.ParseFloat(fields[2], 64)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf(`field "%s" time: %s`, fields[0], err)
 | |
| 		}
 | |
| 
 | |
| 		// -1 is a special value that gets converted to current UTC time
 | |
| 		// See https://github.com/graphite-project/carbon/issues/54
 | |
| 		if unixTime != float64(-1) {
 | |
| 			// Check if we have fractional seconds
 | |
| 			timestamp = time.Unix(int64(unixTime), int64((unixTime-math.Floor(unixTime))*float64(time.Second)))
 | |
| 			if timestamp.Before(MinDate) || timestamp.After(MaxDate) {
 | |
| 				return nil, fmt.Errorf("timestamp out of range")
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	// Set the default tags on the point if they are not already set
 | |
| 	for k, v := range p.DefaultTags {
 | |
| 		if _, ok := tags[k]; !ok {
 | |
| 			tags[k] = v
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return metric.New(measurement, tags, fieldValues, timestamp)
 | |
| }
 | |
| 
 | |
| // ApplyTemplate extracts the template fields from the given line and
 | |
| // returns the measurement name and tags.
 | |
| func (p *GraphiteParser) ApplyTemplate(line string) (string, map[string]string, string, error) {
 | |
| 	// Break line into fields (name, value, timestamp), only name is used
 | |
| 	fields := strings.Fields(line)
 | |
| 	if len(fields) == 0 {
 | |
| 		return "", make(map[string]string), "", nil
 | |
| 	}
 | |
| 	// decode the name and tags
 | |
| 	name, tags, field, err := p.templateEngine.Apply(fields[0])
 | |
| 
 | |
| 	// Set the default tags on the point if they are not already set
 | |
| 	for k, v := range p.DefaultTags {
 | |
| 		if _, ok := tags[k]; !ok {
 | |
| 			tags[k] = v
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return name, tags, field, err
 | |
| }
 |