blob: 87050403cb3b4f01604080e6bc2d569a64b129ff [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"
34)
35
36type Thread uint64
37
38const (
39 MainThread = Thread(iota)
40 MaxInitThreads = Thread(iota)
41)
42
43type Tracer interface {
44 Begin(name string, thread Thread)
45 End(thread Thread)
46 Complete(name string, thread Thread, begin, end uint64)
47
Dan Willemsencae59bc2017-07-13 14:27:31 -070048 ImportMicrofactoryLog(filename string)
Dan Willemsend9f6fa22016-08-21 15:17:17 -070049 ImportNinjaLog(thread Thread, filename string, startOffset time.Time)
Dan Willemsendb8457c2017-05-12 16:38:17 -070050
51 NewThread(name string) Thread
Dan Willemsend9f6fa22016-08-21 15:17:17 -070052}
53
54type tracerImpl struct {
55 lock sync.Mutex
56 log logger.Logger
57
58 buf bytes.Buffer
59 file *os.File
60 w io.WriteCloser
61
62 firstEvent bool
63 nextTid uint64
64}
65
66var _ Tracer = &tracerImpl{}
67
68type viewerEvent struct {
69 Name string `json:"name,omitempty"`
70 Phase string `json:"ph"`
71 Scope string `json:"s,omitempty"`
72 Time uint64 `json:"ts"`
73 Dur uint64 `json:"dur,omitempty"`
74 Pid uint64 `json:"pid"`
75 Tid uint64 `json:"tid"`
76 ID uint64 `json:"id,omitempty"`
77 Arg interface{} `json:"args,omitempty"`
78}
79
80type nameArg struct {
81 Name string `json:"name"`
82}
83
84type nopCloser struct{ io.Writer }
85
86func (nopCloser) Close() error { return nil }
87
88// New creates a new Tracer, storing log in order to log errors later.
89// Events are buffered in memory until SetOutput is called.
90func New(log logger.Logger) *tracerImpl {
91 ret := &tracerImpl{
92 log: log,
93
94 firstEvent: true,
95 nextTid: uint64(MaxInitThreads),
96 }
97 ret.startBuffer()
98
99 return ret
100}
101
102func (t *tracerImpl) startBuffer() {
103 t.w = nopCloser{&t.buf}
104 fmt.Fprintln(t.w, "[")
105
106 t.defineThread(MainThread, "main")
107}
108
109func (t *tracerImpl) close() {
110 if t.file != nil {
111 fmt.Fprintln(t.w, "]")
112
113 if err := t.w.Close(); err != nil {
114 t.log.Println("Error closing trace writer:", err)
115 }
116
117 if err := t.file.Close(); err != nil {
118 t.log.Println("Error closing trace file:", err)
119 }
120 t.file = nil
121 t.startBuffer()
122 }
123}
124
125// SetOutput creates the output file (rotating old files).
126func (t *tracerImpl) SetOutput(filename string) {
127 t.lock.Lock()
128 defer t.lock.Unlock()
129
130 t.close()
131
132 // chrome://tracing requires that compressed trace files end in .gz
133 if !strings.HasSuffix(filename, ".gz") {
134 filename += ".gz"
135 }
136
137 f, err := logger.CreateFileWithRotation(filename, 5)
138 if err != nil {
139 t.log.Println("Failed to create trace file:", err)
140 return
141 }
142 // Save the file, since closing the gzip Writer doesn't close the
143 // underlying file.
144 t.file = f
145 t.w = gzip.NewWriter(f)
146
147 // Write out everything that happened since the start
148 if _, err := io.Copy(t.w, &t.buf); err != nil {
149 t.log.Println("Failed to write trace buffer to file:", err)
150 }
151 t.buf = bytes.Buffer{}
152}
153
154// Close closes the output file. Any future events will be buffered until the
155// next call to SetOutput.
156func (t *tracerImpl) Close() {
157 t.lock.Lock()
158 defer t.lock.Unlock()
159
160 t.close()
161}
162
163func (t *tracerImpl) writeEvent(event *viewerEvent) {
164 t.lock.Lock()
165 defer t.lock.Unlock()
166
167 t.writeEventLocked(event)
168}
169
170func (t *tracerImpl) writeEventLocked(event *viewerEvent) {
171 bytes, err := json.Marshal(event)
172 if err != nil {
173 t.log.Println("Failed to marshal event:", err)
174 t.log.Verbosef("Event: %#v", event)
175 return
176 }
177
178 if !t.firstEvent {
179 fmt.Fprintln(t.w, ",")
180 } else {
181 t.firstEvent = false
182 }
183
184 if _, err = t.w.Write(bytes); err != nil {
185 t.log.Println("Trace write error:", err)
186 }
187}
188
189func (t *tracerImpl) defineThread(thread Thread, name string) {
190 t.writeEventLocked(&viewerEvent{
191 Name: "thread_name",
192 Phase: "M",
193 Pid: 0,
194 Tid: uint64(thread),
195 Arg: &nameArg{
196 Name: name,
197 },
198 })
199}
200
201// NewThread returns a new Thread with an unused tid, writing the name out to
202// the trace file.
203func (t *tracerImpl) NewThread(name string) Thread {
204 t.lock.Lock()
205 defer t.lock.Unlock()
206
207 ret := Thread(t.nextTid)
208 t.nextTid += 1
209
210 t.defineThread(ret, name)
211 return ret
212}
213
214// Begin starts a new Duration Event. More than one Duration Event may be active
215// at a time on each Thread, but they're nested.
216func (t *tracerImpl) Begin(name string, thread Thread) {
217 t.writeEvent(&viewerEvent{
218 Name: name,
219 Phase: "B",
220 Time: uint64(time.Now().UnixNano()) / 1000,
221 Pid: 0,
222 Tid: uint64(thread),
223 })
224}
225
226// End finishes the most recent active Duration Event on the thread.
227func (t *tracerImpl) End(thread Thread) {
228 t.writeEvent(&viewerEvent{
229 Phase: "E",
230 Time: uint64(time.Now().UnixNano()) / 1000,
231 Pid: 0,
232 Tid: uint64(thread),
233 })
234}
235
236// Complete writes a Complete Event, which are like Duration Events, but include
237// a begin and end timestamp in the same event.
238func (t *tracerImpl) Complete(name string, thread Thread, begin, end uint64) {
239 t.writeEvent(&viewerEvent{
240 Name: name,
241 Phase: "X",
242 Time: begin / 1000,
243 Dur: (end - begin) / 1000,
244 Pid: 0,
245 Tid: uint64(thread),
246 })
247}