2018-01-08 23:11:36 +00:00
|
|
|
package dropwizard
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/influxdata/telegraf"
|
|
|
|
"github.com/influxdata/telegraf/internal/templating"
|
2018-05-14 18:00:03 +00:00
|
|
|
"github.com/influxdata/telegraf/metric"
|
2018-03-28 00:30:51 +00:00
|
|
|
"github.com/influxdata/telegraf/plugins/parsers/influx"
|
2018-01-08 23:11:36 +00:00
|
|
|
"github.com/tidwall/gjson"
|
|
|
|
)
|
|
|
|
|
2018-01-16 20:12:14 +00:00
|
|
|
var fieldEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")
|
|
|
|
var keyEscaper = strings.NewReplacer(" ", "\\ ", ",", "\\,", "=", "\\=")
|
|
|
|
|
2018-01-08 23:11:36 +00:00
|
|
|
// Parser parses json inputs containing dropwizard metrics,
|
|
|
|
// either top-level or embedded inside a json field.
|
2018-05-14 18:00:03 +00:00
|
|
|
// This parser is using gjson for retrieving paths within the json file.
|
|
|
|
type parser struct {
|
2018-01-08 23:11:36 +00:00
|
|
|
|
|
|
|
// an optional json path containing the metric registry object
|
|
|
|
// if left empty, the whole json object is parsed as a metric registry
|
|
|
|
MetricRegistryPath string
|
|
|
|
|
|
|
|
// an optional json path containing the default time of the metrics
|
|
|
|
// if left empty, or if cannot be parsed the current processing time is used as the time of the metrics
|
|
|
|
TimePath string
|
|
|
|
|
|
|
|
// time format to use for parsing the time field
|
|
|
|
// defaults to time.RFC3339
|
|
|
|
TimeFormat string
|
|
|
|
|
|
|
|
// an optional json path pointing to a json object with tag key/value pairs
|
|
|
|
// takes precedence over TagPathsMap
|
|
|
|
TagsPath string
|
|
|
|
|
|
|
|
// an optional map containing tag names as keys and json paths to retrieve the tag values from as values
|
|
|
|
// used if TagsPath is empty or doesn't return any tags
|
|
|
|
TagPathsMap map[string]string
|
|
|
|
|
|
|
|
// an optional map of default tags to use for metrics
|
|
|
|
DefaultTags map[string]string
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
separator string
|
2018-01-08 23:11:36 +00:00
|
|
|
templateEngine *templating.Engine
|
2018-05-14 18:00:03 +00:00
|
|
|
|
|
|
|
timeFunc metric.TimeFunc
|
|
|
|
|
|
|
|
// seriesParser parses line protocol measurement + tags
|
|
|
|
seriesParser *influx.Parser
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewParser() *parser {
|
|
|
|
handler := influx.NewMetricHandler()
|
|
|
|
seriesParser := influx.NewSeriesParser(handler)
|
|
|
|
|
|
|
|
parser := &parser{
|
|
|
|
timeFunc: time.Now,
|
|
|
|
seriesParser: seriesParser,
|
|
|
|
}
|
|
|
|
return parser
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse parses the input bytes to an array of metrics
|
2018-05-14 18:00:03 +00:00
|
|
|
func (p *parser) Parse(buf []byte) ([]telegraf.Metric, error) {
|
2018-01-08 23:11:36 +00:00
|
|
|
|
|
|
|
metrics := make([]telegraf.Metric, 0)
|
|
|
|
|
|
|
|
metricTime, err := p.parseTime(buf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
dwr, err := p.unmarshalMetrics(buf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
metrics = p.readDWMetrics("counter", dwr["counters"], metrics, metricTime)
|
|
|
|
metrics = p.readDWMetrics("meter", dwr["meters"], metrics, metricTime)
|
|
|
|
metrics = p.readDWMetrics("gauge", dwr["gauges"], metrics, metricTime)
|
|
|
|
metrics = p.readDWMetrics("histogram", dwr["histograms"], metrics, metricTime)
|
|
|
|
metrics = p.readDWMetrics("timer", dwr["timers"], metrics, metricTime)
|
|
|
|
|
|
|
|
jsonTags := p.readTags(buf)
|
|
|
|
|
|
|
|
// fill json tags first
|
|
|
|
if len(jsonTags) > 0 {
|
|
|
|
for _, m := range metrics {
|
|
|
|
for k, v := range jsonTags {
|
|
|
|
// only set the tag if it doesn't already exist:
|
|
|
|
if !m.HasTag(k) {
|
|
|
|
m.AddTag(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// fill default tags last
|
|
|
|
if len(p.DefaultTags) > 0 {
|
|
|
|
for _, m := range metrics {
|
|
|
|
for k, v := range p.DefaultTags {
|
|
|
|
// only set the default tag if it doesn't already exist:
|
|
|
|
if !m.HasTag(k) {
|
|
|
|
m.AddTag(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return metrics, nil
|
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
func (p *parser) SetTemplates(separator string, templates []string) error {
|
|
|
|
if len(templates) == 0 {
|
|
|
|
p.templateEngine = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultTemplate, err := templating.NewDefaultTemplateWithPattern("measurement*")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
templateEngine, err := templating.NewEngine(separator, defaultTemplate, templates)
|
|
|
|
if err != nil {
|
2018-01-08 23:11:36 +00:00
|
|
|
return err
|
|
|
|
}
|
2018-05-14 18:00:03 +00:00
|
|
|
|
|
|
|
p.separator = separator
|
|
|
|
p.templateEngine = templateEngine
|
2018-01-08 23:11:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseLine is not supported by the dropwizard format
|
2018-05-14 18:00:03 +00:00
|
|
|
func (p *parser) ParseLine(line string) (telegraf.Metric, error) {
|
2018-01-08 23:11:36 +00:00
|
|
|
return nil, fmt.Errorf("ParseLine not supported: %s, for data format: dropwizard", line)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetDefaultTags sets the default tags
|
2018-05-14 18:00:03 +00:00
|
|
|
func (p *parser) SetDefaultTags(tags map[string]string) {
|
2018-01-08 23:11:36 +00:00
|
|
|
p.DefaultTags = tags
|
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
func (p *parser) readTags(buf []byte) map[string]string {
|
2018-01-08 23:11:36 +00:00
|
|
|
|
|
|
|
if p.TagsPath != "" {
|
|
|
|
var tagsBytes []byte
|
|
|
|
tagsResult := gjson.GetBytes(buf, p.TagsPath)
|
|
|
|
if tagsResult.Index > 0 {
|
|
|
|
tagsBytes = buf[tagsResult.Index : tagsResult.Index+len(tagsResult.Raw)]
|
|
|
|
} else {
|
|
|
|
tagsBytes = []byte(tagsResult.Raw)
|
|
|
|
}
|
|
|
|
var tags map[string]string
|
|
|
|
err := json.Unmarshal(tagsBytes, &tags)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("W! failed to parse tags from JSON path '%s': %s\n", p.TagsPath, err)
|
|
|
|
} else if len(tags) > 0 {
|
|
|
|
return tags
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tags := make(map[string]string)
|
|
|
|
for tagKey, jsonPath := range p.TagPathsMap {
|
|
|
|
tags[tagKey] = gjson.GetBytes(buf, jsonPath).String()
|
|
|
|
}
|
|
|
|
return tags
|
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
func (p *parser) parseTime(buf []byte) (time.Time, error) {
|
2018-01-08 23:11:36 +00:00
|
|
|
|
|
|
|
if p.TimePath != "" {
|
|
|
|
timeFormat := p.TimeFormat
|
|
|
|
if timeFormat == "" {
|
|
|
|
timeFormat = time.RFC3339
|
|
|
|
}
|
|
|
|
timeString := gjson.GetBytes(buf, p.TimePath).String()
|
|
|
|
if timeString == "" {
|
|
|
|
err := fmt.Errorf("time not found in JSON path %s", p.TimePath)
|
2018-05-14 18:00:03 +00:00
|
|
|
return p.timeFunc(), err
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
|
|
|
t, err := time.Parse(timeFormat, timeString)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("time %s cannot be parsed with format %s, %s", timeString, timeFormat, err)
|
2018-05-14 18:00:03 +00:00
|
|
|
return p.timeFunc(), err
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
|
|
|
return t.UTC(), nil
|
|
|
|
}
|
2018-05-14 18:00:03 +00:00
|
|
|
return p.timeFunc(), nil
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
func (p *parser) unmarshalMetrics(buf []byte) (map[string]interface{}, error) {
|
2018-01-08 23:11:36 +00:00
|
|
|
|
|
|
|
var registryBytes []byte
|
|
|
|
if p.MetricRegistryPath != "" {
|
|
|
|
regResult := gjson.GetBytes(buf, p.MetricRegistryPath)
|
|
|
|
if regResult.Index > 0 {
|
|
|
|
registryBytes = buf[regResult.Index : regResult.Index+len(regResult.Raw)]
|
|
|
|
} else {
|
|
|
|
registryBytes = []byte(regResult.Raw)
|
|
|
|
}
|
|
|
|
if len(registryBytes) == 0 {
|
|
|
|
err := fmt.Errorf("metric registry not found in JSON path %s", p.MetricRegistryPath)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
registryBytes = buf
|
|
|
|
}
|
|
|
|
var jsonOut map[string]interface{}
|
|
|
|
err := json.Unmarshal(registryBytes, &jsonOut)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("unable to parse dropwizard metric registry from JSON document, %s", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return jsonOut, nil
|
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
func (p *parser) readDWMetrics(metricType string, dwms interface{}, metrics []telegraf.Metric, tm time.Time) []telegraf.Metric {
|
|
|
|
if dwmsTyped, ok := dwms.(map[string]interface{}); ok {
|
2018-01-08 23:11:36 +00:00
|
|
|
for dwmName, dwmFields := range dwmsTyped {
|
|
|
|
measurementName := dwmName
|
|
|
|
tags := make(map[string]string)
|
|
|
|
fieldPrefix := ""
|
|
|
|
if p.templateEngine != nil {
|
|
|
|
measurementName, tags, fieldPrefix, _ = p.templateEngine.Apply(dwmName)
|
|
|
|
if len(fieldPrefix) > 0 {
|
2018-05-14 18:00:03 +00:00
|
|
|
fieldPrefix = fmt.Sprintf("%s%s", fieldPrefix, p.separator)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parsed, err := p.seriesParser.Parse([]byte(measurementName))
|
|
|
|
var m telegraf.Metric
|
|
|
|
if err != nil || len(parsed) != 1 {
|
|
|
|
m, err = metric.New(measurementName, map[string]string{}, map[string]interface{}{}, tm)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("W! failed to create metric of type '%s': %s\n", metricType, err)
|
|
|
|
continue
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
2018-05-14 18:00:03 +00:00
|
|
|
} else {
|
|
|
|
m = parsed[0]
|
|
|
|
m.SetTime(tm)
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
m.AddTag("metric_type", metricType)
|
|
|
|
for k, v := range tags {
|
|
|
|
m.AddTag(k, v)
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
if fields, ok := dwmFields.(map[string]interface{}); ok {
|
|
|
|
for k, v := range fields {
|
|
|
|
switch v := v.(type) {
|
|
|
|
case float64, string, bool:
|
|
|
|
m.AddField(fieldPrefix+k, v)
|
|
|
|
default:
|
|
|
|
// ignore
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
metrics = append(metrics, m)
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
return metrics
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:00:03 +00:00
|
|
|
func (p *parser) SetTimeFunc(f metric.TimeFunc) {
|
|
|
|
p.timeFunc = f
|
2018-01-08 23:11:36 +00:00
|
|
|
}
|