More GH comments changes

- Move comments to SQL statements
- Shrink interface, add struct for stats
- No fatal errors, use defaults
This commit is contained in:
Till Faelligen 2022-04-07 08:41:48 +02:00
parent 5bb0b56029
commit 27c76e3f89
7 changed files with 213 additions and 131 deletions

View file

@ -22,6 +22,7 @@ import (
"github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/dendrite/userapi/storage/tables" "github.com/matrix-org/dendrite/userapi/storage/tables"
"github.com/matrix-org/dendrite/userapi/types"
) )
type Profile interface { type Profile interface {
@ -34,6 +35,7 @@ type Profile interface {
type Database interface { type Database interface {
Profile Profile
Statistics
GetAccountByPassword(ctx context.Context, localpart, plaintextPassword string) (*api.Account, error) GetAccountByPassword(ctx context.Context, localpart, plaintextPassword string) (*api.Account, error)
// CreateAccount makes a new account with the given login name and password, and creates an empty profile // CreateAccount makes a new account with the given login name and password, and creates an empty profile
// for this account. If no password is supplied, the account will be a passwordless account. If the // for this account. If no password is supplied, the account will be a passwordless account. If the
@ -107,14 +109,10 @@ type Database interface {
GetPushers(ctx context.Context, localpart string) ([]api.Pusher, error) GetPushers(ctx context.Context, localpart string) ([]api.Pusher, error)
RemovePusher(ctx context.Context, appid, pushkey, localpart string) error RemovePusher(ctx context.Context, appid, pushkey, localpart string) error
RemovePushers(ctx context.Context, appid, pushkey string) error RemovePushers(ctx context.Context, appid, pushkey string) error
}
AllUsers(ctx context.Context) (result int64, err error) type Statistics interface {
NonBridgedUsers(ctx context.Context) (result int64, err error) UserStatistics(ctx context.Context) (*types.UserStatistics, *types.DatabaseEngine, error)
RegisteredUserByType(ctx context.Context) (map[string]int64, error)
DailyUsers(ctx context.Context) (result int64, err error)
MonthlyUsers(ctx context.Context) (result int64, err error)
R30Users(ctx context.Context) (map[string]int64, error)
R30UsersV2(ctx context.Context) (map[string]int64, error)
} }
// Err3PIDInUse is the error returned when trying to save an association involving // Err3PIDInUse is the error returned when trying to save an association involving

View file

@ -21,9 +21,9 @@ import (
"github.com/lib/pq" "github.com/lib/pq"
"github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/dendrite/internal/sqlutil"
"github.com/matrix-org/dendrite/userapi/storage/tables" "github.com/matrix-org/dendrite/userapi/storage/tables"
"github.com/matrix-org/dendrite/userapi/types"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -48,6 +48,12 @@ const countUsersLastSeenAfterSQL = "" +
" GROUP BY localpart" + " GROUP BY localpart" +
" ) u" " ) u"
/*
R30Users counts the number of 30 day retained users, defined as:
- Users who have created their accounts more than 30 days ago
- Where last seen at most 30 days ago
- Where account creation and last_seen are > 30 days apart
*/
const countR30UsersSQL = ` const countR30UsersSQL = `
SELECT platform, COUNT(*) FROM ( SELECT platform, COUNT(*) FROM (
SELECT users.localpart, platform, users.created_ts, MAX(uip.last_seen_ts) SELECT users.localpart, platform, users.created_ts, MAX(uip.last_seen_ts)
@ -75,6 +81,11 @@ SELECT platform, COUNT(*) FROM (
) u GROUP BY PLATFORM ) u GROUP BY PLATFORM
` `
/*
R30UsersV2 counts the number of 30 day retained users, defined as users that:
- Appear more than once in the past 60 days
- Have more than 30 days between the most and least recent appearances that occurred in the past 60 days.
*/
const countR30UsersV2SQL = ` const countR30UsersV2SQL = `
SELECT SELECT
client_type, client_type,
@ -140,6 +151,8 @@ ON CONFLICT (localpart, device_id, timestamp) DO NOTHING
; ;
` `
const queryDBEngineVersion = "SHOW server_version;"
type statsStatements struct { type statsStatements struct {
serverName gomatrixserverlib.ServerName serverName gomatrixserverlib.ServerName
lastUpdate time.Time lastUpdate time.Time
@ -149,6 +162,7 @@ type statsStatements struct {
updateUserDailyVisitsStmt *sql.Stmt updateUserDailyVisitsStmt *sql.Stmt
countUserByAccountTypeStmt *sql.Stmt countUserByAccountTypeStmt *sql.Stmt
countRegisteredUserByTypeStmt *sql.Stmt countRegisteredUserByTypeStmt *sql.Stmt
dbEngineVersionStmt *sql.Stmt
} }
func NewPostgresStatsTable(db *sql.DB, serverName gomatrixserverlib.ServerName) (tables.StatsTable, error) { func NewPostgresStatsTable(db *sql.DB, serverName gomatrixserverlib.ServerName) (tables.StatsTable, error) {
@ -169,6 +183,7 @@ func NewPostgresStatsTable(db *sql.DB, serverName gomatrixserverlib.ServerName)
{&s.updateUserDailyVisitsStmt, updateUserDailyVisitsSQL}, {&s.updateUserDailyVisitsStmt, updateUserDailyVisitsSQL},
{&s.countUserByAccountTypeStmt, countUserByAccountTypeSQL}, {&s.countUserByAccountTypeStmt, countUserByAccountTypeSQL},
{&s.countRegisteredUserByTypeStmt, countRegisteredUserByTypeStmt}, {&s.countRegisteredUserByTypeStmt, countRegisteredUserByTypeStmt},
{&s.dbEngineVersionStmt, queryDBEngineVersion},
}.Prepare(db) }.Prepare(db)
} }
@ -184,7 +199,7 @@ func (s *statsStatements) startTimers() {
time.AfterFunc(time.Minute*5, updateStatsFunc) time.AfterFunc(time.Minute*5, updateStatsFunc)
} }
func (s *statsStatements) AllUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) { func (s *statsStatements) allUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) {
stmt := sqlutil.TxStmt(txn, s.countUserByAccountTypeStmt) stmt := sqlutil.TxStmt(txn, s.countUserByAccountTypeStmt)
err = stmt.QueryRowContext(ctx, err = stmt.QueryRowContext(ctx,
pq.Int64Array{1, 2, 3, 4}, pq.Int64Array{1, 2, 3, 4},
@ -192,7 +207,7 @@ func (s *statsStatements) AllUsers(ctx context.Context, txn *sql.Tx) (result int
return return
} }
func (s *statsStatements) NonBridgedUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) { func (s *statsStatements) nonBridgedUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) {
stmt := sqlutil.TxStmt(txn, s.countUserByAccountTypeStmt) stmt := sqlutil.TxStmt(txn, s.countUserByAccountTypeStmt)
err = stmt.QueryRowContext(ctx, err = stmt.QueryRowContext(ctx,
pq.Int64Array{1, 2, 3}, pq.Int64Array{1, 2, 3},
@ -200,7 +215,7 @@ func (s *statsStatements) NonBridgedUsers(ctx context.Context, txn *sql.Tx) (res
return return
} }
func (s *statsStatements) RegisteredUserByType(ctx context.Context, txn *sql.Tx) (map[string]int64, error) { func (s *statsStatements) registeredUserByType(ctx context.Context, txn *sql.Tx) (map[string]int64, error) {
stmt := sqlutil.TxStmt(txn, s.countRegisteredUserByTypeStmt) stmt := sqlutil.TxStmt(txn, s.countRegisteredUserByTypeStmt)
registeredAfter := time.Now().AddDate(0, 0, -1) registeredAfter := time.Now().AddDate(0, 0, -1)
@ -225,7 +240,7 @@ func (s *statsStatements) RegisteredUserByType(ctx context.Context, txn *sql.Tx)
return result, rows.Err() return result, rows.Err()
} }
func (s *statsStatements) DailyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) { func (s *statsStatements) dailyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) {
stmt := sqlutil.TxStmt(txn, s.countUsersLastSeenAfterStmt) stmt := sqlutil.TxStmt(txn, s.countUsersLastSeenAfterStmt)
lastSeenAfter := time.Now().AddDate(0, 0, -1) lastSeenAfter := time.Now().AddDate(0, 0, -1)
err = stmt.QueryRowContext(ctx, err = stmt.QueryRowContext(ctx,
@ -234,7 +249,7 @@ func (s *statsStatements) DailyUsers(ctx context.Context, txn *sql.Tx) (result i
return return
} }
func (s *statsStatements) MonthlyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) { func (s *statsStatements) monthlyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) {
stmt := sqlutil.TxStmt(txn, s.countUsersLastSeenAfterStmt) stmt := sqlutil.TxStmt(txn, s.countUsersLastSeenAfterStmt)
lastSeenAfter := time.Now().AddDate(0, 0, -30) lastSeenAfter := time.Now().AddDate(0, 0, -30)
err = stmt.QueryRowContext(ctx, err = stmt.QueryRowContext(ctx,
@ -243,12 +258,13 @@ func (s *statsStatements) MonthlyUsers(ctx context.Context, txn *sql.Tx) (result
return return
} }
/* R30Users counts the number of 30 day retained users, defined as: /*
R30Users counts the number of 30 day retained users, defined as:
- Users who have created their accounts more than 30 days ago - Users who have created their accounts more than 30 days ago
- Where last seen at most 30 days ago - Where last seen at most 30 days ago
- Where account creation and last_seen are > 30 days apart - Where account creation and last_seen are > 30 days apart
*/ */
func (s *statsStatements) R30Users(ctx context.Context, txn *sql.Tx) (map[string]int64, error) { func (s *statsStatements) r30Users(ctx context.Context, txn *sql.Tx) (map[string]int64, error) {
stmt := sqlutil.TxStmt(txn, s.countR30UsersStmt) stmt := sqlutil.TxStmt(txn, s.countR30UsersStmt)
lastSeenAfter := time.Now().AddDate(0, 0, -30) lastSeenAfter := time.Now().AddDate(0, 0, -30)
diff := time.Hour * 24 * 30 diff := time.Hour * 24 * 30
@ -279,11 +295,12 @@ func (s *statsStatements) R30Users(ctx context.Context, txn *sql.Tx) (map[string
return result, rows.Err() return result, rows.Err()
} }
/* R30UsersV2 counts the number of 30 day retained users, defined as users that: /*
R30UsersV2 counts the number of 30 day retained users, defined as users that:
- Appear more than once in the past 60 days - Appear more than once in the past 60 days
- Have more than 30 days between the most and least recent appearances that occurred in the past 60 days. - Have more than 30 days between the most and least recent appearances that occurred in the past 60 days.
*/ */
func (s *statsStatements) R30UsersV2(ctx context.Context, txn *sql.Tx) (map[string]int64, error) { func (s *statsStatements) r30UsersV2(ctx context.Context, txn *sql.Tx) (map[string]int64, error) {
stmt := sqlutil.TxStmt(txn, s.countR30UsersV2Stmt) stmt := sqlutil.TxStmt(txn, s.countR30UsersV2Stmt)
sixtyDaysAgo := time.Now().AddDate(0, 0, -60) sixtyDaysAgo := time.Now().AddDate(0, 0, -60)
thirtyDaysAgo := time.Now().AddDate(0, 0, -30) thirtyDaysAgo := time.Now().AddDate(0, 0, -30)
@ -322,6 +339,59 @@ func (s *statsStatements) R30UsersV2(ctx context.Context, txn *sql.Tx) (map[stri
return result, rows.Err() return result, rows.Err()
} }
// UserStatistics collects some information about users on this instance.
// Returns the stats itself as well as the database engine version and type.
// On error, returns the stats collected up to the error.
func (s *statsStatements) UserStatistics(ctx context.Context, txn *sql.Tx) (*types.UserStatistics, *types.DatabaseEngine, error) {
var (
stats = &types.UserStatistics{
R30UsersV2: map[string]int64{
"ios": 0,
"android": 0,
"web": 0,
"electron": 0,
"all": 0,
},
R30Users: map[string]int64{},
RegisteredUsersByType: map[string]int64{},
}
dbEngine = &types.DatabaseEngine{Engine: "Postgres", Version: "unknown"}
err error
)
stats.AllUsers, err = s.allUsers(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.DailyUsers, err = s.dailyUsers(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.MonthlyUsers, err = s.monthlyUsers(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.R30Users, err = s.r30Users(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.R30UsersV2, err = s.r30UsersV2(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.NonBridgedUsers, err = s.nonBridgedUsers(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.RegisteredUsersByType, err = s.registeredUserByType(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stmt := sqlutil.TxStmt(txn, s.dbEngineVersionStmt)
err = stmt.QueryRowContext(ctx).Scan(&dbEngine.Version)
return stats, dbEngine, err
}
func (s *statsStatements) updateUserDailyVisits(ctx context.Context, txn *sql.Tx) error { func (s *statsStatements) updateUserDailyVisits(ctx context.Context, txn *sql.Tx) error {
stmt := sqlutil.TxStmt(txn, s.updateUserDailyVisitsStmt) stmt := sqlutil.TxStmt(txn, s.updateUserDailyVisitsStmt)
_ = stmt _ = stmt

View file

@ -26,6 +26,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/matrix-org/dendrite/userapi/types"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -773,24 +774,7 @@ func (d *Database) RemovePushers(
}) })
} }
func (d *Database) AllUsers(ctx context.Context) (result int64, err error) { // UserStatistics populates types.UserStatistics, used in reports.
return d.Stats.AllUsers(ctx, nil) func (d *Database) UserStatistics(ctx context.Context) (*types.UserStatistics, *types.DatabaseEngine, error) {
} return d.Stats.UserStatistics(ctx, nil)
func (d *Database) NonBridgedUsers(ctx context.Context) (result int64, err error) {
return d.Stats.NonBridgedUsers(ctx, nil)
}
func (d *Database) RegisteredUserByType(ctx context.Context) (map[string]int64, error) {
return d.Stats.RegisteredUserByType(ctx, nil)
}
func (d *Database) DailyUsers(ctx context.Context) (result int64, err error) {
return d.Stats.DailyUsers(ctx, nil)
}
func (d *Database) MonthlyUsers(ctx context.Context) (result int64, err error) {
return d.Stats.MonthlyUsers(ctx, nil)
}
func (d *Database) R30Users(ctx context.Context) (map[string]int64, error) {
return d.Stats.R30Users(ctx, nil)
}
func (d *Database) R30UsersV2(ctx context.Context) (map[string]int64, error) {
return d.Stats.R30UsersV2(ctx, nil)
} }

View file

@ -23,6 +23,7 @@ import (
"github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/dendrite/internal/sqlutil"
"github.com/matrix-org/dendrite/userapi/storage/tables" "github.com/matrix-org/dendrite/userapi/storage/tables"
"github.com/matrix-org/dendrite/userapi/types"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -47,6 +48,12 @@ const countUsersLastSeenAfterSQL = "" +
" GROUP BY localpart" + " GROUP BY localpart" +
" ) u" " ) u"
/*
R30Users counts the number of 30 day retained users, defined as:
- Users who have created their accounts more than 30 days ago
- Where last seen at most 30 days ago
- Where account creation and last_seen are > 30 days apart
*/
const countR30UsersSQL = ` const countR30UsersSQL = `
SELECT platform, COUNT(*) FROM ( SELECT platform, COUNT(*) FROM (
SELECT users.localpart, platform, users.created_ts, MAX(uip.last_seen_ts) SELECT users.localpart, platform, users.created_ts, MAX(uip.last_seen_ts)
@ -74,6 +81,11 @@ SELECT platform, COUNT(*) FROM (
) u GROUP BY PLATFORM ) u GROUP BY PLATFORM
` `
/*
R30UsersV2 counts the number of 30 day retained users, defined as users that:
- Appear more than once in the past 60 days
- Have more than 30 days between the most and least recent appearances that occurred in the past 60 days.
*/
const countR30UsersV2SQL = ` const countR30UsersV2SQL = `
SELECT SELECT
client_type, client_type,
@ -139,6 +151,8 @@ ON CONFLICT (localpart, device_id, timestamp) DO NOTHING
; ;
` `
const queryDBEngineVersion = "select sqlite_version();"
type statsStatements struct { type statsStatements struct {
serverName gomatrixserverlib.ServerName serverName gomatrixserverlib.ServerName
db *sql.DB db *sql.DB
@ -149,6 +163,7 @@ type statsStatements struct {
updateUserDailyVisitsStmt *sql.Stmt updateUserDailyVisitsStmt *sql.Stmt
countUserByAccountTypeStmt *sql.Stmt countUserByAccountTypeStmt *sql.Stmt
countRegisteredUserByTypeStmt *sql.Stmt countRegisteredUserByTypeStmt *sql.Stmt
dbEngineVersionStmt *sql.Stmt
} }
func NewSQLiteStatsTable(db *sql.DB, serverName gomatrixserverlib.ServerName) (tables.StatsTable, error) { func NewSQLiteStatsTable(db *sql.DB, serverName gomatrixserverlib.ServerName) (tables.StatsTable, error) {
@ -170,6 +185,7 @@ func NewSQLiteStatsTable(db *sql.DB, serverName gomatrixserverlib.ServerName) (t
{&s.updateUserDailyVisitsStmt, updateUserDailyVisitsSQL}, {&s.updateUserDailyVisitsStmt, updateUserDailyVisitsSQL},
{&s.countUserByAccountTypeStmt, countUserByAccountTypeSQL}, {&s.countUserByAccountTypeStmt, countUserByAccountTypeSQL},
{&s.countRegisteredUserByTypeStmt, countRegisteredUserByTypeStmt}, {&s.countRegisteredUserByTypeStmt, countRegisteredUserByTypeStmt},
{&s.dbEngineVersionStmt, queryDBEngineVersion},
}.Prepare(db) }.Prepare(db)
} }
@ -185,7 +201,7 @@ func (s *statsStatements) startTimers() {
time.AfterFunc(time.Minute*5, updateStatsFunc) time.AfterFunc(time.Minute*5, updateStatsFunc)
} }
func (s *statsStatements) AllUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) { func (s *statsStatements) allUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) {
query := strings.Replace(countUserByAccountTypeSQL, "($1)", sqlutil.QueryVariadic(4), 1) query := strings.Replace(countUserByAccountTypeSQL, "($1)", sqlutil.QueryVariadic(4), 1)
queryStmt, err := s.db.Prepare(query) queryStmt, err := s.db.Prepare(query)
if err != nil { if err != nil {
@ -198,7 +214,7 @@ func (s *statsStatements) AllUsers(ctx context.Context, txn *sql.Tx) (result int
return return
} }
func (s *statsStatements) NonBridgedUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) { func (s *statsStatements) nonBridgedUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) {
query := strings.Replace(countUserByAccountTypeSQL, "($1)", sqlutil.QueryVariadic(3), 1) query := strings.Replace(countUserByAccountTypeSQL, "($1)", sqlutil.QueryVariadic(3), 1)
queryStmt, err := s.db.Prepare(query) queryStmt, err := s.db.Prepare(query)
if err != nil { if err != nil {
@ -211,7 +227,7 @@ func (s *statsStatements) NonBridgedUsers(ctx context.Context, txn *sql.Tx) (res
return return
} }
func (s *statsStatements) RegisteredUserByType(ctx context.Context, txn *sql.Tx) (map[string]int64, error) { func (s *statsStatements) registeredUserByType(ctx context.Context, txn *sql.Tx) (map[string]int64, error) {
stmt := sqlutil.TxStmt(txn, s.countRegisteredUserByTypeStmt) stmt := sqlutil.TxStmt(txn, s.countRegisteredUserByTypeStmt)
registeredAfter := time.Now().AddDate(0, 0, -1) registeredAfter := time.Now().AddDate(0, 0, -1)
@ -236,7 +252,7 @@ func (s *statsStatements) RegisteredUserByType(ctx context.Context, txn *sql.Tx)
return result, rows.Err() return result, rows.Err()
} }
func (s *statsStatements) DailyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) { func (s *statsStatements) dailyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) {
stmt := sqlutil.TxStmt(txn, s.countUsersLastSeenAfterStmt) stmt := sqlutil.TxStmt(txn, s.countUsersLastSeenAfterStmt)
lastSeenAfter := time.Now().AddDate(0, 0, -1) lastSeenAfter := time.Now().AddDate(0, 0, -1)
err = stmt.QueryRowContext(ctx, err = stmt.QueryRowContext(ctx,
@ -245,7 +261,7 @@ func (s *statsStatements) DailyUsers(ctx context.Context, txn *sql.Tx) (result i
return return
} }
func (s *statsStatements) MonthlyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) { func (s *statsStatements) monthlyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) {
stmt := sqlutil.TxStmt(txn, s.countUsersLastSeenAfterStmt) stmt := sqlutil.TxStmt(txn, s.countUsersLastSeenAfterStmt)
lastSeenAfter := time.Now().AddDate(0, 0, -30) lastSeenAfter := time.Now().AddDate(0, 0, -30)
err = stmt.QueryRowContext(ctx, err = stmt.QueryRowContext(ctx,
@ -259,7 +275,7 @@ func (s *statsStatements) MonthlyUsers(ctx context.Context, txn *sql.Tx) (result
- Where last seen at most 30 days ago - Where last seen at most 30 days ago
- Where account creation and last_seen are > 30 days apart - Where account creation and last_seen are > 30 days apart
*/ */
func (s *statsStatements) R30Users(ctx context.Context, txn *sql.Tx) (map[string]int64, error) { func (s *statsStatements) r30Users(ctx context.Context, txn *sql.Tx) (map[string]int64, error) {
stmt := sqlutil.TxStmt(txn, s.countR30UsersStmt) stmt := sqlutil.TxStmt(txn, s.countR30UsersStmt)
lastSeenAfter := time.Now().AddDate(0, 0, -30) lastSeenAfter := time.Now().AddDate(0, 0, -30)
diff := time.Hour * 24 * 30 diff := time.Hour * 24 * 30
@ -295,7 +311,7 @@ func (s *statsStatements) R30Users(ctx context.Context, txn *sql.Tx) (map[string
- Appear more than once in the past 60 days - Appear more than once in the past 60 days
- Have more than 30 days between the most and least recent appearances that occurred in the past 60 days. - Have more than 30 days between the most and least recent appearances that occurred in the past 60 days.
*/ */
func (s *statsStatements) R30UsersV2(ctx context.Context, txn *sql.Tx) (map[string]int64, error) { func (s *statsStatements) r30UsersV2(ctx context.Context, txn *sql.Tx) (map[string]int64, error) {
stmt := sqlutil.TxStmt(txn, s.countR30UsersV2Stmt) stmt := sqlutil.TxStmt(txn, s.countR30UsersV2Stmt)
sixtyDaysAgo := time.Now().AddDate(0, 0, -60) sixtyDaysAgo := time.Now().AddDate(0, 0, -60)
thirtyDaysAgo := time.Now().AddDate(0, 0, -30) thirtyDaysAgo := time.Now().AddDate(0, 0, -30)
@ -334,6 +350,59 @@ func (s *statsStatements) R30UsersV2(ctx context.Context, txn *sql.Tx) (map[stri
return result, rows.Err() return result, rows.Err()
} }
// UserStatistics collects some information about users on this instance.
// Returns the stats itself as well as the database engine version and type.
// On error, returns the stats collected up to the error.
func (s *statsStatements) UserStatistics(ctx context.Context, txn *sql.Tx) (*types.UserStatistics, *types.DatabaseEngine, error) {
var (
stats = &types.UserStatistics{
R30UsersV2: map[string]int64{
"ios": 0,
"android": 0,
"web": 0,
"electron": 0,
"all": 0,
},
R30Users: map[string]int64{},
RegisteredUsersByType: map[string]int64{},
}
dbEngine = &types.DatabaseEngine{Engine: "SQLite", Version: "unknown"}
err error
)
stats.AllUsers, err = s.allUsers(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.DailyUsers, err = s.dailyUsers(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.MonthlyUsers, err = s.monthlyUsers(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.R30Users, err = s.r30Users(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.R30UsersV2, err = s.r30UsersV2(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.NonBridgedUsers, err = s.nonBridgedUsers(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stats.RegisteredUsersByType, err = s.registeredUserByType(ctx, txn)
if err != nil {
return stats, dbEngine, err
}
stmt := sqlutil.TxStmt(txn, s.dbEngineVersionStmt)
err = stmt.QueryRowContext(ctx).Scan(&dbEngine.Version)
return stats, dbEngine, err
}
func (s *statsStatements) updateUserDailyVisits(ctx context.Context, txn *sql.Tx) error { func (s *statsStatements) updateUserDailyVisits(ctx context.Context, txn *sql.Tx) error {
stmt := sqlutil.TxStmt(txn, s.updateUserDailyVisitsStmt) stmt := sqlutil.TxStmt(txn, s.updateUserDailyVisitsStmt)
_ = stmt _ = stmt

View file

@ -21,6 +21,7 @@ import (
"github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/dendrite/userapi/types"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
) )
@ -113,13 +114,7 @@ type NotificationTable interface {
} }
type StatsTable interface { type StatsTable interface {
AllUsers(ctx context.Context, txn *sql.Tx) (result int64, err error) UserStatistics(ctx context.Context, txn *sql.Tx) (*types.UserStatistics, *types.DatabaseEngine, error)
NonBridgedUsers(ctx context.Context, txn *sql.Tx) (result int64, err error)
RegisteredUserByType(ctx context.Context, txn *sql.Tx) (map[string]int64, error)
DailyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error)
MonthlyUsers(ctx context.Context, txn *sql.Tx) (result int64, err error)
R30Users(ctx context.Context, txn *sql.Tx) (map[string]int64, error)
R30UsersV2(ctx context.Context, txn *sql.Tx) (map[string]int64, error)
} }
type NotificationFilter uint32 type NotificationFilter uint32

View file

@ -0,0 +1,30 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
type UserStatistics struct {
RegisteredUsersByType map[string]int64
R30Users map[string]int64
R30UsersV2 map[string]int64
AllUsers int64
NonBridgedUsers int64
DailyUsers int64
MonthlyUsers int64
}
type DatabaseEngine struct {
Engine string
Version string
}

View file

@ -25,7 +25,6 @@ import (
"time" "time"
"github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/internal/sqlutil"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/userapi/storage" "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
@ -38,7 +37,7 @@ type phoneHomeStats struct {
serverName gomatrixserverlib.ServerName serverName gomatrixserverlib.ServerName
startTime time.Time startTime time.Time
cfg *config.Dendrite cfg *config.Dendrite
db storage.Database db storage.Statistics
isMonolith bool isMonolith bool
client *http.Client client *http.Client
} }
@ -48,13 +47,13 @@ type timestampToRUUsage struct {
usage syscall.Rusage usage syscall.Rusage
} }
func StartPhoneHomeCollector(startTime time.Time, cfg *config.Dendrite, userDB storage.Database) { func StartPhoneHomeCollector(startTime time.Time, cfg *config.Dendrite, statsDB storage.Statistics) {
p := phoneHomeStats{ p := phoneHomeStats{
startTime: startTime, startTime: startTime,
serverName: cfg.Global.ServerName, serverName: cfg.Global.ServerName,
cfg: cfg, cfg: cfg,
db: userDB, db: statsDB,
isMonolith: cfg.IsMonolith, isMonolith: cfg.IsMonolith,
client: &http.Client{ client: &http.Client{
Timeout: time.Second * 30, Timeout: time.Second * 30,
@ -91,8 +90,7 @@ func (p *phoneHomeStats) collect() {
// cpu and memory usage information // cpu and memory usage information
err := getMemoryStats(p) err := getMemoryStats(p)
if err != nil { if err != nil {
logrus.WithError(err).Error("unable to get memory/cpu stats") logrus.WithError(err).Error("unable to get memory/cpu stats, using defaults")
return
} }
// configuration information // configuration information
@ -109,35 +107,6 @@ func (p *phoneHomeStats) collect() {
p.stats["log_level"] = "info" p.stats["log_level"] = "info"
} }
// database configuration
db, err := sqlutil.Open(&p.cfg.UserAPI.AccountDatabase)
if err != nil {
logrus.WithError(err).Error("unable to connect to database")
return
}
defer internal.CloseAndLogIfError(context.Background(), db, "phoneHomeStats.collect(): failed to close database connection")
dbVersion := "unknown"
dbEngine := "unknown"
switch {
case p.cfg.UserAPI.AccountDatabase.ConnectionString.IsSQLite():
dbEngine = "SQLite"
row := db.QueryRow("select sqlite_version();")
if err = row.Scan(&dbVersion); err != nil {
logrus.WithError(err).Error("unable to query version")
return
}
case p.cfg.UserAPI.AccountDatabase.ConnectionString.IsPostgres():
dbEngine = "Postgres"
row := db.QueryRow("SHOW server_version;")
if err = row.Scan(&dbVersion); err != nil {
logrus.WithError(err).Error("unable to query version")
return
}
}
p.stats["database_engine"] = dbEngine
p.stats["database_server_version"] = dbVersion
// message and room stats // message and room stats
// TODO: Find a solution to actually set these values // TODO: Find a solution to actually set these values
p.stats["total_room_count"] = 0 p.stats["total_room_count"] = 0
@ -146,57 +115,24 @@ func (p *phoneHomeStats) collect() {
p.stats["daily_e2ee_messages"] = 0 p.stats["daily_e2ee_messages"] = 0
p.stats["daily_sent_e2ee_messages"] = 0 p.stats["daily_sent_e2ee_messages"] = 0
count, err := p.db.AllUsers(ctx) // user stats and DB engine
userStats, db, err := p.db.UserStatistics(ctx)
if err != nil { if err != nil {
logrus.WithError(err).Error("unable to query AllUsers") logrus.WithError(err).Error("unable to query userstats, using default values")
return
} }
p.stats["total_users"] = count p.stats["database_engine"] = db.Engine
p.stats["database_server_version"] = db.Version
count, err = p.db.NonBridgedUsers(ctx) p.stats["total_users"] = userStats.AllUsers
if err != nil { p.stats["total_nonbridged_users"] = userStats.NonBridgedUsers
logrus.WithError(err).Error("unable to query NonBridgedUsers") p.stats["daily_active_users"] = userStats.DailyUsers
return p.stats["monthly_active_users"] = userStats.MonthlyUsers
} for t, c := range userStats.RegisteredUsersByType {
p.stats["total_nonbridged_users"] = count
count, err = p.db.DailyUsers(ctx)
if err != nil {
logrus.WithError(err).Error("unable to query DailyUsers")
return
}
p.stats["daily_active_users"] = count
count, err = p.db.MonthlyUsers(ctx)
if err != nil {
logrus.WithError(err).Error("unable to query MonthlyUsers")
return
}
p.stats["monthly_active_users"] = count
res, err := p.db.RegisteredUserByType(ctx)
if err != nil {
logrus.WithError(err).Error("unable to query RegisteredUserByType")
return
}
for t, c := range res {
p.stats["daily_user_type_"+t] = c p.stats["daily_user_type_"+t] = c
} }
for t, c := range userStats.R30Users {
res, err = p.db.R30Users(ctx)
if err != nil {
logrus.WithError(err).Error("unable to query R30Users")
return
}
for t, c := range res {
p.stats["r30_users_"+t] = c p.stats["r30_users_"+t] = c
} }
res, err = p.db.R30UsersV2(ctx) for t, c := range userStats.R30UsersV2 {
if err != nil {
logrus.WithError(err).Error("unable to query R30UsersV2")
return
}
for t, c := range res {
p.stats["r30v2_users_"+t] = c p.stats["r30v2_users_"+t] = c
} }