Skip to content

Commit 2a89b67

Browse files
committed
Add the instance struct to handle connections
The intent is to use the instance struct to hold the connection to the database as well as metadata about the instance. Currently this metadata only includes the version of postgres for the instance which can be used in the collectors to decide what query to run. In the future this could hold more metadata but for now it keeps the Collector interface arguments to a reasonable number. Signed-off-by: Joe Adams <[email protected]>
1 parent 5f57b78 commit 2a89b67

File tree

6 files changed

+103
-25
lines changed

6 files changed

+103
-25
lines changed

collector/collector.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ package collector
1515

1616
import (
1717
"context"
18-
"database/sql"
1918
"errors"
2019
"fmt"
2120
"sync"
@@ -59,7 +58,7 @@ var (
5958
)
6059

6160
type Collector interface {
62-
Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error
61+
Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error
6362
}
6463

6564
type collectorConfig struct {
@@ -92,7 +91,7 @@ type PostgresCollector struct {
9291
Collectors map[string]Collector
9392
logger log.Logger
9493

95-
db *sql.DB
94+
instance *instance
9695
}
9796

9897
type Option func(*PostgresCollector) error
@@ -149,14 +148,11 @@ func NewPostgresCollector(logger log.Logger, excludeDatabases []string, dsn stri
149148
return nil, errors.New("empty dsn")
150149
}
151150

152-
db, err := sql.Open("postgres", dsn)
151+
instance, err := newInstance(dsn)
153152
if err != nil {
154153
return nil, err
155154
}
156-
db.SetMaxOpenConns(1)
157-
db.SetMaxIdleConns(1)
158-
159-
p.db = db
155+
p.instance = instance
160156

161157
return p, nil
162158
}
@@ -174,16 +170,16 @@ func (p PostgresCollector) Collect(ch chan<- prometheus.Metric) {
174170
wg.Add(len(p.Collectors))
175171
for name, c := range p.Collectors {
176172
go func(name string, c Collector) {
177-
execute(ctx, name, c, p.db, ch, p.logger)
173+
execute(ctx, name, c, p.instance, ch, p.logger)
178174
wg.Done()
179175
}(name, c)
180176
}
181177
wg.Wait()
182178
}
183179

184-
func execute(ctx context.Context, name string, c Collector, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) {
180+
func execute(ctx context.Context, name string, c Collector, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) {
185181
begin := time.Now()
186-
err := c.Update(ctx, db, ch)
182+
err := c.Update(ctx, instance, ch)
187183
duration := time.Since(begin)
188184
var success float64
189185

collector/instance.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package collector
15+
16+
import (
17+
"database/sql"
18+
"fmt"
19+
"regexp"
20+
21+
"github.com/blang/semver/v4"
22+
)
23+
24+
type instance struct {
25+
db *sql.DB
26+
version semver.Version
27+
}
28+
29+
func newInstance(dsn string) (*instance, error) {
30+
i := &instance{}
31+
db, err := sql.Open("postgres", dsn)
32+
if err != nil {
33+
return nil, err
34+
}
35+
db.SetMaxOpenConns(1)
36+
db.SetMaxIdleConns(1)
37+
i.db = db
38+
39+
version, err := queryVersion(db)
40+
if err != nil {
41+
db.Close()
42+
return nil, err
43+
}
44+
45+
i.version = version
46+
47+
return i, nil
48+
}
49+
50+
func (i *instance) getDB() *sql.DB {
51+
return i.db
52+
}
53+
54+
func (i *instance) Close() error {
55+
return i.db.Close()
56+
}
57+
58+
// Regex used to get the "short-version" from the postgres version field.
59+
// The result of SELECT version() is something like "PostgreSQL 9.6.2 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 6.2.1 20160830, 64-bit"
60+
var versionRegex = regexp.MustCompile(`^\w+ ((\d+)(\.\d+)?(\.\d+)?)`)
61+
var serverVersionRegex = regexp.MustCompile(`^((\d+)(\.\d+)?(\.\d+)?)`)
62+
63+
func queryVersion(db *sql.DB) (semver.Version, error) {
64+
var version string
65+
err := db.QueryRow("SELECT version();").Scan(&version)
66+
if err != nil {
67+
return semver.Version{}, err
68+
}
69+
submatches := versionRegex.FindStringSubmatch(version)
70+
if len(submatches) > 1 {
71+
return semver.ParseTolerant(submatches[1])
72+
}
73+
74+
// We could also try to parse the version from the server_version field.
75+
// This is of the format 13.3 (Debian 13.3-1.pgdg100+1)
76+
err = db.QueryRow("SHOW server_version;").Scan(&version)
77+
if err != nil {
78+
return semver.Version{}, err
79+
}
80+
submatches = serverVersionRegex.FindStringSubmatch(version)
81+
if len(submatches) > 1 {
82+
return semver.ParseTolerant(submatches[1])
83+
}
84+
return semver.Version{}, fmt.Errorf("could not parse version from %q", version)
85+
}

collector/pg_database.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ package collector
1515

1616
import (
1717
"context"
18-
"database/sql"
1918

2019
"github.com/go-kit/log"
2120
"github.com/prometheus/client_golang/prometheus"
@@ -57,7 +56,8 @@ var pgDatabase = map[string]*prometheus.Desc{
5756
// each database individually. This is because we can't filter the
5857
// list of databases in the query because the list of excluded
5958
// databases is dynamic.
60-
func (c PGDatabaseCollector) Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error {
59+
func (c PGDatabaseCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
60+
db := instance.getDB()
6161
// Query the list of databases
6262
rows, err := db.QueryContext(ctx,
6363
`SELECT pg_database.datname

collector/pg_stat_bgwriter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ package collector
1515

1616
import (
1717
"context"
18-
"database/sql"
1918
"time"
2019

2120
"github.com/prometheus/client_golang/prometheus"
@@ -103,7 +102,8 @@ var statBGWriter = map[string]*prometheus.Desc{
103102
),
104103
}
105104

106-
func (PGStatBGWriterCollector) Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error {
105+
func (PGStatBGWriterCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
106+
db := instance.getDB()
107107
row := db.QueryRowContext(ctx,
108108
`SELECT
109109
checkpoints_timed

collector/probe.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ package collector
1515

1616
import (
1717
"context"
18-
"database/sql"
1918
"sync"
2019

2120
"github.com/go-kit/log"
@@ -27,7 +26,7 @@ type ProbeCollector struct {
2726
registry *prometheus.Registry
2827
collectors map[string]Collector
2928
logger log.Logger
30-
db *sql.DB
29+
instance *instance
3130
}
3231

3332
func NewProbeCollector(logger log.Logger, excludeDatabases []string, registry *prometheus.Registry, dsn config.DSN) (*ProbeCollector, error) {
@@ -58,18 +57,16 @@ func NewProbeCollector(logger log.Logger, excludeDatabases []string, registry *p
5857
}
5958
}
6059

61-
db, err := sql.Open("postgres", dsn.GetConnectionString())
60+
instance, err := newInstance(dsn.GetConnectionString())
6261
if err != nil {
6362
return nil, err
6463
}
65-
db.SetMaxOpenConns(1)
66-
db.SetMaxIdleConns(1)
6764

6865
return &ProbeCollector{
6966
registry: registry,
7067
collectors: collectors,
7168
logger: logger,
72-
db: db,
69+
instance: instance,
7370
}, nil
7471
}
7572

@@ -81,13 +78,13 @@ func (pc *ProbeCollector) Collect(ch chan<- prometheus.Metric) {
8178
wg.Add(len(pc.collectors))
8279
for name, c := range pc.collectors {
8380
go func(name string, c Collector) {
84-
execute(context.TODO(), name, c, pc.db, ch, pc.logger)
81+
execute(context.TODO(), name, c, pc.instance, ch, pc.logger)
8582
wg.Done()
8683
}(name, c)
8784
}
8885
wg.Wait()
8986
}
9087

9188
func (pc *ProbeCollector) Close() error {
92-
return pc.db.Close()
89+
return pc.instance.Close()
9390
}

collector/replication_slots.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ package collector
1515

1616
import (
1717
"context"
18-
"database/sql"
1918

2019
"github.com/go-kit/log"
2120
"github.com/prometheus/client_golang/prometheus"
@@ -51,7 +50,8 @@ var pgReplicationSlot = map[string]*prometheus.Desc{
5150
),
5251
}
5352

54-
func (PGReplicationSlotCollector) Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error {
53+
func (PGReplicationSlotCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
54+
db := instance.getDB()
5555
rows, err := db.QueryContext(ctx,
5656
`SELECT
5757
slot_name,

0 commit comments

Comments
 (0)