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 }