diff --git a/README.md b/README.md index 73c3db6..f1963d4 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ collect.perf_schema.eventsstatements.limit | 5.6 | Limit t collect.perf_schema.eventsstatements.timelimit | 5.6 | Limit how old the 'last_seen' events statements can be, in seconds. (default: 86400) collect.perf_schema.eventswaits | 5.5 | Collect metrics from performance_schema.events_waits_summary_global_by_event_name. collect.perf_schema.file_events | 5.6 | Collect metrics from performance_schema.file_summary_by_event_name. +collect.perf_schema.file_instances | 5.5 | Collect metrics from performance_schema.file_summary_by_instance. collect.perf_schema.indexiowaits | 5.6 | Collect metrics from performance_schema.table_io_waits_summary_by_index_usage. collect.perf_schema.tableiowaits | 5.6 | Collect metrics from performance_schema.table_io_waits_summary_by_table. collect.perf_schema.tablelocks | 5.6 | Collect metrics from performance_schema.table_lock_waits_summary_by_table. diff --git a/collector/perf_schema_file_instances.go b/collector/perf_schema_file_instances.go new file mode 100644 index 0000000..148a749 --- /dev/null +++ b/collector/perf_schema_file_instances.go @@ -0,0 +1,94 @@ +// Scrape `performance_schema.file_summary_by_instance`. + +package collector + +import ( + "database/sql" + + "github.com/prometheus/client_golang/prometheus" + + "flag" + "path" + "path/filepath" +) + +const perfFileInstancesQuery = ` + SELECT + FILE_NAME, + COUNT_READ, COUNT_WRITE, + SUM_NUMBER_OF_BYTES_READ, SUM_NUMBER_OF_BYTES_WRITE + FROM performance_schema.file_summary_by_instance + where FILE_NAME REGEXP ? + ` + +// Metric descriptors. +var ( + performanceSchemaFileInstancesFilter = flag.String( + "collect.perf_schema.file_instances.filter", ".*", + "RegEx file_name filter for performance_schema.file_summary_by_instance", + ) + + performanceSchemaFileInstancesRemovePrefix = flag.Bool( + "collect.perf_schema.file_instances.remove_prefix", true, + "Remove path prefix in performance_schema.file_summary_by_instance", + ) + + performanceSchemaFileInstancesBytesDesc = prometheus.NewDesc( + prometheus.BuildFQName(namespace, performanceSchema, "file_instances_bytes"), + "The number of bytes processed by file read/write operations.", + []string{"file_name", "mode"}, nil, + ) + performanceSchemaFileInstancesCountDesc = prometheus.NewDesc( + prometheus.BuildFQName(namespace, performanceSchema, "file_instances_total"), + "The total number of file read/write operations.", + []string{"file_name", "mode"}, nil, + ) +) + +// ScrapePerfFileEvents collects from `performance_schema.file_summary_by_event_name`. +func ScrapePerfFileInstances(db *sql.DB, ch chan<- prometheus.Metric) error { + // Timers here are returned in picoseconds. + perfSchemaFileInstancesRows, err := db.Query(perfFileInstancesQuery, *performanceSchemaFileInstancesFilter) + if err != nil { + return err + } + defer perfSchemaFileInstancesRows.Close() + + var ( + fileName string + countRead, countWrite uint64 + sumBytesRead, sumBytesWritten uint64 + ) + + for perfSchemaFileInstancesRows.Next() { + if err := perfSchemaFileInstancesRows.Scan( + &fileName, + &countRead, &countWrite, + &sumBytesRead, &sumBytesWritten, + ); err != nil { + return err + } + + if *performanceSchemaFileInstancesRemovePrefix { + fileName = path.Base(filepath.ToSlash(fileName)) + } + ch <- prometheus.MustNewConstMetric( + performanceSchemaFileInstancesCountDesc, prometheus.CounterValue, float64(countRead), + fileName, "read", + ) + ch <- prometheus.MustNewConstMetric( + performanceSchemaFileInstancesCountDesc, prometheus.CounterValue, float64(countWrite), + fileName, "write", + ) + ch <- prometheus.MustNewConstMetric( + performanceSchemaFileInstancesBytesDesc, prometheus.CounterValue, float64(sumBytesRead), + fileName, "read", + ) + ch <- prometheus.MustNewConstMetric( + performanceSchemaFileInstancesBytesDesc, prometheus.CounterValue, float64(sumBytesWritten), + fileName, "write", + ) + + } + return nil +} diff --git a/collector/perf_schema_file_instances_test.go b/collector/perf_schema_file_instances_test.go new file mode 100644 index 0000000..474a84e --- /dev/null +++ b/collector/perf_schema_file_instances_test.go @@ -0,0 +1,62 @@ +package collector + +import ( + "testing" + + "flag" + "fmt" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/smartystreets/goconvey/convey" + "gopkg.in/DATA-DOG/go-sqlmock.v1" +) + +func TestScrapePerfFileInstances(t *testing.T) { + err := flag.Set("collect.perf_schema.file_instances.filter", "") + if err != nil { + t.Fatal(err) + } + + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("error opening a stub database connection: %s", err) + } + defer db.Close() + + columns := []string{"FILE_NAME", "COUNT_READ", "COUNT_WRITE", "SUM_NUMBER_OF_BYTES_READ", "SUM_NUMBER_OF_BYTES_WRITE"} + + rows := sqlmock.NewRows(columns). + AddRow("file_1", "3", "4", "725", "128"). + AddRow("file_2", "23", "12", "3123", "967") + mock.ExpectQuery(sanitizeQuery(perfFileInstancesQuery)).WillReturnRows(rows) + + ch := make(chan prometheus.Metric) + go func() { + if err = ScrapePerfFileInstances(db, ch); err != nil { + panic(fmt.Sprintf("error calling function on test: %s", err)) + } + close(ch) + }() + + metricExpected := []MetricResult{ + {labels: labelMap{"file_name": "file_1", "mode": "read"}, value: 3, metricType: dto.MetricType_COUNTER}, + {labels: labelMap{"file_name": "file_1", "mode": "write"}, value: 4, metricType: dto.MetricType_COUNTER}, + {labels: labelMap{"file_name": "file_1", "mode": "read"}, value: 725, metricType: dto.MetricType_COUNTER}, + {labels: labelMap{"file_name": "file_1", "mode": "write"}, value: 128, metricType: dto.MetricType_COUNTER}, + {labels: labelMap{"file_name": "file_2", "mode": "read"}, value: 23, metricType: dto.MetricType_COUNTER}, + {labels: labelMap{"file_name": "file_2", "mode": "write"}, value: 12, metricType: dto.MetricType_COUNTER}, + {labels: labelMap{"file_name": "file_2", "mode": "read"}, value: 3123, metricType: dto.MetricType_COUNTER}, + {labels: labelMap{"file_name": "file_2", "mode": "write"}, value: 967, metricType: dto.MetricType_COUNTER}, + } + convey.Convey("Metrics comparison", t, func() { + for _, expect := range metricExpected { + got := readMetric(<-ch) + convey.So(got, convey.ShouldResemble, expect) + } + }) + + // Ensure all SQL queries were executed + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expections: %s", err) + } +} diff --git a/mysqld_exporter.go b/mysqld_exporter.go index a625acb..c5fb7d9 100644 --- a/mysqld_exporter.go +++ b/mysqld_exporter.go @@ -99,6 +99,10 @@ var ( "collect.perf_schema.file_events", false, "Collect metrics from performance_schema.file_summary_by_event_name", ) + collectPerfFileInstances = flag.Bool( + "collect.perf_schema.file_instances", false, + "Collect metrics from performance_schema.file_summary_by_instance", + ) collectUserStat = flag.Bool("collect.info_schema.userstats", false, "If running with userstat=1, set to true to collect user statistics", ) @@ -368,6 +372,12 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) { e.scrapeErrors.WithLabelValues("collect.perf_schema.file_events").Inc() } } + if *collectPerfFileInstances { + if err = collector.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() + } + } if *collectUserStat { if err = collector.ScrapeUserStat(db, ch); err != nil { log.Errorln("Error scraping for collect.info_schema.userstats:", err)