1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View 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.

View 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{}
},
)
}

File diff suppressed because it is too large Load diff