| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | package sqlstore | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"database/sql" | 
					
						
							|  |  |  | 	"database/sql/driver" | 
					
						
							| 
									
										
										
										
											2020-11-19 21:47:17 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-01-07 15:21:35 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/gchaincl/sqlhooks" | 
					
						
							|  |  |  | 	"github.com/go-sql-driver/mysql" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2022-01-20 18:10:12 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/tracing" | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/sqlstore/migrator" | 
					
						
							|  |  |  | 	"github.com/lib/pq" | 
					
						
							|  |  |  | 	"github.com/mattn/go-sqlite3" | 
					
						
							|  |  |  | 	"github.com/prometheus/client_golang/prometheus" | 
					
						
							|  |  |  | 	"xorm.io/core" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							| 
									
										
										
										
											2021-01-07 15:21:35 +08:00
										 |  |  | 	databaseQueryHistogram *prometheus.HistogramVec | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func init() { | 
					
						
							| 
									
										
										
										
											2021-01-07 15:21:35 +08:00
										 |  |  | 	databaseQueryHistogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 		Namespace: "grafana", | 
					
						
							|  |  |  | 		Name:      "database_queries_duration_seconds", | 
					
						
							|  |  |  | 		Help:      "Database query histogram", | 
					
						
							| 
									
										
										
										
											2021-02-09 02:51:48 +08:00
										 |  |  | 		Buckets:   prometheus.ExponentialBuckets(0.00001, 4, 10), | 
					
						
							| 
									
										
										
										
											2021-01-07 15:21:35 +08:00
										 |  |  | 	}, []string{"status"}) | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 15:21:35 +08:00
										 |  |  | 	prometheus.MustRegister(databaseQueryHistogram) | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // WrapDatabaseDriverWithHooks creates a fake database driver that
 | 
					
						
							|  |  |  | // executes pre and post functions which we use to gather metrics about
 | 
					
						
							| 
									
										
										
										
											2021-01-07 15:21:35 +08:00
										 |  |  | // database queries. It also registers the metrics.
 | 
					
						
							| 
									
										
										
										
											2022-01-20 18:10:12 +08:00
										 |  |  | func WrapDatabaseDriverWithHooks(dbType string, tracer tracing.Tracer) string { | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 	drivers := map[string]driver.Driver{ | 
					
						
							| 
									
										
										
										
											2020-11-11 13:21:08 +08:00
										 |  |  | 		migrator.SQLite:   &sqlite3.SQLiteDriver{}, | 
					
						
							|  |  |  | 		migrator.MySQL:    &mysql.MySQLDriver{}, | 
					
						
							|  |  |  | 		migrator.Postgres: &pq.Driver{}, | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	d, exist := drivers[dbType] | 
					
						
							|  |  |  | 	if !exist { | 
					
						
							|  |  |  | 		return dbType | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	driverWithHooks := dbType + "WithHooks" | 
					
						
							| 
									
										
										
										
											2022-01-20 18:10:12 +08:00
										 |  |  | 	sql.Register(driverWithHooks, sqlhooks.Wrap(d, &databaseQueryWrapper{log: log.New("sqlstore.metrics"), tracer: tracer})) | 
					
						
							| 
									
										
										
										
											2021-01-07 15:21:35 +08:00
										 |  |  | 	core.RegisterDriver(driverWithHooks, &databaseQueryWrapperDriver{dbType: dbType}) | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 	return driverWithHooks | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // databaseQueryWrapper satisfies the sqlhook.databaseQueryWrapper interface
 | 
					
						
							|  |  |  | // which allow us to wrap all SQL queries with a `Before` & `After` hook.
 | 
					
						
							|  |  |  | type databaseQueryWrapper struct { | 
					
						
							| 
									
										
										
										
											2022-01-20 18:10:12 +08:00
										 |  |  | 	log    log.Logger | 
					
						
							|  |  |  | 	tracer tracing.Tracer | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // databaseQueryWrapperKey is used as key to save values in `context.Context`
 | 
					
						
							|  |  |  | type databaseQueryWrapperKey struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Before hook will print the query with its args and return the context with the timestamp
 | 
					
						
							|  |  |  | func (h *databaseQueryWrapper) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) { | 
					
						
							|  |  |  | 	return context.WithValue(ctx, databaseQueryWrapperKey{}, time.Now()), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // After hook will get the timestamp registered on the Before hook and print the elapsed time
 | 
					
						
							|  |  |  | func (h *databaseQueryWrapper) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) { | 
					
						
							| 
									
										
										
										
											2021-05-27 19:55:33 +08:00
										 |  |  | 	h.instrument(ctx, "success", query, nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ctx, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *databaseQueryWrapper) instrument(ctx context.Context, status string, query string, err error) { | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 	begin := ctx.Value(databaseQueryWrapperKey{}).(time.Time) | 
					
						
							|  |  |  | 	elapsed := time.Since(begin) | 
					
						
							| 
									
										
										
										
											2021-05-27 19:55:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	histogram := databaseQueryHistogram.WithLabelValues(status) | 
					
						
							| 
									
										
										
										
											2022-04-14 23:54:49 +08:00
										 |  |  | 	if traceID := tracing.TraceIDFromContext(ctx, true); traceID != "" { | 
					
						
							| 
									
										
										
										
											2021-05-27 19:55:33 +08:00
										 |  |  | 		// Need to type-convert the Observer to an
 | 
					
						
							|  |  |  | 		// ExemplarObserver. This will always work for a
 | 
					
						
							|  |  |  | 		// HistogramVec.
 | 
					
						
							|  |  |  | 		histogram.(prometheus.ExemplarObserver).ObserveWithExemplar( | 
					
						
							|  |  |  | 			elapsed.Seconds(), prometheus.Labels{"traceID": traceID}, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		histogram.Observe(elapsed.Seconds()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-05 13:39:22 +08:00
										 |  |  | 	ctx = log.IncDBCallCounter(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-20 18:10:12 +08:00
										 |  |  | 	_, span := h.tracer.Start(ctx, "database query") | 
					
						
							|  |  |  | 	defer span.End() | 
					
						
							| 
									
										
										
										
											2021-05-27 19:55:33 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-20 18:10:12 +08:00
										 |  |  | 	span.AddEvents([]string{"query", "status"}, []tracing.EventValue{{Str: query}, {Str: status}}) | 
					
						
							| 
									
										
										
										
											2021-05-27 19:55:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-01-20 18:10:12 +08:00
										 |  |  | 		span.AddEvents([]string{"error"}, []tracing.EventValue{{Str: err.Error()}}) | 
					
						
							| 
									
										
										
										
											2021-05-27 19:55:33 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-21 00:32:06 +08:00
										 |  |  | 	ctxLogger := h.log.FromContext(ctx) | 
					
						
							|  |  |  | 	ctxLogger.Debug("query finished", "status", status, "elapsed time", elapsed, "sql", query, "error", err) | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // OnError will be called if any error happens
 | 
					
						
							|  |  |  | func (h *databaseQueryWrapper) OnError(ctx context.Context, err error, query string, args ...interface{}) error { | 
					
						
							| 
									
										
										
										
											2022-09-08 17:14:55 +08:00
										 |  |  | 	// Not a user error: driver is telling sql package that an
 | 
					
						
							|  |  |  | 	// optional interface method is not implemented. There is
 | 
					
						
							|  |  |  | 	// nothing to instrument here.
 | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 	// https://golang.org/pkg/database/sql/driver/#ErrSkip
 | 
					
						
							| 
									
										
										
										
											2022-09-08 17:14:55 +08:00
										 |  |  | 	// https://github.com/DataDog/dd-trace-go/issues/270
 | 
					
						
							|  |  |  | 	if errors.Is(err, driver.ErrSkip) { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	status := "error" | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 		status = "success" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-27 19:55:33 +08:00
										 |  |  | 	h.instrument(ctx, status, query, err) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 15:21:35 +08:00
										 |  |  | // databaseQueryWrapperDriver satisfies the xorm.io/core.Driver interface
 | 
					
						
							|  |  |  | type databaseQueryWrapperDriver struct { | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | 	dbType string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-07 15:21:35 +08:00
										 |  |  | 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) | 
					
						
							| 
									
										
										
										
											2020-10-20 02:06:12 +08:00
										 |  |  | } |