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  )