github.com/square/finch@v0.0.0-20240412205204-6530c03e2b96/data/scope.go (about) 1 // Copyright 2024 Block, Inc. 2 3 package data 4 5 import ( 6 "fmt" 7 "sync" 8 9 "github.com/square/finch" 10 ) 11 12 // Id identifies the scope and copy number of a @d in a ScopedGenerator. 13 // It's only for debugging; it's not used to determine anything. The CopyNo 14 // is especially important because ScopedGenerator are copied from real 15 // generators (from the pkg Make func), and they must be copied at the correct 16 // scope/run level, else @d will generate values at the wrong time/place. 17 type Id struct { 18 finch.RunLevel 19 Scope string // finch.SCOPE_* 20 Type string // str-fill-az 21 DataKey string // @d 22 CopyNo uint // 1..N 23 } 24 25 func (id Id) String() string { 26 return fmt.Sprintf("%s %s(%d)/%s %s", id.RunLevel, id.DataKey, id.CopyNo, id.Scope, id.Type) 27 } 28 29 // Key represents a data key (@d). These are parsed in trx/trx.go as part of a 30 // Scope struct (below) because all keys are scoped and copied baseed on scope. 31 // Therefore, Key.Generator is the original data generator, also created in 32 // trx/trx.go (search that file for data.Make). In workload/workload.go, 33 // Scope.Copy is called to create a scoped key: copy of the original data generator 34 // based on the key scope configured in the stage file that trx.go passed to 35 // data.Make earlier. The original data generator is never called; it's only 36 // used to create scoped keys. 37 type Key struct { 38 Name string // @d 39 Trx string // trx (file) name 40 Line uint // line number in trx file 41 Statement uint // statement number (1-indexed) in trx file 42 Column int // -1: none, 0: insert ID, >=1: column 43 Scope string // finch.SCOPE_* 44 Generator Generator `deep:"-"` // original (copy 0) from which others are copied 45 } 46 47 func (k Key) String() string { 48 s := fmt.Sprintf("%s line %d statement %d", k.Trx, k.Line, k.Statement) 49 if k.Column >= 0 { 50 s += fmt.Sprintf(" column %d", k.Column) 51 } 52 return s 53 } 54 55 // Scope tracks each Key to create a scoped key copy (in Scope.Copy). Only one 56 // Scope is created in trx/trx.go for all data keys in all trx files. Then 57 // workload/workload.go calls Scope.Copy to create scoped keys for however the 58 // trx are assigned to exec groups, client groups, and clients (details that 59 // workload knows and handles). 60 type Scope struct { 61 Keys map[string]Key // original generators created in trx.Load() 62 CopyOf map[string]*ScopedGenerator `deep:"-"` // current scope (copy) of @d 63 CopiedAt map[string]finch.RunLevel // that created ^ 64 CopyCount map[string]uint `deep:"-"` 65 noop *ScopedGenerator 66 } 67 68 func NewScope() *Scope { 69 return &Scope{ 70 Keys: map[string]Key{}, 71 CopyOf: map[string]*ScopedGenerator{}, 72 CopiedAt: map[string]finch.RunLevel{}, 73 CopyCount: map[string]uint{}, 74 } 75 } 76 77 func (s *Scope) Copy(keyName string, rl finch.RunLevel) *ScopedGenerator { 78 // Don't copy @PREV because the previous generator will return multiple values 79 if keyName == "@PREV" { 80 return nil 81 } 82 83 k, ok := s.Keys[keyName] 84 if !ok { 85 panic("key not loaded: " + keyName) // panic because input already validated 86 } 87 88 if k.Name == finch.NOOP_COLUMN { 89 // Only need 1 global no-op generator 90 if s.noop == nil { 91 s.noop = NewScopedGenerator(Id{Type: Noop.Name(), Scope: finch.SCOPE_GLOBAL}, Noop) 92 } 93 return s.noop 94 } 95 96 // If scope wasn't explicitly configured and generator didn't set its default scope, 97 // then set default statement scope to ensure Id.Scope is always set. 98 if k.Scope == "" { 99 switch k.Generator.Name() { 100 case "column": 101 k.Scope = finch.SCOPE_TRX 102 default: 103 k.Scope = finch.SCOPE_STATEMENT 104 } 105 s.Keys[keyName] = k 106 finch.Debug("%s: defaults to %s scope", k.Name, k.Scope) 107 } 108 109 prev := s.CopiedAt[keyName] // last time we saw this @d 110 if rl.GreaterThan(prev, k.Scope) { 111 s.CopyCount[keyName] += 1 112 id := Id{ 113 RunLevel: rl, 114 Scope: k.Scope, 115 Type: k.Generator.Name(), 116 DataKey: keyName, 117 CopyNo: s.CopyCount[keyName], 118 } 119 s.CopyOf[keyName] = NewScopedGenerator(id, k.Generator.Copy()) 120 s.CopiedAt[k.Name] = rl 121 } 122 return s.CopyOf[keyName] 123 } 124 125 func (s *Scope) Reset() { 126 for keyName, k := range s.Keys { 127 if k.Scope == finch.SCOPE_STAGE || k.Scope == finch.SCOPE_GLOBAL { 128 continue 129 } 130 finch.Debug("delete %s (scope: %s)", keyName, k.Scope) 131 delete(s.Keys, keyName) 132 delete(s.CopyOf, keyName) 133 delete(s.CopiedAt, keyName) 134 } 135 } 136 137 // -------------------------------------------------------------------------- 138 139 // ScopedGenerator wraps a real Generator to handle scoped value generation 140 // when called by a Client. The factory Make func in this pkg returns real 141 // generators that are scope-agnostic. Then workload.Allocator.Clients calls 142 // Scope.Copy to scope each real generator by wrapping it in a ScopedGenerator. 143 // Then the workload allocator assigns either ScopedGenerator.Values (normal 144 // case) or ScopedGenerator.Call to each @d input--the Client doesn't know or 145 // care which--to handle either scoped value generator or explicit calls like 146 // @d(). 147 type ScopedGenerator struct { 148 id Id // identify this copy of the real Generator for debugging 149 g Generator // real Generator: 150 sno byte // scope number in RunCount (if singleClient == true) 151 last RunCount // last time value was generated 152 vals []interface{} // last value 153 singleClient bool // Single client scopes (typical): STATEMENT, TRX, ITER, CILENT 154 oneTime bool // One time scopes: STAGE and GLOBAL 155 cgMux *sync.RWMutex // Multi client: client-group, exec-group, workload 156 cgIter map[uint]uint // last client iter 157 cgVals map[uint][]interface{} // last client value 158 } 159 160 var _ Generator = &ScopedGenerator{} 161 162 func NewScopedGenerator(id Id, g Generator) *ScopedGenerator { 163 s := &ScopedGenerator{ 164 id: id, 165 g: g, // real Generator 166 } 167 168 n := finch.RunLevelNumber(id.Scope) 169 if id.Scope == finch.SCOPE_VALUE { 170 // No special handling 171 } else if n <= finch.RunLevelNumber(finch.SCOPE_CLIENT) { 172 // Single client scopes (most common) 173 s.singleClient = true 174 s.sno = byte(n) // these match, see finch.runlevelNumber comment 175 } else if n <= finch.RunLevelNumber(finch.SCOPE_WORKLOAD) { 176 // Multi client scopes: iter = each <client, iter> 177 s.cgMux = &sync.RWMutex{} 178 s.cgIter = map[uint]uint{} 179 s.cgVals = map[uint][]interface{}{} 180 } else if n <= finch.RunLevelNumber(finch.SCOPE_GLOBAL) { 181 // One time scopes 182 s.oneTime = true 183 } else { 184 panic("invalid scope: " + id.Scope) 185 } 186 187 return s 188 } 189 190 func (s *ScopedGenerator) Name() string { return s.g.Name() } 191 func (s *ScopedGenerator) Id() Id { return s.id } 192 func (s *ScopedGenerator) Format() (uint, string) { return s.g.Format() } 193 func (s *ScopedGenerator) Scan(any interface{}) error { return s.g.Scan(any) } 194 195 func (s *ScopedGenerator) Copy() Generator { 196 panic("cannot copy ScopedGenerator") // only real Generator is copied 197 } 198 199 func (s *ScopedGenerator) Call(cnt RunCount) []interface{} { 200 /* 201 This func called in performance critical path: Client.Run. 202 Don't debug or call anything slow/superfluous. 203 */ 204 if s.cgMux != nil { // multi client 205 clientNo := cnt[CLIENT] 206 v := s.g.Values(cnt) 207 s.cgMux.Lock() 208 s.cgIter[clientNo] = cnt[ITER] 209 s.cgVals[clientNo] = v 210 s.cgMux.Unlock() 211 return v 212 } 213 s.last[s.sno] = cnt[s.sno] // save last run counter value 214 s.vals = s.g.Values(cnt) // generate new data value 215 return s.vals 216 } 217 218 func (s *ScopedGenerator) Values(cnt RunCount) []interface{} { 219 /* 220 This func called in performance critical path: Client.Run. 221 Don't debug or call anything slow/superfluous. 222 */ 223 224 // Typical case: single client scopes 225 // Generate a new data value (g.g.Values) when the run counter 226 // for this scope has incremented (is greater than last value) 227 if s.singleClient { 228 if cnt[s.sno] > s.last[s.sno] { 229 return s.Call(cnt) // new value 230 } 231 return s.vals // old value (scope hasn't changed) 232 } 233 234 // Multi client scopes: CLIENT_GROUP, EXEC_GROUP, WORKLOAD 235 if s.cgMux != nil { 236 clientNo := cnt[CLIENT] 237 s.cgMux.RLock() 238 prevIter, ok := s.cgIter[clientNo] 239 if !ok || cnt[ITER] > prevIter { 240 s.cgMux.RUnlock() 241 return s.Call(cnt) 242 } 243 v := s.cgVals[clientNo] 244 s.cgMux.RUnlock() 245 return v 246 } 247 248 // One time scopes: STAGE and GLOBAL 249 if s.oneTime { 250 // @todo guard with mux 251 if s.vals != nil { 252 return s.Call(cnt) 253 } 254 return s.vals 255 } 256 257 // VALUE scope 258 return s.g.Values(cnt) 259 } 260 261 // RunCount counts execution (or changes) at each level in the order defined 262 // by the const below: STATEMENT and up the run levels. Each Client maintains 263 // a RunCount that is used by ScopedGenerator.Values to determine when it's 264 // time to generate a new value based on the scope of the @d. 265 type RunCount [8]uint 266 267 const ( 268 // Counters 269 STATEMENT byte = iota 270 TRX 271 ITER 272 CONN 273 // From Client.RunLevel in case a data.Generator wants to know 274 CLIENT 275 CLIENT_GROUP 276 EXEC_GROUP 277 STAGE 278 )