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