diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0a451c --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# proc +statistics utils for golang + +## peference +``` + // proc.counter Benchmark Test Result + BenchmarkSCounterBaseIncr 5000000 49.7 ns/op + BenchmarkSCounterBaseIncrConcurrent 5000000 51.1 ns/op + BenchmarkSCounterQpsIncr 5000000 51.0 ns/op + BenchmarkSCounterQpsIncrConcurrent 5000000 51.7 ns/op + +``` + +## usage +``` +``` + +## reference diff --git a/counter.go b/counter.go new file mode 100644 index 0000000..f2890bc --- /dev/null +++ b/counter.go @@ -0,0 +1,140 @@ +package proc + +import ( + ntime "github.com/niean/gotools/time" + "sync" + "time" +) + +const ( + DefaultOtherMaxSize = 100 + DefaultSCounterQpsPeriod = 1 +) + +// basic counter +type SCounterBase struct { + sync.RWMutex + Name string + Cnt int64 + Time string + ts int64 + Other map[string]interface{} +} + +func NewSCounterBase(name string) *SCounterBase { + uts := time.Now().Unix() + return &SCounterBase{Name: name, Cnt: 0, Time: ntime.FormatTs(uts), + ts: uts, Other: make(map[string]interface{})} +} + +func (this *SCounterBase) Get() *SCounterBase { + this.RLock() + defer this.RUnlock() + + return this +} + +func (this *SCounterBase) SetCnt(cnt int64) { + this.Lock() + this.Cnt = cnt + this.ts = time.Now().Unix() + this.Time = ntime.FormatTs(this.ts) + this.Unlock() +} + +func (this *SCounterBase) Incr() { + this.IncrBy(int64(1)) +} + +func (this *SCounterBase) IncrBy(incr int64) { + this.Lock() + this.Cnt += incr + this.Unlock() +} + +func (this *SCounterBase) PutOther(key string, value interface{}) bool { + this.Lock() + defer this.Unlock() + + ret := false + _, exist := this.Other[key] + if exist { + this.Other[key] = value + ret = true + } else { + if len(this.Other) < DefaultOtherMaxSize { + this.Other[key] = value + ret = true + } + } + + return ret +} + +// counter with qps +type SCounterQps struct { + sync.RWMutex + Name string + Cnt int64 + Qps int64 + Time string + ts int64 + lastTs int64 + lastCnt int64 + Other map[string]interface{} +} + +func NewSCounterQps(name string) *SCounterQps { + uts := time.Now().Unix() + return &SCounterQps{Name: name, Cnt: 0, Time: ntime.FormatTs(uts), ts: uts, + Qps: 0, lastCnt: 0, lastTs: uts, Other: make(map[string]interface{})} +} + +func (this *SCounterQps) Get() *SCounterQps { + this.Lock() + defer this.Unlock() + + this.ts = time.Now().Unix() + this.Time = ntime.FormatTs(this.ts) + // get smooth qps value + if this.ts-this.lastTs > DefaultSCounterQpsPeriod { + this.Qps = int64((this.Cnt - this.lastCnt) / (this.ts - this.lastTs)) + this.lastTs = this.ts + this.lastCnt = this.Cnt + } + + return this +} + +func (this *SCounterQps) Incr() { + this.IncrBy(int64(1)) +} + +func (this *SCounterQps) IncrBy(incr int64) { + this.Lock() + this.incrBy(incr) + this.Unlock() +} + +func (this *SCounterQps) PutOther(key string, value interface{}) bool { + this.Lock() + defer this.Unlock() + + ret := false + _, exist := this.Other[key] + if exist { + this.Other[key] = value + ret = true + } else { + if len(this.Other) < DefaultOtherMaxSize { + this.Other[key] = value + ret = true + } + } + + return ret +} + +func (this *SCounterQps) incrBy(incr int64) { + this.Cnt += incr +} diff --git a/counter_test.go b/counter_test.go new file mode 100644 index 0000000..4fb2064 --- /dev/null +++ b/counter_test.go @@ -0,0 +1,79 @@ +package proc + +import ( + "runtime" + "sync" + "testing" +) + +func BenchmarkSCounterBaseIncr(b *testing.B) { + b.StopTimer() + b.N = 5000000 + cnt := NewSCounterBase("cnt.base") + b.StartTimer() + for i := 0; i < b.N; i++ { + cnt.Incr() + } + if int(cnt.Cnt) != b.N { + b.Error("error, SCounterBase.Incr not safe") + } +} + +func BenchmarkSCounterBaseIncrConcurrent(b *testing.B) { + b.StopTimer() + b.N = 5000000 + cnt := NewSCounterBase("cnt.base") + wg := sync.WaitGroup{} + workers := runtime.NumCPU() + each := b.N / workers + wg.Add(workers) + b.StartTimer() + for i := 0; i < workers; i++ { + go func() { + for i := 0; i < each; i++ { + cnt.Incr() + } + wg.Done() + }() + } + wg.Wait() + if int(cnt.Cnt) != b.N { + b.Error("error, SCounterBase.Incr concurrently not safe") + } +} + +func BenchmarkSCounterQpsIncr(b *testing.B) { + b.StopTimer() + b.N = 5000000 + cnt := NewSCounterQps("cnt.qps") + b.StartTimer() + for i := 0; i < b.N; i++ { + cnt.Incr() + } + if int(cnt.Cnt) != b.N { + b.Error("error, SCounterQps.Incr not safe") + } +} + +func BenchmarkSCounterQpsIncrConcurrent(b *testing.B) { + b.StopTimer() + b.N = 5000000 + cnt := NewSCounterQps("cnt.qps") + wg := sync.WaitGroup{} + workers := runtime.NumCPU() + each := b.N / workers + wg.Add(workers) + b.StartTimer() + for i := 0; i < workers; i++ { + go func() { + for i := 0; i < each; i++ { + cnt.Incr() + } + wg.Done() + }() + } + wg.Wait() + if int(cnt.Cnt) != b.N { + b.Error("error, SCounterQps.Incr concurrently not safe") + } +} diff --git a/trace.go b/trace.go new file mode 100644 index 0000000..ff3fcca --- /dev/null +++ b/trace.go @@ -0,0 +1,59 @@ +package proc + +import ( + "container/list" + "sync" +) + +type DataTrace struct { + sync.RWMutex + MaxSize int + Name string + PK string + L *list.List +} + +func NewDataTrace(name string, maxSize int) *DataTrace { + return &DataTrace{L: list.New(), Name: name, MaxSize: maxSize} +} + +func (this *DataTrace) SetPK(pk string) { + this.Lock() + defer this.Unlock() + + // rm old caches when trace's pk changed + if this.PK != pk { + this.L = list.New() + } + this.PK = pk +} + +// proposed that there were few traced items +func (this *DataTrace) Trace(pk string, v interface{}) { + this.RLock() + if this.PK != pk { + this.RUnlock() + return + } + this.RUnlock() + + // we could almost not step here, so we get few wlock + this.Lock() + defer this.Unlock() + this.L.PushFront(v) + if this.L.Len() > this.MaxSize { + this.L.Remove(this.L.Back()) + } +} + +func (this *DataTrace) GetAllTraced() []interface{} { + this.RLock() + defer this.RUnlock() + + items := make([]interface{}, 0) + for e := this.L.Front(); e != nil; e = e.Next() { + items = append(items, e) + } + + return items +}