lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/cmd/gmigrate/gmigrate.go (about) 1 // Copyright (C) 2017-2021 Nexedi SA and Contributors. 2 // Kirill Smelkov <kirr@nexedi.com> 3 // 4 // This program is free software: you can Use, Study, Modify and Redistribute 5 // it under the terms of the GNU General Public License version 3, or (at your 6 // option) any later version, as published by the Free Software Foundation. 7 // 8 // You can also Link and Combine this program with other software covered by 9 // the terms of any of the Free Software licenses or any of the Open Source 10 // Initiative approved licenses and Convey the resulting work. Corresponding 11 // source of such a combination shall include the source code for all other 12 // software used. 13 // 14 // This program is distributed WITHOUT ANY WARRANTY; without even the implied 15 // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 // 17 // See COPYING file for full licensing terms. 18 // See https://www.nexedi.com/licensing for rationale and options. 19 20 // gmigrate - show number of times G migrates to another M (OS thread). 21 // 22 // usage: `go tool trace -d <trace.out> |gmigrate` 23 package main 24 25 import ( 26 "bufio" 27 "errors" 28 "fmt" 29 "io" 30 "log" 31 "os" 32 "regexp" 33 "sort" 34 "strconv" 35 "strings" 36 ) 37 38 // we analyze input stream and take notices on ProcStart and GoStart events. 39 // when a ProcStart event comes, e.g. 40 // 41 // 9782014 ProcStart p=2 g=0 off=133138 thread=5 42 // 43 // we remember that a P is currently running on an M (=thread). 44 // 45 // when a GoStart event comes, e.g. 46 // 47 // 9782310 GoStart p=2 g=33 off=133142 g=33 seq=0 48 // 49 // we see that a G is using P to run, and by using previously noted P->M 50 // relation, conclude G->M relation. 51 // 52 // Then for every G noticed we record how much it changes its M. 53 54 type procStart struct { 55 p int 56 m int // =thread 57 } 58 59 type goStart struct { 60 g int 61 p int 62 } 63 64 // information about a G 65 type gInfo struct { 66 g int 67 m int // last time was running on this M 68 nmigrate int // how much times migrated between different Ms 69 } 70 71 func main() { 72 var pm = map[int]int{} // p -> m 73 var gg = map[int]*gInfo{} // g -> (m, #migrate) 74 75 in := bufio.NewReader(os.Stdin) 76 77 tstart, tend, tprev := -1, -1, -1 78 for lineno := 1; ; lineno++ { 79 bad := func(err error) { 80 log.Fatalf("%d: %v", lineno, err) 81 } 82 badf := func(format string, argv ...interface{}) { 83 bad(fmt.Errorf(format, argv...)) 84 } 85 86 l, err := in.ReadString('\n') 87 if err != nil { 88 if err == io.EOF { 89 break 90 } 91 bad(err) 92 } 93 l = l[:len(l)-1] // strip trailing '\n' 94 95 t, evname, args, err := parseLineHeader(l) 96 if err != nil { 97 bad(err) 98 } 99 100 if t < tprev { 101 badf("time monotonity broken") 102 } 103 tprev = t 104 105 if tstart == -1 { 106 tstart = t 107 } else { 108 tend = t 109 } 110 111 switch evname { 112 case "ProcStart": 113 pstart, err := parseProcStart(args) 114 if err != nil { 115 bad(err) 116 } 117 118 pm[pstart.p] = pstart.m 119 120 case "GoStart": 121 gstart, err := parseGoStart(args) 122 if err != nil { 123 bad(err) 124 } 125 126 m, ok := pm[gstart.p] 127 if !ok { 128 badf("G%d start on P%d, but no matching previous P%d start", 129 gstart.g, gstart.p, gstart.p) 130 } 131 132 g, seen := gg[gstart.g] 133 if !seen { 134 gg[gstart.g] = &gInfo{g: gstart.g, m: m, nmigrate: 0} 135 break 136 } 137 138 if g.m != m { 139 g.m = m 140 g.nmigrate++ 141 } 142 } 143 } 144 145 // all information collected - analyze 146 gv := make([]*gInfo, 0, len(gg)) 147 for _, g := range gg { 148 gv = append(gv, g) 149 } 150 151 // order: (nmigrate, g)↓ 152 sort.Slice(gv, func(i, j int) bool { 153 ni, nj := gv[i].nmigrate, gv[j].nmigrate 154 return (nj < ni) || (nj == ni && gv[j].g < gv[i].g) // reverse 155 }) 156 157 fmt.Printf("G\tN(migrate)\tmigrate/s\n") 158 fmt.Printf("-\t----------\t---------\n") 159 for _, g := range gv { 160 fmt.Printf("G%d\t%8d\t%8.1f\n", g.g, g.nmigrate, 161 float64(g.nmigrate) / (float64(tend - tstart) * 1E-9)) 162 } 163 } 164 165 // "9782014 ProcStart p=2 g=0 off=133138 thread=5" 166 // -> 167 // 9782014 "ProcStart" "p=2 g=0 off=133138 thread=5" 168 func parseLineHeader(l string) (t int, event, args string, err error) { 169 sp := strings.IndexByte(l, ' ') 170 if sp < 0 { 171 return 0, "", "", fmt.Errorf("parse: invalid timestamp") 172 } 173 t, err = strconv.Atoi(l[:sp]) 174 if err != nil { 175 return 0, "", "", fmt.Errorf("parse: invalid timestamp") 176 } 177 178 l = l[sp+1:] 179 sp = strings.IndexByte(l, ' ') 180 if sp < 0 { 181 return 0, "", "", fmt.Errorf("parse: invalid event name") 182 } 183 184 return t, l[:sp], l[sp+1:], nil 185 } 186 187 // ex: 9782014 ProcStart p=2 g=0 off=133138 thread=5 188 var ( 189 pStartArgvRe = regexp.MustCompile("^p=([^ ]+) g=[^ ]+ off=[^ ]+ thread=([^ ]+)$") 190 pStartArgvErr = errors.New("ProcStart: argv invalid") 191 ) 192 193 func parseProcStart(args string) (procStart, error) { 194 argv := pStartArgvRe.FindStringSubmatch(args) 195 if argv == nil { 196 return procStart{}, pStartArgvErr 197 } 198 199 var pstart procStart 200 var err error 201 pstart.p, err = strconv.Atoi(argv[1]) 202 if err != nil { 203 return procStart{}, pStartArgvErr 204 } 205 206 pstart.m, err = strconv.Atoi(argv[2]) 207 if err != nil { 208 return procStart{}, pStartArgvErr 209 } 210 211 return pstart, nil 212 } 213 214 // ex: 9782310 GoStart p=2 g=33 off=133142 g=33 seq=0 215 var ( 216 gStartArgvRe = regexp.MustCompile("^p=([^ ]+) g=([^ ]+) off=[^ ]+ g=([^ ]+) seq=[^ ]+$") 217 gStartArgvErr = errors.New("GoStart: argv invalid") 218 ) 219 220 func parseGoStart(args string) (goStart, error) { 221 argv := gStartArgvRe.FindStringSubmatch(args) 222 if argv == nil { 223 return goStart{}, gStartArgvErr 224 } 225 226 var gstart goStart 227 var err error 228 gstart.p, err = strconv.Atoi(argv[1]) 229 if err != nil { 230 return goStart{}, gStartArgvErr 231 } 232 233 gstart.g, err = strconv.Atoi(argv[2]) 234 if err != nil { 235 return goStart{}, gStartArgvErr 236 } 237 238 // double-check g is the same 239 g2, _ := strconv.Atoi(argv[3]) 240 if g2 != gstart.g { 241 log.Print("found GoStart with different g") 242 return goStart{}, gStartArgvErr 243 } 244 245 return gstart, nil 246 }