Create hourly and monthly statistics
This commit is contained in:
parent
a4ccd41d05
commit
5693b3e58f
5 changed files with 163 additions and 55 deletions
36
config.go
36
config.go
|
@ -3,23 +3,27 @@ package main
|
||||||
const (
|
const (
|
||||||
PORT = "8080"
|
PORT = "8080"
|
||||||
|
|
||||||
DB_IP = "127.0.0.1"
|
DB_IP = "127.0.0.1"
|
||||||
DB_NAME = "trantor"
|
DB_NAME = "trantor"
|
||||||
META_COLL = "meta"
|
META_COLL = "meta"
|
||||||
BOOKS_COLL = "books"
|
BOOKS_COLL = "books"
|
||||||
TAGS_COLL = "tags"
|
TAGS_COLL = "tags"
|
||||||
DAILY_VISITS_COLL = "visits.daily"
|
HOURLY_VISITS_COLL = "visits.hourly"
|
||||||
USERS_COLL = "users"
|
DAILY_VISITS_COLL = "visits.daily"
|
||||||
STATS_COLL = "statistics"
|
MONTHLY_VISITS_COLL = "visits.monthly"
|
||||||
FS_BOOKS = "fs_books"
|
USERS_COLL = "users"
|
||||||
FS_IMGS = "fs_imgs"
|
STATS_COLL = "statistics"
|
||||||
|
FS_BOOKS = "fs_books"
|
||||||
|
FS_IMGS = "fs_imgs"
|
||||||
|
|
||||||
PASS_SALT = "ImperialLibSalt"
|
PASS_SALT = "ImperialLibSalt"
|
||||||
MINUTES_UPDATE_TAGS = 10
|
MINUTES_UPDATE_TAGS = 10
|
||||||
MINUTES_UPDATE_DAILY = 60 * 12
|
MINUTES_UPDATE_HOURLY = 30
|
||||||
TAGS_DISPLAY = 50
|
MINUTES_UPDATE_DAILY = 60 * 12
|
||||||
SEARCH_ITEMS_PAGE = 20
|
MINUTES_UPDATE_MONTHLY = 60 * 24
|
||||||
NEW_ITEMS_PAGE = 50
|
TAGS_DISPLAY = 50
|
||||||
|
SEARCH_ITEMS_PAGE = 20
|
||||||
|
NEW_ITEMS_PAGE = 50
|
||||||
|
|
||||||
TEMPLATE_PATH = "templates/"
|
TEMPLATE_PATH = "templates/"
|
||||||
CSS_PATH = "css/"
|
CSS_PATH = "css/"
|
||||||
|
|
|
@ -191,6 +191,14 @@ type Visits struct {
|
||||||
Count int "value"
|
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) {
|
func (d *DB) GetDayVisits(start time.Time) ([]Visits, error) {
|
||||||
return d.mr.GetDayVisits(start, d.stats)
|
return d.mr.GetDayVisits(start, d.stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DB) GetMonthVisits(start time.Time) ([]Visits, error) {
|
||||||
|
return d.mr.GetMonthVisits(start, d.stats)
|
||||||
|
}
|
||||||
|
|
70
mapreduce.go
70
mapreduce.go
|
@ -7,16 +7,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type MR struct {
|
type MR struct {
|
||||||
meta *mgo.Collection
|
meta *mgo.Collection
|
||||||
tags *mgo.Collection
|
tags *mgo.Collection
|
||||||
daily *mgo.Collection
|
hourly *mgo.Collection
|
||||||
|
daily *mgo.Collection
|
||||||
|
monthly *mgo.Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMR(database *mgo.Database) *MR {
|
func NewMR(database *mgo.Database) *MR {
|
||||||
m := new(MR)
|
m := new(MR)
|
||||||
m.meta = database.C(META_COLL)
|
m.meta = database.C(META_COLL)
|
||||||
m.tags = database.C(TAGS_COLL)
|
m.tags = database.C(TAGS_COLL)
|
||||||
|
m.hourly = database.C(HOURLY_VISITS_COLL)
|
||||||
m.daily = database.C(DAILY_VISITS_COLL)
|
m.daily = database.C(DAILY_VISITS_COLL)
|
||||||
|
m.monthly = database.C(MONTHLY_VISITS_COLL)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,15 +58,41 @@ func (m *MR) GetTags(numTags int, booksColl *mgo.Collection) ([]string, error) {
|
||||||
return tags, nil
|
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) {
|
func (m *MR) GetDayVisits(start time.Time, statsColl *mgo.Collection) ([]Visits, error) {
|
||||||
if m.isOutdated(DAILY_VISITS_COLL, MINUTES_UPDATE_DAILY) {
|
if m.isOutdated(DAILY_VISITS_COLL, MINUTES_UPDATE_DAILY) {
|
||||||
var mr mgo.MapReduce
|
var mr mgo.MapReduce
|
||||||
mr.Map = `function() {
|
mr.Map = `function() {
|
||||||
var day = Date.UTC(this.date.getFullYear(),
|
var day = Date.UTC(this.date.getUTCFullYear(),
|
||||||
this.date.getMonth(),
|
this.date.getUTCMonth(),
|
||||||
this.date.getDate());
|
this.date.getUTCDate());
|
||||||
emit(day, 1);
|
emit(day, 1);
|
||||||
}`
|
}`
|
||||||
mr.Reduce = `function(date, vals) {
|
mr.Reduce = `function(date, vals) {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
vals.forEach(function(v) { count += v; });
|
vals.forEach(function(v) { count += v; });
|
||||||
|
@ -79,6 +109,30 @@ func (m *MR) GetDayVisits(start time.Time, statsColl *mgo.Collection) ([]Visits,
|
||||||
return result, err
|
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 {
|
func (m *MR) update(mr *mgo.MapReduce, query bson.M, queryColl *mgo.Collection, storeColl string) error {
|
||||||
_, err := m.meta.RemoveAll(bson.M{"type": storeColl})
|
_, err := m.meta.RemoveAll(bson.M{"type": storeColl})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
64
stats.go
64
stats.go
|
@ -47,14 +47,18 @@ func statsWorker() {
|
||||||
func statsHandler(w http.ResponseWriter, r *http.Request, sess *Session) {
|
func statsHandler(w http.ResponseWriter, r *http.Request, sess *Session) {
|
||||||
var data statsData
|
var data statsData
|
||||||
data.S = GetStatus(w, r)
|
data.S = GetStatus(w, r)
|
||||||
|
data.Hourly = getHourlyVisits()
|
||||||
data.Daily = getDailyVisits()
|
data.Daily = getDailyVisits()
|
||||||
|
data.Monthly = getMonthlyVisits()
|
||||||
|
|
||||||
loadTemplate(w, "stats", data)
|
loadTemplate(w, "stats", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
type statsData struct {
|
type statsData struct {
|
||||||
S Status
|
S Status
|
||||||
Daily []visitData
|
Hourly []visitData
|
||||||
|
Daily []visitData
|
||||||
|
Monthly []visitData
|
||||||
}
|
}
|
||||||
|
|
||||||
type visitData struct {
|
type visitData struct {
|
||||||
|
@ -62,26 +66,52 @@ type visitData struct {
|
||||||
Count int
|
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 {
|
func getDailyVisits() []visitData {
|
||||||
const numDays = 30
|
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)
|
visit, _ := db.GetDayVisits(start)
|
||||||
prevDay := start.Day()
|
|
||||||
i := 0
|
|
||||||
for _, v := range visit {
|
for _, v := range visit {
|
||||||
day := time.Unix(v.Date/1000, 0).Day()
|
var elem visitData
|
||||||
for prevDay+1 < day {
|
day := time.Unix(v.Date/1000, 0).UTC().Day()
|
||||||
prevDay++
|
elem.Label = strconv.Itoa(day)
|
||||||
visits[i].Label = strconv.Itoa(prevDay)
|
elem.Count = v.Count
|
||||||
visits[i].Count = 0
|
visits = append(visits, elem)
|
||||||
i++
|
}
|
||||||
}
|
|
||||||
visits[i].Label = strconv.Itoa(day)
|
return visits
|
||||||
visits[i].Count = v.Count
|
}
|
||||||
prevDay = day
|
|
||||||
i++
|
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
|
return visits
|
||||||
|
|
|
@ -3,27 +3,39 @@
|
||||||
<script src="/js/Chart.min.js"></script>
|
<script src="/js/Chart.min.js"></script>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<h4>Hourly visits:</h4>
|
||||||
|
<canvas id="hourly" height="400" width="800"></canvas>
|
||||||
<h4>Daily visits:</h4>
|
<h4>Daily visits:</h4>
|
||||||
<canvas id="daily" height="400" width="800"></canvas>
|
<canvas id="daily" height="400" width="800"></canvas>
|
||||||
|
<h4>Monthly visits:</h4>
|
||||||
|
<canvas id="monthly" height="400" width="800"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var data = {
|
function chart(id, labels, counts) {
|
||||||
labels : [{{range .Daily}}"{{.Label}}",{{end}}],
|
var data = {
|
||||||
datasets : [
|
labels : labels,
|
||||||
{
|
datasets : [
|
||||||
fillColor : "rgba(151,187,205,0.5)",
|
{
|
||||||
strokeColor : "rgba(151,187,205,1)",
|
fillColor : "rgba(151,187,205,0.5)",
|
||||||
pointColor : "rgba(151,187,205,1)",
|
strokeColor : "rgba(151,187,205,1)",
|
||||||
pointStrokeColor : "#fff",
|
pointColor : "rgba(151,187,205,1)",
|
||||||
data : [{{range .Daily}}{{.Count}},{{end}}]
|
pointStrokeColor : "#fff",
|
||||||
}
|
data : counts
|
||||||
]
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctx = $(id).get(0).getContext("2d");
|
||||||
|
new Chart(ctx).Line(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chart("#hourly", [{{range .Hourly}}"{{.Label}}",{{end}}],
|
||||||
var ctx = $("#daily").get(0).getContext("2d");
|
[{{range .Hourly}}{{.Count}},{{end}}])
|
||||||
new Chart(ctx).Line(data);
|
chart("#daily", [{{range .Daily}}"{{.Label}}",{{end}}],
|
||||||
|
[{{range .Daily}}{{.Count}},{{end}}])
|
||||||
|
chart("#monthly", [{{range .Monthly}}"{{.Label}}",{{end}}],
|
||||||
|
[{{range .Monthly}}{{.Count}},{{end}}])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "footer.html"}}
|
{{template "footer.html"}}
|
||||||
|
|
Reference in a new issue