github.com/v2pro/plz@v0.0.0-20221028024117-e5f9aec5b631/countlog/stats/window.go (about) 1 package stats 2 3 import ( 4 "github.com/v2pro/plz/countlog/spi" 5 "unsafe" 6 "strings" 7 "reflect" 8 "sync" 9 "github.com/v2pro/plz/gls" 10 "time" 11 "context" 12 ) 13 14 type Window struct { 15 collector Collector 16 event string 17 dimensionElemCount int 18 shards [16]windowShard 19 } 20 21 type windowShard struct { 22 MapMonoid 23 lock *sync.Mutex 24 } 25 26 func newWindow(executor Executor, collector Collector, dimensionElemCount int) *Window { 27 window := &Window{ 28 collector: collector, 29 dimensionElemCount: dimensionElemCount, 30 } 31 for i := 0; i < 16; i++ { 32 window.shards[i] = windowShard{ 33 MapMonoid: MapMonoid{}, 34 lock: &sync.Mutex{}, 35 } 36 } 37 executor(window.exportEverySecond) 38 return window 39 } 40 41 func (window *Window) exportEverySecond(ctx context.Context) { 42 timer := time.NewTimer(time.Second) 43 for { 44 select { 45 case <-timer.C: 46 window.Export(time.Now()) 47 case <-ctx.Done(): 48 return 49 } 50 } 51 } 52 53 func (window *Window) Mutate() (*sync.Mutex, MapMonoid) { 54 shardId := gls.GoID() % 16 55 shard := window.shards[shardId] 56 return shard.lock, shard.MapMonoid 57 } 58 59 func (window *Window) Export(now time.Time) { 60 for i := 0; i < 16; i++ { 61 window.exportShard(now, window.shards[i]) 62 } 63 } 64 65 func (window *Window) exportShard(now time.Time, shard windowShard) { 66 shard.lock.Lock() 67 defer shard.lock.Unlock() 68 // batch allocate memory to hold dimensions 69 space := make([]string, len(shard.MapMonoid)*window.dimensionElemCount) 70 for dimensionObj, monoid := range shard.MapMonoid { 71 dimension := space[:window.dimensionElemCount] 72 space = space[window.dimensionElemCount:] 73 slice := &sliceHeader{ 74 Cap: window.dimensionElemCount, 75 Len: window.dimensionElemCount, 76 Data: (*emptyInterface)(unsafe.Pointer(&dimensionObj)).word, 77 } 78 copy(dimension, *(*[]string)(unsafe.Pointer(slice))) 79 window.collector.Collect(&Point{ 80 Event: window.event, 81 Timestamp: now, 82 Dimension: dimension, 83 Value: monoid.Export(), 84 }) 85 } 86 } 87 88 type propIdx int 89 90 type dimensionExtractor interface { 91 Extract(event *spi.Event, monoid MapMonoid, createElem func() Monoid) Monoid 92 } 93 94 func newDimensionExtractor(site *spi.LogSite) (dimensionExtractor, int) { 95 var dimensionElems []string 96 for i := 0; i < len(site.Sample); i += 2 { 97 key := site.Sample[i].(string) 98 if key == "dim" { 99 dimensionElems = strings.Split(site.Sample[i+1].(string), ",") 100 } 101 } 102 indices := make([]propIdx, 0, len(dimensionElems)) 103 for i := 0; i < len(site.Sample); i += 2 { 104 key := site.Sample[i].(string) 105 for _, dimension := range dimensionElems { 106 if key == dimension { 107 indices = append(indices, propIdx(i)) 108 indices = append(indices, propIdx(i+1)) 109 } 110 } 111 } 112 arrayType := reflect.ArrayOf(len(indices), reflect.TypeOf("")) 113 arrayObj := reflect.New(arrayType).Elem().Interface() 114 sampleInterface := *(*emptyInterface)(unsafe.Pointer(&arrayObj)) 115 if len(indices) == 0 { 116 return &dimensionExtractor0{}, 0 117 } 118 if len(indices) <= 2 { 119 return &dimensionExtractor2{ 120 sampleInterface: sampleInterface, 121 indices: indices, 122 }, len(indices) 123 } 124 if len(indices) <= 4 { 125 return &dimensionExtractor4{ 126 sampleInterface: sampleInterface, 127 indices: indices, 128 }, len(indices) 129 } 130 if len(indices) <= 8 { 131 return &dimensionExtractor8{ 132 sampleInterface: sampleInterface, 133 indices: indices, 134 }, len(indices) 135 } 136 return &dimensionExtractorAny{ 137 sampleInterface: sampleInterface, 138 indices: indices, 139 }, len(indices) 140 } 141 142 type dimensionExtractor0 struct { 143 } 144 145 func (extractor *dimensionExtractor0) Extract(event *spi.Event, monoid MapMonoid, createElem func() Monoid) Monoid { 146 elem := monoid[0] 147 if elem == nil { 148 elem = createElem() 149 monoid[0] = elem 150 } 151 return elem 152 } 153 154 type dimensionExtractor2 struct { 155 sampleInterface emptyInterface 156 indices []propIdx 157 } 158 159 func (extractor *dimensionExtractor2) Extract(event *spi.Event, monoid MapMonoid, createElem func() Monoid) Monoid { 160 dimensionArr := [2]string{} 161 dimension := dimensionArr[:len(extractor.indices)] 162 return extractDimension(extractor.sampleInterface, dimension, 163 extractor.indices, event, monoid, createElem) 164 } 165 166 type dimensionExtractor4 struct { 167 sampleInterface emptyInterface 168 indices []propIdx 169 } 170 171 func (extractor *dimensionExtractor4) Extract(event *spi.Event, monoid MapMonoid, createElem func() Monoid) Monoid { 172 dimensionArr := [4]string{} 173 dimension := dimensionArr[:len(extractor.indices)] 174 return extractDimension(extractor.sampleInterface, dimension, 175 extractor.indices, event, monoid, createElem) 176 } 177 178 type dimensionExtractor8 struct { 179 sampleInterface emptyInterface 180 indices []propIdx 181 } 182 183 func (extractor *dimensionExtractor8) Extract(event *spi.Event, monoid MapMonoid, createElem func() Monoid) Monoid { 184 dimensionArr := [8]string{} 185 dimension := dimensionArr[:len(extractor.indices)] 186 return extractDimension(extractor.sampleInterface, dimension, 187 extractor.indices, event, monoid, createElem) 188 } 189 190 type dimensionExtractorAny struct { 191 sampleInterface emptyInterface 192 indices []propIdx 193 } 194 195 func (extractor *dimensionExtractorAny) Extract(event *spi.Event, monoid MapMonoid, createElem func() Monoid) Monoid { 196 dimension := make([]string, len(extractor.indices)) 197 return extractDimension(extractor.sampleInterface, dimension, 198 extractor.indices, event, monoid, createElem) 199 } 200 201 func extractDimension( 202 sampleInterface emptyInterface, dimension []string, indices []propIdx, 203 event *spi.Event, monoid MapMonoid, createElem func() Monoid) Monoid { 204 for i, idx := range indices { 205 dimension[i] = event.Properties[idx].(string) 206 } 207 dimensionInterface := sampleInterface 208 dimensionInterface.word = unsafe.Pointer(&dimension[0]) 209 dimensionObj := castEmptyInterface(uintptr(unsafe.Pointer(&dimensionInterface))) 210 elem := monoid[dimensionObj] 211 if elem == nil { 212 elem = createElem() 213 monoid[copyDimension(dimensionInterface, dimension)] = elem 214 } 215 return elem 216 } 217 218 func copyDimension(dimensionInterface emptyInterface, dimension []string) interface{} { 219 copied := make([]string, len(dimension)) 220 for i, elem := range dimension { 221 copyOfElem := string(append([]byte(nil), elem...)) 222 copied[i] = copyOfElem 223 } 224 dimensionInterface.word = unsafe.Pointer(&copied[0]) 225 return *(*interface{})(unsafe.Pointer(&dimensionInterface)) 226 } 227 228 func castEmptyInterface(ptr uintptr) interface{} { 229 return *(*interface{})(unsafe.Pointer(ptr)) 230 } 231 232 // emptyInterface is the header for an interface{} value. 233 type emptyInterface struct { 234 typ unsafe.Pointer 235 word unsafe.Pointer 236 } 237 238 type sliceHeader struct { 239 Data unsafe.Pointer 240 Len int 241 Cap int 242 }