in plugins/parsers/xpath/parser.go [166:417]
func (p *Parser) parseQuery(starttime time.Time, doc, selected dataNode, config Config) (telegraf.Metric, error) {
var timestamp time.Time
var metricname string
// Determine the metric name. If a query was specified, use the result of this query and the default metric name
// otherwise.
metricname = config.MetricDefaultName
if len(config.MetricQuery) > 0 {
v, err := p.executeQuery(doc, selected, config.MetricQuery)
if err != nil {
return nil, fmt.Errorf("failed to query metric name: %v", err)
}
var ok bool
if metricname, ok = v.(string); !ok {
if v == nil {
p.Log.Infof("Hint: Empty metric-name-node. If you wanted to set a constant please use `metric_name = \"'name'\"`.")
}
return nil, fmt.Errorf("failed to query metric name: query result is of type %T not 'string'", v)
}
}
// By default take the time the parser was invoked and override the value
// with the queried timestamp if an expresion was specified.
timestamp = starttime
if len(config.Timestamp) > 0 {
v, err := p.executeQuery(doc, selected, config.Timestamp)
if err != nil {
return nil, fmt.Errorf("failed to query timestamp: %v", err)
}
switch v := v.(type) {
case string:
// Parse the string with the given format or assume the string to contain
// a unix timestamp in seconds if no format is given.
if len(config.TimestampFmt) < 1 || strings.HasPrefix(config.TimestampFmt, "unix") {
var nanoseconds int64
t, err := strconv.ParseFloat(v, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse unix timestamp: %v", err)
}
switch config.TimestampFmt {
case "unix_ns":
nanoseconds = int64(t)
case "unix_us":
nanoseconds = int64(t * 1e3)
case "unix_ms":
nanoseconds = int64(t * 1e6)
default:
nanoseconds = int64(t * 1e9)
}
timestamp = time.Unix(0, nanoseconds)
} else {
timestamp, err = time.Parse(config.TimestampFmt, v)
if err != nil {
return nil, fmt.Errorf("failed to query timestamp format: %v", err)
}
}
case float64:
// Assume the value to contain a timestamp in seconds and fractions thereof.
timestamp = time.Unix(0, int64(v*1e9))
case nil:
// No timestamp found. Just ignore the time and use "starttime"
default:
return nil, fmt.Errorf("unknown format '%T' for timestamp query '%v'", v, config.Timestamp)
}
}
// Query tags and add default ones
tags := make(map[string]string)
for name, query := range config.Tags {
// Execute the query and cast the returned values into strings
v, err := p.executeQuery(doc, selected, query)
if err != nil {
return nil, fmt.Errorf("failed to query tag '%s': %v", name, err)
}
switch v := v.(type) {
case string:
tags[name] = v
case bool:
tags[name] = strconv.FormatBool(v)
case float64:
tags[name] = strconv.FormatFloat(v, 'G', -1, 64)
case nil:
continue
default:
return nil, fmt.Errorf("unknown format '%T' for tag '%s'", v, name)
}
}
// Handle the tag batch definitions if any.
if len(config.TagSelection) > 0 {
tagnamequery := "name()"
tagvaluequery := "."
if len(config.TagNameQuery) > 0 {
tagnamequery = config.TagNameQuery
}
if len(config.TagValueQuery) > 0 {
tagvaluequery = config.TagValueQuery
}
// Query all tags
selectedTagNodes, err := p.document.QueryAll(selected, config.TagSelection)
if err != nil {
return nil, err
}
p.Log.Debugf("Number of selected tag nodes: %d", len(selectedTagNodes))
if len(selectedTagNodes) > 0 && selectedTagNodes[0] != nil {
for _, selectedtag := range selectedTagNodes {
n, err := p.executeQuery(doc, selectedtag, tagnamequery)
if err != nil {
return nil, fmt.Errorf("failed to query tag name with query '%s': %v", tagnamequery, err)
}
name, ok := n.(string)
if !ok {
return nil, fmt.Errorf("failed to query tag name with query '%s': result is not a string (%v)", tagnamequery, n)
}
v, err := p.executeQuery(doc, selectedtag, tagvaluequery)
if err != nil {
return nil, fmt.Errorf("failed to query tag value for '%s': %v", name, err)
}
if config.TagNameExpand {
p := p.document.GetNodePath(selectedtag, selected, "_")
if len(p) > 0 {
name = p + "_" + name
}
}
// Check if field name already exists and if so, append an index number.
if _, ok := tags[name]; ok {
for i := 1; ; i++ {
p := name + "_" + strconv.Itoa(i)
if _, ok := tags[p]; !ok {
name = p
break
}
}
}
// Convert the tag to be a string
s, err := internal.ToString(v)
if err != nil {
return nil, fmt.Errorf("failed to query tag value for '%s': result is not a string (%v)", name, v)
}
tags[name] = s
}
} else {
p.debugEmptyQuery("tag selection", selected, config.TagSelection)
}
}
for name, v := range p.DefaultTags {
tags[name] = v
}
// Query fields
fields := make(map[string]interface{})
for name, query := range config.FieldsInt {
// Execute the query and cast the returned values into integers
v, err := p.executeQuery(doc, selected, query)
if err != nil {
return nil, fmt.Errorf("failed to query field (int) '%s': %v", name, err)
}
switch v := v.(type) {
case string:
fields[name], err = strconv.ParseInt(v, 10, 54)
if err != nil {
return nil, fmt.Errorf("failed to parse field (int) '%s': %v", name, err)
}
case bool:
fields[name] = int64(0)
if v {
fields[name] = int64(1)
}
case float64:
fields[name] = int64(v)
case nil:
continue
default:
return nil, fmt.Errorf("unknown format '%T' for field (int) '%s'", v, name)
}
}
for name, query := range config.Fields {
// Execute the query and store the result in fields
v, err := p.executeQuery(doc, selected, query)
if err != nil {
return nil, fmt.Errorf("failed to query field '%s': %v", name, err)
}
fields[name] = v
}
// Handle the field batch definitions if any.
if len(config.FieldSelection) > 0 {
fieldnamequery := "name()"
fieldvaluequery := "."
if len(config.FieldNameQuery) > 0 {
fieldnamequery = config.FieldNameQuery
}
if len(config.FieldValueQuery) > 0 {
fieldvaluequery = config.FieldValueQuery
}
// Query all fields
selectedFieldNodes, err := p.document.QueryAll(selected, config.FieldSelection)
if err != nil {
return nil, err
}
p.Log.Debugf("Number of selected field nodes: %d", len(selectedFieldNodes))
if len(selectedFieldNodes) > 0 && selectedFieldNodes[0] != nil {
for _, selectedfield := range selectedFieldNodes {
n, err := p.executeQuery(doc, selectedfield, fieldnamequery)
if err != nil {
return nil, fmt.Errorf("failed to query field name with query '%s': %v", fieldnamequery, err)
}
name, ok := n.(string)
if !ok {
return nil, fmt.Errorf("failed to query field name with query '%s': result is not a string (%v)", fieldnamequery, n)
}
v, err := p.executeQuery(doc, selectedfield, fieldvaluequery)
if err != nil {
return nil, fmt.Errorf("failed to query field value for '%s': %v", name, err)
}
if config.FieldNameExpand {
p := p.document.GetNodePath(selectedfield, selected, "_")
if len(p) > 0 {
name = p + "_" + name
}
}
// Check if field name already exists and if so, append an index number.
if _, ok := fields[name]; ok {
for i := 1; ; i++ {
p := name + "_" + strconv.Itoa(i)
if _, ok := fields[p]; !ok {
name = p
break
}
}
}
fields[name] = v
}
} else {
p.debugEmptyQuery("field selection", selected, config.FieldSelection)
}
}
return metric.New(metricname, tags, fields, timestamp), nil
}