From 867b8d108fe7e1641ed032baecdbeb16918530be Mon Sep 17 00:00:00 2001 From: Fabian Reinartz Date: Tue, 20 Jun 2017 18:48:17 +0200 Subject: [PATCH] cmd/prometheus: cleanup --- cmd/prometheus/config.go | 215 ------------------ cmd/prometheus/main.go | 136 ++++++++--- .../{config_test.go => main_test.go} | 33 +-- 3 files changed, 115 insertions(+), 269 deletions(-) delete mode 100644 cmd/prometheus/config.go rename cmd/prometheus/{config_test.go => main_test.go} (60%) diff --git a/cmd/prometheus/config.go b/cmd/prometheus/config.go deleted file mode 100644 index f81e8c2bd..000000000 --- a/cmd/prometheus/config.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2015 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "fmt" - "net" - "net/url" - "os" - "sort" - "strings" - "time" - - "github.com/asaskevich/govalidator" - "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/model" - "github.com/prometheus/prometheus/config" - "github.com/prometheus/prometheus/notifier" - "github.com/prometheus/prometheus/promql" - "github.com/prometheus/prometheus/storage/tsdb" - "github.com/prometheus/prometheus/web" -) - -// cfg contains immutable configuration parameters for a running Prometheus -// server. It is populated by its flag set. -var cfg = struct { - printVersion bool - configFile string - - localStoragePath string - localStorageEngine string - notifier notifier.Options - notifierTimeout model.Duration - queryEngine promql.EngineOptions - web web.Options - tsdb tsdb.Options - lookbackDelta model.Duration - webTimeout model.Duration - queryTimeout model.Duration - - alertmanagerURLs stringset - prometheusURL string - - logFormat string - logLevel string -}{ - // The defaults for model.Duration flag parsing. - notifierTimeout: model.Duration(10 * time.Second), - tsdb: tsdb.Options{ - MinBlockDuration: model.Duration(2 * time.Hour), - Retention: model.Duration(15 * 24 * time.Hour), - }, - lookbackDelta: model.Duration(5 * time.Minute), - webTimeout: model.Duration(30 * time.Second), - queryTimeout: model.Duration(2 * time.Minute), - - alertmanagerURLs: stringset{}, - notifier: notifier.Options{ - Registerer: prometheus.DefaultRegisterer, - }, -} - -func validate() error { - if err := parsePrometheusURL(); err != nil { - return errors.Wrapf(err, "parse external URL %q", cfg.prometheusURL) - } - - cfg.web.ReadTimeout = time.Duration(cfg.webTimeout) - // Default -web.route-prefix to path of -web.external-url. - if cfg.web.RoutePrefix == "" { - cfg.web.RoutePrefix = cfg.web.ExternalURL.Path - } - // RoutePrefix must always be at least '/'. - cfg.web.RoutePrefix = "/" + strings.Trim(cfg.web.RoutePrefix, "/") - - for u := range cfg.alertmanagerURLs { - if err := validateAlertmanagerURL(u); err != nil { - return err - } - } - - if cfg.tsdb.MaxBlockDuration == 0 { - cfg.tsdb.MaxBlockDuration = cfg.tsdb.Retention / 10 - } - - promql.LookbackDelta = time.Duration(cfg.lookbackDelta) - - cfg.queryEngine.Timeout = time.Duration(cfg.queryTimeout) - - return nil -} - -func parsePrometheusURL() error { - fmt.Println("promurl", cfg.prometheusURL) - if cfg.prometheusURL == "" { - hostname, err := os.Hostname() - if err != nil { - return err - } - fmt.Println("listenaddr", cfg.web.ListenAddress) - _, port, err := net.SplitHostPort(cfg.web.ListenAddress) - if err != nil { - return err - } - cfg.prometheusURL = fmt.Sprintf("http://%s:%s/", hostname, port) - } - - if ok := govalidator.IsURL(cfg.prometheusURL); !ok { - return fmt.Errorf("invalid Prometheus URL: %s", cfg.prometheusURL) - } - - promURL, err := url.Parse(cfg.prometheusURL) - if err != nil { - return err - } - cfg.web.ExternalURL = promURL - - ppref := strings.TrimRight(cfg.web.ExternalURL.Path, "/") - if ppref != "" && !strings.HasPrefix(ppref, "/") { - ppref = "/" + ppref - } - cfg.web.ExternalURL.Path = ppref - return nil -} - -func validateAlertmanagerURL(u string) error { - if u == "" { - return nil - } - if ok := govalidator.IsURL(u); !ok { - return fmt.Errorf("invalid Alertmanager URL: %s", u) - } - url, err := url.Parse(u) - if err != nil { - return err - } - if url.Scheme == "" { - return fmt.Errorf("missing scheme in Alertmanager URL: %s", u) - } - return nil -} - -func parseAlertmanagerURLToConfig(us string) (*config.AlertmanagerConfig, error) { - u, err := url.Parse(us) - if err != nil { - return nil, err - } - acfg := &config.AlertmanagerConfig{ - Scheme: u.Scheme, - PathPrefix: u.Path, - Timeout: time.Duration(cfg.notifierTimeout), - ServiceDiscoveryConfig: config.ServiceDiscoveryConfig{ - StaticConfigs: []*config.TargetGroup{ - { - Targets: []model.LabelSet{ - { - model.AddressLabel: model.LabelValue(u.Host), - }, - }, - }, - }, - }, - } - - if u.User != nil { - acfg.HTTPClientConfig = config.HTTPClientConfig{ - BasicAuth: &config.BasicAuth{ - Username: u.User.Username(), - }, - } - - if password, isSet := u.User.Password(); isSet { - acfg.HTTPClientConfig.BasicAuth.Password = config.Secret(password) - } - } - - return acfg, nil -} - -type stringset map[string]struct{} - -func (ss stringset) Set(s string) error { - for _, v := range strings.Split(s, ",") { - v = strings.TrimSpace(v) - if v != "" { - ss[v] = struct{}{} - } - } - return nil -} - -func (ss stringset) String() string { - return strings.Join(ss.slice(), ",") -} - -func (ss stringset) slice() []string { - slice := make([]string, 0, len(ss)) - for k := range ss { - slice = append(slice, k) - } - sort.Strings(slice) - return slice -} diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index 7df4f7297..59065f561 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -16,15 +16,21 @@ package main import ( "fmt" + "net" _ "net/http/pprof" // Comment this line to disable pprof endpoint. + "net/url" "os" "os/signal" "path/filepath" + "strings" "syscall" "time" + "github.com/asaskevich/govalidator" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/log" + "github.com/prometheus/common/model" "github.com/prometheus/common/version" "golang.org/x/net/context" "gopkg.in/alecthomas/kingpin.v2" @@ -38,7 +44,48 @@ import ( "github.com/prometheus/prometheus/web" ) +var ( + configSuccess = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "prometheus", + Name: "config_last_reload_successful", + Help: "Whether the last configuration reload attempt was successful.", + }) + configSuccessTime = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "prometheus", + Name: "config_last_reload_success_timestamp_seconds", + Help: "Timestamp of the last successful configuration reload.", + }) +) + +func init() { + prometheus.MustRegister(version.NewCollector("prometheus")) +} + func main() { + cfg := struct { + printVersion bool + configFile string + + localStoragePath string + notifier notifier.Options + notifierTimeout model.Duration + queryEngine promql.EngineOptions + web web.Options + tsdb tsdb.Options + lookbackDelta model.Duration + webTimeout model.Duration + queryTimeout model.Duration + + prometheusURL string + + logFormat string + logLevel string + }{ + notifier: notifier.Options{ + Registerer: prometheus.DefaultRegisterer, + }, + } + a := kingpin.New(filepath.Base(os.Args[0]), "The Prometheus monitoring server") a.Version(version.Print("prometheus")) @@ -103,58 +150,49 @@ func main() { Default("10000").IntVar(&cfg.notifier.QueueCapacity) a.Flag("alertmanager.timeout", "Timeout for sending alerts to Alertmanager"). - SetValue(&cfg.notifierTimeout) + Default("10s").SetValue(&cfg.notifierTimeout) a.Flag("query.lookback-delta", "The delta difference allowed for retrieving metrics during expression evaluations."). Default("5m").SetValue(&cfg.lookbackDelta) a.Flag("query.timeout", "Maximum time a query may take before being aborted."). - SetValue(&cfg.queryTimeout) + Default("2m").SetValue(&cfg.queryTimeout) a.Flag("query.max-concurrency", "Maximum number of queries executed concurrently."). Default("20").IntVar(&cfg.queryEngine.MaxConcurrentQueries) - if _, err := a.Parse(os.Args[1:]); err != nil { + _, err := a.Parse(os.Args[1:]) + if err != nil { a.Usage(os.Args[1:]) os.Exit(2) } - fmt.Printf("a %+v\n", cfg) - if err := validate(); err != nil { - fmt.Fprintf(os.Stderr, "invalid argument: %s\n", err) + + cfg.web.ExternalURL, err = computeExternalURL(cfg.prometheusURL, cfg.web.ListenAddress) + if err != nil { + fmt.Fprintln(os.Stderr, errors.Wrapf(err, "parse external URL %q", cfg.prometheusURL)) os.Exit(2) } - os.Exit(Main(a)) -} + cfg.web.ReadTimeout = time.Duration(cfg.webTimeout) + // Default -web.route-prefix to path of -web.external-url. + if cfg.web.RoutePrefix == "" { + cfg.web.RoutePrefix = cfg.web.ExternalURL.Path + } + // RoutePrefix must always be at least '/'. + cfg.web.RoutePrefix = "/" + strings.Trim(cfg.web.RoutePrefix, "/") -var ( - configSuccess = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: "prometheus", - Name: "config_last_reload_successful", - Help: "Whether the last configuration reload attempt was successful.", - }) - configSuccessTime = prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: "prometheus", - Name: "config_last_reload_success_timestamp_seconds", - Help: "Timestamp of the last successful configuration reload.", - }) -) + if cfg.tsdb.MaxBlockDuration == 0 { + cfg.tsdb.MaxBlockDuration = cfg.tsdb.Retention / 10 + } -func init() { - prometheus.MustRegister(version.NewCollector("prometheus")) -} + promql.LookbackDelta = time.Duration(cfg.lookbackDelta) + + cfg.queryEngine.Timeout = time.Duration(cfg.queryTimeout) -// Main manages the stup and shutdown lifecycle of the entire Prometheus server. -func Main(a *kingpin.Application) int { logger := log.NewLogger(os.Stdout) logger.SetLevel(cfg.logLevel) logger.SetFormat(cfg.logFormat) - if cfg.printVersion { - fmt.Fprintln(os.Stdout, version.Print("prometheus")) - return 0 - } - logger.Infoln("Starting prometheus", version.Info()) logger.Infoln("Build context", version.BuildContext()) logger.Infoln("Host details", Uname()) @@ -173,7 +211,7 @@ func Main(a *kingpin.Application) int { localStorage, err := tsdb.Open(cfg.localStoragePath, prometheus.DefaultRegisterer, &cfg.tsdb) if err != nil { log.Errorf("Opening storage failed: %s", err) - return 1 + os.Exit(1) } logger.Infoln("tsdb started") @@ -225,7 +263,7 @@ func Main(a *kingpin.Application) int { if err := reloadConfig(cfg.configFile, logger, reloadables...); err != nil { logger.Errorf("Error loading config: %s", err) - return 1 + os.Exit(1) } // Wait for reload or termination signals. Start the handler for SIGHUP as @@ -294,7 +332,6 @@ func Main(a *kingpin.Application) int { } logger.Info("See you next time!") - return 0 } // Reloadable things can change their internal state to match a new config @@ -331,3 +368,36 @@ func reloadConfig(filename string, logger log.Logger, rls ...Reloadable) (err er } return nil } + +// computeExternalURL computes a sanitized external URL from a raw input. It infers unset +// URL parts from the OS and the given listen address. +func computeExternalURL(u, listenAddr string) (*url.URL, error) { + if u == "" { + hostname, err := os.Hostname() + if err != nil { + return nil, err + } + _, port, err := net.SplitHostPort(listenAddr) + if err != nil { + return nil, err + } + u = fmt.Sprintf("http://%s:%s/", hostname, port) + } + + if ok := govalidator.IsURL(u); !ok { + return nil, fmt.Errorf("invalid external URL %q", u) + } + + eu, err := url.Parse(u) + if err != nil { + return nil, err + } + + ppref := strings.TrimRight(eu.Path, "/") + if ppref != "" && !strings.HasPrefix(ppref, "/") { + ppref = "/" + ppref + } + eu.Path = ppref + + return eu, nil +} diff --git a/cmd/prometheus/config_test.go b/cmd/prometheus/main_test.go similarity index 60% rename from cmd/prometheus/config_test.go rename to cmd/prometheus/main_test.go index d80fcd9b7..67be9ee81 100644 --- a/cmd/prometheus/config_test.go +++ b/cmd/prometheus/main_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Prometheus Authors +// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -15,40 +15,31 @@ package main import "testing" -func TestParse(t *testing.T) { +func TestComputeExternalURL(t *testing.T) { tests := []struct { - input []string - valid bool + extURL string + valid bool }{ { - input: []string{}, - valid: true, + extURL: "", + valid: true, }, { - input: []string{"--web.external-url", ""}, - valid: true, + extURL: "http://proxy.com/prometheus", + valid: true, }, { - input: []string{"--web.external-url", "http://proxy.com/prometheus"}, - valid: true, - }, - { - input: []string{"--web.external-url", "'https://url/prometheus'"}, - valid: false, + extURL: "https:/proxy.com/prometheus", + valid: false, }, } for i, test := range tests { - // reset "immutable" config - cfg.prometheusURL = "" - cfg.alertmanagerURLs = stringset{} - - newRootCmd() // To register the flags and flagset. - - err := parse(test.input) + r, err := computeExternalURL(test.extURL, "0.0.0.0:9090") if test.valid && err != nil { t.Errorf("%d. expected input to be valid, got %s", i, err) } else if !test.valid && err == nil { + t.Logf("%+v", r) t.Errorf("%d. expected input to be invalid", i) } }