mirror of
https://github.com/prometheus/prometheus.git
synced 2024-09-19 23:37:31 -07:00
Changed the configuration allowing content-type and encoding preferences for both sending and receiving.
Signed-off-by: bwplotka <bwplotka@gmail.com>
This commit is contained in:
parent
7b88101cf5
commit
7322f421fd
|
@ -158,10 +158,8 @@ type flagConfig struct {
|
||||||
enableNewSDManager bool
|
enableNewSDManager bool
|
||||||
enablePerStepStats bool
|
enablePerStepStats bool
|
||||||
enableAutoGOMAXPROCS bool
|
enableAutoGOMAXPROCS bool
|
||||||
// todo: how to use the enable feature flag properly + use the remote format enum type
|
enableAutoGOMEMLIMIT bool
|
||||||
rwFormat int
|
enableConcurrentRuleEval bool
|
||||||
enableAutoGOMEMLIMIT bool
|
|
||||||
enableConcurrentRuleEval bool
|
|
||||||
|
|
||||||
prometheusURL string
|
prometheusURL string
|
||||||
corsRegexString string
|
corsRegexString string
|
||||||
|
@ -314,6 +312,12 @@ func main() {
|
||||||
a.Flag("web.enable-remote-write-receiver", "Enable API endpoint accepting remote write requests.").
|
a.Flag("web.enable-remote-write-receiver", "Enable API endpoint accepting remote write requests.").
|
||||||
Default("false").BoolVar(&cfg.web.EnableRemoteWriteReceiver)
|
Default("false").BoolVar(&cfg.web.EnableRemoteWriteReceiver)
|
||||||
|
|
||||||
|
a.Flag("web.remote-write-receiver.protobuf-types", fmt.Sprintf("List of accepted remote write 2.0 content types to advertise to senders, ordered by the preference. Note that the final decision is on the sender. Supported list values: %v", config.DefaultRemoteWriteProtoTypes.String())).
|
||||||
|
Default(config.DefaultRemoteWriteProtoTypes.Strings()...).SetValue(rwProtoTypeFlagValue(&cfg.web.RemoteWriteReceiverProtoTypes))
|
||||||
|
|
||||||
|
a.Flag("web.remote-write-receiver.compressions", fmt.Sprintf("List of accepted remote write 2.0 content encodings (compressions) to advertise to senders, ordered by the preference. Note that the final decision is on the sender. Supported list values: %v", config.DefaultRemoteWriteCompressions.String())).
|
||||||
|
Default(config.DefaultRemoteWriteCompressions.Strings()...).SetValue(rwCompressionFlagValue(&cfg.web.RemoteWriteReceiverCompressions))
|
||||||
|
|
||||||
a.Flag("web.console.templates", "Path to the console template directory, available at /consoles.").
|
a.Flag("web.console.templates", "Path to the console template directory, available at /consoles.").
|
||||||
Default("consoles").StringVar(&cfg.web.ConsoleTemplatesPath)
|
Default("consoles").StringVar(&cfg.web.ConsoleTemplatesPath)
|
||||||
|
|
||||||
|
@ -455,9 +459,6 @@ func main() {
|
||||||
a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver, created-timestamp-zero-ingestion, concurrent-rule-eval. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
|
a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver, created-timestamp-zero-ingestion, concurrent-rule-eval. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
|
||||||
Default("").StringsVar(&cfg.featureList)
|
Default("").StringsVar(&cfg.featureList)
|
||||||
|
|
||||||
a.Flag("remote-write-format", "remote write proto format to use, valid options: 0 (1.0), 1 (reduced format), 3 (min64 format)").
|
|
||||||
Default("0").IntVar(&cfg.rwFormat)
|
|
||||||
|
|
||||||
promlogflag.AddFlags(a, &cfg.promlogConfig)
|
promlogflag.AddFlags(a, &cfg.promlogConfig)
|
||||||
|
|
||||||
a.Flag("write-documentation", "Generate command line documentation. Internal use.").Hidden().Action(func(ctx *kingpin.ParseContext) error {
|
a.Flag("write-documentation", "Generate command line documentation. Internal use.").Hidden().Action(func(ctx *kingpin.ParseContext) error {
|
||||||
|
@ -820,7 +821,6 @@ func main() {
|
||||||
cfg.web.Flags[f.Name] = f.Value.String()
|
cfg.web.Flags[f.Name] = f.Value.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.web.RemoteWriteFormat = config.RemoteWriteFormat(cfg.rwFormat)
|
|
||||||
// Depends on cfg.web.ScrapeManager so needs to be after cfg.web.ScrapeManager = scrapeManager.
|
// Depends on cfg.web.ScrapeManager so needs to be after cfg.web.ScrapeManager = scrapeManager.
|
||||||
webHandler := web.New(log.With(logger, "component", "web"), &cfg.web)
|
webHandler := web.New(log.With(logger, "component", "web"), &cfg.web)
|
||||||
|
|
||||||
|
@ -1737,3 +1737,63 @@ type discoveryManager interface {
|
||||||
Run() error
|
Run() error
|
||||||
SyncCh() <-chan map[string][]*targetgroup.Group
|
SyncCh() <-chan map[string][]*targetgroup.Group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(bwplotka): Add unit test.
|
||||||
|
type rwProtoTypeFlagParser struct {
|
||||||
|
types *[]config.RemoteWriteProtoType
|
||||||
|
}
|
||||||
|
|
||||||
|
func rwProtoTypeFlagValue(types *[]config.RemoteWriteProtoType) kingpin.Value {
|
||||||
|
return &rwProtoTypeFlagParser{types: types}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *rwProtoTypeFlagParser) IsCumulative() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *rwProtoTypeFlagParser) String() string {
|
||||||
|
ss := make([]string, 0, len(*p.types))
|
||||||
|
for _, t := range *p.types {
|
||||||
|
ss = append(ss, string(t))
|
||||||
|
}
|
||||||
|
return strings.Join(ss, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *rwProtoTypeFlagParser) Set(opt string) error {
|
||||||
|
t := config.RemoteWriteProtoType(opt)
|
||||||
|
if err := t.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p.types = append(*p.types, t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(bwplotka): Add unit test.
|
||||||
|
type rwCompressionFlagParser struct {
|
||||||
|
types *[]config.RemoteWriteCompression
|
||||||
|
}
|
||||||
|
|
||||||
|
func rwCompressionFlagValue(types *[]config.RemoteWriteCompression) kingpin.Value {
|
||||||
|
return &rwCompressionFlagParser{types: types}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *rwCompressionFlagParser) IsCumulative() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *rwCompressionFlagParser) String() string {
|
||||||
|
ss := make([]string, 0, len(*p.types))
|
||||||
|
for _, t := range *p.types {
|
||||||
|
ss = append(ss, string(t))
|
||||||
|
}
|
||||||
|
return strings.Join(ss, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *rwCompressionFlagParser) Set(opt string) error {
|
||||||
|
t := config.RemoteWriteCompression(opt)
|
||||||
|
if err := t.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p.types = append(*p.types, t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -172,7 +172,10 @@ var (
|
||||||
|
|
||||||
// DefaultRemoteWriteConfig is the default remote write configuration.
|
// DefaultRemoteWriteConfig is the default remote write configuration.
|
||||||
DefaultRemoteWriteConfig = RemoteWriteConfig{
|
DefaultRemoteWriteConfig = RemoteWriteConfig{
|
||||||
RemoteTimeout: model.Duration(30 * time.Second),
|
RemoteTimeout: model.Duration(30 * time.Second),
|
||||||
|
ProtobufTypes: DefaultRemoteWriteProtoTypes,
|
||||||
|
Compressions: DefaultRemoteWriteCompressions,
|
||||||
|
|
||||||
QueueConfig: DefaultQueueConfig,
|
QueueConfig: DefaultQueueConfig,
|
||||||
MetadataConfig: DefaultMetadataConfig,
|
MetadataConfig: DefaultMetadataConfig,
|
||||||
HTTPClientConfig: config.DefaultHTTPClientConfig,
|
HTTPClientConfig: config.DefaultHTTPClientConfig,
|
||||||
|
@ -1025,22 +1028,18 @@ func CheckTargetAddress(address model.LabelValue) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This needs to live here rather than in the remote package to avoid an import cycle.
|
|
||||||
type RemoteWriteFormat int64
|
|
||||||
|
|
||||||
// RemoteWriteConfig is the configuration for writing to remote storage.
|
// RemoteWriteConfig is the configuration for writing to remote storage.
|
||||||
type RemoteWriteConfig struct {
|
type RemoteWriteConfig struct {
|
||||||
URL *config.URL `yaml:"url"`
|
URL *config.URL `yaml:"url"`
|
||||||
RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"`
|
RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"`
|
||||||
Headers map[string]string `yaml:"headers,omitempty"`
|
Headers map[string]string `yaml:"headers,omitempty"`
|
||||||
WriteRelabelConfigs []*relabel.Config `yaml:"write_relabel_configs,omitempty"`
|
WriteRelabelConfigs []*relabel.Config `yaml:"write_relabel_configs,omitempty"`
|
||||||
Name string `yaml:"name,omitempty"`
|
Name string `yaml:"name,omitempty"`
|
||||||
SendExemplars bool `yaml:"send_exemplars,omitempty"`
|
SendExemplars bool `yaml:"send_exemplars,omitempty"`
|
||||||
SendNativeHistograms bool `yaml:"send_native_histograms,omitempty"`
|
SendNativeHistograms bool `yaml:"send_native_histograms,omitempty"`
|
||||||
ProtocolVersion RemoteWriteFormat `yaml:"remote_write_version,omitempty"`
|
ProtobufTypes []RemoteWriteProtoType `yaml:"proto_types,omitempty"`
|
||||||
|
Compressions []RemoteWriteCompression `yaml:"compressions,omitempty"`
|
||||||
|
|
||||||
// We cannot do proper Go type embedding below as the parser will then parse
|
|
||||||
// values arbitrarily into the overflow maps of further-down types.
|
|
||||||
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
|
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
|
||||||
QueueConfig QueueConfig `yaml:"queue_config,omitempty"`
|
QueueConfig QueueConfig `yaml:"queue_config,omitempty"`
|
||||||
MetadataConfig MetadataConfig `yaml:"metadata_config,omitempty"`
|
MetadataConfig MetadataConfig `yaml:"metadata_config,omitempty"`
|
||||||
|
@ -1072,6 +1071,20 @@ func (c *RemoteWriteConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.ProtobufTypes == nil {
|
||||||
|
c.ProtobufTypes = DefaultRemoteWriteProtoTypes
|
||||||
|
}
|
||||||
|
if err := validateRemoteWriteProtoTypes(c.ProtobufTypes); err != nil {
|
||||||
|
return fmt.Errorf("invalid protobuf_types value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Compressions == nil {
|
||||||
|
c.Compressions = DefaultRemoteWriteCompressions
|
||||||
|
}
|
||||||
|
if err := validateRemoteWriteCompressions(c.Compressions); err != nil {
|
||||||
|
return fmt.Errorf("invalid compressions value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
|
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
|
||||||
// We cannot make it a pointer as the parser panics for inlined pointer structs.
|
// We cannot make it a pointer as the parser panics for inlined pointer structs.
|
||||||
// Thus we just do its validation here.
|
// Thus we just do its validation here.
|
||||||
|
|
162
config/config_remote_write.go
Normal file
162
config/config_remote_write.go
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(bwplotka): Consider an util for "preference enums" as it's similar code for rw compression, proto type and scrape protocol.
|
||||||
|
|
||||||
|
// RemoteWriteProtoType represents the supported protobuf types for the remote write.
|
||||||
|
type RemoteWriteProtoType string
|
||||||
|
|
||||||
|
// Validate returns error if the given protobuf type is not supported.
|
||||||
|
func (s RemoteWriteProtoType) Validate() error {
|
||||||
|
if _, ok := RemoteWriteContentTypeHeaders[s]; !ok {
|
||||||
|
return fmt.Errorf("unknown remote write protobuf type %v, supported: %v",
|
||||||
|
s, func() (ret []string) {
|
||||||
|
for k := range RemoteWriteContentTypeHeaders {
|
||||||
|
ret = append(ret, string(k))
|
||||||
|
}
|
||||||
|
sort.Strings(ret)
|
||||||
|
return ret
|
||||||
|
}())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoteWriteProtoTypes []RemoteWriteProtoType
|
||||||
|
|
||||||
|
func (t RemoteWriteProtoTypes) Strings() []string {
|
||||||
|
ret := make([]string, 0, len(t))
|
||||||
|
for _, typ := range t {
|
||||||
|
ret = append(ret, string(typ))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t RemoteWriteProtoTypes) String() string {
|
||||||
|
return strings.Join(t.Strings(), ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerAcceptHeaderValue returns server Accept header value for
|
||||||
|
// given list of proto types as per RFC 9110 https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.1-14
|
||||||
|
func (t RemoteWriteProtoTypes) ServerAcceptHeaderValue() string {
|
||||||
|
// TODO(bwplotka): Consider implementing an optional quality factor.
|
||||||
|
ret := make([]string, 0, len(t))
|
||||||
|
for _, typ := range t {
|
||||||
|
ret = append(ret, RemoteWriteContentTypeHeaders[typ])
|
||||||
|
}
|
||||||
|
return strings.Join(ret, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
RemoteWriteProtoTypeV1 RemoteWriteProtoType = "v1.WriteRequest"
|
||||||
|
RemoteWriteProtoTypeV2 RemoteWriteProtoType = "v2.WriteRequest"
|
||||||
|
RemoteWriteContentTypeHeaders = map[RemoteWriteProtoType]string{
|
||||||
|
RemoteWriteProtoTypeV1: "application/x-protobuf", // Also application/x-protobuf; proto=prometheus.WriteRequest but simplified for compatibility with 1.x spec.
|
||||||
|
RemoteWriteProtoTypeV2: "application/x-protobuf;proto=io.prometheus.remote.write.v2.WriteRequest",
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultRemoteWriteProtoTypes is the set of remote write protobuf types that will be
|
||||||
|
// preferred by the remote write client.
|
||||||
|
DefaultRemoteWriteProtoTypes = RemoteWriteProtoTypes{
|
||||||
|
RemoteWriteProtoTypeV1,
|
||||||
|
RemoteWriteProtoTypeV2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// validateRemoteWriteProtoTypes return errors if we see problems with rw protobuf types in
|
||||||
|
// the Prometheus configuration.
|
||||||
|
func validateRemoteWriteProtoTypes(ts []RemoteWriteProtoType) error {
|
||||||
|
if len(ts) == 0 {
|
||||||
|
return errors.New("protobuf_types cannot be empty")
|
||||||
|
}
|
||||||
|
dups := map[string]struct{}{}
|
||||||
|
for _, t := range ts {
|
||||||
|
if _, ok := dups[strings.ToLower(string(t))]; ok {
|
||||||
|
return fmt.Errorf("duplicated protobuf types in protobuf_types, got %v", ts)
|
||||||
|
}
|
||||||
|
if err := t.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("protobuf_types: %w", err)
|
||||||
|
}
|
||||||
|
dups[strings.ToLower(string(t))] = struct{}{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteWriteCompression represents the supported compressions for the remote write.
|
||||||
|
type RemoteWriteCompression string
|
||||||
|
|
||||||
|
// Validate returns error if the given protobuf type is not supported.
|
||||||
|
func (s RemoteWriteCompression) Validate() error {
|
||||||
|
if _, ok := RemoteWriteContentEncodingHeaders[s]; !ok {
|
||||||
|
return fmt.Errorf("unknown remote write protobuf type %v, supported: %v",
|
||||||
|
s, func() (ret []string) {
|
||||||
|
for k := range RemoteWriteContentEncodingHeaders {
|
||||||
|
ret = append(ret, string(k))
|
||||||
|
}
|
||||||
|
sort.Strings(ret)
|
||||||
|
return ret
|
||||||
|
}())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoteWriteCompressions []RemoteWriteCompression
|
||||||
|
|
||||||
|
func (cs RemoteWriteCompressions) Strings() []string {
|
||||||
|
ret := make([]string, 0, len(cs))
|
||||||
|
for _, c := range cs {
|
||||||
|
ret = append(ret, string(c))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs RemoteWriteCompressions) String() string {
|
||||||
|
return strings.Join(cs.Strings(), ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerAcceptEncodingHeaderValue returns server Accept-Encoding header value for
|
||||||
|
// given list of compressions as per RFC 9110 https://www.rfc-editor.org/rfc/rfc9110.html#name-accept-encoding
|
||||||
|
func (cs RemoteWriteCompressions) ServerAcceptEncodingHeaderValue() string {
|
||||||
|
// TODO(bwplotka): Consider implementing an optional quality factor.
|
||||||
|
ret := make([]string, 0, len(cs))
|
||||||
|
for _, typ := range cs {
|
||||||
|
ret = append(ret, RemoteWriteContentEncodingHeaders[typ])
|
||||||
|
}
|
||||||
|
return strings.Join(ret, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateRemoteWriteCompressions return errors if we see problems with rw compressions in
|
||||||
|
// the Prometheus configuration.
|
||||||
|
func validateRemoteWriteCompressions(cs []RemoteWriteCompression) error {
|
||||||
|
if len(cs) == 0 {
|
||||||
|
return errors.New("compressions cannot be empty")
|
||||||
|
}
|
||||||
|
dups := map[string]struct{}{}
|
||||||
|
for _, c := range cs {
|
||||||
|
if _, ok := dups[strings.ToLower(string(c))]; ok {
|
||||||
|
return fmt.Errorf("duplicated compression in compressions, got %v", cs)
|
||||||
|
}
|
||||||
|
if err := c.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("compressions: %w", err)
|
||||||
|
}
|
||||||
|
dups[strings.ToLower(string(c))] = struct{}{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
RemoteWriteCompressionSnappy RemoteWriteCompression = "snappy"
|
||||||
|
|
||||||
|
RemoteWriteContentEncodingHeaders = map[RemoteWriteCompression]string{
|
||||||
|
RemoteWriteCompressionSnappy: "snappy",
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultRemoteWriteCompressions = RemoteWriteCompressions{
|
||||||
|
RemoteWriteCompressionSnappy,
|
||||||
|
}
|
||||||
|
)
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-kit/log"
|
"github.com/go-kit/log"
|
||||||
"github.com/go-kit/log/level"
|
"github.com/go-kit/log/level"
|
||||||
|
@ -38,89 +37,31 @@ import (
|
||||||
otlptranslator "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheusremotewrite"
|
otlptranslator "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheusremotewrite"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
RemoteWriteVersionHeader = "X-Prometheus-Remote-Write-Version"
|
|
||||||
RemoteWriteVersion1HeaderValue = "0.1.0"
|
|
||||||
RemoteWriteVersion20HeaderValue = "2.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
func rwHeaderNameValues(rwFormat config.RemoteWriteFormat) map[string]string {
|
|
||||||
// Return the correct remote write header name/values based on provided rwFormat.
|
|
||||||
ret := make(map[string]string, 1)
|
|
||||||
|
|
||||||
switch rwFormat {
|
|
||||||
case Version1:
|
|
||||||
ret[RemoteWriteVersionHeader] = RemoteWriteVersion1HeaderValue
|
|
||||||
case Version2:
|
|
||||||
// We need to add the supported protocol definitions in order:
|
|
||||||
tuples := make([]string, 0, 2)
|
|
||||||
// Add "2.0;snappy".
|
|
||||||
tuples = append(tuples, RemoteWriteVersion20HeaderValue+";snappy")
|
|
||||||
// Add default "0.1.0".
|
|
||||||
tuples = append(tuples, RemoteWriteVersion1HeaderValue)
|
|
||||||
ret[RemoteWriteVersionHeader] = strings.Join(tuples, ",")
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeHeadHandler struct {
|
|
||||||
logger log.Logger
|
|
||||||
|
|
||||||
remoteWriteHeadRequests prometheus.Counter
|
|
||||||
|
|
||||||
// Experimental feature, new remote write proto format.
|
|
||||||
// The handler will accept the new format, but it can still accept the old one.
|
|
||||||
rwFormat config.RemoteWriteFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWriteHeadHandler(logger log.Logger, reg prometheus.Registerer, rwFormat config.RemoteWriteFormat) http.Handler {
|
|
||||||
h := &writeHeadHandler{
|
|
||||||
logger: logger,
|
|
||||||
rwFormat: rwFormat,
|
|
||||||
remoteWriteHeadRequests: prometheus.NewCounter(prometheus.CounterOpts{
|
|
||||||
Namespace: "prometheus",
|
|
||||||
Subsystem: "api",
|
|
||||||
Name: "remote_write_head_requests",
|
|
||||||
Help: "The number of remote write HEAD requests.",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
if reg != nil {
|
|
||||||
reg.MustRegister(h.remoteWriteHeadRequests)
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a response to the HEAD request based on the format supported.
|
|
||||||
func (h *writeHeadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Add appropriate header values for the specific rwFormat.
|
|
||||||
for hName, hValue := range rwHeaderNameValues(h.rwFormat) {
|
|
||||||
w.Header().Set(hName, hValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment counter
|
|
||||||
h.remoteWriteHeadRequests.Inc()
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
type writeHandler struct {
|
type writeHandler struct {
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
appendable storage.Appendable
|
appendable storage.Appendable
|
||||||
|
|
||||||
samplesWithInvalidLabelsTotal prometheus.Counter
|
samplesWithInvalidLabelsTotal prometheus.Counter
|
||||||
|
|
||||||
// Experimental feature, new remote write proto format.
|
// PRW 2.0 definies backward compatible content negotiation across prototypes
|
||||||
// The handler will accept the new format, but it can still accept the old one.
|
// and compressions.
|
||||||
rwFormat config.RemoteWriteFormat
|
protoTypes []config.RemoteWriteProtoType
|
||||||
|
acceptHeaderValue string
|
||||||
|
compressions []config.RemoteWriteCompression
|
||||||
|
acceptEncodingHeaderValue string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWriteHandler creates a http.Handler that accepts remote write requests and
|
// NewWriteHandler creates a http.Handler that accepts remote write requests and
|
||||||
// writes them to the provided appendable.
|
// writes them to the provided appendable.
|
||||||
func NewWriteHandler(logger log.Logger, reg prometheus.Registerer, appendable storage.Appendable, rwFormat config.RemoteWriteFormat) http.Handler {
|
// Compatible with both 1.x and 2.0 spec.
|
||||||
|
func NewWriteHandler(logger log.Logger, reg prometheus.Registerer, appendable storage.Appendable, protoTypes []config.RemoteWriteProtoType, compressions []config.RemoteWriteCompression) http.Handler {
|
||||||
h := &writeHandler{
|
h := &writeHandler{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
appendable: appendable,
|
appendable: appendable,
|
||||||
rwFormat: rwFormat,
|
protoTypes: protoTypes,
|
||||||
|
acceptHeaderValue: config.RemoteWriteProtoTypes(protoTypes).ServerAcceptHeaderValue(),
|
||||||
|
compressions: compressions,
|
||||||
|
acceptEncodingHeaderValue: config.RemoteWriteCompressions(compressions).ServerAcceptEncodingHeaderValue(),
|
||||||
samplesWithInvalidLabelsTotal: prometheus.NewCounter(prometheus.CounterOpts{
|
samplesWithInvalidLabelsTotal: prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
Namespace: "prometheus",
|
Namespace: "prometheus",
|
||||||
Subsystem: "api",
|
Subsystem: "api",
|
||||||
|
@ -134,35 +75,59 @@ func NewWriteHandler(logger log.Logger, reg prometheus.Registerer, appendable st
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *writeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *writeHandler) parseEncoding(contentEncoding string) (config.RemoteWriteCompression, error) {
|
||||||
var err error
|
// TODO(bwplotka): TBD
|
||||||
|
got := config.RemoteWriteCompression(contentEncoding)
|
||||||
// Set the header(s) in the response based on the rwFormat the server supports.
|
if err := got.Validate(); err != nil {
|
||||||
for hName, hValue := range rwHeaderNameValues(h.rwFormat) {
|
return "", err // TODO(bwplotka): Wrap properly
|
||||||
w.Header().Set(hName, hValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the headers to work out how to handle this.
|
// TODO(bwplotka): Build utils for this.
|
||||||
contentEncoding := r.Header.Get("Content-Encoding")
|
for _, c := range h.compressions {
|
||||||
protoVer := r.Header.Get(RemoteWriteVersionHeader)
|
if c == got {
|
||||||
|
return got, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("unsupported compression, got %v, supported %v", got, h.acceptEncodingHeaderValue)
|
||||||
|
}
|
||||||
|
|
||||||
switch protoVer {
|
func (h *writeHandler) parseType(contentType string) (config.RemoteWriteProtoType, error) {
|
||||||
case "":
|
// TODO(bwplotka): TBD
|
||||||
// No header provided, assume 0.1.0 as everything that relies on later.
|
got := config.RemoteWriteProtoType(contentType)
|
||||||
protoVer = RemoteWriteVersion1HeaderValue
|
if err := got.Validate(); err != nil {
|
||||||
case RemoteWriteVersion1HeaderValue, RemoteWriteVersion20HeaderValue:
|
return "", err // TODO(bwplotka): Wrap properly
|
||||||
// We know this header, woo.
|
}
|
||||||
default:
|
|
||||||
// We have a version in the header but it is not one we recognise.
|
// TODO(bwplotka): Build utils for this.
|
||||||
level.Error(h.logger).Log("msg", "Error decoding remote write request", "err", "Unknown remote write version in headers", "ver", protoVer)
|
for _, c := range h.protoTypes {
|
||||||
// Return a 406 so that the client can choose a more appropriate protocol to use.
|
if c == got {
|
||||||
http.Error(w, "Unknown remote write version in headers", http.StatusNotAcceptable)
|
return got, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("unsupported proto type, got %v, supported %v", got, h.acceptHeaderValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *writeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Start with ensuring receiver response headers indicate PRW 2.0 and accepted
|
||||||
|
// content type and compressions.
|
||||||
|
w.Header().Set("Accept", h.acceptHeaderValue)
|
||||||
|
w.Header().Set("Accept-Encoding", h.acceptEncodingHeaderValue)
|
||||||
|
|
||||||
|
// Initial content type and encoding negotiation.
|
||||||
|
// NOTE: PRW 2.0 deprecated X-Prometheus-Remote-Write-Version, ignore it, we
|
||||||
|
// rely on content-type header only.
|
||||||
|
enc, err := h.parseEncoding(r.Header.Get("Content-Encoding"))
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||||
|
w.Write([]byte(err.Error())) // TODO(bwplotka) Format? Log?
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
typ, err := h.parseType(r.Header.Get("Content-Type"))
|
||||||
// Deal with 0.1.0 clients that forget to send Content-Encoding.
|
if err != nil {
|
||||||
if protoVer == RemoteWriteVersion1HeaderValue && contentEncoding == "" {
|
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||||
contentEncoding = "snappy"
|
w.Write([]byte(err.Error())) // TODO(bwplotka) Format? Log? use http.Error
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the request body.
|
// Read the request body.
|
||||||
|
@ -173,11 +138,9 @@ func (h *writeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deal with contentEncoding first.
|
|
||||||
var decompressed []byte
|
var decompressed []byte
|
||||||
|
switch enc {
|
||||||
switch contentEncoding {
|
case config.RemoteWriteCompressionSnappy:
|
||||||
case "snappy":
|
|
||||||
decompressed, err = snappy.Decode(nil, body)
|
decompressed, err = snappy.Decode(nil, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
level.Error(h.logger).Log("msg", "Error decoding remote write request", "err", err.Error())
|
level.Error(h.logger).Log("msg", "Error decoding remote write request", "err", err.Error())
|
||||||
|
@ -185,16 +148,11 @@ func (h *writeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
level.Error(h.logger).Log("msg", "Error decoding remote write request", "err", "Unsupported Content-Encoding", "contentEncoding", contentEncoding)
|
panic("should not happen")
|
||||||
// Return a 406 so that the client can choose a more appropriate protocol to use.
|
|
||||||
http.Error(w, "Unsupported Content-Encoding", http.StatusNotAcceptable)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we have a decompressed buffer we can unmarshal it.
|
switch typ {
|
||||||
// At this point we are happy with the version but need to check the encoding.
|
case config.RemoteWriteProtoTypeV1:
|
||||||
switch protoVer {
|
|
||||||
case RemoteWriteVersion1HeaderValue:
|
|
||||||
var req prompb.WriteRequest
|
var req prompb.WriteRequest
|
||||||
if err := proto.Unmarshal(decompressed, &req); err != nil {
|
if err := proto.Unmarshal(decompressed, &req); err != nil {
|
||||||
level.Error(h.logger).Log("msg", "Error decoding remote write request", "err", err.Error())
|
level.Error(h.logger).Log("msg", "Error decoding remote write request", "err", err.Error())
|
||||||
|
@ -202,7 +160,7 @@ func (h *writeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = h.write(r.Context(), &req)
|
err = h.write(r.Context(), &req)
|
||||||
case RemoteWriteVersion20HeaderValue:
|
case config.RemoteWriteProtoTypeV2:
|
||||||
// 2.0 request.
|
// 2.0 request.
|
||||||
var reqMinStr writev2.WriteRequest
|
var reqMinStr writev2.WriteRequest
|
||||||
if err := proto.Unmarshal(decompressed, &reqMinStr); err != nil {
|
if err := proto.Unmarshal(decompressed, &reqMinStr); err != nil {
|
||||||
|
@ -211,6 +169,8 @@ func (h *writeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = h.writeMinStr(r.Context(), &reqMinStr)
|
err = h.writeMinStr(r.Context(), &reqMinStr)
|
||||||
|
default:
|
||||||
|
panic("should not happen")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
|
@ -210,10 +210,9 @@ type API struct {
|
||||||
isAgent bool
|
isAgent bool
|
||||||
statsRenderer StatsRenderer
|
statsRenderer StatsRenderer
|
||||||
|
|
||||||
remoteWriteHeadHandler http.Handler
|
remoteWriteHandler http.Handler
|
||||||
remoteWriteHandler http.Handler
|
remoteReadHandler http.Handler
|
||||||
remoteReadHandler http.Handler
|
otlpWriteHandler http.Handler
|
||||||
otlpWriteHandler http.Handler
|
|
||||||
|
|
||||||
codecs []Codec
|
codecs []Codec
|
||||||
}
|
}
|
||||||
|
@ -247,7 +246,8 @@ func NewAPI(
|
||||||
registerer prometheus.Registerer,
|
registerer prometheus.Registerer,
|
||||||
statsRenderer StatsRenderer,
|
statsRenderer StatsRenderer,
|
||||||
rwEnabled bool,
|
rwEnabled bool,
|
||||||
rwFormat config.RemoteWriteFormat,
|
rwProtoTypes []config.RemoteWriteProtoType,
|
||||||
|
rwCompressions []config.RemoteWriteCompression,
|
||||||
otlpEnabled bool,
|
otlpEnabled bool,
|
||||||
) *API {
|
) *API {
|
||||||
a := &API{
|
a := &API{
|
||||||
|
@ -290,20 +290,7 @@ func NewAPI(
|
||||||
}
|
}
|
||||||
|
|
||||||
if rwEnabled {
|
if rwEnabled {
|
||||||
// TODO(alexg) - Two phase rwFormat rollout needs to create handlers with flag for advertising.
|
a.remoteWriteHandler = remote.NewWriteHandler(logger, registerer, ap, rwProtoTypes, rwCompressions)
|
||||||
// For rollout we do two phases:
|
|
||||||
// 0. (Before) no flags set
|
|
||||||
// 1. (During) support new protocols but don't advertise
|
|
||||||
// <wait until all servers have rolled out and now support RW2.0>
|
|
||||||
// 2. (After) support new protocols and advertise
|
|
||||||
//
|
|
||||||
// For rollback the two phases are:
|
|
||||||
// 0. (Before) support new protocols and advertise
|
|
||||||
// 1. (During) support new protocols but don't advertise
|
|
||||||
// <wait a suitable period for all sending clients to be aware that receiving servers no longer support 2.0>
|
|
||||||
// 2. (After) no flags set
|
|
||||||
a.remoteWriteHandler = remote.NewWriteHandler(logger, registerer, ap, rwFormat)
|
|
||||||
a.remoteWriteHeadHandler = remote.NewWriteHeadHandler(logger, registerer, rwFormat)
|
|
||||||
}
|
}
|
||||||
if otlpEnabled {
|
if otlpEnabled {
|
||||||
a.otlpWriteHandler = remote.NewOTLPWriteHandler(logger, ap)
|
a.otlpWriteHandler = remote.NewOTLPWriteHandler(logger, ap)
|
||||||
|
@ -400,7 +387,6 @@ func (api *API) Register(r *route.Router) {
|
||||||
r.Get("/status/walreplay", api.serveWALReplayStatus)
|
r.Get("/status/walreplay", api.serveWALReplayStatus)
|
||||||
r.Post("/read", api.ready(api.remoteRead))
|
r.Post("/read", api.ready(api.remoteRead))
|
||||||
r.Post("/write", api.ready(api.remoteWrite))
|
r.Post("/write", api.ready(api.remoteWrite))
|
||||||
r.Head("/write", api.remoteWriteHead)
|
|
||||||
r.Post("/otlp/v1/metrics", api.ready(api.otlpWrite))
|
r.Post("/otlp/v1/metrics", api.ready(api.otlpWrite))
|
||||||
|
|
||||||
r.Get("/alerts", wrapAgent(api.alerts))
|
r.Get("/alerts", wrapAgent(api.alerts))
|
||||||
|
@ -1661,14 +1647,6 @@ func (api *API) remoteRead(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) remoteWriteHead(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if api.remoteWriteHeadHandler != nil {
|
|
||||||
api.remoteWriteHeadHandler.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
http.Error(w, "remote write receiver needs to be enabled with --web.enable-remote-write-receiver", http.StatusNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) remoteWrite(w http.ResponseWriter, r *http.Request) {
|
func (api *API) remoteWrite(w http.ResponseWriter, r *http.Request) {
|
||||||
if api.remoteWriteHandler != nil {
|
if api.remoteWriteHandler != nil {
|
||||||
api.remoteWriteHandler.ServeHTTP(w, r)
|
api.remoteWriteHandler.ServeHTTP(w, r)
|
||||||
|
|
47
web/web.go
47
web/web.go
|
@ -244,28 +244,28 @@ type Options struct {
|
||||||
Version *PrometheusVersion
|
Version *PrometheusVersion
|
||||||
Flags map[string]string
|
Flags map[string]string
|
||||||
|
|
||||||
ListenAddress string
|
ListenAddress string
|
||||||
CORSOrigin *regexp.Regexp
|
CORSOrigin *regexp.Regexp
|
||||||
ReadTimeout time.Duration
|
ReadTimeout time.Duration
|
||||||
MaxConnections int
|
MaxConnections int
|
||||||
ExternalURL *url.URL
|
ExternalURL *url.URL
|
||||||
RoutePrefix string
|
RoutePrefix string
|
||||||
UseLocalAssets bool
|
UseLocalAssets bool
|
||||||
UserAssetsPath string
|
UserAssetsPath string
|
||||||
ConsoleTemplatesPath string
|
ConsoleTemplatesPath string
|
||||||
ConsoleLibrariesPath string
|
ConsoleLibrariesPath string
|
||||||
EnableLifecycle bool
|
EnableLifecycle bool
|
||||||
EnableAdminAPI bool
|
EnableAdminAPI bool
|
||||||
PageTitle string
|
PageTitle string
|
||||||
RemoteReadSampleLimit int
|
RemoteReadSampleLimit int
|
||||||
RemoteReadConcurrencyLimit int
|
RemoteReadConcurrencyLimit int
|
||||||
RemoteReadBytesInFrame int
|
RemoteReadBytesInFrame int
|
||||||
EnableRemoteWriteReceiver bool
|
EnableRemoteWriteReceiver bool
|
||||||
EnableOTLPWriteReceiver bool
|
RemoteWriteReceiverProtoTypes []config.RemoteWriteProtoType
|
||||||
IsAgent bool
|
RemoteWriteReceiverCompressions []config.RemoteWriteCompression
|
||||||
AppName string
|
EnableOTLPWriteReceiver bool
|
||||||
// TODO(cstyan): should this change to a list of tuples, maybe via the content negotiation PR?
|
IsAgent bool
|
||||||
RemoteWriteFormat config.RemoteWriteFormat
|
AppName string
|
||||||
|
|
||||||
Gatherer prometheus.Gatherer
|
Gatherer prometheus.Gatherer
|
||||||
Registerer prometheus.Registerer
|
Registerer prometheus.Registerer
|
||||||
|
@ -355,7 +355,8 @@ func New(logger log.Logger, o *Options) *Handler {
|
||||||
o.Registerer,
|
o.Registerer,
|
||||||
nil,
|
nil,
|
||||||
o.EnableRemoteWriteReceiver,
|
o.EnableRemoteWriteReceiver,
|
||||||
o.RemoteWriteFormat,
|
o.RemoteWriteReceiverProtoTypes,
|
||||||
|
o.RemoteWriteReceiverCompressions,
|
||||||
o.EnableOTLPWriteReceiver,
|
o.EnableOTLPWriteReceiver,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue