github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/apps/gcvis/main.go (about)

     1  // gzvis is a tool to assist you visualising the operation of
     2  // the go runtime garbage collector.
     3  //
     4  // usage:
     5  //
     6  //     gcvis program [arguments]...
     7  package main
     8  
     9  import (
    10  	"bufio"
    11  	"fmt"
    12  	"html/template"
    13  	"io"
    14  	"log"
    15  	"net"
    16  	"net/http"
    17  	"os"
    18  	"os/exec"
    19  	"regexp"
    20  	"runtime"
    21  	"strings"
    22  	"sync"
    23  	"time"
    24  )
    25  
    26  func startSubprocess(w io.Writer) {
    27  	env := os.Environ()
    28  	env = append(env, "GODEBUG=gctrace=1")
    29  	args := os.Args[1:]
    30  	cmd := exec.Command(args[0], args[1:]...)
    31  	cmd.Env = env
    32  	cmd.Stdin = os.Stdin
    33  	cmd.Stdout = os.Stdout
    34  	cmd.Stderr = w
    35  
    36  	if err := cmd.Run(); err != nil {
    37  		log.Fatal(err)
    38  	}
    39  }
    40  
    41  type gctrace struct {
    42  	NumGC       int
    43  	Nproc       int
    44  	t1          int
    45  	t2          int
    46  	t3          int
    47  	t4          int
    48  	Heap0       int // heap size before, in megabytes
    49  	Heap1       int // heap size after, in megabytes
    50  	Obj         int
    51  	NMalloc     int
    52  	NFree       int
    53  	NSpan       int
    54  	NBGSweep    int
    55  	NPauseSweep int
    56  	NHandoff    int
    57  	NHandoffCnt int
    58  	NSteal      int
    59  	NStealCnt   int
    60  	NProcYield  int
    61  	NOsYield    int
    62  	NSleep      int
    63  }
    64  
    65  type scvgtrace struct {
    66  	inuse    int
    67  	idle     int
    68  	sys      int
    69  	released int
    70  	consumed int
    71  }
    72  
    73  type graphPoints [2]int
    74  
    75  var heapuse, scvginuse, scvgidle, scvgsys, scvgreleased, scvgconsumed []graphPoints
    76  
    77  var mu sync.RWMutex
    78  
    79  func index(w http.ResponseWriter, req *http.Request) {
    80  	mu.RLock()
    81  	defer mu.RUnlock()
    82  	visTmpl.Execute(w, struct {
    83  		HeapUse, ScvgInuse, ScvgIdle, ScvgSys, ScvgReleased, ScvgConsumed []graphPoints
    84  		Title                                                             string
    85  	}{
    86  		HeapUse:      heapuse,
    87  		ScvgInuse:    scvginuse,
    88  		ScvgIdle:     scvgidle,
    89  		ScvgSys:      scvgsys,
    90  		ScvgReleased: scvgreleased,
    91  		ScvgConsumed: scvgconsumed,
    92  		Title:        strings.Join(os.Args[1:], " "),
    93  	})
    94  
    95  }
    96  
    97  var visTmpl = template.Must(template.New("vis").Parse(`
    98  <html>
    99  <head>
   100  <title>gcvis - {{ .Title }}</title>
   101  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
   102  <script src="//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.min.js"></script>
   103  <script src="//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.time.min.js"></script>
   104  <script src="//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.selection.min.js"></script>
   105  
   106  <script type="text/javascript">
   107  
   108  	var data = [
   109      		{ label: "gc.heapinuse", data: {{ .HeapUse }} },
   110      		{ label: "scvg.inuse", data: {{ .ScvgInuse }} },
   111      		{ label: "scvg.idle", data: {{ .ScvgIdle }} },
   112      		{ label: "scvg.sys", data: {{ .ScvgSys }} },
   113      		{ label: "scvg.released", data: {{ .ScvgReleased }} },
   114      		{ label: "scvg.consumed", data: {{ .ScvgConsumed }} },
   115  	];
   116  
   117  	var options = {
   118  		xaxis: {
   119  			mode: "time",
   120  			timeformat: "%I:%M:%S "
   121  		},
   122  		selection: {
   123  			mode: "x"
   124  		},
   125  	};
   126  
   127  	$(document).ready(function() {
   128  
   129  	var plot = $.plot("#placeholder", data, options);
   130  
   131  	var overview = $.plot("#overview", data, {
   132  		legend: { show: false},
   133  		series: {
   134  			lines: {
   135  				show: true,
   136  				lineWidth: 1
   137  			},
   138  			shadowSize: 0
   139  		},
   140  		xaxis: {
   141  			ticks: [],
   142  			mode: "time"
   143  		},
   144  		yaxis: {
   145  			ticks: [],
   146  			min: 0,
   147  			autoscaleMargin: 0.1
   148  		},
   149  		selection: {
   150  			mode: "x"
   151  		}
   152  	});
   153  
   154  	// now connect the two
   155  	$("#placeholder").bind("plotselected", function (event, ranges) {
   156  
   157  		// do the zooming
   158  		$.each(plot.getXAxes(), function(_, axis) {
   159  			var opts = axis.options;
   160  			opts.min = ranges.xaxis.from;
   161  			opts.max = ranges.xaxis.to;
   162  		});
   163  		plot.setupGrid();
   164  		plot.draw();
   165  		plot.clearSelection();
   166  
   167  		// don't fire event on the overview to prevent eternal loop
   168  
   169  		overview.setSelection(ranges, true);
   170  	});
   171  
   172  	$("#overview").bind("plotselected", function (event, ranges) {
   173  		plot.setSelection(ranges);
   174  	});
   175  	
   176  	});
   177  </script>
   178  <style>
   179  #content {
   180  	margin: 0 auto;
   181  	padding: 10px;
   182  }
   183  
   184  .demo-container {
   185  	box-sizing: border-box;
   186  	width: 1200px;
   187  	height: 450px;
   188  	padding: 20px 15px 15px 15px;
   189  	margin: 15px auto 30px auto;
   190  	border: 1px solid #ddd;
   191  	background: #fff;
   192  	background: linear-gradient(#f6f6f6 0, #fff 50px);
   193  	background: -o-linear-gradient(#f6f6f6 0, #fff 50px);
   194  	background: -ms-linear-gradient(#f6f6f6 0, #fff 50px);
   195  	background: -moz-linear-gradient(#f6f6f6 0, #fff 50px);
   196  	background: -webkit-linear-gradient(#f6f6f6 0, #fff 50px);
   197  	box-shadow: 0 3px 10px rgba(0,0,0,0.15);
   198  	-o-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
   199  	-ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
   200  	-moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
   201  	-webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
   202  }
   203  
   204  .demo-placeholder {
   205  	width: 100%;
   206  	height: 100%;
   207  	font-size: 14px;
   208  	line-height: 1.2em;
   209  }
   210  </style>
   211  </head>
   212  <body>
   213  <pre>{{ .Title }}</pre>
   214  <div id="content">
   215  
   216  	<div class="demo-container">
   217  		<div id="placeholder" class="demo-placeholder"></div>
   218  	</div>
   219  
   220  	<div class="demo-container" style="height:150px;">
   221  		<div id="overview" class="demo-placeholder"></div>
   222  	</div>
   223  
   224  	<p>The smaller plot is linked to the main plot, so it acts as an overview. Try dragging a selection on either plot, and watch the behavior of the other.</p>
   225  
   226  </div>
   227  
   228  <pre><b>Legend</b>
   229  
   230  gc.heapinuse: heap in use after gc
   231  scvg.inuse: virtual memory considered in use by the scavenger
   232  scvg.idle: virtual memory considered unused by the scavenger
   233  scvg.sys: virtual memory requested from the operating system (should aproximate VSS)
   234  scvg.released: virtual memory returned to the operating system by the scavenger
   235  scvg.consumed: virtual memory in use (should roughly match process RSS)
   236  </pre>
   237  </body>
   238  </html>
   239  `))
   240  
   241  var (
   242  	gcre   = regexp.MustCompile(`gc\d+\(\d+\): \d+\+\d+\+\d+\+\d+ us, \d+ -> \d+ MB, \d+ \(\d+-\d+\) objects, \d+\/\d+\/\d+ sweeps, \d+\(\d+\) handoff, \d+\(\d+\) steal, \d+\/\d+\/\d+ yields`)
   243  	scvgre = regexp.MustCompile(`scvg\d+: inuse: \d+, idle: \d+, sys: \d+, released: \d+, consumed: \d+ \(MB\)`)
   244  )
   245  
   246  func startParser(r io.Reader, gcc chan *gctrace, scvgc chan *scvgtrace) {
   247  	sc := bufio.NewScanner(r)
   248  	for sc.Scan() {
   249  		line := sc.Text()
   250  		// try to parse as a gc trace line
   251  		if gcre.MatchString(line) {
   252  			var gc gctrace
   253  			_, err := fmt.Sscanf(line, "gc%d(%d): %d+%d+%d+%d us, %d -> %d MB, %d (%d-%d) objects, %d/%d/%d sweeps, %d(%d) handoff, %d(%d) steal, %d/%d/%d yields\n",
   254  				&gc.NumGC, &gc.Nproc, &gc.t1, &gc.t2, &gc.t3, &gc.t4, &gc.Heap0, &gc.Heap1, &gc.Obj, &gc.NMalloc, &gc.NFree,
   255  				&gc.NSpan, &gc.NBGSweep, &gc.NPauseSweep, &gc.NHandoff, &gc.NHandoffCnt, &gc.NSteal, &gc.NStealCnt, &gc.NProcYield, &gc.NOsYield, &gc.NSleep)
   256  			if err != nil {
   257  				log.Printf("corrupt gctrace: %v: %s", err, line)
   258  				continue
   259  			}
   260  			gcc <- &gc
   261  			continue
   262  		}
   263  		// try to parse as a scavenger line
   264  		if scvgre.MatchString(line) {
   265  			var scvg scvgtrace
   266  			var n int
   267  			_, err := fmt.Sscanf(line, "scvg%d: inuse: %d, idle: %d, sys: %d, released: %d, consumed: %d (MB)\n",
   268  				&n, &scvg.inuse, &scvg.idle, &scvg.sys, &scvg.released, &scvg.consumed)
   269  			if err != nil {
   270  				log.Printf("corrupt scvgtrace: %v: %s", err, line)
   271  				continue
   272  			}
   273  			scvgc <- &scvg
   274  			continue
   275  		}
   276  		// nope ? oh well, print it out
   277  		fmt.Println(line)
   278  	}
   279  	if err := sc.Err(); err != nil {
   280  		log.Fatal(err)
   281  	}
   282  }
   283  
   284  // startBrowser tries to open the URL in a browser, and returns
   285  // whether it succeed.
   286  func startBrowser(url string) bool {
   287  	// try to start the browser
   288  	var args []string
   289  	switch runtime.GOOS {
   290  	case "darwin":
   291  		args = []string{"open"}
   292  	case "windows":
   293  		args = []string{"cmd", "/c", "start"}
   294  	default:
   295  		args = []string{"xdg-open"}
   296  	}
   297  	cmd := exec.Command(args[0], append(args[1:], url)...)
   298  	return cmd.Start() == nil
   299  }
   300  
   301  func main() {
   302  	if len(os.Args) < 2 {
   303  		log.Fatalf("usage: %s command <args>...", os.Args[0])
   304  	}
   305  	pr, pw, _ := os.Pipe()
   306  	gc := make(chan *gctrace, 1)
   307  	scvg := make(chan *scvgtrace, 1)
   308  
   309  	go startSubprocess(pw)
   310  	go startParser(pr, gc, scvg)
   311  
   312  	l, err := net.Listen("tcp4", "127.0.0.1:0")
   313  	if err != nil {
   314  		log.Fatal(err)
   315  	}
   316  	http.HandleFunc("/", index)
   317  	go http.Serve(l, nil)
   318  
   319  	url := fmt.Sprintf("http://%s/", l.Addr())
   320  	log.Printf("opening browser window, if this fails, navigate to %s", url)
   321  	startBrowser(url)
   322  
   323  	for {
   324  		select {
   325  		case gc := <-gc:
   326  			mu.Lock()
   327  			ts := int(time.Now().UnixNano() / 1e6)
   328  			heapuse = append(heapuse, graphPoints{ts, gc.Heap1})
   329  			mu.Unlock()
   330  		case scvg := <-scvg:
   331  			mu.Lock()
   332  			ts := int(time.Now().UnixNano() / 1e6)
   333  			scvginuse = append(scvginuse, graphPoints{ts, scvg.inuse})
   334  			scvgidle = append(scvgidle, graphPoints{ts, scvg.idle})
   335  			scvgsys = append(scvgsys, graphPoints{ts, scvg.sys})
   336  			scvgreleased = append(scvgreleased, graphPoints{ts, scvg.released})
   337  			scvgconsumed = append(scvgconsumed, graphPoints{ts, scvg.consumed})
   338  			mu.Unlock()
   339  		}
   340  	}
   341  }