github.com/square/finch@v0.0.0-20240412205204-6530c03e2b96/finch.go (about) 1 // Copyright 2024 Block, Inc. 2 3 package finch 4 5 import ( 6 "database/sql" 7 "fmt" 8 "io" 9 "log" 10 "net/http" 11 "os" 12 "path" 13 "regexp" 14 "runtime" 15 "strconv" 16 "strings" 17 "time" 18 ) 19 20 const ( 21 VERSION = "1.0.0" 22 23 DEFAULT_SERVER_PORT = "33075" 24 25 COPY_NUMBER = `/*!copy-number*/` 26 27 NOOP_COLUMN = "_" 28 29 ROWS = 100000 30 ) 31 32 type RunLevel struct { 33 Stage uint 34 StageName string 35 ExecGroup uint 36 ExecGroupName string 37 ClientGroup uint 38 Client uint 39 Trx uint 40 TrxName string 41 Query uint64 42 } 43 44 func (s RunLevel) String() string { 45 return fmt.Sprintf("%d(%s)/e%d(%s)/g%d/c%d/t%d(%s)/q%d", 46 s.Stage, s.StageName, s.ExecGroup, s.ExecGroupName, s.ClientGroup, s.Client, s.Trx, s.TrxName, s.Query) 47 } 48 49 func (s RunLevel) ClientId() string { 50 return fmt.Sprintf("%d(%s)/e%d(%s)/g%d/c%d", 51 s.Stage, s.StageName, s.ExecGroup, s.ExecGroupName, s.ClientGroup, s.Client) 52 } 53 54 const ( 55 SCOPE_GLOBAL = "global" 56 SCOPE_STAGE = "stage" 57 SCOPE_WORKLOAD = "workload" 58 SCOPE_EXEC_GROUP = "exec-group" 59 SCOPE_CLIENT_GROUP = "client-group" 60 SCOPE_CLIENT = "client" 61 SCOPE_ITER = "iter" 62 SCOPE_TRX = "trx" 63 SCOPE_STATEMENT = "statement" 64 SCOPE_ROW = "row" // special: INSERT INTO t VALUES (@d), (@d), ... 65 SCOPE_VALUE = "value" 66 ) 67 68 func (rl RunLevel) array() []uint { 69 return []uint{ 70 uint(rl.Query), // 0 71 rl.Trx, // 1 72 0, // 2 ITER (data scope) 73 rl.Client, // 3 74 rl.ClientGroup, // 4 75 rl.ExecGroup, // 5 76 1, // 6 WORKLOAD not counted 77 rl.Stage, // 7 STAGE 78 1, // 8 GLOBAL 79 } 80 } 81 82 func (rl RunLevel) GreaterThan(prev RunLevel, s string) bool { 83 switch s { 84 case SCOPE_VALUE: 85 return true // all value scoped @d are unique 86 case SCOPE_ROW: 87 s = SCOPE_STATEMENT // row scoped @d per statement 88 case SCOPE_ITER: 89 s = SCOPE_CLIENT // iter scoped @d per client 90 } 91 n := RunLevelNumber(s) 92 now := rl.array() 93 before := prev.array() 94 for i := n; i < uint(len(now)); i++ { 95 if now[i] > before[i] { 96 return true 97 } 98 } 99 return false 100 } 101 102 var runlevelNumber = map[string]uint{ 103 SCOPE_VALUE: 0, 104 SCOPE_ROW: 0, 105 SCOPE_STATEMENT: 0, // data.STATEMENT | These four must 106 SCOPE_TRX: 1, // data.TRX | match, else 107 SCOPE_ITER: 2, // data.ITER | NewScopedGenerator 108 SCOPE_CLIENT: 3, // data.CONN | needs to be updated 109 SCOPE_CLIENT_GROUP: 4, 110 SCOPE_EXEC_GROUP: 5, 111 SCOPE_WORKLOAD: 6, 112 SCOPE_STAGE: 7, 113 SCOPE_GLOBAL: 8, 114 } 115 116 func RunLevelNumber(s string) uint { 117 n, ok := runlevelNumber[s] 118 if !ok { 119 panic("invalid run level: " + s) 120 } 121 return n 122 } 123 124 func RunLevelIsValid(s string) bool { 125 _, ok := runlevelNumber[s] 126 return ok 127 } 128 129 var ( 130 CPUProfile io.Writer // --cpu-profile FILE 131 Debugging = false 132 debugLog = log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds) 133 ) 134 135 func Debug(msg string, v ...interface{}) { 136 if !Debugging { 137 return 138 } 139 _, file, line, _ := runtime.Caller(1) 140 msg = fmt.Sprintf("DEBUG %s:%d %s", path.Base(file), line, msg) 141 debugLog.Printf(msg, v...) 142 } 143 144 var MakeHTTPClient func() *http.Client = func() *http.Client { 145 tr := &http.Transport{ 146 MaxIdleConns: 1, 147 IdleConnTimeout: 1 * time.Hour, 148 } 149 return &http.Client{Transport: tr} 150 } 151 152 func Bool(s string) bool { 153 v := strings.ToLower(s) 154 return v == "true" || v == "yes" || v == "on" || v == "aye" 155 } 156 157 func BoolString(b bool) string { 158 if b { 159 return "true" 160 } 161 return "false" 162 } 163 164 var portRe = regexp.MustCompile(`:\d+$`) 165 166 // WithPort return s:p if s doesn't have port suffix p. 167 // p must not have a colon prefix. 168 func WithPort(s, p string) string { 169 if portRe.MatchString(s) { 170 return s 171 } 172 return s + ":" + p 173 } 174 175 // Uint returns s as a unit presuming s has already been validated. 176 func Uint(s string) uint { 177 i, _ := strconv.ParseUint(s, 10, 64) 178 return uint(i) 179 } 180 181 var SystemParams = map[string]string{} 182 183 func init() { 184 SystemParams["CPU_CORES"] = strconv.Itoa(runtime.NumCPU()) 185 } 186 187 const ( 188 Eabort = 0 // default (set to clear default flags) 189 Ereconnect = 1 << iota // reconnect, next iter 190 Econtinue // don't reconnect, continue next iter 191 Esilent // don't repot error or reconnect 192 Erollback // execute ROLLBACK if in trx 193 ) 194 195 var MySQLErrorHandling = map[uint16]byte{ 196 1046: Eabort, // no database selected 197 1062: Eabort, // duplicate key 198 1064: Eabort, // You have an error in your SQL syntax 199 1146: Eabort, // table doesn't exist 200 1205: Erollback | Econtinue, // lock wait timeout; no automatic rollback (innodb_rollback_on_timeout=OFF by default) 201 1213: Econtinue, // deadlock; automatic rollback 202 1290: Erollback | Econtinue, // read-only (server is running with the --read-only option so it cannot execute this statement) 203 1317: Econtinue, // query killed (Query execution was interrupted) 204 1836: Erollback | Econtinue, // read-only (Running in read-only mode) 205 } 206 207 var ModifyDB func(*sql.DB, RunLevel)