mirror of
https://github.com/grafana/grafana.git
synced 2024-11-27 19:30:36 -06:00
516 lines
17 KiB
Go
516 lines
17 KiB
Go
package metrics
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"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"
|
|
)
|
|
|
|
const exporterName = "grafana"
|
|
|
|
var (
|
|
M_Instance_Start prometheus.Counter
|
|
M_Page_Status *prometheus.CounterVec
|
|
M_Api_Status *prometheus.CounterVec
|
|
M_Proxy_Status *prometheus.CounterVec
|
|
M_Http_Request_Total *prometheus.CounterVec
|
|
M_Http_Request_Summary *prometheus.SummaryVec
|
|
|
|
M_Api_User_SignUpStarted prometheus.Counter
|
|
M_Api_User_SignUpCompleted prometheus.Counter
|
|
M_Api_User_SignUpInvite prometheus.Counter
|
|
M_Api_Dashboard_Save prometheus.Summary
|
|
M_Api_Dashboard_Get prometheus.Summary
|
|
M_Api_Dashboard_Search prometheus.Summary
|
|
M_Api_Admin_User_Create prometheus.Counter
|
|
M_Api_Login_Post prometheus.Counter
|
|
M_Api_Login_OAuth prometheus.Counter
|
|
M_Api_Org_Create prometheus.Counter
|
|
|
|
M_Api_Dashboard_Snapshot_Create prometheus.Counter
|
|
M_Api_Dashboard_Snapshot_External prometheus.Counter
|
|
M_Api_Dashboard_Snapshot_Get prometheus.Counter
|
|
M_Api_Dashboard_Insert prometheus.Counter
|
|
M_Alerting_Result_State *prometheus.CounterVec
|
|
M_Alerting_Notification_Sent *prometheus.CounterVec
|
|
M_Aws_CloudWatch_GetMetricStatistics prometheus.Counter
|
|
M_Aws_CloudWatch_ListMetrics prometheus.Counter
|
|
M_Aws_CloudWatch_GetMetricData prometheus.Counter
|
|
M_DB_DataSource_QueryById prometheus.Counter
|
|
|
|
// Timers
|
|
M_DataSource_ProxyReq_Timer prometheus.Summary
|
|
M_Alerting_Execution_Time prometheus.Summary
|
|
|
|
// StatTotals
|
|
M_Alerting_Active_Alerts prometheus.Gauge
|
|
M_StatTotal_Dashboards prometheus.Gauge
|
|
M_StatTotal_Users prometheus.Gauge
|
|
M_StatActive_Users prometheus.Gauge
|
|
M_StatTotal_Orgs prometheus.Gauge
|
|
M_StatTotal_Playlists prometheus.Gauge
|
|
|
|
// M_Grafana_Version is a gauge that contains build info about this binary
|
|
//
|
|
// Deprecated: use M_Grafana_Build_Version instead.
|
|
M_Grafana_Version *prometheus.GaugeVec
|
|
|
|
// grafanaBuildVersion is a gauge that contains build info about this binary
|
|
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() {
|
|
M_Instance_Start = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "instance_start_total",
|
|
Help: "counter for started instances",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
httpStatusCodes := []string{"200", "404", "500", "unknown"}
|
|
M_Page_Status = newCounterVecStartingAtZero(
|
|
prometheus.CounterOpts{
|
|
Name: "page_response_status_total",
|
|
Help: "page http response status",
|
|
Namespace: exporterName,
|
|
}, []string{"code"}, httpStatusCodes...)
|
|
|
|
M_Api_Status = newCounterVecStartingAtZero(
|
|
prometheus.CounterOpts{
|
|
Name: "api_response_status_total",
|
|
Help: "api http response status",
|
|
Namespace: exporterName,
|
|
}, []string{"code"}, httpStatusCodes...)
|
|
|
|
M_Proxy_Status = newCounterVecStartingAtZero(
|
|
prometheus.CounterOpts{
|
|
Name: "proxy_response_status_total",
|
|
Help: "proxy http response status",
|
|
Namespace: exporterName,
|
|
}, []string{"code"}, httpStatusCodes...)
|
|
|
|
M_Http_Request_Total = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Name: "http_request_total",
|
|
Help: "http request counter",
|
|
},
|
|
[]string{"handler", "statuscode", "method"},
|
|
)
|
|
|
|
M_Http_Request_Summary = prometheus.NewSummaryVec(
|
|
prometheus.SummaryOpts{
|
|
Name: "http_request_duration_milliseconds",
|
|
Help: "http request summary",
|
|
},
|
|
[]string{"handler", "statuscode", "method"},
|
|
)
|
|
|
|
M_Api_User_SignUpStarted = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_user_signup_started_total",
|
|
Help: "amount of users who started the signup flow",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_User_SignUpCompleted = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_user_signup_completed_total",
|
|
Help: "amount of users who completed the signup flow",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_User_SignUpInvite = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_user_signup_invite_total",
|
|
Help: "amount of users who have been invited",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Dashboard_Save = prometheus.NewSummary(prometheus.SummaryOpts{
|
|
Name: "api_dashboard_save_milliseconds",
|
|
Help: "summary for dashboard save duration",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Dashboard_Get = prometheus.NewSummary(prometheus.SummaryOpts{
|
|
Name: "api_dashboard_get_milliseconds",
|
|
Help: "summary for dashboard get duration",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Dashboard_Search = prometheus.NewSummary(prometheus.SummaryOpts{
|
|
Name: "api_dashboard_search_milliseconds",
|
|
Help: "summary for dashboard search duration",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Admin_User_Create = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_admin_user_created_total",
|
|
Help: "api admin user created counter",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Login_Post = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_login_post_total",
|
|
Help: "api login post counter",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Login_OAuth = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_login_oauth_total",
|
|
Help: "api login oauth counter",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Org_Create = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_org_create_total",
|
|
Help: "api org created counter",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Dashboard_Snapshot_Create = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_dashboard_snapshot_create_total",
|
|
Help: "dashboard snapshots created",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Dashboard_Snapshot_External = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_dashboard_snapshot_external_total",
|
|
Help: "external dashboard snapshots created",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Dashboard_Snapshot_Get = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_dashboard_snapshot_get_total",
|
|
Help: "loaded dashboards",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Api_Dashboard_Insert = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "api_models_dashboard_insert_total",
|
|
Help: "dashboards inserted ",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Alerting_Result_State = prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "alerting_result_total",
|
|
Help: "alert execution result counter",
|
|
Namespace: exporterName,
|
|
}, []string{"state"})
|
|
|
|
M_Alerting_Notification_Sent = prometheus.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "alerting_notification_sent_total",
|
|
Help: "counter for how many alert notifications been sent",
|
|
Namespace: exporterName,
|
|
}, []string{"type"})
|
|
|
|
M_Aws_CloudWatch_GetMetricStatistics = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "aws_cloudwatch_get_metric_statistics_total",
|
|
Help: "counter for getting metric statistics from aws",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Aws_CloudWatch_ListMetrics = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "aws_cloudwatch_list_metrics_total",
|
|
Help: "counter for getting list of metrics from aws",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Aws_CloudWatch_GetMetricData = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "aws_cloudwatch_get_metric_data_total",
|
|
Help: "counter for getting metric data time series from aws",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_DB_DataSource_QueryById = newCounterStartingAtZero(prometheus.CounterOpts{
|
|
Name: "db_datasource_query_by_id_total",
|
|
Help: "counter for getting datasource by id",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_DataSource_ProxyReq_Timer = prometheus.NewSummary(prometheus.SummaryOpts{
|
|
Name: "api_dataproxy_request_all_milliseconds",
|
|
Help: "summary for dataproxy request duration",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Alerting_Execution_Time = prometheus.NewSummary(prometheus.SummaryOpts{
|
|
Name: "alerting_execution_time_milliseconds",
|
|
Help: "summary of alert exeuction duration",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Alerting_Active_Alerts = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "alerting_active_alerts",
|
|
Help: "amount of active alerts",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_StatTotal_Dashboards = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "stat_totals_dashboard",
|
|
Help: "total amount of dashboards",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_StatTotal_Users = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "stat_total_users",
|
|
Help: "total amount of users",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_StatActive_Users = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "stat_active_users",
|
|
Help: "number of active users",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_StatTotal_Orgs = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "stat_total_orgs",
|
|
Help: "total amount of orgs",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_StatTotal_Playlists = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "stat_total_playlists",
|
|
Help: "total amount of playlists",
|
|
Namespace: exporterName,
|
|
})
|
|
|
|
M_Grafana_Version = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "info",
|
|
Help: "Information about the Grafana. This metric is deprecated. please use `grafana_build_info`",
|
|
Namespace: exporterName,
|
|
}, []string{"version"})
|
|
|
|
grafanaBuildVersion = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
|
Name: "build_info",
|
|
Help: "A metric with a constant '1' value labeled by version, revision, branch, and goversion from which Grafana was built.",
|
|
Namespace: exporterName,
|
|
}, []string{"version", "revision", "branch", "goversion"})
|
|
}
|
|
|
|
// SetBuildInformation sets the build information for this binary
|
|
func SetBuildInformation(version, revision, branch string) {
|
|
// We export this info twice for backwards compatibility.
|
|
// Once this have been released for some time we should be able to remote `M_Grafana_Version`
|
|
// The reason we added a new one is that its common practice in the prometheus community
|
|
// to name this metric `*_build_info` so its easy to do aggregation on all programs.
|
|
M_Grafana_Version.WithLabelValues(version).Set(1)
|
|
grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version()).Set(1)
|
|
}
|
|
|
|
func initMetricVars() {
|
|
prometheus.MustRegister(
|
|
M_Instance_Start,
|
|
M_Page_Status,
|
|
M_Api_Status,
|
|
M_Proxy_Status,
|
|
M_Http_Request_Total,
|
|
M_Http_Request_Summary,
|
|
M_Api_User_SignUpStarted,
|
|
M_Api_User_SignUpCompleted,
|
|
M_Api_User_SignUpInvite,
|
|
M_Api_Dashboard_Save,
|
|
M_Api_Dashboard_Get,
|
|
M_Api_Dashboard_Search,
|
|
M_DataSource_ProxyReq_Timer,
|
|
M_Alerting_Execution_Time,
|
|
M_Api_Admin_User_Create,
|
|
M_Api_Login_Post,
|
|
M_Api_Login_OAuth,
|
|
M_Api_Org_Create,
|
|
M_Api_Dashboard_Snapshot_Create,
|
|
M_Api_Dashboard_Snapshot_External,
|
|
M_Api_Dashboard_Snapshot_Get,
|
|
M_Api_Dashboard_Insert,
|
|
M_Alerting_Result_State,
|
|
M_Alerting_Notification_Sent,
|
|
M_Aws_CloudWatch_GetMetricStatistics,
|
|
M_Aws_CloudWatch_ListMetrics,
|
|
M_Aws_CloudWatch_GetMetricData,
|
|
M_DB_DataSource_QueryById,
|
|
M_Alerting_Active_Alerts,
|
|
M_StatTotal_Dashboards,
|
|
M_StatTotal_Users,
|
|
M_StatActive_Users,
|
|
M_StatTotal_Orgs,
|
|
M_StatTotal_Playlists,
|
|
M_Grafana_Version,
|
|
grafanaBuildVersion)
|
|
|
|
}
|
|
|
|
func updateTotalStats() {
|
|
statsQuery := models.GetSystemStatsQuery{}
|
|
if err := bus.Dispatch(&statsQuery); err != nil {
|
|
metricsLogger.Error("Failed to get system stats", "error", err)
|
|
return
|
|
}
|
|
|
|
M_StatTotal_Dashboards.Set(float64(statsQuery.Result.Dashboards))
|
|
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 getEdition() string {
|
|
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)
|
|
}
|