2018-02-19 04:26:01 +00:00
package counters
2018-02-04 08:15:20 +00:00
import (
2018-02-05 10:29:13 +00:00
"database/sql"
2018-02-04 08:15:20 +00:00
"sync"
"sync/atomic"
2018-02-05 10:29:13 +00:00
2019-07-28 04:01:33 +00:00
c "github.com/Azareal/Gosora/common"
2018-10-27 03:21:02 +00:00
"github.com/Azareal/Gosora/query_gen"
2019-07-28 04:01:33 +00:00
"github.com/pkg/errors"
2018-02-04 08:15:20 +00:00
)
2018-02-05 10:29:13 +00:00
var ReferrerTracker * DefaultReferrerTracker
2018-02-04 08:15:20 +00:00
2018-02-05 10:29:13 +00:00
// Add ReferrerItems here after they've had zero views for a while
var referrersToDelete = make ( map [ string ] * ReferrerItem )
2018-02-04 08:15:20 +00:00
type ReferrerItem struct {
2018-02-05 10:29:13 +00:00
Count int64
2018-02-04 08:15:20 +00:00
}
// ? We'll track referrer domains here rather than the exact URL they arrived from for now, we'll think about expanding later
// ? Referrers are fluid and ever-changing so we have to use string keys rather than 'enum' ints
type DefaultReferrerTracker struct {
odd map [ string ] * ReferrerItem
even map [ string ] * ReferrerItem
oddLock sync . RWMutex
evenLock sync . RWMutex
2018-02-05 10:29:13 +00:00
insert * sql . Stmt
2018-02-04 08:15:20 +00:00
}
2018-02-05 10:29:13 +00:00
func NewDefaultReferrerTracker ( ) ( * DefaultReferrerTracker , error ) {
2018-08-04 11:46:36 +00:00
acc := qgen . NewAcc ( )
2018-02-05 10:29:13 +00:00
refTracker := & DefaultReferrerTracker {
odd : make ( map [ string ] * ReferrerItem ) ,
even : make ( map [ string ] * ReferrerItem ) ,
insert : acc . Insert ( "viewchunks_referrers" ) . Columns ( "count, createdAt, domain" ) . Fields ( "?,UTC_TIMESTAMP(),?" ) . Prepare ( ) , // TODO: Do something more efficient than doing a query for each referrer
2018-02-04 08:15:20 +00:00
}
2019-07-28 04:01:33 +00:00
c . AddScheduledFifteenMinuteTask ( refTracker . Tick )
//c.AddScheduledSecondTask(refTracker.Tick)
c . AddShutdownTask ( refTracker . Tick )
2018-02-05 10:29:13 +00:00
return refTracker , acc . FirstError ( )
2018-02-04 08:15:20 +00:00
}
2018-02-05 10:29:13 +00:00
// TODO: Move this and the other view tickers out of the main task loop to avoid blocking other tasks?
2018-02-04 08:15:20 +00:00
func ( ref * DefaultReferrerTracker ) Tick ( ) ( err error ) {
2018-02-05 10:29:13 +00:00
for referrer , counter := range referrersToDelete {
// Handle views which squeezed through the gaps at the last moment
count := counter . Count
if count != 0 {
err := ref . insertChunk ( referrer , count ) // TODO: Bulk insert for speed?
if err != nil {
2019-07-28 04:01:33 +00:00
return errors . Wrap ( errors . WithStack ( err ) , "ref counter" )
2018-02-05 10:29:13 +00:00
}
}
delete ( referrersToDelete , referrer )
2018-02-04 08:15:20 +00:00
}
2018-02-05 10:29:13 +00:00
// Run the queries and schedule zero view refs for deletion from memory
2019-07-28 04:01:33 +00:00
refLoop := func ( l * sync . RWMutex , m map [ string ] * ReferrerItem ) error {
l . Lock ( )
defer l . Unlock ( )
for referrer , counter := range m {
if counter . Count == 0 {
referrersToDelete [ referrer ] = counter
delete ( m , referrer )
}
count := atomic . SwapInt64 ( & counter . Count , 0 )
err := ref . insertChunk ( referrer , count ) // TODO: Bulk insert for speed?
if err != nil {
return errors . Wrap ( errors . WithStack ( err ) , "ref counter" )
}
2018-02-05 10:29:13 +00:00
}
2019-07-28 04:01:33 +00:00
return nil
2018-02-05 10:29:13 +00:00
}
2019-07-28 04:01:33 +00:00
err = refLoop ( & ref . oddLock , ref . odd )
if err != nil {
return err
2018-02-05 10:29:13 +00:00
}
2019-07-28 04:01:33 +00:00
return refLoop ( & ref . evenLock , ref . even )
2018-02-04 08:15:20 +00:00
}
2018-02-05 10:29:13 +00:00
func ( ref * DefaultReferrerTracker ) insertChunk ( referrer string , count int64 ) error {
if count == 0 {
return nil
}
2019-07-28 04:01:33 +00:00
c . DebugDetailf ( "Inserting a vchunk with a count of %d for referrer %s" , count , referrer )
2018-02-05 10:29:13 +00:00
_ , err := ref . insert . Exec ( count , referrer )
return err
}
2018-02-04 08:15:20 +00:00
func ( ref * DefaultReferrerTracker ) Bump ( referrer string ) {
if referrer == "" {
return
}
var refItem * ReferrerItem
// Slightly crude and rudimentary, but it should give a basic degree of sharding
if referrer [ 0 ] % 2 == 0 {
ref . evenLock . RLock ( )
refItem = ref . even [ referrer ]
ref . evenLock . RUnlock ( )
2018-02-05 10:29:13 +00:00
if refItem != nil {
atomic . AddInt64 ( & refItem . Count , 1 )
2018-02-04 08:15:20 +00:00
} else {
ref . evenLock . Lock ( )
2018-02-05 10:29:13 +00:00
ref . even [ referrer ] = & ReferrerItem { Count : 1 }
2018-02-04 08:15:20 +00:00
ref . evenLock . Unlock ( )
}
} else {
ref . oddLock . RLock ( )
refItem = ref . odd [ referrer ]
ref . oddLock . RUnlock ( )
2018-02-05 10:29:13 +00:00
if refItem != nil {
atomic . AddInt64 ( & refItem . Count , 1 )
2018-02-04 08:15:20 +00:00
} else {
ref . oddLock . Lock ( )
2018-02-05 10:29:13 +00:00
ref . odd [ referrer ] = & ReferrerItem { Count : 1 }
2018-02-04 08:15:20 +00:00
ref . oddLock . Unlock ( )
}
}
}