diff --git a/pkg/infra/usagestats/usage_stats.go b/pkg/infra/usagestats/usage_stats.go index 3ba8c180411..266e4b20a23 100644 --- a/pkg/infra/usagestats/usage_stats.go +++ b/pkg/infra/usagestats/usage_stats.go @@ -52,6 +52,9 @@ func (uss *UsageStatsService) GetUsageReport(ctx context.Context) (UsageReport, metrics["stats.dashboards.count"] = statsQuery.Result.Dashboards metrics["stats.users.count"] = statsQuery.Result.Users + metrics["stats.admins.count"] = statsQuery.Result.Admins + metrics["stats.editors.count"] = statsQuery.Result.Editors + metrics["stats.viewers.count"] = statsQuery.Result.Viewers metrics["stats.orgs.count"] = statsQuery.Result.Orgs metrics["stats.playlist.count"] = statsQuery.Result.Playlists metrics["stats.plugins.apps.count"] = uss.PluginManager.AppCount() @@ -59,6 +62,15 @@ func (uss *UsageStatsService) GetUsageReport(ctx context.Context) (UsageReport, metrics["stats.plugins.datasources.count"] = uss.PluginManager.DataSourceCount() metrics["stats.alerts.count"] = statsQuery.Result.Alerts metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers + metrics["stats.active_admins.count"] = statsQuery.Result.ActiveAdmins + metrics["stats.active_editors.count"] = statsQuery.Result.ActiveEditors + metrics["stats.active_viewers.count"] = statsQuery.Result.ActiveViewers + metrics["stats.active_sessions.count"] = statsQuery.Result.ActiveSessions + metrics["stats.daily_active_users.count"] = statsQuery.Result.DailyActiveUsers + metrics["stats.daily_active_admins.count"] = statsQuery.Result.DailyActiveAdmins + metrics["stats.daily_active_editors.count"] = statsQuery.Result.DailyActiveEditors + metrics["stats.daily_active_viewers.count"] = statsQuery.Result.DailyActiveViewers + metrics["stats.daily_active_sessions.count"] = statsQuery.Result.DailyActiveSessions metrics["stats.datasources.count"] = statsQuery.Result.Datasources metrics["stats.stars.count"] = statsQuery.Result.Stars metrics["stats.folders.count"] = statsQuery.Result.Folders diff --git a/pkg/infra/usagestats/usage_stats_test.go b/pkg/infra/usagestats/usage_stats_test.go index c9c55567553..d8a950b9bea 100644 --- a/pkg/infra/usagestats/usage_stats_test.go +++ b/pkg/infra/usagestats/usage_stats_test.go @@ -47,7 +47,19 @@ func TestMetrics(t *testing.T) { Dashboards: 1, Datasources: 2, Users: 3, + Admins: 31, + Editors: 32, + Viewers: 33, ActiveUsers: 4, + ActiveAdmins: 21, + ActiveEditors: 22, + ActiveViewers: 23, + ActiveSessions: 24, + DailyActiveUsers: 25, + DailyActiveAdmins: 26, + DailyActiveEditors: 27, + DailyActiveViewers: 28, + DailyActiveSessions: 29, Orgs: 5, Playlists: 6, Alerts: 7, @@ -298,6 +310,9 @@ func TestMetrics(t *testing.T) { metrics := j.Get("metrics") assert.Equal(t, getSystemStatsQuery.Result.Dashboards, metrics.Get("stats.dashboards.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.Users, metrics.Get("stats.users.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.Admins, metrics.Get("stats.admins.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.Editors, metrics.Get("stats.editors.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.Viewers, metrics.Get("stats.viewers.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.Orgs, metrics.Get("stats.orgs.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.Playlists, metrics.Get("stats.playlist.count").MustInt64()) assert.Equal(t, uss.PluginManager.AppCount(), metrics.Get("stats.plugins.apps.count").MustInt()) @@ -305,6 +320,15 @@ func TestMetrics(t *testing.T) { assert.Equal(t, uss.PluginManager.DataSourceCount(), metrics.Get("stats.plugins.datasources.count").MustInt()) assert.Equal(t, getSystemStatsQuery.Result.Alerts, metrics.Get("stats.alerts.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.ActiveUsers, metrics.Get("stats.active_users.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.ActiveAdmins, metrics.Get("stats.active_admins.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.ActiveEditors, metrics.Get("stats.active_editors.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.ActiveViewers, metrics.Get("stats.active_viewers.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.ActiveSessions, metrics.Get("stats.active_sessions.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.DailyActiveUsers, metrics.Get("stats.daily_active_users.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.DailyActiveAdmins, metrics.Get("stats.daily_active_admins.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.DailyActiveEditors, metrics.Get("stats.daily_active_editors.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.DailyActiveViewers, metrics.Get("stats.daily_active_viewers.count").MustInt64()) + assert.Equal(t, getSystemStatsQuery.Result.DailyActiveSessions, metrics.Get("stats.daily_active_sessions.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.Datasources, metrics.Get("stats.datasources.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.Stars, metrics.Get("stats.stars.count").MustInt64()) assert.Equal(t, getSystemStatsQuery.Result.Folders, metrics.Get("stats.folders.count").MustInt64()) diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 9036d9ca5da..43bb80bdeec 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -5,6 +5,7 @@ type SystemStats struct { Datasources int64 Users int64 ActiveUsers int64 + DailyActiveUsers int64 Orgs int64 Playlists int64 Alerts int64 @@ -25,14 +26,17 @@ type SystemStats struct { DashboardsViewersCanAdmin int64 FoldersViewersCanEdit int64 FoldersViewersCanAdmin int64 - - Admins int - Editors int - Viewers int - ActiveAdmins int - ActiveEditors int - ActiveViewers int - ActiveSessions int + Admins int64 + Editors int64 + Viewers int64 + ActiveAdmins int64 + ActiveEditors int64 + ActiveViewers int64 + ActiveSessions int64 + DailyActiveAdmins int64 + DailyActiveEditors int64 + DailyActiveViewers int64 + DailyActiveSessions int64 } type DataSourceStats struct { @@ -68,23 +72,28 @@ type GetAlertNotifierUsageStatsQuery struct { } type AdminStats struct { - Orgs int `json:"orgs"` - Dashboards int `json:"dashboards"` - Snapshots int `json:"snapshots"` - Tags int `json:"tags"` - Datasources int `json:"datasources"` - Playlists int `json:"playlists"` - Stars int `json:"stars"` - Alerts int `json:"alerts"` - Users int `json:"users"` - Admins int `json:"admins"` - Editors int `json:"editors"` - Viewers int `json:"viewers"` - ActiveUsers int `json:"activeUsers"` - ActiveAdmins int `json:"activeAdmins"` - ActiveEditors int `json:"activeEditors"` - ActiveViewers int `json:"activeViewers"` - ActiveSessions int `json:"activeSessions"` + Orgs int64 `json:"orgs"` + Dashboards int64 `json:"dashboards"` + Snapshots int64 `json:"snapshots"` + Tags int64 `json:"tags"` + Datasources int64 `json:"datasources"` + Playlists int64 `json:"playlists"` + Stars int64 `json:"stars"` + Alerts int64 `json:"alerts"` + Users int64 `json:"users"` + Admins int64 `json:"admins"` + Editors int64 `json:"editors"` + Viewers int64 `json:"viewers"` + ActiveUsers int64 `json:"activeUsers"` + ActiveAdmins int64 `json:"activeAdmins"` + ActiveEditors int64 `json:"activeEditors"` + ActiveViewers int64 `json:"activeViewers"` + ActiveSessions int64 `json:"activeSessions"` + DailyActiveUsers int64 `json:"dailyActiveUsers"` + DailyActiveAdmins int64 `json:"dailyActiveAdmins"` + DailyActiveEditors int64 `json:"dailyActiveEditors"` + DailyActiveViewers int64 `json:"dailyActiveViewers"` + DailyActiveSessions int64 `json:"dailyActiveSessions"` } type GetAdminStatsQuery struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 5b9dc4dc500..00d121d3530 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -19,6 +19,7 @@ func init() { } const activeUserTimeLimit = time.Hour * 24 * 30 +const dailyActiveUserTimeLimit = time.Hour * 24 func GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error { var rawSQL = `SELECT COUNT(*) AS count, type FROM ` + dialect.Quote("alert_notification") + ` GROUP BY type` @@ -54,6 +55,9 @@ func GetSystemStats(query *models.GetSystemStatsQuery) error { activeUserDeadlineDate := time.Now().Add(-activeUserTimeLimit) sb.Write(`(SELECT COUNT(*) FROM `+dialect.Quote("user")+` WHERE last_seen_at > ?) AS active_users,`, activeUserDeadlineDate) + dailyActiveUserDeadlineDate := time.Now().Add(-dailyActiveUserTimeLimit) + sb.Write(`(SELECT COUNT(*) FROM `+dialect.Quote("user")+` WHERE last_seen_at > ?) AS daily_active_users,`, dailyActiveUserDeadlineDate) + sb.Write(`(SELECT COUNT(id) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboards,`, dialect.BooleanStr(false)) sb.Write(`(SELECT COUNT(id) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS folders,`, dialect.BooleanStr(true)) @@ -112,7 +116,10 @@ func roleCounterSQL() string { strconv.FormatInt(userStatsCache.total.Viewers, 10) + ` AS viewers, ` + strconv.FormatInt(userStatsCache.active.Admins, 10) + ` AS active_admins, ` + strconv.FormatInt(userStatsCache.active.Editors, 10) + ` AS active_editors, ` + - strconv.FormatInt(userStatsCache.active.Viewers, 10) + ` AS active_viewers` + strconv.FormatInt(userStatsCache.active.Viewers, 10) + ` AS active_viewers, ` + + strconv.FormatInt(userStatsCache.dailyActive.Admins, 10) + ` AS daily_active_admins, ` + + strconv.FormatInt(userStatsCache.dailyActive.Editors, 10) + ` AS daily_active_editors, ` + + strconv.FormatInt(userStatsCache.dailyActive.Viewers, 10) + ` AS daily_active_viewers` return sqlQuery } @@ -131,6 +138,7 @@ func viewersPermissionsCounterSQL(statName string, isFolder bool, permission mod func GetAdminStats(query *models.GetAdminStatsQuery) error { activeEndDate := time.Now().Add(-activeUserTimeLimit) + dailyActiveEndDate := time.Now().Add(-dailyActiveUserTimeLimit) var rawSQL = `SELECT ( @@ -173,14 +181,22 @@ func GetAdminStats(query *models.GetAdminStatsQuery) error { SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` WHERE last_seen_at > ? ) AS active_users, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("user") + ` WHERE last_seen_at > ? + ) AS daily_active_users, ` + roleCounterSQL() + `, ( SELECT COUNT(*) FROM ` + dialect.Quote("user_auth_token") + ` WHERE rotated_at > ? - ) AS active_sessions` + ) AS active_sessions, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("user_auth_token") + ` WHERE rotated_at > ? + ) AS daily_active_sessions` var stats models.AdminStats - _, err := x.SQL(rawSQL, activeEndDate, activeEndDate.Unix()).Get(&stats) + _, err := x.SQL(rawSQL, activeEndDate, dailyActiveEndDate, activeEndDate.Unix(), dailyActiveEndDate.Unix()).Get(&stats) if err != nil { return err } @@ -216,8 +232,9 @@ func updateUserRoleCountsIfNecessary(ctx context.Context, forced bool) error { } type memoUserStats struct { - active models.UserStats - total models.UserStats + active models.UserStats + dailyActive models.UserStats + total models.UserStats memoized time.Time } @@ -230,7 +247,7 @@ var ( func updateUserRoleCounts(ctx context.Context) error { query := ` SELECT role AS bitrole, active, COUNT(role) AS count FROM - (SELECT last_seen_at>? AS active, SUM(role) AS role + (SELECT last_seen_at>? AS active, last_seen_at>? AS daily_active, SUM(role) AS role FROM (SELECT u.id, CASE org_user.role @@ -242,18 +259,20 @@ SELECT role AS bitrole, active, COUNT(role) AS count FROM FROM ` + dialect.Quote("user") + ` AS u INNER JOIN org_user ON org_user.user_id = u.id GROUP BY u.id, u.last_seen_at, org_user.role) AS t2 GROUP BY id, last_seen_at) AS t1 -GROUP BY active, role;` +GROUP BY active, daily_active, role;` activeUserDeadline := time.Now().Add(-activeUserTimeLimit) + dailyActiveUserDeadline := time.Now().Add(-dailyActiveUserTimeLimit) type rolebitmap struct { - Active bool - Bitrole int64 - Count int64 + Active bool + DailyActive bool + Bitrole int64 + Count int64 } bitmap := []rolebitmap{} - err := x.Context(ctx).SQL(query, activeUserDeadline).Find(&bitmap) + err := x.Context(ctx).SQL(query, activeUserDeadline, dailyActiveUserDeadline).Find(&bitmap) if err != nil { return err } @@ -271,6 +290,9 @@ GROUP BY active, role;` if role.Active { memo.active = addToStats(memo.active, roletype, role.Count) } + if role.DailyActive { + memo.dailyActive = addToStats(memo.dailyActive, roletype, role.Count) + } } userStatsCache = memo