cmd/prometheus: cleanup

This commit is contained in:
Fabian Reinartz 2017-06-20 18:48:17 +02:00
parent 34ab7a885a
commit 867b8d108f
3 changed files with 115 additions and 269 deletions

View file

@ -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
}

View file

@ -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, "/")
if cfg.tsdb.MaxBlockDuration == 0 {
cfg.tsdb.MaxBlockDuration = cfg.tsdb.Retention / 10
}
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.",
})
)
promql.LookbackDelta = time.Duration(cfg.lookbackDelta)
func init() {
prometheus.MustRegister(version.NewCollector("prometheus"))
}
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
}

View file

@ -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
extURL string
valid bool
}{
{
input: []string{},
extURL: "",
valid: true,
},
{
input: []string{"--web.external-url", ""},
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'"},
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)
}
}