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

Introduce Scraper interface

Signed-off-by: Kamil Dziedzic <arvenil@klecza.pl>
This commit is contained in:
Kamil Dziedzic
2018-02-01 23:33:02 +01:00
parent f76ef420f1
commit f556a61867
47 changed files with 604 additions and 540 deletions

View File

@ -38,7 +38,20 @@ var (
)
// ScrapeBinlogSize colects from `SHOW BINARY LOGS`.
func ScrapeBinlogSize(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeBinlogSize struct{}
// Name of the Scraper. Should be unique.
func (ScrapeBinlogSize) Name() string {
return "binlog_size"
}
// Help describes the role of the Scraper.
func (ScrapeBinlogSize) Help() string {
return "Collect the current size of all registered binlog files"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeBinlogSize) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
var logBin uint8
err := db.QueryRow(logbinQuery).Scan(&logBin)
if err != nil {

View File

@ -27,7 +27,7 @@ func TestScrapeBinlogSize(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeBinlogSize(db, ch); err != nil {
if err = (ScrapeBinlogSize{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -19,7 +19,20 @@ const (
)
// ScrapeEngineInnodbStatus scrapes from `SHOW ENGINE INNODB STATUS`.
func ScrapeEngineInnodbStatus(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeEngineInnodbStatus struct{}
// Name of the Scraper. Should be unique.
func (ScrapeEngineInnodbStatus) Name() string {
return "engine_innodb_status"
}
// Help describes the role of the Scraper.
func (ScrapeEngineInnodbStatus) Help() string {
return "Collect from SHOW ENGINE INNODB STATUS"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeEngineInnodbStatus) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
rows, err := db.Query(engineInnodbStatusQuery)
if err != nil {
return err

View File

@ -140,7 +140,7 @@ END OF INNODB MONITOR OUTPUT
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeEngineInnodbStatus(db, ch); err != nil {
if err = (ScrapeEngineInnodbStatus{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -16,26 +16,21 @@ const (
engineTokudbStatusQuery = `SHOW ENGINE TOKUDB STATUS`
)
func sanitizeTokudbMetric(metricName string) string {
replacements := map[string]string{
">": "",
",": "",
":": "",
"(": "",
")": "",
" ": "_",
"-": "_",
"+": "and",
"/": "and",
}
for r := range replacements {
metricName = strings.Replace(metricName, r, replacements[r], -1)
}
return metricName
// ScrapeEngineTokudbStatus scrapes from `SHOW ENGINE TOKUDB STATUS`.
type ScrapeEngineTokudbStatus struct{}
// Name of the Scraper. Should be unique.
func (ScrapeEngineTokudbStatus) Name() string {
return "engine_tokudb_status"
}
// ScrapeEngineTokudbStatus scrapes from `SHOW ENGINE TOKUDB STATUS`.
func ScrapeEngineTokudbStatus(db *sql.DB, ch chan<- prometheus.Metric) error {
// Help describes the role of the Scraper.
func (ScrapeEngineTokudbStatus) Help() string {
return "Collect from SHOW ENGINE TOKUDB STATUS"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeEngineTokudbStatus) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
tokudbRows, err := db.Query(engineTokudbStatusQuery)
if err != nil {
return err
@ -60,3 +55,21 @@ func ScrapeEngineTokudbStatus(db *sql.DB, ch chan<- prometheus.Metric) error {
}
return nil
}
func sanitizeTokudbMetric(metricName string) string {
replacements := map[string]string{
">": "",
",": "",
":": "",
"(": "",
")": "",
" ": "_",
"-": "_",
"+": "and",
"/": "and",
}
for r := range replacements {
metricName = strings.Replace(metricName, r, replacements[r], -1)
}
return metricName
}

View File

@ -44,7 +44,7 @@ func TestScrapeEngineTokudbStatus(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeEngineTokudbStatus(db, ch); err != nil {
if err = (ScrapeEngineTokudbStatus{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -4,6 +4,7 @@ import (
"database/sql"
"fmt"
"strings"
"sync"
"time"
_ "github.com/go-sql-driver/mysql"
@ -28,7 +29,7 @@ const (
upQuery = `SELECT 1`
)
// Metric descriptors.
// Tunable flags.
var (
exporterLockTimeout = kingpin.Flag(
"exporter.lock_wait_timeout",
@ -38,7 +39,10 @@ var (
"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 descriptors.
var (
scrapeDurationDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, exporter, "collector_duration_seconds"),
"Collector time duration.",
@ -46,42 +50,10 @@ var (
)
)
// Collect defines which metrics we should collect
type Collect struct {
Processlist bool
TableSchema bool
InnodbTablespaces bool
InnodbMetrics bool
GlobalStatus bool
GlobalVariables bool
SlaveStatus bool
AutoIncrementColumns bool
BinlogSize bool
PerfTableIOWaits bool
PerfIndexIOWaits bool
PerfTableLockWaits bool
PerfEventsStatements bool
PerfEventsWaits bool
PerfFileEvents bool
PerfFileInstances bool
PerfRepGroupMemberStats bool
UserStat bool
ClientStat bool
TableStat bool
InnodbCmp bool
InnodbCmpMem bool
QueryResponseTime bool
EngineTokudbStatus bool
EngineInnodbStatus bool
Heartbeat bool
HeartbeatDatabase string
HeartbeatTable string
}
// Exporter collects MySQL metrics. It implements prometheus.Collector.
type Exporter struct {
dsn string
collect Collect
scrapers []Scraper
error prometheus.Gauge
totalScrapes prometheus.Counter
scrapeErrors *prometheus.CounterVec
@ -89,7 +61,7 @@ type Exporter struct {
}
// New returns a new MySQL exporter for the provided DSN.
func New(dsn string, collect Collect) *Exporter {
func New(dsn string, scrapers []Scraper) *Exporter {
// Setup extra params for the DSN, default to having a lock timeout.
dsnParams := []string{fmt.Sprintf(timeoutParam, *exporterLockTimeout)}
@ -106,7 +78,7 @@ func New(dsn string, collect Collect) *Exporter {
return &Exporter{
dsn: dsn,
collect: collect,
scrapers: scrapers,
totalScrapes: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: exporter,
@ -203,237 +175,20 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "connection")
if e.collect.GlobalStatus {
scrapeTime = time.Now()
if err = ScrapeGlobalStatus(db, ch); err != nil {
log.Errorln("Error scraping for collect.global_status:", err)
e.scrapeErrors.WithLabelValues("collect.global_status").Inc()
wg := &sync.WaitGroup{}
defer wg.Wait()
for _, scraper := range e.scrapers {
wg.Add(1)
go func(scraper Scraper) {
defer wg.Done()
label := "collect." + scraper.Name()
scrapeTime := time.Now()
if err := scraper.Scrape(db, ch); err != nil {
log.Errorln("Error scraping for "+label+":", err)
e.scrapeErrors.WithLabelValues(label).Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.global_status")
}
if e.collect.GlobalVariables {
scrapeTime = time.Now()
if err = ScrapeGlobalVariables(db, ch); err != nil {
log.Errorln("Error scraping for collect.global_variables:", err)
e.scrapeErrors.WithLabelValues("collect.global_variables").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.global_variables")
}
if e.collect.SlaveStatus {
scrapeTime = time.Now()
if err = ScrapeSlaveStatus(db, ch); err != nil {
log.Errorln("Error scraping for collect.slave_status:", err)
e.scrapeErrors.WithLabelValues("collect.slave_status").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.slave_status")
}
if e.collect.Processlist {
scrapeTime = time.Now()
if err = ScrapeProcesslist(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.processlist:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.processlist").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.processlist")
}
if e.collect.TableSchema {
scrapeTime = time.Now()
if err = ScrapeTableSchema(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.tables:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.tables").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.tables")
}
if e.collect.InnodbTablespaces {
scrapeTime = time.Now()
if err = ScrapeInfoSchemaInnodbTablespaces(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.innodb_sys_tablespaces:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.innodb_sys_tablespaces").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.innodb_sys_tablespaces")
}
if e.collect.InnodbMetrics {
if err = ScrapeInnodbMetrics(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.innodb_metrics:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.innodb_metrics").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.innodb_metrics")
}
if e.collect.AutoIncrementColumns {
scrapeTime = time.Now()
if err = ScrapeAutoIncrementColumns(db, ch); err != nil {
log.Errorln("Error scraping for collect.auto_increment.columns:", err)
e.scrapeErrors.WithLabelValues("collect.auto_increment.columns").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.auto_increment.columns")
}
if e.collect.BinlogSize {
scrapeTime = time.Now()
if err = ScrapeBinlogSize(db, ch); err != nil {
log.Errorln("Error scraping for collect.binlog_size:", err)
e.scrapeErrors.WithLabelValues("collect.binlog_size").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.binlog_size")
}
if e.collect.PerfTableIOWaits {
scrapeTime = time.Now()
if err = ScrapePerfTableIOWaits(db, ch); err != nil {
log.Errorln("Error scraping for collect.perf_schema.tableiowaits:", err)
e.scrapeErrors.WithLabelValues("collect.perf_schema.tableiowaits").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.tableiowaits")
}
if e.collect.PerfIndexIOWaits {
scrapeTime = time.Now()
if err = ScrapePerfIndexIOWaits(db, ch); err != nil {
log.Errorln("Error scraping for collect.perf_schema.indexiowaits:", err)
e.scrapeErrors.WithLabelValues("collect.perf_schema.indexiowaits").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.indexiowaits")
}
if e.collect.PerfTableLockWaits {
scrapeTime = time.Now()
if err = ScrapePerfTableLockWaits(db, ch); err != nil {
log.Errorln("Error scraping for collect.perf_schema.tablelocks:", err)
e.scrapeErrors.WithLabelValues("collect.perf_schema.tablelocks").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.tablelocks")
}
if e.collect.PerfEventsStatements {
scrapeTime = time.Now()
if err = ScrapePerfEventsStatements(db, ch); err != nil {
log.Errorln("Error scraping for collect.perf_schema.eventsstatements:", err)
e.scrapeErrors.WithLabelValues("collect.perf_schema.eventsstatements").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.eventsstatements")
}
if e.collect.PerfEventsWaits {
scrapeTime = time.Now()
if err = ScrapePerfEventsWaits(db, ch); err != nil {
log.Errorln("Error scraping for collect.perf_schema.eventswaits:", err)
e.scrapeErrors.WithLabelValues("collect.perf_schema.eventswaits").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.eventswaits")
}
if e.collect.PerfFileEvents {
scrapeTime = time.Now()
if err = ScrapePerfFileEvents(db, ch); err != nil {
log.Errorln("Error scraping for collect.perf_schema.file_events:", err)
e.scrapeErrors.WithLabelValues("collect.perf_schema.file_events").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.file_events")
}
if e.collect.PerfFileInstances {
scrapeTime = time.Now()
if err = ScrapePerfFileInstances(db, ch); err != nil {
log.Errorln("Error scraping for collect.perf_schema.file_instances:", err)
e.scrapeErrors.WithLabelValues("collect.perf_schema.file_instances").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.perf_schema.file_instances")
}
if e.collect.PerfRepGroupMemberStats {
scrapeTime = time.Now()
if err = ScrapeReplicationGroupMemberStats(db, ch); err != nil {
log.Errorln("Error scraping for collect.replication_group_member_stats:", err)
e.scrapeErrors.WithLabelValues("collect.replication_group_member_stats").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.replication_group_member_stats")
}
if e.collect.UserStat {
scrapeTime = time.Now()
if err = ScrapeUserStat(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.userstats:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.userstats").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.userstats")
}
if e.collect.InnodbCmp {
scrapeTime = time.Now()
if err = ScrapeInnodbCmp(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.innodbcmp:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.innodbcmp").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.innodbcmp")
}
if e.collect.InnodbCmpMem {
scrapeTime = time.Now()
if err = ScrapeInnodbCmpMem(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.innodbcmpmem:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.innodbcmpmem").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.innodbcmpmem")
}
if e.collect.ClientStat {
scrapeTime = time.Now()
if err = ScrapeClientStat(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.clientstats:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.clientstats").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.clientstats")
}
if e.collect.TableStat {
scrapeTime = time.Now()
if err = ScrapeTableStat(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.tablestats:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.tablestats").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.tablestats")
}
if e.collect.QueryResponseTime {
scrapeTime = time.Now()
if err = ScrapeQueryResponseTime(db, ch); err != nil {
log.Errorln("Error scraping for collect.info_schema.query_response_time:", err)
e.scrapeErrors.WithLabelValues("collect.info_schema.query_response_time").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.info_schema.query_response_time")
}
if e.collect.EngineTokudbStatus {
scrapeTime = time.Now()
if err = ScrapeEngineTokudbStatus(db, ch); err != nil {
log.Errorln("Error scraping for collect.engine_tokudb_status:", err)
e.scrapeErrors.WithLabelValues("collect.engine_tokudb_status").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.engine_tokudb_status")
}
if e.collect.EngineInnodbStatus {
scrapeTime = time.Now()
if err = ScrapeEngineInnodbStatus(db, ch); err != nil {
log.Errorln("Error scraping for collect.engine_innodb_status:", err)
e.scrapeErrors.WithLabelValues("collect.engine_innodb_status").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.engine_innodb_status")
}
if e.collect.Heartbeat {
scrapeTime = time.Now()
if err = ScrapeHeartbeat(db, ch, e.collect.HeartbeatDatabase, e.collect.HeartbeatTable); err != nil {
log.Errorln("Error scraping for collect.heartbeat:", err)
e.scrapeErrors.WithLabelValues("collect.heartbeat").Inc()
e.error.Set(1)
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), "collect.heartbeat")
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(scrapeTime).Seconds(), label)
}(scraper)
}
}

View File

@ -15,8 +15,8 @@ func TestExporter(t *testing.T) {
t.Skip("-short is passed, skipping test")
}
exporter := New(dsn, Collect{
GlobalStatus: true,
exporter := New(dsn, []Scraper{
ScrapeGlobalStatus{},
})
convey.Convey("Metrics describing", t, func() {

View File

@ -11,7 +11,7 @@ import (
)
const (
// Scrape query
// Scrape query.
globalStatusQuery = `SHOW GLOBAL STATUS`
// Subsystem.
globalStatus = "global_status"
@ -20,6 +20,7 @@ const (
// Regexp to match various groups of status vars.
var globalStatusRE = regexp.MustCompile(`^(com|handler|connection_errors|innodb_buffer_pool_pages|innodb_rows|performance_schema)_(.*)$`)
// Metric descriptors.
var (
globalCommandsDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, globalStatus, "commands_total"),
@ -59,7 +60,20 @@ var (
)
// ScrapeGlobalStatus collects from `SHOW GLOBAL STATUS`.
func ScrapeGlobalStatus(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeGlobalStatus struct{}
// Name of the Scraper. Should be unique.
func (ScrapeGlobalStatus) Name() string {
return globalStatus
}
// Help describes the role of the Scraper.
func (ScrapeGlobalStatus) Help() string {
return "Collect from SHOW GLOBAL STATUS"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeGlobalStatus) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
globalStatusRows, err := db.Query(globalStatusQuery)
if err != nil {
return err

View File

@ -38,7 +38,7 @@ func TestScrapeGlobalStatus(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeGlobalStatus(db, ch); err != nil {
if err = (ScrapeGlobalStatus{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -19,7 +19,20 @@ const (
)
// ScrapeGlobalVariables collects from `SHOW GLOBAL VARIABLES`.
func ScrapeGlobalVariables(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeGlobalVariables struct{}
// Name of the Scraper. Should be unique.
func (ScrapeGlobalVariables) Name() string {
return globalVariables
}
// Help describes the role of the Scraper.
func (ScrapeGlobalVariables) Help() string {
return "Collect from SHOW GLOBAL VARIABLES"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeGlobalVariables) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
globalVariablesRows, err := db.Query(globalVariablesQuery)
if err != nil {
return err

View File

@ -37,7 +37,7 @@ func TestScrapeGlobalVariables(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeGlobalVariables(db, ch); err != nil {
if err = (ScrapeGlobalVariables{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -8,6 +8,7 @@ import (
"strconv"
"github.com/prometheus/client_golang/prometheus"
"gopkg.in/alecthomas/kingpin.v2"
)
const (
@ -20,6 +21,17 @@ const (
heartbeatQuery = "SELECT UNIX_TIMESTAMP(ts), UNIX_TIMESTAMP(NOW(6)), server_id from `%s`.`%s`"
)
var (
collectHeartbeatDatabase = kingpin.Flag(
"collect.heartbeat.database",
"Database from where to collect heartbeat data",
).Default("heartbeat").String()
collectHeartbeatTable = kingpin.Flag(
"collect.heartbeat.table",
"Table from where to collect heartbeat data",
).Default("heartbeat").String()
)
// Metric descriptors.
var (
HeartbeatStoredDesc = prometheus.NewDesc(
@ -41,8 +53,21 @@ var (
// ts varchar(26) NOT NULL,
// server_id int unsigned NOT NULL PRIMARY KEY,
// );
func ScrapeHeartbeat(db *sql.DB, ch chan<- prometheus.Metric, collectDatabase, collectTable string) error {
query := fmt.Sprintf(heartbeatQuery, collectDatabase, collectTable)
type ScrapeHeartbeat struct{}
// Name of the Scraper. Should be unique.
func (ScrapeHeartbeat) Name() string {
return "heartbeat"
}
// Help describes the role of the Scraper.
func (ScrapeHeartbeat) Help() string {
return "Collect from heartbeat"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeHeartbeat) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
query := fmt.Sprintf(heartbeatQuery, *collectHeartbeatDatabase, *collectHeartbeatTable)
heartbeatRows, err := db.Query(query)
if err != nil {
return err

View File

@ -7,9 +7,18 @@ import (
dto "github.com/prometheus/client_model/go"
"github.com/smartystreets/goconvey/convey"
"gopkg.in/DATA-DOG/go-sqlmock.v1"
"gopkg.in/alecthomas/kingpin.v2"
)
func TestScrapeHeartbeat(t *testing.T) {
_, err := kingpin.CommandLine.Parse([]string{
"--collect.heartbeat.database", "heartbeat-test",
"--collect.heartbeat.table", "heartbeat-test",
})
if err != nil {
t.Fatal(err)
}
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("error opening a stub database connection: %s", err)
@ -19,13 +28,11 @@ func TestScrapeHeartbeat(t *testing.T) {
columns := []string{"UNIX_TIMESTAMP(ts)", "UNIX_TIMESTAMP(NOW(6))", "server_id"}
rows := sqlmock.NewRows(columns).
AddRow("1487597613.001320", "1487598113.448042", 1)
mock.ExpectQuery(sanitizeQuery("SELECT UNIX_TIMESTAMP(ts), UNIX_TIMESTAMP(NOW(6)), server_id from `heartbeat`.`heartbeat`")).WillReturnRows(rows)
mock.ExpectQuery(sanitizeQuery("SELECT UNIX_TIMESTAMP(ts), UNIX_TIMESTAMP(NOW(6)), server_id from `heartbeat-test`.`heartbeat-test`")).WillReturnRows(rows)
ch := make(chan prometheus.Metric)
go func() {
database := "heartbeat"
table := "heartbeat"
if err = ScrapeHeartbeat(db, ch, database, table); err != nil {
if err = (ScrapeHeartbeat{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -22,6 +22,7 @@ const infoSchemaAutoIncrementQuery = `
WHERE c.extra = 'auto_increment' AND t.auto_increment IS NOT NULL
`
// Metric descriptors.
var (
globalInfoSchemaAutoIncrementDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, informationSchema, "auto_increment_column"),
@ -36,7 +37,20 @@ var (
)
// ScrapeAutoIncrementColumns collects auto_increment column information.
func ScrapeAutoIncrementColumns(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeAutoIncrementColumns struct{}
// Name of the Scraper. Should be unique.
func (ScrapeAutoIncrementColumns) Name() string {
return "auto_increment.columns"
}
// Help describes the role of the Scraper.
func (ScrapeAutoIncrementColumns) Help() string {
return "Collect auto_increment columns and max values from information_schema"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeAutoIncrementColumns) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
autoIncrementRows, err := db.Query(infoSchemaAutoIncrementQuery)
if err != nil {
return err

View File

@ -128,7 +128,20 @@ var (
)
// ScrapeClientStat collects from `information_schema.client_statistics`.
func ScrapeClientStat(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeClientStat struct{}
// Name of the Scraper. Should be unique.
func (ScrapeClientStat) Name() string {
return "info_schema.clientstats"
}
// Help describes the role of the Scraper.
func (ScrapeClientStat) Help() string {
return "If running with userstat=1, set to true to collect client statistics"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeClientStat) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
var varName, varVal string
err := db.QueryRow(userstatCheckQuery).Scan(&varName, &varVal)
if err != nil {

View File

@ -26,7 +26,7 @@ func TestScrapeClientStat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeClientStat(db, ch); err != nil {
if err = (ScrapeClientStat{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -14,6 +14,7 @@ const innodbCmpQuery = `
FROM information_schema.innodb_cmp
`
// Metric descriptors.
var (
infoSchemaInnodbCmpCompressOps = prometheus.NewDesc(
prometheus.BuildFQName(namespace, informationSchema, "innodb_cmp_compress_ops_total"),
@ -43,7 +44,20 @@ var (
)
// ScrapeInnodbCmp collects from `information_schema.innodb_cmp`.
func ScrapeInnodbCmp(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeInnodbCmp struct{}
// Name of the Scraper. Should be unique.
func (ScrapeInnodbCmp) Name() string {
return informationSchema + ".innodb_cmp"
}
// Help describes the role of the Scraper.
func (ScrapeInnodbCmp) Help() string {
return "Collect metrics from information_schema.innodb_cmp"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeInnodbCmp) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
informationSchemaInnodbCmpRows, err := db.Query(innodbCmpQuery)
if err != nil {

View File

@ -23,7 +23,7 @@ func TestScrapeInnodbCmp(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeInnodbCmp(db, ch); err != nil {
if err = (ScrapeInnodbCmp{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -14,6 +14,7 @@ const innodbCmpMemQuery = `
FROM information_schema.innodb_cmpmem
`
// Metric descriptors.
var (
infoSchemaInnodbCmpMemPagesRead = prometheus.NewDesc(
prometheus.BuildFQName(namespace, informationSchema, "innodb_cmpmem_pages_used_total"),
@ -38,7 +39,20 @@ var (
)
// ScrapeInnodbCmp collects from `information_schema.innodb_cmp`.
func ScrapeInnodbCmpMem(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeInnodbCmpMem struct{}
// Name of the Scraper. Should be unique.
func (ScrapeInnodbCmpMem) Name() string {
return informationSchema + ".innodb_cmpmem"
}
// Help describes the role of the Scraper.
func (ScrapeInnodbCmpMem) Help() string {
return "Collect metrics from information_schema.innodb_cmpmem"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeInnodbCmpMem) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
informationSchemaInnodbCmpMemRows, err := db.Query(innodbCmpMemQuery)
if err != nil {

View File

@ -23,7 +23,7 @@ func TestScrapeInnodbCmpMem(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeInnodbCmpMem(db, ch); err != nil {
if err = (ScrapeInnodbCmpMem{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -49,7 +49,20 @@ var (
)
// ScrapeInnodbMetrics collects from `information_schema.innodb_metrics`.
func ScrapeInnodbMetrics(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeInnodbMetrics struct{}
// Name of the Scraper. Should be unique.
func (ScrapeInnodbMetrics) Name() string {
return informationSchema + ".innodb_metrics"
}
// Help describes the role of the Scraper.
func (ScrapeInnodbMetrics) Help() string {
return "Collect metrics from information_schema.innodb_metrics"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeInnodbMetrics) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
innodbMetricsRows, err := db.Query(infoSchemaInnodbMetricsQuery)
if err != nil {
return err

View File

@ -40,7 +40,7 @@ func TestScrapeInnodbMetrics(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeInnodbMetrics(db, ch); err != nil {
if err = (ScrapeInnodbMetrics{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -20,6 +20,7 @@ const innodbTablespacesQuery = `
FROM information_schema.innodb_sys_tablespaces
`
// Metric descriptors.
var (
infoSchemaInnodbTablesspaceInfoDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, informationSchema, "innodb_tablespace_space_info"),
@ -39,7 +40,20 @@ var (
)
// ScrapeInfoSchemaInnodbTablespaces collects from `information_schema.innodb_sys_tablespaces`.
func ScrapeInfoSchemaInnodbTablespaces(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeInfoSchemaInnodbTablespaces struct{}
// Name of the Scraper. Should be unique.
func (ScrapeInfoSchemaInnodbTablespaces) Name() string {
return informationSchema + ".innodb_tablespaces"
}
// Help describes the role of the Scraper.
func (ScrapeInfoSchemaInnodbTablespaces) Help() string {
return "Collect metrics from information_schema.innodb_sys_tablespaces"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeInfoSchemaInnodbTablespaces) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
tablespacesRows, err := db.Query(innodbTablespacesQuery)
if err != nil {
return err

View File

@ -24,7 +24,7 @@ func TestScrapeInfoSchemaInnodbTablespaces(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeInfoSchemaInnodbTablespaces(db, ch); err != nil {
if err = (ScrapeInfoSchemaInnodbTablespaces{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -20,13 +20,16 @@ const infoSchemaProcesslistQuery = `
ORDER BY null
`
var (
// Tunable flags.
var (
processlistMinTime = kingpin.Flag(
"collect.info_schema.processlist.min_time",
"Minimum time a thread must be in each state to be counted",
).Default("0").Int()
// Prometheus descriptors.
)
// Metric descriptors.
var (
processlistCountDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, informationSchema, "threads"),
"The number of threads (connections) split by current state.",
@ -118,37 +121,21 @@ var (
}
)
func deriveThreadState(command string, state string) string {
var normCmd = strings.Replace(strings.ToLower(command), "_", " ", -1)
var normState = strings.Replace(strings.ToLower(state), "_", " ", -1)
// check if it's already a valid state
_, knownState := threadStateCounterMap[normState]
if knownState {
return normState
}
// check if plain mapping applies
mappedState, canMap := threadStateMapping[normState]
if canMap {
return mappedState
}
// check special waiting for XYZ lock
if strings.Contains(normState, "waiting for") && strings.Contains(normState, "lock") {
return "waiting for lock"
}
if normCmd == "sleep" && normState == "" {
return "idle"
}
if normCmd == "query" {
return "executing"
}
if normCmd == "binlog dump" {
return "replication master"
}
return "other"
// ScrapeProcesslist collects from `information_schema.processlist`.
type ScrapeProcesslist struct{}
// Name of the Scraper. Should be unique.
func (ScrapeProcesslist) Name() string {
return informationSchema + ".processlist"
}
// ScrapeProcesslist collects from `information_schema.processlist`.
func ScrapeProcesslist(db *sql.DB, ch chan<- prometheus.Metric) error {
// Help describes the role of the Scraper.
func (ScrapeProcesslist) Help() string {
return "Collect current thread state counts from the information_schema.processlist"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeProcesslist) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
processQuery := fmt.Sprintf(
infoSchemaProcesslistQuery,
*processlistMinTime,
@ -191,3 +178,32 @@ func ScrapeProcesslist(db *sql.DB, ch chan<- prometheus.Metric) error {
return nil
}
func deriveThreadState(command string, state string) string {
var normCmd = strings.Replace(strings.ToLower(command), "_", " ", -1)
var normState = strings.Replace(strings.ToLower(state), "_", " ", -1)
// check if it's already a valid state
_, knownState := threadStateCounterMap[normState]
if knownState {
return normState
}
// check if plain mapping applies
mappedState, canMap := threadStateMapping[normState]
if canMap {
return mappedState
}
// check special waiting for XYZ lock
if strings.Contains(normState, "waiting for") && strings.Contains(normState, "lock") {
return "waiting for lock"
}
if normCmd == "sleep" && normState == "" {
return "idle"
}
if normCmd == "query" {
return "executing"
}
if normCmd == "binlog dump" {
return "replication master"
}
return "other"
}

View File

@ -86,7 +86,20 @@ func processQueryResponseTimeTable(db *sql.DB, ch chan<- prometheus.Metric, quer
}
// ScrapeQueryResponseTime collects from `information_schema.query_response_time`.
func ScrapeQueryResponseTime(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeQueryResponseTime struct{}
// Name of the Scraper. Should be unique.
func (ScrapeQueryResponseTime) Name() string {
return "info_schema.query_response_time"
}
// Help describes the role of the Scraper.
func (ScrapeQueryResponseTime) Help() string {
return "Collect query response time distribution if query_response_time_stats is ON."
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeQueryResponseTime) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
var queryStats uint8
err := db.QueryRow(queryResponseCheckQuery).Scan(&queryStats)
if err != nil {

View File

@ -37,7 +37,7 @@ func TestScrapeQueryResponseTime(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeQueryResponseTime(db, ch); err != nil {
if err = (ScrapeQueryResponseTime{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -36,11 +36,16 @@ const (
`
)
// Tunable flags.
var (
tableSchemaDatabases = kingpin.Flag(
"collect.info_schema.tables.databases",
"The list of databases to collect table stats for, or '*' for all",
).Default("*").String()
)
// Metric descriptors.
var (
infoSchemaTablesVersionDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, informationSchema, "table_version"),
"The version number of the table's .frm file",
@ -59,7 +64,20 @@ var (
)
// ScrapeTableSchema collects from `information_schema.tables`.
func ScrapeTableSchema(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeTableSchema struct{}
// Name of the Scraper. Should be unique.
func (ScrapeTableSchema) Name() string {
return informationSchema + ".tables"
}
// Help describes the role of the Scraper.
func (ScrapeTableSchema) Help() string {
return "Collect metrics from information_schema.tables"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeTableSchema) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
var dbList []string
if *tableSchemaDatabases == "*" {
dbListRows, err := db.Query(dbListQuery)

View File

@ -19,6 +19,7 @@ const tableStatQuery = `
FROM information_schema.table_statistics
`
// Metric descriptors.
var (
infoSchemaTableStatsRowsReadDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, informationSchema, "table_statistics_rows_read_total"),
@ -38,7 +39,20 @@ var (
)
// ScrapeTableStat collects from `information_schema.table_statistics`.
func ScrapeTableStat(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeTableStat struct{}
// Name of the Scraper. Should be unique.
func (ScrapeTableStat) Name() string {
return "info_schema.tablestats"
}
// Help describes the role of the Scraper.
func (ScrapeTableStat) Help() string {
return "If running with userstat=1, set to true to collect table statistics"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeTableStat) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
var varName, varVal string
err := db.QueryRow(userstatCheckQuery).Scan(&varName, &varVal)
if err != nil {

View File

@ -27,7 +27,7 @@ func TestScrapeTableStat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeTableStat(db, ch); err != nil {
if err = (ScrapeTableStat{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -124,7 +124,20 @@ var (
)
// ScrapeUserStat collects from `information_schema.user_statistics`.
func ScrapeUserStat(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeUserStat struct{}
// Name of the Scraper. Should be unique.
func (ScrapeUserStat) Name() string {
return "info_schema.userstats"
}
// Help describes the role of the Scraper.
func (ScrapeUserStat) Help() string {
return "If running with userstat=1, set to true to collect user statistics"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeUserStat) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
var varName, varVal string
err := db.QueryRow(userstatCheckQuery).Scan(&varName, &varVal)
if err != nil {

View File

@ -26,7 +26,7 @@ func TestScrapeUserStat(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeUserStat(db, ch); err != nil {
if err = (ScrapeUserStat{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -54,7 +54,7 @@ const perfEventsStatementsQuery = `
LIMIT %d
`
// Tuning flags.
// Tunable flags.
var (
perfEventsStatementsLimit = kingpin.Flag(
"collect.perf_schema.eventsstatements.limit",
@ -135,7 +135,20 @@ var (
)
// ScrapePerfEventsStatements collects from `performance_schema.events_statements_summary_by_digest`.
func ScrapePerfEventsStatements(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapePerfEventsStatements struct{}
// Name of the Scraper. Should be unique.
func (ScrapePerfEventsStatements) Name() string {
return "perf_schema.eventsstatements"
}
// Help describes the role of the Scraper.
func (ScrapePerfEventsStatements) Help() string {
return "Collect metrics from performance_schema.events_statements_summary_by_digest"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfEventsStatements) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
perfQuery := fmt.Sprintf(
perfEventsStatementsQuery,
*perfEventsStatementsDigestTextLimit,

View File

@ -28,7 +28,20 @@ var (
)
// ScrapePerfEventsWaits collects from `performance_schema.events_waits_summary_global_by_event_name`.
func ScrapePerfEventsWaits(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapePerfEventsWaits struct{}
// Name of the Scraper. Should be unique.
func (ScrapePerfEventsWaits) Name() string {
return "perf_schema.eventswaits"
}
// Help describes the role of the Scraper.
func (ScrapePerfEventsWaits) Help() string {
return "Collect metrics from performance_schema.events_waits_summary_global_by_event_name"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfEventsWaits) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
// Timers here are returned in picoseconds.
perfSchemaEventsWaitsRows, err := db.Query(perfEventsWaitsQuery)
if err != nil {

View File

@ -37,7 +37,20 @@ var (
)
// ScrapePerfFileEvents collects from `performance_schema.file_summary_by_event_name`.
func ScrapePerfFileEvents(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapePerfFileEvents struct{}
// Name of the Scraper. Should be unique.
func (ScrapePerfFileEvents) Name() string {
return "perf_schema.file_events"
}
// Help describes the role of the Scraper.
func (ScrapePerfFileEvents) Help() string {
return "Collect metrics from performance_schema.file_summary_by_event_name"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfFileEvents) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
// Timers here are returned in picoseconds.
perfSchemaFileEventsRows, err := db.Query(perfFileEventsQuery)
if err != nil {

View File

@ -19,13 +19,16 @@ const perfFileInstancesQuery = `
where FILE_NAME REGEXP ?
`
// Metric descriptors.
// Tunable flags.
var (
performanceSchemaFileInstancesFilter = kingpin.Flag(
"collect.perf_schema.file_instances.filter",
"RegEx file_name filter for performance_schema.file_summary_by_instance",
).Default(".*").String()
)
// Metric descriptors.
var (
performanceSchemaFileInstancesRemovePrefix = kingpin.Flag(
"collect.perf_schema.file_instances.remove_prefix",
"Remove path prefix in performance_schema.file_summary_by_instance",
@ -43,8 +46,21 @@ var (
)
)
// ScrapePerfFileEvents collects from `performance_schema.file_summary_by_event_name`.
func ScrapePerfFileInstances(db *sql.DB, ch chan<- prometheus.Metric) error {
// ScrapePerfFileInstances collects from `performance_schema.file_summary_by_instance`.
type ScrapePerfFileInstances struct{}
// Name of the Scraper. Should be unique.
func (ScrapePerfFileInstances) Name() string {
return "perf_schema.file_instances"
}
// Help describes the role of the Scraper.
func (ScrapePerfFileInstances) Help() string {
return "Collect metrics from performance_schema.file_summary_by_instance"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfFileInstances) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
// Timers here are returned in picoseconds.
perfSchemaFileInstancesRows, err := db.Query(perfFileInstancesQuery, *performanceSchemaFileInstancesFilter)
if err != nil {

View File

@ -33,7 +33,7 @@ func TestScrapePerfFileInstances(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapePerfFileInstances(db, ch); err != nil {
if err = (ScrapePerfFileInstances{}).Scrape(db, ch); err != nil {
panic(fmt.Sprintf("error calling function on test: %s", err))
}
close(ch)

View File

@ -31,7 +31,20 @@ var (
)
// ScrapePerfIndexIOWaits collects for `performance_schema.table_io_waits_summary_by_index_usage`.
func ScrapePerfIndexIOWaits(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapePerfIndexIOWaits struct{}
// Name of the Scraper. Should be unique.
func (ScrapePerfIndexIOWaits) Name() string {
return "perf_schema.indexiowaits"
}
// Help describes the role of the Scraper.
func (ScrapePerfIndexIOWaits) Help() string {
return "Collect metrics from performance_schema.table_io_waits_summary_by_index_usage"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfIndexIOWaits) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
perfSchemaIndexWaitsRows, err := db.Query(perfIndexIOWaitsQuery)
if err != nil {
return err

View File

@ -25,7 +25,7 @@ func TestScrapePerfIndexIOWaits(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapePerfIndexIOWaits(db, ch); err != nil {
if err = (ScrapePerfIndexIOWaits{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -37,7 +37,20 @@ var (
)
// ScrapeReplicationGroupMemberStats collects from `performance_schema.replication_group_member_stats`.
func ScrapeReplicationGroupMemberStats(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapePerfReplicationGroupMemberStats struct{}
// Name of the Scraper. Should be unique.
func (ScrapePerfReplicationGroupMemberStats) Name() string {
return performanceSchema + ".replication_group_member_stats"
}
// Help describes the role of the Scraper.
func (ScrapePerfReplicationGroupMemberStats) Help() string {
return "Collect metrics from performance_schema.replication_group_member_stats"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfReplicationGroupMemberStats) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
perfReplicationGroupMemeberStatsRows, err := db.Query(perfReplicationGroupMemeberStatsQuery)
if err != nil {
return err
@ -58,19 +71,19 @@ func ScrapeReplicationGroupMemberStats(db *sql.DB, ch chan<- prometheus.Metric)
return err
}
ch <- prometheus.MustNewConstMetric(
performanceSchemaTableWaitsDesc, prometheus.CounterValue, float64(countTransactionsInQueue),
performanceSchemaReplicationGroupMemberStatsTransInQueueDesc, prometheus.CounterValue, float64(countTransactionsInQueue),
memberId,
)
ch <- prometheus.MustNewConstMetric(
performanceSchemaTableWaitsDesc, prometheus.CounterValue, float64(countTransactionsChecked),
performanceSchemaReplicationGroupMemberStatsTransCheckedDesc, prometheus.CounterValue, float64(countTransactionsChecked),
memberId,
)
ch <- prometheus.MustNewConstMetric(
performanceSchemaTableWaitsDesc, prometheus.CounterValue, float64(countConflictsDetected),
performanceSchemaReplicationGroupMemberStatsConflictsDetectedDesc, prometheus.CounterValue, float64(countConflictsDetected),
memberId,
)
ch <- prometheus.MustNewConstMetric(
performanceSchemaTableWaitsDesc, prometheus.CounterValue, float64(countTransactionsRowsValidating),
performanceSchemaReplicationGroupMemberStatsTransRowValidatingDesc, prometheus.CounterValue, float64(countTransactionsRowsValidating),
memberId,
)
}

View File

@ -32,7 +32,20 @@ var (
)
// ScrapePerfTableIOWaits collects from `performance_schema.table_io_waits_summary_by_table`.
func ScrapePerfTableIOWaits(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapePerfTableIOWaits struct{}
// Name of the Scraper. Should be unique.
func (ScrapePerfTableIOWaits) Name() string {
return "perf_schema.tableiowaits"
}
// Help describes the role of the Scraper.
func (ScrapePerfTableIOWaits) Help() string {
return "Collect metrics from performance_schema.table_io_waits_summary_by_table"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfTableIOWaits) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
perfSchemaTableWaitsRows, err := db.Query(perfTableIOWaitsQuery)
if err != nil {
return err

View File

@ -61,7 +61,20 @@ var (
)
// ScrapePerfTableLockWaits collects from `performance_schema.table_lock_waits_summary_by_table`.
func ScrapePerfTableLockWaits(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapePerfTableLockWaits struct{}
// Name of the Scraper. Should be unique.
func (ScrapePerfTableLockWaits) Name() string {
return "perf_schema.tablelocks"
}
// Help describes the role of the Scraper.
func (ScrapePerfTableLockWaits) Help() string {
return "Collect metrics from performance_schema.table_lock_waits_summary_by_table"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapePerfTableLockWaits) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
perfSchemaTableLockWaitsRows, err := db.Query(perfTableLockWaitsQuery)
if err != nil {
return err

19
collector/scraper.go Normal file
View File

@ -0,0 +1,19 @@
package collector
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/prometheus/client_golang/prometheus"
)
// Scraper is minimal interface that let's you add new prometheus metrics to mysqld_exporter.
type Scraper interface {
// Name of the Scraper. Should be unique.
Name() string
// Help describes the role of the Scraper.
// Example: "Collect from SHOW ENGINE INNODB STATUS"
Help() string
// Scrape collects data from database connection and sends it over channel as prometheus metric.
Scrape(db *sql.DB, ch chan<- prometheus.Metric) error
}

View File

@ -36,7 +36,20 @@ func columnValue(scanArgs []interface{}, slaveCols []string, colName string) str
}
// ScrapeSlaveStatus collects from `SHOW SLAVE STATUS`.
func ScrapeSlaveStatus(db *sql.DB, ch chan<- prometheus.Metric) error {
type ScrapeSlaveStatus struct{}
// Name of the Scraper. Should be unique.
func (ScrapeSlaveStatus) Name() string {
return slaveStatus
}
// Help describes the role of the Scraper.
func (ScrapeSlaveStatus) Help() string {
return "Collect from SHOW SLAVE STATUS"
}
// Scrape collects data from database connection and sends it over channel as prometheus metric.
func (ScrapeSlaveStatus) Scrape(db *sql.DB, ch chan<- prometheus.Metric) error {
var (
slaveStatusRows *sql.Rows
err error

View File

@ -23,7 +23,7 @@ func TestScrapeSlaveStatus(t *testing.T) {
ch := make(chan prometheus.Metric)
go func() {
if err = ScrapeSlaveStatus(db, ch); err != nil {
if err = (ScrapeSlaveStatus{}).Scrape(db, ch); err != nil {
t.Errorf("error calling function on test: %s", err)
}
close(ch)

View File

@ -29,121 +29,39 @@ var (
"config.my-cnf",
"Path to .my.cnf file to read MySQL credentials from.",
).Default(path.Join(os.Getenv("HOME"), ".my.cnf")).String()
collectProcesslist = kingpin.Flag(
"collect.info_schema.processlist",
"Collect current thread state counts from the information_schema.processlist",
).Default("false").Bool()
collectTableSchema = kingpin.Flag(
"collect.info_schema.tables",
"Collect metrics from information_schema.tables",
).Default("true").Bool()
collectInnodbTablespaces = kingpin.Flag(
"collect.info_schema.innodb_tablespaces",
"Collect metrics from information_schema.innodb_sys_tablespaces",
).Default("false").Bool()
collectInnodbMetrics = kingpin.Flag(
"collect.info_schema.innodb_metrics",
"Collect metrics from information_schema.innodb_metrics",
).Default("false").Bool()
collectGlobalStatus = kingpin.Flag(
"collect.global_status",
"Collect from SHOW GLOBAL STATUS",
).Default("true").Bool()
collectGlobalVariables = kingpin.Flag(
"collect.global_variables",
"Collect from SHOW GLOBAL VARIABLES",
).Default("true").Bool()
collectSlaveStatus = kingpin.Flag(
"collect.slave_status",
"Collect from SHOW SLAVE STATUS",
).Default("true").Bool()
collectAutoIncrementColumns = kingpin.Flag(
"collect.auto_increment.columns",
"Collect auto_increment columns and max values from information_schema",
).Default("false").Bool()
collectBinlogSize = kingpin.Flag(
"collect.binlog_size",
"Collect the current size of all registered binlog files",
).Default("false").Bool()
collectPerfTableIOWaits = kingpin.Flag(
"collect.perf_schema.tableiowaits",
"Collect metrics from performance_schema.table_io_waits_summary_by_table",
).Default("false").Bool()
collectPerfIndexIOWaits = kingpin.Flag(
"collect.perf_schema.indexiowaits",
"Collect metrics from performance_schema.table_io_waits_summary_by_index_usage",
).Default("false").Bool()
collectPerfTableLockWaits = kingpin.Flag(
"collect.perf_schema.tablelocks",
"Collect metrics from performance_schema.table_lock_waits_summary_by_table",
).Default("false").Bool()
collectPerfEventsStatements = kingpin.Flag(
"collect.perf_schema.eventsstatements",
"Collect metrics from performance_schema.events_statements_summary_by_digest",
).Default("false").Bool()
collectPerfEventsWaits = kingpin.Flag(
"collect.perf_schema.eventswaits",
"Collect metrics from performance_schema.events_waits_summary_global_by_event_name",
).Default("false").Bool()
collectPerfFileEvents = kingpin.Flag(
"collect.perf_schema.file_events",
"Collect metrics from performance_schema.file_summary_by_event_name",
).Default("false").Bool()
collectPerfFileInstances = kingpin.Flag(
"collect.perf_schema.file_instances",
"Collect metrics from performance_schema.file_summary_by_instance",
).Default("false").Bool()
collectPerfRepGroupMemberStats = kingpin.Flag(
"collect.replication_group_member_stats",
"Collect metrics from performance_schema.replication_group_member_stats",
).Default("false").Bool()
collectUserStat = kingpin.Flag(
"collect.info_schema.userstats",
"If running with userstat=1, set to true to collect user statistics",
).Default("false").Bool()
collectClientStat = kingpin.Flag(
"collect.info_schema.clientstats",
"If running with userstat=1, set to true to collect client statistics",
).Default("false").Bool()
collectTableStat = kingpin.Flag(
"collect.info_schema.tablestats",
"If running with userstat=1, set to true to collect table statistics",
).Default("false").Bool()
collectQueryResponseTime = kingpin.Flag(
"collect.info_schema.query_response_time",
"Collect query response time distribution if query_response_time_stats is ON.",
).Default("false").Bool()
collectInnodbCmp = kingpin.Flag(
"collect.info_schema.innodbcmp",
"If running with innodbcmp=1, set to true to collect innodb cmp statistics",
).Default("false").Bool()
collectInnodbCmpMem = kingpin.Flag(
"collect.info_schema.innodbcmpmem",
"If running with innodbcmpmem=1, set to true to collect innodb cmpmem statistics",
).Default("false").Bool()
collectEngineTokudbStatus = kingpin.Flag(
"collect.engine_tokudb_status",
"Collect from SHOW ENGINE TOKUDB STATUS",
).Default("false").Bool()
collectEngineInnodbStatus = kingpin.Flag(
"collect.engine_innodb_status",
"Collect from SHOW ENGINE INNODB STATUS",
).Default("false").Bool()
collectHeartbeat = kingpin.Flag(
"collect.heartbeat",
"Collect from heartbeat",
).Default("false").Bool()
collectHeartbeatDatabase = kingpin.Flag(
"collect.heartbeat.database",
"Database from where to collect heartbeat data",
).Default("heartbeat").String()
collectHeartbeatTable = kingpin.Flag(
"collect.heartbeat.table",
"Table from where to collect heartbeat data",
).Default("heartbeat").String()
dsn string
)
// scrapers lists all possible collection methods and if they should be enabled by default.
var scrapers = map[collector.Scraper]bool{
collector.ScrapeGlobalStatus{}: true,
collector.ScrapeGlobalVariables{}: true,
collector.ScrapeSlaveStatus{}: true,
collector.ScrapeProcesslist{}: false,
collector.ScrapeTableSchema{}: true,
collector.ScrapeInfoSchemaInnodbTablespaces{}: false,
collector.ScrapeInnodbMetrics{}: false,
collector.ScrapeAutoIncrementColumns{}: false,
collector.ScrapeBinlogSize{}: false,
collector.ScrapePerfTableIOWaits{}: false,
collector.ScrapePerfIndexIOWaits{}: false,
collector.ScrapePerfTableLockWaits{}: false,
collector.ScrapePerfEventsStatements{}: false,
collector.ScrapePerfEventsWaits{}: false,
collector.ScrapePerfFileEvents{}: false,
collector.ScrapePerfFileInstances{}: false,
collector.ScrapePerfReplicationGroupMemberStats{}: false,
collector.ScrapeUserStat{}: false,
collector.ScrapeClientStat{}: false,
collector.ScrapeTableStat{}: false,
collector.ScrapeInnodbCmp{}: false,
collector.ScrapeInnodbCmpMem{}: false,
collector.ScrapeQueryResponseTime{}: false,
collector.ScrapeEngineTokudbStatus{}: false,
collector.ScrapeEngineInnodbStatus{}: false,
collector.ScrapeHeartbeat{}: false,
}
func parseMycnf(config interface{}) (string, error) {
var dsn string
cfg, err := ini.Load(config)
@ -171,58 +89,29 @@ func init() {
prometheus.MustRegister(version.NewCollector("mysqld_exporter"))
}
func filter(filters map[string]bool, name string, flag bool) bool {
if len(filters) > 0 {
return flag && filters[name]
}
return flag
}
func handler(w http.ResponseWriter, r *http.Request) {
var filters map[string]bool
func newHandler(scrapers []collector.Scraper) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
filteredScrapers := scrapers
params := r.URL.Query()["collect[]"]
log.Debugln("collect query:", params)
// Check if we have some "collect[]" query parameters.
if len(params) > 0 {
filters = make(map[string]bool)
filters := make(map[string]bool)
for _, param := range params {
filters[param] = true
}
}
collect := collector.Collect{
Processlist: filter(filters, "info_schema.processlist", *collectProcesslist),
TableSchema: filter(filters, "info_schema.tables", *collectTableSchema),
InnodbTablespaces: filter(filters, "info_schema.innodb_tablespaces", *collectInnodbTablespaces),
InnodbMetrics: filter(filters, "info_schema.innodb_metrics", *collectInnodbMetrics),
GlobalStatus: filter(filters, "global_status", *collectGlobalStatus),
GlobalVariables: filter(filters, "global_variables", *collectGlobalVariables),
SlaveStatus: filter(filters, "slave_status", *collectSlaveStatus),
AutoIncrementColumns: filter(filters, "auto_increment.columns", *collectAutoIncrementColumns),
BinlogSize: filter(filters, "binlog_size", *collectBinlogSize),
PerfTableIOWaits: filter(filters, "perf_schema.tableiowaits", *collectPerfTableIOWaits),
PerfIndexIOWaits: filter(filters, "perf_schema.indexiowaits", *collectPerfIndexIOWaits),
PerfTableLockWaits: filter(filters, "perf_schema.tablelocks", *collectPerfTableLockWaits),
PerfEventsStatements: filter(filters, "perf_schema.eventsstatements", *collectPerfEventsStatements),
PerfEventsWaits: filter(filters, "perf_schema.eventswaits", *collectPerfEventsWaits),
PerfFileEvents: filter(filters, "perf_schema.file_events", *collectPerfFileEvents),
PerfFileInstances: filter(filters, "perf_schema.file_instances", *collectPerfFileInstances),
PerfRepGroupMemberStats: filter(filters, "perf_schema.replication_group_member_stats", *collectPerfRepGroupMemberStats),
UserStat: filter(filters, "info_schema.userstats", *collectUserStat),
ClientStat: filter(filters, "info_schema.clientstats", *collectClientStat),
InnodbCmp: filter(filters, "info_schema.innodbcmp", *collectInnodbCmp),
InnodbCmpMem: filter(filters, "info_schema.innodbcmpmem", *collectInnodbCmpMem),
TableStat: filter(filters, "info_schema.tablestats", *collectTableStat),
QueryResponseTime: filter(filters, "info_schema.query_response_time", *collectQueryResponseTime),
EngineTokudbStatus: filter(filters, "engine_tokudb_status", *collectEngineTokudbStatus),
EngineInnodbStatus: filter(filters, "engine_innodb_status", *collectEngineInnodbStatus),
Heartbeat: filter(filters, "heartbeat", *collectHeartbeat),
HeartbeatDatabase: *collectHeartbeatDatabase,
HeartbeatTable: *collectHeartbeatTable,
filteredScrapers = nil
for _, scraper := range scrapers {
if filters[scraper.Name()] {
filteredScrapers = append(filteredScrapers, scraper)
}
}
}
registry := prometheus.NewRegistry()
registry.MustRegister(collector.New(dsn, collect))
registry.MustRegister(collector.New(dsn, filteredScrapers))
gatherers := prometheus.Gatherers{
prometheus.DefaultGatherer,
@ -232,8 +121,26 @@ func handler(w http.ResponseWriter, r *http.Request) {
h := promhttp.HandlerFor(gatherers, promhttp.HandlerOpts{})
h.ServeHTTP(w, r)
}
}
func main() {
// Generate ON/OFF flags for all scrapers.
scraperFlags := map[collector.Scraper]*bool{}
for scraper, enabledByDefault := range scrapers {
defaultOn := "false"
if enabledByDefault {
defaultOn = "true"
}
f := kingpin.Flag(
"collect."+scraper.Name(),
scraper.Help(),
).Default(defaultOn).Bool()
scraperFlags[scraper] = f
}
// Parse flags.
log.AddFlags(kingpin.CommandLine)
kingpin.Version(version.Print("mysqld_exporter"))
kingpin.HelpFlag.Short('h')
@ -261,7 +168,16 @@ func main() {
}
}
http.HandleFunc(*metricPath, prometheus.InstrumentHandlerFunc("metrics", handler))
// Register only scrapers enabled by flag.
log.Infof("Enabled scrapers:")
enabledScrapers := []collector.Scraper{}
for scraper, enabled := range scraperFlags {
if *enabled {
log.Infof(" --collect.%s", scraper.Name())
enabledScrapers = append(enabledScrapers, scraper)
}
}
http.HandleFunc(*metricPath, prometheus.InstrumentHandlerFunc("metrics", newHandler(enabledScrapers)))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write(landingPage)
})