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