xref: /aosp_15_r20/build/soong/ui/tracer/tracer.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// This package implements a trace file writer, whose files can be opened in
16// chrome://tracing.
17//
18// It implements the JSON Array Format defined here:
19// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit
20package tracer
21
22import (
23	"bytes"
24	"compress/gzip"
25	"encoding/json"
26	"fmt"
27	"io"
28	"os"
29	"strings"
30	"sync"
31	"time"
32
33	"android/soong/ui/logger"
34	"android/soong/ui/status"
35)
36
37type Thread uint64
38
39const (
40	MainThread     = Thread(iota)
41	MaxInitThreads = Thread(iota)
42)
43
44type Tracer interface {
45	Begin(name string, thread Thread)
46	End(thread Thread)
47	Complete(name string, thread Thread, begin, end uint64)
48
49	CountersAtTime(name string, thread Thread, time uint64, counters []Counter)
50
51	ImportMicrofactoryLog(filename string)
52
53	StatusTracer() status.StatusOutput
54
55	NewThread(name string) Thread
56}
57
58type tracerImpl struct {
59	lock sync.Mutex
60	log  logger.Logger
61
62	buf  bytes.Buffer
63	file *os.File
64	w    io.WriteCloser
65
66	firstEvent bool
67	nextTid    uint64
68}
69
70var _ Tracer = &tracerImpl{}
71
72type viewerEvent struct {
73	Name  string      `json:"name,omitempty"`
74	Phase string      `json:"ph"`
75	Scope string      `json:"s,omitempty"`
76	Time  uint64      `json:"ts"`
77	Dur   uint64      `json:"dur,omitempty"`
78	Pid   uint64      `json:"pid"`
79	Tid   uint64      `json:"tid"`
80	ID    uint64      `json:"id,omitempty"`
81	Arg   interface{} `json:"args,omitempty"`
82}
83
84type nameArg struct {
85	Name string `json:"name"`
86}
87
88type nopCloser struct{ io.Writer }
89
90func (nopCloser) Close() error { return nil }
91
92// New creates a new Tracer, storing log in order to log errors later.
93// Events are buffered in memory until SetOutput is called.
94func New(log logger.Logger) *tracerImpl {
95	ret := &tracerImpl{
96		log: log,
97
98		firstEvent: true,
99		nextTid:    uint64(MaxInitThreads),
100	}
101	ret.startBuffer()
102
103	return ret
104}
105
106func (t *tracerImpl) startBuffer() {
107	t.w = nopCloser{&t.buf}
108	fmt.Fprintln(t.w, "[")
109
110	t.defineThread(MainThread, "main")
111}
112
113func (t *tracerImpl) close() {
114	if t.file != nil {
115		fmt.Fprintln(t.w, "]")
116
117		if err := t.w.Close(); err != nil {
118			t.log.Println("Error closing trace writer:", err)
119		}
120
121		if err := t.file.Close(); err != nil {
122			t.log.Println("Error closing trace file:", err)
123		}
124		t.file = nil
125		t.startBuffer()
126	}
127}
128
129// SetOutput creates the output file (rotating old files).
130func (t *tracerImpl) SetOutput(filename string) {
131	t.lock.Lock()
132	defer t.lock.Unlock()
133
134	t.close()
135
136	// chrome://tracing requires that compressed trace files end in .gz
137	if !strings.HasSuffix(filename, ".gz") {
138		filename += ".gz"
139	}
140
141	f, err := logger.CreateFileWithRotation(filename, 5)
142	if err != nil {
143		t.log.Println("Failed to create trace file:", err)
144		return
145	}
146	// Save the file, since closing the gzip Writer doesn't close the
147	// underlying file.
148	t.file = f
149	t.w = gzip.NewWriter(f)
150
151	// Write out everything that happened since the start
152	if _, err := io.Copy(t.w, &t.buf); err != nil {
153		t.log.Println("Failed to write trace buffer to file:", err)
154	}
155	t.buf = bytes.Buffer{}
156}
157
158// Close closes the output file. Any future events will be buffered until the
159// next call to SetOutput.
160func (t *tracerImpl) Close() {
161	t.lock.Lock()
162	defer t.lock.Unlock()
163
164	t.close()
165}
166
167func (t *tracerImpl) writeEvent(event *viewerEvent) {
168	t.lock.Lock()
169	defer t.lock.Unlock()
170
171	t.writeEventLocked(event)
172}
173
174func (t *tracerImpl) writeEventLocked(event *viewerEvent) {
175	bytes, err := json.Marshal(event)
176	if err != nil {
177		t.log.Println("Failed to marshal event:", err)
178		t.log.Verbosef("Event: %#v", event)
179		return
180	}
181
182	if !t.firstEvent {
183		fmt.Fprintln(t.w, ",")
184	} else {
185		t.firstEvent = false
186	}
187
188	if _, err = t.w.Write(bytes); err != nil {
189		t.log.Println("Trace write error:", err)
190	}
191}
192
193func (t *tracerImpl) defineThread(thread Thread, name string) {
194	t.writeEventLocked(&viewerEvent{
195		Name:  "thread_name",
196		Phase: "M",
197		Pid:   0,
198		Tid:   uint64(thread),
199		Arg: &nameArg{
200			Name: name,
201		},
202	})
203}
204
205// NewThread returns a new Thread with an unused tid, writing the name out to
206// the trace file.
207func (t *tracerImpl) NewThread(name string) Thread {
208	t.lock.Lock()
209	defer t.lock.Unlock()
210
211	ret := Thread(t.nextTid)
212	t.nextTid += 1
213
214	t.defineThread(ret, name)
215	return ret
216}
217
218// Begin starts a new Duration Event. More than one Duration Event may be active
219// at a time on each Thread, but they're nested.
220func (t *tracerImpl) Begin(name string, thread Thread) {
221	t.writeEvent(&viewerEvent{
222		Name:  name,
223		Phase: "B",
224		Time:  uint64(time.Now().UnixNano()) / 1000,
225		Pid:   0,
226		Tid:   uint64(thread),
227	})
228}
229
230// End finishes the most recent active Duration Event on the thread.
231func (t *tracerImpl) End(thread Thread) {
232	t.writeEvent(&viewerEvent{
233		Phase: "E",
234		Time:  uint64(time.Now().UnixNano()) / 1000,
235		Pid:   0,
236		Tid:   uint64(thread),
237	})
238}
239
240// Complete writes a Complete Event, which are like Duration Events, but include
241// a begin and end timestamp in the same event.
242func (t *tracerImpl) Complete(name string, thread Thread, begin, end uint64) {
243	t.writeEvent(&viewerEvent{
244		Name:  name,
245		Phase: "X",
246		Time:  begin / 1000,
247		Dur:   (end - begin) / 1000,
248		Pid:   0,
249		Tid:   uint64(thread),
250	})
251}
252
253type Counter struct {
254	Name  string
255	Value int64
256}
257
258type countersMarshaller []Counter
259
260var _ json.Marshaler = countersMarshaller(nil)
261
262func (counters countersMarshaller) MarshalJSON() ([]byte, error) {
263	// This produces similar output to a map[string]int64, but maintains the order of the slice.
264	buf := bytes.Buffer{}
265	buf.WriteRune('{')
266	for i, counter := range counters {
267		name, err := json.Marshal(counter.Name)
268		if err != nil {
269			return nil, err
270		}
271		buf.Write(name)
272		buf.WriteByte(':')
273		value, err := json.Marshal(counter.Value)
274		if err != nil {
275			return nil, err
276		}
277		buf.Write(value)
278		if i != len(counters)-1 {
279			buf.WriteRune(',')
280		}
281	}
282	buf.WriteRune('}')
283	return buf.Bytes(), nil
284}
285
286// CountersAtTime writes a Counter event at the given timestamp in nanoseconds.
287func (t *tracerImpl) CountersAtTime(name string, thread Thread, time uint64, counters []Counter) {
288	t.writeEvent(&viewerEvent{
289		Name:  name,
290		Phase: "C",
291		Time:  time / 1000,
292		Pid:   0,
293		Tid:   uint64(thread),
294		Arg:   countersMarshaller(counters),
295	})
296}
297