From 0646c7f5a0952ecdf6cd171328a5e71089e1bbfd Mon Sep 17 00:00:00 2001 From: "jxin.song" Date: Wed, 4 Sep 2024 11:14:26 +0800 Subject: [PATCH] Add metrics the number of running transactions Signed-off-by: jxin.song --- README.md | 1 + collector/info_schema_innodb_trx.go | 81 ++++++++++++++++++++++++ collector/info_schema_innodb_trx_test.go | 66 +++++++++++++++++++ mysqld_exporter.go | 1 + 4 files changed, 149 insertions(+) create mode 100644 collector/info_schema_innodb_trx.go create mode 100644 collector/info_schema_innodb_trx_test.go diff --git a/README.md b/README.md index 3b962092..a0f213e1 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,7 @@ collect.perf_schema.replication_applier_status_by_worker | 5.7 | C collect.slave_status | 5.1 | Collect from SHOW SLAVE STATUS (Enabled by default) collect.slave_hosts | 5.1 | Collect from SHOW SLAVE HOSTS collect.sys.user_summary | 5.7 | Collect metrics from sys.x$user_summary (disabled by default). +collect.info_schema.innodb_trx | 5.7 | Collect Number of running transactions that have been running for more than the specified time(by --collector.info_schema_innodb_trx.running_time)(disabled by default). ### General Flags diff --git a/collector/info_schema_innodb_trx.go b/collector/info_schema_innodb_trx.go new file mode 100644 index 00000000..66baf0af --- /dev/null +++ b/collector/info_schema_innodb_trx.go @@ -0,0 +1,81 @@ +// 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. + +// Scrape `information_schema.INNODB_TRX`. +package collector + +import ( + "context" + "fmt" + "github.com/alecthomas/kingpin/v2" + "github.com/go-kit/log" + "github.com/prometheus/client_golang/prometheus" +) + +const infoSchemaInnodbTRX = ` + SELECT count(1) FROM information_schema.INNODB_TRX WHERE trx_started < NOW() - INTERVAL %d MINUTE; +` + +// Tunable flags. +var trxRunningTime = kingpin.Flag( + "collector.info_schema_innodb_trx.running_time", + "The running time in minutes for which to collect the number of running transactions.", +).Default("0").Int() + +// Metric descriptors. +var ( + infoSchemaInnodbTrxDesc = prometheus.NewDesc( + prometheus.BuildFQName(namespace, informationSchema, "innodb_trx_running_transactions"), + "Number of running transactions that have been running for more than the specified time.", + nil, nil) +) + +// ScrapeTransaction collects from `information_schema.INNODB_TRX`. +type ScrapeTransaction struct{} + +// Name of the Scraper. Should be unique. +func (ScrapeTransaction) Name() string { return informationSchema + ".innodb_trx" } + +// Help describes the role of the Scraper. +func (ScrapeTransaction) Help() string { + return "Number of running transactions that have been running for more than the specified time." +} + +// Version of MySQL from which scraper is available. +func (ScrapeTransaction) Version() float64 { + return 5.7 +} + +func (ScrapeTransaction) Scrape(ctx context.Context, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) error { + query := fmt.Sprintf(infoSchemaInnodbTRX, *trxRunningTime) + db := instance.getDB() + rows, err := db.QueryContext(ctx, query) + if err != nil { + return err + } + defer rows.Close() + + var trxRunning int + + for rows.Next() { + if err := rows.Scan(&trxRunning); err != nil { + return err + } + } + ch <- prometheus.MustNewConstMetric(infoSchemaInnodbTrxDesc, prometheus.GaugeValue, float64(trxRunning)) + + return nil +} + +// check interface +var _ Scraper = ScrapeTransaction{} diff --git a/collector/info_schema_innodb_trx_test.go b/collector/info_schema_innodb_trx_test.go new file mode 100644 index 00000000..a2fa8f1f --- /dev/null +++ b/collector/info_schema_innodb_trx_test.go @@ -0,0 +1,66 @@ +// Copyright 2021 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" + "github.com/DATA-DOG/go-sqlmock" + "github.com/alecthomas/kingpin/v2" + "github.com/go-kit/log" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/smartystreets/goconvey/convey" + "regexp" + "testing" +) + +func TestScrapeTransaction(t *testing.T) { + _, err := kingpin.CommandLine.Parse([]string{ + "--collect.info_schema.processlist.min_time=0", + }) + 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() + inst := &instance{db: db} + query := fmt.Sprintf(infoSchemaInnodbTRX, 0) + columns := []string{"count"} + rows := sqlmock.NewRows(columns).AddRow(0) + mock.ExpectQuery(regexp.QuoteMeta(query)).WillReturnRows(rows) + ch := make(chan prometheus.Metric) + go func() { + if err = (ScrapeTransaction{}).Scrape(context.Background(), inst, ch, log.NewNopLogger()); err != nil { + t.Errorf("error calling scrapeTransaction: %s", err) + } + close(ch) + }() + + expected := []MetricResult{ + {labels: labelMap{}, value: 0, metricType: dto.MetricType_GAUGE}, + } + convey.Convey("Metrics comparison", t, func() { + for _, expect := range expected { + got := readMetric(<-ch) + convey.So(expect, convey.ShouldResemble, got) + } + }) + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled exceptions: %s", err) + } +} diff --git a/mysqld_exporter.go b/mysqld_exporter.go index 7a3c9e3f..3971e66d 100644 --- a/mysqld_exporter.go +++ b/mysqld_exporter.go @@ -105,6 +105,7 @@ var scrapers = map[collector.Scraper]bool{ collector.ScrapeHeartbeat{}: false, collector.ScrapeSlaveHosts{}: false, collector.ScrapeReplicaHost{}: false, + collector.ScrapeTransaction{}: false, } func filterScrapers(scrapers []collector.Scraper, collectParams []string) []collector.Scraper {