From aef3f26ad3d60df5cccdc7dfaa75005e9bddf89f Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 10 Mar 2021 14:00:51 +0100 Subject: [PATCH] Add config options for metrics --- metrics/api.go | 3 ++- metrics/config.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++ metrics/metric.go | 10 +++++-- metrics/module.go | 23 +++++++--------- 4 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 metrics/config.go diff --git a/metrics/api.go b/metrics/api.go index 030b0c0..e206416 100644 --- a/metrics/api.go +++ b/metrics/api.go @@ -90,7 +90,7 @@ func writeMetricsTo(ctx context.Context, url string) error { } // Create request - req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, buf) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, buf) if err != nil { return fmt.Errorf("failed to create request: %w", err) } @@ -118,6 +118,7 @@ func writeMetricsTo(ctx context.Context, url string) error { } func metricsWriter(ctx context.Context) error { + pushURL := pushOption() ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() diff --git a/metrics/config.go b/metrics/config.go new file mode 100644 index 0000000..9767d76 --- /dev/null +++ b/metrics/config.go @@ -0,0 +1,69 @@ +package metrics + +import ( + "flag" + + "github.com/safing/portbase/config" +) + +// Configuration Keys +var ( + CfgOptionInstanceKey = "core/metrics/instance" + instanceOption config.StringOption + cfgOptionInstanceOrder = 0 + + CfgOptionPushKey = "core/metrics/push" + pushOption config.StringOption + cfgOptionPushOrder = 0 + + pushFlag string + instanceFlag string +) + +func init() { + flag.StringVar(&pushFlag, "push-metrics", "", "Set default URL to push prometheus metrics to.") + flag.StringVar(&instanceFlag, "metrics-instance", "", "Set the default global instance label.") +} + +func prepConfig() error { + err := config.Register(&config.Option{ + Name: "Metrics Instance Name", + Key: CfgOptionInstanceKey, + Description: "Define the prometheus instance label for exported metrics. Please note that changing the instance name will reset persisted metrics.", + OptType: config.OptTypeString, + ExpertiseLevel: config.ExpertiseLevelExpert, + ReleaseLevel: config.ReleaseLevelStable, + DefaultValue: instanceFlag, + RequiresRestart: true, + Annotations: config.Annotations{ + config.DisplayOrderAnnotation: cfgOptionInstanceOrder, + config.CategoryAnnotation: "Metrics", + }, + ValidationRegex: "^(" + prometheusBaseFormt + ")?$", + }) + if err != nil { + return err + } + instanceOption = config.Concurrent.GetAsString(CfgOptionInstanceKey, instanceFlag) + + err = config.Register(&config.Option{ + Name: "Push Metrics", + Key: CfgOptionPushKey, + Description: "Push metrics to this URL in the prometheus format.", + OptType: config.OptTypeString, + ExpertiseLevel: config.ExpertiseLevelExpert, + ReleaseLevel: config.ReleaseLevelStable, + DefaultValue: pushFlag, + RequiresRestart: true, + Annotations: config.Annotations{ + config.DisplayOrderAnnotation: cfgOptionPushOrder, + config.CategoryAnnotation: "Metrics", + }, + }) + if err != nil { + return err + } + pushOption = config.Concurrent.GetAsString(CfgOptionPushKey, pushFlag) + + return nil +} diff --git a/metrics/metric.go b/metrics/metric.go index 1345365..fb46dc4 100644 --- a/metrics/metric.go +++ b/metrics/metric.go @@ -15,7 +15,8 @@ import ( // PrometheusFormatRequirement is required format defined by prometheus for // metric and label names. -const PrometheusFormatRequirement = "[a-zA-Z_][a-zA-Z0-9_]*" +const prometheusBaseFormt = "[a-zA-Z_][a-zA-Z0-9_]*" +const PrometheusFormatRequirement = "^" + prometheusBaseFormt + "$" var prometheusFormat = regexp.MustCompile(PrometheusFormatRequirement) @@ -60,7 +61,7 @@ type Options struct { func newMetricBase(id string, labels map[string]string, opts Options) (*metricBase, error) { // Check formats. - if !prometheusFormat.MatchString(id) { + if !prometheusFormat.MatchString(strings.ReplaceAll(id, "/", "_")) { return nil, fmt.Errorf("metric name %q must match %s", id, PrometheusFormatRequirement) } for labelName := range labels { @@ -75,6 +76,11 @@ func newMetricBase(id string, labels map[string]string, opts Options) (*metricBa opts.Permission = api.PermitUser } + // Ensure that labels is a map. + if labels == nil { + labels = make(map[string]string) + } + // Create metric base. base := &metricBase{ Identifier: id, diff --git a/metrics/module.go b/metrics/module.go index 517029f..1b92864 100644 --- a/metrics/module.go +++ b/metrics/module.go @@ -2,7 +2,6 @@ package metrics import ( "errors" - "flag" "fmt" "sort" "sync" @@ -20,9 +19,6 @@ var ( metricNamespace string globalLabels = make(map[string]string) - pushURL string - metricInstance string - // ErrAlreadyStarted is returned when an operation is only valid before the // first metric is registered, and is called after. ErrAlreadyStarted = errors.New("can only be changed before first metric is registered") @@ -36,29 +32,30 @@ var ( ) func init() { - flag.StringVar(&pushURL, "push-metrics", "", "URL to push prometheus metrics to") - flag.StringVar(&metricInstance, "metrics-instance", "", "Set the global instance label") - module = modules.Register("metrics", prep, start, stop, "database", "api") } func prep() error { + return prepConfig() +} + +func start() error { // Add metric instance name as global variable if set. - if metricInstance != "" { - if err := AddGlobalLabel("instance", metricInstance); err != nil { + if instanceOption() != "" { + if err := AddGlobalLabel("instance", instanceOption()); err != nil { return err } } - return registerInfoMetric() -} + if err := registerInfoMetric(); err != nil { + return err + } -func start() error { if err := registerAPI(); err != nil { return err } - if pushURL != "" { + if pushOption() != "" { module.StartServiceWorker("metric pusher", 0, metricsWriter) }