Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad9d408ac2 | ||
|
|
9f7f13e02a | ||
|
|
d9540bc368 | ||
|
|
b1543b5676 | ||
|
|
0b4aa8b909 | ||
|
|
4d21ade9cd | ||
|
|
36c8a3bd93 | ||
|
|
589be2872b | ||
|
|
f60edaf029 |
@@ -30,7 +30,7 @@ executors:
|
||||
- image: cimg/go:1.14
|
||||
e2e:
|
||||
docker:
|
||||
- image: srclosson/grafana-plugin-ci-e2e:latest
|
||||
- image: grafana/ci-e2e:12.19.0-1
|
||||
grafana-build:
|
||||
docker:
|
||||
- image: grafana/build-container:1.2.26
|
||||
|
||||
@@ -646,6 +646,12 @@ disable_total_stats = false
|
||||
basic_auth_username =
|
||||
basic_auth_password =
|
||||
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# can expose more information about the Grafana instance.
|
||||
[metrics.environment_info]
|
||||
#exampleLabel1 = exampleValue1
|
||||
#exampleLabel2 = exampleValue2
|
||||
|
||||
# Send internal Grafana metrics to graphite
|
||||
[metrics.graphite]
|
||||
# Enable by setting the address setting (ex localhost:2003)
|
||||
|
||||
@@ -640,6 +640,12 @@
|
||||
; basic_auth_username =
|
||||
; basic_auth_password =
|
||||
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# can expose more information about the Grafana instance.
|
||||
[metrics.environment_info]
|
||||
#exampleLabel1 = exampleValue1
|
||||
#exampleLabel2 = exampleValue2
|
||||
|
||||
# Send internal metrics to Graphite
|
||||
[metrics.graphite]
|
||||
# Enable by setting the address setting (ex localhost:2003)
|
||||
|
||||
@@ -1041,6 +1041,15 @@ If both are set, then basic authentication is required to access the metrics end
|
||||
|
||||
<hr>
|
||||
|
||||
## [metrics.environment_info]
|
||||
|
||||
Adds dimensions to the `grafana_environment_info` metric, which can expose more information about the Grafana instance.
|
||||
|
||||
```
|
||||
; exampleLabel1 = exampleValue1
|
||||
; exampleLabel2 = exampleValue2
|
||||
```
|
||||
|
||||
## [metrics.graphite]
|
||||
|
||||
Use these options if you want to send internal Grafana metrics to Graphite.
|
||||
|
||||
1
go.mod
1
go.mod
@@ -29,6 +29,7 @@ require (
|
||||
github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/gchaincl/sqlhooks v1.3.0
|
||||
github.com/go-macaron/binding v0.0.0-20190806013118-0b4f37bab25b
|
||||
github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07
|
||||
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659
|
||||
|
||||
10
go.sum
10
go.sum
@@ -176,10 +176,10 @@ github.com/cenkalti/backoff v1.0.0/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQ
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/centrifugal/centrifuge v0.10.0 h1:Eften1Akke0mgHJwHPqhG8oR/6q/SO4y/+KKdz+Qr14=
|
||||
github.com/centrifugal/centrifuge v0.10.0/go.mod h1:mkuUdBuYFqz2GincQhkwWmHuVs9VWEHhMjzMcdUk6oA=
|
||||
github.com/centrifugal/protocol v0.3.3 h1:GCNee3RFsjQu6SyKBX0Ir7ByUrp+Gw0MU/PsIc2CM2s=
|
||||
github.com/centrifugal/protocol v0.3.3/go.mod h1:2YbBCaDwQHl37ErRdMrKSj18X2yVvpkQYtSX6aVbe5A=
|
||||
github.com/centrifugal/centrifuge v0.11.0 h1:aaG74PBO+rBmz3hE9W8bCGz8kofiam1LNE4EAVtiMaA=
|
||||
github.com/centrifugal/centrifuge v0.11.0/go.mod h1:jdFw/2dBFpME3OTisc5FVAC1ilqodnyMnQFjem0k0yg=
|
||||
github.com/centrifugal/protocol v0.3.4 h1:9q22iSp4CQOdQahfopvfmWWxDbj1Lo7ERG2j56mAxkE=
|
||||
github.com/centrifugal/protocol v0.3.4/go.mod h1:2YbBCaDwQHl37ErRdMrKSj18X2yVvpkQYtSX6aVbe5A=
|
||||
github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
@@ -317,6 +317,8 @@ github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03D
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
|
||||
github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y=
|
||||
github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34=
|
||||
github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
|
||||
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"version": "7.2.1"
|
||||
"version": "7.2.2"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.2",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.2",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.2",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.2",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -44,7 +44,7 @@
|
||||
"types": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@cypress/webpack-preprocessor": "4.1.3",
|
||||
"@grafana/e2e-selectors": "7.2.1",
|
||||
"@grafana/e2e-selectors": "7.2.2",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@mochajs/json-file-reporter": "^1.2.0",
|
||||
"blink-diff": "1.0.13",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.2",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -22,8 +22,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "7.2.1",
|
||||
"@grafana/ui": "7.2.1",
|
||||
"@grafana/data": "7.2.2",
|
||||
"@grafana/ui": "7.2.2",
|
||||
"systemjs": "0.20.19",
|
||||
"systemjs-plugin-css": "0.1.37"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.2",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.2",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -27,8 +27,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.27",
|
||||
"@grafana/data": "7.2.1",
|
||||
"@grafana/e2e-selectors": "7.2.1",
|
||||
"@grafana/data": "7.2.2",
|
||||
"@grafana/e2e-selectors": "7.2.2",
|
||||
"@grafana/slate-react": "0.22.9-grafana",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@iconscout/react-unicons": "1.1.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jaegertracing/jaeger-ui-components",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -14,8 +14,8 @@
|
||||
"typescript": "3.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "7.2.1",
|
||||
"@grafana/ui": "7.2.1",
|
||||
"@grafana/data": "7.2.2",
|
||||
"@grafana/ui": "7.2.2",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/deep-freeze": "^0.1.1",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
|
||||
@@ -513,6 +513,26 @@ func SetBuildInformation(version, revision, branch string) {
|
||||
grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1)
|
||||
}
|
||||
|
||||
// SetEnvironmentInformation exposes environment values provided by the operators as an `_info` metric.
|
||||
// If there are no environment metrics labels configured, this metric will not be exposed.
|
||||
func SetEnvironmentInformation(labels map[string]string) error {
|
||||
if len(labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
grafanaEnvironmentInfo := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "environment_info",
|
||||
Help: "A metric with a constant '1' value labeled by environment information about the running instance.",
|
||||
Namespace: ExporterName,
|
||||
ConstLabels: labels,
|
||||
})
|
||||
|
||||
prometheus.MustRegister(grafanaEnvironmentInfo)
|
||||
|
||||
grafanaEnvironmentInfo.Set(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetPluginBuildInformation(pluginID, pluginType, version string) {
|
||||
grafanaPluginBuildInfoDesc.WithLabelValues(pluginID, pluginType, version).Set(1)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
httpRequestsInFlight prometheus.Gauge
|
||||
httpRequestsInFlight prometheus.Gauge
|
||||
httpRequestDurationHistogram *prometheus.HistogramVec
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -23,33 +25,52 @@ func init() {
|
||||
},
|
||||
)
|
||||
|
||||
prometheus.MustRegister(httpRequestsInFlight)
|
||||
httpRequestDurationHistogram = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "grafana",
|
||||
Name: "http_request_duration_seconds",
|
||||
Help: "Histogram of latencies for HTTP requests.",
|
||||
Buckets: []float64{.1, .2, .4, 1, 3, 8, 20, 60, 120},
|
||||
},
|
||||
[]string{"handler"},
|
||||
)
|
||||
|
||||
prometheus.MustRegister(httpRequestsInFlight, httpRequestDurationHistogram)
|
||||
}
|
||||
|
||||
// RequestMetrics is a middleware handler that instruments the request
|
||||
func RequestMetrics(handler string) macaron.Handler {
|
||||
return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
|
||||
rw := res.(macaron.ResponseWriter)
|
||||
now := time.Now()
|
||||
httpRequestsInFlight.Inc()
|
||||
defer httpRequestsInFlight.Dec()
|
||||
c.Next()
|
||||
func RequestMetrics(cfg *setting.Cfg) func(handler string) macaron.Handler {
|
||||
return func(handler string) macaron.Handler {
|
||||
return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
|
||||
rw := res.(macaron.ResponseWriter)
|
||||
now := time.Now()
|
||||
httpRequestsInFlight.Inc()
|
||||
defer httpRequestsInFlight.Dec()
|
||||
c.Next()
|
||||
|
||||
status := rw.Status()
|
||||
status := rw.Status()
|
||||
|
||||
code := sanitizeCode(status)
|
||||
method := sanitizeMethod(req.Method)
|
||||
metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc()
|
||||
duration := time.Since(now).Nanoseconds() / int64(time.Millisecond)
|
||||
metrics.MHttpRequestSummary.WithLabelValues(handler, code, method).Observe(float64(duration))
|
||||
code := sanitizeCode(status)
|
||||
method := sanitizeMethod(req.Method)
|
||||
metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc()
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(req.RequestURI, "/api/datasources/proxy"):
|
||||
countProxyRequests(status)
|
||||
case strings.HasPrefix(req.RequestURI, "/api/"):
|
||||
countApiRequests(status)
|
||||
default:
|
||||
countPageRequests(status)
|
||||
duration := time.Since(now).Nanoseconds() / int64(time.Millisecond)
|
||||
|
||||
// enable histogram and disable summaries for http requests.
|
||||
if cfg.IsHTTPRequestHistogramEnabled() {
|
||||
httpRequestDurationHistogram.WithLabelValues(handler).Observe(float64(duration))
|
||||
} else {
|
||||
metrics.MHttpRequestSummary.WithLabelValues(handler, code, method).Observe(float64(duration))
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(req.RequestURI, "/api/datasources/proxy"):
|
||||
countProxyRequests(status)
|
||||
case strings.HasPrefix(req.RequestURI, "/api/"):
|
||||
countApiRequests(status)
|
||||
default:
|
||||
countPageRequests(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
_ "github.com/grafana/grafana/pkg/extensions"
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
_ "github.com/grafana/grafana/pkg/infra/metrics"
|
||||
_ "github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
_ "github.com/grafana/grafana/pkg/infra/serverlock"
|
||||
@@ -117,6 +118,9 @@ func (s *Server) init(cfg *Config) error {
|
||||
|
||||
s.loadConfiguration()
|
||||
s.writePIDFile()
|
||||
if err := metrics.SetEnvironmentInformation(s.cfg.MetricsGrafanaEnvironmentInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
login.Init()
|
||||
social.NewOAuthService()
|
||||
@@ -269,7 +273,7 @@ func (s *Server) buildServiceGraph(services []*registry.Descriptor) error {
|
||||
objs := []interface{}{
|
||||
bus.GetBus(),
|
||||
s.cfg,
|
||||
routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing),
|
||||
routing.NewRouteRegister(middleware.RequestMetrics(s.cfg), middleware.RequestTracing),
|
||||
localcache.New(5*time.Minute, 10*time.Minute),
|
||||
s,
|
||||
}
|
||||
|
||||
110
pkg/services/sqlstore/database_wrapper.go
Normal file
110
pkg/services/sqlstore/database_wrapper.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"time"
|
||||
|
||||
"github.com/gchaincl/sqlhooks"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"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 (
|
||||
databaseQueryCounter *prometheus.CounterVec
|
||||
databaseQueryHistogram prometheus.Histogram
|
||||
)
|
||||
|
||||
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{
|
||||
Namespace: "grafana",
|
||||
Name: "database_queries_duration_seconds",
|
||||
Help: "Database query histogram",
|
||||
Buckets: prometheus.ExponentialBuckets(0.0001, 4, 9),
|
||||
})
|
||||
|
||||
prometheus.MustRegister(databaseQueryCounter, databaseQueryHistogram)
|
||||
}
|
||||
|
||||
// WrapDatabaseDriverWithHooks creates a fake database driver that
|
||||
// executes pre and post functions which we use to gather metrics about
|
||||
// database queries.
|
||||
func WrapDatabaseDriverWithHooks(dbType string) string {
|
||||
drivers := map[string]driver.Driver{
|
||||
migrator.SQLITE: &sqlite3.SQLiteDriver{},
|
||||
migrator.MYSQL: &mysql.MySQLDriver{},
|
||||
migrator.POSTGRES: &pq.Driver{},
|
||||
}
|
||||
|
||||
d, exist := drivers[dbType]
|
||||
if !exist {
|
||||
return dbType
|
||||
}
|
||||
|
||||
driverWithHooks := dbType + "WithHooks"
|
||||
sql.Register(driverWithHooks, sqlhooks.Wrap(d, &databaseQueryWrapper{log: log.New("sqlstore.metrics")}))
|
||||
core.RegisterDriver(driverWithHooks, &databaseQueryWrapperParser{dbType: dbType})
|
||||
return driverWithHooks
|
||||
}
|
||||
|
||||
// databaseQueryWrapper satisfies the sqlhook.databaseQueryWrapper interface
|
||||
// which allow us to wrap all SQL queries with a `Before` & `After` hook.
|
||||
type databaseQueryWrapper struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// 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) {
|
||||
begin := ctx.Value(databaseQueryWrapperKey{}).(time.Time)
|
||||
elapsed := time.Since(begin)
|
||||
databaseQueryCounter.WithLabelValues("success").Inc()
|
||||
databaseQueryHistogram.Observe(elapsed.Seconds())
|
||||
h.log.Debug("query finished", "status", "success", "elapsed time", elapsed, "sql", query)
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// OnError will be called if any error happens
|
||||
func (h *databaseQueryWrapper) OnError(ctx context.Context, err error, query string, args ...interface{}) error {
|
||||
status := "error"
|
||||
// https://golang.org/pkg/database/sql/driver/#ErrSkip
|
||||
if err == nil || err == driver.ErrSkip {
|
||||
status = "success"
|
||||
}
|
||||
|
||||
begin := ctx.Value(databaseQueryWrapperKey{}).(time.Time)
|
||||
elapsed := time.Since(begin)
|
||||
databaseQueryCounter.WithLabelValues(status).Inc()
|
||||
databaseQueryHistogram.Observe(elapsed.Seconds())
|
||||
h.log.Debug("query finished", "status", status, "elapsed time", elapsed, "sql", query, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
type databaseQueryWrapperParser struct {
|
||||
dbType string
|
||||
}
|
||||
|
||||
func (hp *databaseQueryWrapperParser) Parse(string, string) (*core.Uri, error) {
|
||||
return &core.Uri{
|
||||
DbType: core.DbType(hp.dbType),
|
||||
}, nil
|
||||
}
|
||||
@@ -52,15 +52,21 @@ type Dialect interface {
|
||||
IsDeadlock(err error) bool
|
||||
}
|
||||
|
||||
type dialectFunc func(*xorm.Engine) Dialect
|
||||
|
||||
var supportedDialects = map[string]dialectFunc{
|
||||
MYSQL: NewMysqlDialect,
|
||||
SQLITE: NewSqlite3Dialect,
|
||||
POSTGRES: NewPostgresDialect,
|
||||
MYSQL + "WithHooks": NewMysqlDialect,
|
||||
SQLITE + "WithHooks": NewSqlite3Dialect,
|
||||
POSTGRES + "WithHooks": NewPostgresDialect,
|
||||
}
|
||||
|
||||
func NewDialect(engine *xorm.Engine) Dialect {
|
||||
name := engine.DriverName()
|
||||
switch name {
|
||||
case MYSQL:
|
||||
return NewMysqlDialect(engine)
|
||||
case SQLITE:
|
||||
return NewSqlite3Dialect(engine)
|
||||
case POSTGRES:
|
||||
return NewPostgresDialect(engine)
|
||||
if fn, exist := supportedDialects[name]; exist {
|
||||
return fn(engine)
|
||||
}
|
||||
|
||||
panic("Unsupported database type: " + name)
|
||||
|
||||
@@ -14,7 +14,7 @@ type Mysql struct {
|
||||
BaseDialect
|
||||
}
|
||||
|
||||
func NewMysqlDialect(engine *xorm.Engine) *Mysql {
|
||||
func NewMysqlDialect(engine *xorm.Engine) Dialect {
|
||||
d := Mysql{}
|
||||
d.BaseDialect.dialect = &d
|
||||
d.BaseDialect.engine = engine
|
||||
|
||||
@@ -15,7 +15,7 @@ type Postgres struct {
|
||||
BaseDialect
|
||||
}
|
||||
|
||||
func NewPostgresDialect(engine *xorm.Engine) *Postgres {
|
||||
func NewPostgresDialect(engine *xorm.Engine) Dialect {
|
||||
d := Postgres{}
|
||||
d.BaseDialect.dialect = &d
|
||||
d.BaseDialect.engine = engine
|
||||
|
||||
@@ -11,7 +11,7 @@ type Sqlite3 struct {
|
||||
BaseDialect
|
||||
}
|
||||
|
||||
func NewSqlite3Dialect(engine *xorm.Engine) *Sqlite3 {
|
||||
func NewSqlite3Dialect(engine *xorm.Engine) Dialect {
|
||||
d := Sqlite3{}
|
||||
d.BaseDialect.dialect = &d
|
||||
d.BaseDialect.engine = engine
|
||||
|
||||
@@ -238,6 +238,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)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/prometheus/common/model"
|
||||
ini "gopkg.in/ini.v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/gtime"
|
||||
@@ -261,17 +262,20 @@ type Cfg struct {
|
||||
CookieSameSiteDisabled bool
|
||||
CookieSameSiteMode http.SameSite
|
||||
|
||||
TempDataLifetime time.Duration
|
||||
TempDataLifetime time.Duration
|
||||
PluginsEnableAlpha bool
|
||||
PluginsAppsSkipVerifyTLS bool
|
||||
PluginSettings PluginSettings
|
||||
PluginsAllowUnsigned []string
|
||||
DisableSanitizeHtml bool
|
||||
EnterpriseLicensePath string
|
||||
|
||||
// Metrics
|
||||
MetricsEndpointEnabled bool
|
||||
MetricsEndpointBasicAuthUsername string
|
||||
MetricsEndpointBasicAuthPassword string
|
||||
MetricsEndpointDisableTotalStats bool
|
||||
PluginsEnableAlpha bool
|
||||
PluginsAppsSkipVerifyTLS bool
|
||||
PluginSettings PluginSettings
|
||||
PluginsAllowUnsigned []string
|
||||
DisableSanitizeHtml bool
|
||||
EnterpriseLicensePath string
|
||||
MetricsGrafanaEnvironmentInfo map[string]string
|
||||
|
||||
// Dashboards
|
||||
DefaultHomeDashboardPath string
|
||||
@@ -320,6 +324,14 @@ func (c Cfg) IsLiveEnabled() bool {
|
||||
return c.FeatureToggles["live"]
|
||||
}
|
||||
|
||||
func (c Cfg) IsDatabaseMetricsEnabled() bool {
|
||||
return c.FeatureToggles["database_metrics"]
|
||||
}
|
||||
|
||||
func (c Cfg) IsHTTPRequestHistogramEnabled() bool {
|
||||
return c.FeatureToggles["http_request_histogram"]
|
||||
}
|
||||
|
||||
type CommandLineArgs struct {
|
||||
Config string
|
||||
HomePath string
|
||||
@@ -395,6 +407,29 @@ func applyEnvVariableOverrides(file *ini.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readGrafanaEnvironmentMetrics() error {
|
||||
environmentMetricsSection := cfg.Raw.Section("metrics.environment_info")
|
||||
keys := environmentMetricsSection.Keys()
|
||||
cfg.MetricsGrafanaEnvironmentInfo = make(map[string]string, len(keys))
|
||||
|
||||
for _, key := range keys {
|
||||
labelName := model.LabelName(key.Name())
|
||||
labelValue := model.LabelValue(key.Value())
|
||||
|
||||
if !labelName.IsValid() {
|
||||
return fmt.Errorf("invalid label name in [metrics.environment_info] configuration. name %q", labelName)
|
||||
}
|
||||
|
||||
if !labelValue.IsValid() {
|
||||
return fmt.Errorf("invalid label value in [metrics.environment_info] configuration. name %q value %q", labelName, labelValue)
|
||||
}
|
||||
|
||||
cfg.MetricsGrafanaEnvironmentInfo[string(labelName)] = string(labelValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readAnnotationSettings() {
|
||||
dashboardAnnotation := cfg.Raw.Section("annotations.dashboard")
|
||||
apiIAnnotation := cfg.Raw.Section("annotations.api")
|
||||
@@ -760,6 +795,9 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
cfg.readSmtpSettings()
|
||||
cfg.readQuotaSettings()
|
||||
cfg.readAnnotationSettings()
|
||||
if err := cfg.readGrafanaEnvironmentMetrics(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if VerifyEmailEnabled && !cfg.Smtp.Enabled {
|
||||
log.Warnf("require_email_validation is enabled but smtp is disabled")
|
||||
|
||||
@@ -412,10 +412,7 @@ func (e *cloudWatchExecutor) handleGetDimensions(ctx context.Context, parameters
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
dsInfo := e.getDSInfo(region)
|
||||
dsInfo.Namespace = namespace
|
||||
|
||||
if dimensionValues, err = e.getDimensionsForCustomMetrics(region); err != nil {
|
||||
if dimensionValues, err = e.getDimensionsForCustomMetrics(region, namespace); err != nil {
|
||||
return nil, errutil.Wrap("unable to call AWS API", err)
|
||||
}
|
||||
}
|
||||
@@ -700,15 +697,14 @@ func (e *cloudWatchExecutor) resourceGroupsGetResources(region string, filters [
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (e *cloudWatchExecutor) getAllMetrics(region string) (cloudwatch.ListMetricsOutput, error) {
|
||||
func (e *cloudWatchExecutor) getAllMetrics(region, namespace string) (cloudwatch.ListMetricsOutput, error) {
|
||||
client, err := e.getCWClient(region)
|
||||
if err != nil {
|
||||
return cloudwatch.ListMetricsOutput{}, err
|
||||
}
|
||||
|
||||
dsInfo := e.getDSInfo(region)
|
||||
params := &cloudwatch.ListMetricsInput{
|
||||
Namespace: aws.String(dsInfo.Namespace),
|
||||
Namespace: aws.String(namespace),
|
||||
}
|
||||
|
||||
plog.Debug("Listing metrics pages")
|
||||
@@ -737,7 +733,6 @@ func (e *cloudWatchExecutor) getMetricsForCustomMetrics(region, namespace string
|
||||
defer metricsCacheLock.Unlock()
|
||||
|
||||
dsInfo := e.getDSInfo(region)
|
||||
dsInfo.Namespace = namespace
|
||||
|
||||
if _, ok := customMetricsMetricsMap[dsInfo.Profile]; !ok {
|
||||
customMetricsMetricsMap[dsInfo.Profile] = make(map[string]map[string]*customMetricsCache)
|
||||
@@ -745,36 +740,36 @@ func (e *cloudWatchExecutor) getMetricsForCustomMetrics(region, namespace string
|
||||
if _, ok := customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region]; !ok {
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region] = make(map[string]*customMetricsCache)
|
||||
}
|
||||
if _, ok := customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace]; !ok {
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace] = &customMetricsCache{}
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
|
||||
if _, ok := customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace]; !ok {
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace] = &customMetricsCache{}
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache = make([]string, 0)
|
||||
}
|
||||
|
||||
if customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire.After(time.Now()) {
|
||||
return customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, nil
|
||||
if customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace].Expire.After(time.Now()) {
|
||||
return customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache, nil
|
||||
}
|
||||
result, err := e.getAllMetrics(region)
|
||||
result, err := e.getAllMetrics(region, namespace)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire = time.Now().Add(5 * time.Minute)
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache = make([]string, 0)
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace].Expire = time.Now().Add(5 * time.Minute)
|
||||
|
||||
for _, metric := range result.Metrics {
|
||||
if isDuplicate(customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, *metric.MetricName) {
|
||||
if isDuplicate(customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache, *metric.MetricName) {
|
||||
continue
|
||||
}
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = append(
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, *metric.MetricName)
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache = append(
|
||||
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache, *metric.MetricName)
|
||||
}
|
||||
|
||||
return customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, nil
|
||||
return customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache, nil
|
||||
}
|
||||
|
||||
var dimensionsCacheLock sync.Mutex
|
||||
|
||||
func (e *cloudWatchExecutor) getDimensionsForCustomMetrics(region string) ([]string, error) {
|
||||
func (e *cloudWatchExecutor) getDimensionsForCustomMetrics(region, namespace string) ([]string, error) {
|
||||
dimensionsCacheLock.Lock()
|
||||
defer dimensionsCacheLock.Unlock()
|
||||
|
||||
@@ -786,32 +781,32 @@ func (e *cloudWatchExecutor) getDimensionsForCustomMetrics(region string) ([]str
|
||||
if _, ok := customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region]; !ok {
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region] = make(map[string]*customMetricsCache)
|
||||
}
|
||||
if _, ok := customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace]; !ok {
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace] = &customMetricsCache{}
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
|
||||
if _, ok := customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace]; !ok {
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace] = &customMetricsCache{}
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache = make([]string, 0)
|
||||
}
|
||||
|
||||
if customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire.After(time.Now()) {
|
||||
return customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, nil
|
||||
if customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace].Expire.After(time.Now()) {
|
||||
return customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache, nil
|
||||
}
|
||||
result, err := e.getAllMetrics(region)
|
||||
result, err := e.getAllMetrics(region, namespace)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire = time.Now().Add(5 * time.Minute)
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache = make([]string, 0)
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace].Expire = time.Now().Add(5 * time.Minute)
|
||||
|
||||
for _, metric := range result.Metrics {
|
||||
for _, dimension := range metric.Dimensions {
|
||||
if isDuplicate(customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, *dimension.Name) {
|
||||
if isDuplicate(customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache, *dimension.Name) {
|
||||
continue
|
||||
}
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = append(
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, *dimension.Name)
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache = append(
|
||||
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache, *dimension.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, nil
|
||||
return customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][namespace].Cache, nil
|
||||
}
|
||||
|
||||
func isDuplicate(nameList []string, target string) bool {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@grafana-plugins/input-datasource",
|
||||
"version": "7.2.1",
|
||||
"version": "7.2.2",
|
||||
"description": "Input Datasource",
|
||||
"private": true,
|
||||
"repository": {
|
||||
@@ -16,9 +16,9 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@grafana/data": "7.2.1",
|
||||
"@grafana/toolkit": "7.2.1",
|
||||
"@grafana/ui": "7.2.1"
|
||||
"@grafana/data": "7.2.2",
|
||||
"@grafana/toolkit": "7.2.2",
|
||||
"@grafana/ui": "7.2.2"
|
||||
},
|
||||
"volta": {
|
||||
"node": "12.16.2"
|
||||
|
||||
Reference in New Issue
Block a user