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

chore: fixing casing

chore: add constant labels to hana provider

fix: add provider name to connection pool
  • Loading branch information
iwpnd committed Mar 24, 2023
1 parent f56b286 commit 2d42670
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 28 deletions.
4 changes: 2 additions & 2 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
)

var (
Version = "Version not set"
Version = "version not set"
GitRevision = "not set"
GitBranch = "not set"
uiVersionDefaultText = "Viewer not build"
uiVersionDefaultText = "viewer not build"
Tags []string
Commands = []string{"tegola"}
)
Expand Down
38 changes: 29 additions & 9 deletions provider/hana/hana.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ import (
const Name = "hana"

type connectionPoolCollector struct {
pool *sql.DB
pool *sql.DB

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

maxConnectionDesc *prometheus.Desc
currentConnectionsDesc *prometheus.Desc
availableConnectionsDesc *prometheus.Desc
Expand Down Expand Up @@ -106,28 +111,30 @@ func (c *connectionPoolCollector) Collectors(prefix string, _ func(configKey str
prefix+"hana_max_connections",
"Max number of hana connections in the pool",
nil,
nil,
prometheus.Labels{"provider_name": c.providerName},
)

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

c.availableConnectionsDesc = prometheus.NewDesc(
prefix+"hana_available_connections",
"Current number of available hana connections in the pool",
nil,
nil,
prometheus.Labels{"provider_name": c.providerName},
)

return []observability.Collector{c}, nil
}

// Provider provides the HANA data provider.
type Provider struct {
dbVersion uint
name string
pool *connectionPoolCollector
// map of layer name and corresponding sql
layers map[string]Layer
Expand Down Expand Up @@ -387,10 +394,16 @@ 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
}

p := Provider{
name: name,
dbVersion: uint(majorVersion),
srid: uint64(srid),
pool: &connectionPoolCollector{pool: conn},
pool: &connectionPoolCollector{pool: conn, providerName: name},
}

layers, err := config.MapSlice(ConfigKeyLayers)
Expand All @@ -405,7 +418,7 @@ func CreateProvider(config dict.Dicter, maps []provider.Map, providerType string

lName, err := layer.String(ConfigKeyLayerName, nil)
if err != nil {
return nil, fmt.Errorf("For layer (%v) we got the following error trying to get the layer's name field: %w", i, err)
return nil, fmt.Errorf("for layer (%v) we got the following error trying to get the layer's name field: %w", i, err)
}

if j, ok := lyrsSeen[lName]; ok {
Expand Down Expand Up @@ -480,7 +493,7 @@ func CreateProvider(config dict.Dicter, maps []provider.Map, providerType string

if isSrsRoundEarth(p.pool, uint64(lsrid)) {
if !hasSrsPlanarEquivalent(p.pool, uint64(lsrid)) {
return nil, fmt.Errorf("Unable to find a planar equivalent for srid %v in layer: %v", lsrid, lName)
return nil, fmt.Errorf("unable to find a planar equivalent for srid %v in layer: %v", lsrid, lName)
}
lsrid = int(toPlanarEquivalenSrid(uint64(lsrid)))
}
Expand Down Expand Up @@ -562,7 +575,7 @@ func CreateProvider(config dict.Dicter, maps []provider.Map, providerType string
return nil, err
}

var clipGeom bool = true
var clipGeom = true
if clipGeom, err = layer.Bool(ConfigKeyClipGeometry, &clipGeom); err != nil {
return nil, err
}
Expand Down Expand Up @@ -665,6 +678,10 @@ func (p Provider) inspectLayerGeomType(pname string, l *Layer, maps []provider.M
}

fields, err := getFieldDescriptions(l.Name(), l.GeomFieldName(), l.IDFieldName(), columns, false)
if err != nil {
return err
}

rowValues := make([]interface{}, len(fields))

for rows.Next() {
Expand All @@ -681,7 +698,10 @@ func (p Provider) inspectLayerGeomType(pname string, l *Layer, maps []provider.M
}

value := *(rowValues[i].(*sql.NullString))
p.setLayerGeomType(l, strings.Trim(value.String, "ST_"))
err := p.setLayerGeomType(l, strings.Trim(value.String, "ST_"))
if err != nil {
return err
}

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

// providerName 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 2d42670

Please sign in to comment.