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