Skip to content

Commit

Permalink
Expose query duration as histogram
Browse files Browse the repository at this point in the history
Signed-off-by: Wilfried Roset <[email protected]>
  • Loading branch information
wilfriedroset committed Mar 22, 2024
1 parent 1c69ded commit 7edd749
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 3 deletions.
21 changes: 20 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/go-kit/log"
"github.com/jmoiron/sqlx"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/robfig/cron/v3"
"gopkg.in/yaml.v2"
)
Expand All @@ -25,9 +26,10 @@ func getenv(key, defaultVal string) string {
}

var (
metricsPrefix = "sql_exporter"
failedScrapes = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "sql_exporter_last_scrape_failed",
Name: fmt.Sprintf("%s_last_scrape_failed", metricsPrefix),
Help: "Failed scrapes",
},
[]string{"driver", "host", "database", "user", "sql_job", "query"},
Expand All @@ -41,6 +43,18 @@ var (
regexp.QuoteMeta(tmplEnd),
),
)
QueryMetricsLabels = []string{"sql_job", "query"}
queryCounter = promauto.NewCounterVec(prometheus.CounterOpts{
Name: fmt.Sprintf("%s_queries_total", metricsPrefix),
}, QueryMetricsLabels)
failedQueryCounter = promauto.NewCounterVec(prometheus.CounterOpts{
Name: fmt.Sprintf("%s_query_failures_total", metricsPrefix),
}, QueryMetricsLabels)

// Those are the default buckets
DefaultQueryDurationHistogramBuckets = prometheus.DefBuckets
// To make the buckets configurable let's init it after loading the configuration.
queryDurationHistogram *prometheus.HistogramVec
)

func init() {
Expand Down Expand Up @@ -100,11 +114,16 @@ type CloudSQLConfig struct {

// File is a collection of jobs
type File struct {
Configuration Configuration `yaml:"configuration,omitempty"`
Jobs []*Job `yaml:"jobs"`
Queries map[string]string `yaml:"queries"`
CloudSQLConfig *CloudSQLConfig `yaml:"cloudsql_config"`
}

type Configuration struct {
HistogramBuckets []float64 `yaml:"histogram_buckets"`
}

type cronConfig struct {
definition string
schedule cron.Schedule
Expand Down
18 changes: 16 additions & 2 deletions exporter.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package main

import (
"context"
"fmt"

"cloud.google.com/go/cloudsqlconn"
"cloud.google.com/go/cloudsqlconn/mysql/mysql"
"cloud.google.com/go/cloudsqlconn/postgres/pgxv4"
"context"
"fmt"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/robfig/cron/v3"
"google.golang.org/api/option"
sqladmin "google.golang.org/api/sqladmin/v1beta4"
Expand All @@ -34,6 +36,18 @@ func NewExporter(logger log.Logger, configFile string) (*Exporter, error) {
return nil, err
}

var queryDurationHistogramBuckets []float64
if len(cfg.Configuration.HistogramBuckets) == 0 {
queryDurationHistogramBuckets = DefaultQueryDurationHistogramBuckets
} else {
queryDurationHistogramBuckets = cfg.Configuration.HistogramBuckets
}
queryDurationHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: fmt.Sprintf("%s_query_duration_seconds", metricsPrefix),
Help: "Time spent by querying the database.",
Buckets: queryDurationHistogramBuckets,
}, QueryMetricsLabels)

exp := &Exporter{
jobs: make([]*Job, 0, len(cfg.Jobs)),
logger: logger,
Expand Down
2 changes: 2 additions & 0 deletions job.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ func (j *Job) runOnceConnection(conn *connection, done chan int) {
if err := conn.connect(j); err != nil {
level.Warn(j.log).Log("msg", "Failed to connect", "err", err, "host", conn.host)
j.markFailed(conn)
// we don't have the query name yet.
failedQueryCounter.WithLabelValues(j.Name, "").Inc()
return
}

Expand Down
8 changes: 8 additions & 0 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,30 @@ func (q *Query) Run(conn *connection) error {
if q.log == nil {
q.log = log.NewNopLogger()
}
queryCounter.WithLabelValues(q.jobName, q.Name).Inc()
if q.desc == nil {
failedQueryCounter.WithLabelValues(q.jobName, q.Name).Inc()
return fmt.Errorf("metrics descriptor is nil")
}
if q.Query == "" {
failedQueryCounter.WithLabelValues(q.jobName, q.Name).Inc()
return fmt.Errorf("query is empty")
}
if conn == nil || conn.conn == nil {
failedQueryCounter.WithLabelValues(q.jobName, q.Name).Inc()
return fmt.Errorf("db connection not initialized (should not happen)")
}
// execute query
now := time.Now()
rows, err := conn.conn.Queryx(q.Query)
if err != nil {
failedScrapes.WithLabelValues(conn.driver, conn.host, conn.database, conn.user, q.jobName, q.Name).Set(1.0)
failedQueryCounter.WithLabelValues(q.jobName, q.Name).Inc()
return err
}
defer rows.Close()
duration := time.Since(now)
queryDurationHistogram.WithLabelValues(q.jobName, q.Name).Observe(duration.Seconds())

updated := 0
metrics := make([]prometheus.Metric, 0, len(q.metrics))
Expand Down

0 comments on commit 7edd749

Please sign in to comment.