mirror of
https://github.com/ddworken/hishtory.git
synced 2025-06-20 20:07:52 +02:00
Add backend table to track daily/weekly active user stats
This commit is contained in:
parent
fdec51bd14
commit
96e8e4f620
@ -5,6 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ddworken/hishtory/shared"
|
"github.com/ddworken/hishtory/shared"
|
||||||
"github.com/jackc/pgx/v4/stdlib"
|
"github.com/jackc/pgx/v4/stdlib"
|
||||||
@ -51,6 +52,7 @@ func (db *DB) AddDatabaseTables() error {
|
|||||||
&shared.DumpRequest{},
|
&shared.DumpRequest{},
|
||||||
&shared.DeletionRequest{},
|
&shared.DeletionRequest{},
|
||||||
&shared.Feedback{},
|
&shared.Feedback{},
|
||||||
|
&ActiveUserStats{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, model := range models {
|
for _, model := range models {
|
||||||
@ -264,6 +266,74 @@ func (db *DB) Clean(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractInt64FromRow(row *sql.Row) (int64, error) {
|
||||||
|
var ret int64
|
||||||
|
err := row.Scan(&ret)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("extractInt64FromRow: %w", err)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActiveUserStats struct {
|
||||||
|
Date time.Time
|
||||||
|
TotalNumDevices int64
|
||||||
|
TotalNumUsers int64
|
||||||
|
DailyActiveSubmitUsers int64
|
||||||
|
DailyActiveQueryUsers int64
|
||||||
|
WeeklyActiveSubmitUsers int64
|
||||||
|
WeeklyActiveQueryUsers int64
|
||||||
|
DailyInstalls int64
|
||||||
|
DailyUninstalls int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) GenerateAndStoreActiveUserStats(ctx context.Context) error {
|
||||||
|
totalNumDevices, err := extractInt64FromRow(db.WithContext(ctx).Raw("SELECT COUNT(DISTINCT devices.device_id) FROM devices").Row())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
totalNumUsers, err := extractInt64FromRow(db.WithContext(ctx).Raw("SELECT COUNT(DISTINCT devices.user_id) FROM devices").Row())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dauSubmit, err := extractInt64FromRow(db.WithContext(ctx).Raw("SELECT COUNT(DISTINCT user_id) FROM usage_data WHERE last_used > (now()::date-1)::timestamp").Row())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dauQuery, err := extractInt64FromRow(db.WithContext(ctx).Raw("SELECT COUNT(DISTINCT user_id) FROM usage_data WHERE last_queried > (now()::date-1)::timestamp").Row())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wauSubmit, err := extractInt64FromRow(db.WithContext(ctx).Raw("SELECT COUNT(DISTINCT user_id) FROM usage_data WHERE last_used > (now()::date-7)::timestamp").Row())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wauQuery, err := extractInt64FromRow(db.WithContext(ctx).Raw("SELECT COUNT(DISTINCT user_id) FROM usage_data WHERE last_queried > (now()::date-7)::timestamp").Row())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dailyInstalls, err := extractInt64FromRow(db.WithContext(ctx).Raw("SELECT count(distinct device_id) FROM devices WHERE registration_date > (now()::date-1)::timestamp").Row())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dailyUninstalls, err := extractInt64FromRow(db.WithContext(ctx).Raw("SELECT COUNT(*) FROM feedbacks WHERE date > (now()::date-1)::timestamp").Row())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Create(ActiveUserStats{
|
||||||
|
Date: time.Now(),
|
||||||
|
TotalNumDevices: totalNumDevices,
|
||||||
|
TotalNumUsers: totalNumUsers,
|
||||||
|
DailyActiveSubmitUsers: dauSubmit,
|
||||||
|
DailyActiveQueryUsers: dauQuery,
|
||||||
|
WeeklyActiveSubmitUsers: wauSubmit,
|
||||||
|
WeeklyActiveQueryUsers: wauQuery,
|
||||||
|
DailyInstalls: dailyInstalls,
|
||||||
|
DailyUninstalls: dailyUninstalls,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) DeepClean(ctx context.Context) error {
|
func (db *DB) DeepClean(ctx context.Context) error {
|
||||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
r := tx.Exec(`
|
r := tx.Exec(`
|
||||||
|
@ -102,19 +102,36 @@ func OpenDB() (*database.DB, error) {
|
|||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var CRON_COUNTER = 0
|
||||||
|
|
||||||
func cron(ctx context.Context, db *database.DB, stats *statsd.Client) error {
|
func cron(ctx context.Context, db *database.DB, stats *statsd.Client) error {
|
||||||
|
// Determine the latest released version of hishtory to serve via the /api/v1/download
|
||||||
|
// endpoint for hishtory updates.
|
||||||
if err := release.UpdateReleaseVersion(); err != nil {
|
if err := release.UpdateReleaseVersion(); err != nil {
|
||||||
return fmt.Errorf("updateReleaseVersion: %w", err)
|
return fmt.Errorf("updateReleaseVersion: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean the DB to remove entries that have already been read
|
||||||
if err := db.Clean(ctx); err != nil {
|
if err := db.Clean(ctx); err != nil {
|
||||||
return fmt.Errorf("db.Clean: %w", err)
|
return fmt.Errorf("db.Clean: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush out datadog statsd
|
||||||
if stats != nil {
|
if stats != nil {
|
||||||
if err := stats.Flush(); err != nil {
|
if err := stats.Flush(); err != nil {
|
||||||
return fmt.Errorf("stats.Flush: %w", err)
|
return fmt.Errorf("stats.Flush: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect and store metrics on active users so we can track trends over time. This doesn't
|
||||||
|
// have to be run as often, so only run it for a fraction of cron jobs
|
||||||
|
if CRON_COUNTER%40 == 0 {
|
||||||
|
if err := db.GenerateAndStoreActiveUserStats(ctx); err != nil {
|
||||||
|
return fmt.Errorf("db.GenerateAndStoreActiveUserStats: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRON_COUNTER += 1
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,10 +140,7 @@ func runBackgroundJobs(ctx context.Context, srv *server.Server, db *database.DB,
|
|||||||
for {
|
for {
|
||||||
err := cron(ctx, db, stats)
|
err := cron(ctx, db, stats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Cron failure: %v", err)
|
panic(fmt.Sprintf("Cron failure: %v", err))
|
||||||
|
|
||||||
// cron no longer panics, panicking here.
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
srv.UpdateReleaseVersion(release.Version, release.BuildUpdateInfo(release.Version))
|
srv.UpdateReleaseVersion(release.Version, release.BuildUpdateInfo(release.Version))
|
||||||
time.Sleep(10 * time.Minute)
|
time.Sleep(10 * time.Minute)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user