github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/counters/routes.go (about) 1 package counters 2 3 import ( 4 "database/sql" 5 "sync" 6 "sync/atomic" 7 "time" 8 9 c "github.com/Azareal/Gosora/common" 10 qgen "github.com/Azareal/Gosora/query_gen" 11 "github.com/Azareal/Gosora/uutils" 12 "github.com/pkg/errors" 13 ) 14 15 var RouteViewCounter *DefaultRouteViewCounter 16 17 type RVBucket struct { 18 counter int64 19 avg int 20 21 sync.Mutex 22 } 23 24 // TODO: Make this lockless? 25 type DefaultRouteViewCounter struct { 26 buckets []*RVBucket //[RouteID]count 27 insert *sql.Stmt 28 insert5 *sql.Stmt 29 } 30 31 func NewDefaultRouteViewCounter(acc *qgen.Accumulator) (*DefaultRouteViewCounter, error) { 32 routeBuckets := make([]*RVBucket, len(routeMapEnum)) 33 for bucketID, _ := range routeBuckets { 34 routeBuckets[bucketID] = &RVBucket{counter: 0, avg: 0} 35 } 36 37 fields := "?,?,UTC_TIMESTAMP(),?" 38 co := &DefaultRouteViewCounter{ 39 buckets: routeBuckets, 40 insert: acc.Insert("viewchunks").Columns("count,avg,createdAt,route").Fields(fields).Prepare(), 41 insert5: acc.BulkInsert("viewchunks").Columns("count,avg,createdAt,route").Fields(fields, fields, fields, fields, fields).Prepare(), 42 } 43 if !c.Config.DisableAnalytics { 44 c.Tasks.FifteenMin.Add(co.Tick) // There could be a lot of routes, so we don't want to be running this every second 45 //c.Tasks.Sec.Add(co.Tick) 46 c.Tasks.Shutdown.Add(co.Tick) 47 } 48 return co, acc.FirstError() 49 } 50 51 type RVCount struct { 52 RouteID int 53 Count int64 54 Avg int 55 } 56 57 func (co *DefaultRouteViewCounter) Tick() (err error) { 58 var tb []RVCount 59 for routeID, b := range co.buckets { 60 var avg int 61 count := atomic.SwapInt64(&b.counter, 0) 62 b.Lock() 63 avg = b.avg 64 b.avg = 0 65 b.Unlock() 66 67 if count == 0 { 68 continue 69 } 70 tb = append(tb, RVCount{routeID, count, avg}) 71 } 72 73 // TODO: Expand on this? 74 var i int 75 if len(tb) >= 5 { 76 for ; len(tb) > (i + 5); i += 5 { 77 err := co.insert5Chunk(tb[i : i+5]) 78 if err != nil { 79 c.DebugLogf("tb: %+v\n", tb) 80 c.DebugLog("i: ", i) 81 return errors.Wrap(errors.WithStack(err), "route counter x 5") 82 } 83 } 84 } 85 86 for ; len(tb) > i; i++ { 87 it := tb[i] 88 err = co.insertChunk(it.Count, it.Avg, it.RouteID) 89 if err != nil { 90 c.DebugLogf("tb: %+v\n", tb) 91 c.DebugLog("i: ", i) 92 return errors.Wrap(errors.WithStack(err), "route counter") 93 } 94 } 95 96 return nil 97 } 98 99 func (co *DefaultRouteViewCounter) insertChunk(count int64, avg, route int) error { 100 routeName := reverseRouteMapEnum[route] 101 c.DebugLogf("Inserting vchunk with count %d, avg %d for route %s (%d)", count, avg, routeName, route) 102 _, err := co.insert.Exec(count, avg, routeName) 103 return err 104 } 105 106 func (co *DefaultRouteViewCounter) insert5Chunk(rvs []RVCount) error { 107 args := make([]interface{}, len(rvs)*3) 108 i := 0 109 for _, rv := range rvs { 110 routeName := reverseRouteMapEnum[rv.RouteID] 111 if rv.Avg == 0 { 112 c.DebugLogf("Queueing vchunk with count %d for routes %s (%d)", rv.Count, routeName, rv.RouteID) 113 } else { 114 c.DebugLogf("Queueing vchunk with count %d, avg %d for routes %s (%d)", rv.Count, rv.Avg, routeName, rv.RouteID) 115 } 116 args[i] = rv.Count 117 args[i+1] = rv.Avg 118 args[i+2] = routeName 119 i += 3 120 } 121 c.DebugDetailf("args: %+v\n", args) 122 _, err := co.insert5.Exec(args...) 123 return err 124 } 125 126 func (co *DefaultRouteViewCounter) Bump(route int) { 127 if c.Config.DisableAnalytics { 128 return 129 } 130 // TODO: Test this check 131 b := co.buckets[route] 132 c.DebugDetail("bucket ", route, ": ", b) 133 if len(co.buckets) <= route || route < 0 { 134 return 135 } 136 atomic.AddInt64(&b.counter, 1) 137 } 138 139 // TODO: Eliminate the lock? 140 func (co *DefaultRouteViewCounter) Bump2(route int, t time.Time) { 141 if c.Config.DisableAnalytics { 142 return 143 } 144 // TODO: Test this check 145 b := co.buckets[route] 146 c.DebugDetail("bucket ", route, ": ", b) 147 if len(co.buckets) <= route || route < 0 { 148 return 149 } 150 micro := int(time.Since(t).Microseconds()) 151 //co.PerfCounter.Push(since, true) 152 atomic.AddInt64(&b.counter, 1) 153 b.Lock() 154 if micro != b.avg { 155 if b.avg == 0 { 156 b.avg = micro 157 } else { 158 b.avg = (micro + b.avg) / 2 159 } 160 } 161 b.Unlock() 162 } 163 164 // TODO: Eliminate the lock? 165 func (co *DefaultRouteViewCounter) Bump3(route int, nano int64) { 166 if c.Config.DisableAnalytics { 167 return 168 } 169 // TODO: Test this check 170 b := co.buckets[route] 171 c.DebugDetail("bucket ", route, ": ", b) 172 if len(co.buckets) <= route || route < 0 { 173 return 174 } 175 micro := int((uutils.Nanotime() - nano) / 1000) 176 //co.PerfCounter.Push(since, true) 177 atomic.AddInt64(&b.counter, 1) 178 b.Lock() 179 if micro != b.avg { 180 if b.avg == 0 { 181 b.avg = micro 182 } else { 183 b.avg = (micro + b.avg) / 2 184 } 185 } 186 b.Unlock() 187 }