blob: 33b3d89429f0c0c4148b3fa9c952252049b66fbf [file] [log] [blame]
Dan Willemsend9f6fa22016-08-21 15:17:17 -07001// 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"
Dan Willemsenb82471a2018-05-17 16:37:09 -070034 "android/soong/ui/status"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070035)
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
Colin Cross46b0c752023-10-27 14:56:12 -070049 CountersAtTime(name string, thread Thread, time uint64, counters []Counter)
50
Dan Willemsencae59bc2017-07-13 14:27:31 -070051 ImportMicrofactoryLog(filename string)
Dan Willemsenb82471a2018-05-17 16:37:09 -070052
53 StatusTracer() status.StatusOutput
Dan Willemsendb8457c2017-05-12 16:38:17 -070054
55 NewThread(name string) Thread
Dan Willemsend9f6fa22016-08-21 15:17:17 -070056}
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}
Colin Cross46b0c752023-10-27 14:56:12 -0700252
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}