Home | History | Annotate | Download | only in tracer
      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
     20 package tracer
     21 
     22 import (
     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 
     36 type Thread uint64
     37 
     38 const (
     39 	MainThread     = Thread(iota)
     40 	MaxInitThreads = Thread(iota)
     41 )
     42 
     43 type Tracer interface {
     44 	Begin(name string, thread Thread)
     45 	End(thread Thread)
     46 	Complete(name string, thread Thread, begin, end uint64)
     47 
     48 	ImportMicrofactoryLog(filename string)
     49 	ImportNinjaLog(thread Thread, filename string, startOffset time.Time)
     50 
     51 	NewThread(name string) Thread
     52 }
     53 
     54 type 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 
     66 var _ Tracer = &tracerImpl{}
     67 
     68 type 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 
     80 type nameArg struct {
     81 	Name string `json:"name"`
     82 }
     83 
     84 type nopCloser struct{ io.Writer }
     85 
     86 func (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.
     90 func 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 
    102 func (t *tracerImpl) startBuffer() {
    103 	t.w = nopCloser{&t.buf}
    104 	fmt.Fprintln(t.w, "[")
    105 
    106 	t.defineThread(MainThread, "main")
    107 }
    108 
    109 func (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).
    126 func (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.
    156 func (t *tracerImpl) Close() {
    157 	t.lock.Lock()
    158 	defer t.lock.Unlock()
    159 
    160 	t.close()
    161 }
    162 
    163 func (t *tracerImpl) writeEvent(event *viewerEvent) {
    164 	t.lock.Lock()
    165 	defer t.lock.Unlock()
    166 
    167 	t.writeEventLocked(event)
    168 }
    169 
    170 func (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 
    189 func (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.
    203 func (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.
    216 func (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.
    227 func (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.
    238 func (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 }
    248