diff --git a/pkg/services/sqlstore/database_wrapper.go b/pkg/services/sqlstore/database_wrapper.go index 9826319d769..172654a55d1 100644 --- a/pkg/services/sqlstore/database_wrapper.go +++ b/pkg/services/sqlstore/database_wrapper.go @@ -5,6 +5,7 @@ import ( "database/sql" "database/sql/driver" "errors" + "fmt" "time" "github.com/gchaincl/sqlhooks" @@ -18,30 +19,23 @@ import ( ) var ( - databaseQueryCounter *prometheus.CounterVec - databaseQueryHistogram prometheus.Histogram + databaseQueryHistogram *prometheus.HistogramVec ) func init() { - databaseQueryCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "grafana", - Name: "database_queries_total", - Help: "The total amount of Database queries", - }, []string{"status"}) - - databaseQueryHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ + databaseQueryHistogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Namespace: "grafana", Name: "database_queries_duration_seconds", Help: "Database query histogram", Buckets: prometheus.ExponentialBuckets(0.0001, 4, 9), - }) + }, []string{"status"}) - prometheus.MustRegister(databaseQueryCounter, databaseQueryHistogram) + prometheus.MustRegister(databaseQueryHistogram) } // WrapDatabaseDriverWithHooks creates a fake database driver that // executes pre and post functions which we use to gather metrics about -// database queries. +// database queries. It also registers the metrics. func WrapDatabaseDriverWithHooks(dbType string) string { drivers := map[string]driver.Driver{ migrator.SQLite: &sqlite3.SQLiteDriver{}, @@ -56,7 +50,7 @@ func WrapDatabaseDriverWithHooks(dbType string) string { driverWithHooks := dbType + "WithHooks" sql.Register(driverWithHooks, sqlhooks.Wrap(d, &databaseQueryWrapper{log: log.New("sqlstore.metrics")})) - core.RegisterDriver(driverWithHooks, &databaseQueryWrapperParser{dbType: dbType}) + core.RegisterDriver(driverWithHooks, &databaseQueryWrapperDriver{dbType: dbType}) return driverWithHooks } @@ -78,8 +72,7 @@ func (h *databaseQueryWrapper) Before(ctx context.Context, query string, args .. func (h *databaseQueryWrapper) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) { begin := ctx.Value(databaseQueryWrapperKey{}).(time.Time) elapsed := time.Since(begin) - databaseQueryCounter.WithLabelValues("success").Inc() - databaseQueryHistogram.Observe(elapsed.Seconds()) + databaseQueryHistogram.WithLabelValues("success").Observe(elapsed.Seconds()) h.log.Debug("query finished", "status", "success", "elapsed time", elapsed, "sql", query) return ctx, nil } @@ -94,18 +87,20 @@ func (h *databaseQueryWrapper) OnError(ctx context.Context, err error, query str begin := ctx.Value(databaseQueryWrapperKey{}).(time.Time) elapsed := time.Since(begin) - databaseQueryCounter.WithLabelValues(status).Inc() - databaseQueryHistogram.Observe(elapsed.Seconds()) + databaseQueryHistogram.WithLabelValues(status).Observe(elapsed.Seconds()) h.log.Debug("query finished", "status", status, "elapsed time", elapsed, "sql", query, "error", err) return err } -type databaseQueryWrapperParser struct { +// databaseQueryWrapperDriver satisfies the xorm.io/core.Driver interface +type databaseQueryWrapperDriver struct { dbType string } -func (hp *databaseQueryWrapperParser) Parse(string, string) (*core.Uri, error) { - return &core.Uri{ - DbType: core.DbType(hp.dbType), - }, nil +func (hp *databaseQueryWrapperDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { + driver := core.QueryDriver(hp.dbType) + if driver == nil { + return nil, fmt.Errorf("could not find driver with name %s", hp.dbType) + } + return driver.Parse(driverName, dataSourceName) } diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 227f87d11de..ae45c5c8f9c 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -259,6 +259,10 @@ func (ss *SQLStore) getEngine() (*xorm.Engine, error) { return nil, err } + if ss.Cfg.IsDatabaseMetricsEnabled() { + ss.dbCfg.Type = WrapDatabaseDriverWithHooks(ss.dbCfg.Type) + } + sqlog.Info("Connecting to DB", "dbtype", ss.dbCfg.Type) if ss.dbCfg.Type == migrator.SQLite && strings.HasPrefix(connectionString, "file:") { exists, err := fs.Exists(ss.dbCfg.Path) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index e1db33e7313..3d4cbb56930 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -348,6 +348,11 @@ func (cfg Cfg) IsNgAlertEnabled() bool { return cfg.FeatureToggles["ngalert"] } +// IsDatabaseMetricsEnabled returns whether the database instrumentation feature is enabled. +func (cfg Cfg) IsDatabaseMetricsEnabled() bool { + return cfg.FeatureToggles["database_metrics"] +} + // IsHTTPRequestHistogramEnabled returns whether the http_request_histogram feature is enabled. func (cfg Cfg) IsHTTPRequestHistogramEnabled() bool { return cfg.FeatureToggles["http_request_histogram"]