mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
Merge pull request #15300 from bergquist/token_usage_stats
adds usage stats for sessions
This commit is contained in:
commit
396a5a947f
54
pkg/infra/usagestats/service.go
Normal file
54
pkg/infra/usagestats/service.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package usagestats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
|
"github.com/grafana/grafana/pkg/social"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/log"
|
||||||
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
var metricsLogger log.Logger = log.New("metrics")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.RegisterService(&UsageStatsService{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type UsageStatsService struct {
|
||||||
|
Cfg *setting.Cfg `inject:""`
|
||||||
|
Bus bus.Bus `inject:""`
|
||||||
|
SQLStore *sqlstore.SqlStore `inject:""`
|
||||||
|
|
||||||
|
oauthProviders map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uss *UsageStatsService) Init() error {
|
||||||
|
|
||||||
|
uss.oauthProviders = social.GetOAuthProviders(uss.Cfg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uss *UsageStatsService) Run(ctx context.Context) error {
|
||||||
|
uss.updateTotalStats()
|
||||||
|
|
||||||
|
onceEveryDayTick := time.NewTicker(time.Hour * 24)
|
||||||
|
everyMinuteTicker := time.NewTicker(time.Minute)
|
||||||
|
defer onceEveryDayTick.Stop()
|
||||||
|
defer everyMinuteTicker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-onceEveryDayTick.C:
|
||||||
|
uss.sendUsageStats(uss.oauthProviders)
|
||||||
|
case <-everyMinuteTicker.C:
|
||||||
|
uss.updateTotalStats()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
177
pkg/infra/usagestats/usage_stats.go
Normal file
177
pkg/infra/usagestats/usage_stats.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package usagestats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
var usageStatsURL = "https://stats.grafana.org/grafana-usage-report"
|
||||||
|
|
||||||
|
func (uss *UsageStatsService) sendUsageStats(oauthProviders map[string]bool) {
|
||||||
|
if !setting.ReportingEnabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metricsLogger.Debug(fmt.Sprintf("Sending anonymous usage stats to %s", usageStatsURL))
|
||||||
|
|
||||||
|
version := strings.Replace(setting.BuildVersion, ".", "_", -1)
|
||||||
|
|
||||||
|
metrics := map[string]interface{}{}
|
||||||
|
report := map[string]interface{}{
|
||||||
|
"version": version,
|
||||||
|
"metrics": metrics,
|
||||||
|
"os": runtime.GOOS,
|
||||||
|
"arch": runtime.GOARCH,
|
||||||
|
"edition": getEdition(),
|
||||||
|
"packaging": setting.Packaging,
|
||||||
|
}
|
||||||
|
|
||||||
|
statsQuery := models.GetSystemStatsQuery{}
|
||||||
|
if err := uss.Bus.Dispatch(&statsQuery); err != nil {
|
||||||
|
metricsLogger.Error("Failed to get system stats", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics["stats.dashboards.count"] = statsQuery.Result.Dashboards
|
||||||
|
metrics["stats.users.count"] = statsQuery.Result.Users
|
||||||
|
metrics["stats.orgs.count"] = statsQuery.Result.Orgs
|
||||||
|
metrics["stats.playlist.count"] = statsQuery.Result.Playlists
|
||||||
|
metrics["stats.plugins.apps.count"] = len(plugins.Apps)
|
||||||
|
metrics["stats.plugins.panels.count"] = len(plugins.Panels)
|
||||||
|
metrics["stats.plugins.datasources.count"] = len(plugins.DataSources)
|
||||||
|
metrics["stats.alerts.count"] = statsQuery.Result.Alerts
|
||||||
|
metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers
|
||||||
|
metrics["stats.datasources.count"] = statsQuery.Result.Datasources
|
||||||
|
metrics["stats.stars.count"] = statsQuery.Result.Stars
|
||||||
|
metrics["stats.folders.count"] = statsQuery.Result.Folders
|
||||||
|
metrics["stats.dashboard_permissions.count"] = statsQuery.Result.DashboardPermissions
|
||||||
|
metrics["stats.folder_permissions.count"] = statsQuery.Result.FolderPermissions
|
||||||
|
metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards
|
||||||
|
metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots
|
||||||
|
metrics["stats.teams.count"] = statsQuery.Result.Teams
|
||||||
|
metrics["stats.total_sessions.count"] = statsQuery.Result.Sessions
|
||||||
|
|
||||||
|
userCount := statsQuery.Result.Users
|
||||||
|
avgSessionsPerUser := statsQuery.Result.Sessions
|
||||||
|
if userCount != 0 {
|
||||||
|
avgSessionsPerUser = avgSessionsPerUser / userCount
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics["stats.avg_sessions_per_user.count"] = avgSessionsPerUser
|
||||||
|
|
||||||
|
dsStats := models.GetDataSourceStatsQuery{}
|
||||||
|
if err := uss.Bus.Dispatch(&dsStats); err != nil {
|
||||||
|
metricsLogger.Error("Failed to get datasource stats", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// send counters for each data source
|
||||||
|
// but ignore any custom data sources
|
||||||
|
// as sending that name could be sensitive information
|
||||||
|
dsOtherCount := 0
|
||||||
|
for _, dsStat := range dsStats.Result {
|
||||||
|
if models.IsKnownDataSourcePlugin(dsStat.Type) {
|
||||||
|
metrics["stats.ds."+dsStat.Type+".count"] = dsStat.Count
|
||||||
|
} else {
|
||||||
|
dsOtherCount += dsStat.Count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metrics["stats.ds.other.count"] = dsOtherCount
|
||||||
|
|
||||||
|
metrics["stats.packaging."+setting.Packaging+".count"] = 1
|
||||||
|
|
||||||
|
dsAccessStats := models.GetDataSourceAccessStatsQuery{}
|
||||||
|
if err := uss.Bus.Dispatch(&dsAccessStats); err != nil {
|
||||||
|
metricsLogger.Error("Failed to get datasource access stats", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// send access counters for each data source
|
||||||
|
// but ignore any custom data sources
|
||||||
|
// as sending that name could be sensitive information
|
||||||
|
dsAccessOtherCount := make(map[string]int64)
|
||||||
|
for _, dsAccessStat := range dsAccessStats.Result {
|
||||||
|
if dsAccessStat.Access == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
access := strings.ToLower(dsAccessStat.Access)
|
||||||
|
|
||||||
|
if models.IsKnownDataSourcePlugin(dsAccessStat.Type) {
|
||||||
|
metrics["stats.ds_access."+dsAccessStat.Type+"."+access+".count"] = dsAccessStat.Count
|
||||||
|
} else {
|
||||||
|
old := dsAccessOtherCount[access]
|
||||||
|
dsAccessOtherCount[access] = old + dsAccessStat.Count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for access, count := range dsAccessOtherCount {
|
||||||
|
metrics["stats.ds_access.other."+access+".count"] = count
|
||||||
|
}
|
||||||
|
|
||||||
|
anStats := models.GetAlertNotifierUsageStatsQuery{}
|
||||||
|
if err := uss.Bus.Dispatch(&anStats); err != nil {
|
||||||
|
metricsLogger.Error("Failed to get alert notification stats", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stats := range anStats.Result {
|
||||||
|
metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count
|
||||||
|
}
|
||||||
|
|
||||||
|
authTypes := map[string]bool{}
|
||||||
|
authTypes["anonymous"] = setting.AnonymousEnabled
|
||||||
|
authTypes["basic_auth"] = setting.BasicAuthEnabled
|
||||||
|
authTypes["ldap"] = setting.LdapEnabled
|
||||||
|
authTypes["auth_proxy"] = setting.AuthProxyEnabled
|
||||||
|
|
||||||
|
for provider, enabled := range oauthProviders {
|
||||||
|
authTypes["oauth_"+provider] = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
for authType, enabled := range authTypes {
|
||||||
|
enabledValue := 0
|
||||||
|
if enabled {
|
||||||
|
enabledValue = 1
|
||||||
|
}
|
||||||
|
metrics["stats.auth_enabled."+authType+".count"] = enabledValue
|
||||||
|
}
|
||||||
|
|
||||||
|
out, _ := json.MarshalIndent(report, "", " ")
|
||||||
|
data := bytes.NewBuffer(out)
|
||||||
|
|
||||||
|
client := http.Client{Timeout: 5 * time.Second}
|
||||||
|
go client.Post(usageStatsURL, "application/json", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uss *UsageStatsService) updateTotalStats() {
|
||||||
|
statsQuery := models.GetSystemStatsQuery{}
|
||||||
|
if err := uss.Bus.Dispatch(&statsQuery); err != nil {
|
||||||
|
metricsLogger.Error("Failed to get system stats", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics.M_StatTotal_Dashboards.Set(float64(statsQuery.Result.Dashboards))
|
||||||
|
metrics.M_StatTotal_Users.Set(float64(statsQuery.Result.Users))
|
||||||
|
metrics.M_StatActive_Users.Set(float64(statsQuery.Result.ActiveUsers))
|
||||||
|
metrics.M_StatTotal_Playlists.Set(float64(statsQuery.Result.Playlists))
|
||||||
|
metrics.M_StatTotal_Orgs.Set(float64(statsQuery.Result.Orgs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEdition() string {
|
||||||
|
if setting.IsEnterprise {
|
||||||
|
return "enterprise"
|
||||||
|
} else {
|
||||||
|
return "oss"
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package metrics
|
package usagestats
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -15,14 +15,21 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMetrics(t *testing.T) {
|
func TestMetrics(t *testing.T) {
|
||||||
Convey("Test send usage stats", t, func() {
|
Convey("Test send usage stats", t, func() {
|
||||||
|
uss := &UsageStatsService{
|
||||||
|
Bus: bus.New(),
|
||||||
|
SQLStore: sqlstore.InitTestDB(t),
|
||||||
|
}
|
||||||
|
|
||||||
var getSystemStatsQuery *models.GetSystemStatsQuery
|
var getSystemStatsQuery *models.GetSystemStatsQuery
|
||||||
bus.AddHandler("test", func(query *models.GetSystemStatsQuery) error {
|
uss.Bus.AddHandler(func(query *models.GetSystemStatsQuery) error {
|
||||||
|
|
||||||
query.Result = &models.SystemStats{
|
query.Result = &models.SystemStats{
|
||||||
Dashboards: 1,
|
Dashboards: 1,
|
||||||
Datasources: 2,
|
Datasources: 2,
|
||||||
@ -38,13 +45,14 @@ func TestMetrics(t *testing.T) {
|
|||||||
ProvisionedDashboards: 12,
|
ProvisionedDashboards: 12,
|
||||||
Snapshots: 13,
|
Snapshots: 13,
|
||||||
Teams: 14,
|
Teams: 14,
|
||||||
|
Sessions: 15,
|
||||||
}
|
}
|
||||||
getSystemStatsQuery = query
|
getSystemStatsQuery = query
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
var getDataSourceStatsQuery *models.GetDataSourceStatsQuery
|
var getDataSourceStatsQuery *models.GetDataSourceStatsQuery
|
||||||
bus.AddHandler("test", func(query *models.GetDataSourceStatsQuery) error {
|
uss.Bus.AddHandler(func(query *models.GetDataSourceStatsQuery) error {
|
||||||
query.Result = []*models.DataSourceStats{
|
query.Result = []*models.DataSourceStats{
|
||||||
{
|
{
|
||||||
Type: models.DS_ES,
|
Type: models.DS_ES,
|
||||||
@ -68,7 +76,7 @@ func TestMetrics(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
var getDataSourceAccessStatsQuery *models.GetDataSourceAccessStatsQuery
|
var getDataSourceAccessStatsQuery *models.GetDataSourceAccessStatsQuery
|
||||||
bus.AddHandler("test", func(query *models.GetDataSourceAccessStatsQuery) error {
|
uss.Bus.AddHandler(func(query *models.GetDataSourceAccessStatsQuery) error {
|
||||||
query.Result = []*models.DataSourceAccessStats{
|
query.Result = []*models.DataSourceAccessStats{
|
||||||
{
|
{
|
||||||
Type: models.DS_ES,
|
Type: models.DS_ES,
|
||||||
@ -116,7 +124,7 @@ func TestMetrics(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
var getAlertNotifierUsageStatsQuery *models.GetAlertNotifierUsageStatsQuery
|
var getAlertNotifierUsageStatsQuery *models.GetAlertNotifierUsageStatsQuery
|
||||||
bus.AddHandler("test", func(query *models.GetAlertNotifierUsageStatsQuery) error {
|
uss.Bus.AddHandler(func(query *models.GetAlertNotifierUsageStatsQuery) error {
|
||||||
query.Result = []*models.NotifierUsageStats{
|
query.Result = []*models.NotifierUsageStats{
|
||||||
{
|
{
|
||||||
Type: "slack",
|
Type: "slack",
|
||||||
@ -155,11 +163,11 @@ func TestMetrics(t *testing.T) {
|
|||||||
"grafana_com": true,
|
"grafana_com": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
sendUsageStats(oauthProviders)
|
uss.sendUsageStats(oauthProviders)
|
||||||
|
|
||||||
Convey("Given reporting not enabled and sending usage stats", func() {
|
Convey("Given reporting not enabled and sending usage stats", func() {
|
||||||
setting.ReportingEnabled = false
|
setting.ReportingEnabled = false
|
||||||
sendUsageStats(oauthProviders)
|
uss.sendUsageStats(oauthProviders)
|
||||||
|
|
||||||
Convey("Should not gather stats or call http endpoint", func() {
|
Convey("Should not gather stats or call http endpoint", func() {
|
||||||
So(getSystemStatsQuery, ShouldBeNil)
|
So(getSystemStatsQuery, ShouldBeNil)
|
||||||
@ -179,7 +187,7 @@ func TestMetrics(t *testing.T) {
|
|||||||
setting.Packaging = "deb"
|
setting.Packaging = "deb"
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
sendUsageStats(oauthProviders)
|
uss.sendUsageStats(oauthProviders)
|
||||||
|
|
||||||
Convey("Should gather stats and call http endpoint", func() {
|
Convey("Should gather stats and call http endpoint", func() {
|
||||||
if waitTimeout(&wg, 2*time.Second) {
|
if waitTimeout(&wg, 2*time.Second) {
|
||||||
@ -221,6 +229,8 @@ func TestMetrics(t *testing.T) {
|
|||||||
So(metrics.Get("stats.provisioned_dashboards.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.ProvisionedDashboards)
|
So(metrics.Get("stats.provisioned_dashboards.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.ProvisionedDashboards)
|
||||||
So(metrics.Get("stats.snapshots.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Snapshots)
|
So(metrics.Get("stats.snapshots.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Snapshots)
|
||||||
So(metrics.Get("stats.teams.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Teams)
|
So(metrics.Get("stats.teams.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Teams)
|
||||||
|
So(metrics.Get("stats.total_sessions.count").MustInt64(), ShouldEqual, 15)
|
||||||
|
So(metrics.Get("stats.avg_sessions_per_user.count").MustInt64(), ShouldEqual, 5)
|
||||||
|
|
||||||
So(metrics.Get("stats.ds."+models.DS_ES+".count").MustInt(), ShouldEqual, 9)
|
So(metrics.Get("stats.ds."+models.DS_ES+".count").MustInt(), ShouldEqual, 9)
|
||||||
So(metrics.Get("stats.ds."+models.DS_PROMETHEUS+".count").MustInt(), ShouldEqual, 10)
|
So(metrics.Get("stats.ds."+models.DS_PROMETHEUS+".count").MustInt(), ShouldEqual, 10)
|
||||||
@ -246,6 +256,7 @@ func TestMetrics(t *testing.T) {
|
|||||||
So(metrics.Get("stats.auth_enabled.oauth_grafana_com.count").MustInt(), ShouldEqual, 1)
|
So(metrics.Get("stats.auth_enabled.oauth_grafana_com.count").MustInt(), ShouldEqual, 1)
|
||||||
|
|
||||||
So(metrics.Get("stats.packaging.deb.count").MustInt(), ShouldEqual, 1)
|
So(metrics.Get("stats.packaging.deb.count").MustInt(), ShouldEqual, 1)
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -1,17 +1,8 @@
|
|||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -68,23 +59,6 @@ var (
|
|||||||
grafanaBuildVersion *prometheus.GaugeVec
|
grafanaBuildVersion *prometheus.GaugeVec
|
||||||
)
|
)
|
||||||
|
|
||||||
func newCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues ...string) *prometheus.CounterVec {
|
|
||||||
counter := prometheus.NewCounterVec(opts, labels)
|
|
||||||
|
|
||||||
for _, label := range labelValues {
|
|
||||||
counter.WithLabelValues(label).Add(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCounterStartingAtZero(opts prometheus.CounterOpts, labelValues ...string) prometheus.Counter {
|
|
||||||
counter := prometheus.NewCounter(opts)
|
|
||||||
counter.Add(0)
|
|
||||||
|
|
||||||
return counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
M_Instance_Start = prometheus.NewCounter(prometheus.CounterOpts{
|
M_Instance_Start = prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
Name: "instance_start_total",
|
Name: "instance_start_total",
|
||||||
@ -362,154 +336,19 @@ func initMetricVars() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTotalStats() {
|
func newCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues ...string) *prometheus.CounterVec {
|
||||||
statsQuery := models.GetSystemStatsQuery{}
|
counter := prometheus.NewCounterVec(opts, labels)
|
||||||
if err := bus.Dispatch(&statsQuery); err != nil {
|
|
||||||
metricsLogger.Error("Failed to get system stats", "error", err)
|
for _, label := range labelValues {
|
||||||
return
|
counter.WithLabelValues(label).Add(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
M_StatTotal_Dashboards.Set(float64(statsQuery.Result.Dashboards))
|
return counter
|
||||||
M_StatTotal_Users.Set(float64(statsQuery.Result.Users))
|
|
||||||
M_StatActive_Users.Set(float64(statsQuery.Result.ActiveUsers))
|
|
||||||
M_StatTotal_Playlists.Set(float64(statsQuery.Result.Playlists))
|
|
||||||
M_StatTotal_Orgs.Set(float64(statsQuery.Result.Orgs))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var usageStatsURL = "https://stats.grafana.org/grafana-usage-report"
|
func newCounterStartingAtZero(opts prometheus.CounterOpts, labelValues ...string) prometheus.Counter {
|
||||||
|
counter := prometheus.NewCounter(opts)
|
||||||
|
counter.Add(0)
|
||||||
|
|
||||||
func getEdition() string {
|
return counter
|
||||||
if setting.IsEnterprise {
|
|
||||||
return "enterprise"
|
|
||||||
} else {
|
|
||||||
return "oss"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendUsageStats(oauthProviders map[string]bool) {
|
|
||||||
if !setting.ReportingEnabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
metricsLogger.Debug("Sending anonymous usage stats to stats.grafana.org")
|
|
||||||
|
|
||||||
version := strings.Replace(setting.BuildVersion, ".", "_", -1)
|
|
||||||
|
|
||||||
metrics := map[string]interface{}{}
|
|
||||||
report := map[string]interface{}{
|
|
||||||
"version": version,
|
|
||||||
"metrics": metrics,
|
|
||||||
"os": runtime.GOOS,
|
|
||||||
"arch": runtime.GOARCH,
|
|
||||||
"edition": getEdition(),
|
|
||||||
"packaging": setting.Packaging,
|
|
||||||
}
|
|
||||||
|
|
||||||
statsQuery := models.GetSystemStatsQuery{}
|
|
||||||
if err := bus.Dispatch(&statsQuery); err != nil {
|
|
||||||
metricsLogger.Error("Failed to get system stats", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics["stats.dashboards.count"] = statsQuery.Result.Dashboards
|
|
||||||
metrics["stats.users.count"] = statsQuery.Result.Users
|
|
||||||
metrics["stats.orgs.count"] = statsQuery.Result.Orgs
|
|
||||||
metrics["stats.playlist.count"] = statsQuery.Result.Playlists
|
|
||||||
metrics["stats.plugins.apps.count"] = len(plugins.Apps)
|
|
||||||
metrics["stats.plugins.panels.count"] = len(plugins.Panels)
|
|
||||||
metrics["stats.plugins.datasources.count"] = len(plugins.DataSources)
|
|
||||||
metrics["stats.alerts.count"] = statsQuery.Result.Alerts
|
|
||||||
metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers
|
|
||||||
metrics["stats.datasources.count"] = statsQuery.Result.Datasources
|
|
||||||
metrics["stats.stars.count"] = statsQuery.Result.Stars
|
|
||||||
metrics["stats.folders.count"] = statsQuery.Result.Folders
|
|
||||||
metrics["stats.dashboard_permissions.count"] = statsQuery.Result.DashboardPermissions
|
|
||||||
metrics["stats.folder_permissions.count"] = statsQuery.Result.FolderPermissions
|
|
||||||
metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards
|
|
||||||
metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots
|
|
||||||
metrics["stats.teams.count"] = statsQuery.Result.Teams
|
|
||||||
|
|
||||||
dsStats := models.GetDataSourceStatsQuery{}
|
|
||||||
if err := bus.Dispatch(&dsStats); err != nil {
|
|
||||||
metricsLogger.Error("Failed to get datasource stats", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// send counters for each data source
|
|
||||||
// but ignore any custom data sources
|
|
||||||
// as sending that name could be sensitive information
|
|
||||||
dsOtherCount := 0
|
|
||||||
for _, dsStat := range dsStats.Result {
|
|
||||||
if models.IsKnownDataSourcePlugin(dsStat.Type) {
|
|
||||||
metrics["stats.ds."+dsStat.Type+".count"] = dsStat.Count
|
|
||||||
} else {
|
|
||||||
dsOtherCount += dsStat.Count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
metrics["stats.ds.other.count"] = dsOtherCount
|
|
||||||
|
|
||||||
metrics["stats.packaging."+setting.Packaging+".count"] = 1
|
|
||||||
|
|
||||||
dsAccessStats := models.GetDataSourceAccessStatsQuery{}
|
|
||||||
if err := bus.Dispatch(&dsAccessStats); err != nil {
|
|
||||||
metricsLogger.Error("Failed to get datasource access stats", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// send access counters for each data source
|
|
||||||
// but ignore any custom data sources
|
|
||||||
// as sending that name could be sensitive information
|
|
||||||
dsAccessOtherCount := make(map[string]int64)
|
|
||||||
for _, dsAccessStat := range dsAccessStats.Result {
|
|
||||||
if dsAccessStat.Access == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
access := strings.ToLower(dsAccessStat.Access)
|
|
||||||
|
|
||||||
if models.IsKnownDataSourcePlugin(dsAccessStat.Type) {
|
|
||||||
metrics["stats.ds_access."+dsAccessStat.Type+"."+access+".count"] = dsAccessStat.Count
|
|
||||||
} else {
|
|
||||||
old := dsAccessOtherCount[access]
|
|
||||||
dsAccessOtherCount[access] = old + dsAccessStat.Count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for access, count := range dsAccessOtherCount {
|
|
||||||
metrics["stats.ds_access.other."+access+".count"] = count
|
|
||||||
}
|
|
||||||
|
|
||||||
anStats := models.GetAlertNotifierUsageStatsQuery{}
|
|
||||||
if err := bus.Dispatch(&anStats); err != nil {
|
|
||||||
metricsLogger.Error("Failed to get alert notification stats", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, stats := range anStats.Result {
|
|
||||||
metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count
|
|
||||||
}
|
|
||||||
|
|
||||||
authTypes := map[string]bool{}
|
|
||||||
authTypes["anonymous"] = setting.AnonymousEnabled
|
|
||||||
authTypes["basic_auth"] = setting.BasicAuthEnabled
|
|
||||||
authTypes["ldap"] = setting.LdapEnabled
|
|
||||||
authTypes["auth_proxy"] = setting.AuthProxyEnabled
|
|
||||||
|
|
||||||
for provider, enabled := range oauthProviders {
|
|
||||||
authTypes["oauth_"+provider] = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
for authType, enabled := range authTypes {
|
|
||||||
enabledValue := 0
|
|
||||||
if enabled {
|
|
||||||
enabledValue = 1
|
|
||||||
}
|
|
||||||
metrics["stats.auth_enabled."+authType+".count"] = enabledValue
|
|
||||||
}
|
|
||||||
|
|
||||||
out, _ := json.MarshalIndent(report, "", " ")
|
|
||||||
data := bytes.NewBuffer(out)
|
|
||||||
|
|
||||||
client := http.Client{Timeout: 5 * time.Second}
|
|
||||||
go client.Post(usageStatsURL, "application/json", data)
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package metrics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
|
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
|
||||||
@ -30,7 +29,6 @@ type InternalMetricsService struct {
|
|||||||
|
|
||||||
intervalSeconds int64
|
intervalSeconds int64
|
||||||
graphiteCfg *graphitebridge.Config
|
graphiteCfg *graphitebridge.Config
|
||||||
oauthProviders map[string]bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *InternalMetricsService) Init() error {
|
func (im *InternalMetricsService) Init() error {
|
||||||
@ -50,22 +48,6 @@ func (im *InternalMetricsService) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
M_Instance_Start.Inc()
|
M_Instance_Start.Inc()
|
||||||
|
|
||||||
// set the total stats gauges before we publishing metrics
|
<-ctx.Done()
|
||||||
updateTotalStats()
|
return ctx.Err()
|
||||||
|
|
||||||
onceEveryDayTick := time.NewTicker(time.Hour * 24)
|
|
||||||
everyMinuteTicker := time.NewTicker(time.Minute)
|
|
||||||
defer onceEveryDayTick.Stop()
|
|
||||||
defer everyMinuteTicker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-onceEveryDayTick.C:
|
|
||||||
sendUsageStats(im.oauthProviders)
|
|
||||||
case <-everyMinuteTicker.C:
|
|
||||||
updateTotalStats()
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/social"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
|
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@ -24,8 +22,6 @@ func (im *InternalMetricsService) readSettings() error {
|
|||||||
return fmt.Errorf("Unable to parse metrics graphite section, %v", err)
|
return fmt.Errorf("Unable to parse metrics graphite section, %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
im.oauthProviders = social.GetOAuthProviders(im.Cfg)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ type SystemStats struct {
|
|||||||
FolderPermissions int64
|
FolderPermissions int64
|
||||||
Folders int64
|
Folders int64
|
||||||
ProvisionedDashboards int64
|
ProvisionedDashboards int64
|
||||||
|
Sessions int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataSourceStats struct {
|
type DataSourceStats struct {
|
||||||
|
@ -74,7 +74,8 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error {
|
|||||||
|
|
||||||
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_provisioning") + `) AS provisioned_dashboards,`)
|
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_provisioning") + `) AS provisioned_dashboards,`)
|
||||||
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_snapshot") + `) AS snapshots,`)
|
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_snapshot") + `) AS snapshots,`)
|
||||||
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("team") + `) AS teams`)
|
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("team") + `) AS teams,`)
|
||||||
|
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("user_auth_token") + `) AS sessions`)
|
||||||
|
|
||||||
var stats m.SystemStats
|
var stats m.SystemStats
|
||||||
_, err := x.SQL(sb.GetSqlString(), sb.params...).Get(&stats)
|
_, err := x.SQL(sb.GetSqlString(), sb.params...).Get(&stats)
|
||||||
|
Loading…
Reference in New Issue
Block a user