1
0
mirror of https://github.com/prometheus/mysqld_exporter.git synced 2025-07-30 06:43:05 +03:00

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>
This commit is contained in:
Vlad Gusev
2024-08-12 16:39:52 +03:00
committed by GitHub
parent dd8afce2a4
commit 31bc75a200
67 changed files with 267 additions and 156 deletions

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"fmt"
"strconv"
"strings"
@ -72,8 +71,9 @@ func (ScrapeBinlogSize) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeBinlogSize) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeBinlogSize) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var logBin uint8
db := instance.getDB()
err := db.QueryRowContext(ctx, logbinQuery).Scan(&logBin)
if err != nil {
return err

View File

@ -31,6 +31,8 @@ func TestScrapeBinlogSize(t *testing.T) {
}
defer db.Close()
inst := &instance{db: db}
mock.ExpectQuery(logbinQuery).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(1))
columns := []string{"Log_name", "File_size"}
@ -42,7 +44,7 @@ func TestScrapeBinlogSize(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeBinlogSize{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeBinlogSize{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"regexp"
"strconv"
"strings"
@ -52,7 +51,8 @@ func (ScrapeEngineInnodbStatus) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeEngineInnodbStatus) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeEngineInnodbStatus) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
rows, err := db.QueryContext(ctx, engineInnodbStatusQuery)
if err != nil {
return err

View File

@ -152,10 +152,10 @@ END OF INNODB MONITOR OUTPUT
rows := sqlmock.NewRows(columns).AddRow("InnoDB", "", sample)
mock.ExpectQuery(sanitizeQuery(engineInnodbStatusQuery)).WillReturnRows(rows)
inst := &instance{db: db}
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeEngineInnodbStatus{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeEngineInnodbStatus{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -50,7 +50,8 @@ func (ScrapeEngineTokudbStatus) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeEngineTokudbStatus) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeEngineTokudbStatus) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
tokudbRows, err := db.QueryContext(ctx, engineTokudbStatusQuery)
if err != nil {
return err

View File

@ -46,6 +46,7 @@ func TestScrapeEngineTokudbStatus(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"Type", "Name", "Status"}
rows := sqlmock.NewRows(columns).
@ -59,7 +60,7 @@ func TestScrapeEngineTokudbStatus(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeEngineTokudbStatus{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeEngineTokudbStatus{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -15,10 +15,7 @@ package collector
import (
"context"
"database/sql"
"fmt"
"regexp"
"strconv"
"strings"
"sync"
"time"
@ -38,18 +35,12 @@ const (
// SQL queries and parameters.
const (
versionQuery = `SELECT @@version`
// System variable params formatting.
// See: https://github.com/go-sql-driver/mysql#system-variables
sessionSettingsParam = `log_slow_filter=%27tmp_table_on_disk,filesort_on_disk%27`
timeoutParam = `lock_wait_timeout=%d`
)
var (
versionRE = regexp.MustCompile(`^\d+\.\d+`)
)
// Tunable flags.
var (
exporterLockTimeout = kingpin.Flag(
@ -92,6 +83,7 @@ type Exporter struct {
logger log.Logger
dsn string
scrapers []Scraper
instance *instance
}
// New returns a new MySQL exporter for the provided DSN.
@ -135,27 +127,23 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
func (e *Exporter) scrape(ctx context.Context, ch chan<- prometheus.Metric) float64 {
var err error
scrapeTime := time.Now()
db, err := sql.Open("mysql", e.dsn)
instance, err := newInstance(e.dsn)
if err != nil {
level.Error(e.logger).Log("msg", "Error opening connection to database", "err", err)
return 0.0
}
defer db.Close()
defer instance.Close()
e.instance = instance
// By design exporter should use maximum one connection per request.
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
// Set max lifetime for a connection.
db.SetConnMaxLifetime(1 * time.Minute)
if err := db.PingContext(ctx); err != nil {
if err := instance.Ping(); err != nil {
level.Error(e.logger).Log("msg", "Error pinging mysqld", "err", err)
return 0.0
}
ch <- prometheus.MustNewConstMetric(mysqlScrapeDurationSeconds, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "connection")
version := getMySQLVersion(db, e.logger)
version := instance.versionMajorMinor
var wg sync.WaitGroup
defer wg.Wait()
for _, scraper := range e.scrapers {
@ -169,7 +157,7 @@ func (e *Exporter) scrape(ctx context.Context, ch chan<- prometheus.Metric) floa
label := "collect." + scraper.Name()
scrapeTime := time.Now()
collectorSuccess := 1.0
if err := scraper.Scrape(ctx, db, ch, log.With(e.logger, "scraper", scraper.Name())); err != nil {
if err := scraper.Scrape(ctx, instance, ch, log.With(e.logger, "scraper", scraper.Name())); err != nil {
level.Error(e.logger).Log("msg", "Error from scraper", "scraper", scraper.Name(), "target", e.getTargetFromDsn(), "err", err)
collectorSuccess = 0.0
}
@ -189,19 +177,3 @@ func (e *Exporter) getTargetFromDsn() string {
}
return dsnConfig.Addr
}
func getMySQLVersion(db *sql.DB, logger log.Logger) float64 {
var versionStr string
var versionNum float64
if err := db.QueryRow(versionQuery).Scan(&versionStr); err == nil {
versionNum, _ = strconv.ParseFloat(versionRE.FindString(versionStr), 64)
} else {
level.Debug(logger).Log("msg", "Error querying version", "err", err)
}
// If we can't match/parse the version, set it some big value that matches all versions.
if versionNum == 0 {
level.Debug(logger).Log("msg", "Error parsing version string", "version", versionStr)
versionNum = 999
}
return versionNum
}

View File

@ -15,12 +15,9 @@ package collector
import (
"context"
"database/sql"
"os"
"testing"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/smartystreets/goconvey/convey"
@ -68,20 +65,3 @@ func TestExporter(t *testing.T) {
}
})
}
func TestGetMySQLVersion(t *testing.T) {
if testing.Short() {
t.Skip("-short is passed, skipping test")
}
logger := log.NewLogfmtLogger(os.Stderr)
logger = level.NewFilter(logger, level.AllowDebug())
convey.Convey("Version parsing", t, func() {
db, err := sql.Open("mysql", dsn)
convey.So(err, convey.ShouldBeNil)
defer db.Close()
convey.So(getMySQLVersion(db, logger), convey.ShouldBeBetweenOrEqual, 5.6, 12.0)
})
}

View File

@ -99,7 +99,8 @@ func (ScrapeGlobalStatus) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeGlobalStatus) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeGlobalStatus) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
globalStatusRows, err := db.QueryContext(ctx, globalStatusQuery)
if err != nil {
return err

View File

@ -30,6 +30,7 @@ func TestScrapeGlobalStatus(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"Variable_name", "Value"}
rows := sqlmock.NewRows(columns).
@ -63,7 +64,7 @@ func TestScrapeGlobalStatus(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeGlobalStatus{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeGlobalStatus{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -138,7 +138,8 @@ func (ScrapeGlobalVariables) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeGlobalVariables) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeGlobalVariables) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
globalVariablesRows, err := db.QueryContext(ctx, globalVariablesQuery)
if err != nil {
return err

View File

@ -30,6 +30,7 @@ func TestScrapeGlobalVariables(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"Variable_name", "Value"}
rows := sqlmock.NewRows(columns).
@ -52,7 +53,7 @@ func TestScrapeGlobalVariables(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeGlobalVariables{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeGlobalVariables{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

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

View File

@ -65,6 +65,7 @@ func TestScrapeHeartbeat(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
rows := sqlmock.NewRows(tt.Columns).
AddRow("1487597613.001320", "1487598113.448042", 1)
@ -72,7 +73,7 @@ func TestScrapeHeartbeat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeHeartbeat{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeHeartbeat{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -70,7 +69,8 @@ func (ScrapeAutoIncrementColumns) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeAutoIncrementColumns) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeAutoIncrementColumns) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
autoIncrementRows, err := db.QueryContext(ctx, infoSchemaAutoIncrementQuery)
if err != nil {
return err

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"fmt"
"strings"
@ -161,8 +160,9 @@ func (ScrapeClientStat) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeClientStat) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeClientStat) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var varName, varVal string
db := instance.getDB()
err := db.QueryRowContext(ctx, userstatCheckQuery).Scan(&varName, &varVal)
if err != nil {
level.Debug(logger).Log("msg", "Detailed client stats are not available.")

View File

@ -30,6 +30,7 @@ func TestScrapeClientStat(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
mock.ExpectQuery(sanitizeQuery(userstatCheckQuery)).WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
AddRow("userstat", "ON"))
@ -41,7 +42,7 @@ func TestScrapeClientStat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeClientStat{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeClientStat{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -77,7 +76,8 @@ func (ScrapeInnodbCmp) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeInnodbCmp) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeInnodbCmp) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
informationSchemaInnodbCmpRows, err := db.QueryContext(ctx, innodbCmpQuery)
if err != nil {
return err

View File

@ -30,6 +30,7 @@ func TestScrapeInnodbCmp(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"page_size", "compress_ops", "compress_ops_ok", "compress_time", "uncompress_ops", "uncompress_time"}
rows := sqlmock.NewRows(columns).
@ -38,7 +39,7 @@ func TestScrapeInnodbCmp(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeInnodbCmp{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeInnodbCmp{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -72,7 +71,8 @@ func (ScrapeInnodbCmpMem) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeInnodbCmpMem) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeInnodbCmpMem) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
informationSchemaInnodbCmpMemRows, err := db.QueryContext(ctx, innodbCmpMemQuery)
if err != nil {
return err

View File

@ -30,6 +30,7 @@ func TestScrapeInnodbCmpMem(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"page_size", "buffer_pool", "pages_used", "pages_free", "relocation_ops", "relocation_time"}
rows := sqlmock.NewRows(columns).
@ -38,7 +39,7 @@ func TestScrapeInnodbCmpMem(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeInnodbCmpMem{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeInnodbCmpMem{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"errors"
"fmt"
"regexp"
@ -93,10 +92,11 @@ func (ScrapeInnodbMetrics) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeInnodbMetrics) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeInnodbMetrics) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var enabledColumnName string
var query string
db := instance.getDB()
err := db.QueryRowContext(ctx, infoSchemaInnodbMetricsEnabledColumnQuery).Scan(&enabledColumnName)
if err != nil {
return err

View File

@ -31,6 +31,7 @@ func TestScrapeInnodbMetrics(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
enabledColumnName := []string{"COLUMN_NAME"}
rows := sqlmock.NewRows(enabledColumnName).
@ -53,7 +54,7 @@ func TestScrapeInnodbMetrics(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeInnodbMetrics{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeInnodbMetrics{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"errors"
"fmt"
@ -85,9 +84,10 @@ func (ScrapeInfoSchemaInnodbTablespaces) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeInfoSchemaInnodbTablespaces) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeInfoSchemaInnodbTablespaces) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var tablespacesTablename string
var query string
db := instance.getDB()
err := db.QueryRowContext(ctx, innodbTablespacesTablenameQuery).Scan(&tablespacesTablename)
if err != nil {
return err

View File

@ -31,6 +31,7 @@ func TestScrapeInfoSchemaInnodbTablespaces(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"TABLE_NAME"}
rows := sqlmock.NewRows(columns).
@ -47,7 +48,7 @@ func TestScrapeInfoSchemaInnodbTablespaces(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeInfoSchemaInnodbTablespaces{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeInfoSchemaInnodbTablespaces{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"fmt"
"reflect"
"sort"
@ -97,11 +96,12 @@ func (ScrapeProcesslist) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeProcesslist) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeProcesslist) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
processQuery := fmt.Sprintf(
infoSchemaProcesslistQuery,
*processlistMinTime,
)
db := instance.getDB()
processlistRows, err := db.QueryContext(ctx, processQuery)
if err != nil {
return err

View File

@ -40,6 +40,7 @@ func TestScrapeProcesslist(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
query := fmt.Sprintf(infoSchemaProcesslistQuery, 0)
columns := []string{"user", "host", "command", "state", "processes", "seconds"}
@ -56,7 +57,7 @@ func TestScrapeProcesslist(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeProcesslist{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeProcesslist{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"strconv"
"strings"
@ -56,7 +55,8 @@ var (
}
)
func processQueryResponseTimeTable(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, query string, i int) error {
func processQueryResponseTimeTable(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, query string, i int) error {
db := instance.getDB()
queryDistributionRows, err := db.QueryContext(ctx, query)
if err != nil {
return err
@ -119,8 +119,9 @@ func (ScrapeQueryResponseTime) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeQueryResponseTime) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeQueryResponseTime) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var queryStats uint8
db := instance.getDB()
err := db.QueryRowContext(ctx, queryResponseCheckQuery).Scan(&queryStats)
if err != nil {
level.Debug(logger).Log("msg", "Query response time distribution is not available.")
@ -132,7 +133,7 @@ func (ScrapeQueryResponseTime) Scrape(ctx context.Context, db *sql.DB, ch chan<-
}
for i, query := range queryResponseTimeQueries {
err := processQueryResponseTimeTable(ctx, db, ch, query, i)
err := processQueryResponseTimeTable(ctx, instance, ch, query, i)
// The first query should not fail if query_response_time_stats is ON,
// unlike the other two when the read/write tables exist only with Percona Server 5.6/5.7.
if i == 0 && err != nil {

View File

@ -30,6 +30,7 @@ func TestScrapeQueryResponseTime(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
mock.ExpectQuery(queryResponseCheckQuery).WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(1))
@ -52,7 +53,7 @@ func TestScrapeQueryResponseTime(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeQueryResponseTime{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeQueryResponseTime{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
@ -84,7 +83,8 @@ func (ScrapeReplicaHost) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeReplicaHost) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeReplicaHost) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
replicaHostRows, err := db.QueryContext(ctx, replicaHostQuery)
if err != nil {
if mysqlErr, ok := err.(*MySQL.MySQLError); ok { // Now the error number is accessible directly

View File

@ -30,6 +30,7 @@ func TestScrapeReplicaHost(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"SERVER_ID", "ROLE", "CPU", "MASTER_SLAVE_LATENCY_IN_MICROSECONDS", "REPLICA_LAG_IN_MILLISECONDS", "LOG_STREAM_SPEED_IN_KiB_PER_SECOND", "CURRENT_REPLAY_LATENCY_IN_MICROSECONDS"}
rows := sqlmock.NewRows(columns).
@ -39,7 +40,7 @@ func TestScrapeReplicaHost(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeReplicaHost{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeReplicaHost{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
@ -72,9 +71,10 @@ func (ScrapeSchemaStat) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeSchemaStat) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeSchemaStat) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var varName, varVal string
db := instance.getDB()
err := db.QueryRowContext(ctx, userstatCheckQuery).Scan(&varName, &varVal)
if err != nil {
level.Debug(logger).Log("msg", "Detailed schema stats are not available.")

View File

@ -29,6 +29,7 @@ func TestScrapeSchemaStat(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
mock.ExpectQuery(sanitizeQuery(userstatCheckQuery)).WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
AddRow("userstat", "ON"))
@ -41,7 +42,7 @@ func TestScrapeSchemaStat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeSchemaStat{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeSchemaStat{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"fmt"
"strings"
@ -97,8 +96,9 @@ func (ScrapeTableSchema) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeTableSchema) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeTableSchema) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var dbList []string
db := instance.getDB()
if *tableSchemaDatabases == "*" {
dbListRows, err := db.QueryContext(ctx, dbListQuery)
if err != nil {

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
@ -72,8 +71,9 @@ func (ScrapeTableStat) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeTableStat) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeTableStat) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var varName, varVal string
db := instance.getDB()
err := db.QueryRowContext(ctx, userstatCheckQuery).Scan(&varName, &varVal)
if err != nil {
level.Debug(logger).Log("msg", "Detailed table stats are not available.")

View File

@ -29,6 +29,7 @@ func TestScrapeTableStat(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
mock.ExpectQuery(sanitizeQuery(userstatCheckQuery)).WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
AddRow("userstat", "ON"))
@ -42,7 +43,7 @@ func TestScrapeTableStat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeTableStat{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeTableStat{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"fmt"
"strings"
@ -157,8 +156,9 @@ func (ScrapeUserStat) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeUserStat) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeUserStat) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var varName, varVal string
db := instance.getDB()
err := db.QueryRowContext(ctx, userstatCheckQuery).Scan(&varName, &varVal)
if err != nil {
level.Debug(logger).Log("msg", "Detailed user stats are not available.")

View File

@ -30,6 +30,7 @@ func TestScrapeUserStat(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
mock.ExpectQuery(sanitizeQuery(userstatCheckQuery)).WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).
AddRow("userstat", "ON"))
@ -41,7 +42,7 @@ func TestScrapeUserStat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeUserStat{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeUserStat{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

114
collector/instance.go Normal file
View File

@ -0,0 +1,114 @@
// Copyright 2024 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 collector
import (
"database/sql"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/blang/semver/v4"
)
const (
FlavorMySQL = "mysql"
FlavorMariaDB = "mariadb"
)
type instance struct {
db *sql.DB
flavor string
version semver.Version
versionMajorMinor float64
}
func newInstance(dsn string) (*instance, error) {
i := &instance{}
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
i.db = db
version, versionString, err := queryVersion(db)
if err != nil {
db.Close()
return nil, err
}
i.version = version
versionMajorMinor, err := strconv.ParseFloat(fmt.Sprintf("%d.%d", i.version.Major, i.version.Minor), 64)
if err != nil {
db.Close()
return nil, err
}
i.versionMajorMinor = versionMajorMinor
if strings.Contains(strings.ToLower(versionString), "mariadb") {
i.flavor = FlavorMariaDB
} else {
i.flavor = FlavorMySQL
}
return i, nil
}
func (i *instance) getDB() *sql.DB {
return i.db
}
func (i *instance) Close() error {
return i.db.Close()
}
// Ping checks connection availability and possibly invalidates the connection if it fails.
func (i *instance) Ping() error {
if err := i.db.Ping(); err != nil {
if cerr := i.Close(); cerr != nil {
return err
}
return err
}
return nil
}
// The result of SELECT version() is something like:
// for MariaDB: "10.5.17-MariaDB-1:10.5.17+maria~ubu2004-log"
// for MySQL: "8.0.36-28.1"
var versionRegex = regexp.MustCompile(`^((\d+)(\.\d+)(\.\d+))`)
func queryVersion(db *sql.DB) (semver.Version, string, error) {
var version string
err := db.QueryRow("SELECT @@version;").Scan(&version)
if err != nil {
return semver.Version{}, version, err
}
matches := versionRegex.FindStringSubmatch(version)
if len(matches) > 1 {
parsedVersion, err := semver.ParseTolerant(matches[1])
if err != nil {
return semver.Version{}, version, fmt.Errorf("could not parse version from %q", matches[1])
}
return parsedVersion, version, nil
}
return semver.Version{}, version, fmt.Errorf("could not parse version from %q", version)
}

View File

@ -120,7 +120,8 @@ func (ScrapeUser) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeUser) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeUser) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
var (
userRows *sql.Rows
err error

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"fmt"
"github.com/alecthomas/kingpin/v2"
@ -168,13 +167,14 @@ func (ScrapePerfEventsStatements) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfEventsStatements) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfEventsStatements) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
perfQuery := fmt.Sprintf(
perfEventsStatementsQuery,
*perfEventsStatementsDigestTextLimit,
*perfEventsStatementsTimeLimit,
*perfEventsStatementsLimit,
)
db := instance.getDB()
// Timers here are returned in picoseconds.
perfSchemaEventsStatementsRows, err := db.QueryContext(ctx, perfQuery)
if err != nil {

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -177,7 +176,8 @@ func (ScrapePerfEventsStatementsSum) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfEventsStatementsSum) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfEventsStatementsSum) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
// Timers here are returned in picoseconds.
perfEventsStatementsSumRows, err := db.QueryContext(ctx, perfEventsStatementsSumQuery)
if err != nil {

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -61,7 +60,8 @@ func (ScrapePerfEventsWaits) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfEventsWaits) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfEventsWaits) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
// Timers here are returned in picoseconds.
perfSchemaEventsWaitsRows, err := db.QueryContext(ctx, perfEventsWaitsQuery)
if err != nil {

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -70,7 +69,8 @@ func (ScrapePerfFileEvents) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfFileEvents) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfFileEvents) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
// Timers here are returned in picoseconds.
perfSchemaFileEventsRows, err := db.QueryContext(ctx, perfFileEventsQuery)
if err != nil {

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"strings"
"github.com/alecthomas/kingpin/v2"
@ -80,7 +79,8 @@ func (ScrapePerfFileInstances) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfFileInstances) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfFileInstances) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
// Timers here are returned in picoseconds.
perfSchemaFileInstancesRows, err := db.QueryContext(ctx, perfFileInstancesQuery, *performanceSchemaFileInstancesFilter)
if err != nil {

View File

@ -37,6 +37,7 @@ func TestScrapePerfFileInstances(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"FILE_NAME", "EVENT_NAME", "COUNT_READ", "COUNT_WRITE", "SUM_NUMBER_OF_BYTES_READ", "SUM_NUMBER_OF_BYTES_WRITE"}
@ -48,7 +49,7 @@ func TestScrapePerfFileInstances(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapePerfFileInstances{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapePerfFileInstances{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
panic(fmt.Sprintf("error calling function on test: %s", err))
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -64,7 +63,8 @@ func (ScrapePerfIndexIOWaits) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfIndexIOWaits) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfIndexIOWaits) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
perfSchemaIndexWaitsRows, err := db.QueryContext(ctx, perfIndexIOWaitsQuery)
if err != nil {
return err

View File

@ -30,6 +30,7 @@ func TestScrapePerfIndexIOWaits(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"OBJECT_SCHEMA", "OBJECT_NAME", "INDEX_NAME", "COUNT_FETCH", "COUNT_INSERT", "COUNT_UPDATE", "COUNT_DELETE", "SUM_TIMER_FETCH", "SUM_TIMER_INSERT", "SUM_TIMER_UPDATE", "SUM_TIMER_DELETE"}
rows := sqlmock.NewRows(columns).
@ -40,7 +41,7 @@ func TestScrapePerfIndexIOWaits(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapePerfIndexIOWaits{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapePerfIndexIOWaits{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"strings"
"github.com/alecthomas/kingpin/v2"
@ -79,7 +78,8 @@ func (ScrapePerfMemoryEvents) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfMemoryEvents) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfMemoryEvents) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
perfSchemaMemoryEventsRows, err := db.QueryContext(ctx, perfMemoryEventsQuery)
if err != nil {
return err

View File

@ -37,6 +37,7 @@ func TestScrapePerfMemoryEvents(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{
"EVENT_NAME",
@ -54,7 +55,7 @@ func TestScrapePerfMemoryEvents(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapePerfMemoryEvents{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapePerfMemoryEvents{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
panic(fmt.Sprintf("error calling function on test: %s", err))
}
close(ch)

View File

@ -15,7 +15,6 @@ package collector
import (
"context"
"database/sql"
"time"
"github.com/go-kit/log"
@ -101,7 +100,8 @@ func (ScrapePerfReplicationApplierStatsByWorker) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfReplicationApplierStatsByWorker) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfReplicationApplierStatsByWorker) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
perfReplicationApplierStatsByWorkerRows, err := db.QueryContext(ctx, perfReplicationApplierStatsByWorkerQuery)
if err != nil {
return err

View File

@ -31,6 +31,7 @@ func TestScrapePerfReplicationApplierStatsByWorker(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{
"CHANNEL_NAME",
@ -54,7 +55,7 @@ func TestScrapePerfReplicationApplierStatsByWorker(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapePerfReplicationApplierStatsByWorker{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapePerfReplicationApplierStatsByWorker{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -79,7 +79,8 @@ func (ScrapePerfReplicationGroupMemberStats) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfReplicationGroupMemberStats) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfReplicationGroupMemberStats) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
rows, err := db.QueryContext(ctx, perfReplicationGroupMemberStatsQuery)
if err != nil {
return err

View File

@ -30,6 +30,7 @@ func TestScrapePerfReplicationGroupMemberStats(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{
"CHANNEL_NAME",
@ -66,7 +67,7 @@ func TestScrapePerfReplicationGroupMemberStats(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapePerfReplicationGroupMemberStats{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapePerfReplicationGroupMemberStats{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -16,9 +16,10 @@ package collector
import (
"context"
"database/sql"
"strings"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
"strings"
)
const perfReplicationGroupMembersQuery = `
@ -44,7 +45,8 @@ func (ScrapePerfReplicationGroupMembers) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfReplicationGroupMembers) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfReplicationGroupMembers) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
perfReplicationGroupMembersRows, err := db.QueryContext(ctx, perfReplicationGroupMembersQuery)
if err != nil {
return err

View File

@ -15,12 +15,13 @@ package collector
import (
"context"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/smartystreets/goconvey/convey"
"testing"
)
func TestScrapePerfReplicationGroupMembers(t *testing.T) {
@ -29,6 +30,7 @@ func TestScrapePerfReplicationGroupMembers(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{
"CHANNEL_NAME",
@ -49,7 +51,7 @@ func TestScrapePerfReplicationGroupMembers(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapePerfReplicationGroupMembers{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapePerfReplicationGroupMembers{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)
@ -82,6 +84,7 @@ func TestScrapePerfReplicationGroupMembersMySQL57(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{
"CHANNEL_NAME",
@ -100,7 +103,7 @@ func TestScrapePerfReplicationGroupMembersMySQL57(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapePerfReplicationGroupMembers{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapePerfReplicationGroupMembers{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -65,7 +64,8 @@ func (ScrapePerfTableIOWaits) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfTableIOWaits) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfTableIOWaits) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
perfSchemaTableWaitsRows, err := db.QueryContext(ctx, perfTableIOWaitsQuery)
if err != nil {
return err

View File

@ -17,7 +17,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -94,7 +93,8 @@ func (ScrapePerfTableLockWaits) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfTableLockWaits) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapePerfTableLockWaits) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
perfSchemaTableLockWaitsRows, err := db.QueryContext(ctx, perfTableLockWaitsQuery)
if err != nil {
return err

View File

@ -15,7 +15,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
_ "github.com/go-sql-driver/mysql"
@ -35,5 +34,5 @@ type Scraper interface {
Version() float64
// Scrape collects data from database connection and sends it over channel as prometheus metric.
Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error
Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error
}

View File

@ -63,11 +63,12 @@ func (ScrapeSlaveHosts) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeSlaveHosts) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeSlaveHosts) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var (
slaveHostsRows *sql.Rows
err error
)
db := instance.getDB()
// Try the both syntax for MySQL 8.0 and MySQL 8.4
if slaveHostsRows, err = db.QueryContext(ctx, slaveHostsQuery); err != nil {
if slaveHostsRows, err = db.QueryContext(ctx, showReplicasQuery); err != nil {

View File

@ -30,6 +30,7 @@ func TestScrapeSlaveHostsOldFormat(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"Server_id", "Host", "Port", "Rpl_recovery_rank", "Master_id"}
rows := sqlmock.NewRows(columns).
@ -39,7 +40,7 @@ func TestScrapeSlaveHostsOldFormat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeSlaveHosts{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeSlaveHosts{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)
@ -68,6 +69,7 @@ func TestScrapeSlaveHostsNewFormat(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"Server_id", "Host", "Port", "Master_id", "Slave_UUID"}
rows := sqlmock.NewRows(columns).
@ -77,7 +79,7 @@ func TestScrapeSlaveHostsNewFormat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeSlaveHosts{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeSlaveHosts{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)
@ -106,6 +108,7 @@ func TestScrapeSlaveHostsWithoutSlaveUuid(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"Server_id", "Host", "Port", "Master_id"}
rows := sqlmock.NewRows(columns).
@ -115,7 +118,7 @@ func TestScrapeSlaveHostsWithoutSlaveUuid(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeSlaveHosts{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeSlaveHosts{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -69,11 +69,12 @@ func (ScrapeSlaveStatus) Version() float64 {
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeSlaveStatus) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeSlaveStatus) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
var (
slaveStatusRows *sql.Rows
err error
)
db := instance.getDB()
// Try the both syntax for MySQL/Percona and MariaDB
for _, query := range slaveStatusQueries {
slaveStatusRows, err = db.QueryContext(ctx, query)

View File

@ -30,6 +30,7 @@ func TestScrapeSlaveStatus(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{"Master_Host", "Read_Master_Log_Pos", "Slave_IO_Running", "Slave_SQL_Running", "Seconds_Behind_Master"}
rows := sqlmock.NewRows(columns).
@ -38,7 +39,7 @@ func TestScrapeSlaveStatus(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeSlaveStatus{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeSlaveStatus{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -15,7 +15,6 @@ package collector
import (
"context"
"database/sql"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
@ -99,7 +98,9 @@ func (ScrapeSysUserSummary) Version() float64 {
}
// Scrape the information from sys.user_summary, creating a metric for each value of each row, labeled with the user
func (ScrapeSysUserSummary) Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric, logger log.Logger) error {
func (ScrapeSysUserSummary) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error {
db := instance.getDB()
userSummaryRows, err := db.QueryContext(ctx, sysUserSummaryQuery)
if err != nil {

View File

@ -16,14 +16,15 @@ package collector
import (
"context"
"database/sql/driver"
"regexp"
"strconv"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/go-kit/log"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/smartystreets/goconvey/convey"
"regexp"
"strconv"
"testing"
)
func TestScrapeSysUserSummary(t *testing.T) {
@ -33,6 +34,7 @@ func TestScrapeSysUserSummary(t *testing.T) {
t.Fatalf("error opening a stub database connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{
"user",
@ -111,7 +113,7 @@ func TestScrapeSysUserSummary(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = (ScrapeSysUserSummary{}).Scrape(context.Background(), db, ch, log.NewNopLogger()); err != nil {
if err = (ScrapeSysUserSummary{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.21
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/blang/semver/v4 v4.0.0
github.com/go-kit/log v0.2.1
github.com/go-sql-driver/mysql v1.8.1
github.com/google/go-cmp v0.6.0

2
go.sum
View File

@ -8,6 +8,8 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAu
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=