// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Minimum mutator utilization (MMU) graphing. // TODO: // // In worst window list, show break-down of GC utilization sources // (STW, assist, etc). Probably requires a different MutatorUtil // representation. // // When a window size is selected, show a second plot of the mutator // utilization distribution for that window size. // // Render plot progressively so rough outline is visible quickly even // for very complex MUTs. Start by computing just a few window sizes // and then add more window sizes. // // Consider using sampling to compute an approximate MUT. This would // work by sampling the mutator utilization at randomly selected // points in time in the trace to build an empirical distribution. We // could potentially put confidence intervals on these estimates and // render this progressively as we refine the distributions. package main import ( "encoding/json" "fmt" "internal/trace" "log" "math" "net/http" "strconv" "strings" "sync" "time" ) func init() { http.HandleFunc("/mmu", httpMMU) http.HandleFunc("/mmuPlot", httpMMUPlot) http.HandleFunc("/mmuDetails", httpMMUDetails) } var utilFlagNames = map[string]trace.UtilFlags{ "perProc": trace.UtilPerProc, "stw": trace.UtilSTW, "background": trace.UtilBackground, "assist": trace.UtilAssist, "sweep": trace.UtilSweep, } type mmuCacheEntry struct { init sync.Once util [][]trace.MutatorUtil mmuCurve *trace.MMUCurve err error } var mmuCache struct { m map[trace.UtilFlags]*mmuCacheEntry lock sync.Mutex } func init() { mmuCache.m = make(map[trace.UtilFlags]*mmuCacheEntry) } func getMMUCurve(r *http.Request) ([][]trace.MutatorUtil, *trace.MMUCurve, error) { var flags trace.UtilFlags for _, flagStr := range strings.Split(r.FormValue("flags"), "|") { flags |= utilFlagNames[flagStr] } mmuCache.lock.Lock() c := mmuCache.m[flags] if c == nil { c = new(mmuCacheEntry) mmuCache.m[flags] = c } mmuCache.lock.Unlock() c.init.Do(func() { events, err := parseEvents() if err != nil { c.err = err } else { c.util = trace.MutatorUtilization(events, flags) c.mmuCurve = trace.NewMMUCurve(c.util) } }) return c.util, c.mmuCurve, c.err } // httpMMU serves the MMU plot page. func httpMMU(w http.ResponseWriter, r *http.Request) { http.ServeContent(w, r, "", time.Time{}, strings.NewReader(templMMU)) } // httpMMUPlot serves the JSON data for the MMU plot. func httpMMUPlot(w http.ResponseWriter, r *http.Request) { mu, mmuCurve, err := getMMUCurve(r) if err != nil { http.Error(w, fmt.Sprintf("failed to parse events: %v", err), http.StatusInternalServerError) return } var quantiles []float64 for _, flagStr := range strings.Split(r.FormValue("flags"), "|") { if flagStr == "mut" { quantiles = []float64{0, 1 - .999, 1 - .99, 1 - .95} break } } // Find a nice starting point for the plot. xMin := time.Second for xMin > 1 { if mmu := mmuCurve.MMU(xMin); mmu < 0.0001 { break } xMin /= 1000 } // Cover six orders of magnitude. xMax := xMin * 1e6 // But no more than the length of the trace. minEvent, maxEvent := mu[0][0].Time, mu[0][len(mu[0])-1].Time for _, mu1 := range mu[1:] { if mu1[0].Time < minEvent { minEvent = mu1[0].Time } if mu1[len(mu1)-1].Time > maxEvent { maxEvent = mu1[len(mu1)-1].Time } } if maxMax := time.Duration(maxEvent - minEvent); xMax > maxMax { xMax = maxMax } // Compute MMU curve. logMin, logMax := math.Log(float64(xMin)), math.Log(float64(xMax)) const samples = 100 plot := make([][]float64, samples) for i := 0; i < samples; i++ { window := time.Duration(math.Exp(float64(i)/(samples-1)*(logMax-logMin) + logMin)) if quantiles == nil { plot[i] = make([]float64, 2) plot[i][1] = mmuCurve.MMU(window) } else { plot[i] = make([]float64, 1+len(quantiles)) copy(plot[i][1:], mmuCurve.MUD(window, quantiles)) } plot[i][0] = float64(window) } // Create JSON response. err = json.NewEncoder(w).Encode(map[string]interface{}{"xMin": int64(xMin), "xMax": int64(xMax), "quantiles": quantiles, "curve": plot}) if err != nil { log.Printf("failed to serialize response: %v", err) return } } var templMMU = `
Loading plot...

View
?Consider whole system utilization. For example, if one of four procs is available to the mutator, mutator utilization will be 0.25. This is the standard definition of an MMU.
?Consider per-goroutine utilization. When even one goroutine is interrupted by GC, mutator utilization is 0.

Include
?Stop-the-world stops all goroutines simultaneously.
?Background workers are GC-specific goroutines. 25% of the CPU is dedicated to background workers during GC.
?Mark assists are performed by allocation to prevent the mutator from outpacing GC.
?Sweep reclaims unused memory between GCs. (Enabling this may be very slow.).

Display
?Display percentile mutator utilization in addition to minimum. E.g., p99 MU drops the worst 1% of windows.

Select a point for details.
` // httpMMUDetails serves details of an MMU graph at a particular window. func httpMMUDetails(w http.ResponseWriter, r *http.Request) { _, mmuCurve, err := getMMUCurve(r) if err != nil { http.Error(w, fmt.Sprintf("failed to parse events: %v", err), http.StatusInternalServerError) return } windowStr := r.FormValue("window") window, err := strconv.ParseUint(windowStr, 10, 64) if err != nil { http.Error(w, fmt.Sprintf("failed to parse window parameter %q: %v", windowStr, err), http.StatusBadRequest) return } worst := mmuCurve.Examples(time.Duration(window), 10) // Construct a link for each window. var links []linkedUtilWindow for _, ui := range worst { links = append(links, newLinkedUtilWindow(ui, time.Duration(window))) } err = json.NewEncoder(w).Encode(links) if err != nil { log.Printf("failed to serialize trace: %v", err) return } } type linkedUtilWindow struct { trace.UtilWindow URL string } func newLinkedUtilWindow(ui trace.UtilWindow, window time.Duration) linkedUtilWindow { // Find the range containing this window. var r Range for _, r = range ranges { if r.EndTime > ui.Time { break } } return linkedUtilWindow{ui, fmt.Sprintf("%s#%v:%v", r.URL(), float64(ui.Time)/1e6, float64(ui.Time+int64(window))/1e6)} }