Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
90
plugins/serializers/graphite/README.md
Normal file
90
plugins/serializers/graphite/README.md
Normal file
|
@ -0,0 +1,90 @@
|
|||
# Graphite
|
||||
|
||||
The Graphite data format is translated from Telegraf Metrics using either the
|
||||
template pattern or tag support method. You can select between the two
|
||||
methods using the [`graphite_tag_support`](#graphite_tag_support) option. When set, the tag support
|
||||
method is used, otherwise the [Template Pattern][templates] is used.
|
||||
|
||||
[templates]: /docs/TEMPLATE_PATTERN.md
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
[[outputs.file]]
|
||||
## Files to write to, "stdout" is a specially handled file.
|
||||
files = ["stdout", "/tmp/metrics.out"]
|
||||
|
||||
## Data format to output.
|
||||
## Each data format has its own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
data_format = "graphite"
|
||||
|
||||
## Prefix added to each graphite bucket
|
||||
prefix = "telegraf"
|
||||
## Graphite template pattern
|
||||
template = "host.tags.measurement.field"
|
||||
|
||||
## Graphite templates patterns
|
||||
## 1. Template for cpu
|
||||
## 2. Template for disk*
|
||||
## 3. Default template
|
||||
# templates = [
|
||||
# "cpu tags.measurement.host.field",
|
||||
# "disk* measurement.field",
|
||||
# "host.measurement.tags.field"
|
||||
#]
|
||||
|
||||
## Strict sanitization regex
|
||||
## This is the default sanitization regex that is used on data passed to the
|
||||
## graphite serializer. Users can add additional characters here if required.
|
||||
## Be aware that the characters, '/' '@' '*' are always replaced with '_',
|
||||
## '..' is replaced with '.', and '\' is removed even if added to the
|
||||
## following regex.
|
||||
# graphite_strict_sanitize_regex = '[^a-zA-Z0-9-:._=\p{L}]'
|
||||
|
||||
## Support Graphite tags, recommended to enable when using Graphite 1.1 or later.
|
||||
# graphite_tag_support = false
|
||||
|
||||
## Applied sanitization mode when graphite tag support is enabled.
|
||||
## * strict - uses the regex specified above
|
||||
## * compatible - allows for greater number of characters
|
||||
# graphite_tag_sanitize_mode = "strict"
|
||||
|
||||
## Character for separating metric name and field for Graphite tags
|
||||
# graphite_separator = "."
|
||||
```
|
||||
|
||||
### graphite_tag_support
|
||||
|
||||
When the `graphite_tag_support` option is enabled, the template pattern is not
|
||||
used. Instead, tags are encoded using
|
||||
[Graphite tag support](http://graphite.readthedocs.io/en/latest/tags.html)
|
||||
added in Graphite 1.1. The `metric_path` is a combination of the optional
|
||||
`prefix` option, measurement name, and field name.
|
||||
|
||||
The tag `name` is reserved by Graphite, any conflicting tags and will be encoded as `_name`.
|
||||
|
||||
**Example Conversion**:
|
||||
|
||||
```text
|
||||
cpu,cpu=cpu-total,dc=us-east-1,host=tars usage_idle=98.09,usage_user=0.89 1455320660004257758
|
||||
=>
|
||||
cpu.usage_user;cpu=cpu-total;dc=us-east-1;host=tars 0.89 1455320690
|
||||
cpu.usage_idle;cpu=cpu-total;dc=us-east-1;host=tars 98.09 1455320690
|
||||
```
|
||||
|
||||
With set option `graphite_separator` to "_"
|
||||
|
||||
```text
|
||||
cpu,cpu=cpu-total,dc=us-east-1,host=tars usage_idle=98.09,usage_user=0.89 1455320660004257758
|
||||
=>
|
||||
cpu_usage_user;cpu=cpu-total;dc=us-east-1;host=tars 0.89 1455320690
|
||||
cpu_usage_idle;cpu=cpu-total;dc=us-east-1;host=tars 98.09 1455320690
|
||||
```
|
||||
|
||||
The `graphite_tag_sanitize_mode` option defines how we should sanitize the tag names and values. Possible values are `strict`, or `compatible`, with the default being `strict`.
|
||||
|
||||
When in `strict` mode Telegraf uses the same rules as metrics when not using tags.
|
||||
When in `compatible` mode Telegraf allows more characters through, and is based on the Graphite specification:
|
||||
>Tag names must have a length >= 1 and may contain any ascii characters except `;!^=`. Tag values must also have a length >= 1, they may contain any ascii characters except `;` and the first character must not be `~`. UTF-8 characters may work for names and values, but they are not well tested and it is not recommended to use non-ascii characters in metric names or tags. Metric names get indexed under the special tag name, if a metric name starts with one or multiple ~ they simply get removed from the derived tag value because the ~ character is not allowed to be in the first position of the tag value. If a metric name consists of no other characters than ~, then it is considered invalid and may get dropped.
|
362
plugins/serializers/graphite/graphite.go
Normal file
362
plugins/serializers/graphite/graphite.go
Normal file
|
@ -0,0 +1,362 @@
|
|||
package graphite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/filter"
|
||||
"github.com/influxdata/telegraf/plugins/serializers"
|
||||
)
|
||||
|
||||
const DefaultTemplate = "host.tags.measurement.field"
|
||||
|
||||
var (
|
||||
compatibleAllowedCharsName = regexp.MustCompile(`[^ "-:\<>-\]_a-~\p{L}]`) //nolint:gocritic // valid range for use-case
|
||||
compatibleAllowedCharsValue = regexp.MustCompile(`[^ -:<-~\p{L}]`) //nolint:gocritic // valid range for use-case
|
||||
compatibleLeadingTildeDrop = regexp.MustCompile(`^[~]*(.*)`)
|
||||
hyphenChars = strings.NewReplacer(
|
||||
"/", "-",
|
||||
"@", "-",
|
||||
"*", "-",
|
||||
)
|
||||
dropChars = strings.NewReplacer(
|
||||
`\`, "",
|
||||
"..", ".",
|
||||
)
|
||||
|
||||
fieldDeleter = strings.NewReplacer(".FIELDNAME", "", "FIELDNAME.", "")
|
||||
)
|
||||
|
||||
type GraphiteTemplate struct {
|
||||
Filter filter.Filter
|
||||
Value string
|
||||
}
|
||||
|
||||
type GraphiteSerializer struct {
|
||||
Prefix string `toml:"prefix"`
|
||||
Template string `toml:"template"`
|
||||
StrictRegex string `toml:"graphite_strict_sanitize_regex"`
|
||||
TagSupport bool `toml:"graphite_tag_support"`
|
||||
TagSanitizeMode string `toml:"graphite_tag_sanitize_mode"`
|
||||
Separator string `toml:"graphite_separator"`
|
||||
Templates []string `toml:"templates"`
|
||||
|
||||
tmplts []*GraphiteTemplate
|
||||
strictAllowedChars *regexp.Regexp
|
||||
}
|
||||
|
||||
func (s *GraphiteSerializer) Init() error {
|
||||
graphiteTemplates, defaultTemplate, err := InitGraphiteTemplates(s.Templates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.tmplts = graphiteTemplates
|
||||
|
||||
if defaultTemplate != "" {
|
||||
s.Template = defaultTemplate
|
||||
}
|
||||
|
||||
if s.TagSanitizeMode == "" {
|
||||
s.TagSanitizeMode = "strict"
|
||||
}
|
||||
|
||||
if s.Separator == "" {
|
||||
s.Separator = "."
|
||||
}
|
||||
|
||||
if s.StrictRegex == "" {
|
||||
s.strictAllowedChars = regexp.MustCompile(`[^a-zA-Z0-9-:._=\p{L}]`)
|
||||
} else {
|
||||
var err error
|
||||
s.strictAllowedChars, err = regexp.Compile(s.StrictRegex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid regex provided %q: %w", s.StrictRegex, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GraphiteSerializer) Serialize(metric telegraf.Metric) ([]byte, error) {
|
||||
var out []byte
|
||||
|
||||
// Convert UnixNano to Unix timestamps
|
||||
timestamp := metric.Time().UnixNano() / 1000000000
|
||||
|
||||
switch s.TagSupport {
|
||||
case true:
|
||||
for fieldName, value := range metric.Fields() {
|
||||
fieldValue := formatValue(value)
|
||||
if fieldValue == "" {
|
||||
continue
|
||||
}
|
||||
bucket := s.SerializeBucketNameWithTags(metric.Name(), metric.Tags(), s.Prefix, s.Separator, fieldName, s.TagSanitizeMode)
|
||||
metricString := fmt.Sprintf("%s %s %d\n",
|
||||
// insert "field" section of template
|
||||
bucket,
|
||||
// bucket,
|
||||
fieldValue,
|
||||
timestamp)
|
||||
point := []byte(metricString)
|
||||
out = append(out, point...)
|
||||
}
|
||||
default:
|
||||
template := s.Template
|
||||
for _, graphiteTemplate := range s.tmplts {
|
||||
if graphiteTemplate.Filter.Match(metric.Name()) {
|
||||
template = graphiteTemplate.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
bucket := SerializeBucketName(metric.Name(), metric.Tags(), template, s.Prefix)
|
||||
if bucket == "" {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
for fieldName, value := range metric.Fields() {
|
||||
fieldValue := formatValue(value)
|
||||
if fieldValue == "" {
|
||||
continue
|
||||
}
|
||||
metricString := fmt.Sprintf("%s %s %d\n",
|
||||
// insert "field" section of template
|
||||
s.strictSanitize(InsertField(bucket, fieldName)),
|
||||
fieldValue,
|
||||
timestamp)
|
||||
point := []byte(metricString)
|
||||
out = append(out, point...)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *GraphiteSerializer) SerializeBatch(metrics []telegraf.Metric) ([]byte, error) {
|
||||
var batch bytes.Buffer
|
||||
for _, m := range metrics {
|
||||
buf, err := s.Serialize(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
batch.Write(buf)
|
||||
}
|
||||
return batch.Bytes(), nil
|
||||
}
|
||||
|
||||
func formatValue(value interface{}) string {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return ""
|
||||
case bool:
|
||||
if v {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
case uint64:
|
||||
return strconv.FormatUint(v, 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
case float64:
|
||||
if math.IsNaN(v) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if math.IsInf(v, 0) {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// SerializeBucketName will take the given measurement name and tags and
|
||||
// produce a graphite bucket. It will use the GraphiteSerializer.Template
|
||||
// to generate this, or DefaultTemplate.
|
||||
//
|
||||
// NOTE: SerializeBucketName replaces the "field" portion of the template with
|
||||
// FIELDNAME. It is up to the user to replace this. This is so that
|
||||
// SerializeBucketName can be called just once per measurement, rather than
|
||||
// once per field. See GraphiteSerializer.InsertField() function.
|
||||
func SerializeBucketName(measurement string, tags map[string]string, template, prefix string) string {
|
||||
if template == "" {
|
||||
template = DefaultTemplate
|
||||
}
|
||||
tagsCopy := make(map[string]string)
|
||||
for k, v := range tags {
|
||||
tagsCopy[k] = v
|
||||
}
|
||||
|
||||
var out []string
|
||||
templateParts := strings.Split(template, ".")
|
||||
for _, templatePart := range templateParts {
|
||||
switch templatePart {
|
||||
case "measurement":
|
||||
out = append(out, measurement)
|
||||
case "tags":
|
||||
// we will replace this later
|
||||
out = append(out, "TAGS")
|
||||
case "field":
|
||||
// user of SerializeBucketName needs to replace this
|
||||
out = append(out, "FIELDNAME")
|
||||
default:
|
||||
// This is a tag being applied
|
||||
if tagvalue, ok := tagsCopy[templatePart]; ok {
|
||||
out = append(out, strings.ReplaceAll(tagvalue, ".", "_"))
|
||||
delete(tagsCopy, templatePart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert remaining tags into output name
|
||||
for i, templatePart := range out {
|
||||
if templatePart == "TAGS" {
|
||||
out[i] = buildTags(tagsCopy)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(out) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if prefix == "" {
|
||||
return strings.Join(out, ".")
|
||||
}
|
||||
return prefix + "." + strings.Join(out, ".")
|
||||
}
|
||||
|
||||
func InitGraphiteTemplates(templates []string) ([]*GraphiteTemplate, string, error) {
|
||||
defaultTemplate := ""
|
||||
graphiteTemplates := make([]*GraphiteTemplate, 0, len(templates))
|
||||
for i, t := range templates {
|
||||
parts := strings.Fields(t)
|
||||
|
||||
if len(parts) == 0 {
|
||||
return nil, "", fmt.Errorf("missing template at position: %d", i)
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
if parts[0] == "" {
|
||||
return nil, "", fmt.Errorf("missing template at position: %d", i)
|
||||
}
|
||||
|
||||
// Override default template
|
||||
defaultTemplate = t
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) > 2 {
|
||||
return nil, "", fmt.Errorf("invalid template format: %q", t)
|
||||
}
|
||||
|
||||
tFilter, err := filter.Compile([]string{parts[0]})
|
||||
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
graphiteTemplates = append(graphiteTemplates, &GraphiteTemplate{
|
||||
Filter: tFilter,
|
||||
Value: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return graphiteTemplates, defaultTemplate, nil
|
||||
}
|
||||
|
||||
// SerializeBucketNameWithTags will take the given measurement name and tags and
|
||||
// produce a graphite bucket. It will use the Graphite11Serializer.
|
||||
// http://graphite.readthedocs.io/en/latest/tags.html
|
||||
func (s *GraphiteSerializer) SerializeBucketNameWithTags(measurement string, tags map[string]string, prefix, separator, field, tagSanitizeMode string) string {
|
||||
var out string
|
||||
var tagsCopy []string
|
||||
for k, v := range tags {
|
||||
if k == "name" {
|
||||
k = "_name"
|
||||
}
|
||||
if tagSanitizeMode == "compatible" {
|
||||
tagsCopy = append(tagsCopy, compatibleSanitize(k, v))
|
||||
} else {
|
||||
tagsCopy = append(tagsCopy, s.strictSanitize(k+"="+v))
|
||||
}
|
||||
}
|
||||
sort.Strings(tagsCopy)
|
||||
|
||||
if prefix != "" {
|
||||
out = prefix + separator
|
||||
}
|
||||
|
||||
out += measurement
|
||||
|
||||
if field != "value" {
|
||||
out += separator + field
|
||||
}
|
||||
|
||||
out = s.strictSanitize(out)
|
||||
|
||||
if len(tagsCopy) > 0 {
|
||||
out += ";" + strings.Join(tagsCopy, ";")
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// InsertField takes the bucket string from SerializeBucketName and replaces the
|
||||
// FIELDNAME portion. If fieldName == "value", it will simply delete the
|
||||
// FIELDNAME portion.
|
||||
func InsertField(bucket, fieldName string) string {
|
||||
// if the field name is "value", then dont use it
|
||||
if fieldName == "value" {
|
||||
return fieldDeleter.Replace(bucket)
|
||||
}
|
||||
return strings.Replace(bucket, "FIELDNAME", fieldName, 1)
|
||||
}
|
||||
|
||||
func buildTags(tags map[string]string) string {
|
||||
keys := make([]string, 0, len(tags))
|
||||
for k := range tags {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var tagStr string
|
||||
for i, k := range keys {
|
||||
tagValue := strings.ReplaceAll(tags[k], ".", "_")
|
||||
if i == 0 {
|
||||
tagStr += tagValue
|
||||
} else {
|
||||
tagStr += "." + tagValue
|
||||
}
|
||||
}
|
||||
return tagStr
|
||||
}
|
||||
|
||||
func (s *GraphiteSerializer) strictSanitize(value string) string {
|
||||
// Apply special hyphenation rules to preserve backwards compatibility
|
||||
value = hyphenChars.Replace(value)
|
||||
// Apply rule to drop some chars to preserve backwards compatibility
|
||||
value = dropChars.Replace(value)
|
||||
// Replace any remaining illegal chars
|
||||
return s.strictAllowedChars.ReplaceAllLiteralString(value, "_")
|
||||
}
|
||||
|
||||
func compatibleSanitize(name, value string) string {
|
||||
name = compatibleAllowedCharsName.ReplaceAllLiteralString(name, "_")
|
||||
value = compatibleAllowedCharsValue.ReplaceAllLiteralString(value, "_")
|
||||
value = compatibleLeadingTildeDrop.FindStringSubmatch(value)[1]
|
||||
return name + "=" + value
|
||||
}
|
||||
|
||||
func init() {
|
||||
serializers.Add("graphite",
|
||||
func() telegraf.Serializer {
|
||||
return &GraphiteSerializer{}
|
||||
},
|
||||
)
|
||||
}
|
1246
plugins/serializers/graphite/graphite_test.go
Normal file
1246
plugins/serializers/graphite/graphite_test.go
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue