Skip to content

Commit c785be4

Browse files
committed
cmd/trace: common up the mmu page and add it to cmd/trace/v2
This change moves the MMU HTTP handlers and functionality into the traceviewer package, since unlike the goroutine pages the vast majority of that functionality is identical between v1 and v2. This change involves some refactoring so that callers can plug in their own mutator utilization computation functions (which is the only point of difference between v1 and v2). The new interface isn't especially nice, but part of the problem is the MMU handlers depend on specific endpoints to exist. A follow-up CL will clean this up a bit. Like the previous CL did for goroutine analysis, modify the v2 mutator utilization API to accept a slice of trace events. Again, we might as well reuse what was already parsed and will be needed for other purposes. It also simplifies the API slightly. For #60773. For #63960. Change-Id: I6c21ec8d1bf7e95eff5363d0e0005c9217fa00e7 Reviewed-on: https://go-review.googlesource.com/c/go/+/541258 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Auto-Submit: Michael Knyszek <[email protected]>
1 parent 48a6362 commit c785be4

File tree

5 files changed

+100
-75
lines changed

5 files changed

+100
-75
lines changed

src/cmd/trace/main.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,13 @@ func main() {
139139
log.Printf("Opening browser. Trace viewer is listening on %s", addr)
140140
browser.Open(addr)
141141

142-
// Start http server.
142+
// Install MMU handlers.
143+
traceviewer.InstallMMUHandlers(http.DefaultServeMux, ranges, mutatorUtil)
144+
145+
// Install main handler.
143146
http.Handle("/", traceviewer.MainHandler(ranges))
147+
148+
// Start http server.
144149
err = http.Serve(ln, nil)
145150
dief("failed to start http server: %v\n", err)
146151
}
@@ -228,3 +233,11 @@ func reportMemoryUsage(msg string) {
228233
fmt.Printf("Enter to continue...")
229234
fmt.Scanf("%s", &dummy)
230235
}
236+
237+
func mutatorUtil(flags trace.UtilFlags) ([][]trace.MutatorUtil, error) {
238+
events, err := parseEvents()
239+
if err != nil {
240+
return nil, err
241+
}
242+
return trace.MutatorUtilization(events, flags), nil
243+
}

src/cmd/trace/v2/main.go

+6
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ func Main(traceFile, httpAddr, pprof string, debug int) error {
7070
mux.HandleFunc("/goroutines", GoroutinesHandlerFunc(gSummaries))
7171
mux.HandleFunc("/goroutine", GoroutineHandler(gSummaries))
7272

73+
// Install MMU handlers.
74+
mutatorUtil := func(flags trace.UtilFlags) ([][]trace.MutatorUtil, error) {
75+
return trace.MutatorUtilizationV2(parsed.events, flags), nil
76+
}
77+
traceviewer.InstallMMUHandlers(mux, ranges, mutatorUtil)
78+
7379
err = http.Serve(ln, mux)
7480
return fmt.Errorf("failed to start http server: %w", err)
7581
}

src/internal/trace/gc.go

+7-21
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package trace
77
import (
88
"container/heap"
99
tracev2 "internal/trace/v2"
10-
"io"
1110
"math"
1211
"sort"
1312
"strings"
@@ -212,13 +211,7 @@ func MutatorUtilization(events []*Event, flags UtilFlags) [][]MutatorUtil {
212211
//
213212
// If the UtilPerProc flag is not given, this always returns a single
214213
// utilization function. Otherwise, it returns one function per P.
215-
func MutatorUtilizationV2(trace io.Reader, flags UtilFlags) ([][]MutatorUtil, error) {
216-
// Create a reader.
217-
r, err := tracev2.NewReader(trace)
218-
if err != nil {
219-
return nil, err
220-
}
221-
214+
func MutatorUtilizationV2(events []tracev2.Event, flags UtilFlags) [][]MutatorUtil {
222215
// Set up a bunch of analysis state.
223216
type perP struct {
224217
// gc > 0 indicates that GC is active on this P.
@@ -255,16 +248,9 @@ func MutatorUtilizationV2(trace io.Reader, flags UtilFlags) ([][]MutatorUtil, er
255248
}
256249

257250
// Iterate through the trace, tracking mutator utilization.
258-
var lastEv tracev2.Event
259-
for {
260-
// Read a single event.
261-
ev, err := r.ReadEvent()
262-
if err == io.EOF {
263-
break
264-
}
265-
if err != nil {
266-
return nil, err
267-
}
251+
var lastEv *tracev2.Event
252+
for i := range events {
253+
ev := &events[i]
268254
lastEv = ev
269255

270256
// Process the event.
@@ -451,8 +437,8 @@ func MutatorUtilizationV2(trace io.Reader, flags UtilFlags) ([][]MutatorUtil, er
451437
}
452438

453439
// No events in the stream.
454-
if lastEv.Kind() == tracev2.EventBad {
455-
return nil, nil
440+
if lastEv == nil {
441+
return nil
456442
}
457443

458444
// Add final 0 utilization event to any remaining series. This
@@ -463,7 +449,7 @@ func MutatorUtilizationV2(trace io.Reader, flags UtilFlags) ([][]MutatorUtil, er
463449
for i := range ps {
464450
out[ps[i].series] = addUtil(out[ps[i].series], mu)
465451
}
466-
return out, nil
452+
return out
467453
}
468454

469455
func addUtil(util []MutatorUtil, mu MutatorUtil) []MutatorUtil {

src/internal/trace/gc_test.go

+18-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ package trace
66

77
import (
88
"bytes"
9+
"internal/trace/v2"
10+
tracev2 "internal/trace/v2"
911
"internal/trace/v2/testtrace"
12+
"io"
1013
"math"
1114
"os"
1215
"testing"
@@ -133,12 +136,23 @@ func TestMMUTrace(t *testing.T) {
133136
if err != nil {
134137
t.Fatalf("malformed test %s: bad trace file: %v", testPath, err)
135138
}
136-
// Pass the trace through MutatorUtilizationV2.
137-
mu, err := MutatorUtilizationV2(r, UtilSTW|UtilBackground|UtilAssist)
139+
var events []tracev2.Event
140+
tr, err := trace.NewReader(r)
138141
if err != nil {
139-
t.Fatalf("failed to compute mutator utilization or parse trace: %v", err)
142+
t.Fatalf("malformed test %s: bad trace file: %v", testPath, err)
143+
}
144+
for {
145+
ev, err := tr.ReadEvent()
146+
if err == io.EOF {
147+
break
148+
}
149+
if err != nil {
150+
t.Fatalf("malformed test %s: bad trace file: %v", testPath, err)
151+
}
152+
events = append(events, ev)
140153
}
141-
check(t, mu)
154+
// Pass the trace through MutatorUtilizationV2 and check it.
155+
check(t, MutatorUtilizationV2(events, UtilSTW|UtilBackground|UtilAssist))
142156
})
143157
}
144158

src/cmd/trace/mmu.go renamed to src/internal/trace/traceviewer/mmu.go

+55-49
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2017 The Go Authors. All rights reserved.
1+
// Copyright 2023 The Go Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

@@ -23,13 +23,12 @@
2323
// could potentially put confidence intervals on these estimates and
2424
// render this progressively as we refine the distributions.
2525

26-
package main
26+
package traceviewer
2727

2828
import (
2929
"encoding/json"
3030
"fmt"
3131
"internal/trace"
32-
"internal/trace/traceviewer"
3332
"log"
3433
"math"
3534
"net/http"
@@ -39,10 +38,21 @@ import (
3938
"time"
4039
)
4140

42-
func init() {
43-
http.HandleFunc("/mmu", httpMMU)
44-
http.HandleFunc("/mmuPlot", httpMMUPlot)
45-
http.HandleFunc("/mmuDetails", httpMMUDetails)
41+
type MutatorUtilFunc func(trace.UtilFlags) ([][]trace.MutatorUtil, error)
42+
43+
func InstallMMUHandlers(mux *http.ServeMux, ranges []Range, f MutatorUtilFunc) {
44+
mmu := &mmu{
45+
cache: make(map[trace.UtilFlags]*mmuCacheEntry),
46+
f: f,
47+
ranges: ranges,
48+
}
49+
mux.HandleFunc("/mmu", func(w http.ResponseWriter, r *http.Request) {
50+
// N.B. templMMU has Javascript that implicitly relies upon the existence
51+
// of /mmuPlot and /mmuDetails on the same server.
52+
http.ServeContent(w, r, "", time.Time{}, strings.NewReader(templMMU))
53+
})
54+
mux.HandleFunc("/mmuPlot", mmu.HandlePlot)
55+
mux.HandleFunc("/mmuDetails", mmu.HandleDetails)
4656
}
4757

4858
var utilFlagNames = map[string]trace.UtilFlags{
@@ -53,58 +63,54 @@ var utilFlagNames = map[string]trace.UtilFlags{
5363
"sweep": trace.UtilSweep,
5464
}
5565

66+
func requestUtilFlags(r *http.Request) trace.UtilFlags {
67+
var flags trace.UtilFlags
68+
for _, flagStr := range strings.Split(r.FormValue("flags"), "|") {
69+
flags |= utilFlagNames[flagStr]
70+
}
71+
return flags
72+
}
73+
5674
type mmuCacheEntry struct {
5775
init sync.Once
5876
util [][]trace.MutatorUtil
5977
mmuCurve *trace.MMUCurve
6078
err error
6179
}
6280

63-
var mmuCache struct {
64-
m map[trace.UtilFlags]*mmuCacheEntry
65-
lock sync.Mutex
81+
type mmu struct {
82+
mu sync.Mutex
83+
cache map[trace.UtilFlags]*mmuCacheEntry
84+
f MutatorUtilFunc
85+
ranges []Range
6686
}
6787

68-
func init() {
69-
mmuCache.m = make(map[trace.UtilFlags]*mmuCacheEntry)
70-
}
71-
72-
func getMMUCurve(r *http.Request) ([][]trace.MutatorUtil, *trace.MMUCurve, error) {
73-
var flags trace.UtilFlags
74-
for _, flagStr := range strings.Split(r.FormValue("flags"), "|") {
75-
flags |= utilFlagNames[flagStr]
88+
func (m *mmu) get(flags trace.UtilFlags) ([][]trace.MutatorUtil, *trace.MMUCurve, error) {
89+
m.mu.Lock()
90+
entry := m.cache[flags]
91+
if entry == nil {
92+
entry = new(mmuCacheEntry)
93+
m.cache[flags] = entry
7694
}
95+
m.mu.Unlock()
7796

78-
mmuCache.lock.Lock()
79-
c := mmuCache.m[flags]
80-
if c == nil {
81-
c = new(mmuCacheEntry)
82-
mmuCache.m[flags] = c
83-
}
84-
mmuCache.lock.Unlock()
85-
86-
c.init.Do(func() {
87-
events, err := parseEvents()
97+
entry.init.Do(func() {
98+
util, err := m.f(flags)
8899
if err != nil {
89-
c.err = err
100+
entry.err = err
90101
} else {
91-
c.util = trace.MutatorUtilization(events, flags)
92-
c.mmuCurve = trace.NewMMUCurve(c.util)
102+
entry.util = util
103+
entry.mmuCurve = trace.NewMMUCurve(util)
93104
}
94105
})
95-
return c.util, c.mmuCurve, c.err
96-
}
97-
98-
// httpMMU serves the MMU plot page.
99-
func httpMMU(w http.ResponseWriter, r *http.Request) {
100-
http.ServeContent(w, r, "", time.Time{}, strings.NewReader(templMMU))
106+
return entry.util, entry.mmuCurve, entry.err
101107
}
102108

103-
// httpMMUPlot serves the JSON data for the MMU plot.
104-
func httpMMUPlot(w http.ResponseWriter, r *http.Request) {
105-
mu, mmuCurve, err := getMMUCurve(r)
109+
// HandlePlot serves the JSON data for the MMU plot.
110+
func (m *mmu) HandlePlot(w http.ResponseWriter, r *http.Request) {
111+
mu, mmuCurve, err := m.get(requestUtilFlags(r))
106112
if err != nil {
107-
http.Error(w, fmt.Sprintf("failed to parse events: %v", err), http.StatusInternalServerError)
113+
http.Error(w, fmt.Sprintf("failed to produce MMU data: %v", err), http.StatusInternalServerError)
108114
return
109115
}
110116

@@ -358,11 +364,11 @@ var templMMU = `<!doctype html>
358364
</html>
359365
`
360366

361-
// httpMMUDetails serves details of an MMU graph at a particular window.
362-
func httpMMUDetails(w http.ResponseWriter, r *http.Request) {
363-
_, mmuCurve, err := getMMUCurve(r)
367+
// HandleDetails serves details of an MMU graph at a particular window.
368+
func (m *mmu) HandleDetails(w http.ResponseWriter, r *http.Request) {
369+
_, mmuCurve, err := m.get(requestUtilFlags(r))
364370
if err != nil {
365-
http.Error(w, fmt.Sprintf("failed to parse events: %v", err), http.StatusInternalServerError)
371+
http.Error(w, fmt.Sprintf("failed to produce MMU data: %v", err), http.StatusInternalServerError)
366372
return
367373
}
368374

@@ -377,7 +383,7 @@ func httpMMUDetails(w http.ResponseWriter, r *http.Request) {
377383
// Construct a link for each window.
378384
var links []linkedUtilWindow
379385
for _, ui := range worst {
380-
links = append(links, newLinkedUtilWindow(ui, time.Duration(window)))
386+
links = append(links, m.newLinkedUtilWindow(ui, time.Duration(window)))
381387
}
382388

383389
err = json.NewEncoder(w).Encode(links)
@@ -392,10 +398,10 @@ type linkedUtilWindow struct {
392398
URL string
393399
}
394400

395-
func newLinkedUtilWindow(ui trace.UtilWindow, window time.Duration) linkedUtilWindow {
401+
func (m *mmu) newLinkedUtilWindow(ui trace.UtilWindow, window time.Duration) linkedUtilWindow {
396402
// Find the range containing this window.
397-
var r traceviewer.Range
398-
for _, r = range ranges {
403+
var r Range
404+
for _, r = range m.ranges {
399405
if r.EndTime > ui.Time {
400406
break
401407
}

0 commit comments

Comments
 (0)