Skip to content

Commit 74d5373

Browse files
authored
Merge pull request #21 from percona/PMM-1753_keep_MySQL_connections_open_between_scrapes
PMM-1753: Keep MySQL connections open between scrapes.
2 parents 8687569 + 793bfa9 commit 74d5373

File tree

4 files changed

+116
-61
lines changed

4 files changed

+116
-61
lines changed

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Running using ~/.my.cnf:
3535
Name | MySQL Version | Description
3636
-------------------------------------------------------|---------------|------------------------------------------------------------------------------------
3737
collect.auto_increment.columns | 5.1 | Collect auto_increment columns and max values from information_schema.
38-
collect.binlog_size | 5.1 | Collect the current size of all registered binlog files
38+
collect.binlog_size | 5.1 | Collect the current size of all registered binlog files.
3939
collect.engine_innodb_status | 5.1 | Collect from SHOW ENGINE INNODB STATUS.
4040
collect.engine_tokudb_status | 5.6 | Collect from SHOW ENGINE TOKUDB STATUS.
4141
collect.global_status | 5.1 | Collect from SHOW GLOBAL STATUS (Enabled by default)
@@ -64,7 +64,7 @@ collect.slave_status | 5.1 | Collect
6464
collect.heartbeat | 5.1 | Collect from [heartbeat](#heartbeat).
6565
collect.heartbeat.database | 5.1 | Database from where to collect heartbeat data. (default: heartbeat)
6666
collect.heartbeat.table | 5.1 | Table from where to collect heartbeat data. (default: heartbeat)
67-
67+
collect.all | - | Collect all metrics.
6868

6969
### General Flags
7070
Name | Description
@@ -73,6 +73,10 @@ config.my-cnf | Path to .my.cnf file to read MySQL
7373
log.level | Logging verbosity (default: info)
7474
exporter.lock_wait_timeout | Set a lock_wait_timeout on the connection to avoid long metadata locking. (default: 2 seconds)
7575
exporter.log_slow_filter | Add a log_slow_filter to avoid slow query logging of scrapes. NOTE: Not supported by Oracle MySQL.
76+
exporter.global-conn-pool | Use global connection pool instead of creating new pool for each http request.
77+
exporter.max-open-conns | Maximum number of open connections to the database. https://golang.org/pkg/database/sql/#DB.SetMaxOpenConns
78+
exporter.max-idle-conns | Maximum number of connections in the idle connection pool. https://golang.org/pkg/database/sql/#DB.SetMaxIdleConns
79+
exporter.conn-max-lifetime | Maximum amount of time a connection may be reused. https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime
7680
web.listen-address | Address to listen on for web interface and telemetry.
7781
web.telemetry-path | Path under which to expose metrics.
7882
version | Print the version information.

collector/exporter.go

+7-51
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@ package collector
22

33
import (
44
"database/sql"
5-
"flag"
6-
"fmt"
75
"regexp"
86
"strconv"
9-
"strings"
107
"sync"
118
"time"
129

@@ -23,24 +20,11 @@ const (
2320

2421
// SQL Queries.
2522
const (
26-
// System variable params formatting.
27-
// See: https://github.com/go-sql-driver/mysql#system-variables
28-
sessionSettingsParam = `log_slow_filter=%27tmp_table_on_disk,filesort_on_disk%27`
29-
timeoutParam = `lock_wait_timeout=%d`
30-
versionQuery = `SELECT @@version`
23+
versionQuery = `SELECT @@version`
3124
)
3225

3326
// Metric descriptors.
3427
var (
35-
exporterLockTimeout = flag.Int(
36-
"exporter.lock_wait_timeout", 2,
37-
"Set a lock_wait_timeout on the connection to avoid long metadata locking.",
38-
)
39-
slowLogFilter = flag.Bool(
40-
"exporter.log_slow_filter", false,
41-
"Add a log_slow_filter to avoid slow query logging of scrapes. NOTE: Not supported by Oracle MySQL.",
42-
)
43-
4428
scrapeDurationDesc = prometheus.NewDesc(
4529
prometheus.BuildFQName(namespace, exporter, "collector_duration_seconds"),
4630
"Collector time duration.",
@@ -50,7 +34,7 @@ var (
5034

5135
// Exporter collects MySQL metrics. It implements prometheus.Collector.
5236
type Exporter struct {
53-
dsn string
37+
db *sql.DB
5438
scrapers []Scraper
5539
error prometheus.Gauge
5640
totalScrapes prometheus.Counter
@@ -59,23 +43,9 @@ type Exporter struct {
5943
}
6044

6145
// New returns a new MySQL exporter for the provided DSN.
62-
func New(dsn string, scrapers []Scraper) *Exporter {
63-
// Setup extra params for the DSN, default to having a lock timeout.
64-
dsnParams := []string{fmt.Sprintf(timeoutParam, *exporterLockTimeout)}
65-
66-
if *slowLogFilter {
67-
dsnParams = append(dsnParams, sessionSettingsParam)
68-
}
69-
70-
if strings.Contains(dsn, "?") {
71-
dsn = dsn + "&"
72-
} else {
73-
dsn = dsn + "?"
74-
}
75-
dsn += strings.Join(dsnParams, "&")
76-
46+
func New(db *sql.DB, scrapers []Scraper) *Exporter {
7747
return &Exporter{
78-
dsn: dsn,
48+
db: db,
7949
scrapers: scrapers,
8050
totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
8151
Namespace: namespace,
@@ -146,21 +116,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
146116
var err error
147117

148118
scrapeTime := time.Now()
149-
db, err := sql.Open("mysql", e.dsn)
150-
if err != nil {
151-
log.Errorln("Error opening connection to database:", err)
152-
e.error.Set(1)
153-
return
154-
}
155-
defer db.Close()
156-
157-
// By design exporter should use maximum one connection per request.
158-
db.SetMaxOpenConns(1)
159-
db.SetMaxIdleConns(1)
160-
// Set max lifetime for a connection.
161-
db.SetConnMaxLifetime(1 * time.Minute)
162-
163-
if err = db.Ping(); err != nil {
119+
if err = e.db.Ping(); err != nil {
164120
log.Errorln("Error pinging mysqld:", err)
165121
e.mysqldUp.Set(0)
166122
e.error.Set(1)
@@ -169,7 +125,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
169125

170126
e.mysqldUp.Set(1)
171127

172-
versionNum := getMySQLVersion(db)
128+
versionNum := getMySQLVersion(e.db)
173129

174130
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "connection")
175131

@@ -184,7 +140,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
184140
defer wg.Done()
185141
label := "collect." + scraper.Name()
186142
scrapeTime := time.Now()
187-
if err := scraper.Scrape(db, ch); err != nil {
143+
if err := scraper.Scrape(e.db, ch); err != nil {
188144
log.Errorln("Error scraping for "+label+":", err)
189145
e.scrapeErrors.WithLabelValues(label).Inc()
190146
e.error.Set(1)

collector/exporter_test.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package collector
22

33
import (
4+
"database/sql"
45
"testing"
56

67
"github.com/prometheus/client_golang/prometheus"
@@ -16,7 +17,13 @@ func TestExporter(t *testing.T) {
1617
t.Skip("-short is passed, skipping test")
1718
}
1819

19-
exporter := New(dsn, []Scraper{
20+
db, err := sql.Open("mysql", dsn)
21+
if err != nil {
22+
t.Fatal(err)
23+
}
24+
defer db.Close()
25+
26+
exporter := New(db, []Scraper{
2027
ScrapeGlobalStatus{},
2128
})
2229

mysqld_exporter.go

+95-7
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ package main
22

33
import (
44
"crypto/tls"
5+
"database/sql"
56
"flag"
67
"fmt"
78
"io/ioutil"
89
"net/http"
910
"os"
1011
"path"
1112
"strings"
13+
"time"
1214

15+
_ "github.com/go-sql-driver/mysql"
1316
"github.com/prometheus/client_golang/prometheus"
1417
"github.com/prometheus/client_golang/prometheus/promhttp"
1518
"github.com/prometheus/common/log"
@@ -20,6 +23,13 @@ import (
2023
"github.com/percona/mysqld_exporter/collector"
2124
)
2225

26+
// System variable params formatting.
27+
// See: https://github.com/go-sql-driver/mysql#system-variables
28+
const (
29+
sessionSettingsParam = `log_slow_filter=%27tmp_table_on_disk,filesort_on_disk%27`
30+
timeoutParam = `lock_wait_timeout=%d`
31+
)
32+
2333
var (
2434
showVersion = flag.Bool(
2535
"version", false,
@@ -49,6 +59,35 @@ var (
4959
"web.ssl-key-file", "",
5060
"Path to SSL key file.",
5161
)
62+
exporterLockTimeout = flag.Int(
63+
"exporter.lock_wait_timeout", 2,
64+
"Set a lock_wait_timeout on the connection to avoid long metadata locking.",
65+
)
66+
exporterLogSlowFilter = flag.Bool(
67+
"exporter.log_slow_filter", false,
68+
"Add a log_slow_filter to avoid slow query logging of scrapes. NOTE: Not supported by Oracle MySQL.",
69+
)
70+
exporterGlobalConnPool = flag.Bool(
71+
"exporter.global-conn-pool", true,
72+
"Use global connection pool instead of creating new pool for each http request.",
73+
)
74+
exporterMaxOpenConns = flag.Int(
75+
"exporter.max-open-conns", 3,
76+
"Maximum number of open connections to the database. https://golang.org/pkg/database/sql/#DB.SetMaxOpenConns",
77+
)
78+
exporterMaxIdleConns = flag.Int(
79+
"exporter.max-idle-conns", 3,
80+
"Maximum number of connections in the idle connection pool. https://golang.org/pkg/database/sql/#DB.SetMaxIdleConns",
81+
)
82+
exporterConnMaxLifetime = flag.Duration(
83+
"exporter.conn-max-lifetime", 60*time.Second,
84+
"Maximum amount of time a connection may be reused. https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime",
85+
)
86+
collectAll = flag.Bool(
87+
"collect.all", false,
88+
"Collect all metrics.",
89+
)
90+
5291
dsn string
5392
)
5493

@@ -159,12 +198,11 @@ func init() {
159198
prometheus.MustRegister(version.NewCollector("mysqld_exporter"))
160199
}
161200

162-
func newHandler(cfg *webAuth, scrapers []collector.Scraper) http.HandlerFunc {
201+
func newHandler(cfg *webAuth, db *sql.DB, scrapers []collector.Scraper) http.HandlerFunc {
163202
return func(w http.ResponseWriter, r *http.Request) {
164203
filteredScrapers := scrapers
165204
params := r.URL.Query()["collect[]"]
166205
log.Debugln("collect query:", params)
167-
168206
if len(params) > 0 {
169207
filters := make(map[string]bool)
170208
for _, param := range params {
@@ -179,8 +217,20 @@ func newHandler(cfg *webAuth, scrapers []collector.Scraper) http.HandlerFunc {
179217
}
180218
}
181219

220+
// Copy db as local variable, so the pointer passed to newHandler doesn't get updated.
221+
db := db
222+
// If there is no global connection pool then create new.
223+
var err error
224+
if db == nil {
225+
db, err = newDB(dsn)
226+
if err != nil {
227+
log.Fatalln("Error opening connection to database:", err)
228+
}
229+
defer db.Close()
230+
}
231+
182232
registry := prometheus.NewRegistry()
183-
registry.MustRegister(collector.New(dsn, filteredScrapers))
233+
registry.MustRegister(collector.New(db, filteredScrapers))
184234

185235
gatherers := prometheus.Gatherers{
186236
prometheus.DefaultGatherer,
@@ -236,6 +286,7 @@ func main() {
236286
log.Infoln("Starting mysqld_exporter", version.Info())
237287
log.Infoln("Build context", version.BuildContext())
238288

289+
// Get DSN.
239290
dsn = os.Getenv("DATA_SOURCE_NAME")
240291
if len(dsn) == 0 {
241292
var err error
@@ -244,6 +295,30 @@ func main() {
244295
}
245296
}
246297

298+
// Setup extra params for the DSN, default to having a lock timeout.
299+
dsnParams := []string{fmt.Sprintf(timeoutParam, *exporterLockTimeout)}
300+
if *exporterLogSlowFilter {
301+
dsnParams = append(dsnParams, sessionSettingsParam)
302+
}
303+
304+
if strings.Contains(dsn, "?") {
305+
dsn = dsn + "&"
306+
} else {
307+
dsn = dsn + "?"
308+
}
309+
dsn += strings.Join(dsnParams, "&")
310+
311+
// Open global connection pool if requested.
312+
var db *sql.DB
313+
var err error
314+
if *exporterGlobalConnPool {
315+
db, err = newDB(dsn)
316+
if err != nil {
317+
log.Fatalln("Error opening connection to database:", err)
318+
}
319+
defer db.Close()
320+
}
321+
247322
cfg := &webAuth{}
248323
httpAuth := os.Getenv("HTTP_AUTH")
249324
if *webAuthFile != "" {
@@ -286,9 +361,9 @@ func main() {
286361

287362
// Defines what to scrape in each resolution.
288363
hr, mr, lr := enabledScrapers(scraperFlags)
289-
mux.Handle(*metricPath+"-hr", newHandler(cfg, hr))
290-
mux.Handle(*metricPath+"-mr", newHandler(cfg, mr))
291-
mux.Handle(*metricPath+"-lr", newHandler(cfg, lr))
364+
mux.Handle(*metricPath+"-hr", newHandler(cfg, db, hr))
365+
mux.Handle(*metricPath+"-mr", newHandler(cfg, db, mr))
366+
mux.Handle(*metricPath+"-lr", newHandler(cfg, db, lr))
292367

293368
// Log which scrapers are enabled.
294369
if len(hr) > 0 {
@@ -349,7 +424,7 @@ func main() {
349424

350425
func enabledScrapers(scraperFlags map[collector.Scraper]*bool) (hr, mr, lr []collector.Scraper) {
351426
for scraper, enabled := range scraperFlags {
352-
if *enabled {
427+
if *collectAll || *enabled {
353428
if _, ok := scrapersHr[scraper]; ok {
354429
hr = append(hr, scraper)
355430
}
@@ -364,3 +439,16 @@ func enabledScrapers(scraperFlags map[collector.Scraper]*bool) (hr, mr, lr []col
364439

365440
return hr, mr, lr
366441
}
442+
443+
func newDB(dsn string) (*sql.DB, error) {
444+
// Validate DSN, and open connection pool.
445+
db, err := sql.Open("mysql", dsn)
446+
if err != nil {
447+
return nil, err
448+
}
449+
db.SetMaxOpenConns(*exporterMaxOpenConns)
450+
db.SetMaxIdleConns(*exporterMaxIdleConns)
451+
db.SetConnMaxLifetime(*exporterConnMaxLifetime)
452+
453+
return db, nil
454+
}

0 commit comments

Comments
 (0)