ea1037bd63
experiment with tracking average route performance temporary error route stub optimise dumprequest add DisableAnalytics config setting fix double hyphens in slugs being mistaken for sql injection more querygen tests You wil need to run the updater / patcher for this commit.
164 lines
3.8 KiB
Go
164 lines
3.8 KiB
Go
package counters
|
|
|
|
import (
|
|
"database/sql"
|
|
"sync"
|
|
"time"
|
|
|
|
c "github.com/Azareal/Gosora/common"
|
|
qgen "github.com/Azareal/Gosora/query_gen"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var RouteViewCounter *DefaultRouteViewCounter
|
|
|
|
type RVBucket struct {
|
|
counter int
|
|
avg int
|
|
|
|
sync.Mutex
|
|
}
|
|
|
|
// TODO: Make this lockless?
|
|
type DefaultRouteViewCounter struct {
|
|
buckets []*RVBucket //[RouteID]count
|
|
insert *sql.Stmt
|
|
insert5 *sql.Stmt
|
|
}
|
|
|
|
func NewDefaultRouteViewCounter(acc *qgen.Accumulator) (*DefaultRouteViewCounter, error) {
|
|
routeBuckets := make([]*RVBucket, len(routeMapEnum))
|
|
for bucketID, _ := range routeBuckets {
|
|
routeBuckets[bucketID] = &RVBucket{counter: 0, avg: 0}
|
|
}
|
|
|
|
fields := "?,?,UTC_TIMESTAMP(),?"
|
|
co := &DefaultRouteViewCounter{
|
|
buckets: routeBuckets,
|
|
insert: acc.Insert("viewchunks").Columns("count,avg,createdAt,route").Fields(fields).Prepare(),
|
|
insert5: acc.BulkInsert("viewchunks").Columns("count,avg,createdAt,route").Fields(fields, fields, fields, fields, fields).Prepare(),
|
|
}
|
|
if !c.Config.DisableAnalytics {
|
|
c.AddScheduledFifteenMinuteTask(co.Tick) // There could be a lot of routes, so we don't want to be running this every second
|
|
//c.AddScheduledSecondTask(co.Tick)
|
|
c.AddShutdownTask(co.Tick)
|
|
}
|
|
return co, acc.FirstError()
|
|
}
|
|
|
|
type RVCount struct {
|
|
RouteID int
|
|
Count int
|
|
Avg int
|
|
}
|
|
|
|
func (co *DefaultRouteViewCounter) Tick() (err error) {
|
|
var tb []RVCount
|
|
for routeID, b := range co.buckets {
|
|
var count, avg int
|
|
b.Lock()
|
|
count = b.counter
|
|
b.counter = 0
|
|
avg = b.avg
|
|
b.avg = 0
|
|
b.Unlock()
|
|
|
|
if count == 0 {
|
|
continue
|
|
}
|
|
tb = append(tb, RVCount{routeID, count, avg})
|
|
}
|
|
|
|
// TODO: Expand on this?
|
|
var i int
|
|
if len(tb) >= 5 {
|
|
for ; len(tb) > (i + 5); i += 5 {
|
|
err := co.insert5Chunk(tb[i : i+5])
|
|
if err != nil {
|
|
c.DebugLogf("tb: %+v\n", tb)
|
|
c.DebugLog("i: ", i)
|
|
return errors.Wrap(errors.WithStack(err), "route counter x 5")
|
|
}
|
|
}
|
|
}
|
|
|
|
for ; len(tb) > i; i++ {
|
|
it := tb[i]
|
|
err = co.insertChunk(it.Count, it.Avg, it.RouteID)
|
|
if err != nil {
|
|
c.DebugLogf("tb: %+v\n", tb)
|
|
c.DebugLog("i: ", i)
|
|
return errors.Wrap(errors.WithStack(err), "route counter")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (co *DefaultRouteViewCounter) insertChunk(count, avg, route int) error {
|
|
routeName := reverseRouteMapEnum[route]
|
|
c.DebugLogf("Inserting a vchunk with a count of %d, avg of %d for route %s (%d)", count, avg, routeName, route)
|
|
_, err := co.insert.Exec(count, avg, routeName)
|
|
return err
|
|
}
|
|
|
|
func (co *DefaultRouteViewCounter) insert5Chunk(rvs []RVCount) error {
|
|
args := make([]interface{}, len(rvs)*3)
|
|
i := 0
|
|
for _, rv := range rvs {
|
|
routeName := reverseRouteMapEnum[rv.RouteID]
|
|
if rv.Avg == 0 {
|
|
c.DebugLogf("Queueing a vchunk with a count of %d for routes %s (%d)", rv.Count, routeName, rv.RouteID)
|
|
} else {
|
|
c.DebugLogf("Queueing a vchunk with count %d, avg %d for routes %s (%d)", rv.Count, rv.Avg, routeName, rv.RouteID)
|
|
}
|
|
args[i] = rv.Count
|
|
args[i+1] = rv.Avg
|
|
args[i+2] = routeName
|
|
i += 3
|
|
}
|
|
c.DebugDetailf("args: %+v\n", args)
|
|
_, err := co.insert5.Exec(args...)
|
|
return err
|
|
}
|
|
|
|
func (co *DefaultRouteViewCounter) Bump(route int) {
|
|
if c.Config.DisableAnalytics {
|
|
return
|
|
}
|
|
// TODO: Test this check
|
|
b := co.buckets[route]
|
|
c.DebugDetail("buckets[", route, "]: ", b)
|
|
if len(co.buckets) <= route || route < 0 {
|
|
return
|
|
}
|
|
// TODO: Avoid lock by using atomic increment?
|
|
b.Lock()
|
|
b.counter++
|
|
b.Unlock()
|
|
}
|
|
|
|
// TODO: Eliminate the lock?
|
|
func (co *DefaultRouteViewCounter) Bump2(route int, t time.Time) {
|
|
if c.Config.DisableAnalytics {
|
|
return
|
|
}
|
|
// TODO: Test this check
|
|
b := co.buckets[route]
|
|
c.DebugDetail("buckets[", route, "]: ", b)
|
|
if len(co.buckets) <= route || route < 0 {
|
|
return
|
|
}
|
|
micro := int(time.Since(t).Microseconds())
|
|
b.Lock()
|
|
b.counter++
|
|
if micro != b.avg {
|
|
if b.avg == 0 {
|
|
b.avg = micro
|
|
} else {
|
|
b.avg = (micro + b.avg) / 2
|
|
}
|
|
}
|
|
b.Unlock()
|
|
}
|