From 5693b3e58fb11f32874460da1fd08b2b9da5a986 Mon Sep 17 00:00:00 2001 From: Las Zenow Date: Sat, 1 Jun 2013 03:40:06 +0200 Subject: [PATCH] Create hourly and monthly statistics --- config.go | 36 +++++++++++++---------- database.go | 8 +++++ mapreduce.go | 70 +++++++++++++++++++++++++++++++++++++++----- stats.go | 64 +++++++++++++++++++++++++++++----------- templates/stats.html | 40 ++++++++++++++++--------- 5 files changed, 163 insertions(+), 55 deletions(-) diff --git a/config.go b/config.go index 465e659..6a0b921 100644 --- a/config.go +++ b/config.go @@ -3,23 +3,27 @@ package main const ( PORT = "8080" - DB_IP = "127.0.0.1" - DB_NAME = "trantor" - META_COLL = "meta" - BOOKS_COLL = "books" - TAGS_COLL = "tags" - DAILY_VISITS_COLL = "visits.daily" - USERS_COLL = "users" - STATS_COLL = "statistics" - FS_BOOKS = "fs_books" - FS_IMGS = "fs_imgs" + DB_IP = "127.0.0.1" + DB_NAME = "trantor" + META_COLL = "meta" + BOOKS_COLL = "books" + TAGS_COLL = "tags" + HOURLY_VISITS_COLL = "visits.hourly" + DAILY_VISITS_COLL = "visits.daily" + MONTHLY_VISITS_COLL = "visits.monthly" + USERS_COLL = "users" + STATS_COLL = "statistics" + FS_BOOKS = "fs_books" + FS_IMGS = "fs_imgs" - PASS_SALT = "ImperialLibSalt" - MINUTES_UPDATE_TAGS = 10 - MINUTES_UPDATE_DAILY = 60 * 12 - TAGS_DISPLAY = 50 - SEARCH_ITEMS_PAGE = 20 - NEW_ITEMS_PAGE = 50 + PASS_SALT = "ImperialLibSalt" + MINUTES_UPDATE_TAGS = 10 + MINUTES_UPDATE_HOURLY = 30 + MINUTES_UPDATE_DAILY = 60 * 12 + MINUTES_UPDATE_MONTHLY = 60 * 24 + TAGS_DISPLAY = 50 + SEARCH_ITEMS_PAGE = 20 + NEW_ITEMS_PAGE = 50 TEMPLATE_PATH = "templates/" CSS_PATH = "css/" diff --git a/database.go b/database.go index 9a8fa98..9789a32 100644 --- a/database.go +++ b/database.go @@ -191,6 +191,14 @@ type Visits struct { Count int "value" } +func (d *DB) GetHourVisits(start time.Time) ([]Visits, error) { + return d.mr.GetHourVisits(start, d.stats) +} + func (d *DB) GetDayVisits(start time.Time) ([]Visits, error) { return d.mr.GetDayVisits(start, d.stats) } + +func (d *DB) GetMonthVisits(start time.Time) ([]Visits, error) { + return d.mr.GetMonthVisits(start, d.stats) +} diff --git a/mapreduce.go b/mapreduce.go index d46c8c3..9462806 100644 --- a/mapreduce.go +++ b/mapreduce.go @@ -7,16 +7,20 @@ import ( ) type MR struct { - meta *mgo.Collection - tags *mgo.Collection - daily *mgo.Collection + meta *mgo.Collection + tags *mgo.Collection + hourly *mgo.Collection + daily *mgo.Collection + monthly *mgo.Collection } func NewMR(database *mgo.Database) *MR { m := new(MR) m.meta = database.C(META_COLL) m.tags = database.C(TAGS_COLL) + m.hourly = database.C(HOURLY_VISITS_COLL) m.daily = database.C(DAILY_VISITS_COLL) + m.monthly = database.C(MONTHLY_VISITS_COLL) return m } @@ -54,15 +58,41 @@ func (m *MR) GetTags(numTags int, booksColl *mgo.Collection) ([]string, error) { return tags, nil } +func (m *MR) GetHourVisits(start time.Time, statsColl *mgo.Collection) ([]Visits, error) { + if m.isOutdated(HOURLY_VISITS_COLL, MINUTES_UPDATE_HOURLY) { + var mr mgo.MapReduce + mr.Map = `function() { + var day = Date.UTC(this.date.getUTCFullYear(), + this.date.getUTCMonth(), + this.date.getUTCDate(), + this.date.getUTCHours()); + emit(day, 1); + }` + mr.Reduce = `function(date, vals) { + var count = 0; + vals.forEach(function(v) { count += v; }); + return count; + }` + err := m.update(&mr, bson.M{"date": bson.M{"$gte": start}}, statsColl, HOURLY_VISITS_COLL) + if err != nil { + return nil, err + } + } + + var result []Visits + err := m.hourly.Find(nil).All(&result) + return result, err +} + func (m *MR) GetDayVisits(start time.Time, statsColl *mgo.Collection) ([]Visits, error) { if m.isOutdated(DAILY_VISITS_COLL, MINUTES_UPDATE_DAILY) { var mr mgo.MapReduce mr.Map = `function() { - var day = Date.UTC(this.date.getFullYear(), - this.date.getMonth(), - this.date.getDate()); - emit(day, 1); - }` + var day = Date.UTC(this.date.getUTCFullYear(), + this.date.getUTCMonth(), + this.date.getUTCDate()); + emit(day, 1); + }` mr.Reduce = `function(date, vals) { var count = 0; vals.forEach(function(v) { count += v; }); @@ -79,6 +109,30 @@ func (m *MR) GetDayVisits(start time.Time, statsColl *mgo.Collection) ([]Visits, return result, err } +func (m *MR) GetMonthVisits(start time.Time, statsColl *mgo.Collection) ([]Visits, error) { + if m.isOutdated(MONTHLY_VISITS_COLL, MINUTES_UPDATE_MONTHLY) { + var mr mgo.MapReduce + mr.Map = `function() { + var day = Date.UTC(this.date.getUTCFullYear(), + this.date.getUTCMonth()); + emit(day, 1); + }` + mr.Reduce = `function(date, vals) { + var count = 0; + vals.forEach(function(v) { count += v; }); + return count; + }` + err := m.update(&mr, bson.M{"date": bson.M{"$gte": start}}, statsColl, MONTHLY_VISITS_COLL) + if err != nil { + return nil, err + } + } + + var result []Visits + err := m.monthly.Find(nil).All(&result) + return result, err +} + func (m *MR) update(mr *mgo.MapReduce, query bson.M, queryColl *mgo.Collection, storeColl string) error { _, err := m.meta.RemoveAll(bson.M{"type": storeColl}) if err != nil { diff --git a/stats.go b/stats.go index b9e8d23..c167888 100644 --- a/stats.go +++ b/stats.go @@ -47,14 +47,18 @@ func statsWorker() { func statsHandler(w http.ResponseWriter, r *http.Request, sess *Session) { var data statsData data.S = GetStatus(w, r) + data.Hourly = getHourlyVisits() data.Daily = getDailyVisits() + data.Monthly = getMonthlyVisits() loadTemplate(w, "stats", data) } type statsData struct { - S Status - Daily []visitData + S Status + Hourly []visitData + Daily []visitData + Monthly []visitData } type visitData struct { @@ -62,26 +66,52 @@ type visitData struct { Count int } +func getHourlyVisits() []visitData { + const numDays = 2.5 + var visits []visitData + + start := time.Now().UTC().Add(-numDays * 24 * time.Hour) + visit, _ := db.GetHourVisits(start) + for _, v := range visit { + var elem visitData + hour := time.Unix(v.Date/1000, 0).UTC().Hour() + elem.Label = strconv.Itoa(hour + 1) + elem.Count = v.Count + visits = append(visits, elem) + } + + return visits +} + func getDailyVisits() []visitData { const numDays = 30 - visits := make([]visitData, numDays) + var visits []visitData - start := time.Now().Add(-numDays * 24 * time.Hour).Truncate(24 * time.Hour) + start := time.Now().UTC().Add(-numDays * 24 * time.Hour).Truncate(24 * time.Hour) visit, _ := db.GetDayVisits(start) - prevDay := start.Day() - i := 0 for _, v := range visit { - day := time.Unix(v.Date/1000, 0).Day() - for prevDay+1 < day { - prevDay++ - visits[i].Label = strconv.Itoa(prevDay) - visits[i].Count = 0 - i++ - } - visits[i].Label = strconv.Itoa(day) - visits[i].Count = v.Count - prevDay = day - i++ + var elem visitData + day := time.Unix(v.Date/1000, 0).UTC().Day() + elem.Label = strconv.Itoa(day) + elem.Count = v.Count + visits = append(visits, elem) + } + + return visits +} + +func getMonthlyVisits() []visitData { + const numDays = 365 + var visits []visitData + + start := time.Now().UTC().Add(-numDays * 24 * time.Hour).Truncate(24 * time.Hour) + visit, _ := db.GetMonthVisits(start) + for _, v := range visit { + var elem visitData + month := time.Unix(v.Date/1000, 0).UTC().Month() + elem.Label = month.String() + elem.Count = v.Count + visits = append(visits, elem) } return visits diff --git a/templates/stats.html b/templates/stats.html index dd81785..d6f8b0d 100644 --- a/templates/stats.html +++ b/templates/stats.html @@ -3,27 +3,39 @@
+

Hourly visits:

+

Daily visits:

+

Monthly visits:

+
{{template "footer.html"}}