Skip to content

Commit 31bc75a

Browse files
authored
Add the instance struct to handle connections (#859)
The intent is to use the instance struct to hold the connection to the database as well as metadata about the instance: - version - flavor (mariadb or mysql) Change is similar to prometheus-community/postgres_exporter#785 Signed-off-by: Vlad Gusev <vlad.esten@gmail.com>
1 parent dd8afce commit 31bc75a

File tree

67 files changed

+267
-156
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+267
-156
lines changed

collector/binlog.go

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

1818
import (
1919
"context"
20-
"database/sql"
2120
"fmt"
2221
"strconv"
2322
"strings"
@@ -72,8 +71,9 @@ func (ScrapeBinlogSize) Version() float64 {
7271
}
7372

7473
// Scrape collects data from database connection and sends it over channel as prometheus metric.
75-
func (ScrapeBinlogSize) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
74+
func (ScrapeBinlogSize) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
7675
var logBin uint8
76+
db := instance.getDB()
7777
err := db.QueryRowContext(ctx, logbinQuery).Scan(&logBin)
7878
if err != nil {
7979
return err

collector/binlog_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ func TestScrapeBinlogSize(t *testing.T) {
3131
}
3232
defer db.Close()
3333

34+
inst := &instance{db: db}
35+
3436
mock.ExpectQuery(logbinQuery).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(1))
3537

3638
columns := []string{"Log_name", "File_size"}
@@ -42,7 +44,7 @@ func TestScrapeBinlogSize(t *testing.T) {
4244

4345
ch := make(chan prometheus.Metric)
4446
go func() {
45-
if err = (ScrapeBinlogSize{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
47+
if err = (ScrapeBinlogSize{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
4648
t.Errorf("error calling function on test: %s", err)
4749
}
4850
close(ch)

collector/engine_innodb.go

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

1818
import (
1919
"context"
20-
"database/sql"
2120
"regexp"
2221
"strconv"
2322
"strings"
@@ -52,7 +51,8 @@ func (ScrapeEngineInnodbStatus) Version() float64 {
5251
}
5352

5453
// Scrape collects data from database connection and sends it over channel as prometheus metric.
55-
func (ScrapeEngineInnodbStatus) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
54+
func (ScrapeEngineInnodbStatus) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
55+
db := instance.getDB()
5656
rows, err := db.QueryContext(ctx, engineInnodbStatusQuery)
5757
if err != nil {
5858
return err

collector/engine_innodb_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ END OF INNODB MONITOR OUTPUT
152152
rows := sqlmock.NewRows(columns).AddRow("InnoDB", "", sample)
153153

154154
mock.ExpectQuery(sanitizeQuery(engineInnodbStatusQuery)).WillReturnRows(rows)
155-
155+
inst := &instance{db: db}
156156
ch := make(chan prometheus.Metric)
157157
go func() {
158-
if err = (ScrapeEngineInnodbStatus{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
158+
if err = (ScrapeEngineInnodbStatus{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
159159
t.Errorf("error calling function on test: %s", err)
160160
}
161161
close(ch)

collector/engine_tokudb.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ func (ScrapeEngineTokudbStatus) Version() float64 {
5050
}
5151

5252
// Scrape collects data from database connection and sends it over channel as prometheus metric.
53-
func (ScrapeEngineTokudbStatus) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
53+
func (ScrapeEngineTokudbStatus) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
54+
db := instance.getDB()
5455
tokudbRows, err := db.QueryContext(ctx, engineTokudbStatusQuery)
5556
if err != nil {
5657
return err

collector/engine_tokudb_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func TestScrapeEngineTokudbStatus(t *testing.T) {
4646
t.Fatalf("error opening a stub database connection: %s", err)
4747
}
4848
defer db.Close()
49+
inst := &instance{db: db}
4950

5051
columns := []string{"Type", "Name", "Status"}
5152
rows := sqlmock.NewRows(columns).
@@ -59,7 +60,7 @@ func TestScrapeEngineTokudbStatus(t *testing.T) {
5960

6061
ch := make(chan prometheus.Metric)
6162
go func() {
62-
if err = (ScrapeEngineTokudbStatus{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
63+
if err = (ScrapeEngineTokudbStatus{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
6364
t.Errorf("error calling function on test: %s", err)
6465
}
6566
close(ch)

collector/exporter.go

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

1616
import (
1717
"context"
18-
"database/sql"
1918
"fmt"
20-
"regexp"
21-
"strconv"
2219
"strings"
2320
"sync"
2421
"time"
@@ -38,18 +35,12 @@ const (
3835

3936
// SQL queries and parameters.
4037
const (
41-
versionQuery = `SELECT @@version`
42-
4338
// System variable params formatting.
4439
// See: https://github.com/go-sql-driver/mysql#system-variables
4540
sessionSettingsParam = `log_slow_filter=%27tmp_table_on_disk,filesort_on_disk%27`
4641
timeoutParam = `lock_wait_timeout=%d`
4742
)
4843

49-
var (
50-
versionRE = regexp.MustCompile(`^\d+\.\d+`)
51-
)
52-
5344
// Tunable flags.
5445
var (
5546
exporterLockTimeout = kingpin.Flag(
@@ -92,6 +83,7 @@ type Exporter struct {
9283
logger log.Logger
9384
dsn string
9485
scrapers []Scraper
86+
instance *instance
9587
}
9688

9789
// New returns a new MySQL exporter for the provided DSN.
@@ -135,27 +127,23 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
135127
func (e *Exporter) scrape(ctx context.Context, ch chan<- prometheus.Metric) float64 {
136128
var err error
137129
scrapeTime := time.Now()
138-
db, err := sql.Open("mysql", e.dsn)
130+
instance, err := newInstance(e.dsn)
139131
if err != nil {
140132
level.Error(e.logger).Log("msg", "Error opening connection to database", "err", err)
141133
return 0.0
142134
}
143-
defer db.Close()
144-
145-
// By design exporter should use maximum one connection per request.
146-
db.SetMaxOpenConns(1)
147-
db.SetMaxIdleConns(1)
148-
// Set max lifetime for a connection.
149-
db.SetConnMaxLifetime(1 * time.Minute)
135+
defer instance.Close()
136+
e.instance = instance
150137

151-
if err := db.PingContext(ctx); err != nil {
138+
if err := instance.Ping(); err != nil {
152139
level.Error(e.logger).Log("msg", "Error pinging mysqld", "err", err)
153140
return 0.0
154141
}
155142

156143
ch <- prometheus.MustNewConstMetric(mysqlScrapeDurationSeconds, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "connection")
157144

158-
version := getMySQLVersion(db, e.logger)
145+
version := instance.versionMajorMinor
146+
159147
var wg sync.WaitGroup
160148
defer wg.Wait()
161149
for _, scraper := range e.scrapers {
@@ -169,7 +157,7 @@ func (e *Exporter) scrape(ctx context.Context, ch chan<- prometheus.Metric) floa
169157
label := "collect." + scraper.Name()
170158
scrapeTime := time.Now()
171159
collectorSuccess := 1.0
172-
if err := scraper.Scrape(ctx, db, ch, log.With(e.logger, "scraper", scraper.Name())); err != nil {
160+
if err := scraper.Scrape(ctx, instance, ch, log.With(e.logger, "scraper", scraper.Name())); err != nil {
173161
level.Error(e.logger).Log("msg", "Error from scraper", "scraper", scraper.Name(), "target", e.getTargetFromDsn(), "err", err)
174162
collectorSuccess = 0.0
175163
}
@@ -189,19 +177,3 @@ func (e *Exporter) getTargetFromDsn() string {
189177
}
190178
return dsnConfig.Addr
191179
}
192-
193-
func getMySQLVersion(db *sql.DB, logger log.Logger) float64 {
194-
var versionStr string
195-
var versionNum float64
196-
if err := db.QueryRow(versionQuery).Scan(&versionStr); err == nil {
197-
versionNum, _ = strconv.ParseFloat(versionRE.FindString(versionStr), 64)
198-
} else {
199-
level.Debug(logger).Log("msg", "Error querying version", "err", err)
200-
}
201-
// If we can't match/parse the version, set it some big value that matches all versions.
202-
if versionNum == 0 {
203-
level.Debug(logger).Log("msg", "Error parsing version string", "version", versionStr)
204-
versionNum = 999
205-
}
206-
return versionNum
207-
}

collector/exporter_test.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,9 @@ package collector
1515

1616
import (
1717
"context"
18-
"database/sql"
19-
"os"
2018
"testing"
2119

2220
"github.com/go-kit/log"
23-
"github.com/go-kit/log/level"
2421
"github.com/prometheus/client_golang/prometheus"
2522
"github.com/prometheus/common/model"
2623
"github.com/smartystreets/goconvey/convey"
@@ -68,20 +65,3 @@ func TestExporter(t *testing.T) {
6865
}
6966
})
7067
}
71-
72-
func TestGetMySQLVersion(t *testing.T) {
73-
if testing.Short() {
74-
t.Skip("-short is passed, skipping test")
75-
}
76-
77-
logger := log.NewLogfmtLogger(os.Stderr)
78-
logger = level.NewFilter(logger, level.AllowDebug())
79-
80-
convey.Convey("Version parsing", t, func() {
81-
db, err := sql.Open("mysql", dsn)
82-
convey.So(err, convey.ShouldBeNil)
83-
defer db.Close()
84-
85-
convey.So(getMySQLVersion(db, logger), convey.ShouldBeBetweenOrEqual, 5.6, 12.0)
86-
})
87-
}

collector/global_status.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ func (ScrapeGlobalStatus) Version() float64 {
9999
}
100100

101101
// Scrape collects data from database connection and sends it over channel as prometheus metric.
102-
func (ScrapeGlobalStatus) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
102+
func (ScrapeGlobalStatus) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
103+
db := instance.getDB()
103104
globalStatusRows, err := db.QueryContext(ctx, globalStatusQuery)
104105
if err != nil {
105106
return err

collector/global_status_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestScrapeGlobalStatus(t *testing.T) {
3030
t.Fatalf("error opening a stub database connection: %s", err)
3131
}
3232
defer db.Close()
33+
inst := &instance{db: db}
3334

3435
columns := []string{"Variable_name", "Value"}
3536
rows := sqlmock.NewRows(columns).
@@ -63,7 +64,7 @@ func TestScrapeGlobalStatus(t *testing.T) {
6364

6465
ch := make(chan prometheus.Metric)
6566
go func() {
66-
if err = (ScrapeGlobalStatus{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
67+
if err = (ScrapeGlobalStatus{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
6768
t.Errorf("error calling function on test: %s", err)
6869
}
6970
close(ch)

collector/global_variables.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ func (ScrapeGlobalVariables) Version() float64 {
138138
}
139139

140140
// Scrape collects data from database connection and sends it over channel as prometheus metric.
141-
func (ScrapeGlobalVariables) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
141+
func (ScrapeGlobalVariables) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
142+
db := instance.getDB()
142143
globalVariablesRows, err := db.QueryContext(ctx, globalVariablesQuery)
143144
if err != nil {
144145
return err

collector/global_variables_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestScrapeGlobalVariables(t *testing.T) {
3030
t.Fatalf("error opening a stub database connection: %s", err)
3131
}
3232
defer db.Close()
33+
inst := &instance{db: db}
3334

3435
columns := []string{"Variable_name", "Value"}
3536
rows := sqlmock.NewRows(columns).
@@ -52,7 +53,7 @@ func TestScrapeGlobalVariables(t *testing.T) {
5253

5354
ch := make(chan prometheus.Metric)
5455
go func() {
55-
if err = (ScrapeGlobalVariables{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
56+
if err = (ScrapeGlobalVariables{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
5657
t.Errorf("error calling function on test: %s", err)
5758
}
5859
close(ch)

collector/heartbeat.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ func nowExpr() string {
100100
}
101101

102102
// Scrape collects data from database connection and sends it over channel as prometheus metric.
103-
func (ScrapeHeartbeat) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
103+
func (ScrapeHeartbeat) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
104+
db := instance.getDB()
104105
query := fmt.Sprintf(heartbeatQuery, nowExpr(), *collectHeartbeatDatabase, *collectHeartbeatTable)
105106
heartbeatRows, err := db.QueryContext(ctx, query)
106107
if err != nil {

collector/heartbeat_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,15 @@ func TestScrapeHeartbeat(t *testing.T) {
6565
t.Fatalf("error opening a stub database connection: %s", err)
6666
}
6767
defer db.Close()
68+
inst := &instance{db: db}
6869

6970
rows := sqlmock.NewRows(tt.Columns).
7071
AddRow("1487597613.001320", "1487598113.448042", 1)
7172
mock.ExpectQuery(sanitizeQuery(tt.Query)).WillReturnRows(rows)
7273

7374
ch := make(chan prometheus.Metric)
7475
go func() {
75-
if err = (ScrapeHeartbeat{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
76+
if err = (ScrapeHeartbeat{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
7677
t.Errorf("error calling function on test: %s", err)
7778
}
7879
close(ch)

collector/info_schema_auto_increment.go

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

1818
import (
1919
"context"
20-
"database/sql"
2120

2221
"github.com/go-kit/log"
2322
"github.com/prometheus/client_golang/prometheus"
@@ -70,7 +69,8 @@ func (ScrapeAutoIncrementColumns) Version() float64 {
7069
}
7170

7271
// Scrape collects data from database connection and sends it over channel as prometheus metric.
73-
func (ScrapeAutoIncrementColumns) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
72+
func (ScrapeAutoIncrementColumns) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
73+
db := instance.getDB()
7474
autoIncrementRows, err := db.QueryContext(ctx, infoSchemaAutoIncrementQuery)
7575
if err != nil {
7676
return err

collector/info_schema_clientstats.go

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

1818
import (
1919
"context"
20-
"database/sql"
2120
"fmt"
2221
"strings"
2322

@@ -161,8 +160,9 @@ func (ScrapeClientStat) Version() float64 {
161160
}
162161

163162
// Scrape collects data from database connection and sends it over channel as prometheus metric.
164-
func (ScrapeClientStat) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
163+
func (ScrapeClientStat) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
165164
var varName, varVal string
165+
db := instance.getDB()
166166
err := db.QueryRowContext(ctx, userstatCheckQuery).Scan(&varName, &varVal)
167167
if err != nil {
168168
level.Debug(logger).Log("msg", "Detailed client stats are not available.")

collector/info_schema_clientstats_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestScrapeClientStat(t *testing.T) {
3030
t.Fatalf("error opening a stub database connection: %s", err)
3131
}
3232
defer db.Close()
33+
inst := &instance{db: db}
3334

3435
mock.ExpectQuery(sanitizeQuery(userstatCheckQuery)).WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
3536
AddRow("userstat", "ON"))
@@ -41,7 +42,7 @@ func TestScrapeClientStat(t *testing.T) {
4142

4243
ch := make(chan prometheus.Metric)
4344
go func() {
44-
if err = (ScrapeClientStat{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
45+
if err = (ScrapeClientStat{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
4546
t.Errorf("error calling function on test: %s", err)
4647
}
4748
close(ch)

collector/info_schema_innodb_cmp.go

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

1818
import (
1919
"context"
20-
"database/sql"
2120

2221
"github.com/go-kit/log"
2322
"github.com/prometheus/client_golang/prometheus"
@@ -77,7 +76,8 @@ func (ScrapeInnodbCmp) Version() float64 {
7776
}
7877

7978
// Scrape collects data from database connection and sends it over channel as prometheus metric.
80-
func (ScrapeInnodbCmp) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
79+
func (ScrapeInnodbCmp) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
80+
db := instance.getDB()
8181
informationSchemaInnodbCmpRows, err := db.QueryContext(ctx, innodbCmpQuery)
8282
if err != nil {
8383
return err

collector/info_schema_innodb_cmp_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestScrapeInnodbCmp(t *testing.T) {
3030
t.Fatalf("error opening a stub database connection: %s", err)
3131
}
3232
defer db.Close()
33+
inst := &instance{db: db}
3334

3435
columns := []string{"page_size", "compress_ops", "compress_ops_ok", "compress_time", "uncompress_ops", "uncompress_time"}
3536
rows := sqlmock.NewRows(columns).
@@ -38,7 +39,7 @@ func TestScrapeInnodbCmp(t *testing.T) {
3839

3940
ch := make(chan prometheus.Metric)
4041
go func() {
41-
if err = (ScrapeInnodbCmp{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
42+
if err = (ScrapeInnodbCmp{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
4243
t.Errorf("error calling function on test: %s", err)
4344
}
4445
close(ch)

0 commit comments

Comments
 (0)