Skip to content

Commit 5ad6fbd

Browse files
committed
Move Exporter to /collector so it can be used as library
1 parent e755b01 commit 5ad6fbd

File tree

4 files changed

+399
-333
lines changed

4 files changed

+399
-333
lines changed

collector/exporter.go

+364
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
package collector
2+
3+
import (
4+
"database/sql"
5+
"time"
6+
7+
_ "github.com/go-sql-driver/mysql"
8+
"github.com/prometheus/client_golang/prometheus"
9+
"github.com/prometheus/common/log"
10+
)
11+
12+
// Metric name parts.
13+
const (
14+
// Subsystem(s).
15+
exporter = "exporter"
16+
)
17+
18+
// SQL Queries.
19+
const (
20+
sessionSettingsQuery = `SET SESSION log_slow_filter = 'tmp_table_on_disk,filesort_on_disk'`
21+
upQuery = `SELECT 1`
22+
)
23+
24+
// Metric descriptors.
25+
var (
26+
scrapeDurationDesc = prometheus.NewDesc(
27+
prometheus.BuildFQName(namespace, exporter, "collector_duration_seconds"),
28+
"Collector time duration.",
29+
[]string{"collector"}, nil,
30+
)
31+
)
32+
33+
// Collect defines which metrics we should collect
34+
type Collect struct {
35+
SlowLogFilter bool
36+
Processlist bool
37+
TableSchema bool
38+
InnodbTablespaces bool
39+
InnodbMetrics bool
40+
GlobalStatus bool
41+
GlobalVariables bool
42+
SlaveStatus bool
43+
AutoIncrementColumns bool
44+
BinlogSize bool
45+
PerfTableIOWaits bool
46+
PerfIndexIOWaits bool
47+
PerfTableLockWaits bool
48+
PerfEventsStatements bool
49+
PerfEventsWaits bool
50+
PerfFileEvents bool
51+
PerfFileInstances bool
52+
UserStat bool
53+
ClientStat bool
54+
TableStat bool
55+
QueryResponseTime bool
56+
EngineTokudbStatus bool
57+
EngineInnodbStatus bool
58+
Heartbeat bool
59+
HeartbeatDatabase string
60+
HeartbeatTable string
61+
}
62+
63+
// Exporter collects MySQL metrics. It implements prometheus.Collector.
64+
type Exporter struct {
65+
dsn string
66+
collect Collect
67+
error prometheus.Gauge
68+
totalScrapes prometheus.Counter
69+
scrapeErrors *prometheus.CounterVec
70+
mysqldUp prometheus.Gauge
71+
}
72+
73+
// New returns a new MySQL exporter for the provided DSN.
74+
func New(dsn string, collect Collect) *Exporter {
75+
return &Exporter{
76+
dsn: dsn,
77+
collect: collect,
78+
totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
79+
Namespace: namespace,
80+
Subsystem: exporter,
81+
Name: "scrapes_total",
82+
Help: "Total number of times MySQL was scraped for metrics.",
83+
}),
84+
scrapeErrors: prometheus.NewCounterVec(prometheus.CounterOpts{
85+
Namespace: namespace,
86+
Subsystem: exporter,
87+
Name: "scrape_errors_total",
88+
Help: "Total number of times an error occurred scraping a MySQL.",
89+
}, []string{"collector"}),
90+
error: prometheus.NewGauge(prometheus.GaugeOpts{
91+
Namespace: namespace,
92+
Subsystem: exporter,
93+
Name: "last_scrape_error",
94+
Help: "Whether the last scrape of metrics from MySQL resulted in an error (1 for error, 0 for success).",
95+
}),
96+
mysqldUp: prometheus.NewGauge(prometheus.GaugeOpts{
97+
Namespace: namespace,
98+
Name: "up",
99+
Help: "Whether the MySQL server is up.",
100+
}),
101+
}
102+
}
103+
104+
// Describe implements prometheus.Collector.
105+
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
106+
// We cannot know in advance what metrics the exporter will generate
107+
// from MySQL. So we use the poor man's describe method: Run a collect
108+
// and send the descriptors of all the collected metrics. The problem
109+
// here is that we need to connect to the MySQL DB. If it is currently
110+
// unavailable, the descriptors will be incomplete. Since this is a
111+
// stand-alone exporter and not used as a library within other code
112+
// implementing additional metrics, the worst that can happen is that we
113+
// don't detect inconsistent metrics created by this exporter
114+
// itself. Also, a change in the monitored MySQL instance may change the
115+
// exported metrics during the runtime of the exporter.
116+
117+
metricCh := make(chan prometheus.Metric)
118+
doneCh := make(chan struct{})
119+
120+
go func() {
121+
for m := range metricCh {
122+
ch <- m.Desc()
123+
}
124+
close(doneCh)
125+
}()
126+
127+
e.Collect(metricCh)
128+
close(metricCh)
129+
<-doneCh
130+
}
131+
132+
// Collect implements prometheus.Collector.
133+
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
134+
e.scrape(ch)
135+
136+
ch <- e.totalScrapes
137+
ch <- e.error
138+
e.scrapeErrors.Collect(ch)
139+
ch <- e.mysqldUp
140+
}
141+
142+
func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
143+
e.totalScrapes.Inc()
144+
var err error
145+
146+
scrapeTime := time.Now()
147+
db, err := sql.Open("mysql", e.dsn)
148+
if err != nil {
149+
log.Errorln("Error opening connection to database:", err)
150+
return
151+
}
152+
defer db.Close()
153+
154+
// By design exporter should use maximum one connection per request.
155+
db.SetMaxOpenConns(1)
156+
db.SetMaxIdleConns(1)
157+
// Set max lifetime for a connection.
158+
db.SetConnMaxLifetime(1 * time.Minute)
159+
160+
isUpRows, err := db.Query(upQuery)
161+
if err != nil {
162+
log.Errorln("Error pinging mysqld:", err)
163+
e.mysqldUp.Set(0)
164+
return
165+
}
166+
isUpRows.Close()
167+
168+
e.mysqldUp.Set(1)
169+
170+
if e.collect.SlowLogFilter {
171+
sessionSettingsRows, err := db.Query(sessionSettingsQuery)
172+
if err != nil {
173+
log.Errorln("Error setting log_slow_filter:", err)
174+
return
175+
}
176+
sessionSettingsRows.Close()
177+
}
178+
179+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "connection")
180+
181+
if e.collect.GlobalStatus {
182+
scrapeTime = time.Now()
183+
if err = ScrapeGlobalStatus(db, ch); err != nil {
184+
log.Errorln("Error scraping for collect.global_status:", err)
185+
e.scrapeErrors.WithLabelValues("collect.global_status").Inc()
186+
}
187+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.global_status")
188+
}
189+
if e.collect.GlobalVariables {
190+
scrapeTime = time.Now()
191+
if err = ScrapeGlobalVariables(db, ch); err != nil {
192+
log.Errorln("Error scraping for collect.global_variables:", err)
193+
e.scrapeErrors.WithLabelValues("collect.global_variables").Inc()
194+
}
195+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.global_variables")
196+
}
197+
if e.collect.SlaveStatus {
198+
scrapeTime = time.Now()
199+
if err = ScrapeSlaveStatus(db, ch); err != nil {
200+
log.Errorln("Error scraping for collect.slave_status:", err)
201+
e.scrapeErrors.WithLabelValues("collect.slave_status").Inc()
202+
}
203+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.slave_status")
204+
}
205+
if e.collect.Processlist {
206+
scrapeTime = time.Now()
207+
if err = ScrapeProcesslist(db, ch); err != nil {
208+
log.Errorln("Error scraping for collect.info_schema.processlist:", err)
209+
e.scrapeErrors.WithLabelValues("collect.info_schema.processlist").Inc()
210+
}
211+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.processlist")
212+
}
213+
if e.collect.TableSchema {
214+
scrapeTime = time.Now()
215+
if err = ScrapeTableSchema(db, ch); err != nil {
216+
log.Errorln("Error scraping for collect.info_schema.tables:", err)
217+
e.scrapeErrors.WithLabelValues("collect.info_schema.tables").Inc()
218+
}
219+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.tables")
220+
}
221+
if e.collect.InnodbTablespaces {
222+
scrapeTime = time.Now()
223+
if err = ScrapeInfoSchemaInnodbTablespaces(db, ch); err != nil {
224+
log.Errorln("Error scraping for collect.info_schema.innodb_sys_tablespaces:", err)
225+
e.scrapeErrors.WithLabelValues("collect.info_schema.innodb_sys_tablespaces").Inc()
226+
}
227+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.innodb_sys_tablespaces")
228+
}
229+
if e.collect.InnodbMetrics {
230+
if err = ScrapeInnodbMetrics(db, ch); err != nil {
231+
log.Errorln("Error scraping for collect.info_schema.innodb_metrics:", err)
232+
e.scrapeErrors.WithLabelValues("collect.info_schema.innodb_metrics").Inc()
233+
}
234+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.innodb_metrics")
235+
}
236+
if e.collect.AutoIncrementColumns {
237+
scrapeTime = time.Now()
238+
if err = ScrapeAutoIncrementColumns(db, ch); err != nil {
239+
log.Errorln("Error scraping for collect.auto_increment.columns:", err)
240+
e.scrapeErrors.WithLabelValues("collect.auto_increment.columns").Inc()
241+
}
242+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.auto_increment.columns")
243+
}
244+
if e.collect.BinlogSize {
245+
scrapeTime = time.Now()
246+
if err = ScrapeBinlogSize(db, ch); err != nil {
247+
log.Errorln("Error scraping for collect.binlog_size:", err)
248+
e.scrapeErrors.WithLabelValues("collect.binlog_size").Inc()
249+
}
250+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.binlog_size")
251+
}
252+
if e.collect.PerfTableIOWaits {
253+
scrapeTime = time.Now()
254+
if err = ScrapePerfTableIOWaits(db, ch); err != nil {
255+
log.Errorln("Error scraping for collect.perf_schema.tableiowaits:", err)
256+
e.scrapeErrors.WithLabelValues("collect.perf_schema.tableiowaits").Inc()
257+
}
258+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.tableiowaits")
259+
}
260+
if e.collect.PerfIndexIOWaits {
261+
scrapeTime = time.Now()
262+
if err = ScrapePerfIndexIOWaits(db, ch); err != nil {
263+
log.Errorln("Error scraping for collect.perf_schema.indexiowaits:", err)
264+
e.scrapeErrors.WithLabelValues("collect.perf_schema.indexiowaits").Inc()
265+
}
266+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.indexiowaits")
267+
}
268+
if e.collect.PerfTableLockWaits {
269+
scrapeTime = time.Now()
270+
if err = ScrapePerfTableLockWaits(db, ch); err != nil {
271+
log.Errorln("Error scraping for collect.perf_schema.tablelocks:", err)
272+
e.scrapeErrors.WithLabelValues("collect.perf_schema.tablelocks").Inc()
273+
}
274+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.tablelocks")
275+
}
276+
if e.collect.PerfEventsStatements {
277+
scrapeTime = time.Now()
278+
if err = ScrapePerfEventsStatements(db, ch); err != nil {
279+
log.Errorln("Error scraping for collect.perf_schema.eventsstatements:", err)
280+
e.scrapeErrors.WithLabelValues("collect.perf_schema.eventsstatements").Inc()
281+
}
282+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.eventsstatements")
283+
}
284+
if e.collect.PerfEventsWaits {
285+
scrapeTime = time.Now()
286+
if err = ScrapePerfEventsWaits(db, ch); err != nil {
287+
log.Errorln("Error scraping for collect.perf_schema.eventswaits:", err)
288+
e.scrapeErrors.WithLabelValues("collect.perf_schema.eventswaits").Inc()
289+
}
290+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.eventswaits")
291+
}
292+
if e.collect.PerfFileEvents {
293+
scrapeTime = time.Now()
294+
if err = ScrapePerfFileEvents(db, ch); err != nil {
295+
log.Errorln("Error scraping for collect.perf_schema.file_events:", err)
296+
e.scrapeErrors.WithLabelValues("collect.perf_schema.file_events").Inc()
297+
}
298+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.file_events")
299+
}
300+
if e.collect.PerfFileInstances {
301+
scrapeTime = time.Now()
302+
if err = ScrapePerfFileInstances(db, ch); err != nil {
303+
log.Errorln("Error scraping for collect.perf_schema.file_instances:", err)
304+
e.scrapeErrors.WithLabelValues("collect.perf_schema.file_instances").Inc()
305+
}
306+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.file_instances")
307+
}
308+
if e.collect.UserStat {
309+
scrapeTime = time.Now()
310+
if err = ScrapeUserStat(db, ch); err != nil {
311+
log.Errorln("Error scraping for collect.info_schema.userstats:", err)
312+
e.scrapeErrors.WithLabelValues("collect.info_schema.userstats").Inc()
313+
}
314+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.userstats")
315+
}
316+
if e.collect.ClientStat {
317+
scrapeTime = time.Now()
318+
if err = ScrapeClientStat(db, ch); err != nil {
319+
log.Errorln("Error scraping for collect.info_schema.clientstats:", err)
320+
e.scrapeErrors.WithLabelValues("collect.info_schema.clientstats").Inc()
321+
}
322+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.clientstats")
323+
}
324+
if e.collect.TableStat {
325+
scrapeTime = time.Now()
326+
if err = ScrapeTableStat(db, ch); err != nil {
327+
log.Errorln("Error scraping for collect.info_schema.tablestats:", err)
328+
e.scrapeErrors.WithLabelValues("collect.info_schema.tablestats").Inc()
329+
}
330+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.tablestats")
331+
}
332+
if e.collect.QueryResponseTime {
333+
scrapeTime = time.Now()
334+
if err = ScrapeQueryResponseTime(db, ch); err != nil {
335+
log.Errorln("Error scraping for collect.info_schema.query_response_time:", err)
336+
e.scrapeErrors.WithLabelValues("collect.info_schema.query_response_time").Inc()
337+
}
338+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.query_response_time")
339+
}
340+
if e.collect.EngineTokudbStatus {
341+
scrapeTime = time.Now()
342+
if err = ScrapeEngineTokudbStatus(db, ch); err != nil {
343+
log.Errorln("Error scraping for collect.engine_tokudb_status:", err)
344+
e.scrapeErrors.WithLabelValues("collect.engine_tokudb_status").Inc()
345+
}
346+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.engine_tokudb_status")
347+
}
348+
if e.collect.EngineInnodbStatus {
349+
scrapeTime = time.Now()
350+
if err = ScrapeEngineInnodbStatus(db, ch); err != nil {
351+
log.Errorln("Error scraping for collect.engine_innodb_status:", err)
352+
e.scrapeErrors.WithLabelValues("collect.engine_innodb_status").Inc()
353+
}
354+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.engine_innodb_status")
355+
}
356+
if e.collect.Heartbeat {
357+
scrapeTime = time.Now()
358+
if err = ScrapeHeartbeat(db, ch, e.collect.HeartbeatDatabase, e.collect.HeartbeatTable); err != nil {
359+
log.Errorln("Error scraping for collect.heartbeat:", err)
360+
e.scrapeErrors.WithLabelValues("collect.heartbeat").Inc()
361+
}
362+
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.heartbeat")
363+
}
364+
}

collector/heartbeat.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ var (
4141
// ts varchar(26) NOT NULL,
4242
// server_id int unsigned NOT NULL PRIMARY KEY,
4343
// );
44-
func ScrapeHeartbeat(db *sql.DB, ch chan<- prometheus.Metric, collectDatabase, collectTable *string) error {
45-
query := fmt.Sprintf(heartbeatQuery, *collectDatabase, *collectTable)
44+
func ScrapeHeartbeat(db *sql.DB, ch chan<- prometheus.Metric, collectDatabase, collectTable string) error {
45+
query := fmt.Sprintf(heartbeatQuery, collectDatabase, collectTable)
4646
heartbeatRows, err := db.Query(query)
4747
if err != nil {
4848
return err

collector/heartbeat_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestScrapeHeartbeat(t *testing.T) {
2525
go func() {
2626
database := "heartbeat"
2727
table := "heartbeat"
28-
if err = ScrapeHeartbeat(db, ch, &database, &table); err != nil {
28+
if err = ScrapeHeartbeat(db, ch, database, table); err != nil {
2929
t.Errorf("error calling function on test: %s", err)
3030
}
3131
close(ch)

0 commit comments

Comments
 (0)