github.com/influxdata/telegraf@v1.30.3/internal/templating/template.go (about)

     1  package templating
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  )
     7  
     8  // Template represents a pattern and tags to map a metric string to an influxdb Point
     9  type Template struct {
    10  	separator         string
    11  	parts             []string
    12  	defaultTags       map[string]string
    13  	greedyField       bool
    14  	greedyMeasurement bool
    15  }
    16  
    17  // Apply extracts the template fields from the given line and returns the measurement
    18  // name, tags and field name
    19  //
    20  //nolint:revive //function-result-limit conditionally 4 return results allowed
    21  func (t *Template) Apply(line string, joiner string) (measurementName string, tags map[string]string, field string, err error) {
    22  	allFields := strings.Split(line, t.separator)
    23  	var (
    24  		measurements []string
    25  		tagsMap      = make(map[string][]string)
    26  		fields       []string
    27  	)
    28  
    29  	// Set any default tags
    30  	for k, v := range t.defaultTags {
    31  		tagsMap[k] = append(tagsMap[k], v)
    32  	}
    33  
    34  	// See if an invalid combination has been specified in the template:
    35  	for _, tag := range t.parts {
    36  		if tag == "measurement*" {
    37  			t.greedyMeasurement = true
    38  		} else if tag == "field*" {
    39  			t.greedyField = true
    40  		}
    41  	}
    42  	if t.greedyField && t.greedyMeasurement {
    43  		return "", nil, "",
    44  			fmt.Errorf("either 'field*' or 'measurement*' can be used in each "+
    45  				"template (but not both together): %q",
    46  				strings.Join(t.parts, joiner))
    47  	}
    48  
    49  	for i, tag := range t.parts {
    50  		if i >= len(allFields) {
    51  			continue
    52  		}
    53  		if tag == "" {
    54  			continue
    55  		}
    56  
    57  		switch tag {
    58  		case "measurement":
    59  			measurements = append(measurements, allFields[i])
    60  		case "field":
    61  			fields = append(fields, allFields[i])
    62  		case "field*":
    63  			fields = append(fields, allFields[i:]...)
    64  		case "measurement*":
    65  			measurements = append(measurements, allFields[i:]...)
    66  		default:
    67  			tagsMap[tag] = append(tagsMap[tag], allFields[i])
    68  		}
    69  	}
    70  
    71  	// Convert to map of strings.
    72  	tags = make(map[string]string)
    73  	for k, values := range tagsMap {
    74  		tags[k] = strings.Join(values, joiner)
    75  	}
    76  
    77  	return strings.Join(measurements, joiner), tags, strings.Join(fields, joiner), nil
    78  }
    79  
    80  func NewDefaultTemplateWithPattern(pattern string) (*Template, error) {
    81  	return NewTemplate(DefaultSeparator, pattern, nil)
    82  }
    83  
    84  // NewTemplate returns a new template ensuring it has a measurement
    85  // specified.
    86  func NewTemplate(separator string, pattern string, defaultTags map[string]string) (*Template, error) {
    87  	parts := strings.Split(pattern, separator)
    88  	hasMeasurement := false
    89  	template := &Template{
    90  		separator:   separator,
    91  		parts:       parts,
    92  		defaultTags: defaultTags,
    93  	}
    94  
    95  	for _, part := range parts {
    96  		if strings.HasPrefix(part, "measurement") {
    97  			hasMeasurement = true
    98  		}
    99  		if part == "measurement*" {
   100  			template.greedyMeasurement = true
   101  		} else if part == "field*" {
   102  			template.greedyField = true
   103  		}
   104  	}
   105  
   106  	if !hasMeasurement {
   107  		return nil, fmt.Errorf("no measurement specified for template. %q", pattern)
   108  	}
   109  
   110  	return template, nil
   111  }
   112  
   113  // templateSpec is a template string split in its constituent parts
   114  type templateSpec struct {
   115  	separator string
   116  	filter    string
   117  	template  string
   118  	tagstring string
   119  }
   120  
   121  // templateSpecs is simply an array of template specs implementing the sorting interface
   122  type templateSpecs []templateSpec
   123  
   124  // Less reports whether the element with
   125  // index j should sort before the element with index k.
   126  func (e templateSpecs) Less(j, k int) bool {
   127  	jlen := len(e[j].filter)
   128  	klen := len(e[k].filter)
   129  	if jlen == 0 && klen != 0 {
   130  		return true
   131  	}
   132  	if klen == 0 && jlen != 0 {
   133  		return false
   134  	}
   135  	return strings.Count(e[j].template, e[j].separator) <
   136  		strings.Count(e[k].template, e[k].separator)
   137  }
   138  
   139  // Swap swaps the elements with indexes i and j.
   140  func (e templateSpecs) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
   141  
   142  // Len is the number of elements in the collection.
   143  func (e templateSpecs) Len() int { return len(e) }