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  }