-
Notifications
You must be signed in to change notification settings - Fork 778
/
Copy pathexporter.go
187 lines (164 loc) · 5.24 KB
/
exporter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// Copyright 2018 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 (
"context"
"fmt"
"log/slog"
"strings"
"sync"
"time"
"github.com/alecthomas/kingpin/v2"
"github.com/go-sql-driver/mysql"
"github.com/prometheus/client_golang/prometheus"
)
// Metric name parts.
const (
// Subsystem(s).
exporter = "exporter"
)
// SQL queries and parameters.
const (
// 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`
)
// Tunable flags.
var (
exporterLockTimeout = kingpin.Flag(
"exporter.lock_wait_timeout",
"Set a lock_wait_timeout (in seconds) on the connection to avoid long metadata locking.",
).Default("2").Int()
enableExporterLockTimeout = kingpin.Flag(
"exporter.enable_lock_wait_timeout",
"Enable the lock_wait_timeout MySQL connection parameter.",
).Default("true").Bool()
slowLogFilter = kingpin.Flag(
"exporter.log_slow_filter",
"Add a log_slow_filter to avoid slow query logging of scrapes. NOTE: Not supported by Oracle MySQL.",
).Default("false").Bool()
)
// metric definition
var (
mysqlUp = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "up"),
"Whether the MySQL server is up.",
nil,
nil,
)
mysqlScrapeCollectorSuccess = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exporter, "collector_success"),
"mysqld_exporter: Whether a collector succeeded.",
[]string{"collector"},
nil,
)
mysqlScrapeDurationSeconds = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exporter, "collector_duration_seconds"),
"Collector time duration.",
[]string{"collector"}, nil,
)
)
// Verify if Exporter implements prometheus.Collector
var _ prometheus.Collector = (*Exporter)(nil)
// Exporter collects MySQL metrics. It implements prometheus.Collector.
type Exporter struct {
ctx context.Context
logger *slog.Logger
dsn string
scrapers []Scraper
instance *instance
}
// New returns a new MySQL exporter for the provided DSN.
func New(ctx context.Context, dsn string, scrapers []Scraper, logger *slog.Logger) *Exporter {
// Setup extra params for the DSN
dsnParams := []string{}
// Only set lock_wait_timeout if it is enabled
if *enableExporterLockTimeout {
dsnParams = append(dsnParams, fmt.Sprintf(timeoutParam, *exporterLockTimeout))
}
if *slowLogFilter {
dsnParams = append(dsnParams, sessionSettingsParam)
}
if strings.Contains(dsn, "?") {
dsn = dsn + "&"
} else {
dsn = dsn + "?"
}
dsn += strings.Join(dsnParams, "&")
return &Exporter{
ctx: ctx,
logger: logger,
dsn: dsn,
scrapers: scrapers,
}
}
// Describe implements prometheus.Collector.
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
ch <- mysqlUp
ch <- mysqlScrapeDurationSeconds
ch <- mysqlScrapeCollectorSuccess
}
// Collect implements prometheus.Collector.
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
up := e.scrape(e.ctx, ch)
ch <- prometheus.MustNewConstMetric(mysqlUp, prometheus.GaugeValue, up)
}
// scrape collects metrics from the target, returns an up metric value.
func (e *Exporter) scrape(ctx context.Context, ch chan<- prometheus.Metric) float64 {
var err error
scrapeTime := time.Now()
instance, err := newInstance(e.dsn)
if err != nil {
e.logger.Error("Error opening connection to database", "err", err)
return 0.0
}
defer instance.Close()
e.instance = instance
if err := instance.Ping(); err != nil {
e.logger.Error("Error pinging mysqld", "err", err)
return 0.0
}
ch <- prometheus.MustNewConstMetric(mysqlScrapeDurationSeconds, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "connection")
version := instance.versionMajorMinor
var wg sync.WaitGroup
defer wg.Wait()
for _, scraper := range e.scrapers {
if version < scraper.Version() {
continue
}
wg.Add(1)
go func(scraper Scraper) {
defer wg.Done()
label := "collect." + scraper.Name()
scrapeTime := time.Now()
collectorSuccess := 1.0
if err := scraper.Scrape(ctx, instance, ch, e.logger.With("scraper", scraper.Name())); err != nil {
e.logger.Error("Error from scraper", "scraper", scraper.Name(), "target", e.getTargetFromDsn(), "err", err)
collectorSuccess = 0.0
}
ch <- prometheus.MustNewConstMetric(mysqlScrapeCollectorSuccess, prometheus.GaugeValue, collectorSuccess, label)
ch <- prometheus.MustNewConstMetric(mysqlScrapeDurationSeconds, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), label)
}(scraper)
}
return 1.0
}
func (e *Exporter) getTargetFromDsn() string {
// Get target from DSN.
dsnConfig, err := mysql.ParseDSN(e.dsn)
if err != nil {
e.logger.Error("Error parsing DSN", "err", err)
return ""
}
return dsnConfig.Addr
}