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  }