Source file
src/cmd/trace/jsontrace_test.go
1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 "encoding/json"
10 "internal/trace"
11 "io"
12 "net/http/httptest"
13 "os"
14 "path/filepath"
15 "slices"
16 "strconv"
17 "strings"
18 "testing"
19 "time"
20
21 "internal/trace/raw"
22 "internal/trace/traceviewer/format"
23 )
24
25 func TestJSONTraceHandler(t *testing.T) {
26 testPaths, err := filepath.Glob("./testdata/*.test")
27 if err != nil {
28 t.Fatalf("discovering tests: %v", err)
29 }
30 for _, testPath := range testPaths {
31 t.Run(filepath.Base(testPath), func(t *testing.T) {
32 parsed := getTestTrace(t, testPath)
33 data := recordJSONTraceHandlerResponse(t, parsed)
34
35 checkExecutionTimes(t, data)
36 checkPlausibleHeapMetrics(t, data)
37
38 checkMetaNamesEmitted(t, data, "process_name", []string{"STATS", "PROCS"})
39 checkMetaNamesEmitted(t, data, "thread_name", []string{"GC", "Network", "Timers", "Syscalls", "Proc 0"})
40 checkProcStartStop(t, data)
41 checkSyscalls(t, data)
42 checkNetworkUnblock(t, data)
43
44 })
45 }
46 }
47
48 func checkSyscalls(t *testing.T, data format.Data) {
49 data = filterViewerTrace(data,
50 filterEventName("syscall"),
51 filterStackRootFunc("main.blockingSyscall"))
52 if len(data.Events) <= 1 {
53 t.Errorf("got %d events, want > 1", len(data.Events))
54 }
55 data = filterViewerTrace(data, filterBlocked("yes"))
56 if len(data.Events) != 1 {
57 t.Errorf("got %d events, want 1", len(data.Events))
58 }
59 }
60
61 type eventFilterFn func(*format.Event, *format.Data) bool
62
63 func filterEventName(name string) eventFilterFn {
64 return func(e *format.Event, _ *format.Data) bool {
65 return e.Name == name
66 }
67 }
68
69
70
71 func filterGoRoutineName(name string) eventFilterFn {
72 return func(e *format.Event, _ *format.Data) bool {
73 return parseGoroutineName(e) == name
74 }
75 }
76
77
78
79 func parseGoroutineName(e *format.Event) string {
80 parts := strings.SplitN(e.Name, " ", 2)
81 if len(parts) != 2 || !strings.HasPrefix(parts[0], "G") {
82 return ""
83 }
84 return parts[1]
85 }
86
87
88
89 func filterBlocked(blocked string) eventFilterFn {
90 return func(e *format.Event, _ *format.Data) bool {
91 m, ok := e.Arg.(map[string]any)
92 if !ok {
93 return false
94 }
95 return m["blocked"] == blocked
96 }
97 }
98
99
100
101 func filterStackRootFunc(name string) eventFilterFn {
102 return func(e *format.Event, data *format.Data) bool {
103 frames := stackFrames(data, e.Stack)
104 rootFrame := frames[len(frames)-1]
105 return strings.HasPrefix(rootFrame, name+":")
106 }
107 }
108
109
110
111 func filterViewerTrace(data format.Data, fns ...eventFilterFn) (filtered format.Data) {
112 filtered = data
113 filtered.Events = nil
114 for _, e := range data.Events {
115 keep := true
116 for _, fn := range fns {
117 keep = keep && fn(e, &filtered)
118 }
119 if keep {
120 filtered.Events = append(filtered.Events, e)
121 }
122 }
123 return
124 }
125
126 func stackFrames(data *format.Data, stackID int) (frames []string) {
127 for {
128 frame, ok := data.Frames[strconv.Itoa(stackID)]
129 if !ok {
130 return
131 }
132 frames = append(frames, frame.Name)
133 stackID = frame.Parent
134 }
135 }
136
137 func checkProcStartStop(t *testing.T, data format.Data) {
138 procStarted := map[uint64]bool{}
139 for _, e := range data.Events {
140 if e.Name == "proc start" {
141 if procStarted[e.TID] == true {
142 t.Errorf("proc started twice: %d", e.TID)
143 }
144 procStarted[e.TID] = true
145 }
146 if e.Name == "proc stop" {
147 if procStarted[e.TID] == false {
148 t.Errorf("proc stopped twice: %d", e.TID)
149 }
150 procStarted[e.TID] = false
151 }
152 }
153 if got, want := len(procStarted), 8; got != want {
154 t.Errorf("wrong number of procs started/stopped got=%d want=%d", got, want)
155 }
156 }
157
158 func checkNetworkUnblock(t *testing.T, data format.Data) {
159 count := 0
160 var netBlockEv *format.Event
161 for _, e := range data.Events {
162 if e.TID == trace.NetpollP && e.Name == "unblock (network)" && e.Phase == "I" && e.Scope == "t" {
163 count++
164 netBlockEv = e
165 }
166 }
167 if netBlockEv == nil {
168 t.Error("failed to find a network unblock")
169 }
170 if count == 0 {
171 t.Errorf("found zero network block events, want at least one")
172 }
173
174 }
175
176 func checkExecutionTimes(t *testing.T, data format.Data) {
177 cpu10 := sumExecutionTime(filterViewerTrace(data, filterGoRoutineName("main.cpu10")))
178 cpu20 := sumExecutionTime(filterViewerTrace(data, filterGoRoutineName("main.cpu20")))
179 if cpu10 <= 0 || cpu20 <= 0 || cpu10 >= cpu20 {
180 t.Errorf("bad execution times: cpu10=%v, cpu20=%v", cpu10, cpu20)
181 }
182 }
183
184 func checkMetaNamesEmitted(t *testing.T, data format.Data, category string, want []string) {
185 t.Helper()
186 names := metaEventNameArgs(category, data)
187 for _, wantName := range want {
188 if !slices.Contains(names, wantName) {
189 t.Errorf("%s: names=%v, want %q", category, names, wantName)
190 }
191 }
192 }
193
194 func metaEventNameArgs(category string, data format.Data) (names []string) {
195 for _, e := range data.Events {
196 if e.Name == category && e.Phase == "M" {
197 names = append(names, e.Arg.(map[string]any)["name"].(string))
198 }
199 }
200 return
201 }
202
203 func checkPlausibleHeapMetrics(t *testing.T, data format.Data) {
204 hms := heapMetrics(data)
205 var nonZeroAllocated, nonZeroNextGC bool
206 for _, hm := range hms {
207 if hm.Allocated > 0 {
208 nonZeroAllocated = true
209 }
210 if hm.NextGC > 0 {
211 nonZeroNextGC = true
212 }
213 }
214
215 if !nonZeroAllocated {
216 t.Errorf("nonZeroAllocated=%v, want true", nonZeroAllocated)
217 }
218 if !nonZeroNextGC {
219 t.Errorf("nonZeroNextGC=%v, want true", nonZeroNextGC)
220 }
221 }
222
223 func heapMetrics(data format.Data) (metrics []format.HeapCountersArg) {
224 for _, e := range data.Events {
225 if e.Phase == "C" && e.Name == "Heap" {
226 j, _ := json.Marshal(e.Arg)
227 var metric format.HeapCountersArg
228 json.Unmarshal(j, &metric)
229 metrics = append(metrics, metric)
230 }
231 }
232 return
233 }
234
235 func recordJSONTraceHandlerResponse(t *testing.T, parsed *parsedTrace) format.Data {
236 h := JSONTraceHandler(parsed)
237 recorder := httptest.NewRecorder()
238 r := httptest.NewRequest("GET", "/jsontrace", nil)
239 h.ServeHTTP(recorder, r)
240
241 var data format.Data
242 if err := json.Unmarshal(recorder.Body.Bytes(), &data); err != nil {
243 t.Fatal(err)
244 }
245 return data
246 }
247
248 func sumExecutionTime(data format.Data) (sum time.Duration) {
249 for _, e := range data.Events {
250 sum += time.Duration(e.Dur) * time.Microsecond
251 }
252 return
253 }
254
255 func getTestTrace(t *testing.T, testPath string) *parsedTrace {
256 t.Helper()
257
258
259 f, err := os.Open(testPath)
260 if err != nil {
261 t.Fatalf("failed to open test %s: %v", testPath, err)
262 }
263 r, err := raw.NewTextReader(f)
264 if err != nil {
265 t.Fatalf("failed to read test %s: %v", testPath, err)
266 }
267 var trace bytes.Buffer
268 w, err := raw.NewWriter(&trace, r.Version())
269 if err != nil {
270 t.Fatalf("failed to write out test %s: %v", testPath, err)
271 }
272 for {
273 ev, err := r.ReadEvent()
274 if err == io.EOF {
275 break
276 }
277 if err != nil {
278 t.Fatalf("failed to read test %s: %v", testPath, err)
279 }
280 if err := w.WriteEvent(ev); err != nil {
281 t.Fatalf("failed to write out test %s: %v", testPath, err)
282 }
283 }
284
285
286 parsed, err := parseTrace(&trace, int64(trace.Len()))
287 if err != nil {
288 t.Fatalf("failed to parse trace: %v", err)
289 }
290 return parsed
291 }
292
View as plain text