mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-25 11:50:13 +01:00
50f5eae0e0
Sort labels explicitly after calling the ParsedConfigs.Apply() when needed. This reduces CPU usage when performing metric-level relabeling, where labels' sorting isn't needed.
200 lines
6.7 KiB
Go
200 lines
6.7 KiB
Go
package notifier
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"fmt"
|
|
"gopkg.in/yaml.v2"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/consul"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/dns"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
|
)
|
|
|
|
// Config contains list of supported configuration settings
|
|
// for Notifier
|
|
type Config struct {
|
|
// Scheme defines the HTTP scheme for Notifier address
|
|
Scheme string `yaml:"scheme,omitempty"`
|
|
// PathPrefix is added to URL path before adding alertManagerPath value
|
|
PathPrefix string `yaml:"path_prefix,omitempty"`
|
|
|
|
// ConsulSDConfigs contains list of settings for service discovery via Consul
|
|
// see https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config
|
|
ConsulSDConfigs []consul.SDConfig `yaml:"consul_sd_configs,omitempty"`
|
|
// DNSSDConfigs ontains list of settings for service discovery via DNS.
|
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config
|
|
DNSSDConfigs []dns.SDConfig `yaml:"dns_sd_configs,omitempty"`
|
|
|
|
// StaticConfigs contains list of static targets
|
|
StaticConfigs []StaticConfig `yaml:"static_configs,omitempty"`
|
|
|
|
// HTTPClientConfig contains HTTP configuration for Notifier clients
|
|
HTTPClientConfig promauth.HTTPClientConfig `yaml:",inline"`
|
|
// RelabelConfigs contains list of relabeling rules for entities discovered via SD
|
|
RelabelConfigs []promrelabel.RelabelConfig `yaml:"relabel_configs,omitempty"`
|
|
// AlertRelabelConfigs contains list of relabeling rules alert labels
|
|
AlertRelabelConfigs []promrelabel.RelabelConfig `yaml:"alert_relabel_configs,omitempty"`
|
|
// The timeout used when sending alerts.
|
|
Timeout *promutils.Duration `yaml:"timeout,omitempty"`
|
|
|
|
// Checksum stores the hash of yaml definition for the config.
|
|
// May be used to detect any changes to the config file.
|
|
Checksum string
|
|
|
|
// Catches all undefined fields and must be empty after parsing.
|
|
XXX map[string]interface{} `yaml:",inline"`
|
|
|
|
// This is set to the directory from where the config has been loaded.
|
|
baseDir string
|
|
|
|
// stores already parsed RelabelConfigs object
|
|
parsedRelabelConfigs *promrelabel.ParsedConfigs
|
|
// stores already parsed AlertRelabelConfigs object
|
|
parsedAlertRelabelConfigs *promrelabel.ParsedConfigs
|
|
}
|
|
|
|
// StaticConfig contains list of static targets in the following form:
|
|
//
|
|
// targets:
|
|
// [ - '<host>' ]
|
|
type StaticConfig struct {
|
|
Targets []string `yaml:"targets"`
|
|
// HTTPClientConfig contains HTTP configuration for the Targets
|
|
HTTPClientConfig promauth.HTTPClientConfig `yaml:",inline"`
|
|
}
|
|
|
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
|
func (cfg *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
type config Config
|
|
if err := unmarshal((*config)(cfg)); err != nil {
|
|
return err
|
|
}
|
|
if cfg.Scheme == "" {
|
|
cfg.Scheme = "http"
|
|
}
|
|
if cfg.Timeout.Duration() == 0 {
|
|
cfg.Timeout = promutils.NewDuration(time.Second * 10)
|
|
}
|
|
rCfg, err := promrelabel.ParseRelabelConfigs(cfg.RelabelConfigs, false)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse relabeling config: %w", err)
|
|
}
|
|
cfg.parsedRelabelConfigs = rCfg
|
|
arCfg, err := promrelabel.ParseRelabelConfigs(cfg.AlertRelabelConfigs, false)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse alert relabeling config: %w", err)
|
|
}
|
|
cfg.parsedAlertRelabelConfigs = arCfg
|
|
|
|
b, err := yaml.Marshal(cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal configuration for checksum: %w", err)
|
|
}
|
|
h := md5.New()
|
|
h.Write(b)
|
|
cfg.Checksum = fmt.Sprintf("%x", h.Sum(nil))
|
|
return nil
|
|
}
|
|
|
|
func parseConfig(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading config file: %w", err)
|
|
}
|
|
var cfg *Config
|
|
err = yaml.Unmarshal(data, &cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(cfg.XXX) > 0 {
|
|
var keys []string
|
|
for k := range cfg.XXX {
|
|
keys = append(keys, k)
|
|
}
|
|
return nil, fmt.Errorf("unknown fields in %s", strings.Join(keys, ", "))
|
|
}
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot obtain abs path for %q: %w", path, err)
|
|
}
|
|
cfg.baseDir = filepath.Dir(absPath)
|
|
return cfg, nil
|
|
}
|
|
|
|
func parseLabels(target string, metaLabels map[string]string, cfg *Config) (string, []prompbmarshal.Label, error) {
|
|
labels := mergeLabels(target, metaLabels, cfg)
|
|
labels = cfg.parsedRelabelConfigs.Apply(labels, 0)
|
|
labels = promrelabel.RemoveMetaLabels(labels[:0], labels)
|
|
promrelabel.SortLabels(labels)
|
|
// Remove references to already deleted labels, so GC could clean strings for label name and label value past len(labels).
|
|
// This should reduce memory usage when relabeling creates big number of temporary labels with long names and/or values.
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/825 for details.
|
|
labels = append([]prompbmarshal.Label{}, labels...)
|
|
|
|
if len(labels) == 0 {
|
|
return "", nil, nil
|
|
}
|
|
schemeRelabeled := promrelabel.GetLabelValueByName(labels, "__scheme__")
|
|
if len(schemeRelabeled) == 0 {
|
|
schemeRelabeled = "http"
|
|
}
|
|
addressRelabeled := promrelabel.GetLabelValueByName(labels, "__address__")
|
|
if len(addressRelabeled) == 0 {
|
|
return "", nil, nil
|
|
}
|
|
if strings.Contains(addressRelabeled, "/") {
|
|
return "", nil, nil
|
|
}
|
|
addressRelabeled = addMissingPort(schemeRelabeled, addressRelabeled)
|
|
alertsPathRelabeled := promrelabel.GetLabelValueByName(labels, "__alerts_path__")
|
|
if !strings.HasPrefix(alertsPathRelabeled, "/") {
|
|
alertsPathRelabeled = "/" + alertsPathRelabeled
|
|
}
|
|
u := fmt.Sprintf("%s://%s%s", schemeRelabeled, addressRelabeled, alertsPathRelabeled)
|
|
if _, err := url.Parse(u); err != nil {
|
|
return "", nil, fmt.Errorf("invalid url %q for scheme=%q (%q), target=%q, metrics_path=%q (%q): %w",
|
|
u, cfg.Scheme, schemeRelabeled, target, addressRelabeled, alertsPathRelabeled, err)
|
|
}
|
|
return u, labels, nil
|
|
}
|
|
|
|
func addMissingPort(scheme, target string) string {
|
|
if strings.Contains(target, ":") {
|
|
return target
|
|
}
|
|
if scheme == "https" {
|
|
target += ":443"
|
|
} else {
|
|
target += ":80"
|
|
}
|
|
return target
|
|
}
|
|
|
|
func mergeLabels(target string, metaLabels map[string]string, cfg *Config) []prompbmarshal.Label {
|
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config
|
|
m := make(map[string]string)
|
|
m["__address__"] = target
|
|
m["__scheme__"] = cfg.Scheme
|
|
m["__alerts_path__"] = path.Join("/", cfg.PathPrefix, alertManagerPath)
|
|
for k, v := range metaLabels {
|
|
m[k] = v
|
|
}
|
|
result := make([]prompbmarshal.Label, 0, len(m))
|
|
for k, v := range m {
|
|
result = append(result, prompbmarshal.Label{
|
|
Name: k,
|
|
Value: v,
|
|
})
|
|
}
|
|
return result
|
|
}
|