Skip to content

Commit

Permalink
fix: prometheus with multiple providers
Browse files Browse the repository at this point in the history
we encountered an issue where tegola would panic if you'd try to register
duplicate metrics collectors.
that would happen if you have more than one mvt provider in the same config
file.

this fix makes provider metrics unique by adding the respective
provider name to an array of constant labels. Upon collecting metrics
on start up we'd pass unique metrics to the registration step.

resolves: #886
  • Loading branch information
iwpnd committed Mar 23, 2023
1 parent b358d4d commit 4cc179c
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 18 deletions.
2 changes: 1 addition & 1 deletion internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

var (
Version = "Version not set"
Version = "version not set"
GitRevision = "not set"
GitBranch = "not set"
uiVersionDefaultText = "Viewer not build"
Expand Down
48 changes: 35 additions & 13 deletions provider/postgis/postgis.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const Name = "postgis"

type connectionPoolCollector struct {
*pgxpool.Pool

// provider the pool is created for
// required to make metrics unique
providerName string

maxConnectionDesc *prometheus.Desc
currentConnectionsDesc *prometheus.Desc
availableConnectionsDesc *prometheus.Desc
Expand All @@ -47,7 +52,7 @@ func (c connectionPoolCollector) Collect(ch chan<- prometheus.Metric) {
if c.Pool == nil {
return
}
stat := c.Pool.Stat()
stat := c.Stat()
ch <- prometheus.MustNewConstMetric(
c.maxConnectionDesc,
prometheus.GaugeValue,
Expand All @@ -73,33 +78,38 @@ func (c *connectionPoolCollector) Collectors(prefix string, _ func(configKey str
prefix = prefix + "_"
}

// a constant label ensures that the metrics are unique
// this allows the registration of multiple providers in the same
// config.
c.maxConnectionDesc = prometheus.NewDesc(
prefix+"postgres_max_connections",
"Max number of postgres connections in the pool",
nil,
nil,
prometheus.Labels{"provider_name": c.providerName},
)

c.currentConnectionsDesc = prometheus.NewDesc(
prefix+"postgres_current_connections",
"Current number of postgres connections in the pool",
nil,
nil,
prometheus.Labels{"provider_name": c.providerName},
)

c.availableConnectionsDesc = prometheus.NewDesc(
prefix+"postgres_available_connections",
"Current number of available postgres connections in the pool",
nil,
nil,
prometheus.Labels{"provider_name": c.providerName},
)
return []observability.Collector{c}, nil
}

// Provider provides the postgis data provider.
type Provider struct {
config pgxpool.Config
name string
pool *connectionPoolCollector

// map of layer name and corresponding sql
layers map[string]Layer
srid uint64
Expand All @@ -122,31 +132,37 @@ func (p *Provider) Collectors(prefix string, cfgFn func(configKey string) map[st
}

buckets := []float64{.1, 1, 5, 20}
collectors, err := p.pool.Collectors(prefix, cfgFn)
c, err := p.pool.Collectors(prefix, cfgFn)
if err != nil {
return nil, err
}

// a constant label ensures that the metrics are unique
// this allows the registration of multiple providers in the same
// config.
// Additional label names will be appended to the constant labels.
p.mvtProviderQueryHistogramSeconds = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: prefix + "_mvt_provider_sql_query_seconds",
Help: "A histogram of query time for sql for mvt providers",
Buckets: buckets,
Name: prefix + "_mvt_provider_sql_query_seconds",
Help: "A histogram of query time for sql for mvt providers",
Buckets: buckets,
ConstLabels: prometheus.Labels{"provider_name": p.name},
},
[]string{"map_name", "z"},
)

p.queryHistogramSeconds = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: prefix + "_provider_sql_query_seconds",
Help: "A histogram of query time for sql for providers",
Buckets: buckets,
Name: prefix + "_provider_sql_query_seconds",
Help: "A histogram of query time for sql for providers",
Buckets: buckets,
ConstLabels: prometheus.Labels{"provider_name": p.name},
},
[]string{"map_name", "layer_name", "z"},
)

p.collectorsRegistered = true
return append(collectors, p.mvtProviderQueryHistogramSeconds, p.queryHistogramSeconds), nil
return append(c, p.mvtProviderQueryHistogramSeconds, p.queryHistogramSeconds), nil
}

const (
Expand Down Expand Up @@ -460,20 +476,26 @@ func CreateProvider(config dict.Dicter, maps []provider.Map, providerType string
return nil, err
}

name, err := config.String(ConfigKeyName, nil)
if err != nil {
return nil, err
}

if err = ConfigTLS(sslmode, sslkey, sslcert, sslrootcert, dbconfig); err != nil {
return nil, err
}

p := Provider{
srid: uint64(srid),
config: *dbconfig,
name: name,
}

pool, err := pgxpool.ConnectConfig(context.Background(), &p.config)
if err != nil {
return nil, fmt.Errorf("failed while creating connection pool: %w", err)
}
p.pool = &connectionPoolCollector{Pool: pool}
p.pool = &connectionPoolCollector{Pool: pool, providerName: name}

layers, err := config.MapSlice(ConfigKeyLayers)
if err != nil {
Expand Down
9 changes: 5 additions & 4 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package server

import (
"fmt"
"github.com/go-spatial/tegola/internal/build"
"net/http"
"net/url"
"path"

"github.com/go-spatial/tegola/internal/build"

"github.com/go-spatial/tegola/observability"

"github.com/dimfeld/httptreemux"
Expand All @@ -25,7 +26,7 @@ const (
var (
// Version is the version of the software, this should be set by the main program, before starting up.
// It is used by various Middleware to determine the version.
Version string = "Version Not Set"
Version string = "version not set"

// HostName is the name of the host to use for construction of URLS.
// configurable via the tegola config.toml file (set in main.go)
Expand Down Expand Up @@ -71,7 +72,7 @@ func NewRouter(a *atlas.Atlas) *httptreemux.TreeMux {
)
if h := o.Handler(metricsRoute); h != nil {
// Only set up the /metrics endpoint if we have a configured observer
log.Infof("Setting up observer: %v", o.Name())
log.Infof("setting up observer: %v", o.Name())
group.UsingContext().Handler(http.MethodGet, metricsRoute, h)
}
}
Expand All @@ -98,7 +99,7 @@ func NewRouter(a *atlas.Atlas) *httptreemux.TreeMux {
func Start(a *atlas.Atlas, port string) *http.Server {

// notify the user the server is starting
log.Infof("starting tegola server(%v) on port %v", build.Version, port)
log.Infof("starting tegola server (%v) on port %v", build.Version, port)

srv := &http.Server{Addr: port, Handler: NewRouter(a)}

Expand Down

0 comments on commit 4cc179c

Please sign in to comment.